Index: pkg/polymer/lib/src/js/polymer/polymer.concat.js |
diff --git a/pkg/polymer/lib/src/js/polymer/polymer.concat.js b/pkg/polymer/lib/src/js/polymer/polymer.concat.js |
index 2478c928e744ef62ef805160cffdaedeb98749bb..b217d9d9aaa2cac257c48dd302f54f86f1c7c594 100644 |
--- a/pkg/polymer/lib/src/js/polymer/polymer.concat.js |
+++ b/pkg/polymer/lib/src/js/polymer/polymer.concat.js |
@@ -7,7 +7,10 @@ |
* Code distributed by Google as part of the polymer project is also |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
*/ |
-window.PolymerGestures = {}; |
+window.PolymerGestures = { |
+ hasSDPolyfill: Boolean(window.ShadowDOMPolyfill) |
+}; |
+PolymerGestures.wrap = PolymerGestures.hasSDPolyfill ? ShadowDOMPolyfill.wrapIfNeeded : function(a){ return a; }; |
/* |
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
@@ -19,6 +22,30 @@ window.PolymerGestures = {}; |
*/ |
(function(scope) { |
+ var HAS_FULL_PATH = false; |
+ |
+ // test for full event path support |
+ var pathTest = document.createElement('meta'); |
+ if (!scope.hasSDPolyfill && pathTest.createShadowRoot) { |
+ var sr = pathTest.createShadowRoot(); |
+ var s = document.createElement('span'); |
+ sr.appendChild(s); |
+ pathTest.addEventListener('testpath', function(ev) { |
+ if (ev.path) { |
+ // if the span is in the event path, then path[0] is the real source for all events |
+ HAS_FULL_PATH = ev.path[0] === s; |
+ } |
+ ev.stopPropagation(); |
+ }); |
+ var ev = new CustomEvent('testpath', {bubbles: true}); |
+ // must add node to DOM to trigger event listener |
+ document.head.appendChild(pathTest); |
+ s.dispatchEvent(ev); |
+ pathTest.parentNode.removeChild(pathTest); |
+ sr = s = null; |
+ } |
+ pathTest = null; |
+ |
var target = { |
shadow: function(inEl) { |
if (inEl) { |
@@ -53,25 +80,18 @@ window.PolymerGestures = {}; |
return shadows; |
}, |
searchRoot: function(inRoot, x, y) { |
+ var t, st, sr, os; |
if (inRoot) { |
- var t = inRoot.elementFromPoint(x, y); |
- var st, sr, os; |
- // is element a shadow host? |
- sr = this.targetingShadow(t); |
- while (sr) { |
- // find the the element inside the shadow root |
- st = sr.elementFromPoint(x, y); |
- if (!st) { |
- // check for older shadows |
- sr = this.olderShadow(sr); |
- } else { |
- // shadowed element may contain a shadow root |
- var ssr = this.targetingShadow(st); |
- return this.searchRoot(ssr, x, y) || st; |
- } |
+ t = inRoot.elementFromPoint(x, y); |
+ if (t) { |
+ // found element, check if it has a ShadowRoot |
+ sr = this.targetingShadow(t); |
+ } else if (inRoot !== document) { |
+ // check for sibling roots |
+ sr = this.olderShadow(inRoot); |
} |
- // light dom element is the target |
- return t; |
+ // search other roots, fall back to light dom element |
+ return this.searchRoot(sr, x, y) || t; |
} |
}, |
owner: function(element) { |
@@ -90,6 +110,9 @@ window.PolymerGestures = {}; |
return s; |
}, |
findTarget: function(inEvent) { |
+ if (HAS_FULL_PATH && inEvent.path) { |
+ return inEvent.path[0]; |
+ } |
var x = inEvent.clientX, y = inEvent.clientY; |
// if the listener is in the shadow root, it is much faster to start there |
var s = this.owner(inEvent.target); |
@@ -99,6 +122,26 @@ window.PolymerGestures = {}; |
} |
return this.searchRoot(s, x, y); |
}, |
+ findScrollAxis: function(inEvent) { |
+ var n; |
+ if (HAS_FULL_PATH && inEvent.path) { |
+ var path = inEvent.path; |
+ for (var i = 0; i < path.length; i++) { |
+ n = path[i]; |
+ if (n._scrollType) { |
+ return n._scrollType; |
+ } |
+ } |
+ } else { |
+ n = scope.wrap(inEvent.currentTarget); |
+ while(n) { |
+ if (n._scrollType) { |
+ return n._scrollType; |
+ } |
+ n = n.parentNode || n.host; |
+ } |
+ } |
+ }, |
LCA: function(a, b) { |
if (a === b) { |
return a; |
@@ -122,14 +165,14 @@ window.PolymerGestures = {}; |
var adepth = this.depth(a); |
var bdepth = this.depth(b); |
var d = adepth - bdepth; |
- if (d > 0) { |
+ if (d >= 0) { |
a = this.walk(a, d); |
} else { |
b = this.walk(b, -d); |
} |
- while(a && b && a !== b) { |
- a = this.walk(a, 1); |
- b = this.walk(b, 1); |
+ while (a && b && a !== b) { |
+ a = a.parentNode || a.host; |
+ b = b.parentNode || b.host; |
} |
return a; |
}, |
@@ -229,7 +272,8 @@ window.PolymerGestures = {}; |
'pan-x pan-y', |
'pan-y pan-x' |
] |
- } |
+ }, |
+ 'manipulation' |
]; |
var styles = ''; |
// only install stylesheet if the browser has touch action support |
@@ -377,6 +421,7 @@ window.PolymerGestures = {}; |
e.pointerType = inDict.pointerType || ''; |
e.hwTimestamp = inDict.hwTimestamp || 0; |
e.isPrimary = inDict.isPrimary || false; |
+ e._source = inDict._source || ''; |
return e; |
} |
}; |
@@ -500,7 +545,8 @@ window.PolymerGestures = {}; |
'timeStamp', |
// gesture addons |
'preventTap', |
- 'tapPrevented' |
+ 'tapPrevented', |
+ '_source' |
]; |
var CLONE_DEFAULTS = [ |
@@ -545,9 +591,11 @@ window.PolymerGestures = {}; |
var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); |
- var wrap = window.ShadowDOMPolyfill && ShadowDOMPolyfill.wrapIfNeeded || function(e){ return e; }; |
- |
var eventFactory = scope.eventFactory; |
+ |
+ var hasSDPolyfill = scope.hasSDPolyfill; |
+ var wrap = scope.wrap; |
+ |
/** |
* This module is for normalizing events. Mouse and Touch events will be |
* collected here, and fire PointerEvents that have the same semantics, no |
@@ -594,10 +642,6 @@ window.PolymerGestures = {}; |
this.gestures.push(source); |
}, |
register: function(element) { |
- // NOTE: Work around for #4, don't add listeners to individual Polymer elmenets in SD Polyfill |
- if (window.ShadowDOMPolyfill && element !== document) { |
- return; |
- } |
var l = this.eventSourceList.length; |
for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
// call eventsource register |
@@ -644,19 +688,19 @@ window.PolymerGestures = {}; |
}, |
// set up event listeners |
listen: function(target, events) { |
- events.forEach(function(e) { |
+ for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
this.addEvent(target, e); |
- }, this); |
+ } |
}, |
// remove event listeners |
unlisten: function(target, events) { |
- events.forEach(function(e) { |
+ for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
this.removeEvent(target, e); |
- }, this); |
+ } |
}, |
addEvent: function(target, eventName) { |
// NOTE: Work around for #4, use native event listener in SD Polyfill |
- if (window.ShadowDOMPolyfill) { |
+ if (hasSDPolyfill) { |
target.addEventListener_(eventName, this.boundHandler); |
} else { |
target.addEventListener(eventName, this.boundHandler); |
@@ -664,7 +708,7 @@ window.PolymerGestures = {}; |
}, |
removeEvent: function(target, eventName) { |
// NOTE: Work around for #4, use native event listener in SD Polyfill |
- if (window.ShadowDOMPolyfill) { |
+ if (hasSDPolyfill) { |
target.removeEventListener_(eventName, this.boundHandler); |
} else { |
target.removeEventListener(eventName, this.boundHandler); |
@@ -738,10 +782,11 @@ window.PolymerGestures = {}; |
// process the gesture queue |
for (var i = 0, e; i < this.gestureQueue.length; i++) { |
e = this.gestureQueue[i]; |
- for (var j = 0, g; j < this.gestures.length; j++) { |
+ for (var j = 0, g, fn; j < this.gestures.length; j++) { |
g = this.gestures[j]; |
- if (g.events.indexOf(e.type) >= 0) { |
- g[e.type].call(g, e); |
+ fn = g[e.type]; |
+ if (fn) { |
+ fn.call(g, e); |
} |
} |
} |
@@ -758,8 +803,11 @@ window.PolymerGestures = {}; |
dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); |
scope.dispatcher = dispatcher; |
- scope.register = dispatcher.register.bind(dispatcher); |
+ scope.register = function(root) { |
+ dispatcher.register(root); |
+ }; |
scope.unregister = dispatcher.unregister.bind(dispatcher); |
+ scope.wrap = wrap; |
})(window.PolymerGestures); |
/* |
@@ -916,9 +964,12 @@ window.PolymerGestures = {}; |
events: [ |
'mousedown', |
'mousemove', |
- 'mouseup', |
+ 'mouseup' |
], |
register: function(target) { |
+ if (target !== document) { |
+ return; |
+ } |
dispatcher.listen(target, this.events); |
}, |
unregister: function(target) { |
@@ -942,6 +993,7 @@ window.PolymerGestures = {}; |
e.pointerId = this.POINTER_ID; |
e.isPrimary = true; |
e.pointerType = this.POINTER_TYPE; |
+ e._source = 'mouse'; |
if (!HAS_BUTTONS) { |
e.buttons = WHICH_TO_BUTTONS[e.which] || 0; |
} |
@@ -956,6 +1008,7 @@ window.PolymerGestures = {}; |
this.mouseup(inEvent); |
} |
var e = this.prepareEvent(inEvent); |
+ e.target = scope.wrap(scope.findTarget(inEvent)); |
pointermap.set(this.POINTER_ID, e.target); |
dispatcher.down(e); |
} |
@@ -970,7 +1023,7 @@ window.PolymerGestures = {}; |
mouseup: function(inEvent) { |
if (!this.isEventSimulatedFromTouch(inEvent)) { |
var e = this.prepareEvent(inEvent); |
- e.relatedTarget = e.target; |
+ e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); |
e.target = pointermap.get(this.POINTER_ID); |
dispatcher.up(e); |
this.cleanupMouse(); |
@@ -1001,9 +1054,12 @@ window.PolymerGestures = {}; |
// This should be long enough to ignore compat mouse events made by touch |
var DEDUP_TIMEOUT = 2500; |
var CLICK_COUNT_TIMEOUT = 200; |
+ var HYSTERESIS = 20; |
var ATTRIB = 'touch-action'; |
var INSTALLER; |
- var HAS_TOUCH_ACTION = typeof document.head.style.touchAction === 'string'; |
+ // maybe one day... |
+ // var CAN_USE_GLOBAL = ATTRIB in document.head.style; |
+ var CAN_USE_GLOBAL = false; |
// handler block for native touch events |
var touchEvents = { |
@@ -1014,14 +1070,14 @@ window.PolymerGestures = {}; |
'touchcancel' |
], |
register: function(target) { |
- if (HAS_TOUCH_ACTION) { |
+ if (CAN_USE_GLOBAL) { |
dispatcher.listen(target, this.events); |
} else { |
INSTALLER.enableOnSubtree(target); |
} |
}, |
unregister: function(target) { |
- if (HAS_TOUCH_ACTION) { |
+ if (CAN_USE_GLOBAL) { |
dispatcher.unlisten(target, this.events); |
} else { |
// TODO(dfreedman): is it worth it to disconnect the MO? |
@@ -1069,7 +1125,7 @@ window.PolymerGestures = {}; |
EMITTER: 'none', |
XSCROLLER: 'pan-x', |
YSCROLLER: 'pan-y', |
- SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ |
+ SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto|manipulation$/ |
}, |
touchActionToScrollType: function(touchAction) { |
var t = touchAction; |
@@ -1094,7 +1150,7 @@ window.PolymerGestures = {}; |
if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) { |
this.firstTouch = inTouch.identifier; |
this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; |
- this.scrolling = false; |
+ this.scrolling = null; |
this.cancelResetClickCount(); |
} |
}, |
@@ -1128,7 +1184,17 @@ window.PolymerGestures = {}; |
}, |
findTarget: function(touch, id) { |
if (this.currentTouchEvent.type === 'touchstart') { |
- return scope.findTarget(touch); |
+ if (this.isPrimaryTouch(touch)) { |
+ var fastPath = { |
+ clientX: touch.clientX, |
+ clientY: touch.clientY, |
+ path: this.currentTouchEvent.path, |
+ target: scope.wrap(this.currentTouchEvent.target) |
+ }; |
+ return scope.findTarget(fastPath); |
+ } else { |
+ return scope.findTarget(touch); |
+ } |
} |
// reuse target we found in touchstart |
return pointermap.get(id); |
@@ -1140,7 +1206,7 @@ window.PolymerGestures = {}; |
// Touch identifiers can start at 0. |
// Add 2 to the touch identifier for compatibility. |
var id = e.pointerId = inTouch.identifier + 2; |
- e.target = this.findTarget(inTouch, id); |
+ e.target = scope.wrap(this.findTarget(inTouch, id)); |
e.bubbles = true; |
e.cancelable = true; |
e.detail = this.clickCount; |
@@ -1150,6 +1216,7 @@ window.PolymerGestures = {}; |
e.pressure = inTouch.webkitForce || inTouch.force || 0.5; |
e.isPrimary = this.isPrimaryTouch(inTouch); |
e.pointerType = this.POINTER_TYPE; |
+ e._source = 'touch'; |
// forward touch preventDefaults |
var self = this; |
e.preventDefault = function() { |
@@ -1162,9 +1229,18 @@ window.PolymerGestures = {}; |
processTouches: function(inEvent, inFunction) { |
var tl = inEvent.changedTouches; |
this.currentTouchEvent = inEvent; |
- for (var i = 0, t; i < tl.length; i++) { |
+ for (var i = 0, t, p; i < tl.length; i++) { |
t = tl[i]; |
- inFunction.call(this, this.touchToPointer(t)); |
+ p = this.touchToPointer(t); |
+ if (inEvent.type === 'touchstart') { |
+ pointermap.set(p.pointerId, p.target); |
+ } |
+ if (pointermap.has(p.pointerId)) { |
+ inFunction.call(this, p); |
+ } |
+ if (inEvent.type === 'touchend' || inEvent._cancel) { |
+ this.cleanUpPointer(p); |
+ } |
} |
}, |
// For single axis scrollers, determines whether the element should emit |
@@ -1172,7 +1248,7 @@ window.PolymerGestures = {}; |
shouldScroll: function(inEvent) { |
if (this.firstXY) { |
var ret; |
- var scrollAxis = inEvent.currentTarget._scrollType; |
+ var scrollAxis = scope.targetFinding.findScrollAxis(inEvent); |
if (scrollAxis === 'none') { |
// this element is a touch-action: none, should never scroll |
ret = false; |
@@ -1190,7 +1266,6 @@ window.PolymerGestures = {}; |
// making events |
ret = da >= doa; |
} |
- this.firstXY = null; |
return ret; |
} |
}, |
@@ -1218,11 +1293,14 @@ window.PolymerGestures = {}; |
// Touch identifiers are 2 smaller than their pointerId, which is the |
// index in pointermap. |
if (key !== 1 && !this.findTouch(tl, key - 2)) { |
- var p = value.out; |
+ var p = value; |
d.push(p); |
} |
}, this); |
- d.forEach(this.cancelOut, this); |
+ d.forEach(function(p) { |
+ this.cancel(p); |
+ pointermap.delete(p.pointerId); |
+ }); |
} |
}, |
touchstart: function(inEvent) { |
@@ -1235,30 +1313,34 @@ window.PolymerGestures = {}; |
} |
}, |
down: function(inPointer) { |
- var p = pointermap.set(inPointer.pointerId, inPointer.target); |
dispatcher.down(inPointer); |
}, |
touchmove: function(inEvent) { |
- if (HAS_TOUCH_ACTION) { |
+ if (CAN_USE_GLOBAL) { |
this.processTouches(inEvent, this.move); |
} else { |
if (!this.scrolling) { |
- if (this.shouldScroll(inEvent)) { |
+ if (this.scrolling === null && this.shouldScroll(inEvent)) { |
this.scrolling = true; |
- this.touchcancel(inEvent); |
} else { |
+ this.scrolling = false; |
inEvent.preventDefault(); |
this.processTouches(inEvent, this.move); |
} |
+ } else if (this.firstXY) { |
+ var t = inEvent.changedTouches[0]; |
+ var dx = t.clientX - this.firstXY.X; |
+ var dy = t.clientY - this.firstXY.Y; |
+ var dd = Math.sqrt(dx * dx + dy * dy); |
+ if (dd >= HYSTERESIS) { |
+ this.touchcancel(inEvent); |
+ this.scrolling = true; |
+ this.firstXY = null; |
+ } |
} |
} |
}, |
move: function(inPointer) { |
- var pointer = pointermap.get(inPointer.pointerId); |
- // a finger drifted off the screen, ignore it |
- if (!pointer) { |
- return; |
- } |
dispatcher.move(inPointer); |
}, |
touchend: function(inEvent) { |
@@ -1266,18 +1348,14 @@ window.PolymerGestures = {}; |
this.processTouches(inEvent, this.up); |
}, |
up: function(inPointer) { |
- if (!this.scrolling) { |
- inPointer.relatedTarget = scope.findTarget(inPointer); |
- dispatcher.up(inPointer); |
- } |
- this.cleanUpPointer(inPointer); |
+ inPointer.relatedTarget = scope.wrap(scope.findTarget(inPointer)); |
+ dispatcher.up(inPointer); |
}, |
cancel: function(inPointer) { |
- inPointer.relatedTarget = scope.findTarget(inPointer); |
dispatcher.cancel(inPointer); |
- this.cleanUpPointer(inPointer); |
}, |
touchcancel: function(inEvent) { |
+ inEvent._cancel = true; |
this.processTouches(inEvent, this.cancel); |
}, |
cleanUpPointer: function(inPointer) { |
@@ -1304,7 +1382,7 @@ window.PolymerGestures = {}; |
} |
}; |
- if (!HAS_TOUCH_ACTION) { |
+ if (!CAN_USE_GLOBAL) { |
INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elementRemoved, touchEvents.elementChanged, touchEvents); |
} |
@@ -1332,6 +1410,9 @@ window.PolymerGestures = {}; |
'MSPointerCancel', |
], |
register: function(target) { |
+ if (target !== document) { |
+ return; |
+ } |
dispatcher.listen(target, this.events); |
}, |
unregister: function(target) { |
@@ -1346,10 +1427,11 @@ window.PolymerGestures = {}; |
], |
prepareEvent: function(inEvent) { |
var e = inEvent; |
+ e = dispatcher.cloneEvent(inEvent); |
if (HAS_BITMAP_TYPE) { |
- e = dispatcher.cloneEvent(inEvent); |
e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; |
} |
+ e._source = 'ms'; |
return e; |
}, |
cleanup: function(id) { |
@@ -1357,6 +1439,7 @@ window.PolymerGestures = {}; |
}, |
MSPointerDown: function(inEvent) { |
var e = this.prepareEvent(inEvent); |
+ e.target = scope.wrap(scope.findTarget(inEvent)); |
pointermap.set(inEvent.pointerId, e.target); |
dispatcher.down(e); |
}, |
@@ -1367,14 +1450,14 @@ window.PolymerGestures = {}; |
}, |
MSPointerUp: function(inEvent) { |
var e = this.prepareEvent(inEvent); |
- e.relatedTarget = e.target; |
+ e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); |
e.target = pointermap.get(e.pointerId); |
dispatcher.up(e); |
this.cleanup(inEvent.pointerId); |
}, |
MSPointerCancel: function(inEvent) { |
var e = this.prepareEvent(inEvent); |
- e.relatedTarget = e.target; |
+ e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); |
e.target = pointermap.get(e.pointerId); |
dispatcher.cancel(e); |
this.cleanup(inEvent.pointerId); |
@@ -1404,9 +1487,14 @@ window.PolymerGestures = {}; |
'pointercancel' |
], |
prepareEvent: function(inEvent) { |
- return dispatcher.cloneEvent(inEvent); |
+ var e = dispatcher.cloneEvent(inEvent); |
+ e._source = 'pointer'; |
+ return e; |
}, |
register: function(target) { |
+ if (target !== document) { |
+ return; |
+ } |
dispatcher.listen(target, this.events); |
}, |
unregister: function(target) { |
@@ -1417,6 +1505,7 @@ window.PolymerGestures = {}; |
}, |
pointerdown: function(inEvent) { |
var e = this.prepareEvent(inEvent); |
+ e.target = scope.wrap(scope.findTarget(inEvent)); |
pointermap.set(e.pointerId, e.target); |
dispatcher.down(e); |
}, |
@@ -1427,14 +1516,14 @@ window.PolymerGestures = {}; |
}, |
pointerup: function(inEvent) { |
var e = this.prepareEvent(inEvent); |
- e.relatedTarget = e.target; |
+ e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); |
e.target = pointermap.get(e.pointerId); |
dispatcher.up(e); |
this.cleanup(inEvent.pointerId); |
}, |
pointercancel: function(inEvent) { |
var e = this.prepareEvent(inEvent); |
- e.relatedTarget = e.target; |
+ e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); |
e.target = pointermap.get(e.pointerId); |
dispatcher.cancel(e); |
this.cleanup(inEvent.pointerId); |
@@ -1632,7 +1721,8 @@ window.PolymerGestures = {}; |
trackInfo: t.trackInfo, |
relatedTarget: inEvent.relatedTarget, |
pointerType: inEvent.pointerType, |
- pointerId: inEvent.pointerId |
+ pointerId: inEvent.pointerId, |
+ _source: 'track' |
}); |
t.downTarget.dispatchEvent(e); |
}, |
@@ -1731,7 +1821,7 @@ window.PolymerGestures = {}; |
/** |
* This event is fired when a held pointer is released or moved. |
* |
- * @class released |
+ * @class release |
*/ |
(function(scope) { |
@@ -1793,7 +1883,8 @@ window.PolymerGestures = {}; |
pointerType: this.heldPointer.pointerType, |
pointerId: this.heldPointer.pointerId, |
x: this.heldPointer.clientX, |
- y: this.heldPointer.clientY |
+ y: this.heldPointer.clientY, |
+ _source: 'hold' |
}; |
if (inHoldTime) { |
p.holdTime = inHoldTime; |
@@ -1886,7 +1977,8 @@ window.PolymerGestures = {}; |
altKey: inEvent.altKey, |
ctrlKey: inEvent.ctrlKey, |
metaKey: inEvent.metaKey, |
- shiftKey: inEvent.shiftKey |
+ shiftKey: inEvent.shiftKey, |
+ _source: 'tap' |
}); |
t.dispatchEvent(e); |
} |
@@ -2658,17 +2750,22 @@ window.PolymerGestures = {}; |
} |
function parseLeftHandSideExpression() { |
- var expr, property; |
+ var expr, args, property; |
expr = parsePrimaryExpression(); |
- while (match('.') || match('[')) { |
+ while (true) { |
if (match('[')) { |
property = parseComputedMember(); |
expr = delegate.createMemberExpression('[', expr, property); |
- } else { |
+ } else if (match('.')) { |
property = parseNonComputedMember(); |
expr = delegate.createMemberExpression('.', expr, property); |
+ } else if (match('(')) { |
+ args = parseArguments(); |
+ expr = delegate.createCallExpression(expr, args); |
+ } else { |
+ break; |
} |
} |
@@ -3041,34 +3138,31 @@ window.PolymerGestures = {}; |
}; |
function MemberExpression(object, property, accessor) { |
+ this.computed = accessor == '['; |
+ |
this.dynamicDeps = typeof object == 'function' || |
object.dynamicDeps || |
- (accessor == '[' && !(property instanceof Literal)); |
- |
- // convert literal computed property access where literal value is a value |
- // path to ident dot-access. |
- if (accessor == '[' && |
- property instanceof Literal && |
- Path.get(property.value).valid) { |
- accessor = '.'; |
- property = new IdentPath(property.value); |
- } |
+ (this.computed && !(property instanceof Literal)); |
this.simplePath = |
!this.dynamicDeps && |
- property instanceof IdentPath && |
+ (property instanceof IdentPath || property instanceof Literal) && |
(object instanceof MemberExpression || object instanceof IdentPath); |
this.object = this.simplePath ? object : getFn(object); |
- this.property = accessor == '.' ? property : getFn(property); |
+ this.property = !this.computed || this.simplePath ? |
+ property : getFn(property); |
} |
MemberExpression.prototype = { |
get fullPath() { |
if (!this.fullPath_) { |
- var last = this.object instanceof IdentPath ? |
- this.object.name : this.object.fullPath; |
- this.fullPath_ = Path.get(last + '.' + this.property.name); |
+ |
+ var parts = this.object instanceof MemberExpression ? |
+ this.object.fullPath.slice() : [this.object.name]; |
+ parts.push(this.property instanceof IdentPath ? |
+ this.property.name : this.property.value); |
+ this.fullPath_ = Path.get(parts); |
} |
return this.fullPath_; |
@@ -3087,11 +3181,11 @@ window.PolymerGestures = {}; |
return path.getValueFrom(model); |
}; |
- } else if (this.property instanceof IdentPath) { |
+ } else if (!this.computed) { |
var path = Path.get(this.property.name); |
- this.valueFn_ = function(model, observer) { |
- var context = object(model, observer); |
+ this.valueFn_ = function(model, observer, filterRegistry) { |
+ var context = object(model, observer, filterRegistry); |
if (observer) |
observer.addPath(context, path); |
@@ -3102,11 +3196,11 @@ window.PolymerGestures = {}; |
// Computed property. |
var property = this.property; |
- this.valueFn_ = function(model, observer) { |
- var context = object(model, observer); |
- var propName = property(model, observer); |
+ this.valueFn_ = function(model, observer, filterRegistry) { |
+ var context = object(model, observer, filterRegistry); |
+ var propName = property(model, observer, filterRegistry); |
if (observer) |
- observer.addPath(context, propName); |
+ observer.addPath(context, [propName]); |
return context ? context[propName] : undefined; |
}; |
@@ -3137,8 +3231,8 @@ window.PolymerGestures = {}; |
} |
Filter.prototype = { |
- transform: function(value, toModelDirection, filterRegistry, model, |
- observer) { |
+ transform: function(model, observer, filterRegistry, toModelDirection, |
+ initialArgs) { |
var fn = filterRegistry[this.name]; |
var context = model; |
if (fn) { |
@@ -3146,7 +3240,7 @@ window.PolymerGestures = {}; |
} else { |
fn = context[this.name]; |
if (!fn) { |
- console.error('Cannot find filter: ' + this.name); |
+ console.error('Cannot find function or filter: ' + this.name); |
return; |
} |
} |
@@ -3161,14 +3255,13 @@ window.PolymerGestures = {}; |
} |
if (typeof fn != 'function') { |
- console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') + |
- ' found on' + this.name); |
+ console.error('Cannot find function or filter: ' + this.name); |
return; |
} |
- var args = [value]; |
+ var args = initialArgs || []; |
for (var i = 0; i < this.args.length; i++) { |
- args[i + 1] = getFn(this.args[i])(model, observer); |
+ args.push(getFn(this.args[i])(model, observer, filterRegistry)); |
} |
return fn.apply(context, args); |
@@ -3222,8 +3315,8 @@ window.PolymerGestures = {}; |
argument = getFn(argument); |
- return function(model, observer) { |
- return unaryOperators[op](argument(model, observer)); |
+ return function(model, observer, filterRegistry) { |
+ return unaryOperators[op](argument(model, observer, filterRegistry)); |
}; |
}, |
@@ -3234,9 +3327,9 @@ window.PolymerGestures = {}; |
left = getFn(left); |
right = getFn(right); |
- return function(model, observer) { |
- return binaryOperators[op](left(model, observer), |
- right(model, observer)); |
+ return function(model, observer, filterRegistry) { |
+ return binaryOperators[op](left(model, observer, filterRegistry), |
+ right(model, observer, filterRegistry)); |
}; |
}, |
@@ -3245,9 +3338,10 @@ window.PolymerGestures = {}; |
consequent = getFn(consequent); |
alternate = getFn(alternate); |
- return function(model, observer) { |
- return test(model, observer) ? |
- consequent(model, observer) : alternate(model, observer); |
+ return function(model, observer, filterRegistry) { |
+ return test(model, observer, filterRegistry) ? |
+ consequent(model, observer, filterRegistry) : |
+ alternate(model, observer, filterRegistry); |
} |
}, |
@@ -3264,6 +3358,17 @@ window.PolymerGestures = {}; |
return ex; |
}, |
+ createCallExpression: function(expression, args) { |
+ if (!(expression instanceof IdentPath)) |
+ throw Error('Only identifier function invocations are allowed'); |
+ |
+ var filter = new Filter(expression.name, args); |
+ |
+ return function(model, observer, filterRegistry) { |
+ return filter.transform(model, observer, filterRegistry, false); |
+ }; |
+ }, |
+ |
createLiteral: function(token) { |
return new Literal(token.value); |
}, |
@@ -3272,10 +3377,10 @@ window.PolymerGestures = {}; |
for (var i = 0; i < elements.length; i++) |
elements[i] = getFn(elements[i]); |
- return function(model, observer) { |
+ return function(model, observer, filterRegistry) { |
var arr = [] |
for (var i = 0; i < elements.length; i++) |
- arr.push(elements[i](model, observer)); |
+ arr.push(elements[i](model, observer, filterRegistry)); |
return arr; |
} |
}, |
@@ -3291,10 +3396,11 @@ window.PolymerGestures = {}; |
for (var i = 0; i < properties.length; i++) |
properties[i].value = getFn(properties[i].value); |
- return function(model, observer) { |
+ return function(model, observer, filterRegistry) { |
var obj = {}; |
for (var i = 0; i < properties.length; i++) |
- obj[properties[i].key] = properties[i].value(model, observer); |
+ obj[properties[i].key] = |
+ properties[i].value(model, observer, filterRegistry); |
return obj; |
} |
}, |
@@ -3385,10 +3491,10 @@ window.PolymerGestures = {}; |
}, |
getValue: function(model, observer, filterRegistry) { |
- var value = getFn(this.expression)(model, observer); |
+ var value = getFn(this.expression)(model, observer, filterRegistry); |
for (var i = 0; i < this.filters.length; i++) { |
- value = this.filters[i].transform(value, false, filterRegistry, model, |
- observer); |
+ value = this.filters[i].transform(model, observer, filterRegistry, |
+ false, [value]); |
} |
return value; |
@@ -3397,8 +3503,8 @@ window.PolymerGestures = {}; |
setValue: function(model, newValue, filterRegistry) { |
var count = this.filters ? this.filters.length : 0; |
while (count-- > 0) { |
- newValue = this.filters[count].transform(newValue, true, filterRegistry, |
- model); |
+ newValue = this.filters[count].transform(model, undefined, |
+ filterRegistry, true, [newValue]); |
} |
if (this.expression.setValue) |
@@ -3510,19 +3616,33 @@ window.PolymerGestures = {}; |
var indexName = template.polymerExpressionIndexIdent_; |
return function(model) { |
- var scope = Object.create(parentScope); |
- scope[scopeName] = model; |
- scope[indexName] = undefined; |
- scope[parentScopeName] = parentScope; |
- return scope; |
+ return createScopeObject(parentScope, model, scopeName, indexName); |
}; |
} |
}; |
- global.PolymerExpressions = PolymerExpressions; |
- if (global.exposeGetExpression) |
- global.getExpression_ = getExpression; |
+ var createScopeObject = ('__proto__' in {}) ? |
+ function(parentScope, model, scopeName, indexName) { |
+ var scope = {}; |
+ scope[scopeName] = model; |
+ scope[indexName] = undefined; |
+ scope[parentScopeName] = parentScope; |
+ scope.__proto__ = parentScope; |
+ return scope; |
+ } : |
+ function(parentScope, model, scopeName, indexName) { |
+ var scope = Object.create(parentScope); |
+ Object.defineProperty(scope, scopeName, |
+ { value: model, configurable: true, writable: true }); |
+ Object.defineProperty(scope, indexName, |
+ { value: undefined, configurable: true, writable: true }); |
+ Object.defineProperty(scope, parentScopeName, |
+ { value: parentScope, configurable: true, writable: true }); |
+ return scope; |
+ }; |
+ global.PolymerExpressions = PolymerExpressions; |
+ PolymerExpressions.getExpression = getExpression; |
})(this); |
/* |
@@ -3534,7 +3654,7 @@ window.PolymerGestures = {}; |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
*/ |
Polymer = { |
- version: '0.3.1-604ba08' |
+ version: '0.3.3-0e73963' |
}; |
/* |
@@ -4013,7 +4133,7 @@ if (typeof window.Polymer === 'function') { |
this.async("fire", arguments); |
}, |
/** |
- * Remove class from old, add class to anew, if they exist |
+ * Remove class from old, add class to anew, if they exist. |
* @param classFollows |
* @param anew A node. |
* @param old A node |
@@ -4026,6 +4146,22 @@ if (typeof window.Polymer === 'function') { |
if (anew) { |
anew.classList.add(className); |
} |
+ }, |
+ /** |
+ * Inject HTML which contains markup bound to this element into |
+ * a target element (replacing target element content). |
+ * @param String html to inject |
+ * @param Element target element |
+ */ |
+ injectBoundHTML: function(html, element) { |
+ var template = document.createElement('template'); |
+ template.innerHTML = html; |
+ var fragment = this.instanceTemplate(template); |
+ if (element) { |
+ element.textContent = ''; |
+ element.appendChild(fragment); |
+ } |
+ return fragment; |
} |
}; |
@@ -4222,15 +4358,44 @@ if (typeof window.Polymer === 'function') { |
var empty = []; |
+ var updateRecord = { |
+ object: undefined, |
+ type: 'update', |
+ name: undefined, |
+ oldValue: undefined |
+ }; |
+ |
+ var numberIsNaN = Number.isNaN || function(value) { |
+ return typeof value === 'number' && isNaN(value); |
+ } |
+ |
+ function areSameValue(left, right) { |
+ if (left === right) |
+ return left !== 0 || 1 / left === 1 / right; |
+ if (numberIsNaN(left) && numberIsNaN(right)) |
+ return true; |
+ |
+ return left !== left && right !== right; |
+ } |
+ |
+ // capture A's value if B's value is null or undefined, |
+ // otherwise use B's value |
+ function resolveBindingValue(oldValue, value) { |
+ if (value === undefined && oldValue === null) { |
+ return value; |
+ } |
+ return (value === null || value === undefined) ? oldValue : value; |
+ } |
+ |
var properties = { |
createPropertyObserver: function() { |
var n$ = this._observeNames; |
if (n$ && n$.length) { |
var o = this._propertyObserver = new CompoundObserver(true); |
- this.registerObservers([o]); |
+ this.registerObserver(o); |
// TODO(sorvell): may not be kosher to access the value here (this[n]); |
// previously we looked at the descriptor on the prototype |
- // this doesn't work for inheritance and not for accessors without |
+ // this doesn't work for inheritance and not for accessors without |
// a value property |
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { |
o.addPath(this, n); |
@@ -4297,12 +4462,83 @@ if (typeof window.Polymer === 'function') { |
} |
} |
}, |
+ emitPropertyChangeRecord: function(name, value, oldValue) { |
+ var object = this; |
+ if (areSameValue(value, oldValue)) |
+ return; |
+ |
+ this.propertyChanged_(name, value, oldValue); |
+ |
+ if (!Observer.hasObjectObserve) |
+ return; |
+ |
+ var notifier = this.notifier_; |
+ if (!notifier) |
+ notifier = this.notifier_ = Object.getNotifier(this); |
+ |
+ updateRecord.object = this; |
+ updateRecord.name = name; |
+ updateRecord.oldValue = oldValue; |
+ |
+ notifier.notify(updateRecord); |
+ }, |
+ bindToAccessor: function(name, observable, resolveFn) { |
+ var privateName = name + '_'; |
+ var privateObservable = name + 'Observable_'; |
+ |
+ this[privateObservable] = observable; |
+ var oldValue = this[privateName]; |
+ |
+ var self = this; |
+ var value = observable.open(function(value, oldValue) { |
+ self[privateName] = value; |
+ self.emitPropertyChangeRecord(name, value, oldValue); |
+ }); |
+ |
+ if (resolveFn && !areSameValue(oldValue, value)) { |
+ var resolvedValue = resolveFn(oldValue, value); |
+ if (!areSameValue(value, resolvedValue)) { |
+ value = resolvedValue; |
+ if (observable.setValue) |
+ observable.setValue(value); |
+ } |
+ } |
+ |
+ this[privateName] = value; |
+ this.emitPropertyChangeRecord(name, value, oldValue); |
+ |
+ var observer = { |
+ close: function() { |
+ observable.close(); |
+ self[privateObservable] = undefined; |
+ } |
+ }; |
+ this.registerObserver(observer); |
+ return observer; |
+ }, |
+ createComputedProperties: function() { |
+ if (!this._computedNames) { |
+ return; |
+ } |
+ |
+ for (var i = 0; i < this._computedNames.length; i++) { |
+ var name = this._computedNames[i]; |
+ var expressionText = this.computed[name]; |
+ try { |
+ var expression = PolymerExpressions.getExpression(expressionText); |
+ var observable = expression.getBinding(this, this.element.syntax); |
+ this.bindToAccessor(name, observable); |
+ } catch (ex) { |
+ console.error('Failed to create computed property', ex); |
+ } |
+ } |
+ }, |
bindProperty: function(property, observable, oneTime) { |
if (oneTime) { |
this[property] = observable; |
return; |
} |
- return bindProperties(this, property, observable); |
+ return this.bindToAccessor(property, observable, resolveBindingValue); |
}, |
invokeMethod: function(method, args) { |
var fn = this[method] || method; |
@@ -4310,27 +4546,29 @@ if (typeof window.Polymer === 'function') { |
fn.apply(this, args); |
} |
}, |
- registerObservers: function(observers) { |
- this._observers = this._observers || []; |
- this._observers.push(observers); |
+ registerObserver: function(observer) { |
+ if (!this._observers) { |
+ this._observers = [observer]; |
+ return; |
+ } |
+ |
+ this._observers.push(observer); |
}, |
// observer array items are arrays of observers. |
closeObservers: function() { |
if (!this._observers) { |
return; |
} |
- for (var i=0, l=this._observers.length; i<l; i++) { |
- this.closeObserverArray(this._observers[i]); |
- } |
- this._observers = []; |
- }, |
- closeObserverArray: function(observerArray) { |
- for (var i=0, l=observerArray.length, o; i<l; i++) { |
- o = observerArray[i]; |
- if (o && o.close) { |
- o.close(); |
+ |
+ var observers = this._observers; |
+ for (var i = 0; i < observers.length; i++) { |
+ var observer = observers[i]; |
+ if (observer && typeof observer.close == 'function') { |
+ observer.close(); |
} |
} |
+ |
+ this._observers = []; |
}, |
// bookkeeping observers for memory management |
registerNamedObserver: function(name, observer) { |
@@ -4355,23 +4593,6 @@ if (typeof window.Polymer === 'function') { |
} |
}; |
- // property binding |
- // bind a property in A to a path in B by converting A[property] to a |
- // getter/setter pair that accesses B[...path...] |
- function bindProperties(a, property, observable) { |
- // apply Polymer two-way reference binding |
- return Observer.bindToInstance(a, property, observable, resolveBindingValue); |
- } |
- |
- // capture A's value if B's value is null or undefined, |
- // otherwise use B's value |
- function resolveBindingValue(oldValue, value) { |
- if (value === undefined && oldValue === null) { |
- return value; |
- } |
- return (value === null || value === undefined) ? oldValue : value; |
- } |
- |
// logging |
var LOG_OBSERVE = '[%s] watching [%s]'; |
var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]'; |
@@ -4405,7 +4626,10 @@ if (typeof window.Polymer === 'function') { |
var syntax = this.syntax || (!template.bindingDelegate && |
this.element.syntax); |
var dom = template.createInstance(this, syntax); |
- this.registerObservers(dom.bindings_); |
+ var observers = dom.bindings_; |
+ for (var i = 0; i < observers.length; i++) { |
+ this.registerObserver(observers[i]); |
+ } |
return dom; |
}, |
bind: function(name, observable, oneTime) { |
@@ -4558,6 +4782,7 @@ if (typeof window.Polymer === 'function') { |
return; |
} |
this._readied = true; |
+ this.createComputedProperties(); |
// TODO(sorvell): We could create an entry point here |
// for the user to compute property values. |
// process declarative resources |
@@ -4582,7 +4807,7 @@ if (typeof window.Polymer === 'function') { |
if (this.enteredView) { |
this.enteredView(); |
} |
- // NOTE: domReady can be used to access elements in dom (descendants, |
+ // NOTE: domReady can be used to access elements in dom (descendants, |
// ancestors, siblings) such that the developer is enured to upgrade |
// ordering. If the element definitions have loaded, domReady |
// can be used to access upgraded elements. |
@@ -4647,7 +4872,7 @@ if (typeof window.Polymer === 'function') { |
// make a shadow root |
var root = this.createShadowRoot(); |
// stamp template |
- // which includes parsing and applying MDV bindings before being |
+ // which includes parsing and applying MDV bindings before being |
// inserted (to avoid {{}} in attribute values) |
// e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
var dom = this.instanceTemplate(template); |
@@ -4664,17 +4889,17 @@ if (typeof window.Polymer === 'function') { |
if (template) { |
// TODO(sorvell): mark this element as an eventController so that |
// event listeners on bound nodes inside it will be called on it. |
- // Note, the expectation here is that events on all descendants |
+ // Note, the expectation here is that events on all descendants |
// should be handled by this element. |
this.eventController = this; |
// stamp template |
- // which includes parsing and applying MDV bindings before being |
+ // which includes parsing and applying MDV bindings before being |
// inserted (to avoid {{}} in attribute values) |
// e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
var dom = this.instanceTemplate(template); |
// append to shadow dom |
if (refNode) { |
- this.insertBefore(dom, refNode); |
+ this.insertBefore(dom, refNode); |
} else { |
this.appendChild(dom); |
} |
@@ -4722,7 +4947,7 @@ if (typeof window.Polymer === 'function') { |
// true if object has own PolymerBase api |
function isBase(object) { |
- return object.hasOwnProperty('PolymerBase') |
+ return object.hasOwnProperty('PolymerBase') |
} |
// name a base constructor for dev tools |
@@ -4730,13 +4955,13 @@ if (typeof window.Polymer === 'function') { |
function PolymerBase() {}; |
PolymerBase.prototype = base; |
base.constructor = PolymerBase; |
- |
+ |
// exports |
scope.Base = PolymerBase; |
scope.isBase = isBase; |
scope.api.instance.base = base; |
- |
+ |
})(Polymer); |
/* |
@@ -5401,6 +5626,13 @@ scope.api.declaration.path = path; |
a.push(n); |
} |
} |
+ if (prototype.computed) { |
+ // construct name list |
+ var a = prototype._computedNames = []; |
+ for (var n in prototype.computed) { |
+ a.push(n); |
+ } |
+ } |
}, |
publishProperties: function(prototype, base) { |
// if we have any properties to publish |
@@ -5412,17 +5644,17 @@ scope.api.declaration.path = path; |
prototype._publishLC = this.lowerCaseMap(publish); |
} |
}, |
- // sync prototype to property descriptors; |
- // desriptor format contains default value and optionally a |
+ // sync prototype to property descriptors; |
+ // desriptor format contains default value and optionally a |
// hint for reflecting the property to an attribute. |
// e.g. {foo: 5, bar: {value: true, reflect: true}} |
// reflect: {foo: true} is also supported |
- // |
+ // |
requireProperties: function(propertyDescriptors, prototype, base) { |
// reflected properties |
prototype.reflect = prototype.reflect || {}; |
// ensure a prototype value for each property |
- // and update the property's reflect to attribute status |
+ // and update the property's reflect to attribute status |
for (var n in propertyDescriptors) { |
var propertyDescriptor = propertyDescriptors[n]; |
var reflects = this.reflectHintForDescriptor(propertyDescriptor); |
@@ -5430,12 +5662,12 @@ scope.api.declaration.path = path; |
prototype.reflect[n] = reflects; |
} |
if (prototype[n] === undefined) { |
- prototype[n] = this.valueForDescriptor(propertyDescriptor); |
+ prototype[n] = this.valueForDescriptor(propertyDescriptor); |
} |
} |
}, |
valueForDescriptor: function(propertyDescriptor) { |
- var value = typeof propertyDescriptor === 'object' && |
+ var value = typeof propertyDescriptor === 'object' && |
propertyDescriptor ? propertyDescriptor.value : propertyDescriptor; |
return value !== undefined ? value : null; |
}, |
@@ -5453,13 +5685,52 @@ scope.api.declaration.path = path; |
} |
return map; |
}, |
+ createPropertyAccessor: function(name) { |
+ var proto = this.prototype; |
+ |
+ var privateName = name + '_'; |
+ var privateObservable = name + 'Observable_'; |
+ proto[privateName] = proto[name]; |
+ |
+ Object.defineProperty(proto, name, { |
+ get: function() { |
+ var observable = this[privateObservable]; |
+ if (observable) |
+ observable.deliver(); |
+ |
+ return this[privateName]; |
+ }, |
+ set: function(value) { |
+ var observable = this[privateObservable]; |
+ if (observable) { |
+ observable.setValue(value); |
+ return; |
+ } |
+ |
+ var oldValue = this[privateName]; |
+ this[privateName] = value; |
+ this.emitPropertyChangeRecord(name, value, oldValue); |
+ |
+ return value; |
+ }, |
+ configurable: true |
+ }); |
+ }, |
createPropertyAccessors: function(prototype) { |
var n$ = prototype._publishNames; |
if (n$ && n$.length) { |
for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { |
- Observer.createBindablePrototypeAccessor(prototype, n); |
+ this.createPropertyAccessor(n); |
+ } |
+ } |
+ |
+ var n$ = prototype._computedNames; |
+ if (n$ && n$.length) { |
+ for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { |
+ this.createPropertyAccessor(n); |
} |
} |
+ |
} |
}; |
@@ -5507,11 +5778,24 @@ scope.api.declaration.path = path; |
for (var i=0, l=names.length, n; i<l; i++) { |
// remove excess ws |
n = names[i].trim(); |
- // do not override explicit entries |
- if (n && publish[n] === undefined && base[n] === undefined) { |
+ // if the user hasn't specified a value, we want to use the |
+ // default, unless a superclass has already chosen one |
+ if (n && publish[n] === undefined) { |
+ // TODO(sjmiles): querying native properties on IE11 (and possibly |
+ // on other browsers) throws an exception because there is no actual |
+ // instance. |
+ // In fact, trying to publish native properties is known bad for this |
+ // and other reasons, and we need to solve this problem writ large. |
+ try { |
+ var hasValue = (base[n] !== undefined); |
+ } catch(x) { |
+ hasValue = false; |
+ } |
// supply an empty 'descriptor' object and let the publishProperties |
// code determine a default |
- publish[n] = Polymer.nob; |
+ if (!hasValue) { |
+ publish[n] = Polymer.nob; |
+ } |
} |
} |
} |