Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/html/lib/layout
diff options
context:
space:
mode:
Diffstat (limited to 'html/lib/layout')
-rw-r--r--html/lib/layout/fittable/package.js3
-rw-r--r--html/lib/layout/fittable/source/FittableColumns.js41
-rw-r--r--html/lib/layout/fittable/source/FittableLayout.css69
-rw-r--r--html/lib/layout/fittable/source/FittableLayout.js265
-rw-r--r--html/lib/layout/fittable/source/FittableRows.js40
-rw-r--r--html/lib/layout/fittable/source/design.js23
-rw-r--r--html/lib/layout/fittable/source/package.js6
-rw-r--r--html/lib/layout/list/package.js3
-rw-r--r--html/lib/layout/list/source/AlphaJumpList.js37
-rw-r--r--html/lib/layout/list/source/AlphaJumper.css36
-rw-r--r--html/lib/layout/list/source/AlphaJumper.js70
-rw-r--r--html/lib/layout/list/source/FlyweightRepeater.js175
-rw-r--r--html/lib/layout/list/source/List.css15
-rw-r--r--html/lib/layout/list/source/List.js368
-rw-r--r--html/lib/layout/list/source/PulldownList.css60
-rw-r--r--html/lib/layout/list/source/PulldownList.js180
-rw-r--r--html/lib/layout/list/source/Selection.js141
-rw-r--r--html/lib/layout/list/source/package.js7
-rw-r--r--html/lib/layout/list/source/wip-package.js6
-rw-r--r--html/lib/layout/package.js7
-rw-r--r--html/lib/layout/panels/package.js3
-rw-r--r--html/lib/layout/panels/source/Panels.css12
-rw-r--r--html/lib/layout/panels/source/Panels.js386
-rw-r--r--html/lib/layout/panels/source/arrangers/Arranger.css29
-rw-r--r--html/lib/layout/panels/source/arrangers/Arranger.js226
-rw-r--r--html/lib/layout/panels/source/arrangers/CardArranger.js49
-rw-r--r--html/lib/layout/panels/source/arrangers/CardSlideInArranger.js62
-rw-r--r--html/lib/layout/panels/source/arrangers/CarouselArranger.js109
-rw-r--r--html/lib/layout/panels/source/arrangers/CollapsingArranger.js80
-rw-r--r--html/lib/layout/panels/source/arrangers/OtherArrangers.js202
-rw-r--r--html/lib/layout/panels/source/arrangers/package.js9
-rw-r--r--html/lib/layout/panels/source/package.js5
-rw-r--r--html/lib/layout/slideable/package.js3
-rw-r--r--html/lib/layout/slideable/source/Slideable.js312
-rw-r--r--html/lib/layout/slideable/source/package.js3
-rw-r--r--html/lib/layout/tree/package.js3
-rw-r--r--html/lib/layout/tree/source/Node.css28
-rw-r--r--html/lib/layout/tree/source/Node.js269
-rw-r--r--html/lib/layout/tree/source/package.js4
39 files changed, 3346 insertions, 0 deletions
diff --git a/html/lib/layout/fittable/package.js b/html/lib/layout/fittable/package.js
new file mode 100644
index 0000000..3fae95b
--- /dev/null
+++ b/html/lib/layout/fittable/package.js
@@ -0,0 +1,3 @@
+enyo.depends(
+ "source"
+); \ No newline at end of file
diff --git a/html/lib/layout/fittable/source/FittableColumns.js b/html/lib/layout/fittable/source/FittableColumns.js
new file mode 100644
index 0000000..d28b6fe
--- /dev/null
+++ b/html/lib/layout/fittable/source/FittableColumns.js
@@ -0,0 +1,41 @@
+/**
+ _enyo.FittableColumns_ provides a container in which items are laid out in a
+ set of vertical columns, with most items having natural size, but one
+ expanding to fill the remaining space. The one that expands is labeled with
+ the attribute _fit: true_.
+
+ For example, the following code will align three components as columns, with
+ the second filling the available container space between the first and third:
+
+ enyo.kind({
+ kind: "FittableColumns",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+
+ Alternatively, you may set a kind's _layoutKind_ property to
+ <a href="#enyo.FittableColumnsLayout">enyo.FittableColumnsLayout</a>
+ to use a different base kind while still employing the fittable layout
+ strategy, e.g.:
+
+ enyo.kind({
+ kind: enyo.Control,
+ layoutKind: "FittableColumnsLayout",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+*/
+
+enyo.kind({
+ name: "enyo.FittableColumns",
+ layoutKind: "FittableColumnsLayout",
+ /** By default, items in columns stretch to fit vertically; set to true to
+ avoid this behavior. */
+ noStretch: false
+});
diff --git a/html/lib/layout/fittable/source/FittableLayout.css b/html/lib/layout/fittable/source/FittableLayout.css
new file mode 100644
index 0000000..cdc24b8
--- /dev/null
+++ b/html/lib/layout/fittable/source/FittableLayout.css
@@ -0,0 +1,69 @@
+.enyo-fittable-rows-layout {
+ position: relative;
+}
+
+.enyo-fittable-rows-layout > * {
+ box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ /* float when not stretched */
+ float: left;
+ clear: both;
+}
+
+/* non-floating when stretched */
+.enyo-fittable-rows-layout.enyo-stretch > * {
+ float: none;
+ clear: none;
+}
+
+/* setting to enforce margin collapsing */
+/* NOTE: rows cannot have margin left/right */
+.enyo-fittable-rows-layout.enyo-stretch.enyo-margin-expand > * {
+ float: left;
+ clear: both;
+ width: 100%;
+ /* note: harsh resets */
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+}
+
+.enyo-fittable-columns-layout {
+ position: relative;
+ text-align: left;
+ white-space: nowrap;
+}
+
+.enyo-fittable-columns-layout.enyo-center {
+ text-align: center;
+}
+
+.enyo-fittable-columns-layout > * {
+ box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ vertical-align: top;
+ display: inline-block;
+ white-space: normal;
+}
+
+.enyo-fittable-columns-layout.enyo-tool-decorator > * {
+ vertical-align: middle;
+}
+
+/* repair clobbered white-space setting for pre, code */
+.enyo-fittable-columns-layout > pre, .enyo-fittable-columns-layout > code {
+ white-space: pre;
+}
+
+.enyo-fittable-columns-layout > .enyo-fittable-columns-layout, .enyo-fittable-columns-layout > .onyx-toolbar-inline {
+ white-space: nowrap;
+}
+
+/* NOTE: columns cannot have margin top/bottom */
+.enyo-fittable-columns-layout.enyo-stretch > * {
+ height: 100%;
+ /* note: harsh resets */
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+} \ No newline at end of file
diff --git a/html/lib/layout/fittable/source/FittableLayout.js b/html/lib/layout/fittable/source/FittableLayout.js
new file mode 100644
index 0000000..6045e65
--- /dev/null
+++ b/html/lib/layout/fittable/source/FittableLayout.js
@@ -0,0 +1,265 @@
+/**
+ _enyo.FittableLayout_ provides the base positioning and boundary logic for
+ the fittable layout strategy. The fittable layout strategy is based on
+ laying out items in either a set of rows or a set of columns, with most of
+ the items having natural size, but one item expanding to fill the remaining
+ space. The item that expands is labeled with the attribute _fit: true_.
+
+ For example, in the following kind, the second component fills the available
+ space in the container between the first and third components.
+
+ enyo.kind({
+ kind: "FittableRows",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+
+ <a href="#enyo.FittableColumnsLayout">enyo.FittableColumnsLayout</a> and
+ <a href="#enyo.FittableRowsLayout">enyo.FittableRowsLayout</a> (or their
+ subkinds) are used for layout rather than _enyo.FittableLayout_ because they
+ specify properties that _enyo.FittableLayout_ expects to be available when
+ laying items out.
+*/
+enyo.kind({
+ name: "enyo.FittableLayout",
+ kind: "Layout",
+ //* @protected
+ calcFitIndex: function() {
+ for (var i=0, c$=this.container.children, c; c=c$[i]; i++) {
+ if (c.fit && c.showing) {
+ return i;
+ }
+ }
+ },
+ getFitControl: function() {
+ var c$=this.container.children;
+ var f = c$[this.fitIndex];
+ if (!(f && f.fit && f.showing)) {
+ this.fitIndex = this.calcFitIndex();
+ f = c$[this.fitIndex];
+ }
+ return f;
+ },
+ getLastControl: function() {
+ var c$=this.container.children;
+ var i = c$.length-1;
+ var c = c$[i];
+ while ((c=c$[i]) && !c.showing) {
+ i--;
+ }
+ return c;
+ },
+ _reflow: function(measure, cMeasure, mAttr, nAttr) {
+ this.container.addRemoveClass("enyo-stretch", !this.container.noStretch);
+ var f = this.getFitControl();
+ // no sizing if nothing is fit.
+ if (!f) {
+ return;
+ }
+ //
+ // determine container size, available space
+ var s=0, a=0, b=0, p;
+ var n = this.container.hasNode();
+ // calculate available space
+ if (n) {
+ // measure 1
+ p = enyo.FittableLayout.calcPaddingExtents(n);
+ // measure 2
+ s = n[cMeasure] - (p[mAttr] + p[nAttr]);
+ //console.log("overall size", s);
+ }
+ //
+ // calculate space above fitting control
+ // measure 3
+ var fb = f.getBounds();
+ // offset - container padding.
+ a = fb[mAttr] - ((p && p[mAttr]) || 0);
+ //console.log("above", a);
+ //
+ // calculate space below fitting control
+ var l = this.getLastControl();
+ if (l) {
+ // measure 4
+ var mb = enyo.FittableLayout.getComputedStyleValue(l.hasNode(), "margin", nAttr) || 0;
+ if (l != f) {
+ // measure 5
+ var lb = l.getBounds();
+ // fit offset + size
+ var bf = fb[mAttr] + fb[measure];
+ // last offset + size + ending margin
+ var bl = lb[mAttr] + lb[measure] + mb;
+ // space below is bottom of last item - bottom of fit item.
+ b = bl - bf;
+ } else {
+ b = mb;
+ }
+ }
+
+ // calculate appropriate size for fit control
+ var fs = s - (a + b);
+ //console.log(f.id, fs);
+ // note: must be border-box;
+ f.applyStyle(measure, fs + "px");
+ },
+ //* @public
+ /**
+ Updates the layout to reflect any changes to contained components or the
+ layout container.
+ */
+ reflow: function() {
+ if (this.orient == "h") {
+ this._reflow("width", "clientWidth", "left", "right");
+ } else {
+ this._reflow("height", "clientHeight", "top", "bottom");
+ }
+ },
+ statics: {
+ //* @protected
+ _ieCssToPixelValue: function(inNode, inValue) {
+ var v = inValue;
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var s = inNode.style;
+ // store style and runtime style values
+ var l = s.left;
+ var rl = inNode.runtimeStyle && inNode.runtimeStyle.left;
+ // then put current style in runtime style.
+ if (rl) {
+ inNode.runtimeStyle.left = inNode.currentStyle.left;
+ }
+ // apply given value and measure its pixel value
+ s.left = v;
+ v = s.pixelLeft;
+ // finally restore previous state
+ s.left = l;
+ if (rl) {
+ s.runtimeStyle.left = rl;
+ }
+ return v;
+ },
+ _pxMatch: /px/i,
+ getComputedStyleValue: function(inNode, inProp, inBoundary, inComputedStyle) {
+ var s = inComputedStyle || enyo.dom.getComputedStyle(inNode);
+ if (s) {
+ return parseInt(s.getPropertyValue(inProp + "-" + inBoundary));
+ } else if (inNode && inNode.currentStyle) {
+ var v = inNode.currentStyle[inProp + enyo.cap(inBoundary)];
+ if (!v.match(this._pxMatch)) {
+ v = this._ieCssToPixelValue(inNode, v);
+ }
+ return parseInt(v);
+ }
+ return 0;
+ },
+ //* Gets the boundaries of a node's margin or padding box.
+ calcBoxExtents: function(inNode, inBox) {
+ var s = enyo.dom.getComputedStyle(inNode);
+ return {
+ top: this.getComputedStyleValue(inNode, inBox, "top", s),
+ right: this.getComputedStyleValue(inNode, inBox, "right", s),
+ bottom: this.getComputedStyleValue(inNode, inBox, "bottom", s),
+ left: this.getComputedStyleValue(inNode, inBox, "left", s)
+ };
+ },
+ //* Gets the calculated padding of a node.
+ calcPaddingExtents: function(inNode) {
+ return this.calcBoxExtents(inNode, "padding");
+ },
+ //* Gets the calculated margin of a node.
+ calcMarginExtents: function(inNode) {
+ return this.calcBoxExtents(inNode, "margin");
+ }
+ }
+});
+
+/**
+ _enyo.FittableColumnsLayout_ provides a container in which items are laid
+ out in a set of vertical columns, with most of the items having natural
+ size, but one expanding to fill the remaining space. The one that expands is
+ labeled with the attribute _fit: true_.
+
+ _enyo.FittableColumnsLayout_ is meant to be used as a value for the
+ _layoutKind_ property of other kinds. _layoutKind_ provides a way to add
+ layout behavior in a pluggable fashion while retaining the ability to use a
+ specific base kind.
+
+ For example, the following code will align three components as columns, with
+ the second filling the available container space between the first and third.
+
+ enyo.kind({
+ kind: enyo.Control,
+ layoutKind: "FittableColumnsLayout",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+
+ Alternatively, if a specific base kind is not needed, then instead of
+ setting the _layoutKind_ attribute, you can set the base kind to
+ <a href="#enyo.FittableColumns">enyo.FittableColumns</a>:
+
+ enyo.kind({
+ kind: "FittableColumns",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+*/
+enyo.kind({
+ name: "enyo.FittableColumnsLayout",
+ kind: "FittableLayout",
+ orient: "h",
+ layoutClass: "enyo-fittable-columns-layout"
+});
+
+
+/**
+ _enyo.FittableRowsLayout_ provides a container in which items are laid out
+ in a set of horizontal rows, with most of the items having natural size, but
+ one expanding to fill the remaining space. The one that expands is labeled
+ with the attribute _fit: true_.
+
+ _enyo.FittableRowsLayout_ is meant to be used as a value for the
+ _layoutKind_ property of other kinds. _layoutKind_ provides a way to add
+ layout behavior in a pluggable fashion while retaining the ability to use a
+ specific base kind.
+
+ For example, the following code will align three components as rows, with
+ the second filling the available container space between the first and third.
+
+ enyo.kind({
+ kind: enyo.Control,
+ layoutKind: "FittableRowsLayout",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+
+ Alternatively, if a specific base kind is not needed, then instead of
+ setting the _layoutKind_ attribute, you can set the base kind to
+ <a href="#enyo.FittableRows">enyo.FittableRows</a>:
+
+ enyo.kind({
+ kind: "FittableRows",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+*/
+enyo.kind({
+ name: "enyo.FittableRowsLayout",
+ kind: "FittableLayout",
+ layoutClass: "enyo-fittable-rows-layout",
+ orient: "v"
+});
diff --git a/html/lib/layout/fittable/source/FittableRows.js b/html/lib/layout/fittable/source/FittableRows.js
new file mode 100644
index 0000000..985f82d
--- /dev/null
+++ b/html/lib/layout/fittable/source/FittableRows.js
@@ -0,0 +1,40 @@
+/**
+ _enyo.FittableRows_ provides a container in which items are laid out in a
+ set of horizontal rows, with most of the items having natural size, but one
+ expanding to fill the remaining space. The one that expands is labeled with
+ the attribute _fit: true_.
+
+ For example, the following code will align three components as rows, with
+ the second filling the available container space between the first and third.
+
+ enyo.kind({
+ kind: "FittableRows",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+
+ Alternatively, you may set a kind's _layoutKind_ property to
+ <a href="#enyo.FittableRowsLayout">enyo.FittableRowsLayout</a>
+ to use a different base kind while still employing the fittable layout
+ strategy, e.g.:
+
+ enyo.kind({
+ kind: enyo.Control,
+ layoutKind: "FittableRowsLayout",
+ components: [
+ {content: "1"},
+ {content: "2", fit:true},
+ {content: "3"}
+ ]
+ });
+*/
+enyo.kind({
+ name: "enyo.FittableRows",
+ layoutKind: "FittableRowsLayout",
+ /** By default, items in rows stretch to fit horizontally; set to true to
+ avoid this behavior. */
+ noStretch: false
+});
diff --git a/html/lib/layout/fittable/source/design.js b/html/lib/layout/fittable/source/design.js
new file mode 100644
index 0000000..ab94505
--- /dev/null
+++ b/html/lib/layout/fittable/source/design.js
@@ -0,0 +1,23 @@
+/**
+ Description to make fittable kinds available in Ares.
+*/
+Palette.model.push(
+ {name: "fittable", items: [
+ {name: "FittableRows", title: "Vertical stacked layout", icon: "package_new.png", stars: 4.5, version: 2.0, blurb: "Stack of vertical rows, one of which can be made to fit.",
+ inline: {kind: "FittableRows", style: "height: 80px; position: relative;", padding: 4, components: [
+ {style: "background-color: lightblue; border: 1px dotted blue; height: 15px;"},
+ {style: "background-color: lightblue; border: 1px dotted blue;", fit: true},
+ {style: "background-color: lightblue; border: 1px dotted blue; height: 15px;"}
+ ]},
+ config: {content: "$name", isContainer: true, kind: "FittableRows", padding: 10, margin: 10}
+ },
+ {name: "FittableColumns", title: "Horizontal stacked layout", icon: "package_new.png", stars: 4.5, version: 2.0, blurb: "Stack of horizontal columns, one of which can be made to fit.",
+ inline: {kind: "FittableColumns", style: "height: 60px; position: relative;", padding: 4, components: [
+ {style: "background-color: lightblue; border: 1px dotted blue; width: 20px;"},
+ {style: "background-color: lightblue; border: 1px dotted blue;", fit: true},
+ {style: "background-color: lightblue; border: 1px dotted blue; width: 20px;"}
+ ]},
+ config: {content: "$name", isContainer: true, kind: "FittableColumns", padding: 10, margin: 10}
+ }
+ ]}
+); \ No newline at end of file
diff --git a/html/lib/layout/fittable/source/package.js b/html/lib/layout/fittable/source/package.js
new file mode 100644
index 0000000..74201af
--- /dev/null
+++ b/html/lib/layout/fittable/source/package.js
@@ -0,0 +1,6 @@
+enyo.depends(
+ "FittableLayout.css",
+ "FittableLayout.js",
+ "FittableRows.js",
+ "FittableColumns.js"
+); \ No newline at end of file
diff --git a/html/lib/layout/list/package.js b/html/lib/layout/list/package.js
new file mode 100644
index 0000000..f1de4f2
--- /dev/null
+++ b/html/lib/layout/list/package.js
@@ -0,0 +1,3 @@
+enyo.depends(
+ "source/"
+); \ No newline at end of file
diff --git a/html/lib/layout/list/source/AlphaJumpList.js b/html/lib/layout/list/source/AlphaJumpList.js
new file mode 100644
index 0000000..6b6e326
--- /dev/null
+++ b/html/lib/layout/list/source/AlphaJumpList.js
@@ -0,0 +1,37 @@
+/**
+ A control that presents an alphabetic panel that you can select from, in
+ order to perform actions based on the item selected.
+
+ {kind: "AlphaJumpList", onSetupItem: "setupItem",
+ onAlphaJump: "alphaJump",
+ components: [
+ {name: "divider"},
+ {kind: "onyx.Item"}
+ ]
+ }
+
+*/
+enyo.kind({
+ name: "enyo.AlphaJumpList",
+ kind: "List",
+ //* @protected
+ scrollTools: [
+ {name: "jumper", kind: "AlphaJumper"}
+ ],
+ initComponents: function() {
+ this.createChrome(this.scrollTools);
+ this.inherited(arguments);
+ },
+ rendered: function() {
+ this.inherited(arguments);
+ this.centerJumper();
+ },
+ resizeHandler: function() {
+ this.inherited(arguments);
+ this.centerJumper();
+ },
+ centerJumper: function() {
+ var b = this.getBounds(), sb = this.$.jumper.getBounds();
+ this.$.jumper.applyStyle("top", ((b.height - sb.height) / 2) + "px");
+ }
+}); \ No newline at end of file
diff --git a/html/lib/layout/list/source/AlphaJumper.css b/html/lib/layout/list/source/AlphaJumper.css
new file mode 100644
index 0000000..df1265c
--- /dev/null
+++ b/html/lib/layout/list/source/AlphaJumper.css
@@ -0,0 +1,36 @@
+.enyo-alpha-jumper {
+ text-transform: uppercase;
+ font-size: 11px;
+ xborder-radius: 12px;
+ position: absolute;
+ xbackground: white;
+ text-align: center;
+ right: 10px;
+ z-index: 1;
+ color: #333;
+ padding: 0 14px;
+}
+
+.enyo-alpha-jumper > *:first-child {
+ padding-top: 4px;
+ border-radius: 12px 12px 0 0;
+ border-top: 1px solid #333;
+}
+
+.enyo-alpha-jumper > *:last-child {
+ padding-bottom: 4px;
+ border-radius: 0 0 12px 12px;
+ border-bottom: 1px solid #333;
+}
+
+.enyo-alpha-jumper > * {
+ background: #eee;
+ padding: 1px 4px;
+ border-right: 1px solid #333;
+ border-left: 1px solid #333;
+}
+
+.enyo-alpha-jumper > .active {
+ background: #333;
+ color: white;
+} \ No newline at end of file
diff --git a/html/lib/layout/list/source/AlphaJumper.js b/html/lib/layout/list/source/AlphaJumper.js
new file mode 100644
index 0000000..2f85d00
--- /dev/null
+++ b/html/lib/layout/list/source/AlphaJumper.js
@@ -0,0 +1,70 @@
+enyo.kind({
+ name: "enyo.AlphaJumper",
+ classes: "enyo-alpha-jumper enyo-border-box",
+ published: {
+ marker: null
+ },
+ events: {
+ onAlphaJump: ""
+ },
+ handlers: {
+ ondown: "down",
+ onmove: "move",
+ onup: "up"
+ },
+ initComponents: function() {
+ for (var s="A".charCodeAt(0), i=s; i<s+26; i++) {
+ this.createComponent({content: String.fromCharCode(i)});
+ }
+ this.inherited(arguments);
+ },
+ down: function(inSender, inEvent) {
+ if (this.tracking) {
+ enyo.dispatcher.release();
+ }
+ this.tracking = true;
+ if (this.hasNode()) {
+ var b = this.node.getBoundingClientRect();
+ // IE8 does not return width
+ var w = (b.width === undefined) ? (b.right - b.left) : b.width;
+ this.x = b.left + w/2;
+ }
+ enyo.dispatcher.capture(this);
+ this.track(inEvent);
+ },
+ move: function(inSender, inEvent) {
+ if (this.tracking) {
+ this.track(inEvent);
+ }
+ },
+ up: function() {
+ if (this.tracking) {
+ enyo.dispatcher.release();
+ this.setMarker(null);
+ this.tracking = false;
+ }
+ },
+ track: function(inEvent) {
+ var n = document.elementFromPoint(this.x, inEvent.pageY);
+ var c = this.nodeToControl(n);
+ if (c) {
+ this.setMarker(c);
+ }
+ },
+ nodeToControl: function(inNode) {
+ for (var i=0, c$=this.controls, c; c=c$[i]; i++) {
+ if (inNode == c.hasNode()) {
+ return c;
+ }
+ }
+ },
+ markerChanged: function(inLast) {
+ if (inLast) {
+ inLast.removeClass("active");
+ }
+ if (this.marker) {
+ this.marker.addClass("active");
+ this.doAlphaJump({letter: this.marker.getContent(), index: this.marker.indexInContainer()});
+ }
+ }
+}); \ No newline at end of file
diff --git a/html/lib/layout/list/source/FlyweightRepeater.js b/html/lib/layout/list/source/FlyweightRepeater.js
new file mode 100644
index 0000000..01e6d80
--- /dev/null
+++ b/html/lib/layout/list/source/FlyweightRepeater.js
@@ -0,0 +1,175 @@
+/**
+ A control that displays a repeating list of rows, suitable for displaying
+ medium-sized lists (up to ~100 items). A flyweight strategy is employed to
+ render one set of row controls, as needed, for as many rows as are contained
+ in the repeater.
+
+ The FlyweightRepeater's _components_ block contains the controls to be used
+ for a single row. This set of controls will be rendered for each row. You
+ may customize row rendering by handling the _onSetupItem_ event.
+
+ The controls inside a FlyweightRepeater are non-interactive. This means that
+ calling methods that would normally cause rendering to occur (e.g.,
+ _setContent_) will not do so. However, you can force a row to render by
+ calling _renderRow(inRow)_.
+
+ In addition, you can force a row to be temporarily interactive by calling
+ _prepareRow(inRow)_. Call the _lockRow_ method when the interaction is
+ complete.
+
+ For more information, see the documentation on
+ [Lists](https://github.com/enyojs/enyo/wiki/Lists)
+ in the Enyo Developer Guide.
+*/
+enyo.kind({
+ name: "enyo.FlyweightRepeater",
+ published: {
+ //* Number of rows to render
+ count: 0,
+ //* If true, multiple selections are allowed
+ multiSelect: false,
+ //* If true, the selected item will toggle
+ toggleSelected: false
+ },
+ events: {
+ /**
+ Fires once per row at render time, with event object:
+ _{index: <index of row>, selected: <true if row is selected>}_
+ */
+ onSetupItem: ""
+ },
+ components: [
+ {kind: "Selection", onSelect: "selectDeselect", onDeselect: "selectDeselect"},
+ {name: "client"}
+ ],
+ rowOffset: 0,
+ bottomUp: false,
+ //* @protected
+ create: function() {
+ this.inherited(arguments);
+ this.multiSelectChanged();
+ },
+ multiSelectChanged: function() {
+ this.$.selection.setMulti(this.multiSelect);
+ },
+ setupItem: function(inIndex) {
+ this.doSetupItem({index: inIndex, selected: this.isSelected(inIndex)});
+ },
+ //* Renders the list.
+ generateChildHtml: function() {
+ var h = "";
+ this.index = null;
+ // note: can supply a rowOffset
+ // and indicate if rows should be rendered top down or bottomUp
+ for (var i=0, r=0; i<this.count; i++) {
+ r = this.rowOffset + (this.bottomUp ? this.count - i-1 : i);
+ this.setupItem(r);
+ this.$.client.setAttribute("index", r);
+ h += this.inherited(arguments);
+ this.$.client.teardownRender();
+ }
+ return h;
+ },
+ previewDomEvent: function(inEvent) {
+ var i = this.index = this.rowForEvent(inEvent);
+ inEvent.rowIndex = inEvent.index = i;
+ inEvent.flyweight = this;
+ },
+ decorateEvent: function(inEventName, inEvent, inSender) {
+ // decorate event with index found via dom iff event does not already contain an index.
+ var i = (inEvent && inEvent.index != null) ? inEvent.index : this.index;
+ if (inEvent && i != null) {
+ inEvent.index = i;
+ inEvent.flyweight = this;
+ }
+ this.inherited(arguments);
+ },
+ tap: function(inSender, inEvent) {
+ if (this.toggleSelected) {
+ this.$.selection.toggle(inEvent.index);
+ } else {
+ this.$.selection.select(inEvent.index);
+ }
+ },
+ selectDeselect: function(inSender, inEvent) {
+ this.renderRow(inEvent.key);
+ },
+ //* @public
+ //* Returns the repeater's _selection_ component.
+ getSelection: function() {
+ return this.$.selection;
+ },
+ //* Gets the selection state for the given row index.
+ isSelected: function(inIndex) {
+ return this.getSelection().isSelected(inIndex);
+ },
+ //* Renders the row specified by _inIndex_.
+ renderRow: function(inIndex) {
+ //this.index = null;
+ var node = this.fetchRowNode(inIndex);
+ if (node) {
+ this.setupItem(inIndex);
+ node.innerHTML = this.$.client.generateChildHtml();
+ this.$.client.teardownChildren();
+ }
+ },
+ //* Fetches the DOM node for the given row index.
+ fetchRowNode: function(inIndex) {
+ if (this.hasNode()) {
+ var n$ = this.node.querySelectorAll('[index="' + inIndex + '"]');
+ return n$ && n$[0];
+ }
+ },
+ //* Fetches the DOM node for the given event.
+ rowForEvent: function(inEvent) {
+ var n = inEvent.target;
+ var id = this.hasNode().id;
+ while (n && n.parentNode && n.id != id) {
+ var i = n.getAttribute && n.getAttribute("index");
+ if (i !== null) {
+ return Number(i);
+ }
+ n = n.parentNode;
+ }
+ return -1;
+ },
+ //* Prepares the row specified by _inIndex_ such that changes made to the
+ //* controls inside the repeater will be rendered for the given row.
+ prepareRow: function(inIndex) {
+ var n = this.fetchRowNode(inIndex);
+ enyo.FlyweightRepeater.claimNode(this.$.client, n);
+ },
+ //* Prevents rendering of changes made to controls inside the repeater.
+ lockRow: function() {
+ this.$.client.teardownChildren();
+ },
+ //* Prepares the row specified by _inIndex_ such that changes made to the
+ //* controls in the row will be rendered in the given row; then performs the
+ //* function _inFunc_, and, finally, locks the row.
+ performOnRow: function(inIndex, inFunc, inContext) {
+ if (inFunc) {
+ this.prepareRow(inIndex);
+ enyo.call(inContext || null, inFunc);
+ this.lockRow();
+ }
+ },
+ statics: {
+ //* Associates a flyweight rendered control (_inControl_) with a
+ //* rendering context specified by _inNode_.
+ claimNode: function(inControl, inNode) {
+ var n = inNode && inNode.querySelectorAll("#" + inControl.id);
+ n = n && n[0];
+ // FIXME: consider controls generated if we found a node or tag: null, the later so can teardown render
+ inControl.generated = Boolean(n || !inControl.tag);
+ inControl.node = n;
+ if (inControl.node) {
+ inControl.rendered();
+ } else {
+ //enyo.log("Failed to find node for", inControl.id, inControl.generated);
+ }
+ for (var i=0, c$=inControl.children, c; c=c$[i]; i++) {
+ this.claimNode(c, inNode);
+ }
+ }
+ }
+}); \ No newline at end of file
diff --git a/html/lib/layout/list/source/List.css b/html/lib/layout/list/source/List.css
new file mode 100644
index 0000000..72da968
--- /dev/null
+++ b/html/lib/layout/list/source/List.css
@@ -0,0 +1,15 @@
+.enyo-list {
+ position: relative;
+}
+
+.enyo-list-port {
+ overflow: hidden;
+ position: relative;
+ height: 10000000px;
+}
+
+.enyo-list-page {
+ position: absolute;
+ left: 0;
+ right: 0;
+} \ No newline at end of file
diff --git a/html/lib/layout/list/source/List.js b/html/lib/layout/list/source/List.js
new file mode 100644
index 0000000..c4252b9
--- /dev/null
+++ b/html/lib/layout/list/source/List.js
@@ -0,0 +1,368 @@
+/**
+ A control that displays a scrolling list of rows, suitable for displaying
+ very large lists. _enyo.List_ is optimized such that only a small portion of
+ the list is rendered at a given time. A flyweight pattern is employed, in
+ which controls placed inside the list are created once, but rendered for
+ each list item. For this reason, it's best to use only simple controls in
+ a List, such as <a href="#enyo.Control">enyo.Control</a> and
+ <a href="#enyo.Image">enyo.Image</a>.
+
+ A List's _components_ block contains the controls to be used for a single
+ row. This set of controls will be rendered for each row. You may customize
+ row rendering by handling the _onSetupItem_ event.
+
+ Events fired from within list rows contain the _index_ property, which may
+ be used to identify the row from which the event originated.
+
+ The controls inside a List are non-interactive. This means that calling
+ methods that would normally cause rendering to occur (e.g., _setContent_)
+ will not do so. However, you can force a row to render by calling
+ _renderRow(inRow)_.
+
+ In addition, you can force a row to be temporarily interactive by calling
+ _prepareRow(inRow)_. Call the _lockRow_ method when the interaction is
+ complete.
+
+ For more information, see the documentation on
+ [Lists](https://github.com/enyojs/enyo/wiki/Lists)
+ in the Enyo Developer Guide.
+*/
+enyo.kind({
+ name: "enyo.List",
+ kind: "Scroller",
+ classes: "enyo-list",
+ published: {
+ /**
+ The number of rows contained in the list. Note that as the amount of
+ list data changes, _setRows_ can be called to adjust the number of
+ rows. To re-render the list at the current position when the count
+ has changed, call the _refresh_ method.
+ */
+ count: 0,
+ /**
+ The number of rows to be shown on a given list page segment.
+ There is generally no need to adjust this value.
+ */
+ rowsPerPage: 50,
+ /**
+ If true, renders the list such that row 0 is at the bottom of the
+ viewport and the beginning position of the list is scrolled to the
+ bottom
+ */
+ bottomUp: false,
+ //* If true, multiple selections are allowed
+ multiSelect: false,
+ //* If true, the selected item will toggle
+ toggleSelected: false,
+ //* If true, the list will assume all rows have the same height for optimization
+ fixedHeight: false
+ },
+ events: {
+ /**
+ Fires once per row at render time, with event object:
+ _{index: <index of row>}_
+ */
+ onSetupItem: ""
+ },
+ handlers: {
+ onAnimateFinish: "animateFinish"
+ },
+ //* @protected
+ rowHeight: 0,
+ listTools: [
+ {name: "port", classes: "enyo-list-port enyo-border-box", components: [
+ {name: "generator", kind: "FlyweightRepeater", canGenerate: false, components: [
+ {tag: null, name: "client"}
+ ]},
+ {name: "page0", allowHtml: true, classes: "enyo-list-page"},
+ {name: "page1", allowHtml: true, classes: "enyo-list-page"}
+ ]}
+ ],
+ create: function() {
+ this.pageHeights = [];
+ this.inherited(arguments);
+ this.getStrategy().translateOptimized = true;
+ this.bottomUpChanged();
+ this.multiSelectChanged();
+ this.toggleSelectedChanged();
+ },
+ createStrategy: function() {
+ this.controlParentName = "strategy";
+ this.inherited(arguments);
+ this.createChrome(this.listTools);
+ this.controlParentName = "client";
+ this.discoverControlParent();
+ },
+ rendered: function() {
+ this.inherited(arguments);
+ this.$.generator.node = this.$.port.hasNode();
+ this.$.generator.generated = true;
+ this.reset();
+ },
+ resizeHandler: function() {
+ this.inherited(arguments);
+ this.refresh();
+ },
+ bottomUpChanged: function() {
+ this.$.generator.bottomUp = this.bottomUp;
+ this.$.page0.applyStyle(this.pageBound, null);
+ this.$.page1.applyStyle(this.pageBound, null);
+ this.pageBound = this.bottomUp ? "bottom" : "top";
+ if (this.hasNode()) {
+ this.reset();
+ }
+ },
+ multiSelectChanged: function() {
+ this.$.generator.setMultiSelect(this.multiSelect);
+ },
+ toggleSelectedChanged: function() {
+ this.$.generator.setToggleSelected(this.toggleSelected);
+ },
+ countChanged: function() {
+ if (this.hasNode()) {
+ this.updateMetrics();
+ }
+ },
+ updateMetrics: function() {
+ this.defaultPageHeight = this.rowsPerPage * (this.rowHeight || 100);
+ this.pageCount = Math.ceil(this.count / this.rowsPerPage);
+ this.portSize = 0;
+ for (var i=0; i < this.pageCount; i++) {
+ this.portSize += this.getPageHeight(i);
+ }
+ this.adjustPortSize();
+ },
+ generatePage: function(inPageNo, inTarget) {
+ this.page = inPageNo;
+ var r = this.$.generator.rowOffset = this.rowsPerPage * this.page;
+ var rpp = this.$.generator.count = Math.min(this.count - r, this.rowsPerPage);
+ var html = this.$.generator.generateChildHtml();
+ inTarget.setContent(html);
+ var pageHeight = inTarget.getBounds().height;
+ // if rowHeight is not set, use the height from the first generated page
+ if (!this.rowHeight && pageHeight > 0) {
+ this.rowHeight = Math.floor(pageHeight / rpp);
+ this.updateMetrics();
+ }
+ // update known page heights
+ if (!this.fixedHeight) {
+ var h0 = this.getPageHeight(inPageNo);
+ if (h0 != pageHeight && pageHeight > 0) {
+ this.pageHeights[inPageNo] = pageHeight;
+ this.portSize += pageHeight - h0;
+ }
+ }
+ },
+ update: function(inScrollTop) {
+ var updated = false;
+ // get page info for position
+ var pi = this.positionToPageInfo(inScrollTop);
+ // zone line position
+ var pos = pi.pos + this.scrollerHeight/2;
+ // leap-frog zone position
+ var k = Math.floor(pos/Math.max(pi.height, this.scrollerHeight) + 1/2) + pi.no;
+ // which page number for page0 (even number pages)?
+ var p = k % 2 == 0 ? k : k-1;
+ if (this.p0 != p && this.isPageInRange(p)) {
+ //this.log("update page0", p);
+ this.generatePage(p, this.$.page0);
+ this.positionPage(p, this.$.page0);
+ this.p0 = p;
+ updated = true;
+ }
+ // which page number for page1 (odd number pages)?
+ p = k % 2 == 0 ? Math.max(1, k-1) : k;
+ // position data page 1
+ if (this.p1 != p && this.isPageInRange(p)) {
+ //this.log("update page1", p);
+ this.generatePage(p, this.$.page1);
+ this.positionPage(p, this.$.page1);
+ this.p1 = p;
+ updated = true;
+ }
+ if (updated && !this.fixedHeight) {
+ this.adjustBottomPage();
+ this.adjustPortSize();
+ }
+ },
+ updateForPosition: function(inPos) {
+ this.update(this.calcPos(inPos));
+ },
+ calcPos: function(inPos) {
+ return (this.bottomUp ? (this.portSize - this.scrollerHeight - inPos) : inPos);
+ },
+ adjustBottomPage: function() {
+ var bp = this.p0 >= this.p1 ? this.$.page0 : this.$.page1;
+ this.positionPage(bp.pageNo, bp);
+ },
+ adjustPortSize: function() {
+ this.scrollerHeight = this.getBounds().height;
+ var s = Math.max(this.scrollerHeight, this.portSize);
+ this.$.port.applyStyle("height", s + "px");
+ },
+ positionPage: function(inPage, inTarget) {
+ inTarget.pageNo = inPage;
+ var y = this.pageToPosition(inPage);
+ inTarget.applyStyle(this.pageBound, y + "px");
+ },
+ pageToPosition: function(inPage) {
+ var y = 0;
+ var p = inPage;
+ while (p > 0) {
+ p--;
+ y += this.getPageHeight(p);
+ }
+ return y;
+ },
+ positionToPageInfo: function(inY) {
+ var page = -1;
+ var p = this.calcPos(inY);
+ var h = this.defaultPageHeight;
+ while (p >= 0) {
+ page++;
+ h = this.getPageHeight(page);
+ p -= h;
+ }
+ //page = Math.min(page, this.pageCount-1);
+ return {no: page, height: h, pos: p+h};
+ },
+ isPageInRange: function(inPage) {
+ return inPage == Math.max(0, Math.min(this.pageCount-1, inPage));
+ },
+ getPageHeight: function(inPageNo) {
+ return this.pageHeights[inPageNo] || this.defaultPageHeight;
+ },
+ invalidatePages: function() {
+ this.p0 = this.p1 = null;
+ // clear the html in our render targets
+ this.$.page0.setContent("");
+ this.$.page1.setContent("");
+ },
+ invalidateMetrics: function() {
+ this.pageHeights = [];
+ this.rowHeight = 0;
+ this.updateMetrics();
+ },
+ scroll: function(inSender, inEvent) {
+ var r = this.inherited(arguments);
+ this.update(this.getScrollTop());
+ return r;
+ },
+ //* @public
+ scrollToBottom: function() {
+ this.update(this.getScrollBounds().maxTop);
+ this.inherited(arguments);
+ },
+ setScrollTop: function(inScrollTop) {
+ this.update(inScrollTop);
+ this.inherited(arguments);
+ this.twiddle();
+ },
+ getScrollPosition: function() {
+ return this.calcPos(this.getScrollTop());
+ },
+ setScrollPosition: function(inPos) {
+ this.setScrollTop(this.calcPos(inPos));
+ },
+ //* Scrolls to a specific row.
+ scrollToRow: function(inRow) {
+ var page = Math.floor(inRow / this.rowsPerPage);
+ var pageRow = inRow % this.rowsPerPage;
+ var h = this.pageToPosition(page);
+ // update the page
+ this.updateForPosition(h);
+ // call pageToPosition again and this time should return the right pos since the page info is populated
+ h = this.pageToPosition(page);
+ this.setScrollPosition(h);
+ if (page == this.p0 || page == this.p1) {
+ var rowNode = this.$.generator.fetchRowNode(inRow);
+ if (rowNode) {
+ // calc row offset
+ var offset = rowNode.offsetTop;
+ if (this.bottomUp) {
+ offset = this.getPageHeight(page) - rowNode.offsetHeight - offset;
+ }
+ var y = this.getScrollPosition() + offset;
+ this.setScrollPosition(y);
+ }
+ }
+ },
+ //* Scrolls to the beginning of the list.
+ scrollToStart: function() {
+ this[this.bottomUp ? "scrollToBottom" : "scrollToTop"]();
+ },
+ //* Scrolls to the end of the list.
+ scrollToEnd: function() {
+ this[this.bottomUp ? "scrollToTop" : "scrollToBottom"]();
+ },
+ //* Re-renders the list at the current position.
+ refresh: function() {
+ this.invalidatePages();
+ this.update(this.getScrollTop());
+ this.stabilize();
+
+ //FIXME: Necessary evil for Android 4.0.4 refresh bug
+ if (enyo.platform.android === 4) {
+ this.twiddle();
+ }
+ },
+ //* Re-renders the list from the beginning.
+ reset: function() {
+ this.getSelection().clear();
+ this.invalidateMetrics();
+ this.invalidatePages();
+ this.stabilize();
+ this.scrollToStart();
+ },
+ /**
+ Returns the _selection_ component that manages the selection state for
+ this list.
+ */
+ getSelection: function() {
+ return this.$.generator.getSelection();
+ },
+ //* Sets the selection state for the given row index.
+ select: function(inIndex, inData) {
+ return this.getSelection().select(inIndex, inData);
+ },
+ //* Gets the selection state for the given row index.
+ isSelected: function(inIndex) {
+ return this.$.generator.isSelected(inIndex);
+ },
+ /**
+ Re-renders the specified row. Call after making modifications to a row,
+ to force it to render.
+ */
+ renderRow: function(inIndex) {
+ this.$.generator.renderRow(inIndex);
+ },
+ //* Prepares the row to become interactive.
+ prepareRow: function(inIndex) {
+ this.$.generator.prepareRow(inIndex);
+ },
+ //* Restores the row to being non-interactive.
+ lockRow: function() {
+ this.$.generator.lockRow();
+ },
+ /**
+ Performs a set of tasks by running the function _inFunc_ on a row (which
+ must be interactive at the time the tasks are performed). Locks the row
+ when done.
+ */
+ performOnRow: function(inIndex, inFunc, inContext) {
+ this.$.generator.performOnRow(inIndex, inFunc, inContext);
+ },
+ //* @protected
+ animateFinish: function(inSender) {
+ this.twiddle();
+ return true;
+ },
+ // FIXME: Android 4.04 has issues with nested composited elements; for example, a SwipeableItem,
+ // can incorrectly generate taps on its content when it has slid off the screen;
+ // we address this BUG here by forcing the Scroller to "twiddle" which corrects the bug by
+ // provoking a dom update.
+ twiddle: function() {
+ var s = this.getStrategy();
+ enyo.call(s, "twiddle");
+ }
+});
diff --git a/html/lib/layout/list/source/PulldownList.css b/html/lib/layout/list/source/PulldownList.css
new file mode 100644
index 0000000..89f6da0
--- /dev/null
+++ b/html/lib/layout/list/source/PulldownList.css
@@ -0,0 +1,60 @@
+.enyo-list-pulldown {
+ position: absolute;
+ bottom: 100%;
+ left: 0;
+ right: 0;
+}
+
+.enyo-puller {
+ position: relative;
+ height: 50px;
+ font-size: 22px;
+ color: #444;
+ padding: 20px 0 0px 34px;
+}
+
+.enyo-puller-text {
+ position: absolute;
+ left: 80px;
+ top: 22px;
+}
+
+.enyo-puller-arrow {
+ position: relative;
+ background: #444;
+ width: 7px;
+ height: 28px;
+ transition: transform 0.3s;
+ -webkit-transition: -webkit-transform 0.3s;
+ -moz-transition: -moz-transform 0.3s;
+ -o-transition: -o-transform 0.3s;
+ -ms-transition: -ms-transform 0.3s;
+}
+
+.enyo-puller-arrow:after {
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ border: 10px solid transparent;
+ border-bottom-color: #444;
+ bottom: 100%;
+ left: 50%;
+ margin-left: -10px;
+}
+
+.enyo-puller-arrow-up {
+ transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -ms-transform: rotate(0deg);
+}
+
+.enyo-puller-arrow-down {
+ transform: rotate(180deg);
+ -webkit-transform: rotate(180deg);
+ -moz-transform: rotate(180deg);
+ -o-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+} \ No newline at end of file
diff --git a/html/lib/layout/list/source/PulldownList.js b/html/lib/layout/list/source/PulldownList.js
new file mode 100644
index 0000000..8d0dab1
--- /dev/null
+++ b/html/lib/layout/list/source/PulldownList.js
@@ -0,0 +1,180 @@
+/**
+A list that provides a pull-to-refresh feature, which allows new data to be
+retrieved and updated in the list.
+
+PulldownList provides the onPullRelease event to allow an application to start
+retrieving new data. The onPullComplete event indicates that the pull is
+complete and it's time to update the list with the new data.
+
+ {name: "list", kind: "PulldownList", onSetupItem: "setupItem",
+ onPullRelease: "pullRelease", onPullComplete: "pullComplete",
+ components: [
+ {name: "item"}
+ ]}
+
+ pullRelease: function() {
+ this.search();
+ },
+ processSearchResults: function(inRequest, inResponse) {
+ this.results = inResponse.results;
+ this.$.list.setCount(this.results.length);
+ this.$.list.completePull();
+ },
+ pullComplete: function() {
+ this.$.list.reset();
+ }
+*/
+enyo.kind({
+ name: "enyo.PulldownList",
+ kind: "List",
+ touch: true,
+ pully: null,
+ pulldownTools: [
+ {name: "pulldown", classes: "enyo-list-pulldown", components: [
+ {name: "puller", kind: "Puller"}
+ ]}
+ ],
+ events: {
+ onPullStart: "",
+ onPullCancel: "",
+ onPull: "",
+ onPullRelease: "",
+ onPullComplete: ""
+ },
+ handlers: {
+ onScrollStart: "scrollStartHandler",
+ onScroll: "scrollHandler",
+ onScrollStop: "scrollStopHandler",
+ ondragfinish: "dragfinish"
+ },
+ //
+ pullingMessage: "Pull down to refresh...",
+ pulledMessage: "Release to refresh...",
+ loadingMessage: "Loading...",
+ //
+ pullingIconClass: "enyo-puller-arrow enyo-puller-arrow-down",
+ pulledIconClass: "enyo-puller-arrow enyo-puller-arrow-up",
+ loadingIconClass: "",
+ //* @protected
+ create: function() {
+ var p = {kind: "Puller", showing: false, text: this.loadingMessage, iconClass: this.loadingIconClass, onCreate: "setPully"};
+ this.listTools.splice(0, 0, p);
+ this.inherited(arguments);
+ this.setPulling();
+ },
+ initComponents: function() {
+ this.createChrome(this.pulldownTools);
+ this.accel = enyo.dom.canAccelerate();
+ this.translation = this.accel ? "translate3d" : "translate";
+ this.inherited(arguments);
+ },
+ setPully: function(inSender, inEvent) {
+ this.pully = inEvent.originator;
+ },
+ scrollStartHandler: function() {
+ this.firedPullStart = false;
+ this.firedPull = false;
+ this.firedPullCancel = false;
+ },
+ scrollHandler: function(inSender) {
+ if (this.completingPull) {
+ this.pully.setShowing(false);
+ }
+ var s = this.getStrategy().$.scrollMath;
+ var over = s.y;
+ if (s.isInOverScroll() && over > 0) {
+ enyo.dom.transformValue(this.$.pulldown, this.translation, "0," + over + "px" + (this.accel ? ",0" : ""));
+ if (!this.firedPullStart) {
+ this.firedPullStart = true;
+ this.pullStart();
+ this.pullHeight = this.$.pulldown.getBounds().height;
+ }
+ if (over > this.pullHeight && !this.firedPull) {
+ this.firedPull = true;
+ this.firedPullCancel = false;
+ this.pull();
+ }
+ if (this.firedPull && !this.firedPullCancel && over < this.pullHeight) {
+ this.firedPullCancel = true;
+ this.firedPull = false;
+ this.pullCancel();
+ }
+ }
+ },
+ scrollStopHandler: function() {
+ if (this.completingPull) {
+ this.completingPull = false;
+ this.doPullComplete();
+ }
+ },
+ dragfinish: function() {
+ if (this.firedPull) {
+ var s = this.getStrategy().$.scrollMath;
+ s.setScrollY(s.y - this.pullHeight);
+ this.pullRelease();
+ }
+ },
+ //* @public
+ //* To signal that the list should execute pull completion. This usually be called after the application has received the new data.
+ completePull: function() {
+ this.completingPull = true;
+ this.$.strategy.$.scrollMath.setScrollY(this.pullHeight);
+ this.$.strategy.$.scrollMath.start();
+ },
+ //* @protected
+ pullStart: function() {
+ this.setPulling();
+ this.pully.setShowing(false);
+ this.$.puller.setShowing(true);
+ this.doPullStart();
+ },
+ pull: function() {
+ this.setPulled();
+ this.doPull();
+ },
+ pullCancel: function() {
+ this.setPulling();
+ this.doPullCancel();
+ },
+ pullRelease: function() {
+ this.$.puller.setShowing(false);
+ this.pully.setShowing(true);
+ this.doPullRelease();
+ },
+ setPulling: function() {
+ this.$.puller.setText(this.pullingMessage);
+ this.$.puller.setIconClass(this.pullingIconClass);
+ },
+ setPulled: function() {
+ this.$.puller.setText(this.pulledMessage);
+ this.$.puller.setIconClass(this.pulledIconClass);
+ }
+});
+
+enyo.kind({
+ name: "enyo.Puller",
+ classes: "enyo-puller",
+ published: {
+ text: "",
+ iconClass: ""
+ },
+ events: {
+ onCreate: ""
+ },
+ components: [
+ {name: "icon"},
+ {name: "text", tag: "span", classes: "enyo-puller-text"}
+ ],
+ create: function() {
+ this.inherited(arguments);
+ this.doCreate();
+ this.textChanged();
+ this.iconClassChanged();
+ },
+ textChanged: function() {
+ this.$.text.setContent(this.text);
+ },
+ iconClassChanged: function() {
+ this.$.icon.setClasses(this.iconClass);
+ }
+}); \ No newline at end of file
diff --git a/html/lib/layout/list/source/Selection.js b/html/lib/layout/list/source/Selection.js
new file mode 100644
index 0000000..8581f8d
--- /dev/null
+++ b/html/lib/layout/list/source/Selection.js
@@ -0,0 +1,141 @@
+/**
+ _enyo.Selection_ is used to manage row selection state for lists. It
+ provides selection state management for both single-select and multi-select
+ lists.
+
+ // The following is an excerpt from enyo.FlyweightRepeater.
+ enyo.kind({
+ name: "enyo.FlyweightRepeater",
+ ...
+ components: [
+ {kind: "Selection", onSelect: "selectDeselect", onDeselect: "selectDeselect"},
+ ...
+ ],
+ tap: function(inSender, inEvent) {
+ ...
+ // mark the tapped row as selected
+ this.$.selection.select(inEvent.index);
+ ...
+ },
+ selectDeselect: function(inSender, inEvent) {
+ // this is where a row selection highlight might be applied
+ this.renderRow(inEvent.key);
+ }
+ ...
+ })
+*/
+enyo.kind({
+ name: "enyo.Selection",
+ kind: enyo.Component,
+ published: {
+ //* If true, multiple selections are allowed.
+ multi: false
+ },
+ events: {
+ /**
+ Fires when an item is selected.
+
+ {kind: "Selection", onSelect: "selectRow"...
+ ...
+ selectRow: function(inSender, inKey, inPrivateData) {
+ ...
+
+ _inKey_ is whatever key was used to register
+ the selection (usually a row index).
+
+ _inPrivateData_ references data registered
+ with this key by the code that made the original selection.
+ */
+ onSelect: "",
+ /**
+ Fires when an item is deselected.
+
+ {kind: "Selection", onSelect: "deselectRow"...
+ ...
+ deselectRow: function(inSender, inKey, inPrivateData)
+ ...
+
+ _inKey_ is whatever key was used to request
+ the deselection (usually a row index).
+
+ _inPrivateData_ references data registered
+ with this key by the code that made the selection.
+ */
+ onDeselect: "",
+ //* Sent when selection changes (but not when the selection is cleared).
+ onChange: ""
+ },
+ //* @protected
+ create: function() {
+ this.clear();
+ this.inherited(arguments);
+ },
+ multiChanged: function() {
+ if (!this.multi) {
+ this.clear();
+ }
+ this.doChange();
+ },
+ highlander: function(inKey) {
+ if (!this.multi) {
+ this.deselect(this.lastSelected);
+ }
+ },
+ //* @public
+ //* Removes all selections.
+ clear: function() {
+ this.selected = {};
+ },
+ //* Returns true if the _inKey_ row is selected.
+ isSelected: function(inKey) {
+ return this.selected[inKey];
+ },
+ //* Manually sets a row's state to selected or unselected.
+ setByKey: function(inKey, inSelected, inData) {
+ if (inSelected) {
+ this.selected[inKey] = (inData || true);
+ this.lastSelected = inKey;
+ this.doSelect({key: inKey, data: this.selected[inKey]});
+ } else {
+ var was = this.isSelected(inKey);
+ delete this.selected[inKey];
+ this.doDeselect({key: inKey, data: was});
+ }
+ this.doChange();
+ },
+ //* Deselects a row.
+ deselect: function(inKey) {
+ if (this.isSelected(inKey)) {
+ this.setByKey(inKey, false);
+ }
+ },
+ /**
+ Selects a row. If the _multi_ property is set to false, _select_ will
+ also deselect the previous selection.
+ */
+ select: function(inKey, inData) {
+ if (this.multi) {
+ this.setByKey(inKey, !this.isSelected(inKey), inData);
+ } else if (!this.isSelected(inKey)) {
+ this.highlander();
+ this.setByKey(inKey, true, inData);
+ }
+ },
+ /**
+ Toggles selection state for a row. If the _multi_ property is set to
+ false, toggling a selection on will deselect the previous selection.
+ */
+ toggle: function(inKey, inData) {
+ if (!this.multi && this.lastSelected != inKey) {
+ this.deselect(this.lastSelected);
+ }
+ this.setByKey(inKey, !this.isSelected(inKey), inData);
+ },
+ /**
+ Returns the selection as a hash in which each selected item has a value;
+ unselected items are undefined.
+ */
+ getSelected: function() {
+ return this.selected;
+ }
+});
diff --git a/html/lib/layout/list/source/package.js b/html/lib/layout/list/source/package.js
new file mode 100644
index 0000000..391e38e
--- /dev/null
+++ b/html/lib/layout/list/source/package.js
@@ -0,0 +1,7 @@
+enyo.depends(
+ "FlyweightRepeater.js",
+ "List.css",
+ "List.js",
+ "PulldownList.css",
+ "PulldownList.js"
+); \ No newline at end of file
diff --git a/html/lib/layout/list/source/wip-package.js b/html/lib/layout/list/source/wip-package.js
new file mode 100644
index 0000000..bafc093
--- /dev/null
+++ b/html/lib/layout/list/source/wip-package.js
@@ -0,0 +1,6 @@
+enyo.depends(
+ // Add wip controls here
+ "AlphaJumper.css",
+ "AlphaJumper.js",
+ "AlphaJumpList.js"
+); \ No newline at end of file
diff --git a/html/lib/layout/package.js b/html/lib/layout/package.js
new file mode 100644
index 0000000..7f9777f
--- /dev/null
+++ b/html/lib/layout/package.js
@@ -0,0 +1,7 @@
+enyo.depends(
+ "fittable",
+ "list",
+ "slideable",
+ "panels",
+ "tree"
+); \ No newline at end of file
diff --git a/html/lib/layout/panels/package.js b/html/lib/layout/panels/package.js
new file mode 100644
index 0000000..f1de4f2
--- /dev/null
+++ b/html/lib/layout/panels/package.js
@@ -0,0 +1,3 @@
+enyo.depends(
+ "source/"
+); \ No newline at end of file
diff --git a/html/lib/layout/panels/source/Panels.css b/html/lib/layout/panels/source/Panels.css
new file mode 100644
index 0000000..2e79349
--- /dev/null
+++ b/html/lib/layout/panels/source/Panels.css
@@ -0,0 +1,12 @@
+.enyo-panels {
+}
+
+.enyo-panels-fit-narrow {
+}
+
+@media all and (max-width: 800px) {
+ .enyo-panels-fit-narrow > * {
+ min-width: 100%;
+ max-width: 100%;
+ }
+} \ No newline at end of file
diff --git a/html/lib/layout/panels/source/Panels.js b/html/lib/layout/panels/source/Panels.js
new file mode 100644
index 0000000..d2e2ecc
--- /dev/null
+++ b/html/lib/layout/panels/source/Panels.js
@@ -0,0 +1,386 @@
+/**
+The enyo.Panels kind is designed to satisfy a variety of common use cases for
+application layout. Using enyo.Panels, controls may be arranged as (among other
+things) a carousel, a set of collapsing panels, a card stack that fades between
+panels, or a grid.
+
+Any Enyo control may be placed inside an enyo.Panels, but by convention we refer
+to each of these controls as a "panel." From the set of panels in an enyo.Panels,
+one is considered active. The active panel is set by index using the *setIndex*
+method. The actual layout of the panels typically changes each time the active
+panel is set, such that the new active panel has the most prominent position.
+
+For more information, see the [Panels documentation](https://github.com/enyojs/enyo/wiki/Panels)
+in the Enyo Developer Guide.
+*/
+enyo.kind({
+ name: "enyo.Panels",
+ classes: "enyo-panels",
+ published: {
+ /**
+ The index of the active panel. The layout of panels is controlled by
+ the layoutKind, but as a rule, the active panel is displayed in the
+ most prominent position. For example, in the (default) CardArranger
+ layout, the active panel is shown and the other panels are hidden.
+ */
+ index: 0,
+ //* Controls whether the user can drag between panels.
+ draggable: true,
+ //* Controls whether the panels animate when transitioning; for example,
+ //* when _setIndex_ is called.
+ animate: true,
+ //* Controls whether panels "wrap around" when moving past the end. Actual effect depends upon the arranger in use.
+ wrap: false,
+ //* Sets the arranger kind to be used for dynamic layout.
+ arrangerKind: "CardArranger",
+ //* By default, each panel will be sized to fit the Panels' width when
+ //* the screen size is narrow enough (less than ~800px). Set to false
+ //* to avoid this behavior.
+ narrowFit: true
+ },
+ events: {
+ /**
+ Fires at the start of a panel transition.
+ This event fires when _setIndex_ is called and also during dragging.
+ */
+ onTransitionStart: "",
+ /**
+ Fires at the end of a panel transition.
+ This event fires when _setIndex_ is called and also during dragging.
+ */
+ onTransitionFinish: ""
+ },
+ //* @protected
+ handlers: {
+ ondragstart: "dragstart",
+ ondrag: "drag",
+ ondragfinish: "dragfinish"
+ },
+ tools: [
+ {kind: "Animator", onStep: "step", onEnd: "completed"}
+ ],
+ fraction: 0,
+ create: function() {
+ this.transitionPoints = [];
+ this.inherited(arguments);
+ this.arrangerKindChanged();
+ this.avoidFitChanged();
+ this.indexChanged();
+ },
+ initComponents: function() {
+ this.createChrome(this.tools);
+ this.inherited(arguments);
+ },
+ arrangerKindChanged: function() {
+ this.setLayoutKind(this.arrangerKind);
+ },
+ avoidFitChanged: function() {
+ this.addRemoveClass("enyo-panels-fit-narrow", this.narrowFit);
+ },
+ removeControl: function(inControl) {
+ this.inherited(arguments);
+ if (this.controls.length > 1 && this.isPanel(inControl)) {
+ this.setIndex(Math.max(this.index - 1, 0));
+ this.flow();
+ this.reflow();
+ }
+ },
+ isPanel: function() {
+ // designed to be overridden in kinds derived from Panels that have
+ // non-panel client controls
+ return true;
+ },
+ flow: function() {
+ this.arrangements = [];
+ this.inherited(arguments);
+ },
+ reflow: function() {
+ this.arrangements = [];
+ this.inherited(arguments);
+ this.refresh();
+ },
+ //* @public
+ /**
+ Returns an array of contained panels.
+ Subclasses can override this if they don't want the arranger to layout all of their children
+ */
+ getPanels: function() {
+ var p = this.controlParent || this;
+ return p.children;
+ },
+ //* Returns a reference to the active panel--i.e., the panel at the specified index.
+ getActive: function() {
+ var p$ = this.getPanels();
+ return p$[this.index];
+ },
+ /**
+ Returns a reference to the <a href="#enyo.Animator">enyo.Animator</a>
+ instance used to animate panel transitions. The Panels' animator can be used
+ to set the duration of panel transitions, e.g.:
+
+ this.getAnimator().setDuration(1000);
+ */
+ getAnimator: function() {
+ return this.$.animator;
+ },
+ /**
+ Sets the active panel to the panel specified by the given index.
+ Note that if the _animate_ property is set to true, the active panel
+ will animate into view.
+ */
+ setIndex: function(inIndex) {
+ // override setIndex so that indexChanged is called
+ // whether this.index has actually changed or not
+ this.setPropertyValue("index", inIndex, "indexChanged");
+ },
+ /**
+ Sets the active panel to the panel specified by the given index.
+ Regardless of the value of the _animate_ property, the transition to the
+ next panel will not animate and will be immediate.
+ */
+ setIndexDirect: function(inIndex) {
+ this.setIndex(inIndex);
+ this.completed();
+ },
+ //* Transitions to the previous panel--i.e., the panel whose index value is
+ //* one less than that of the current active panel.
+ previous: function() {
+ this.setIndex(this.index-1);
+ },
+ //* Transitions to the next panel--i.e., the panel whose index value is one
+ //* greater than that of the current active panel.
+ next: function() {
+ this.setIndex(this.index+1);
+ },
+ //* @protected
+ clamp: function(inValue) {
+ var l = this.getPanels().length-1;
+ if (this.wrap) {
+ // FIXME: dragging makes assumptions about direction and from->start indexes.
+ //return inValue < 0 ? l : (inValue > l ? 0 : inValue);
+ return inValue;
+ } else {
+ return Math.max(0, Math.min(inValue, l));
+ }
+ },
+ indexChanged: function(inOld) {
+ this.lastIndex = inOld;
+ this.index = this.clamp(this.index);
+ if (!this.dragging) {
+ if (this.$.animator.isAnimating()) {
+ this.completed();
+ }
+ this.$.animator.stop();
+ if (this.hasNode()) {
+ if (this.animate) {
+ this.startTransition();
+ this.$.animator.play({
+ startValue: this.fraction
+ });
+ } else {
+ this.refresh();
+ }
+ }
+ }
+ },
+ step: function(inSender) {
+ this.fraction = inSender.value;
+ this.stepTransition();
+ },
+ completed: function() {
+ if (this.$.animator.isAnimating()) {
+ this.$.animator.stop();
+ }
+ this.fraction = 1;
+ this.stepTransition();
+ this.finishTransition();
+ },
+ dragstart: function(inSender, inEvent) {
+ if (this.draggable && this.layout && this.layout.canDragEvent(inEvent)) {
+ inEvent.preventDefault();
+ this.dragstartTransition(inEvent);
+ this.dragging = true;
+ this.$.animator.stop();
+ return true;
+ }
+ },
+ drag: function(inSender, inEvent) {
+ if (this.dragging) {
+ inEvent.preventDefault();
+ this.dragTransition(inEvent);
+ }
+ },
+ dragfinish: function(inSender, inEvent) {
+ if (this.dragging) {
+ this.dragging = false;
+ inEvent.preventTap();
+ this.dragfinishTransition(inEvent);
+ }
+ },
+ dragstartTransition: function(inEvent) {
+ if (!this.$.animator.isAnimating()) {
+ var f = this.fromIndex = this.index;
+ this.toIndex = f - (this.layout ? this.layout.calcDragDirection(inEvent) : 0);
+ } else {
+ this.verifyDragTransition(inEvent);
+ }
+ this.fromIndex = this.clamp(this.fromIndex);
+ this.toIndex = this.clamp(this.toIndex);
+ //this.log(this.fromIndex, this.toIndex);
+ this.fireTransitionStart();
+ if (this.layout) {
+ this.layout.start();
+ }
+ },
+ dragTransition: function(inEvent) {
+ // note: for simplicity we choose to calculate the distance directly between
+ // the first and last transition point.
+ var d = this.layout ? this.layout.calcDrag(inEvent) : 0;
+ var t$ = this.transitionPoints, s = t$[0], f = t$[t$.length-1];
+ var as = this.fetchArrangement(s);
+ var af = this.fetchArrangement(f);
+ var dx = this.layout ? this.layout.drag(d, s, as, f, af) : 0;
+ var dragFail = d && !dx;
+ if (dragFail) {
+ //this.log(dx, s, as, f, af);
+ }
+ this.fraction += dx;
+ var fr = this.fraction;
+ if (fr > 1 || fr < 0 || dragFail) {
+ if (fr > 0 || dragFail) {
+ this.dragfinishTransition(inEvent);
+ }
+ this.dragstartTransition(inEvent);
+ this.fraction = 0;
+ // FIXME: account for lost fraction
+ //this.dragTransition(inEvent);
+ }
+ this.stepTransition();
+ },
+ dragfinishTransition: function(inEvent) {
+ this.verifyDragTransition(inEvent);
+ this.setIndex(this.toIndex);
+ // note: if we're still dragging, then we're at a transition boundary
+ // and should fire the finish event
+ if (this.dragging) {
+ this.fireTransitionFinish();
+ }
+ },
+ verifyDragTransition: function(inEvent) {
+ var d = this.layout ? this.layout.calcDragDirection(inEvent) : 0;
+ var f = Math.min(this.fromIndex, this.toIndex);
+ var t = Math.max(this.fromIndex, this.toIndex);
+ if (d > 0) {
+ var s = f;
+ f = t;
+ t = s;
+ }
+ if (f != this.fromIndex) {
+ this.fraction = 1 - this.fraction;
+ }
+ //this.log("old", this.fromIndex, this.toIndex, "new", f, t);
+ this.fromIndex = f;
+ this.toIndex = t;
+ },
+ refresh: function() {
+ if (this.$.animator.isAnimating()) {
+ this.$.animator.stop();
+ }
+ this.startTransition();
+ this.fraction = 1;
+ this.stepTransition();
+ this.finishTransition();
+ },
+ startTransition: function() {
+ this.fromIndex = this.fromIndex != null ? this.fromIndex : this.lastIndex || 0;
+ this.toIndex = this.toIndex != null ? this.toIndex : this.index;
+ //this.log(this.id, this.fromIndex, this.toIndex);
+ if (this.layout) {
+ this.layout.start();
+ }
+ this.fireTransitionStart();
+ },
+ finishTransition: function() {
+ if (this.layout) {
+ this.layout.finish();
+ }
+ this.transitionPoints = [];
+ this.fraction = 0;
+ this.fromIndex = this.toIndex = null;
+ this.fireTransitionFinish();
+ },
+ fireTransitionStart: function() {
+ var t = this.startTransitionInfo;
+ if (this.hasNode() && (!t || (t.fromIndex != this.fromIndex || t.toIndex != this.toIndex))) {
+ this.startTransitionInfo = {fromIndex: this.fromIndex, toIndex: this.toIndex};
+ this.doTransitionStart(enyo.clone(this.startTransitionInfo));
+ }
+ },
+ fireTransitionFinish: function() {
+ var t = this.finishTransitionInfo;
+ if (this.hasNode() && (!t || (t.fromIndex != this.lastIndex || t.toIndex != this.index))) {
+ this.finishTransitionInfo = {fromIndex: this.lastIndex, toIndex: this.index};
+ this.doTransitionFinish(enyo.clone(this.finishTransitionInfo));
+ }
+ this.lastIndex=this.index;
+ },
+ // gambit: we interpolate between arrangements as needed.
+ stepTransition: function() {
+ if (this.hasNode()) {
+ // select correct transition points and normalize fraction.
+ var t$ = this.transitionPoints;
+ var r = (this.fraction || 0) * (t$.length-1);
+ var i = Math.floor(r);
+ r = r - i;
+ var s = t$[i], f = t$[i+1];
+ // get arrangements and lerp between them
+ var s0 = this.fetchArrangement(s);
+ var s1 = this.fetchArrangement(f);
+ this.arrangement = s0 && s1 ? enyo.Panels.lerp(s0, s1, r) : (s0 || s1);
+ if (this.arrangement && this.layout) {
+ this.layout.flowArrangement();
+ }
+ }
+ },
+ fetchArrangement: function(inName) {
+ if ((inName != null) && !this.arrangements[inName] && this.layout) {
+ this.layout._arrange(inName);
+ this.arrangements[inName] = this.readArrangement(this.getPanels());
+ }
+ return this.arrangements[inName];
+ },
+ readArrangement: function(inC) {
+ var r = [];
+ for (var i=0, c$=inC, c; (c=c$[i]); i++) {
+ r.push(enyo.clone(c._arranger));
+ }
+ return r;
+ },
+ statics: {
+ isScreenNarrow: function() {
+ return enyo.dom.getWindowWidth() <= 800;
+ },
+ lerp: function(inA0, inA1, inFrac) {
+ var r = [];
+ for (var i=0, k$=enyo.keys(inA0), k; (k=k$[i]); i++) {
+ r.push(this.lerpObject(inA0[k], inA1[k], inFrac));
+ }
+ return r;
+ },
+ lerpObject: function(inNew, inOld, inFrac) {
+ var b = enyo.clone(inNew), n, o;
+ // inOld might be undefined when deleting panels
+ if (inOld) {
+ for (var i in inNew) {
+ n = inNew[i];
+ o = inOld[i];
+ if (n != o) {
+ b[i] = n - (n - o) * inFrac;
+ }
+ }
+ }
+ return b;
+ }
+ }
+});
+
diff --git a/html/lib/layout/panels/source/arrangers/Arranger.css b/html/lib/layout/panels/source/arrangers/Arranger.css
new file mode 100644
index 0000000..4d23be0
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/Arranger.css
@@ -0,0 +1,29 @@
+.enyo-arranger {
+ position: relative;
+ overflow: hidden;
+}
+
+.enyo-arranger.enyo-fit {
+ position: absolute;
+}
+
+.enyo-arranger > * {
+ position: absolute;
+ left: 0;
+ top: 0;
+ box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+}
+
+.enyo-arranger-fit > * {
+ /*
+ override any width/height set on panels
+ */
+ width: 100% !important;
+ height: 100% !important;
+ min-width: 0 !important;
+ max-width: auto !important;
+ min-height: 0 !important;
+ max-height: auto !important;
+}
diff --git a/html/lib/layout/panels/source/arrangers/Arranger.js b/html/lib/layout/panels/source/arrangers/Arranger.js
new file mode 100644
index 0000000..b10f6b8
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/Arranger.js
@@ -0,0 +1,226 @@
+/**
+ _enyo.Arranger_ is an <a href="#enyo.Layout">enyo.Layout</a> that considers
+ one of the controls it lays out as active. The other controls are placed
+ relative to the active control as makes sense for the layout.
+
+ Arranger supports dynamic layouts, meaning it's possible to transition
+ between its layouts via animation. Typically, arrangers should lay out
+ controls using CSS transforms, since these are optimized for animation. To
+ support this, the controls in an Arranger are absolutely positioned, and
+ the Arranger kind has an `accelerated` property, which marks controls for
+ CSS compositing. The default setting of "auto" ensures that this will occur
+ if enabled by the platform.
+
+ For more information, see the documentation on
+ [Arrangers](https://github.com/enyojs/enyo/wiki/Arrangers)
+ in the Enyo Developer Guide.
+*/
+enyo.kind({
+ name: "enyo.Arranger",
+ kind: "Layout",
+ layoutClass: "enyo-arranger",
+ /**
+ Sets controls being laid out to use CSS compositing. A setting of "auto"
+ will mark controls for compositing if the platform supports it.
+ */
+ accelerated: "auto",
+ //* Property of the drag event used to calculate the amount a drag moves the layout
+ dragProp: "ddx",
+ //* Property of the drag event used to calculate the direction of a drag
+ dragDirectionProp: "xDirection",
+ //* Property of the drag event used to calculate whether a drag should occur
+ canDragProp: "horizontal",
+ /**
+ If set to true, transitions between non-adjacent arrangements will go
+ through the intermediate arrangements. This is useful when direct
+ transitions between arrangements would be visually jarring.
+ */
+ incrementalPoints: false,
+ /**
+ Called when removing an arranger (for example, when switching a Panels
+ control to a different arrangerKind). Subclasses should implement this
+ function to reset whatever properties they've changed on child controls.
+ You *must* call the superclass implementation in your subclass's
+ _destroy_ function.
+ */
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ c._arranger = null;
+ }
+ this.inherited(arguments);
+ },
+ //* @public
+ /**
+ Arranges the given array of controls (_inC_) in the layout specified by
+ _inName_. When implementing this method, rather than apply styling
+ directly to controls, call _arrangeControl(inControl, inArrangement)_
+ with an _inArrangement_ object with styling settings. These will then be
+ applied via the _flowControl(inControl, inArrangement)_ method.
+ */
+ arrange: function(inC, inName) {
+ },
+ /**
+ Sizes the controls in the layout. This method is called only at reflow
+ time. Note that sizing is separated from other layout done in the
+ _arrange_ method because it is expensive and not suitable for dynamic
+ layout.
+ */
+ size: function() {
+ },
+ /**
+ Called when a layout transition begins. Implement this method to perform
+ tasks that should only occur when a transition starts; for example, some
+ controls could be shown or hidden. In addition, the _transitionPoints_
+ array may be set on the container to dictate the named arrangments
+ between which the transition occurs.
+ */
+ start: function() {
+ var f = this.container.fromIndex, t = this.container.toIndex;
+ var p$ = this.container.transitionPoints = [f];
+ // optionally add a transition point for each index between from and to.
+ if (this.incrementalPoints) {
+ var d = Math.abs(t - f) - 2;
+ var i = f;
+ while (d >= 0) {
+ i = i + (t < f ? -1 : 1);
+ p$.push(i);
+ d--;
+ }
+ }
+ p$.push(this.container.toIndex);
+ },
+ /**
+ Called when a layout transition completes. Implement this method to
+ perform tasks that should only occur when a transition ends; for
+ example, some controls could be shown or hidden.
+ */
+ finish: function() {
+ },
+ //* @protected
+ canDragEvent: function(inEvent) {
+ return inEvent[this.canDragProp];
+ },
+ calcDragDirection: function(inEvent) {
+ return inEvent[this.dragDirectionProp];
+ },
+ calcDrag: function(inEvent) {
+ return inEvent[this.dragProp];
+ },
+ drag: function(inDp, inAn, inA, inBn, inB) {
+ var f = this.measureArrangementDelta(-inDp, inAn, inA, inBn, inB);
+ return f;
+ },
+ measureArrangementDelta: function(inX, inI0, inA0, inI1, inA1) {
+ var d = this.calcArrangementDifference(inI0, inA0, inI1, inA1);
+ var s = d ? inX / Math.abs(d) : 0;
+ s = s * (this.container.fromIndex > this.container.toIndex ? -1 : 1);
+ //enyo.log("delta", s);
+ return s;
+ },
+ //* @public
+ /**
+ Called when dragging the layout, this method returns the difference in
+ pixels between the arrangement _inA0_ for layout setting _inI0_ and
+ arrangement _inA1_ for layout setting _inI1_. This data is used to calculate
+ the percentage that a drag should move the layout between two active states.
+ */
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ },
+ //* @protected
+ _arrange: function(inIndex) {
+ var c$ = this.getOrderedControls(inIndex);
+ this.arrange(c$, inIndex);
+ },
+ arrangeControl: function(inControl, inArrangement) {
+ inControl._arranger = enyo.mixin(inControl._arranger || {}, inArrangement);
+ },
+ flow: function() {
+ this.c$ = [].concat(this.container.getPanels());
+ this.controlsIndex = 0;
+ for (var i=0, c$=this.container.getPanels(), c; c=c$[i]; i++) {
+ enyo.dom.accelerate(c, this.accelerated);
+ if (enyo.platform.safari) {
+ // On Safari-desktop, sometimes having the panel's direct child set to accelerate isn't sufficient
+ // this is most often the case with Lists contained inside another control, inside a Panels
+ var grands=c.children;
+ for (var j=0, kid; kid=grands[j]; j++) {
+ enyo.dom.accelerate(kid, this.accelerated);
+ }
+ }
+ }
+ },
+ reflow: function() {
+ var cn = this.container.hasNode();
+ this.containerBounds = cn ? {width: cn.clientWidth, height: cn.clientHeight} : {};
+ this.size();
+ },
+ flowArrangement: function() {
+ var a = this.container.arrangement;
+ if (a) {
+ for (var i=0, c$=this.container.getPanels(), c; c=c$[i]; i++) {
+ this.flowControl(c, a[i]);
+ }
+ }
+ },
+ //* @public
+ /**
+ Lays out the control (_inControl_) according to the settings stored in
+ the _inArrangment_ object. By default, _flowControl_ will apply settings
+ of left, top, and opacity. This method should only be implemented to
+ apply other settings made via _arrangeControl_.
+ */
+ flowControl: function(inControl, inArrangement) {
+ enyo.Arranger.positionControl(inControl, inArrangement);
+ var o = inArrangement.opacity;
+ if (o != null) {
+ enyo.Arranger.opacifyControl(inControl, o);
+ }
+ },
+ //* @protected
+ // Gets an array of controls arranged in state order.
+ // note: optimization, dial around a single array.
+ getOrderedControls: function(inIndex) {
+ var whole = Math.floor(inIndex);
+ var a = whole - this.controlsIndex;
+ var sign = a > 0;
+ var c$ = this.c$ || [];
+ for (var i=0; i<Math.abs(a); i++) {
+ if (sign) {
+ c$.push(c$.shift());
+ } else {
+ c$.unshift(c$.pop());
+ }
+ }
+ this.controlsIndex = whole;
+ return c$;
+ },
+ statics: {
+ // Positions a control via transform: translateX/Y if supported and falls back to left/top if not.
+ positionControl: function(inControl, inBounds, inUnit) {
+ var unit = inUnit || "px";
+ if (!this.updating) {
+ if (enyo.dom.canTransform() && !enyo.platform.android) {
+ var l = inBounds.left, t = inBounds.top;
+ var l = enyo.isString(l) ? l : l && (l + unit);
+ var t = enyo.isString(t) ? t : t && (t + unit);
+ enyo.dom.transform(inControl, {translateX: l || null, translateY: t || null});
+ } else {
+ inControl.setBounds(inBounds, inUnit);
+ }
+ }
+ },
+ opacifyControl: function(inControl, inOpacity) {
+ var o = inOpacity;
+ // FIXME: very high/low settings of opacity can cause a control to
+ // blink so cap this here.
+ o = o > .99 ? 1 : (o < .01 ? 0 : o);
+ // note: we only care about ie8
+ if (enyo.platform.ie < 9) {
+ inControl.applyStyle("filter", "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (o * 100) + ")");
+ } else {
+ inControl.applyStyle("opacity", o);
+ }
+ }
+ }
+});
diff --git a/html/lib/layout/panels/source/arrangers/CardArranger.js b/html/lib/layout/panels/source/arrangers/CardArranger.js
new file mode 100644
index 0000000..28b94b2
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/CardArranger.js
@@ -0,0 +1,49 @@
+/**
+ _enyo.CardArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> that
+ displays only one active control. The non-active controls are hidden with
+ _setShowing(false)_. Transitions between arrangements are handled by fading
+ from one control to the next.
+*/
+enyo.kind({
+ name: "enyo.CardArranger",
+ kind: "Arranger",
+ layoutClass: "enyo-arranger enyo-arranger-fit",
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ return this.containerBounds.width;
+ },
+ arrange: function(inC, inName) {
+ for (var i=0, c, b, v; c=inC[i]; i++) {
+ v = (i == 0) ? 1 : 0;
+ this.arrangeControl(c, {opacity: v});
+ }
+ },
+ start: function() {
+ this.inherited(arguments);
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ var wasShowing=c.showing;
+ c.setShowing(i == this.container.fromIndex || i == (this.container.toIndex));
+ if (c.showing && !wasShowing) {
+ c.resized();
+ }
+ }
+
+ },
+ finish: function() {
+ this.inherited(arguments);
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ c.setShowing(i == this.container.toIndex);
+ }
+ },
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ enyo.Arranger.opacifyControl(c, 1);
+ if (!c.showing) {
+ c.setShowing(true);
+ }
+ }
+ this.inherited(arguments);
+ }
+});
diff --git a/html/lib/layout/panels/source/arrangers/CardSlideInArranger.js b/html/lib/layout/panels/source/arrangers/CardSlideInArranger.js
new file mode 100644
index 0000000..7700557
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/CardSlideInArranger.js
@@ -0,0 +1,62 @@
+/**
+ _enyo.CardSlideInArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a>
+ that displays only one active control. The non-active controls are hidden
+ with _setShowing(false)_. Transitions between arrangements are handled by
+ sliding the new control over the current one.
+
+ Note that CardSlideInArranger always slides controls in from the right. If
+ you want an arranger that slides to the right and left, try
+ <a href="#enyo.LeftRightArranger">enyo.LeftRightArranger</a>.
+*/
+enyo.kind({
+ name: "enyo.CardSlideInArranger",
+ kind: "CardArranger",
+ start: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ var wasShowing=c.showing;
+ c.setShowing(i == this.container.fromIndex || i == (this.container.toIndex));
+ if (c.showing && !wasShowing) {
+ c.resized();
+ }
+ }
+ var l = this.container.fromIndex;
+ var i = this.container.toIndex;
+ this.container.transitionPoints = [
+ i + "." + l + ".s",
+ i + "." + l + ".f"
+ ];
+ },
+ finish: function() {
+ this.inherited(arguments);
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ c.setShowing(i == this.container.toIndex);
+ }
+ },
+ arrange: function(inC, inName) {
+ var p = inName.split(".");
+ var f = p[0], s= p[1], starting = (p[2] == "s");
+ var b = this.containerBounds.width;
+ for (var i=0, c$=this.container.getPanels(), c, v; c=c$[i]; i++) {
+ v = b;
+ if (s == i) {
+ v = starting ? 0 : -b;
+ }
+ if (f == i) {
+ v = starting ? b : 0;
+ }
+ if (s == i && s == f) {
+ v = 0;
+ }
+ this.arrangeControl(c, {left: v});
+ }
+ },
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ enyo.Arranger.positionControl(c, {left: null});
+ }
+ this.inherited(arguments);
+ }
+});
diff --git a/html/lib/layout/panels/source/arrangers/CarouselArranger.js b/html/lib/layout/panels/source/arrangers/CarouselArranger.js
new file mode 100644
index 0000000..3a2f26a
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/CarouselArranger.js
@@ -0,0 +1,109 @@
+/**
+ _enyo.CarouselArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a>
+ that displays the active control, along with some number of inactive
+ controls to fill the available space. The active control is positioned on
+ the left side of the container, and the rest of the views are laid out to
+ the right.
+
+ One of the controls may have _fit: true_ set, in which case it will take up
+ any remaining space after all of the other controls have been sized.
+
+ For best results with CarouselArranger, you should set a minimum width for
+ each control via a CSS style, e.g., _min-width: 25%_ or _min-width: 250px_.
+
+ Transitions between arrangements are handled by sliding the new controls in
+ from the right and sliding the old controls off to the left.
+*/
+enyo.kind({
+ name: "enyo.CarouselArranger",
+ kind: "Arranger",
+ size: function() {
+ var c$ = this.container.getPanels();
+ var padding = this.containerPadding = this.container.hasNode() ? enyo.FittableLayout.calcPaddingExtents(this.container.node) : {};
+ var pb = this.containerBounds;
+ pb.height -= padding.top + padding.bottom;
+ pb.width -= padding.left + padding.right;
+ // used space
+ var fit;
+ for (var i=0, s=0, m, c; c=c$[i]; i++) {
+ m = enyo.FittableLayout.calcMarginExtents(c.hasNode());
+ c.width = c.getBounds().width;
+ c.marginWidth = m.right + m.left;
+ s += (c.fit ? 0 : c.width) + c.marginWidth;
+ if (c.fit) {
+ fit = c;
+ }
+ }
+ if (fit) {
+ var w = pb.width - s;
+ fit.width = w >= 0 ? w : fit.width;
+ }
+ for (var i=0, e=padding.left, m, c; c=c$[i]; i++) {
+ c.setBounds({top: padding.top, bottom: padding.bottom, width: c.fit ? c.width : null});
+ }
+ },
+ arrange: function(inC, inName) {
+ if (this.container.wrap) {
+ this.arrangeWrap(inC, inName);
+ } else {
+ this.arrangeNoWrap(inC, inName);
+ }
+ },
+ arrangeNoWrap: function(inC, inName) {
+ var c$ = this.container.getPanels();
+ var s = this.container.clamp(inName);
+ var nw = this.containerBounds.width;
+ // do we have enough content to fill the width?
+ for (var i=s, cw=0, c; c=c$[i]; i++) {
+ cw += c.width + c.marginWidth;
+ if (cw > nw) {
+ break;
+ }
+ }
+ // if content width is less than needed, adjust starting point index and offset
+ var n = nw - cw;
+ var o = 0;
+ if (n > 0) {
+ var s1 = s;
+ for (var i=s-1, aw=0, c; c=c$[i]; i--) {
+ aw += c.width + c.marginWidth;
+ if (n - aw <= 0) {
+ o = (n - aw);
+ s = i;
+ break;
+ }
+ }
+ }
+ // arrange starting from needed index with detected offset so we fill space
+ for (var i=0, e=this.containerPadding.left + o, w, c; c=c$[i]; i++) {
+ w = c.width + c.marginWidth;
+ if (i < s) {
+ this.arrangeControl(c, {left: -w});
+ } else {
+ this.arrangeControl(c, {left: Math.floor(e)});
+ e += w;
+ }
+ }
+ },
+ arrangeWrap: function(inC, inName) {
+ for (var i=0, e=this.containerPadding.left, m, c; c=inC[i]; i++) {
+ this.arrangeControl(c, {left: e});
+ e += c.width + c.marginWidth;
+ }
+ },
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ var i = Math.abs(inI0 % this.c$.length);
+ return inA0[i].left - inA1[i].left;
+ },
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ enyo.Arranger.positionControl(c, {left: null, top: null});
+ c.applyStyle("top", null);
+ c.applyStyle("bottom", null);
+ c.applyStyle("left", null);
+ c.applyStyle("width", null);
+ }
+ this.inherited(arguments);
+ }
+});
diff --git a/html/lib/layout/panels/source/arrangers/CollapsingArranger.js b/html/lib/layout/panels/source/arrangers/CollapsingArranger.js
new file mode 100644
index 0000000..799c94b
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/CollapsingArranger.js
@@ -0,0 +1,80 @@
+/**
+ _enyo.CollapsingArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a>
+ that displays the active control, along with some number of inactive
+ controls to fill the available space. The active control is positioned on
+ the left side of the container and the rest of the views are laid out to the
+ right. The last control, if visible, will expand to fill whatever space is
+ not taken up by the previous controls.
+
+ For best results with CollapsingArranger, you should set a minimum width
+ for each control via a CSS style, e.g., _min-width: 25%_ or
+ _min-width: 250px_.
+
+ Transitions between arrangements are handled by sliding the new control in
+ from the right and collapsing the old control to the left.
+*/
+enyo.kind({
+ name: "enyo.CollapsingArranger",
+ kind: "CarouselArranger",
+ size: function() {
+ this.clearLastSize();
+ this.inherited(arguments);
+ },
+ //* @protected
+ // clear size from last if it's not actually the last
+ // (required for adding another control)
+ clearLastSize: function() {
+ for (var i=0, c$=this.container.getPanels(), c; c=c$[i]; i++) {
+ if (c._fit && i != c$.length-1) {
+ c.applyStyle("width", null);
+ c._fit = null;
+ }
+ }
+ },
+ //* @public
+ arrange: function(inC, inIndex) {
+ var c$ = this.container.getPanels();
+ for (var i=0, e=this.containerPadding.left, m, c; c=c$[i]; i++) {
+ this.arrangeControl(c, {left: e});
+ if (i >= inIndex) {
+ e += c.width + c.marginWidth;
+ }
+ // FIXME: overdragging-ish
+ if (i == c$.length - 1 && inIndex < 0) {
+ this.arrangeControl(c, {left: e - inIndex});
+ }
+ }
+ },
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ var i = this.container.getPanels().length-1;
+ return Math.abs(inA1[i].left - inA0[i].left);
+ },
+ flowControl: function(inControl, inA) {
+ this.inherited(arguments);
+ if (this.container.realtimeFit) {
+ var c$ = this.container.getPanels();
+ var l = c$.length-1;
+ var last = c$[l];
+ if (inControl == last) {
+ this.fitControl(inControl, inA.left);
+ }
+ }
+
+ },
+ finish: function() {
+ this.inherited(arguments);
+ if (!this.container.realtimeFit && this.containerBounds) {
+ var c$ = this.container.getPanels();
+ var a$ = this.container.arrangement;
+ var l = c$.length-1;
+ var c = c$[l];
+ this.fitControl(c, a$[l].left);
+ }
+ },
+ //* @protected
+ fitControl: function(inControl, inOffset) {
+ inControl._fit = true;
+ inControl.applyStyle("width", (this.containerBounds.width - inOffset) + "px");
+ inControl.resized();
+ }
+});
diff --git a/html/lib/layout/panels/source/arrangers/OtherArrangers.js b/html/lib/layout/panels/source/arrangers/OtherArrangers.js
new file mode 100644
index 0000000..533bac3
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/OtherArrangers.js
@@ -0,0 +1,202 @@
+/**
+ _enyo.LeftRightArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a>
+ that displays the active control and some of the previous and next controls.
+ The active control is centered horizontally in the container, and the
+ previous and next controls are laid out to the left and right, respectively.
+
+ Transitions between arrangements are handled by sliding the new control
+ in from the right and sliding the active control out to the left.
+*/
+enyo.kind({
+ name: "enyo.LeftRightArranger",
+ kind: "Arranger",
+ //* The margin width (i.e., how much of the previous and next controls
+ //* are visible) in pixels
+ margin: 40,
+ //* @protected
+ axisSize: "width",
+ offAxisSize: "height",
+ axisPosition: "left",
+ constructor: function() {
+ this.inherited(arguments);
+ this.margin = this.container.margin != null ? this.container.margin : this.margin;
+ },
+ //* @public
+ size: function() {
+ var c$ = this.container.getPanels();
+ var port = this.containerBounds[this.axisSize];
+ var box = port - this.margin -this.margin;
+ for (var i=0, b, c; c=c$[i]; i++) {
+ b = {};
+ b[this.axisSize] = box;
+ b[this.offAxisSize] = "100%";
+ c.setBounds(b);
+ }
+ },
+ arrange: function(inC, inIndex) {
+ var o = Math.floor(this.container.getPanels().length/2);
+ var c$ = this.getOrderedControls(Math.floor(inIndex)-o);
+ var box = this.containerBounds[this.axisSize] - this.margin -this.margin;
+ var e = this.margin - box * o;
+ var m = (c$.length - 1) / 2;
+ for (var i=0, c, b, v; c=c$[i]; i++) {
+ b = {};
+ b[this.axisPosition] = e;
+ b.opacity = (i == 0 || i == c$.length-1) ? 0 : 1;
+ this.arrangeControl(c, b);
+ e += box;
+ }
+ },
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ var i = Math.abs(inI0 % this.c$.length);
+ //enyo.log(inI0, inI1);
+ return inA0[i][this.axisPosition] - inA1[i][this.axisPosition];
+ },
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ enyo.Arranger.positionControl(c, {left: null, top: null});
+ enyo.Arranger.opacifyControl(c, 1);
+ c.applyStyle("left", null);
+ c.applyStyle("top", null);
+ c.applyStyle("height", null);
+ c.applyStyle("width", null);
+ }
+ this.inherited(arguments);
+ }
+});
+
+/**
+ _enyo.TopBottomArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a>
+ that displays the active control and some of the previous and next controls.
+ The active control is centered vertically in the container, and the previous
+ and next controls are laid out above and below, respectively.
+
+ Transitions between arrangements are handled by sliding the new control
+ in from the bottom and sliding the active control out the top.
+*/
+enyo.kind({
+ name: "enyo.TopBottomArranger",
+ kind: "LeftRightArranger",
+ dragProp: "ddy",
+ dragDirectionProp: "yDirection",
+ canDragProp: "vertical",
+ //* @protected
+ axisSize: "height",
+ offAxisSize: "width",
+ axisPosition: "top"
+});
+
+/**
+ _enyo.SpiralArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> that
+ arranges controls in a spiral. The active control is positioned on top and
+ the other controls are laid out in a spiral pattern below.
+
+ Transitions between arrangements are handled by rotating the new control
+ up from below and rotating the active control down to the end of the spiral.
+*/
+enyo.kind({
+ name: "enyo.SpiralArranger",
+ kind: "Arranger",
+ //* Always go through incremental arrangements when transitioning
+ incrementalPoints: true,
+ //* The amount of space between successive controls
+ inc: 20,
+ size: function() {
+ var c$ = this.container.getPanels();
+ var b = this.containerBounds;
+ var w = this.controlWidth = b.width/3;
+ var h = this.controlHeight = b.height/3;
+ for (var i=0, c; c=c$[i]; i++) {
+ c.setBounds({width: w, height: h});
+ }
+ },
+ arrange: function(inC, inName) {
+ var s = this.inc;
+ for (var i=0, l=inC.length, c; c=inC[i]; i++) {
+ var x = Math.cos(i/l * 2*Math.PI) * i * s + this.controlWidth;
+ var y = Math.sin(i/l * 2*Math.PI) * i * s + this.controlHeight;
+ this.arrangeControl(c, {left: x, top: y});
+ }
+ },
+ start: function() {
+ this.inherited(arguments);
+ var c$ = this.getOrderedControls(this.container.toIndex);
+ for (var i=0, c; c=c$[i]; i++) {
+ c.applyStyle("z-index", c$.length - i);
+ }
+ },
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ return this.controlWidth;
+ },
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ c.applyStyle("z-index", null);
+ enyo.Arranger.positionControl(c, {left: null, top: null});
+ c.applyStyle("left", null);
+ c.applyStyle("top", null);
+ c.applyStyle("height", null);
+ c.applyStyle("width", null);
+ }
+ this.inherited(arguments);
+ }
+});
+
+
+/**
+ _enyo.GridArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> that
+ arranges controls in a grid. The active control is positioned at the
+ top-left of the grid and the other controls are laid out from left to right
+ and then from top to bottom.
+
+ Transitions between arrangements are handled by moving the active control to
+ the end of the grid and shifting the other controls to the left, or up to
+ the previous row, to fill the space.
+*/
+enyo.kind({
+ name: "enyo.GridArranger",
+ kind: "Arranger",
+ //* Always go through incremental arrangements when transitioning
+ incrementalPoints: true,
+ //* @public
+ //* Column width
+ colWidth: 100,
+ //* Column height
+ colHeight: 100,
+ size: function() {
+ var c$ = this.container.getPanels();
+ var w=this.colWidth, h=this.colHeight;
+ for (var i=0, c; c=c$[i]; i++) {
+ c.setBounds({width: w, height: h});
+ }
+ },
+ arrange: function(inC, inIndex) {
+ var w=this.colWidth, h=this.colHeight;
+ var cols = Math.floor(this.containerBounds.width / w);
+ var c;
+ for (var y=0, i=0; i<inC.length; y++) {
+ for (var x=0; (x<cols) && (c=inC[i]); x++, i++) {
+ this.arrangeControl(c, {left: w*x, top: h*y});
+ }
+ }
+ },
+ flowControl: function(inControl, inA) {
+ this.inherited(arguments);
+ enyo.Arranger.opacifyControl(inControl, inA.top % this.colHeight != 0 ? 0.25 : 1);
+ },
+ calcArrangementDifference: function(inI0, inA0, inI1, inA1) {
+ return this.colWidth;
+ },
+ destroy: function() {
+ var c$ = this.container.getPanels();
+ for (var i=0, c; c=c$[i]; i++) {
+ enyo.Arranger.positionControl(c, {left: null, top: null});
+ c.applyStyle("left", null);
+ c.applyStyle("top", null);
+ c.applyStyle("height", null);
+ c.applyStyle("width", null);
+ }
+ this.inherited(arguments);
+ }
+});
diff --git a/html/lib/layout/panels/source/arrangers/package.js b/html/lib/layout/panels/source/arrangers/package.js
new file mode 100644
index 0000000..135b754
--- /dev/null
+++ b/html/lib/layout/panels/source/arrangers/package.js
@@ -0,0 +1,9 @@
+enyo.depends(
+ "Arranger.js",
+ "Arranger.css",
+ "CardArranger.js",
+ "CardSlideInArranger.js",
+ "CarouselArranger.js",
+ "CollapsingArranger.js",
+ "OtherArrangers.js"
+); \ No newline at end of file
diff --git a/html/lib/layout/panels/source/package.js b/html/lib/layout/panels/source/package.js
new file mode 100644
index 0000000..270146f
--- /dev/null
+++ b/html/lib/layout/panels/source/package.js
@@ -0,0 +1,5 @@
+enyo.depends(
+ "arrangers",
+ "Panels.css",
+ "Panels.js"
+); \ No newline at end of file
diff --git a/html/lib/layout/slideable/package.js b/html/lib/layout/slideable/package.js
new file mode 100644
index 0000000..f8b32e7
--- /dev/null
+++ b/html/lib/layout/slideable/package.js
@@ -0,0 +1,3 @@
+enyo.depends(
+ "source"
+);
diff --git a/html/lib/layout/slideable/source/Slideable.js b/html/lib/layout/slideable/source/Slideable.js
new file mode 100644
index 0000000..4ef0a08
--- /dev/null
+++ b/html/lib/layout/slideable/source/Slideable.js
@@ -0,0 +1,312 @@
+/**
+ Slideable is a control that can be dragged either horizontally or vertically
+ between a minimum and a maximum value. When released from dragging, a
+ Slideable will animate to its minimum or maximum position, depending on the
+ direction of the drag.
+
+ The *min* value specifies a position left of or above the initial position,
+ to which the Slideable may be dragged.
+ The *max* value specifies a position right of or below the initial position,
+ to which the Slideable may be dragged.
+ The *value* property specifies the current position of the Slideable,
+ between the minimum and maximum positions.
+
+ *min*, *max*, and *value* may be specified in units of "px" or "%".
+
+ The *axis* property controls whether the Slideable slides left-right (h) or
+ up-down (v).
+
+ The following control is placed 90% off the screen to the right, and slides
+ to its natural position.
+
+ {kind: "enyo.Slideable", value: -90, min: -90, unit: "%",
+ classes: "enyo-fit", style: "width: 300px;",
+ components: [
+ {content: "stuff"}
+ ]
+ }
+*/
+enyo.kind({
+ name: "enyo.Slideable",
+ kind: "Control",
+ published: {
+ //* Direction of sliding; can be "h" or "v"
+ axis: "h",
+ //* A value between min and max to position the Slideable
+ value: 0,
+ //* Unit for min, max, and value; can be "px" or "%"
+ unit: "px",
+ //* A minimum value to slide to
+ min: 0,
+ //* A maximum value to slide to
+ max: 0,
+ accelerated: "auto",
+ //* Set to false to prevent the Slideable from dragging with elasticity past its min/max value.
+ overMoving: true,
+ //* Set to false to disable dragging.
+ draggable: true
+ },
+ events: {
+ //* Fires when the Slideable finishes animating.
+ onAnimateFinish: "",
+ onChange: ""
+ },
+ // Set to true to prevent a drag from bubbling beyond the Slideable.
+ preventDragPropagation: false,
+ //* @protected
+ tools: [
+ {kind: "Animator", onStep: "animatorStep", onEnd: "animatorComplete"}
+ ],
+ handlers: {
+ ondragstart: "dragstart",
+ ondrag: "drag",
+ ondragfinish: "dragfinish"
+ },
+ kDragScalar: 1,
+ dragEventProp: "dx",
+ unitModifier: false,
+ canTransform: false,
+ //* @protected
+ create: function() {
+ this.inherited(arguments);
+ this.acceleratedChanged();
+ this.transformChanged();
+ this.axisChanged();
+ this.valueChanged();
+ this.addClass("enyo-slideable");
+ },
+ initComponents: function() {
+ this.createComponents(this.tools);
+ this.inherited(arguments);
+ },
+ rendered: function() {
+ this.inherited(arguments);
+ this.canModifyUnit();
+ this.updateDragScalar();
+ },
+ resizeHandler: function() {
+ this.inherited(arguments);
+ this.updateDragScalar();
+ },
+ canModifyUnit: function() {
+ if (!this.canTransform) {
+ var b = this.getInitialStyleValue(this.hasNode(), this.boundary);
+ // If inline style of "px" exists, while unit is "%"
+ if (b.match(/px/i) && (this.unit === "%")) {
+ // Set unitModifier - used to over-ride "%"
+ this.unitModifier = this.getBounds()[this.dimension];
+ }
+ }
+ },
+ getInitialStyleValue: function(inNode, inBoundary) {
+ var s = enyo.dom.getComputedStyle(inNode);
+ if (s) {
+ return s.getPropertyValue(inBoundary);
+ } else if (inNode && inNode.currentStyle) {
+ return inNode.currentStyle[inBoundary];
+ }
+ return "0";
+ },
+ updateBounds: function(inValue, inDimensions) {
+ var inBounds = {};
+ inBounds[this.boundary] = inValue;
+ this.setBounds(inBounds, this.unit);
+
+ this.setInlineStyles(inValue, inDimensions);
+ },
+ updateDragScalar: function() {
+ if (this.unit == "%") {
+ var d = this.getBounds()[this.dimension];
+ this.kDragScalar = d ? 100 / d : 1;
+
+ if (!this.canTransform) {
+ this.updateBounds(this.value, 100);
+ }
+ }
+ },
+ transformChanged: function() {
+ this.canTransform = enyo.dom.canTransform();
+ },
+ acceleratedChanged: function() {
+ if (!(enyo.platform.android > 2)) {
+ enyo.dom.accelerate(this, this.accelerated);
+ }
+ },
+ axisChanged: function() {
+ var h = this.axis == "h";
+ this.dragMoveProp = h ? "dx" : "dy";
+ this.shouldDragProp = h ? "horizontal" : "vertical";
+ this.transform = h ? "translateX" : "translateY";
+ this.dimension = h ? "width" : "height";
+ this.boundary = h ? "left" : "top";
+ },
+ setInlineStyles: function(inValue, inDimensions) {
+ var inBounds = {};
+
+ if (this.unitModifier) {
+ inBounds[this.boundary] = this.percentToPixels(inValue, this.unitModifier);
+ inBounds[this.dimension] = this.unitModifier;
+ this.setBounds(inBounds);
+ } else {
+ if (inDimensions) {
+ inBounds[this.dimension] = inDimensions;
+ } else {
+ inBounds[this.boundary] = inValue;
+ }
+ this.setBounds(inBounds, this.unit);
+ }
+ },
+ valueChanged: function(inLast) {
+ var v = this.value;
+ if (this.isOob(v) && !this.isAnimating()) {
+ this.value = this.overMoving ? this.dampValue(v) : this.clampValue(v);
+ }
+ // FIXME: android cannot handle nested compositing well so apply acceleration only if needed
+ // desktop chrome doesn't like this code path so avoid...
+ if (enyo.platform.android > 2) {
+ if (this.value) {
+ if (inLast === 0 || inLast === undefined) {
+ enyo.dom.accelerate(this, this.accelerated);
+ }
+ } else {
+ enyo.dom.accelerate(this, false);
+ }
+ }
+
+ // If platform supports transforms
+ if (this.canTransform) {
+ enyo.dom.transformValue(this, this.transform, this.value + this.unit);
+ // else update inline styles
+ } else {
+ this.setInlineStyles(this.value, false);
+ }
+ this.doChange();
+ },
+ getAnimator: function() {
+ return this.$.animator;
+ },
+ isAtMin: function() {
+ return this.value <= this.calcMin();
+ },
+ isAtMax: function() {
+ return this.value >= this.calcMax();
+ },
+ calcMin: function() {
+ return this.min;
+ },
+ calcMax: function() {
+ return this.max;
+ },
+ clampValue: function(inValue) {
+ var min = this.calcMin();
+ var max = this.calcMax();
+ return Math.max(min, Math.min(inValue, max));
+ },
+ dampValue: function(inValue) {
+ return this.dampBound(this.dampBound(inValue, this.min, 1), this.max, -1);
+ },
+ dampBound: function(inValue, inBoundary, inSign) {
+ var v = inValue;
+ if (v * inSign < inBoundary * inSign) {
+ v = inBoundary + (v - inBoundary) / 4;
+ }
+ return v;
+ },
+ percentToPixels: function(value, dimension) {
+ return Math.floor((dimension / 100) * value);
+ },
+ pixelsToPercent: function(value) {
+ var boundary = this.unitModifier ? this.getBounds()[this.dimension] : this.container.getBounds()[this.dimension];
+ return (value / boundary) * 100;
+ },
+ // dragging
+ shouldDrag: function(inEvent) {
+ return this.draggable && inEvent[this.shouldDragProp];
+ },
+ isOob: function(inValue) {
+ return inValue > this.calcMax() || inValue < this.calcMin();
+ },
+ dragstart: function(inSender, inEvent) {
+ if (this.shouldDrag(inEvent)) {
+ inEvent.preventDefault();
+ this.$.animator.stop();
+ inEvent.dragInfo = {};
+ this.dragging = true;
+ this.drag0 = this.value;
+ this.dragd0 = 0;
+ return this.preventDragPropagation;
+ }
+ },
+ drag: function(inSender, inEvent) {
+ if (this.dragging) {
+ inEvent.preventDefault();
+ var d = this.canTransform ? inEvent[this.dragMoveProp] * this.kDragScalar : this.pixelsToPercent(inEvent[this.dragMoveProp]);
+ var v = this.drag0 + d;
+ var dd = d - this.dragd0;
+ this.dragd0 = d;
+ if (dd) {
+ inEvent.dragInfo.minimizing = dd < 0;
+ }
+ this.setValue(v);
+ return this.preventDragPropagation;
+ }
+ },
+ dragfinish: function(inSender, inEvent) {
+ if (this.dragging) {
+ this.dragging = false;
+ this.completeDrag(inEvent);
+ inEvent.preventTap();
+ return this.preventDragPropagation;
+ }
+ },
+ completeDrag: function(inEvent) {
+ if (this.value !== this.calcMax() && this.value != this.calcMin()) {
+ this.animateToMinMax(inEvent.dragInfo.minimizing);
+ }
+ },
+ // animation
+ isAnimating: function() {
+ return this.$.animator.isAnimating();
+ },
+ play: function(inStart, inEnd) {
+ this.$.animator.play({
+ startValue: inStart,
+ endValue: inEnd,
+ node: this.hasNode()
+ });
+ },
+ //* @public
+ //* Animates to the given value.
+ animateTo: function(inValue) {
+ this.play(this.value, inValue);
+ },
+ //* Animates to the minimum value.
+ animateToMin: function() {
+ this.animateTo(this.calcMin());
+ },
+ //* Animates to the maximum value.
+ animateToMax: function() {
+ this.animateTo(this.calcMax());
+ },
+ //* @protected
+ animateToMinMax: function(inMin) {
+ if (inMin) {
+ this.animateToMin();
+ } else {
+ this.animateToMax();
+ }
+ },
+ animatorStep: function(inSender) {
+ this.setValue(inSender.value);
+ return true;
+ },
+ animatorComplete: function(inSender) {
+ this.doAnimateFinish(inSender);
+ return true;
+ },
+ //* @public
+ //* Toggles between min and max with animation.
+ toggleMinMax: function() {
+ this.animateToMinMax(!this.isAtMin());
+ }
+});
diff --git a/html/lib/layout/slideable/source/package.js b/html/lib/layout/slideable/source/package.js
new file mode 100644
index 0000000..8c4ec43
--- /dev/null
+++ b/html/lib/layout/slideable/source/package.js
@@ -0,0 +1,3 @@
+enyo.depends(
+ "Slideable.js"
+);
diff --git a/html/lib/layout/tree/package.js b/html/lib/layout/tree/package.js
new file mode 100644
index 0000000..1ec0cd0
--- /dev/null
+++ b/html/lib/layout/tree/package.js
@@ -0,0 +1,3 @@
+enyo.depends(
+ "source"
+); \ No newline at end of file
diff --git a/html/lib/layout/tree/source/Node.css b/html/lib/layout/tree/source/Node.css
new file mode 100644
index 0000000..718a2e6
--- /dev/null
+++ b/html/lib/layout/tree/source/Node.css
@@ -0,0 +1,28 @@
+.enyo-node {
+ cursor: default;
+ padding: 4px;
+}
+
+.enyo-node img {
+ vertical-align: middle;
+ padding-right: 6px;
+}
+
+.enyo-node-box {
+ overflow: hidden;
+}
+
+.enyo-node-client {
+ position: relative;
+}
+
+.enyo-animate .enyo-node-box, .enyo-animate .enyo-node-client {
+ transition-property: height, top;
+ transition-duration: 0.2s, 0.2s;
+ -moz-transition-property: height, top;
+ -moz-transition-duration: 0.2s, 0.2s;
+ -o-transition-property: height, top;
+ -o-transition-duration: 0.2s, 0.2s;
+ -webkit-transition-property: height, top;
+ -webkit-transition-duration: 0.2s, 0.2s;
+}
diff --git a/html/lib/layout/tree/source/Node.js b/html/lib/layout/tree/source/Node.js
new file mode 100644
index 0000000..ff79c9a
--- /dev/null
+++ b/html/lib/layout/tree/source/Node.js
@@ -0,0 +1,269 @@
+/**
+ _enyo.Node_ is a control that creates structured trees based on Enyo's child
+ component hierarchy format, e.g.:
+
+ {kind: "Node", icon: "images/folder-open.png", content: "Tree",
+ expandable: true, expanded: true, components: [
+ {icon: "images/file.png", content: "Alpha"},
+ {icon: "images/folder-open.png", content: "Bravo",
+ expandable: true, expanded: false, components: [
+ {icon: "images/file.png", content: "Bravo-Alpha"},
+ {icon: "images/file.png", content: "Bravo-Bravo"},
+ {icon: "images/file.png", content: "Bravo-Charlie"}
+ ]
+ }
+ ]
+ }
+
+ The default kind of components within a node is itself _enyo.Node_, so only
+ the top-level node of the tree needs to be explicitly defined as such.
+
+ When an expandable tree node expands, an _onExpand_ event is sent; when it
+ is tapped, a _nodeTap_ event is sent.
+
+ When the optional property _onlyIconExpands_ is set to true, expandable
+ nodes may only be opened by tapping the icon; tapping the content label
+ will fire the _nodeTap_ event, but will not expand the node.
+*/
+
+enyo.kind({
+ name: "enyo.Node",
+ published: {
+ //* @public
+ //* Whether or not the Node is expandable and has child branches
+ expandable: false,
+ //* Open/closed state of the current Node
+ expanded: false,
+ //* Path to image to be used as the icon for this Node
+ icon: "",
+ /**
+ Optional flag that, when true, causes the Node to expand only when
+ the icon is tapped; not when the contents are tapped
+ */
+ onlyIconExpands: false,
+ //* @protected
+ //* Adds or removes the Enyo-selected CSS class.
+ selected: false
+ },
+ style: "padding: 0 0 0 16px;",
+ content: "Node",
+ defaultKind: "Node",
+ classes: "enyo-node",
+ components: [
+ {name: "icon", kind: "Image", showing: false},
+ {kind: "Control", name: "caption", Xtag: "span", style: "display: inline-block; padding: 4px;", allowHtml: true},
+ {kind: "Control", name: "extra", tag: 'span', allowHtml: true}
+ ],
+ childClient: [
+ {kind: "Control", name: "box", classes: "enyo-node-box", Xstyle: "border: 1px solid orange;", components: [
+ {kind: "Control", name: "client", classes: "enyo-node-client", Xstyle: "border: 1px solid lightblue;"}
+ ]}
+ ],
+ handlers: {
+ ondblclick: "dblclick"
+ },
+ events: {
+ //* @public
+ //* Fired when the Node is tapped
+ onNodeTap: "nodeTap",
+ //* Fired when the Node is double-clicked
+ onNodeDblClick: "nodeDblClick",
+ /**
+ Fired when the Node expands or contracts, as indicated by the
+ 'expanded' property in the event data
+ */
+ onExpand: "nodeExpand",
+ //* Fired when the Node is destroyed
+ onDestroyed: "nodeDestroyed"
+ },
+ //
+ //* @protected
+ create: function() {
+ this.inherited(arguments);
+ //this.expandedChanged();
+ //this.levelChanged();
+ this.selectedChanged();
+ this.iconChanged();
+ },
+ destroy: function() {
+ this.doDestroyed();
+ this.inherited(arguments);
+ },
+ initComponents: function() {
+ // TODO: optimize to create the childClient on demand
+ //this.hasChildren = this.components;
+ if (this.expandable) {
+ this.kindComponents = this.kindComponents.concat(this.childClient);
+ }
+ this.inherited(arguments);
+ },
+ //
+ contentChanged: function() {
+ //this.$.caption.setContent((this.expandable ? (this.expanded ? "-" : "+") : "") + this.content);
+ this.$.caption.setContent(this.content);
+ },
+ iconChanged: function() {
+ this.$.icon.setSrc(this.icon);
+ this.$.icon.setShowing(Boolean(this.icon));
+ },
+ selectedChanged: function() {
+ this.addRemoveClass("enyo-selected", this.selected);
+ },
+ rendered: function() {
+ this.inherited(arguments);
+ if (this.expandable && !this.expanded) {
+ this.quickCollapse();
+ }
+ },
+ //
+ addNodes: function(inNodes) {
+ this.destroyClientControls();
+ for (var i=0, n; n=inNodes[i]; i++) {
+ this.createComponent(n);
+ }
+ this.$.client.render();
+ },
+ addTextNodes: function(inNodes) {
+ this.destroyClientControls();
+ for (var i=0, n; n=inNodes[i]; i++) {
+ this.createComponent({content: n});
+ }
+ this.$.client.render();
+ },
+ //
+ tap: function(inSender, inEvent) {
+ if(!this.onlyIconExpands) {
+ this.toggleExpanded();
+ this.doNodeTap();
+ } else {
+ if((inEvent.target==this.$.icon.hasNode())) {
+ this.toggleExpanded();
+ } else {
+ this.doNodeTap();
+ }
+ }
+ return true;
+ },
+ dblclick: function(inSender, inEvent) {
+ this.doNodeDblClick();
+ return true;
+ },
+ //
+ toggleExpanded: function() {
+ this.setExpanded(!this.expanded);
+ },
+ quickCollapse: function() {
+ this.removeClass("enyo-animate");
+ this.$.box.applyStyle("height", "0");
+ var h = this.$.client.getBounds().height;
+ this.$.client.setBounds({top: -h});
+ },
+ _expand: function() {
+ this.addClass("enyo-animate");
+ var h = this.$.client.getBounds().height;
+ this.$.box.setBounds({height: h});
+ this.$.client.setBounds({top: 0});
+ setTimeout(enyo.bind(this, function() {
+ // things may have happened in the interim, make sure
+ // we only fix height if we are still expanded
+ if (this.expanded) {
+ this.removeClass("enyo-animate");
+ this.$.box.applyStyle("height", "auto");
+ }
+ }), 225);
+ },
+ _collapse: function() {
+ // disable transitions
+ this.removeClass("enyo-animate");
+ // fix the height of our box (rather than 'auto'), this
+ // gives webkit something to lerp from
+ var h = this.$.client.getBounds().height;
+ this.$.box.setBounds({height: h});
+ // yield the thead so DOM can make those changes (without transitions)
+ setTimeout(enyo.bind(this, function() {
+ // enable transitions
+ this.addClass("enyo-animate");
+ // shrink our box to 0
+ this.$.box.applyStyle("height", "0");
+ // slide the contents up
+ this.$.client.setBounds({top: -h});
+ }), 25);
+ },
+ expandedChanged: function(inOldExpanded) {
+ if (!this.expandable) {
+ this.expanded = false;
+ } else {
+ var event = {expanded: this.expanded};
+ this.doExpand(event);
+ if (!event.wait) {
+ this.effectExpanded();
+ }
+ }
+ },
+ effectExpanded: function() {
+ if (this.$.client) {
+ if (!this.expanded) {
+ this._collapse();
+ } else {
+ this._expand();
+ }
+ }
+ //this.contentChanged();
+ }/*,
+ //
+ //
+ levelChanged: function() {
+ this.applyStyle("padding-left", 16 + "px");
+ },
+ toggleChildren: function() {
+ if (this.$.list) {
+ this.$.list.setShowing(this.expanded);
+ }
+ },
+ renderNodes: function(inNodes) {
+ var list = this.createComponent({name: "list", container: this});
+ for (var i=0, n; n=inNodes[i]; i++) {
+ n.setLevel(this.level + 1);
+ n.setContainer(list);
+ n.render();
+ }
+ list.render();
+ },
+ //* @public
+ addNodes: function(inNodes) {
+ this.renderNodes(inNodes);
+ this.toggleChildren();
+ },
+ removeNodes: function() {
+ if (this.$.list) {
+ this.$.list.destroy();
+ }
+ },
+ hasVisibleChildren: function() {
+ return this.expanded && this.$.list && this.$.list.controls.length > 0;
+ },
+ fetchParent: function() {
+ return this.level > 0 && this.container.container;
+ },
+ fetchChildren: function() {
+ return this.$.list && this.$.list.controls;
+ },
+ fetchFirstChild: function() {
+ return this.$.list && this.$.list.controls[0];
+ },
+ fetchLastChild: function() {
+ return this.$.list && this.$.list.controls[this.$.list.controls.length-1];
+ },
+ fetchPrevSibling: function() {
+ var i = this.container.controls.indexOf(this);
+ return this.level > 0 && this.container.controls[i-1];
+ },
+ fetchNextSibling: function() {
+ var i = this.container.controls.indexOf(this);
+ return this.level > 0 && this.container.controls[i+1];
+ },
+ getVisibleBounds: function() {
+ return this.$.client.getBounds();
+ }
+ */
+}); \ No newline at end of file
diff --git a/html/lib/layout/tree/source/package.js b/html/lib/layout/tree/source/package.js
new file mode 100644
index 0000000..5986bea
--- /dev/null
+++ b/html/lib/layout/tree/source/package.js
@@ -0,0 +1,4 @@
+enyo.depends(
+ "Node.css",
+ "Node.js"
+); \ No newline at end of file