diff --git a/composer/translate-composer.js b/composer/translate-composer.js
index 294a8ae82..ee895948e 100644
--- a/composer/translate-composer.js
+++ b/composer/translate-composer.js
@@ -783,7 +783,7 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
var wheelEventName = typeof window.onwheel !== "undefined" || typeof window.WheelEvent !== "undefined" ?
"wheel" : "mousewheel";
- this._element.addEventListener(wheelEventName, this, false);
+ this._element.addEventListener(wheelEventName, this, {capture: false, passive:false});
this._element.addEventListener(wheelEventName, this, true);
}
}
@@ -1249,6 +1249,22 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
}
},
+ _linearScrollingVector: {
+ value: [-300, 0]
+ },
+
+ /**
+ * A constant 2d vector used to transform a drag vector into a scroll vector
+ */
+ linearScrollingVector: {
+ get: function () {
+ return this._linearScrollingVector;
+ },
+ set: function (value) {
+ this._linearScrollingVector = value;
+ }
+ },
+
handleWheel: {
value: function (event) {
if (!this.enabled) {
@@ -1267,11 +1283,14 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
if (this.axis !== "vertical") {
- this.translateX = this._translateX - ((event.wheelDeltaX || -event.deltaX || 0)* 20) / 120;
+ //this.translateX = this._translateX - ((event.wheelDeltaX || -event.deltaX || 0)* 20) / 120;
+ this.translateX = this._translateX - ((event.deltaX || 0));
+ console.debug("handleWheel: this.translateX = "+this.translateX);
}
if (this.axis !== "horizontal") {
- this.translateY = this._translateY - ((event.wheelDeltaY || -event.deltaY || 0)* 20) / 120;
+ //this.translateY = this._translateY - ((event.wheelDeltaY || -event.deltaY || 0)* 20) / 120;
+ this.translateY = this._translateY - ((event.deltaY || 0));
}
this.isMoving = true;
@@ -1300,6 +1319,119 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
}
},
+ handleWheel_wip_from_flow_translate_composer: {
+ value: function (event) {
+ var self = this;
+
+ // If this composers' component is claiming the "wheel" pointer then handle the event
+ if (this.eventManager.isPointerClaimedByComponent(this._WHEEL_POINTER, this)) {
+ this._observedPointer = this._WHEEL_POINTER;
+
+ var oldScroll = this._scroll,
+ deltaX = event.wheelDeltaX || -event.deltaX || 0,
+ deltaY = event.wheelDeltaY || -event.deltaY || 0,
+ delta;
+
+ if (this.translateStrideX) {
+ clearTimeout(this._mousewheelStrideTimeout);
+ if (Math.abs(this._linearScrollingVector[0]) > Math.abs(this._linearScrollingVector[1])) {
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
+ delta = this._linearScrollingVector[0] * -deltaX / Math.abs(this._linearScrollingVector[0]);
+ } else {
+ delta = 0;
+ }
+ } else {
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
+ delta = 0;
+ } else {
+ delta = this._linearScrollingVector[1] * -deltaY / Math.abs(this._linearScrollingVector[1]);
+ }
+ }
+ if ((this._mousewheelStrideTimeout === null) || (Math.abs(delta) > Math.abs(this._previousDelta * (this._mousewheelStrideTimeout === null ? 2 : 4)))) {
+ if (delta > 1) {
+ this.callDelegateMethod("previousStride", this);
+ } else {
+ if (delta < -1) {
+ this.callDelegateMethod("nextStride", this);
+ }
+ }
+ }
+ this._mousewheelStrideTimeout = setTimeout(function () {
+ self._mousewheelStrideTimeout = null;
+ self._previousDelta = 0;
+ }, 70);
+ self._previousDelta = delta;
+ if (delta !== 0 && this._shouldPreventDefault(event)) {
+ event.preventDefault();
+ }
+ } else {
+ if (this._translateEndTimeout === null) {
+ this._dispatchTranslateStart();
+ }
+ this._pageX = this._pageX + ((deltaX * 20) / 100);
+ this._pageY = this._pageY + ((deltaY * 20) / 100);
+ this._updateScroll();
+ this._dispatchTranslate();
+ clearTimeout(this._translateEndTimeout);
+ this._translateEndTimeout = setTimeout(function () {
+ self._dispatchTranslateEnd();
+ self._translateEndTimeout = null;
+
+ if (self.eventManager.isPointerClaimedByComponent(self._WHEEL_POINTER, self)) {
+ self.eventManager.forfeitPointer(self._WHEEL_POINTER, self);
+ }
+
+ }, 400);
+
+ // If we're not at one of the extremes (i.e. the scroll actually
+ // changed the translate) then we want to preventDefault to stop
+ // the page scrolling.
+ if (oldScroll !== this._scroll && this._shouldPreventDefault(event)) {
+ event.preventDefault();
+ }
+ }
+ // this.eventManager.forfeitPointer(this._WHEEL_POINTER, this.component);
+
+ // If we're not at one of the extremes (i.e. the scroll actually changed the translate)
+ // then we want to preventDefault to stop the page scrolling.
+ // event.preventDefault();
+ }
+ }
+ },
+
+ // TODO doc
+ /**
+ */
+ _updateScroll: {
+ value: function () {
+ this._updateLinearScroll();
+ }
+ },
+
+ _linearScrollRatio: {
+ get: function() {
+ return 1;
+ }
+ },
+
+ // TODO doc
+ /**
+ */
+ _updateLinearScroll: {
+ value: function () {
+ var flow = this._flow,
+ ratio = this._linearScrollRatio,
+ x = /*(*/ (this._pageX - this._startPageX) /* * this._linearScrollingVector[0] * ratio * flow._sceneScaleX.denominator) / flow._sceneScaleX.numerator */,
+ y = /*(*/ (this._pageY - this._startPageY) /* * this._linearScrollingVector[1] * ratio * flow._sceneScaleY.denominator) / flow._sceneScaleY.numerator */,
+ squaredMagnitude = 1 /*this._linearScrollingVector[0] * this._linearScrollingVector[0] + this._linearScrollingVector[1] * this._linearScrollingVector[1]*/,
+ scroll = (x + y) / squaredMagnitude;
+
+ this.scroll += scroll - this._previousScrollDelta;
+ this._previousScrollDelta = scroll;
+ }
+ },
+
+
_bezierTValue: {
value: function (x, p1x, p1y, p2x, p2y) {
var a = 1 - 3 * p2x + 3 * p1x,
@@ -1327,6 +1459,10 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
translateStartEvent.initCustomEvent("translateStart", true, true, null);
translateStartEvent.translateX = x;
translateStartEvent.translateY = y;
+
+ //Is this to workaround the fact that today the target is the translate composer itself:
+ translateStartEvent.targetElement = this.element;
+
// Event needs to be the same shape as the one in flow-translate-composer
translateStartEvent.scroll = 0;
translateStartEvent.pointer = this._observedPointer;
@@ -1341,6 +1477,10 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
translateEndEvent.initCustomEvent("translateEnd", true, true, null);
translateEndEvent.translateX = this._translateX;
translateEndEvent.translateY = this._translateY;
+
+ //Is this to workaround the fact that today the target is the translate composer itself:
+ translateEndEvent.targetElement = this.element;
+
// Event needs to be the same shape as the one in flow-translate-composer
translateEndEvent.scroll = 0;
translateEndEvent.pointer = this._observedPointer;
@@ -1355,6 +1495,10 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
translateCancelEvent.initCustomEvent("translateCancel", true, true, null);
translateCancelEvent.translateX = this._translateX;
translateCancelEvent.translateY = this._translateY;
+
+ //Is this to workaround the fact that today the target is the translate composer itself:
+ translateCancelEvent.targetElement = this.element;
+
// Event needs to be the same shape as the one in flow-translate-composer
translateCancelEvent.scroll = 0;
translateCancelEvent.pointer = this._observedPointer;
@@ -1368,6 +1512,10 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
translateEvent.initCustomEvent("translate", true, true, null);
translateEvent.translateX = this._translateX;
translateEvent.translateY = this._translateY;
+
+ //Is this to workaround the fact that today the target is the translate composer itself:
+ translateEvent.targetElement = this.element;
+
// Event needs to be the same shape as the one in flow-translate-composer
translateEvent.scroll = 0;
translateEvent.pointer = this._observedPointer;
@@ -1389,7 +1537,7 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len
endY: {value: null, enumerable: false},
translateStrideX: {
- value: null
+ value: 0.0001
},
translateStrideY: {
diff --git a/core/event/event-manager.js b/core/event/event-manager.js
index 8c15039fa..5c7b215b9 100644
--- a/core/event/event-manager.js
+++ b/core/event/event-manager.js
@@ -1907,12 +1907,18 @@ var EventManager = exports.EventManager = Montage.specialize(/** @lends EventMan
if(!eventDefinition) {
console.debug("Event type "+eventType+" missed definition");
}
+
+ if(typeof listenerOptions === "object" && eventDefinition) {
+ Object.setPrototypeOf(listenerOptions, eventDefinition);
+ }
+ eventOpts = listenerOptions;
+
- eventOpts = this.isPassiveEventType(eventType)
- ? {passive: true}
- : eventDefinition
- ? eventDefinition.bubbles
- : true; //by default
+ // eventOpts = this.isPassiveEventType(eventType)
+ // ? {passive: true}
+ // : eventDefinition
+ // ? eventDefinition.bubbles
+ // : true; //by default
// eventOpts = {
@@ -1920,6 +1926,7 @@ var EventManager = exports.EventManager = Montage.specialize(/** @lends EventMan
// capture: true
// }
+ eventOpts =
listenerTarget.nativeAddEventListener((eventDefinition ? (eventDefinition.type || eventType) : eventType), this, eventOpts);
}
// console.log("started listening: ", eventType, listenerTarget)
diff --git a/data/model/inspectors/organization.mod/organization.css b/data/model/inspectors/organization.mod/organization.css
index 3a810e71b..1325760eb 100644
--- a/data/model/inspectors/organization.mod/organization.css
+++ b/data/model/inspectors/organization.mod/organization.css
@@ -15,7 +15,6 @@
.roleListItem {
border: 1px solid #eeeeee;
align-content: center;
- padding: 12px 8px;
}
.roleListItem.selected {
background: #d9d9d9;
@@ -25,6 +24,9 @@
height: 100%;
}
+ .delete {
+ border-radius: 0;
+ }
}
diff --git a/data/model/inspectors/organization.mod/organization.html b/data/model/inspectors/organization.mod/organization.html
index d5a7e9567..ecb7e04cd 100644
--- a/data/model/inspectors/organization.mod/organization.html
+++ b/data/model/inspectors/organization.mod/organization.html
@@ -25,11 +25,19 @@
}
},
"roleListItem": {
- "prototype": "../../../../ui/text.mod",
+ "prototype": "../../../../ui/list-item-menu.mod",
"values": {
"element": {"#": "roleListItem"},
- "value": {"<-": "@roleList:iteration.object.name"},
- "object": {"<-": "@roleList:iteration.object"}
+ "data": {"<-": "@roleList:iteration.object.name"},
+ "object": {"<-": "@roleList:iteration.object"},
+ "minDistanceBeforeClose": 1200
+ }
+ },
+ "delete": {
+ "prototype": "../../../../ui/button.mod",
+ "values": {
+ "element": {"#": "delete"},
+ "label": "Delete"
}
}
}
@@ -40,7 +48,11 @@
diff --git a/index.html b/index.html
index b81359a87..cfddc9c48 100644
--- a/index.html
+++ b/index.html
@@ -27,7 +27,7 @@ Composers:
Press Composer
Translate Composer
Swipe Composer
-
+
Components:
Anchor
Button
@@ -40,6 +40,7 @@ Components:
Image Gallery
List
ListItem
+ ListItem
Loader (sample without template)
Loader (sample with template)
NumberField
diff --git a/test/mocks/data/icons/check.mod/check.html b/test/mocks/data/icons/check.mod/check.html
index ec82df1d3..149a90281 100644
--- a/test/mocks/data/icons/check.mod/check.html
+++ b/test/mocks/data/icons/check.mod/check.html
@@ -2,7 +2,7 @@
-
+
@@ -15,6 +22,9 @@
diff --git a/ui/button.mod/button.js b/ui/button.mod/button.js
index 997f2a3de..ea73b6bf6 100644
--- a/ui/button.mod/button.js
+++ b/ui/button.mod/button.js
@@ -152,36 +152,12 @@ const Button = (exports.Button = class Button extends ActionTarget {
if (firstDraw) {
this.element.setAttribute("role", "button");
- const lastChild = this.element.lastChild;
-
- // Ensure that the last child is a text node
- // Any whitespace (including indentation) in the template will create a #text node
- // But just in case (compressed version) we still check if the last child is a text node
- if (!lastChild || lastChild.nodeType !== Node.TEXT_NODE) {
- // Create a text node if the last child is not a text node
- this.element.appendChild(document.createTextNode(""));
- }
-
- this._labelNode = this.element.lastChild;
-
- // Apply Button styles
+
this._applyVisualPositionStyles();
this._applyVisualOrientationStyles();
}
}
- /**
- * Draws the component.
- * @override
- */
- draw() {
- super.draw();
-
- if (this._labelNode) {
- this._labelNode.data = this.label;
- }
- }
-
// <---- Private ---->
diff --git a/ui/cascading-list.mod/teach/index.html b/ui/cascading-list.mod/teach/index.html
index 0c833896a..d1bf790b9 100644
--- a/ui/cascading-list.mod/teach/index.html
+++ b/ui/cascading-list.mod/teach/index.html
@@ -4,7 +4,7 @@
Cascading List Samples
-
+
diff --git a/ui/flow.mod/flow-translate-composer.js b/ui/flow.mod/flow-translate-composer.js
index 473624b89..074b3b3b9 100644
--- a/ui/flow.mod/flow-translate-composer.js
+++ b/ui/flow.mod/flow-translate-composer.js
@@ -471,13 +471,20 @@ var FlowTranslateComposer = exports.FlowTranslateComposer = TranslateComposer.sp
}
},
+
+ _linearScrollRatio: {
+ get: function() {
+ return this._flow.isCameraEnabled ? 500 / this._flow._height : 1;
+ }
+ },
+
// TODO doc
/**
*/
_updateLinearScroll: {
value: function () {
var flow = this._flow,
- ratio = flow.isCameraEnabled ? 500 / flow._height : 1,
+ ratio = this._linearScrollRatio,
x = ((this._pageX - this._startPageX) * this._linearScrollingVector[0] * ratio * flow._sceneScaleX.denominator) / flow._sceneScaleX.numerator,
y = ((this._pageY - this._startPageY) * this._linearScrollingVector[1] * ratio * flow._sceneScaleY.denominator) / flow._sceneScaleY.numerator,
squaredMagnitude = this._linearScrollingVector[0] * this._linearScrollingVector[0] + this._linearScrollingVector[1] * this._linearScrollingVector[1],
diff --git a/ui/list-item-menu.mod/list-item-menu.css b/ui/list-item-menu.mod/list-item-menu.css
new file mode 100644
index 000000000..4e1503a54
--- /dev/null
+++ b/ui/list-item-menu.mod/list-item-menu.css
@@ -0,0 +1,229 @@
+.ListItemMenu {
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 42px;
+ border: 1px solid #c8c7cc;
+ box-sizing: border-box;
+ overflow: hidden;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.ListItemMenu .hot-corners {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.ListItemMenu .hot-corners::before,
+.ListItemMenu .hot-corners::after {
+ content: "";
+ animation-name: none;
+ animation-timing-function: linear;
+ animation-fill-mode: forwards;
+ border-style: solid;
+ border-color: #e9e9e9 transparent;
+ position: absolute;
+ bottom: -1px;
+ border-width: 0;
+ border-radius: 0;
+ z-index: 2;
+ box-sizing: border-box;
+}
+
+.ListItemMenu .hot-corners::after {
+ right: -1px;
+}
+
+.ListItemMenu .hot-corners::before {
+ left: -1px;
+}
+
+.ListItemMenu.has-options-left.fold-left .hot-corners::before,
+.ListItemMenu.has-options-right.fold-right .hot-corners::after {
+ box-shadow: -1px -1px 3px #c8c7cc;
+ animation-duration: 0.3s;
+}
+
+.ListItemMenu.has-options-left.fold-left .ListItemMenu-zone.left,
+.ListItemMenu.has-options-right.fold-right .ListItemMenu-zone.right {
+ animation-timing-function: linear;
+ animation-fill-mode: forwards;
+ animation-duration: 0.3s;
+}
+
+.ListItemMenu.has-options-left.fold-left .hot-corners::before {
+ animation-name: fold-item-left;
+}
+
+.ListItemMenu.has-options-left.fold-left .ListItemMenu-zone.left {
+ animation-name: fold-options-left;
+}
+
+.ListItemMenu.has-options-right.fold-right .hot-corners::after {
+ animation-name: fold-item-right;
+}
+
+.ListItemMenu.has-options-right.fold-right .ListItemMenu-zone.right {
+ animation-name: fold-options-right;
+}
+
+.ListItemMenu.has-options-left.unfold-left .hot-corners::before,
+.ListItemMenu.has-options-right.unfold-right .hot-corners::after {
+ animation-duration: 0.15s;
+}
+
+.ListItemMenu.has-options-right.unfold-right .hot-corners::after {
+ animation-name: unfold-item-right;
+}
+
+.ListItemMenu.has-options-left.unfold-left .hot-corners::before {
+ animation-name: unfold-item-left;
+}
+
+@keyframes fold-options-right {
+ 0% {transform: translate3d(0px, 42px, 0);}
+ 100% {transform: translate3d(-14px, 29px, 0);}
+}
+
+@keyframes fold-options-left {
+ 0% {transform: translate3d(0px, 42px, 0);}
+ 100% {transform: translate3d(14px, 29px, 0);}
+}
+
+@keyframes fold-item-right {
+ 0% {border-width: 0px}
+ 100% {border-width: 15px 15px 0 0;}
+}
+
+@keyframes unfold-item-right {
+ 0% {border-width: 15px 15px 0 0;}
+ 100% {border-width: 0px}
+}
+
+@keyframes fold-item-left {
+ 0% {border-width: 0px}
+ 100% {border-width: 15px 0 0 15px;}
+}
+
+@keyframes unfold-item-left {
+ 0% {border-width: 15px 0 0 15px;}
+ 100% {border-width: 0px}
+}
+
+.ListItemMenu + .ListItemMenu {
+ border-top: none;
+}
+
+.ListItemMenu.selected .ListItem {
+ background-color: #d9d9d9;
+}
+
+.ListItemMenu.active .ListItem {
+ background-color: #e9e9e9;
+}
+
+.ListItemMenu .ListItemMenu-wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-transform: translate3d(-100%, 0, 0);
+ transform: translate3d(-100%, 0, 0);
+}
+
+.ListItemMenu .ListItemMenu-zone {
+ -webkit-box-flex: 0;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ width: 100%;
+ height: 100%;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ visibility: visible;
+ position: relative;
+ z-index: 1;
+}
+
+.ListItemMenu .ListItemMenu-zone.hide {
+ visibility: hidden;
+}
+
+.ListItemMenu .ListItemMenu-options,
+.ListItemMenu .ListItemMenu-content {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+
+.ListItemMenu .ListItemMenu-content .ListItem {
+ border: none;
+}
+
+.ListItemMenu .ListItemMenu-zone.left .ListItemMenu-options {
+ -webkit-box-pack: end;
+ -ms-flex-pack: end;
+ justify-content: flex-end;
+}
+
+.ListItemMenu .ListItemMenu-zone.right .ListItemMenu-options {
+ position: relative;
+ -webkit-box-pack: start;
+ -ms-flex-pack: start;
+ justify-content: flex-start;
+}
+
+.ListItemMenu .ListItemMenu-options button {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ border: none;
+ color: white !important;
+ outline: none;
+ text-decoration: none;
+ font-weight: bold;
+ white-space: nowrap;
+ justify-content: start;
+
+}
+
+.ListItemMenu .ListItemMenu-zone.right .ListItemMenu-options button {
+ text-align: left;
+}
+
+.ListItemMenu .ListItemMenu-zone.left .ListItemMenu-options button {
+ text-align: right;
+}
+
+.ListItemMenu .ListItemMenu-options button > span {
+ transform: translate3d(0, 0, 0);
+ position: relative;
+ display: inline-block;
+}
+
+.ListItemMenu button.delete {
+ background-color: #e74c3c;
+}
diff --git a/ui/list-item-menu.mod/list-item-menu.html b/ui/list-item-menu.mod/list-item-menu.html
new file mode 100644
index 000000000..569e76bc1
--- /dev/null
+++ b/ui/list-item-menu.mod/list-item-menu.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/list-item-menu.mod/list-item-menu.js b/ui/list-item-menu.mod/list-item-menu.js
new file mode 100644
index 000000000..ce7b2b92a
--- /dev/null
+++ b/ui/list-item-menu.mod/list-item-menu.js
@@ -0,0 +1,1172 @@
+/**
+ * @module "ui/list-item-menu.reel"
+ */
+var Component = require("../component").Component,
+ TranslateComposer = require("../../composer/translate-composer").TranslateComposer,
+ PressComposer = require("../../composer/press-composer").PressComposer;
+
+/**
+ * @class ListItemMenu
+ * @extends Component
+ */
+var ListItemMenu = exports.ListItemMenu = Component.specialize(/** @lends ListItemMenu.prototype */{
+
+ constructor: {
+ value: function () {
+ this.defineBindings({
+ "classList.has('mod--disabled')": {
+ "<-": "disabled"
+ },
+ "classList.has('is-opened')": {
+ "<-": "__isOpened"
+ },
+ "classList.has('is-translating')": {
+ "<-": "_isTranslating"
+ },
+ "classList.has('has-options-left')": {
+ "<-": "_leftButtons.defined() && _leftButtons.length > 0"
+ },
+ "classList.has('has-options-right')": {
+ "<-": "_rightButtons.defined() && _rightButtons.length > 0"
+ },
+ "_deleteLabel": {
+ "<-": "data.defined() && userInterfaceDescriptor.defined() ? " +
+ "(data.path(userInterfaceDescriptor.listItemMenuDeleteNameExpression || \"''\") || " +
+ "path(userInterfaceDescriptor.listItemMenuDeleteNameExpression || \"''\") || deleteLabel)" +
+ " : deleteLabel"
+ }
+ });
+ }
+ },
+
+ /**
+ * @private
+ * @type {Number}
+ * @default 0
+ * @description Represents the distance traveled by the list item
+ * from the start position.
+ */
+ _distance: {
+ value: null
+ },
+
+ __shouldOpen: {
+ value: false
+ },
+
+ __shouldClose: {
+ value: false
+ },
+
+ /**
+ * @private
+ * @type {boolean}
+ * @default false
+ * @description Indicates if the list item menu should open itself
+ */
+ _shouldOpen: {
+ set: function (should) {
+ should = !!should;
+ this.__shouldOpen = should;
+ this.__shouldClose = !should;
+ },
+ get: function () {
+ return this.__shouldOpen;
+ }
+ },
+
+ /**
+ * @private
+ * @type {boolean}
+ * @default false
+ * @description Indicates if the list item menu should close itself
+ */
+ _shouldClose: {
+ set: function (should) {
+ should = !!should;
+ this.__shouldClose = should;
+ this.__shouldOpen = !should;
+ },
+ get: function () {
+ return this.__shouldClose;
+ }
+ },
+
+ /**
+ * @private
+ * @typedef {string} ListItemMenu.DIRECTION
+ * @default null
+ * @description Represents the current translating direction
+ */
+ _direction: {
+ value: null
+ },
+
+ __translateComposer: {
+ value: null
+ },
+
+ /**
+ * @private
+ * @typedef {Object} TranslateComposer
+ * @readOnly
+ * @default null
+ * @description List item menu's translate composer
+ */
+ _translateComposer: {
+ get: function () {
+ if (!this.__translateComposer) {
+ this.__translateComposer = new TranslateComposer();
+ this.__translateComposer.listenToWheelEvent = true;
+ this.__translateComposer.hasMomentum = false;
+ this.__translateComposer.allowFloats = false;
+ this.__translateComposer.axis = "horizontal";
+ this.__translateComposer.translateX = - this._dragElementRect.width;
+ this.addComposer(this.__translateComposer);
+ }
+
+ return this.__translateComposer;
+ }
+ },
+
+ __pressComposer: {
+ value: null
+ },
+
+ /**
+ * @private
+ * @typedef {Object} PressComposer
+ * @readOnly
+ * @default null
+ * @description List item menu's press composer
+ */
+ _pressComposer: {
+ get: function () {
+ if (!this.__pressComposer) {
+ this.__pressComposer = new PressComposer();
+ this.addComposerForElement(this.__pressComposer, document);
+ }
+
+ return this.__pressComposer;
+ }
+ },
+
+ _isTranslating: {
+ value: false
+ },
+
+ /**
+ * @public
+ * @type {boolean}
+ * @readOnly
+ * @default false
+ * @description Indicates if the list item is currently slidding
+ */
+ isTranslating: {
+ get: function () {
+ return this._isTranslating;
+ }
+ },
+
+ _openedSide: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @typedef {string} ListItemMenu.DIRECTION
+ * @readOnly
+ * @default null
+ * @description Represents the current opened side.
+ */
+ openedSide: {
+ get: function () {
+ return this._openedSide;
+ }
+ },
+
+ __isOpened: {
+ value: false
+ },
+
+ _isOpened: {
+ set: function (opened) {
+ if (opened !== this._opened) {
+ this.__isOpened = opened;
+
+ if (opened) {
+ this.application.addEventListener('press', this);
+ this._pressComposer.addEventListener('pressStart', this);
+ this._pressComposer.load();
+ } else {
+ this.application.removeEventListener('press', this);
+ this.application.removeEventListener('translateEnd', this);
+ this._pressComposer.removeEventListener('pressStart', this);
+ this._pressComposer.unload();
+ }
+ }
+ },
+ get: function () {
+ return this.__isOpened;
+ }
+ },
+
+ /**
+ * @public
+ * @type {boolean}
+ * @default false
+ * @readonly
+ * @description Indicates if the list item menu is opened
+ */
+ isOpened: {
+ get: function () {
+ return this.__isOpened;
+ }
+ },
+
+ _minDistanceBeforeOpen: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @type {Number}
+ * @default 15% of the list item menu width.
+ * @description Represents the minimum distance before
+ * automatically open a list item once a user end
+ * to translate the list item menu.
+ */
+ minDistanceBeforeOpen: {
+ set: function (minDistanceBeforeOpen) {
+ this._minDistanceBeforeOpen = +minDistanceBeforeOpen;
+ },
+ get: function () {
+ if (this._minDistanceBeforeOpen === null && this._dragElementRect) {
+ this._minDistanceBeforeOpen = this._dragElementRect.width * 0.15;
+ }
+
+ return this._minDistanceBeforeOpen;
+ }
+ },
+
+ _minDistanceBeforeClose: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @type {Number}
+ * @default 85
+ * @default 85% of the list item menu width.
+ * @description Represents the minimum distance before
+ * automatically open a list item once a user end
+ * to translate the list item menu.
+ */
+ minDistanceBeforeClose: {
+ set: function (minDistanceBeforeClose) {
+ this._minDistanceBeforeClose = +minDistanceBeforeClose;
+ },
+ get: function () {
+ if (this._minDistanceBeforeClose === null && this._dragElementRect) {
+ this._minDistanceBeforeClose = this._dragElementRect.width * 0.85;
+ }
+
+ return this._minDistanceBeforeClose;
+ }
+ },
+
+ _data: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @type {Object}
+ * @default null
+ * @description Represents the list item menu data
+ */
+ data: {
+ get: function () {
+ return this._data;
+ },
+ set: function (data) {
+ if (this._data !== data) {
+ this._data = data;
+ this._loadDataUserInterfaceDescriptorIfNeeded();
+ }
+ }
+ },
+
+ /**
+ * @public
+ * @typedef {Object} List
+ * @default null
+ * @description Represents the list item menu
+ * parent's list component
+ */
+ list: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @type {boolean}
+ * @default false
+ * @description Indicates if the list item menu is selected
+ */
+ selected: {
+ value: false
+ },
+
+ /**
+ * @public
+ * @type {Number}
+ * @default -1
+ * @description Represents the list item menu position within
+ * its parent's list component
+ */
+ rowIndex: {
+ value: -1
+ },
+
+ /**
+ * @public
+ * @typedef {Object} UserInterfaceDescriptor
+ * @default null
+ * @description Represents the list item menu
+ * user interface descriptor
+ */
+ userInterfaceDescriptor: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @type {string}
+ * @default 'Button'
+ * @description Default value for the label of delete button.
+ */
+ deleteLabel: {
+ value: null
+ },
+
+ /**
+ * @public
+ * @function openLeft
+ * @description Open the left side
+ */
+ openLeft: {
+ value: function () {
+ this._open(ListItemMenu.DIRECTION.LEFT);
+ }
+ },
+
+ /**
+ * @public
+ * @function openRight
+ * @description Open the right side
+ */
+ openRight: {
+ value: function () {
+ this._open(ListItemMenu.DIRECTION.RIGHT);
+ }
+ },
+
+ /**
+ * @public
+ * @function close
+ * @description Close the current opened side
+ */
+ close: {
+ value: function () {
+ if (this.isOpened) {
+ this._shouldClose = true;
+ this.needsDraw = true;
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @function _open
+ * @param {string} ListItemMenu.DIRECTION
+ * @description Open the given side
+ */
+ _open: {
+ value: function (side) {
+ if (!this.isOpened) {
+ if (side === ListItemMenu.DIRECTION.RIGHT ||
+ side === ListItemMenu.DIRECTION.LEFT
+ ) {
+ this._openedSide = side;
+ this._shouldOpen = true;
+ this.needsDraw = true;
+ }
+ }
+ }
+ },
+
+ _shouldUpdateButtonPositions: {
+ value: false
+ },
+
+ /**
+ * @private
+ * @function _loadDataUserInterfaceDescriptorIfNeeded
+ * @description Gets the user interface descriptor
+ * related to the `data` property
+ */
+ _loadDataUserInterfaceDescriptorIfNeeded: {
+ value: function () {
+ if (this.data && this._templateDidLoad) {
+ var self = this,
+ infoDelegate;
+
+ this.loadUserInterfaceDescriptor(this.data).then(function (UIDescriptor) {
+ self.userInterfaceDescriptor = UIDescriptor || self.userInterfaceDescriptor; // trigger biddings.
+
+ self._deleteLabel = self.callDelegateMethod(
+ "listItemMenuWillUseDeleteLabelForObjectAtRowIndex",
+ self,
+ self._deleteLabel,
+ self.data,
+ self.rowIndex,
+ self.list
+ ) || self._deleteLabel; // defined by a bidding expression
+ });
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @function _closeIfNeeded
+ * @description Close the current opened side if not translating.
+ */
+ _closeIfNeeded: {
+ value: function () {
+ if (!this._isTranslating) {
+ this.close();
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @function _hasReachMinDistance
+ * @description Cheks if the minimum distance has been reach
+ * in order to automatically open a list item menu
+ * @return boolean
+ */
+ _hasReachMinDistance: {
+ value: function () {
+ return this._distance >= this.minDistanceBeforeOpen;
+ }
+ },
+
+ /**
+ * @private
+ * @function _hasReachMaxDistance
+ * @description Cheks if the minimum distance has been reach
+ * in order to automatically close a list item menu and disptach
+ * an action event.
+ * @return boolean
+ */
+ _hasReachMaxDistance: {
+ value: function () {
+ return this._distance >= this.minDistanceBeforeClose;
+ }
+ },
+
+ /**
+ * @private
+ * @function _findVelocity
+ * @description Find the velocity of a swipe gesture
+ * @returns Number
+ */
+ _findVelocity: {
+ value: function (deltaTime) {
+ if (deltaTime > 300) {
+ return 0;
+ }
+
+ return Math.sqrt(this._deltaX * this._deltaX) / deltaTime;
+ }
+ },
+
+ /**
+ *
+ * Events cycle management
+ *
+ */
+
+ enterDocument: {
+ value: function (firstTime) {
+ if (!ListItemMenu.cssTransform) {
+ if ("webkitTransform" in this._element.style) {
+ ListItemMenu.cssTransform = "webkitTransform";
+ ListItemMenu.cssTransition = "webkitTransition";
+ } else if ("MozTransform" in this._element.style) {
+ ListItemMenu.cssTransform = "MozTransform";
+ ListItemMenu.cssTransition = "MozTransition";
+ } else if ("oTransform" in this._element.style) {
+ ListItemMenu.cssTransform = "oTransform";
+ ListItemMenu.cssTransition = "oTransition";
+ } else {
+ ListItemMenu.cssTransform = "transform";
+ ListItemMenu.cssTransition = "transition";
+ }
+ }
+
+ this._startListeningToInitialInteractionsIfNeeded();
+ }
+ },
+
+ prepareForActivationEvents: {
+ value: function () {
+ this._startListeningToInitialInteractions();
+ }
+ },
+
+ exitDocument: {
+ value: function () {
+ this._stopListeningToInitialInteractions();
+ }
+ },
+
+ _startListeningToInitialInteractionsIfNeeded: {
+ value: function () {
+ if (this.preparedForActivationEvents) {
+ this._startListeningToInitialInteractions();
+ }
+ }
+ },
+
+ _startListeningToInitialInteractions: {
+ value: function () {
+ this._translateComposer.addEventListener('translateStart', this);
+ this.element.addEventListener("transitionend", this);
+ window.addEventListener("resize", this);
+
+ if (window.PointerEvent) {
+ this.element.addEventListener('pointerenter', this);
+ } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
+ this._element.removeEventListener("MSPointerEnter", this);
+ } else {
+ this.element.addEventListener('mouseenter', this);
+ }
+ }
+ },
+
+ _stopListeningToInitialInteractions: {
+ value: function () {
+ if (this.preparedForActivationEvents) {
+ this._translateComposer.removeEventListener('translateStart', this);
+ this.element.removeEventListener("transitionend", this);
+ window.removeEventListener("resize", this);
+
+ if (window.PointerEvent) {
+ this.element.removeEventListener('pointerenter', this);
+ } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
+ this._element.removeEventListener("MSPointerEnter", this);
+ } else {
+ this.element.removeEventListener('mouseenter', this);
+ }
+ }
+ }
+ },
+
+ handleResize: {
+ value: function () {
+ this._forceComputingBoundaries = true;
+ this.needsDraw = true;
+ }
+ },
+
+ handleTransitionend: {
+ value: function (event) {
+ if (event.target === this.dragElement) {
+ if (this._isTranslating) {
+ this._isTranslating = false;
+ }
+
+ if (this._shouldClose) {
+ this.__shouldClose = false;
+ this._isOpened = false;
+ this._openedSide = null;
+
+ } else if (this._shouldOpen) {
+ this.__shouldOpen = false;
+ this._isOpened = true;
+ }
+
+ this._direction = null;
+ }
+ }
+ },
+
+ handlePointerenter: {
+ value: function (event) {
+ if (window.PointerEvent) {
+ if (event.pointerType === "mouse") {
+ this.element.addEventListener('pointermove', this);
+ this.element.addEventListener('pointerleave', this);
+ }
+ } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
+ if (event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_MOUSE) {
+ this.element.addEventListener('MSPointerMove', this);
+ this.element.addEventListener('MSPointerLeave', this);
+ }
+ } else {
+ this.element.addEventListener('mousemove', this);
+ this.element.addEventListener('mouseleave', this);
+ }
+
+ this._handlePointerOver(event);
+ }
+ },
+
+ _handlePointerOver: {
+ value: function (event) {
+ if (!this.isOpened && !this._isTranslating) {
+ this._overPositionX = event.clientX;
+ this.needsDraw = true;
+ } else {
+ this._overPositionX = null;
+ this._shouldFoldItem = false;
+ this.needsDraw = true;
+ }
+ }
+ },
+
+ handlePointerleave: {
+ value: function () {
+ if (window.PointerEvent) {
+ this.element.removeEventListener('pointermove', this);
+ this.element.removeEventListener('pointerleave', this);
+ } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
+ this.element.removeEventListener('MSPointerMove', this);
+ this.element.removeEventListener('MSPointerLeave', this);
+ } else {
+ this.element.removeEventListener('mousemove', this);
+ this.element.removeEventListener('mouseleave', this);
+ }
+
+ if (!this.isOpened && !this._isTranslating &&
+ this._shouldFoldItem !== false
+ ) {
+ this._shouldFoldItem = false;
+ this._shouldUnfoldItem = true;
+ this.needsDraw = true;
+ }
+ this._overPositionX = null;
+ }
+ },
+
+ handleTranslateStart: {
+ value: function (event) {
+ this._startPositionX = this.__translateComposer.translateX;
+ this._isTranslating = false;
+ this.__shouldClose = false;
+ this.__shouldOpen = false;
+ this._direction = null;
+ this._startTimestamp = event.timeStamp;
+ this.application.addEventListener('translateEnd', this);
+ this._addDragEventListeners();
+ }
+ },
+
+ handleTranslate: {
+ value: function (event) {
+ var translateX = event.translateX,
+ deltaX = translateX - this._startPositionX;
+
+ if (!this._direction) {
+ this._direction = deltaX > 2 ?
+ ListItemMenu.DIRECTION.RIGHT : deltaX < - 2 ?
+ ListItemMenu.DIRECTION.LEFT : null;
+ }
+
+ var direction = this._direction,
+ distance;
+
+ if (!direction && !this._isTranslating) {
+ // wait for a "real" translate.
+ return void 0;
+ }
+
+ if (!this._openedSide &&
+ ((direction === ListItemMenu.DIRECTION.LEFT &&
+ (!this._rightButtons || !this._rightButtons.length)) ||
+ (direction === ListItemMenu.DIRECTION.RIGHT &&
+ (!this._leftButtons || !this._leftButtons.length)))
+ ) {
+ // Cancel translating if there are no options to show
+ this._translateComposer._cancel();
+ return void 0;
+ }
+
+ // Defines the opened side at the first "real" translate.
+ if (!this._openedSide) {
+ this._openedSide = direction === ListItemMenu.DIRECTION.RIGHT ?
+ ListItemMenu.DIRECTION.LEFT : ListItemMenu.DIRECTION.RIGHT;
+ }
+
+ if (this._distance === null) {
+ // Define initial distance.
+ if (this._openedSide === ListItemMenu.DIRECTION.LEFT) {
+ distance = (
+ this.leftOptionsElement.getBoundingClientRect().right -
+ this._hotCornersElementRect.left
+ );
+ } else {
+ distance = (
+ this._hotCornersElementRect.right -
+ this.rightOptionsElement.getBoundingClientRect().left
+ );
+ }
+ } else {
+ var deltaTranslateX = Math.abs(this._translateX) - Math.abs(translateX);
+ direction = deltaTranslateX > 0 ? ListItemMenu.DIRECTION.RIGHT :
+ deltaTranslateX < 0 ? ListItemMenu.DIRECTION.LEFT : this._direction;
+
+ if (this._openedSide === ListItemMenu.DIRECTION.RIGHT) {
+ distance = this._distance - deltaTranslateX;
+ } else {
+ distance = this._distance + deltaTranslateX;
+ }
+ }
+
+ if (this._openedSide === ListItemMenu.DIRECTION.LEFT) {
+ // block distance if the left options reach the right side
+ if (translateX > 0) {
+ distance = this._hotCornersElementRect.width;
+ } else if (
+ this._hotCornersElementRect.width + translateX <= 0
+ ) {
+ // Reset the distance to 0 when a list item menu
+ // is translating above it's edges.
+ distance = 0;
+ }
+ } else {
+ // block distance if the right options reach the left side
+ if (
+ translateX < - this._hotCornersElementRect.width &&
+ Math.abs(translateX) / 2 > this._hotCornersElementRect.width
+ ) {
+ distance = this._hotCornersElementRect.width;
+ } else if (
+ this._hotCornersElementRect.width + translateX >= 0
+ ) {
+ // Reset the distance to 0 when a list item menu
+ // is translating above it's edges.
+ distance = 0;
+ }
+ }
+
+ if (distance < 0) {
+ distance = 0;
+ }
+
+ var buttonList = this._openedSide === ListItemMenu.DIRECTION.RIGHT ?
+ this._rightButtons : this._leftButtons;
+
+ this._hasReachEnd = !!(
+ buttonList &&
+ buttonList.length === 1 &&
+ this._hasReachMaxDistance()
+ );
+
+ this._direction = direction;
+ this._translateX = translateX;
+ this._deltaX = translateX - this._startPositionX;
+ this._isTranslating = true;
+ this._distance = distance;
+ this.needsDraw = true;
+ }
+ },
+
+ handleTranslateEnd: {
+ value: function (event) {
+ var target = event.targetElement || event.target;
+
+ if (target === this.element || this.element.contains(target)) {
+ var direction = this._direction;
+
+ if (direction) {
+ if (this._hasReachEnd) {
+ // Dispatches an action event and close the list item menu
+ // when a user reached the maximum distance
+ var actionEvent = document.createEvent("CustomEvent");
+
+ actionEvent.initCustomEvent("action", true, true, {
+ side: direction === ListItemMenu.DIRECTION.LEFT ?
+ ListItemMenu.DIRECTION.RIGHT :
+ ListItemMenu.DIRECTION.LEFT
+ });
+
+ this.dispatchEvent(actionEvent);
+ this._shouldClose = true;
+ } else {
+ var velocity = this._findVelocity(
+ event.timeStamp - this._startTimestamp
+ ),
+ hasReachMinDistance = this._hasReachMinDistance();
+
+ if (hasReachMinDistance && velocity > 0.15 &&
+ Math.abs(this._deltaX) > this._dragElementRect.width * 0.05
+ ) { // should open a side if we detect a good swipe
+
+ if (this._deltaX > 0) {
+ // should open right side if not already opened
+ this._shouldOpen = this.isOpened &&
+ this._openedSide === ListItemMenu.DIRECTION.RIGHT ?
+ false : true;
+ } else {
+ // should open left side if not already opened
+ this._shouldOpen = this.isOpened &&
+ this._openedSide === ListItemMenu.DIRECTION.LEFT ?
+ false : true;
+ }
+ } else if (hasReachMinDistance) {
+ // should open a side if the minimum distance has been reached.
+ this._shouldOpen = true;
+ } else {
+ // should close a side if the minimum distance has not been reached.
+ this._shouldClose = true;
+ }
+ }
+ }
+
+ this._resetTranslateContext();
+ } else {
+ this._closeIfNeeded();
+ }
+ }
+ },
+
+ handleTranslateCancel: {
+ value: function () {
+ this._resetTranslateContext();
+ this._isTranslating = false;
+ this._direction = null;
+ }
+ },
+
+ handlePressStart: {
+ value: function (event) {
+ var target = event.targetElement;
+
+ if (this.element !== target && !this.element.contains(target)) {
+ this.close();
+ }
+ }
+ },
+
+ handlePress: {
+ value: function () {
+ this._closeIfNeeded();
+ }
+ },
+
+ _addDragEventListeners: {
+ value: function () {
+ this._translateComposer.addEventListener('translate', this);
+ this._translateComposer.addEventListener('translateCancel', this);
+ }
+ },
+
+ _removeDragEventListeners: {
+ value: function () {
+ this._translateComposer.removeEventListener('translate', this);
+ this._translateComposer.removeEventListener('translateCancel', this);
+ }
+ },
+
+ _resetTranslateContext: {
+ value: function () {
+ this._removeDragEventListeners();
+ this._startTimestamp = 0;
+ this._distance = null;
+ this._hasReachEnd = false;
+ this.needsDraw = true;
+ }
+ },
+
+ /**
+ *
+ * Draw cycle management
+ *
+ */
+
+ willDraw: {
+ value: function () {
+ if (
+ !this._dragElementRect ||
+ this._dragElementRect.width === 0 ||
+ this._forceComputingBoundaries
+ ) {
+ this._dragElementRect = this.dragElement.getBoundingClientRect();
+ this._hotCornersElementRect = this.hotCornersElement.getBoundingClientRect();
+ this._leftButtons = this.leftOptionsElement.querySelectorAll('button');
+ this._rightButtons = this.rightOptionsElement.querySelectorAll('button');
+
+ if ((this._rightButtons && this._rightButtons.length > 3) ||
+ (this._leftButtons && this._leftButtons.length > 3)
+ ) {
+ throw new Error(
+ 'the list item menu component doesn\'t support' +
+ 'more than 3 buttons per slidding side'
+ );
+ }
+
+ var hasLeftButtons = this._leftButtons && this._leftButtons.length > 0,
+ hasRightButtons = this._rightButtons && this._rightButtons.length > 0;
+
+ this._shouldUpdateButtonPositions = true;
+ this.disabled = !hasLeftButtons && !hasRightButtons;
+ }
+ }
+ },
+
+ _setButtonBoundaries: {
+ value: function (buttonList, marginSide) {
+ var i, length, button, label, labelRect, buttonWidth;
+
+ if (buttonList && (length = buttonList.length)) {
+ buttonWidth = this._dragElementRect.width / 2 / length;
+
+ for (i = 0; i < length; i++) {
+ button = buttonList[i];
+
+ if ((label = button.firstElementChild)) {
+ labelRect = label.getBoundingClientRect();
+ label.style[marginSide] =
+ (buttonWidth - labelRect.width) / 2 + 'px';
+ }
+ }
+ }
+ }
+ },
+
+ draw: {
+ value: function () {
+ if (this._shouldUpdateButtonPositions) {
+ this._shouldUpdateButtonPositions = false;
+ this._updateButtonPositions();
+ }
+
+ this._setButtonBoundaries(this._rightButtons, 'marginLeft');
+ this._setButtonBoundaries(this._leftButtons, 'marginRight');
+
+ if (this.__translateComposer && !this.disabled) {
+ var dragElementWidth = this._dragElementRect.width,
+ dragElementStyle = this.dragElement.style,
+ elementClassList = this.element.classList,
+ leftOptionsElementClassList = this.leftOptionsElement.classList,
+ rightOptionsElementClassList = this.rightOptionsElement.classList,
+ direction = this._direction, openedSide = this._openedSide,
+ buttonList = openedSide === ListItemMenu.DIRECTION.RIGHT ?
+ this._rightButtons : this._leftButtons,
+ isLeftSideOpened = this._openedSide === ListItemMenu.DIRECTION.LEFT,
+ length, translateX;
+
+ if (this._isTranslating && !this._shouldOpen && !this._shouldClose) {
+ // logic when a user is translating the list item
+ translateX = this._translateX;
+ dragElementStyle[ListItemMenu.cssTransition] = 'none';
+
+ // Hide not sliding options.
+ if (isLeftSideOpened) {
+ rightOptionsElementClassList.add('hide');
+ leftOptionsElementClassList.remove('hide');
+ } else {
+ rightOptionsElementClassList.remove('hide');
+ leftOptionsElementClassList.add('hide');
+ }
+
+ if (isLeftSideOpened) {
+ // block translate if the left options reach the right side
+ if (translateX > 0) {
+ translateX = 0;
+ }
+ } else {
+ // block translate if the right options reach the left side
+ if (
+ translateX < -dragElementWidth &&
+ Math.abs(translateX) / 2 > dragElementWidth
+ ) {
+ translateX = dragElementWidth * -2;
+ }
+ }
+
+ if (buttonList && (length = buttonList.length)) {
+ this._translateButtons(
+ buttonList,
+ (Math.abs(
+ Math.abs(translateX) - dragElementWidth) / length
+ ),
+ 'none',
+ isLeftSideOpened
+ );
+ }
+ } else if (this._shouldOpen || this._shouldClose) {
+ if (this._shouldOpen) {
+ translateX = this.__translateComposer.translateX = (
+ dragElementWidth * (isLeftSideOpened ? -0.5 : -1.5)
+ );
+ } else if (this._shouldClose) {
+ translateX = this.__translateComposer.translateX = (
+ - dragElementWidth
+ );
+ }
+
+ if (buttonList && (length = buttonList.length)) {
+ this._translateButtons(
+ buttonList,
+ this._shouldClose ? 0 : dragElementWidth / 2 / length,
+ ListItemMenu.DEFAULT_TRANSITION,
+ isLeftSideOpened
+ );
+ }
+
+ dragElementStyle[ListItemMenu.cssTransition] = (
+ ListItemMenu.DEFAULT_TRANSITION
+ );
+ } else {
+ if (!this.isOpened) {
+ rightOptionsElementClassList.remove('hide');
+ leftOptionsElementClassList.remove('hide');
+ }
+ }
+
+ if (translateX !== void 0) {
+ dragElementStyle[ListItemMenu.cssTransform] = (
+ "translate3d(" + translateX + "px,0,0)"
+ );
+ }
+
+ if (this._openedSide) {
+ elementClassList.add(this._openedSide.toLowerCase() + '-side');
+ } else {
+ elementClassList.remove('left-side');
+ elementClassList.remove('right-side');
+ }
+
+ if (this._hasReachEnd) {
+ if (this._openedSide === ListItemMenu.DIRECTION.LEFT) {
+ leftOptionsElementClassList.add('has-reach-end');
+ } else {
+ rightOptionsElementClassList.add('has-reach-end');
+ }
+ } else {
+ leftOptionsElementClassList.remove('has-reach-end');
+ rightOptionsElementClassList.remove('has-reach-end');
+ }
+
+ if (this._overPositionX !== null) {
+ var threshold = dragElementWidth * 0.25;
+
+ if (
+ this._leftButtons.length &&
+ this._overPositionX >= this._hotCornersElementRect.left &&
+ this._overPositionX <= this._hotCornersElementRect.left + threshold
+ ) {
+ this._foldSide = ListItemMenu.DIRECTION.LEFT;
+ this._shouldFoldItem = true;
+ } else if (
+ this._rightButtons.length &&
+ this._overPositionX >= this._hotCornersElementRect.right - threshold &&
+ this._overPositionX <= this._hotCornersElementRect.right
+ ) {
+ this._shouldFoldItem = true;
+ this._foldSide = ListItemMenu.DIRECTION.RIGHT;
+ } else {
+ if (this._shouldFoldItem) {
+ this._shouldUnfoldItem = true;
+ }
+
+ this._shouldFoldItem = false;
+ }
+ }
+
+ if (this._shouldFoldItem) {
+ elementClassList.add('fold-' + this._foldSide.toLowerCase());
+ elementClassList.remove('unfold-right');
+ elementClassList.remove('unfold-left');
+ } else {
+ elementClassList.remove('fold-right');
+ elementClassList.remove('fold-left');
+ }
+
+ if (this._shouldUnfoldItem && this._foldSide) {
+ elementClassList.add('unfold-' + this._foldSide.toLowerCase());
+ this._shouldUnfoldItem = false;
+ this._foldSide = null;
+ }
+
+ if (this._forceComputingBoundaries) {
+ this._updateButtonPositions();
+ this._forceComputingBoundaries = false;
+ }
+ }
+ }
+ },
+
+ _updateButtonPositions: {
+ value: function () {
+ if (this._leftButtons && this._leftButtons.length > 0) {
+ this._translateButtons(this._rightButtons, 0, 'none', false);
+ }
+
+ if (this._rightButtons && this._rightButtons.length > 0) {
+ this._translateButtons(this._leftButtons, 0, 'none', true);
+ }
+ }
+ },
+
+ _translateButtons: {
+ value: function (buttonList, position, transition, isLeftSide) {
+ var button, buttonStyle, translate;
+
+ for (var i = 0, length = buttonList.length; i < length; i++) {
+ button = buttonList[i];
+ buttonStyle = button.style;
+
+ if (isLeftSide) {
+ buttonStyle.zIndex = length - i;
+ translate = -((length - i - 1) * position);
+ } else {
+ buttonStyle.zIndex = i;
+ translate = i * position;
+ }
+
+ buttonStyle[ListItemMenu.cssTransition] = transition;
+ buttonStyle[ListItemMenu.cssTransform] = (
+ "translate3d(" + translate + "px,0,0)"
+ );
+ }
+ }
+ }
+
+}, {
+ DIRECTION: {
+ value: {
+ LEFT: 'LEFT',
+ RIGHT: 'RIGHT'
+ }
+ },
+
+ DEFAULT_TRANSITION: {
+ value: 'transform .3s cubic-bezier(0, 0, 0.58, 1)'
+ }
+ }
+);
+
+ListItemMenu.prototype.handlePointermove = ListItemMenu.prototype._handlePointerOver;
+ListItemMenu.prototype.handleMSPointerEnter = ListItemMenu.prototype.handlePointerenter;
+ListItemMenu.prototype.handleMSPointerMove = ListItemMenu.prototype._handlePointerOver;
+ListItemMenu.prototype.handleMSPointerLeave = ListItemMenu.prototype.handlePointerleave;
+ListItemMenu.prototype.handleMouseenter = ListItemMenu.prototype.handlePointerenter;
+ListItemMenu.prototype.handleMousemove = ListItemMenu.prototype._handlePointerOver;
+ListItemMenu.prototype.handleMouseleave = ListItemMenu.prototype.handlePointerleave;
diff --git a/ui/list-item-menu.mod/teach/index.html b/ui/list-item-menu.mod/teach/index.html
new file mode 100644
index 000000000..c2003820f
--- /dev/null
+++ b/ui/list-item-menu.mod/teach/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ List Item Menu Sample
+
+
+
+
+
+
+
diff --git a/ui/list-item-menu.mod/teach/package.json b/ui/list-item-menu.mod/teach/package.json
new file mode 100644
index 000000000..6afbb3873
--- /dev/null
+++ b/ui/list-item-menu.mod/teach/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "list-item-menu-sample",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "mod": "*"
+ },
+ "mappings": {
+ "mod": "../../../"
+ }
+}
diff --git a/ui/list-item-menu.mod/teach/ui/main.mod/main.css b/ui/list-item-menu.mod/teach/ui/main.mod/main.css
new file mode 100644
index 000000000..c820c4559
--- /dev/null
+++ b/ui/list-item-menu.mod/teach/ui/main.mod/main.css
@@ -0,0 +1,82 @@
+html, body, .Main {
+ padding: 0;
+ margin: 0;
+ height: 100%;
+ font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.Main {
+ padding: 20px;
+}
+
+header {
+ margin: 60px 0;
+ font-size: 2rem;
+ text-align: center;
+ color: #33495d;
+ height: 40px;
+}
+
+.items {
+ margin: auto;
+ height: 500px;
+ width: 300px;
+}
+
+.ListItemMenu.gray .ListItemMenu-options button {
+ font-size: 14px;
+ white-space: nowrap;
+ color: rgb(100, 100, 100);
+}
+
+.ListItemMenu.gray .ListItemMenu-options button .dot {
+ background-color: rgb(100, 100, 100);
+}
+
+.ListItemMenu.gray.is-opened.right-side .ListItemMenu-content {
+ border-right: 1px solid #bdc3c7;
+}
+
+.ListItemMenu.gray.is-opened.left-side .ListItemMenu-content {
+ border-left: 1px solid #bdc3c7;
+}
+
+.ListItemMenu .archive {
+ background-color: #2C82C9 !important;
+}
+
+.ListItemMenu .delete {
+ background-color: #e74c3c !important;
+}
+
+.ListItemMenu .more {
+ background-color: #9E9D9B !important;
+}
+
+.ListItemMenu .dot {
+ height: 5px;
+ width: 5px;
+ background-color: white !important;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.ListItemMenu .move {
+ background-color: #5659C9 !important;
+}
+
+.ListItemMenu .pin {
+ background-color: #2CC990 !important;
+}
+
+.ListItemMenu[data-mod-id='list-item-menu-8'] .ListItemMenu-content {
+ padding-left: 16px;
+ padding-right: 16px;
+}
diff --git a/ui/list-item-menu.mod/teach/ui/main.mod/main.html b/ui/list-item-menu.mod/teach/ui/main.mod/main.html
new file mode 100644
index 000000000..b700faea8
--- /dev/null
+++ b/ui/list-item-menu.mod/teach/ui/main.mod/main.html
@@ -0,0 +1,436 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Item 8 (injected)
+
+
+
+
+
+
+
diff --git a/ui/list-item-menu.mod/teach/ui/main.mod/main.js b/ui/list-item-menu.mod/teach/ui/main.mod/main.js
new file mode 100644
index 000000000..1fd17274f
--- /dev/null
+++ b/ui/list-item-menu.mod/teach/ui/main.mod/main.js
@@ -0,0 +1,21 @@
+var Component = require("mod/ui/component").Component,
+ Promise = require('mod/core/promise');
+
+exports.Main = Component.specialize(/** @lends Main# */{
+
+
+ handleArchiveAction: {
+ value: function () {
+ console.log("archive");
+ this.listItem.close();
+ }
+ },
+
+ handleDeleteAction: {
+ value: function () {
+ console.log("delete");
+ this.listItem.close();
+ }
+ }
+
+});