diff --git a/Makefile b/Makefile index 2e5f1d14ed..bae5e7dd8b 100644 --- a/Makefile +++ b/Makefile @@ -7,5 +7,5 @@ release: cordova: @cp -R js/ example/cordova/iOS/www/js - @cp dist/framework.css example/cordova/iOS/www/css + @cp dist/ionic.css example/cordova/iOS/www/css diff --git a/dist/ionic.css b/dist/ionic.css index ab70b47096..b93bcb36ea 100644 --- a/dist/ionic.css +++ b/dist/ionic.css @@ -42,6 +42,16 @@ main > * { .content-padded { padding: 10px; } +.section { + position: fixed; + z-index: 1; } + +.full-section { + position: fixed; + z-index: 1; + width: 100%; + height: 100%; } + .alert { padding: 8px 35px 8px 14px; } @@ -1033,6 +1043,7 @@ a.list-item { display: none; min-height: 100%; max-height: 100%; + width: 270px; position: absolute; top: 0; bottom: 0; @@ -1041,40 +1052,16 @@ a.list-item { /* has to be scroll, not auto */ -webkit-overflow-scrolling: touch; } -.ion-panel-active-left { +.ion-panel-left { left: 0; } -.ion-panel-active-right { +.ion-panel-right { right: 0; } -.ion-panel-active { - display: block; - width: 270px; } - -.bar-header, .content, .bar-footer { - z-index: 100; - left: 0; - right: 0; +.ion-panel-animated { -webkit-transition: -webkit-transform 200ms ease; -moz-transition: -moz-transform 200ms ease; - transition: transform 200ms ease; - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); } - -.ion-panel-left .bar-header, -.ion-panel-left .content, -.ion-panel-left .bar-footer { - -webkit-transform: translate3d(270px, 0, 0); - -moz-transform: translate3d(270px, 0, 0); - transform: translate3d(270px, 0, 0); } - -.ion-panel-right .bar-header, -.ion-panel-right .content, -.ion-panel-right .bar-footer { - -webkit-transform: translate3d(-270px, 0, 0); - -moz-transform: translate3d(-270px, 0, 0); - transform: translate3d(-270px, 0, 0); } + transition: transform 200ms ease; } .ptr-capable { -webkit-user-drag: element; } @@ -1149,6 +1136,11 @@ body { main { background-color: white; } +.full-section { + -webkit-box-shadow: -3px 0px 10px rgba(0, 0, 0, 0.2), 3px 0px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: -3px 0px 10px rgba(0, 0, 0, 0.2), 3px 0px 10px rgba(0, 0, 0, 0.2); + box-shadow: -3px 0px 10px rgba(0, 0, 0, 0.2), 3px 0px 10px rgba(0, 0, 0, 0.2); } + .alert { margin-bottom: 1.42857; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); @@ -1369,7 +1361,8 @@ a.list-item { color: #333333; } .ion-panel { - background: #eeeeee; } + background: #eeeeee; + width: 270px; } .ion-panel-left .ion-panel { border-right: 1px solid #bbbbbb; } diff --git a/example/cordova/iOS/www/css/ionic.css b/example/cordova/iOS/www/css/ionic.css new file mode 100644 index 0000000000..b93bcb36ea --- /dev/null +++ b/example/cordova/iOS/www/css/ionic.css @@ -0,0 +1,1384 @@ +@charset "UTF-8"; +*, *:before, *:after { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; } + +html { + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; } + +body { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; } + +a { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + +ul { + margin: 0; + padding: 0; } + +main { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: auto; + width: 100%; + -webkit-overflow-scrolling: touch; } + +/* Hack to force all relatively and absolutely positioned elements still render while scrolling + Note: This is a bug for "-webkit-overflow-scrolling: touch" (via ratchet) */ +main > * { + -webkit-transform: translateZ(0px); + transform: translateZ(0px); } + +.content-padded { + padding: 10px; } + +.section { + position: fixed; + z-index: 1; } + +.full-section { + position: fixed; + z-index: 1; + width: 100%; + height: 100%; } + +.alert { + padding: 8px 35px 8px 14px; } + +.alert h4 { + margin: 0; } + +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 1.42857; } + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; } + +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; } + +.alert-block p + p { + margin-top: 5px; } + +.bar { + position: fixed; + right: 0; + left: 0; + z-index: 10; + width: 100%; + display: -webkit-box; + display: box; + -webkit-box-orient: horizontal; + box-orient: horizontal; + box-sizing: border-box; + height: 44px; + padding: 8px; + /* + .title + .button:last-child, + .button + .button:last-child, + .button.pull-right { + position: absolute; + top: 5px; + right: 5px; + } + */ } + .bar .title { + /* + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + */ + line-height: 26px; + margin: 0; + text-align: center; + white-space: nowrap; + font-size: 18px; + -webkit-box-flex: 3; + -moz-box-flex: 3; + box-flex: 3; } + .bar .title a { + color: inherit; } + .bar .button { + -webkit-box-flex: 0; + -moz-box-flex: 0; + box-flex: 0; } + +.bar-header { + top: 0; } + +.bar-footer { + bottom: 0; } + +/* Pad top/bottom of content so it doesn't hide behind .bar-title and .bar-tab. + Note: For these to work, content must come after both bars in the markup */ +.has-header { + top: 44px; } + +.has-footer { + bottom: 44px; } + +.button { + position: relative; + display: block; + vertical-align: middle; + text-align: center; + cursor: pointer; + margin: 0; } + .button.button-inline { + display: inline-block; } + .button.button-borderless { + border: none; + padding: 2px 6px; } + +.button-group { + position: relative; + display: inline-block; + vertical-align: middle; } + .button-group > .button { + position: relative; + float: left; } + .button-group > .button:hover, .button-group > .button:focus, .button-group > .button:active, .button-group > .button.active { + z-index: 2; } + .button-group > .button:focus { + outline: none; } + +.button-group .button + .button, +.button-group .button + .button-group, +.button-group .button-group + .button, +.button-group .button-group + .button-group { + margin-left: -1px; } + +.button-group > .button:not(:first-child):not(:last-child) { + border-radius: 0; } + +.button-group > .button:first-child { + margin-left: 0; } + .button-group > .button:first-child:not(:last-child) { + -webkit-border-top-right-radius: 0; + -moz-border-radius-topright: 0; + border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomright: 0; + border-bottom-right-radius: 0; } + +.button-group > .button:last-child:not(:first-child) { + -webkit-border-top-left-radius: 0; + -moz-border-radius-topleft: 0; + border-top-left-radius: 0; + -webkit-border-bottom-left-radius: 0; + -moz-border-radius-bottomleft: 0; + border-bottom-left-radius: 0; } + +.button-group > .button-group { + float: left; } + +.button-group > .button-group:not(:first-child):not(:last-child) > .button { + border-radius: 0; } + +.button-group > .button-group:first-child > .button:last-child, +.button-group > .button-group:first-child > .dropdown-toggle { + -webkit-border-top-right-radius: 0; + -moz-border-radius-topright: 0; + border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomright: 0; + border-bottom-right-radius: 0; } + +.button-group > .button-group:last-child > .button:first-child { + -webkit-border-top-left-radius: 0; + -moz-border-radius-topleft: 0; + border-top-left-radius: 0; + -webkit-border-bottom-left-radius: 0; + -moz-border-radius-bottomleft: 0; + border-bottom-left-radius: 0; } + +form { + margin: 0 0 1.42857; } + +fieldset { + padding: 0; + margin: 0; + border: 0; } + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 1.42857; + font-size: 21px; + line-height: 2.85714; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; } + legend small { + font-size: 1.07143; + color: #999999; } + +label, +input, +button, +select, +textarea { + font-size: 14px; + font-weight: normal; + line-height: 1.42857; } + +input, +button, +select, +textarea { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } + +label { + display: block; + margin-bottom: 5px; } + +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: 34px; + padding: 4px 6px; + margin-bottom: 10px; + font-size: 14px; + line-height: 20px; + color: #555555; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + vertical-align: middle; } + +input, +textarea, +.uneditable-input { + width: 100%; } + +textarea { + height: auto; } + +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: white; + border: 1px solid #cccccc; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + -moz-transition: border linear 0.2s, box-shadow linear 0.2s; + -o-transition: border linear 0.2s, box-shadow linear 0.2s; + transition: border linear 0.2s, box-shadow linear 0.2s; } + textarea:focus, + input[type="text"]:focus, + input[type="password"]:focus, + input[type="datetime"]:focus, + input[type="datetime-local"]:focus, + input[type="date"]:focus, + input[type="month"]:focus, + input[type="time"]:focus, + input[type="week"]:focus, + input[type="number"]:focus, + input[type="email"]:focus, + input[type="url"]:focus, + input[type="search"]:focus, + input[type="tel"]:focus, + input[type="color"]:focus, + .uneditable-input:focus { + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + outline: thin dotted \9; + /* IE6-9 */ + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + *margin-top: 0; + /* IE7 */ + margin-top: 1px \9; + /* IE8-9 */ + line-height: normal; } + +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; } + +select, +input[type="file"] { + height: 24px; + /* In IE7, the height of the select element cannot be changed by height, only font-size */ + *margin-top: 4px; + /* For IE7, add top margin to align select with labels */ + line-height: 24px; } + +select { + width: 220px; + border: 1px solid #cccccc; + background-color: white; } + +select[multiple], +select[size] { + height: auto; } + +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } + +.uneditable-input, +.uneditable-textarea { + color: #999999; + background-color: #fcfcfc; + border-color: #cccccc; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + cursor: not-allowed; } + +.uneditable-input { + overflow: hidden; + white-space: nowrap; } + +.uneditable-textarea { + width: auto; + height: auto; } + +input:-moz-placeholder, +textarea:-moz-placeholder { + color: #999999; } +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: #999999; } +input::-webkit-input-placeholder, +textarea::-webkit-input-placeholder { + color: #999999; } + +.radio, +.checkbox { + min-height: 1.42857; + padding-left: 20px; } + +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: -20px; } + +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; } + +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; } + +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; } + +.input-mini { + width: 60px; } + +.input-small { + width: 90px; } + +.input-medium { + width: 150px; } + +.input-large { + width: 210px; } + +.input-xlarge { + width: 270px; } + +.input-xxlarge { + width: 530px; } + +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; } + +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; } + +input, +textarea, +.uneditable-input { + margin-left: 0; } + +.controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; } + +input.span1, +textarea.span1, +.uneditable-input.span1 { + width: 46px; } + +input.span2, +textarea.span2, +.uneditable-input.span2 { + width: 126px; } + +input.span3, +textarea.span3, +.uneditable-input.span3 { + width: 206px; } + +input.span4, +textarea.span4, +.uneditable-input.span4 { + width: 286px; } + +input.span5, +textarea.span5, +.uneditable-input.span5 { + width: 366px; } + +input.span6, +textarea.span6, +.uneditable-input.span6 { + width: 446px; } + +input.span7, +textarea.span7, +.uneditable-input.span7 { + width: 526px; } + +input.span8, +textarea.span8, +.uneditable-input.span8 { + width: 606px; } + +input.span9, +textarea.span9, +.uneditable-input.span9 { + width: 686px; } + +input.span10, +textarea.span10, +.uneditable-input.span10 { + width: 766px; } + +input.span11, +textarea.span11, +.uneditable-input.span11 { + width: 846px; } + +input.span12, +textarea.span12, +.uneditable-input.span12 { + width: 926px; } + +.controls-row { + *zoom: 1; } + .controls-row:before, .controls-row:after { + display: table; + content: ""; + line-height: 0; } + .controls-row:after { + clear: both; } + +.controls-row [class*="span"], +.row-fluid .controls-row [class*="span"] { + float: left; } + +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; } + +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: #eeeeee; } + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; } + +.control-group.warning .control-label, +.control-group.warning .help-block, +.control-group.warning .help-inline { + color: #c09853; } +.control-group.warning .checkbox, +.control-group.warning .radio, +.control-group.warning input, +.control-group.warning select, +.control-group.warning textarea { + color: #c09853; } +.control-group.warning input, +.control-group.warning select, +.control-group.warning textarea { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .control-group.warning input:focus, + .control-group.warning select:focus, + .control-group.warning textarea:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; } +.control-group.warning .input-prepend .add-on, +.control-group.warning .input-append .add-on { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; } + +.control-group.error .control-label, +.control-group.error .help-block, +.control-group.error .help-inline { + color: #b94a48; } +.control-group.error .checkbox, +.control-group.error .radio, +.control-group.error input, +.control-group.error select, +.control-group.error textarea { + color: #b94a48; } +.control-group.error input, +.control-group.error select, +.control-group.error textarea { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .control-group.error input:focus, + .control-group.error select:focus, + .control-group.error textarea:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; } +.control-group.error .input-prepend .add-on, +.control-group.error .input-append .add-on { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; } + +.control-group.success .control-label, +.control-group.success .help-block, +.control-group.success .help-inline { + color: #468847; } +.control-group.success .checkbox, +.control-group.success .radio, +.control-group.success input, +.control-group.success select, +.control-group.success textarea { + color: #468847; } +.control-group.success input, +.control-group.success select, +.control-group.success textarea { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .control-group.success input:focus, + .control-group.success select:focus, + .control-group.success textarea:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; } +.control-group.success .input-prepend .add-on, +.control-group.success .input-append .add-on { + color: #468847; + background-color: #dff0d8; + border-color: #468847; } + +.control-group.info .control-label, +.control-group.info .help-block, +.control-group.info .help-inline { + color: #3a87ad; } +.control-group.info .checkbox, +.control-group.info .radio, +.control-group.info input, +.control-group.info select, +.control-group.info textarea { + color: #3a87ad; } +.control-group.info input, +.control-group.info select, +.control-group.info textarea { + border-color: #3a87ad; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .control-group.info input:focus, + .control-group.info select:focus, + .control-group.info textarea:focus { + border-color: #2d6987; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; } +.control-group.info .input-prepend .add-on, +.control-group.info .input-append .add-on { + color: #3a87ad; + background-color: #d9edf7; + border-color: #3a87ad; } + +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + border-color: #ee5f5b; } + input:focus:invalid:focus, + textarea:focus:invalid:focus, + select:focus:invalid:focus { + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; } + +.form-actions { + padding: 0.42857 20px 1.42857; + margin-top: 1.42857; + margin-bottom: 1.42857; + background-color: whitesmoke; + border-top: 1px solid #e5e5e5; + *zoom: 1; } + .form-actions:before, .form-actions:after { + display: table; + content: ""; + line-height: 0; } + .form-actions:after { + clear: both; } + +.help-block, +.help-inline { + color: #595959; } + +.help-block { + display: block; + margin-bottom: 0.71429; } + +.help-inline { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + *zoom: 1; + vertical-align: middle; + padding-left: 5px; } + +.input-append, +.input-prepend { + display: inline-block; + margin-bottom: 0.71429; + vertical-align: middle; + font-size: 0; + white-space: nowrap; } + .input-append input, + .input-append select, + .input-append .uneditable-input, + .input-append .dropdown-menu, + .input-append .popover, + .input-prepend input, + .input-prepend select, + .input-prepend .uneditable-input, + .input-prepend .dropdown-menu, + .input-prepend .popover { + font-size: 14px; } + .input-append input, + .input-append select, + .input-append .uneditable-input, + .input-prepend input, + .input-prepend select, + .input-prepend .uneditable-input { + position: relative; + margin-bottom: 0; + *margin-left: 0; + vertical-align: top; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } + .input-append input:focus, + .input-append select:focus, + .input-append .uneditable-input:focus, + .input-prepend input:focus, + .input-prepend select:focus, + .input-prepend .uneditable-input:focus { + z-index: 2; } + .input-append .add-on, + .input-prepend .add-on { + display: inline-block; + width: auto; + height: 1.42857; + min-width: 16px; + padding: 4px 5px; + font-size: 14px; + font-weight: normal; + line-height: 1.42857; + text-align: center; + text-shadow: 0 1px 0 white; + background-color: #eeeeee; + border: 1px solid #cccccc; } + .input-append .add-on, + .input-append .btn, + .input-append .btn-group > .dropdown-toggle, + .input-prepend .add-on, + .input-prepend .btn, + .input-prepend .btn-group > .dropdown-toggle { + vertical-align: top; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; } + .input-append .active, + .input-prepend .active { + background-color: #a9dba9; + border-color: #46a546; } + +.input-prepend .add-on, +.input-prepend .btn { + margin-right: -1px; } +.input-prepend .add-on:first-child, +.input-prepend .btn:first-child { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; } + +.input-append input, +.input-append select, +.input-append .uneditable-input { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; } + .input-append input + .btn-group .btn:last-child, + .input-append select + .btn-group .btn:last-child, + .input-append .uneditable-input + .btn-group .btn:last-child { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } +.input-append .add-on, +.input-append .btn, +.input-append .btn-group { + margin-left: -1px; } +.input-append .add-on:last-child, +.input-append .btn:last-child, +.input-append .btn-group:last-child > .dropdown-toggle { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } + +.input-prepend.input-append input, +.input-prepend.input-append select, +.input-prepend.input-append .uneditable-input { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; } + .input-prepend.input-append input + .btn-group .btn, + .input-prepend.input-append select + .btn-group .btn, + .input-prepend.input-append .uneditable-input + .btn-group .btn { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } +.input-prepend.input-append .add-on:first-child, +.input-prepend.input-append .btn:first-child { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; } +.input-prepend.input-append .add-on:last-child, +.input-prepend.input-append .btn:last-child { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } +.input-prepend.input-append .btn-group:first-child { + margin-left: 0; } + +input.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; + /* IE7-8 doesn't have border-radius, so don't indent the padding */ + margin-bottom: 0; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; } + +/* Allow for input prepend/append in search forms */ +.form-search .input-append .search-query, +.form-search .input-prepend .search-query { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; } + +.form-search .input-append .search-query { + -webkit-border-radius: 14px 0 0 14px; + -moz-border-radius: 14px 0 0 14px; + border-radius: 14px 0 0 14px; } + +.form-search .input-append .btn { + -webkit-border-radius: 0 14px 14px 0; + -moz-border-radius: 0 14px 14px 0; + border-radius: 0 14px 14px 0; } + +.form-search .input-prepend .search-query { + -webkit-border-radius: 0 14px 14px 0; + -moz-border-radius: 0 14px 14px 0; + border-radius: 0 14px 14px 0; } + +.form-search .input-prepend .btn { + -webkit-border-radius: 14px 0 0 14px; + -moz-border-radius: 14px 0 0 14px; + border-radius: 14px 0 0 14px; } + +.form-search input, +.form-search textarea, +.form-search select, +.form-search .help-inline, +.form-search .uneditable-input, +.form-search .input-prepend, +.form-search .input-append, +.form-inline input, +.form-inline textarea, +.form-inline select, +.form-inline .help-inline, +.form-inline .uneditable-input, +.form-inline .input-prepend, +.form-inline .input-append, +.form-horizontal input, +.form-horizontal textarea, +.form-horizontal select, +.form-horizontal .help-inline, +.form-horizontal .uneditable-input, +.form-horizontal .input-prepend, +.form-horizontal .input-append { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + *zoom: 1; + margin-bottom: 0; + vertical-align: middle; } +.form-search .hide, +.form-inline .hide, +.form-horizontal .hide { + display: none; } + +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; } + +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; } + +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; } + +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; } + +.control-group { + margin-bottom: 0.71429; } + +legend + .control-group { + margin-top: 1.42857; + -webkit-margin-top-collapse: separate; } + +.form-horizontal .control-group { + margin-bottom: 1.42857; + *zoom: 1; } + .form-horizontal .control-group:before, .form-horizontal .control-group:after { + display: table; + content: ""; + line-height: 0; } + .form-horizontal .control-group:after { + clear: both; } +.form-horizontal .control-label { + float: left; + width: 160px; + padding-top: 5px; + text-align: right; } +.form-horizontal .controls { + *display: inline-block; + *padding-left: 20px; + margin-left: 180px; + *margin-left: 0; } + .form-horizontal .controls:first-child { + *padding-left: 180px; } +.form-horizontal .help-block { + margin-bottom: 0; } +.form-horizontal input + .help-block, +.form-horizontal select + .help-block, +.form-horizontal textarea + .help-block, +.form-horizontal .uneditable-input + .help-block, +.form-horizontal .input-prepend + .help-block, +.form-horizontal .input-append + .help-block { + margin-top: 0.71429; } +.form-horizontal .form-actions { + padding-left: 180px; } + +.list { + margin-bottom: 20px; + padding-left: 0; } + +.list-item { + position: relative; + display: block; + padding: 15px 15px; + margin-bottom: -1px; + border: 1px solid #dddddd; } + .list-item:last-child { + margin-bottom: 0; } + .list-item > .badge { + float: right; } + .list-item > i:last-child { + float: right; } + .list-item > .badge + .badge { + margin-right: 5px; } + .list-item.active, .list-item.active:hover, .list-item.active:focus { + z-index: 2; } + .list-item.active .list-item-heading, .list-item.active:hover .list-item-heading, .list-item.active:focus .list-item-heading { + color: inherit; } + +a.list-item { + text-decoration: none; } + a.list-item:hover, a.list-item:focus { + text-decoration: none; } + +.list-divider { + padding: 5px 15px; } + +.list-item-heading { + margin-top: 0; + margin-bottom: 5px; } + +.list-item-text { + margin-bottom: 0; + line-height: 1.3; } + +.ion-panel { + display: none; + min-height: 100%; + max-height: 100%; + width: 270px; + position: absolute; + top: 0; + bottom: 0; + z-index: 0; + overflow-y: scroll; + /* has to be scroll, not auto */ + -webkit-overflow-scrolling: touch; } + +.ion-panel-left { + left: 0; } + +.ion-panel-right { + right: 0; } + +.ion-panel-animated { + -webkit-transition: -webkit-transform 200ms ease; + -moz-transition: -moz-transform 200ms ease; + transition: transform 200ms ease; } + +.ptr-capable { + -webkit-user-drag: element; } + +.ptr-content { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: auto; + height: 0; } + +.ptr-content .pulling { + display: none; } + +.ptr-content .refreshing { + display: none; } + +/* Bar docked to bottom used for primary app navigation */ +.tabs { + right: 0; + left: 0; + bottom: 0; + height: 50px; + padding: 0; + box-sizing: border-box; + position: fixed; } + +.tabs-inner { + display: -webkit-box; + display: box; + height: 100%; + list-style: none; + -webkit-box-orient: horizontal; + box-orient: horizontal; } + +/* Navigational tab */ +.tab-item { + height: 100%; + text-align: center; + box-sizing: border-box; + -webkit-box-flex: 1; + box-flex: 1; } + .tab-item a { + text-decoration: none; + display: block; + width: 100%; + height: 100%; } + +/* Active state for tab */ +.tab-item.active, .tab-item:active { + background-color: rgba(0, 0, 0, 0.2); } + +/* Icon for tab */ +.tab-item i { + display: block; + margin: 0 auto; } + +/* Label for tab */ +.tab-label { + margin-top: 1px; + font-size: 10px; + font-weight: bold; + color: #fff; } + +body { + font-size: 14px; + line-height: 1.25; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } + +main { + background-color: white; } + +.full-section { + -webkit-box-shadow: -3px 0px 10px rgba(0, 0, 0, 0.2), 3px 0px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: -3px 0px 10px rgba(0, 0, 0, 0.2), 3px 0px 10px rgba(0, 0, 0, 0.2); + box-shadow: -3px 0px 10px rgba(0, 0, 0, 0.2), 3px 0px 10px rgba(0, 0, 0, 0.2); } + +.alert { + margin-bottom: 1.42857; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; } + +.alert, +.alert h4 { + color: #c09853; } + +.alert-success { + background-color: #dff0d8; + border-color: #d6e9c6; + color: #468847; } + +.alert-success h4 { + color: #468847; } + +.alert-danger, +.alert-error { + background-color: #f2dede; + border-color: #eed3d7; + color: #b94a48; } + +.alert-danger h4, +.alert-error h4 { + color: #b94a48; } + +.alert-info { + background-color: #d9edf7; + border-color: #bce8f1; + color: #3a87ad; } + +.alert-info h4 { + color: #3a87ad; } + +.bar { + background-color: white; + border-style: solid; + border-width: 0; + /* + Disabled temporarily because it's annoying for testing. + @media screen and (orientation : landscape) { + padding: $barPaddingLandscape; + } + */ } + .bar.bar-header { + border-bottom-width: 1px; } + .bar.bar-footer { + border-top-width: 1px; } + .bar.bar-default { + background-color: white; + border-color: #dddddd; + color: #333333; } + .bar.bar-default .tab-item a { + color: #333333; } + .bar.bar-secondary { + background-color: whitesmoke; + border-color: #cccccc; + color: #333333; } + .bar.bar-secondary .tab-item a { + color: #333333; } + .bar.bar-primary { + background-color: #6999e9; + border-color: #5981c5; + color: white; } + .bar.bar-primary .tab-item a { + color: white; } + .bar.bar-info { + background-color: #60d2e6; + border-color: #51b3c4; + color: white; } + .bar.bar-info .tab-item a { + color: white; } + .bar.bar-success { + background-color: #89c163; + border-color: #71a052; + color: white; } + .bar.bar-success .tab-item a { + color: white; } + .bar.bar-warning { + background-color: #f0b840; + border-color: #cf9a29; + color: white; } + .bar.bar-warning .tab-item a { + color: white; } + .bar.bar-danger { + background-color: #de5645; + border-color: #bc4435; + color: white; } + .bar.bar-danger .tab-item a { + color: white; } + .bar.bar-dark { + background-color: #444444; + border-color: #111111; + color: white; } + .bar.bar-dark .tab-item a { + color: white; } + +.button { + color: #222222; + border-radius: 2px; + border-width: 1px; + border-style: solid; + padding: 10px 15px; } + +a.button { + text-decoration: none; } + +.button-default { + color: #333333; + background-color: white; + border-color: #dddddd; } + .button-default:hover { + color: #333333; + text-decoration: none; } + .button-default.active, .button-default:active { + background-color: #ebebeb; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #c4c4c4; } + +.button-secondary { + color: #333333; + background-color: whitesmoke; + border-color: #cccccc; } + .button-secondary:hover { + color: #333333; + text-decoration: none; } + .button-secondary.active, .button-secondary:active { + background-color: #e1e1e1; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #b3b3b3; } + +.button-primary { + color: white; + background-color: #6999e9; + border-color: #5981c5; } + .button-primary:hover { + color: white; + text-decoration: none; } + .button-primary.active, .button-primary:active { + background-color: #4581e4; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #3d67ae; } + +.button-info { + color: white; + background-color: #60d2e6; + border-color: #51b3c4; } + .button-info:hover { + color: white; + text-decoration: none; } + .button-info.active, .button-info:active { + background-color: #3dc8e0; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #3998a9; } + +.button-success { + color: white; + background-color: #89c163; + border-color: #71a052; } + .button-success:hover { + color: white; + text-decoration: none; } + .button-success.active, .button-success:active { + background-color: #73b447; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #597e41; } + +.button-warning { + color: white; + background-color: #f0b840; + border-color: #cf9a29; } + .button-warning:hover { + color: white; + text-decoration: none; } + .button-warning.active, .button-warning:active { + background-color: #edaa1a; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #a47a21; } + +.button-danger { + color: white; + background-color: #de5645; + border-color: #bc4435; } + .button-danger:hover { + color: white; + text-decoration: none; } + .button-danger.active, .button-danger:active { + background-color: #d43926; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: #94362a; } + +.button-dark { + color: white; + background-color: #444444; + border-color: #111111; } + .button-dark:hover { + color: white; + text-decoration: none; } + .button-dark.active, .button-dark:active { + background-color: #303030; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.12); + border-color: black; } + +.button-transparent { + background: transparent; } + +.button-borderless [class^="icon-"], [class*=" icon-"] { + font-size: 2.3em; } + +.list-divider { + background-color: whitesmoke; + color: #222222; + font-weight: bold; } + +a.list-item { + color: #333333; } + +.ion-panel { + background: #eeeeee; + width: 270px; } + +.ion-panel-left .ion-panel { + border-right: 1px solid #bbbbbb; } + +.ion-panel-right .ion-panel { + border-left: 1px solid #bbbbbb; } + +.ptr-content { + background: #eee; } + +.tabs { + font-size: 16px; + height: 60px; } + +.tab-item a { + font-family: "Helvetica Neue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 200; } +.tab-item i { + font-size: 25px; } diff --git a/example/cordova/iOS/www/index.html b/example/cordova/iOS/www/index.html index adcae462a7..3902237112 100755 --- a/example/cordova/iOS/www/index.html +++ b/example/cordova/iOS/www/index.html @@ -6,7 +6,7 @@ - + @@ -30,25 +30,223 @@ - -
- This is my default left side panel! +
+
- -
- This is my right side panel! -
- -
- This is my other left side panel! + +
+
- - - - - + + + diff --git a/example/cordova/iOS/www/js/ionic-buttons.js b/example/cordova/iOS/www/js/ionic-buttons.js new file mode 100644 index 0000000000..591bcf4994 --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-buttons.js @@ -0,0 +1,29 @@ +(function(window, document, ion) { + ion.Button = function() {} + + // Process an the touchstart event and if this is a button, + // add the .active class so Android will show depressed + // button states. + ion.Button.prototype._onTouchStart = function(event) { + console.log('Touch start!', event); + if(event.target && event.target.classList.contains('button')) { + event.target.classList.add('active'); + } + }; + + + // Remove any active state on touch end/cancel/etc. + ion.Button.prototype._onTouchEnd = function(event) { + console.log('Touch end!', event); + if(event.target && event.target.classList.contains('button')) { + event.target.classList.remove('active'); + } + + // TODO: Process the click? Set flag to not process other click events + }; + + document.addEventListener('touchstart', ion.Button.prototype._onTouchStart); + document.addEventListener('touchend', ion.Button.prototype._onTouchEnd); + document.addEventListener('touchcancel', ion.Button.prototype._onTouchEnd); + +})(this, document, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-events.js b/example/cordova/iOS/www/js/ionic-events.js new file mode 100644 index 0000000000..8e13742947 --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-events.js @@ -0,0 +1,98 @@ +/** + * ion-events.js + * + * Author: Max Lynch + * + * Framework events handles various mobile browser events, and + * detects special events like tap/swipe/etc. and emits them + * as custom events that can be used in an app. + * + * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys! + */ + +(function(window, document, ion) { + ion.EventController = { + + // Trigger a new event + trigger: function(eventType, data) { + // TODO: Do we need to use the old-school createEvent stuff? + var event = new CustomEvent(eventType, data); + + // Make sure to trigger the event on the given target, or dispatch it from + // the window if we don't have an event target + data.target && data.target.dispatchEvent(event) || window.dispatchEvent(event); + }, + + // Bind an event + on: function(type, callback, element) { + var e = element || window; + e.addEventListener(type, callback); + }, + + off: function(type, callback, element) { + element.removeEventListener(type, callback); + }, + + // Register for a new gesture event on the given element + onGesture: function(type, callback, element) { + var gesture = new ion.Gesture(element); + gesture.on(type, callback); + return gesture; + }, + + // Unregister a previous gesture event + offGesture: function(gesture, type, callback) { + gesture.off(type, callback); + }, + + // With a click event, we need to check the target + // and if it's an internal target that doesn't want + // a click, cancel it + handleClick: function(e) { + var target = e.target; + + if(ion.Gestures.HAS_TOUCHEVENTS) { + // We don't allow any clicks on mobile + e.preventDefault(); + return false; + } + + if ( + ! target + || e.which > 1 + || e.metaKey + || e.ctrlKey + //|| isScrolling + || location.protocol !== target.protocol + || location.host !== target.host + // Not sure abotu this one + //|| !target.hash && /#/.test(target.href) + || target.hash && target.href.replace(target.hash, '') === location.href.replace(location.hash, '') + //|| target.getAttribute('data-ignore') == 'push' + ) { + // Allow it + return; + } + // We need to cancel this one + e.preventDefault(); + + }, + + handlePopState: function(event) { + console.log("EVENT: popstate", event); + }, + }; + + + // Map some convenient top-level functions for event handling + ion.on = ion.EventController.on; + ion.off = ion.EventController.off; + ion.trigger = ion.EventController.trigger; + ion.onGesture = ion.EventController.onGesture; + ion.offGesture = ion.EventController.offGesture; + + // Set up various listeners + window.addEventListener('click', ion.EventController.handleClick); + //window.addEventListener('popstate', ion.EventController.handlePopState); + +})(this, document, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-gestures.js b/example/cordova/iOS/www/js/ionic-gestures.js new file mode 100644 index 0000000000..a31df278e9 --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-gestures.js @@ -0,0 +1,1420 @@ +/** + * Simple gesture controllers with some common gestures that emit + * gesture events. + * + * Ported from github.com/EightMedia/ion.Gestures.js - thanks! + */ +(function(window, document, ion) { + + /** + * ion.Gestures + * use this to create instances + * @param {HTMLElement} element + * @param {Object} options + * @returns {ion.Gestures.Instance} + * @constructor + */ + ion.Gesture = function(element, options) { + return new ion.Gestures.Instance(element, options || {}); + }; + + ion.Gestures = {}; + + // default settings + ion.Gestures.defaults = { + // add styles and attributes to the element to prevent the browser from doing + // its native behavior. this doesnt prevent the scrolling, but cancels + // the contextmenu, tap highlighting etc + // set to false to disable this + stop_browser_behavior: { + // this also triggers onselectstart=false for IE + userSelect: 'none', + // this makes the element blocking in IE10 >, you could experiment with the value + // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241 + touchAction: 'none', + touchCallout: 'none', + contentZooming: 'none', + userDrag: 'none', + tapHighlightColor: 'rgba(0,0,0,0)' + } + + // more settings are defined per gesture at gestures.js + }; + + // detect touchevents + ion.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled; + ion.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window); + + // dont use mouseevents on mobile devices + ion.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i; + ion.Gestures.NO_MOUSEEVENTS = ion.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ion.Gestures.MOBILE_REGEX); + + // eventtypes per touchevent (start, move, end) + // are filled by ion.Gestures.event.determineEventTypes on setup + ion.Gestures.EVENT_TYPES = {}; + + // direction defines + ion.Gestures.DIRECTION_DOWN = 'down'; + ion.Gestures.DIRECTION_LEFT = 'left'; + ion.Gestures.DIRECTION_UP = 'up'; + ion.Gestures.DIRECTION_RIGHT = 'right'; + + // pointer type + ion.Gestures.POINTER_MOUSE = 'mouse'; + ion.Gestures.POINTER_TOUCH = 'touch'; + ion.Gestures.POINTER_PEN = 'pen'; + + // touch event defines + ion.Gestures.EVENT_START = 'start'; + ion.Gestures.EVENT_MOVE = 'move'; + ion.Gestures.EVENT_END = 'end'; + + // hammer document where the base events are added at + ion.Gestures.DOCUMENT = window.document; + + // plugins namespace + ion.Gestures.plugins = {}; + + // if the window events are set... + ion.Gestures.READY = false; + + /** + * setup events to detect gestures on the document + */ + function setup() { + if(ion.Gestures.READY) { + return; + } + + // find what eventtypes we add listeners to + ion.Gestures.event.determineEventTypes(); + + // Register all gestures inside ion.Gestures.gestures + for(var name in ion.Gestures.gestures) { + if(ion.Gestures.gestures.hasOwnProperty(name)) { + ion.Gestures.detection.register(ion.Gestures.gestures[name]); + } + } + + // Add touch events on the document + ion.Gestures.event.onTouch(ion.Gestures.DOCUMENT, ion.Gestures.EVENT_MOVE, ion.Gestures.detection.detect); + ion.Gestures.event.onTouch(ion.Gestures.DOCUMENT, ion.Gestures.EVENT_END, ion.Gestures.detection.detect); + + // ion.Gestures is ready...! + ion.Gestures.READY = true; + } + + /** + * create new hammer instance + * all methods should return the instance itself, so it is chainable. + * @param {HTMLElement} element + * @param {Object} [options={}] + * @returns {ion.Gestures.Instance} + * @constructor + */ + ion.Gestures.Instance = function(element, options) { + var self = this; + + // setup ion.GesturesJS window events and register all gestures + // this also sets up the default options + setup(); + + this.element = element; + + // start/stop detection option + this.enabled = true; + + // merge options + this.options = ion.Gestures.utils.extend( + ion.Gestures.utils.extend({}, ion.Gestures.defaults), + options || {}); + + // add some css to the element to prevent the browser from doing its native behavoir + if(this.options.stop_browser_behavior) { + ion.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior); + } + + // start detection on touchstart + ion.Gestures.event.onTouch(element, ion.Gestures.EVENT_START, function(ev) { + if(self.enabled) { + ion.Gestures.detection.startDetect(self, ev); + } + }); + + // return instance + return this; + }; + + + ion.Gestures.Instance.prototype = { + /** + * bind events to the instance + * @param {String} gesture + * @param {Function} handler + * @returns {ion.Gestures.Instance} + */ + on: function onEvent(gesture, handler){ + var gestures = gesture.split(' '); + for(var t=0; t 0 && eventType == ion.Gestures.EVENT_END) { + eventType = ion.Gestures.EVENT_MOVE; + } + // no touches, force the end event + else if(!count_touches) { + eventType = ion.Gestures.EVENT_END; + } + + // store the last move event + if(count_touches || last_move_event === null) { + last_move_event = ev; + } + + // trigger the handler + handler.call(ion.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev)); + + // remove pointerevent from list + if(ion.Gestures.HAS_POINTEREVENTS && eventType == ion.Gestures.EVENT_END) { + count_touches = ion.Gestures.PointerEvent.updatePointer(eventType, ev); + } + } + + //debug(sourceEventType +" "+ eventType); + + // on the end we reset everything + if(!count_touches) { + last_move_event = null; + enable_detect = false; + touch_triggered = false; + ion.Gestures.PointerEvent.reset(); + } + }); + }, + + + /** + * we have different events for each device/browser + * determine what we need and set them in the ion.Gestures.EVENT_TYPES constant + */ + determineEventTypes: function determineEventTypes() { + // determine the eventtype we want to set + var types; + + // pointerEvents magic + if(ion.Gestures.HAS_POINTEREVENTS) { + types = ion.Gestures.PointerEvent.getEvents(); + } + // on Android, iOS, blackberry, windows mobile we dont want any mouseevents + else if(ion.Gestures.NO_MOUSEEVENTS) { + types = [ + 'touchstart', + 'touchmove', + 'touchend touchcancel']; + } + // for non pointer events browsers and mixed browsers, + // like chrome on windows8 touch laptop + else { + types = [ + 'touchstart mousedown', + 'touchmove mousemove', + 'touchend touchcancel mouseup']; + } + + ion.Gestures.EVENT_TYPES[ion.Gestures.EVENT_START] = types[0]; + ion.Gestures.EVENT_TYPES[ion.Gestures.EVENT_MOVE] = types[1]; + ion.Gestures.EVENT_TYPES[ion.Gestures.EVENT_END] = types[2]; + }, + + + /** + * create touchlist depending on the event + * @param {Object} ev + * @param {String} eventType used by the fakemultitouch plugin + */ + getTouchList: function getTouchList(ev/*, eventType*/) { + // get the fake pointerEvent touchlist + if(ion.Gestures.HAS_POINTEREVENTS) { + return ion.Gestures.PointerEvent.getTouchList(); + } + // get the touchlist + else if(ev.touches) { + return ev.touches; + } + // make fake touchlist from mouse position + else { + ev.indentifier = 1; + return [ev]; + } + }, + + + /** + * collect event data for ion.Gestures js + * @param {HTMLElement} element + * @param {String} eventType like ion.Gestures.EVENT_MOVE + * @param {Object} eventData + */ + collectEventData: function collectEventData(element, eventType, touches, ev) { + + // find out pointerType + var pointerType = ion.Gestures.POINTER_TOUCH; + if(ev.type.match(/mouse/) || ion.Gestures.PointerEvent.matchType(ion.Gestures.POINTER_MOUSE, ev)) { + pointerType = ion.Gestures.POINTER_MOUSE; + } + + return { + center : ion.Gestures.utils.getCenter(touches), + timeStamp : new Date().getTime(), + target : ev.target, + touches : touches, + eventType : eventType, + pointerType : pointerType, + srcEvent : ev, + + /** + * prevent the browser default actions + * mostly used to disable scrolling of the browser + */ + preventDefault: function() { + if(this.srcEvent.preventManipulation) { + this.srcEvent.preventManipulation(); + } + + if(this.srcEvent.preventDefault) { + this.srcEvent.preventDefault(); + } + }, + + /** + * stop bubbling the event up to its parents + */ + stopPropagation: function() { + this.srcEvent.stopPropagation(); + }, + + /** + * immediately stop gesture detection + * might be useful after a swipe was detected + * @return {*} + */ + stopDetect: function() { + return ion.Gestures.detection.stopDetect(); + } + }; + } + }; + + ion.Gestures.PointerEvent = { + /** + * holds all pointers + * @type {Object} + */ + pointers: {}, + + /** + * get a list of pointers + * @returns {Array} touchlist + */ + getTouchList: function() { + var self = this; + var touchlist = []; + + // we can use forEach since pointerEvents only is in IE10 + Object.keys(self.pointers).sort().forEach(function(id) { + touchlist.push(self.pointers[id]); + }); + return touchlist; + }, + + /** + * update the position of a pointer + * @param {String} type ion.Gestures.EVENT_END + * @param {Object} pointerEvent + */ + updatePointer: function(type, pointerEvent) { + if(type == ion.Gestures.EVENT_END) { + this.pointers = {}; + } + else { + pointerEvent.identifier = pointerEvent.pointerId; + this.pointers[pointerEvent.pointerId] = pointerEvent; + } + + return Object.keys(this.pointers).length; + }, + + /** + * check if ev matches pointertype + * @param {String} pointerType ion.Gestures.POINTER_MOUSE + * @param {PointerEvent} ev + */ + matchType: function(pointerType, ev) { + if(!ev.pointerType) { + return false; + } + + var types = {}; + types[ion.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ion.Gestures.POINTER_MOUSE); + types[ion.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ion.Gestures.POINTER_TOUCH); + types[ion.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ion.Gestures.POINTER_PEN); + return types[pointerType]; + }, + + + /** + * get events + */ + getEvents: function() { + return [ + 'pointerdown MSPointerDown', + 'pointermove MSPointerMove', + 'pointerup pointercancel MSPointerUp MSPointerCancel' + ]; + }, + + /** + * reset the list + */ + reset: function() { + this.pointers = {}; + } + }; + + + ion.Gestures.utils = { + /** + * extend method, + * also used for cloning when dest is an empty object + * @param {Object} dest + * @param {Object} src + * @parm {Boolean} merge do a merge + * @returns {Object} dest + */ + extend: function extend(dest, src, merge) { + for (var key in src) { + if(dest[key] !== undefined && merge) { + continue; + } + dest[key] = src[key]; + } + return dest; + }, + + + /** + * find if a node is in the given parent + * used for event delegation tricks + * @param {HTMLElement} node + * @param {HTMLElement} parent + * @returns {boolean} has_parent + */ + hasParent: function(node, parent) { + while(node){ + if(node == parent) { + return true; + } + node = node.parentNode; + } + return false; + }, + + + /** + * get the center of all the touches + * @param {Array} touches + * @returns {Object} center + */ + getCenter: function getCenter(touches) { + var valuesX = [], valuesY = []; + + for(var t= 0,len=touches.length; t= y) { + return touch1.pageX - touch2.pageX > 0 ? ion.Gestures.DIRECTION_LEFT : ion.Gestures.DIRECTION_RIGHT; + } + else { + return touch1.pageY - touch2.pageY > 0 ? ion.Gestures.DIRECTION_UP : ion.Gestures.DIRECTION_DOWN; + } + }, + + + /** + * calculate the distance between two touches + * @param {Touch} touch1 + * @param {Touch} touch2 + * @returns {Number} distance + */ + getDistance: function getDistance(touch1, touch2) { + var x = touch2.pageX - touch1.pageX, + y = touch2.pageY - touch1.pageY; + return Math.sqrt((x*x) + (y*y)); + }, + + + /** + * calculate the scale factor between two touchLists (fingers) + * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out + * @param {Array} start + * @param {Array} end + * @returns {Number} scale + */ + getScale: function getScale(start, end) { + // need two fingers... + if(start.length >= 2 && end.length >= 2) { + return this.getDistance(end[0], end[1]) / + this.getDistance(start[0], start[1]); + } + return 1; + }, + + + /** + * calculate the rotation degrees between two touchLists (fingers) + * @param {Array} start + * @param {Array} end + * @returns {Number} rotation + */ + getRotation: function getRotation(start, end) { + // need two fingers + if(start.length >= 2 && end.length >= 2) { + return this.getAngle(end[1], end[0]) - + this.getAngle(start[1], start[0]); + } + return 0; + }, + + + /** + * boolean if the direction is vertical + * @param {String} direction + * @returns {Boolean} is_vertical + */ + isVertical: function isVertical(direction) { + return (direction == ion.Gestures.DIRECTION_UP || direction == ion.Gestures.DIRECTION_DOWN); + }, + + + /** + * stop browser default behavior with css props + * @param {HtmlElement} element + * @param {Object} css_props + */ + stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) { + var prop, + vendors = ['webkit','khtml','moz','Moz','ms','o','']; + + if(!css_props || !element.style) { + return; + } + + // with css properties for modern browsers + for(var i = 0; i < vendors.length; i++) { + for(var p in css_props) { + if(css_props.hasOwnProperty(p)) { + prop = p; + + // vender prefix at the property + if(vendors[i]) { + prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); + } + + // set the style + element.style[prop] = css_props[p]; + } + } + } + + // also the disable onselectstart + if(css_props.userSelect == 'none') { + element.onselectstart = function() { + return false; + }; + } + } + }; + + + ion.Gestures.detection = { + // contains all registred ion.Gestures.gestures in the correct order + gestures: [], + + // data of the current ion.Gestures.gesture detection session + current: null, + + // the previous ion.Gestures.gesture session data + // is a full clone of the previous gesture.current object + previous: null, + + // when this becomes true, no gestures are fired + stopped: false, + + + /** + * start ion.Gestures.gesture detection + * @param {ion.Gestures.Instance} inst + * @param {Object} eventData + */ + startDetect: function startDetect(inst, eventData) { + // already busy with a ion.Gestures.gesture detection on an element + if(this.current) { + return; + } + + this.stopped = false; + + this.current = { + inst : inst, // reference to ion.GesturesInstance we're working for + startEvent : ion.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc + lastEvent : false, // last eventData + name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc + }; + + this.detect(eventData); + }, + + + /** + * ion.Gestures.gesture detection + * @param {Object} eventData + */ + detect: function detect(eventData) { + if(!this.current || this.stopped) { + return; + } + + // extend event data with calculations about scale, distance etc + eventData = this.extendEventData(eventData); + + // instance options + var inst_options = this.current.inst.options; + + // call ion.Gestures.gesture handlers + for(var g=0,len=this.gestures.length; g b.index) { + return 1; + } + return 0; + }); + + return this.gestures; + } + }; + + + ion.Gestures.gestures = ion.Gestures.gestures || {}; + + /** + * Custom gestures + * ============================== + * + * Gesture object + * -------------------- + * The object structure of a gesture: + * + * { name: 'mygesture', + * index: 1337, + * defaults: { + * mygesture_option: true + * } + * handler: function(type, ev, inst) { + * // trigger gesture event + * inst.trigger(this.name, ev); + * } + * } + + * @param {String} name + * this should be the name of the gesture, lowercase + * it is also being used to disable/enable the gesture per instance config. + * + * @param {Number} [index=1000] + * the index of the gesture, where it is going to be in the stack of gestures detection + * like when you build an gesture that depends on the drag gesture, it is a good + * idea to place it after the index of the drag gesture. + * + * @param {Object} [defaults={}] + * the default settings of the gesture. these are added to the instance settings, + * and can be overruled per instance. you can also add the name of the gesture, + * but this is also added by default (and set to true). + * + * @param {Function} handler + * this handles the gesture detection of your custom gesture and receives the + * following arguments: + * + * @param {Object} eventData + * event data containing the following properties: + * timeStamp {Number} time the event occurred + * target {HTMLElement} target element + * touches {Array} touches (fingers, pointers, mouse) on the screen + * pointerType {String} kind of pointer that was used. matches ion.Gestures.POINTER_MOUSE|TOUCH + * center {Object} center position of the touches. contains pageX and pageY + * deltaTime {Number} the total time of the touches in the screen + * deltaX {Number} the delta on x axis we haved moved + * deltaY {Number} the delta on y axis we haved moved + * velocityX {Number} the velocity on the x + * velocityY {Number} the velocity on y + * angle {Number} the angle we are moving + * direction {String} the direction we are moving. matches ion.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT + * distance {Number} the distance we haved moved + * scale {Number} scaling of the touches, needs 2 touches + * rotation {Number} rotation of the touches, needs 2 touches * + * eventType {String} matches ion.Gestures.EVENT_START|MOVE|END + * srcEvent {Object} the source event, like TouchStart or MouseDown * + * startEvent {Object} contains the same properties as above, + * but from the first touch. this is used to calculate + * distances, deltaTime, scaling etc + * + * @param {ion.Gestures.Instance} inst + * the instance we are doing the detection for. you can get the options from + * the inst.options object and trigger the gesture event by calling inst.trigger + * + * + * Handle gestures + * -------------------- + * inside the handler you can get/set ion.Gestures.detection.current. This is the current + * detection session. It has the following properties + * @param {String} name + * contains the name of the gesture we have detected. it has not a real function, + * only to check in other gestures if something is detected. + * like in the drag gesture we set it to 'drag' and in the swipe gesture we can + * check if the current gesture is 'drag' by accessing ion.Gestures.detection.current.name + * + * @readonly + * @param {ion.Gestures.Instance} inst + * the instance we do the detection for + * + * @readonly + * @param {Object} startEvent + * contains the properties of the first gesture detection in this session. + * Used for calculations about timing, distance, etc. + * + * @readonly + * @param {Object} lastEvent + * contains all the properties of the last gesture detect in this session. + * + * after the gesture detection session has been completed (user has released the screen) + * the ion.Gestures.detection.current object is copied into ion.Gestures.detection.previous, + * this is usefull for gestures like doubletap, where you need to know if the + * previous gesture was a tap + * + * options that have been set by the instance can be received by calling inst.options + * + * You can trigger a gesture event by calling inst.trigger("mygesture", event). + * The first param is the name of your gesture, the second the event argument + * + * + * Register gestures + * -------------------- + * When an gesture is added to the ion.Gestures.gestures object, it is auto registered + * at the setup of the first ion.Gestures instance. You can also call ion.Gestures.detection.register + * manually and pass your gesture object as a param + * + */ + + /** + * Hold + * Touch stays at the same place for x time + * @events hold + */ + ion.Gestures.gestures.Hold = { + name: 'hold', + index: 10, + defaults: { + hold_timeout : 500, + hold_threshold : 1 + }, + timer: null, + handler: function holdGesture(ev, inst) { + switch(ev.eventType) { + case ion.Gestures.EVENT_START: + // clear any running timers + clearTimeout(this.timer); + + // set the gesture so we can check in the timeout if it still is + ion.Gestures.detection.current.name = this.name; + + // set timer and if after the timeout it still is hold, + // we trigger the hold event + this.timer = setTimeout(function() { + if(ion.Gestures.detection.current.name == 'hold') { + inst.trigger('hold', ev); + } + }, inst.options.hold_timeout); + break; + + // when you move or end we clear the timer + case ion.Gestures.EVENT_MOVE: + if(ev.distance > inst.options.hold_threshold) { + clearTimeout(this.timer); + } + break; + + case ion.Gestures.EVENT_END: + clearTimeout(this.timer); + break; + } + } + }; + + + /** + * Tap/DoubleTap + * Quick touch at a place or double at the same place + * @events tap, doubletap + */ + ion.Gestures.gestures.Tap = { + name: 'tap', + index: 100, + defaults: { + tap_max_touchtime : 250, + tap_max_distance : 10, + tap_always : true, + doubletap_distance : 20, + doubletap_interval : 300 + }, + handler: function tapGesture(ev, inst) { + if(ev.eventType == ion.Gestures.EVENT_END) { + // previous gesture, for the double tap since these are two different gesture detections + var prev = ion.Gestures.detection.previous, + did_doubletap = false; + + // when the touchtime is higher then the max touch time + // or when the moving distance is too much + if(ev.deltaTime > inst.options.tap_max_touchtime || + ev.distance > inst.options.tap_max_distance) { + return; + } + + // check if double tap + if(prev && prev.name == 'tap' && + (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval && + ev.distance < inst.options.doubletap_distance) { + inst.trigger('doubletap', ev); + did_doubletap = true; + } + + // do a single tap + if(!did_doubletap || inst.options.tap_always) { + ion.Gestures.detection.current.name = 'tap'; + inst.trigger(ion.Gestures.detection.current.name, ev); + } + } + } + }; + + + /** + * Swipe + * triggers swipe events when the end velocity is above the threshold + * @events swipe, swipeleft, swiperight, swipeup, swipedown + */ + ion.Gestures.gestures.Swipe = { + name: 'swipe', + index: 40, + defaults: { + // set 0 for unlimited, but this can conflict with transform + swipe_max_touches : 1, + swipe_velocity : 0.7 + }, + handler: function swipeGesture(ev, inst) { + if(ev.eventType == ion.Gestures.EVENT_END) { + // max touches + if(inst.options.swipe_max_touches > 0 && + ev.touches.length > inst.options.swipe_max_touches) { + return; + } + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.velocityX > inst.options.swipe_velocity || + ev.velocityY > inst.options.swipe_velocity) { + // trigger swipe events + inst.trigger(this.name, ev); + inst.trigger(this.name + ev.direction, ev); + } + } + } + }; + + + /** + * Drag + * Move with x fingers (default 1) around on the page. Blocking the scrolling when + * moving left and right is a good practice. When all the drag events are blocking + * you disable scrolling on that area. + * @events drag, drapleft, dragright, dragup, dragdown + */ + ion.Gestures.gestures.Drag = { + name: 'drag', + index: 50, + defaults: { + drag_min_distance : 10, + // Set correct_for_drag_min_distance to true to make the starting point of the drag + // be calculated from where the drag was triggered, not from where the touch started. + // Useful to avoid a jerk-starting drag, which can make fine-adjustments + // through dragging difficult, and be visually unappealing. + correct_for_drag_min_distance : true, + // set 0 for unlimited, but this can conflict with transform + drag_max_touches : 1, + // prevent default browser behavior when dragging occurs + // be careful with it, it makes the element a blocking element + // when you are using the drag gesture, it is a good practice to set this true + drag_block_horizontal : true, + drag_block_vertical : true, + // drag_lock_to_axis keeps the drag gesture on the axis that it started on, + // It disallows vertical directions if the initial direction was horizontal, and vice versa. + drag_lock_to_axis : false, + // drag lock only kicks in when distance > drag_lock_min_distance + // This way, locking occurs only when the distance has become large enough to reliably determine the direction + drag_lock_min_distance : 25 + }, + triggered: false, + handler: function dragGesture(ev, inst) { + // current gesture isnt drag, but dragged is true + // this means an other gesture is busy. now call dragend + if(ion.Gestures.detection.current.name != this.name && this.triggered) { + inst.trigger(this.name +'end', ev); + this.triggered = false; + return; + } + + // max touches + if(inst.options.drag_max_touches > 0 && + ev.touches.length > inst.options.drag_max_touches) { + return; + } + + switch(ev.eventType) { + case ion.Gestures.EVENT_START: + this.triggered = false; + break; + + case ion.Gestures.EVENT_MOVE: + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.distance < inst.options.drag_min_distance && + ion.Gestures.detection.current.name != this.name) { + return; + } + + // we are dragging! + if(ion.Gestures.detection.current.name != this.name) { + ion.Gestures.detection.current.name = this.name; + if (inst.options.correct_for_drag_min_distance) { + // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center. + // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0. + // It might be useful to save the original start point somewhere + var factor = Math.abs(inst.options.drag_min_distance/ev.distance); + ion.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor; + ion.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor; + + // recalculate event data using new start point + ev = ion.Gestures.detection.extendEventData(ev); + } + } + + // lock drag to axis? + if(ion.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance<=ev.distance)) { + ev.drag_locked_to_axis = true; + } + var last_direction = ion.Gestures.detection.current.lastEvent.direction; + if(ev.drag_locked_to_axis && last_direction !== ev.direction) { + // keep direction on the axis that the drag gesture started on + if(ion.Gestures.utils.isVertical(last_direction)) { + ev.direction = (ev.deltaY < 0) ? ion.Gestures.DIRECTION_UP : ion.Gestures.DIRECTION_DOWN; + } + else { + ev.direction = (ev.deltaX < 0) ? ion.Gestures.DIRECTION_LEFT : ion.Gestures.DIRECTION_RIGHT; + } + } + + // first time, trigger dragstart event + if(!this.triggered) { + inst.trigger(this.name +'start', ev); + this.triggered = true; + } + + // trigger normal event + inst.trigger(this.name, ev); + + // direction event, like dragdown + inst.trigger(this.name + ev.direction, ev); + + // block the browser events + if( (inst.options.drag_block_vertical && ion.Gestures.utils.isVertical(ev.direction)) || + (inst.options.drag_block_horizontal && !ion.Gestures.utils.isVertical(ev.direction))) { + ev.preventDefault(); + } + break; + + case ion.Gestures.EVENT_END: + // trigger dragend + if(this.triggered) { + inst.trigger(this.name +'end', ev); + } + + this.triggered = false; + break; + } + } + }; + + + /** + * Transform + * User want to scale or rotate with 2 fingers + * @events transform, pinch, pinchin, pinchout, rotate + */ + ion.Gestures.gestures.Transform = { + name: 'transform', + index: 45, + defaults: { + // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 + transform_min_scale : 0.01, + // rotation in degrees + transform_min_rotation : 1, + // prevent default browser behavior when two touches are on the screen + // but it makes the element a blocking element + // when you are using the transform gesture, it is a good practice to set this true + transform_always_block : false + }, + triggered: false, + handler: function transformGesture(ev, inst) { + // current gesture isnt drag, but dragged is true + // this means an other gesture is busy. now call dragend + if(ion.Gestures.detection.current.name != this.name && this.triggered) { + inst.trigger(this.name +'end', ev); + this.triggered = false; + return; + } + + // atleast multitouch + if(ev.touches.length < 2) { + return; + } + + // prevent default when two fingers are on the screen + if(inst.options.transform_always_block) { + ev.preventDefault(); + } + + switch(ev.eventType) { + case ion.Gestures.EVENT_START: + this.triggered = false; + break; + + case ion.Gestures.EVENT_MOVE: + var scale_threshold = Math.abs(1-ev.scale); + var rotation_threshold = Math.abs(ev.rotation); + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(scale_threshold < inst.options.transform_min_scale && + rotation_threshold < inst.options.transform_min_rotation) { + return; + } + + // we are transforming! + ion.Gestures.detection.current.name = this.name; + + // first time, trigger dragstart event + if(!this.triggered) { + inst.trigger(this.name +'start', ev); + this.triggered = true; + } + + inst.trigger(this.name, ev); // basic transform event + + // trigger rotate event + if(rotation_threshold > inst.options.transform_min_rotation) { + inst.trigger('rotate', ev); + } + + // trigger pinch event + if(scale_threshold > inst.options.transform_min_scale) { + inst.trigger('pinch', ev); + inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev); + } + break; + + case ion.Gestures.EVENT_END: + // trigger dragend + if(this.triggered) { + inst.trigger(this.name +'end', ev); + } + + this.triggered = false; + break; + } + } + }; + + + /** + * Touch + * Called as first, tells the user has touched the screen + * @events touch + */ + ion.Gestures.gestures.Touch = { + name: 'touch', + index: -Infinity, + defaults: { + // call preventDefault at touchstart, and makes the element blocking by + // disabling the scrolling of the page, but it improves gestures like + // transforming and dragging. + // be careful with using this, it can be very annoying for users to be stuck + // on the page + prevent_default: false, + + // disable mouse events, so only touch (or pen!) input triggers events + prevent_mouseevents: false + }, + handler: function touchGesture(ev, inst) { + if(inst.options.prevent_mouseevents && ev.pointerType == ion.Gestures.POINTER_MOUSE) { + ev.stopDetect(); + return; + } + + if(inst.options.prevent_default) { + ev.preventDefault(); + } + + if(ev.eventType == ion.Gestures.EVENT_START) { + inst.trigger(this.name, ev); + } + } + }; + + + /** + * Release + * Called as last, tells the user has released the screen + * @events release + */ + ion.Gestures.gestures.Release = { + name: 'release', + index: Infinity, + handler: function releaseGesture(ev, inst) { + if(ev.eventType == ion.Gestures.EVENT_END) { + inst.trigger(this.name, ev); + } + } + }; +})(this, document, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-init.js b/example/cordova/iOS/www/js/ionic-init.js new file mode 100644 index 0000000000..5b837f8a4e --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-init.js @@ -0,0 +1,28 @@ +(function(window, document, ion) { + + function initalize() { + // remove the ready listeners + document.removeEventListener( "DOMContentLoaded", initalize, false ); + window.removeEventListener( "load", initalize, false ); + + // trigger that the DOM is ready + ion.trigger("ready"); + + // trigger that the start page is in view + ion.trigger("pageview"); + + // trigger that the webapp has been initalized + ion.trigger("initalized"); + } + + // When the DOM is ready, initalize the webapp + if ( document.readyState === "complete" ) { + // DOM is already ready + setTimeout( initalize ); + } else { + // DOM isn't ready yet, add event listeners + document.addEventListener( "DOMContentLoaded", initalize, false ); + window.addEventListener( "load", initalize, false ); + } + +})(this, document, ion = this.ion || {}); \ No newline at end of file diff --git a/example/cordova/iOS/www/js/ionic-list.js b/example/cordova/iOS/www/js/ionic-list.js new file mode 100644 index 0000000000..4bdbc63e1e --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-list.js @@ -0,0 +1,23 @@ +(function(window, document, ion) { + ion.List = function() {} + + ion.List.prototype._TAB_ITEM_CLASS = 'tab-item'; + + ion.List.prototype._onTouchStart = function(event) { + console.log('Touch start!', event); + if(event.target && event.target.parentNode.classList.contains(this._TAB_ITEM_CLASS)) { + event.target.classList.add('active'); + } + }; + ion.List.prototype._onTouchEnd = function(event) { + console.log('Touch end!', event); + if(event.target && event.target.parentNode.classList.contains(this._TAB_ITEM_CLASS)) { + event.target.classList.remove('active'); + } + }; + + document.addEventListener('mousedown', ion.List.prototype._onTouchStart); + document.addEventListener('touchstart', ion.List.prototype._onTouchStart); + document.addEventListener('touchend', ion.List.prototype._onTouchEnd); + +})(this, document, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-navigation.js b/example/cordova/iOS/www/js/ionic-navigation.js new file mode 100644 index 0000000000..2bea49d5d5 --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-navigation.js @@ -0,0 +1,154 @@ +(function(window, document, location, ion) { + + var + x, + el; + + // Add listeners to each link in the document + function click(e) { + // an element has been clicked. If its a link its good to go + // if its not a link then jump up its parents until you find + // its wrapping link. If you never find a link do nothing. + if(e.target) { + el = e.target; + if(el.tagName === "A") { + return linkClick(e, el); + } + while(el.parentElement) { + el = el.parentElement; + if(el.tagName === "A") { + return linkClick(e, el); + } + } + } + } + + // A link has been clicked + function linkClick(e, el) { + + // if they clicked a link while scrolling don't nav to it + if(ion.isScrolling) { + e.preventDefault(); + return false; + } + + // data-history-go="-1" + // shortcut if they just want to use window.history.go() + if(el.dataset.historyGo) { + window.history.go( parseInt(el.dataset.historyGo, 10) ); + e.preventDefault(); + return false; + } + + // only intercept the nav click if they're going to the same domain or page + if (location.protocol === el.protocol && location.host === el.host) { + + // trigger the event that a new page should be shown + ion.trigger("pageinit", el.href); + + // decide how to handle this click depending on the href + if(el.getAttribute("href").indexOf("#") === 0) { + // this click is going to another element within this same page + + + } else { + // this click is going to another page in the same domain + requestData({ + url: el.href, + success: successPageLoad, + fail: failedPageLoad + }); + } + + // stop the browser itself from continuing on with this click + // the above code will take care of the navigation + e.preventDefault(); + return false; + } + } + + function locationChange(e) { + if(!this._initPopstate) { + this._initPopstate = true; + return; + } + + requestData({ + url: location.href, + success: successPageLoad, + fail: failedPageLoad + }); + } + + function requestData(options) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', options.url, true); + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if(xhr.status === 200) { + options.success(xhr, options); + } else { + options.fail(xhr, options); + } + } + }; + xhr.send(); + } + + function successPageLoad(xhr, options) { + var data = parseXHR(xhr, options); + ion.trigger("pageloaded", { + data: data + }); + history.pushState({}, data.title, data.url); + document.title = data.title; + } + + function failedPageLoad(xhr, options) { + ion.trigger("pageinitfailed", { + responseText: xhr.responseText, + responseStatus: xhr.status + }); + } + + function parseXHR(xhr, options) { + var + container, + tmp, + data = {}; + + data.url = options.url; + + if (!xhr.responseText) return data; + + container = document.createElement('div'); + container.innerHTML = xhr.responseText; + + // get the title of the page + tmp = container.querySelector("title"); + if(tmp) { + data.title = tmp.innerText; + } else { + data.title = data.url; + } + + // get the main content of the page + tmp = container.querySelector("main"); + if(tmp) { + data.main = tmp.innerHTML; + } else { + // something is wrong with the data, trigger that the page init failed + ion.trigger("pageinitfailed"); + } + + return data; + } + + // listen to every click + document.addEventListener("click", click, false); + + // listen to when the location changes + ion.on("popstate", locationChange); + + +})(this, document, location, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-panel.js b/example/cordova/iOS/www/js/ionic-panel.js new file mode 100644 index 0000000000..470254751a --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-panel.js @@ -0,0 +1,72 @@ +(function(window, document, ion) { + + var + x, + isPanelOpen, + + PANEL_ACTIVE = "ion-panel-active", + PANEL_ACTIVE_LEFT = "ion-panel-active-left", + PANEL_ACTIVE_RIGHT = "ion-panel-active-right", + + PANEL_OPEN_LEFT = "ion-panel-left", + PANEL_OPEN_RIGHT = "ion-panel-right"; + + ion.Panel = { + + toggle: function(panelId, options) { + if(isPanelOpen) { + this.close(); + } else { + this.open(panelId, options); + } + }, + + open: function(panelId, options) { + // see if there is an element with this id + var panel = document.getElementById(panelId); + if(panel) { + // this element is a panel, open it! + + // remember that a panel is currently open + isPanelOpen = true; + + // find all the panels that are or were once active + var panelsActive = document.getElementsByClassName(PANEL_ACTIVE); + + // remove the panel-active css classes from each of the previously active panels + for(x=0; x that there is a panel open + if(options && options.direction === "right") { + panel.classList.add(PANEL_ACTIVE_RIGHT); + document.body.classList.add(PANEL_OPEN_RIGHT); + } else { + // left is the default + panel.classList.add(PANEL_ACTIVE_LEFT); + document.body.classList.add(PANEL_OPEN_LEFT); + } + } + }, + + close: function() { + if(isPanelOpen) { + // there is a panel already open, so close it + isPanelOpen = false; + + // remove from so that no panels should be open + var className = document.body.className; + className = className.replace(PANEL_OPEN_LEFT, "").replace(PANEL_OPEN_RIGHT, "").trim(); + document.body.className = className; + } + } + + }; + + window.addEventListener("popstate", ion.Panel.close, false); + +})(this, document, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-tabs.js b/example/cordova/iOS/www/js/ionic-tabs.js new file mode 100644 index 0000000000..4f427fd974 --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-tabs.js @@ -0,0 +1,25 @@ +'use strict'; + +(function(window, document, ion) { + ion.Tabs = function() {} + + ion.Tabs.prototype._TAB_ITEM_CLASS = 'tab-item'; + + ion.Tabs.prototype._onTouchStart = function(event) { + console.log('Touch start!', event); + if(event.target && event.target.parentNode.classList.contains(this._TAB_ITEM_CLASS)) { + event.target.classList.add('active'); + } + }; + ion.Tabs.prototype._onTouchEnd = function(event) { + console.log('Touch end!', event); + if(event.target && event.target.parentNode.classList.contains(this._TAB_ITEM_CLASS)) { + event.target.classList.remove('active'); + } + }; + + document.addEventListener('mousedown', ion.Tabs.prototype._onTouchStart); + document.addEventListener('touchstart', ion.Tabs.prototype._onTouchStart); + document.addEventListener('touchend', ion.Tabs.prototype._onTouchEnd); + +})(this, document, ion = this.ion || {}); diff --git a/example/cordova/iOS/www/js/ionic-template.js b/example/cordova/iOS/www/js/ionic-template.js new file mode 100644 index 0000000000..e43bdb55ad --- /dev/null +++ b/example/cordova/iOS/www/js/ionic-template.js @@ -0,0 +1,68 @@ +'use strict'; + +(function(window, document, ion) { + + // Loop through each element in the DOM and collect up all + // the templates it has. A template either has data to supply + // to others, or it needs data from another template + function initTemplates() { + var + x, + el, + tmp, + emptyTemplates = [], + container, + templateElements; + + // collect up all the templates currently in the DOM + templateElements = document.body.querySelectorAll("[data-template]"); + for(x=0; x - + @@ -20,8 +20,7 @@
-

-

+

Cheese!

@@ -31,23 +30,221 @@
-
- This is my default left side panel! +
+
-
- This is my right side panel! -
- -
- This is my other left side panel! +
+
- - - - + +