From 40edbc3b642180f12d766f16a1f005b8ded665b3 Mon Sep 17 00:00:00 2001 From: KimLS Date: Sun, 22 Jan 2017 22:26:34 -0800 Subject: [PATCH] WI work --- common/eq_packet.cpp | 2 +- common/eq_packet.h | 3 + common/net/eqstream.cpp | 1 + wi/http/data/endpoint.js | 24 + wi/http/data/index.js | 9 + wi/http/data/sql.js | 340 + wi/http/index.js | 9 + wi/index.js | 12 +- wi/network/servertalk_api.js | 6 +- wi/package.json | 2 + wi/wi-front/.gitignore | 47 + wi/wi-front/config.json | 3 + wi/wi-front/dist/css/app.css | 13 + wi/wi-front/dist/css/vendor.css | 9 + .../fonts/materialdesignicons-webfont.eot | Bin 0 -> 261608 bytes .../fonts/materialdesignicons-webfont.svg | 5520 ++ .../fonts/materialdesignicons-webfont.ttf | Bin 0 -> 261388 bytes .../fonts/materialdesignicons-webfont.woff | Bin 0 -> 129588 bytes .../fonts/materialdesignicons-webfont.woff2 | Bin 0 -> 99736 bytes wi/wi-front/dist/index.html | 19 + wi/wi-front/dist/js/bundle.js | 76151 ++++++++++++++++ wi/wi-front/dist/js/bundle.js.map | 1 + wi/wi-front/dist/js/templates.js | 4 + wi/wi-front/gulpfile.js | 209 + wi/wi-front/package.json | 42 + wi/wi-front/src/app/app.html | 1 + wi/wi-front/src/app/app.js | 15 + wi/wi-front/src/app/app.scss | 0 wi/wi-front/src/app/components/index.js | 6 + .../src/app/components/login/login.html | 24 + wi/wi-front/src/app/components/login/login.js | 52 + .../src/app/components/login/login.scss | 16 + .../src/app/components/portal/portal.html | 26 + .../src/app/components/portal/portal.js | 39 + .../src/app/components/portal/portal.scss | 0 .../src/app/interceptors/apply-auth.js | 24 + .../src/app/interceptors/check-auth.js | 25 + wi/wi-front/src/app/interceptors/index.js | 6 + wi/wi-front/src/app/routes/core.js | 21 + wi/wi-front/src/app/routes/index.js | 5 + wi/wi-front/src/app/services/config.js | 10 + wi/wi-front/src/app/services/index.js | 6 + wi/wi-front/src/app/services/login.js | 78 + wi/wi-front/src/index.html | 19 + wi/wi-front/src/index.js | 35 + wi/wi-front/src/style.scss | 0 wi/ws/index.js | 7 + zone/client_packet.cpp | 2 +- 48 files changed, 82834 insertions(+), 9 deletions(-) create mode 100644 wi/http/data/endpoint.js create mode 100644 wi/http/data/index.js create mode 100644 wi/http/data/sql.js create mode 100644 wi/http/index.js create mode 100644 wi/wi-front/.gitignore create mode 100644 wi/wi-front/config.json create mode 100644 wi/wi-front/dist/css/app.css create mode 100644 wi/wi-front/dist/css/vendor.css create mode 100644 wi/wi-front/dist/fonts/materialdesignicons-webfont.eot create mode 100644 wi/wi-front/dist/fonts/materialdesignicons-webfont.svg create mode 100644 wi/wi-front/dist/fonts/materialdesignicons-webfont.ttf create mode 100644 wi/wi-front/dist/fonts/materialdesignicons-webfont.woff create mode 100644 wi/wi-front/dist/fonts/materialdesignicons-webfont.woff2 create mode 100644 wi/wi-front/dist/index.html create mode 100644 wi/wi-front/dist/js/bundle.js create mode 100644 wi/wi-front/dist/js/bundle.js.map create mode 100644 wi/wi-front/dist/js/templates.js create mode 100644 wi/wi-front/gulpfile.js create mode 100644 wi/wi-front/package.json create mode 100644 wi/wi-front/src/app/app.html create mode 100644 wi/wi-front/src/app/app.js create mode 100644 wi/wi-front/src/app/app.scss create mode 100644 wi/wi-front/src/app/components/index.js create mode 100644 wi/wi-front/src/app/components/login/login.html create mode 100644 wi/wi-front/src/app/components/login/login.js create mode 100644 wi/wi-front/src/app/components/login/login.scss create mode 100644 wi/wi-front/src/app/components/portal/portal.html create mode 100644 wi/wi-front/src/app/components/portal/portal.js create mode 100644 wi/wi-front/src/app/components/portal/portal.scss create mode 100644 wi/wi-front/src/app/interceptors/apply-auth.js create mode 100644 wi/wi-front/src/app/interceptors/check-auth.js create mode 100644 wi/wi-front/src/app/interceptors/index.js create mode 100644 wi/wi-front/src/app/routes/core.js create mode 100644 wi/wi-front/src/app/routes/index.js create mode 100644 wi/wi-front/src/app/services/config.js create mode 100644 wi/wi-front/src/app/services/index.js create mode 100644 wi/wi-front/src/app/services/login.js create mode 100644 wi/wi-front/src/index.html create mode 100644 wi/wi-front/src/index.js create mode 100644 wi/wi-front/src/style.scss create mode 100644 wi/ws/index.js diff --git a/common/eq_packet.cpp b/common/eq_packet.cpp index d7e607d77..f5c08a301 100644 --- a/common/eq_packet.cpp +++ b/common/eq_packet.cpp @@ -132,7 +132,7 @@ void EQApplicationPacket::build_header_dump(char *buffer) const #ifdef STATIC_OPCODE sprintf(buffer, "[OpCode 0x%04x Size=%u]\n", emu_opcode,size); #else - sprintf(buffer, "[OpCode %s Size=%u]",OpcodeManager::EmuToName(emu_opcode),size); + sprintf(buffer, "[OpCode %s(0x%04x) Size=%u]",OpcodeManager::EmuToName(emu_opcode), GetProtocolOpcode(), size); #endif } diff --git a/common/eq_packet.h b/common/eq_packet.h index 4f49d16f5..1fea3b9d1 100644 --- a/common/eq_packet.h +++ b/common/eq_packet.h @@ -118,8 +118,11 @@ public: uint16 GetOpcodeBypass() const { return opcode_bypass; } void SetOpcodeBypass(uint16 v) { opcode_bypass = v; } + uint16 GetProtocolOpcode() const { return protocol_opcode; } + void SetProtocolOpcode(uint16 v) { protocol_opcode = v; } protected: + uint16 protocol_opcode; uint8 app_opcode_size; uint16 opcode_bypass; private: diff --git a/common/net/eqstream.cpp b/common/net/eqstream.cpp index 3fda00bb7..b323e6323 100644 --- a/common/net/eqstream.cpp +++ b/common/net/eqstream.cpp @@ -120,6 +120,7 @@ EQApplicationPacket *EQ::Net::EQStream::PopPacket() { EmuOpcode emu_op = (*m_opcode_manager)->EQToEmu(opcode); EQApplicationPacket *ret = new EQApplicationPacket(emu_op, (unsigned char*)p->Data() + m_owner->m_options.opcode_size, p->Length() - m_owner->m_options.opcode_size); + ret->SetProtocolOpcode(opcode); m_packet_queue.pop_front(); return ret; } diff --git a/wi/http/data/endpoint.js b/wi/http/data/endpoint.js new file mode 100644 index 000000000..c0ec15658 --- /dev/null +++ b/wi/http/data/endpoint.js @@ -0,0 +1,24 @@ +var auth = require('../../core/jwt_auth.js').auth; +var sql = require('./sql.js'); + +var RegisterEndpoint = function(app, api, single_name, plural_name, pkey) { + app.get('/api/data/' + single_name + '/:' + pkey, auth, function (req, res) { + sql.Retrieve(req, res, plural_name, pkey); + }); + + app.put('/api/data/' + single_name + '/:' + pkey, auth, function (req, res) { + sql.CreateUpdate(req, res, plural_name, pkey); + }); + + app.delete('/api/data/' + single_name + '/:' + pkey, auth, function (req, res) { + sql.Delete(req, res, plural_name, pkey); + }); + + app.post('/api/data/' + single_name + '/search', auth, function (req, res) { + sql.Search(req, res, plural_name, pkey, res); + }); +}; + +module.exports = { + 'Register': RegisterEndpoint +} diff --git a/wi/http/data/index.js b/wi/http/data/index.js new file mode 100644 index 000000000..44c1c6fe6 --- /dev/null +++ b/wi/http/data/index.js @@ -0,0 +1,9 @@ +var endpoint = require('./endpoint.js'); + +var RegisterAPI = function(app, api) { + endpoint.Register(app, api, 'item', 'items', 'id'); +}; + +module.exports = { + 'Register': RegisterAPI +} \ No newline at end of file diff --git a/wi/http/data/sql.js b/wi/http/data/sql.js new file mode 100644 index 000000000..e1e8f2973 --- /dev/null +++ b/wi/http/data/sql.js @@ -0,0 +1,340 @@ +var moment = require('moment'); + +function CreateReplace(table, body, fields) { + try { + var query = 'REPLACE INTO ' + table + ' VALUES('; + var first = true; + var args = []; + + for(var idx in fields) { + if(first) { + first = false; + } else { + query += ','; + } + + query += '?'; + + var entry = fields[idx]; + if(entry.type === 12) { + try { + var d = new moment(body[entry.name]); + + if(d.isValid()) { + args.push(d.format('YYYY-MM-DD HH:mm:ss')); + } else { + args.push(null); + } + } catch(ex) { + args.push(null); + } + } else { + args.push(body[entry.name]); + } + } + + query += ')'; + + return { 'query': query, 'args': args }; + } catch(ex) { + return { 'query': '', 'args': [] }; + } +} + +function CreateUpdate(req, res, table, pkey) { + req.mysql.getConnection(function(err, connection) { + try { + if(err) { + console.log(err); + connection.release(); + res.sendStatus(500); + return; + } + + if(req.body[pkey] !== parseInt(req.params[pkey], 10)) { + connection.release(); + res.sendStatus(400); + return; + } + + connection.query('SELECT * FROM ' + table + ' WHERE ' + pkey + '=? LIMIT 1', [req.params[pkey]], function (error, results, fields) { + try { + if(error) { + console.log(error); + connection.release(); + res.sendStatus(400); + return; + } + + var replace = CreateReplace(table, req.body, fields); + if(replace.query === '') { + connection.release(); + res.sendStatus(400); + return; + } + + connection.query(replace.query, replace.args, function(error, results, fields) { + try { + if(error) { + console.log(error); + connection.release(); + res.sendStatus(400); + return; + } + + connection.release(); + res.sendStatus(200); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); +} + +function Retrieve(req, res, table, pkey) { + req.mysql.getConnection(function(err, connection) { + try { + if(err) { + console.log(err); + connection.release(); + res.sendStatus(500); + return; + } + + connection.query('SELECT * FROM ' + table + ' WHERE ' + pkey + '=? LIMIT 1', [req.params[pkey]], function (error, results, fields) { + try { + if(results.length == 0) { + connection.release(); + res.sendStatus(404); + return; + } + + var result = results[0]; + var ret = { }; + + for(var idx in result) { + var value = result[idx]; + ret[idx] = value; + } + + connection.release(); + res.json(ret); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); +} + +function Delete(req, res, table, pkey) { + req.mysql.getConnection(function(err, connection) { + try { + if(err) { + console.log(err); + connection.release(); + res.sendStatus(500); + return; + } + + connection.query('DELETE FROM ' + table + ' WHERE ' + pkey + '=? LIMIT 1', [req.params[pkey]], function (error, results, fields) { + try { + if(error) { + console.log(error); + connection.release(); + res.sendStatus(400); + return; + } + + connection.release(); + res.sendStatus(200); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); +} + +function Search(req, res, table, pkey) { + //Verify incoming model + if(!req.body.hasOwnProperty('draw')) { + res.sendStatus(400); + return; + } + + if(!req.body.hasOwnProperty('start')) { + res.sendStatus(400); + return; + } + + if(!req.body.hasOwnProperty('length')) { + res.sendStatus(400); + return; + } + + if(!req.body.hasOwnProperty('search')) { + res.sendStatus(400); + return; + } + + if(!req.body.hasOwnProperty('columns')) { + res.sendStatus(400); + return; + } + + if(!req.body.hasOwnProperty('order')) { + res.sendStatus(400); + return; + } + + req.mysql.getConnection(function(err, connection) { + try { + if(err) { + console.log(err); + connection.release(); + res.sendStatus(500); + return; + } + + var ret = { }; + ret.draw = req.body['draw']; + ret.data = []; + + var query = 'SELECT '; + var idx; + var args = []; + var first = true; + for(idx in req.body['columns']) { + var column = req.body['columns'][idx]; + if(first) { + first = false; + } else { + query += ', '; + } + + query += connection.escapeId(column.data); + } + + query += ' FROM ' + table; + + first = true; + for(idx in req.body['order']) { + var order = req.body['order'][idx]; + if(first) { + query += ' ORDER BY '; + first = false; + } else { + query += ', '; + } + + var column = req.body['columns'][order.column]; + + query += connection.escapeId(column.data); + + if(order.dir === 'asc') { + query += ' ASC'; + } else { + query += ' DESC'; + } + } + + connection.query(query, args, function (error, results, fields) { + try { + if(error) { + console.log(error); + connection.release(); + res.sendStatus(400); + return; + } + + ret.recordsTotal = results.length; + + for(var result_idx in results) { + var result = results[result_idx]; + if(req.body['search'].value && req.body['search'].value.length > 0) { + var found = false; + + for(idx in req.body['columns']) { + var column = req.body['columns'][idx]; + + if(column.searchable) { + if(String(result[column.data]).toLowerCase().includes(String(req.body['search'].value).toLowerCase())) { + found = true; + break; + } + } + } + + if(found) { + var obj = { }; + + for(var i in result) { + var value = result[i]; + obj[i] = value; + } + + ret.data.push(obj); + } + } else { + var obj = { }; + + for(var i in result) { + var value = result[i]; + obj[i] = value; + } + + ret.data.push(obj); + } + } + + ret.recordsFiltered = ret.data.length; + ret.data = ret.data.slice(req.body['start'], req.body['start'] + req.body['length']); + + connection.release(); + res.json(ret); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); + } catch(ex) { + console.log(ex); + connection.release(); + res.sendStatus(500); + } + }); +} + +module.exports = { + 'CreateUpdate': CreateUpdate, + 'Retrieve': Retrieve, + 'Delete': Delete, + 'Search': Search, +} \ No newline at end of file diff --git a/wi/http/index.js b/wi/http/index.js new file mode 100644 index 000000000..46e2f6d71 --- /dev/null +++ b/wi/http/index.js @@ -0,0 +1,9 @@ +var RegisterAPI = function(app, api) { + require('./eqw.js').Register(app, api); + require('./token.js').Register(app); + require('./data').Register(app, api); +}; + +module.exports = { + 'Register': RegisterAPI +} \ No newline at end of file diff --git a/wi/index.js b/wi/index.js index ffeaa9c5a..c44c0c7d7 100644 --- a/wi/index.js +++ b/wi/index.js @@ -30,6 +30,12 @@ api.Init(settings.servertalk.addr, settings.servertalk.port, false, settings.ser app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); +app.use(function(req, res, next) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); +}); + //make sure all routes can see our injected dependencies app.use(function (req, res, next) { req.servertalk = api; @@ -42,10 +48,8 @@ app.get('/', function (req, res) { res.send({ status: "online" }); }); -require('./http/token.js').Register(app); -require('./http/eqw.js').Register(app, api); -//require('./ws/token.js').Register(app); -require('./ws/eqw.js').Register(wsi, api); +require('./http').Register(app, api); +require('./ws').Register(wsi, api); server.on('request', app); server.listen(settings.port, function () { console.log('Listening on ' + server.address().port) }); diff --git a/wi/network/servertalk_api.js b/wi/network/servertalk_api.js index cf677d6f2..154c12b1e 100644 --- a/wi/network/servertalk_api.js +++ b/wi/network/servertalk_api.js @@ -11,15 +11,15 @@ class ServertalkAPI var self = this; this.client.on('connecting', function() { - console.log('Connecting...'); + //console.log('Connecting...'); }); this.client.on('connect', function(){ - console.log('Connected'); + //console.log('Connected'); }); this.client.on('close', function(){ - console.log('Closed'); + //console.log('Closed'); }); this.client.on('error', function(err){ diff --git a/wi/package.json b/wi/package.json index 674bd1954..43b9e9f98 100644 --- a/wi/package.json +++ b/wi/package.json @@ -11,10 +11,12 @@ "dependencies": { "body-parser": "^1.15.2", "express": "^4.14.0", + "hammerjs": "^2.0.8", "jsonwebtoken": "^7.2.1", "libsodium": "^0.4.8", "libsodium-wrappers": "^0.4.8", "libsodium-wrappers-sumo": "^0.4.8", + "moment": "^2.17.1", "mysql": "^2.12.0", "node-uuid": "^1.4.7", "ws": "^1.1.1" diff --git a/wi/wi-front/.gitignore b/wi/wi-front/.gitignore new file mode 100644 index 000000000..b449f1759 --- /dev/null +++ b/wi/wi-front/.gitignore @@ -0,0 +1,47 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity diff --git a/wi/wi-front/config.json b/wi/wi-front/config.json new file mode 100644 index 000000000..64232f6dd --- /dev/null +++ b/wi/wi-front/config.json @@ -0,0 +1,3 @@ +{ + "api_url": "http://localhost:9080" +} \ No newline at end of file diff --git a/wi/wi-front/dist/css/app.css b/wi/wi-front/dist/css/app.css new file mode 100644 index 000000000..55f1f92e6 --- /dev/null +++ b/wi/wi-front/dist/css/app.css @@ -0,0 +1,13 @@ + + +.login-panel { + width: 400px; + padding: 28px; } + +.login-title { + margin: 3em 0 3em 0; } + +.login-error-message { + color: #ff2828; + padding: 8px; } + diff --git a/wi/wi-front/dist/css/vendor.css b/wi/wi-front/dist/css/vendor.css new file mode 100644 index 000000000..fb0f48598 --- /dev/null +++ b/wi/wi-front/dist/css/vendor.css @@ -0,0 +1,9 @@ +/*! + * Angular Material Design + * https://github.com/angular/material + * @license MIT + * v1.1.1 + */body,html{height:100%;position:relative}body{margin:0;padding:0}[tabindex='-1']:focus{outline:none}.inset{padding:10px}a.md-no-style,button.md-no-style{font-weight:400;background-color:inherit;text-align:left;border:none;padding:0;margin:0}button,input,select,textarea{vertical-align:baseline}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default}textarea{vertical-align:top;overflow:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box;-webkit-box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input:-webkit-autofill{text-shadow:none}.md-visually-hidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;text-transform:none;width:1px}.md-shadow{position:absolute;top:0;left:0;bottom:0;right:0;border-radius:inherit;pointer-events:none}.md-shadow-bottom-z-1{box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.md-shadow-bottom-z-2{box-shadow:0 4px 8px 0 rgba(0,0,0,.4)}.md-shadow-animated.md-shadow{-webkit-transition:box-shadow .28s cubic-bezier(.4,0,.2,1);transition:box-shadow .28s cubic-bezier(.4,0,.2,1)}.md-ripple-container{pointer-events:none;position:absolute;overflow:hidden;left:0;top:0;width:100%;height:100%;-webkit-transition:all .55s cubic-bezier(.25,.8,.25,1);transition:all .55s cubic-bezier(.25,.8,.25,1)}.md-ripple{position:absolute;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transform-origin:50% 50%;transform-origin:50% 50%;opacity:0;border-radius:50%}.md-ripple.md-ripple-placed{-webkit-transition:margin .9s cubic-bezier(.25,.8,.25,1),border .9s cubic-bezier(.25,.8,.25,1),width .9s cubic-bezier(.25,.8,.25,1),height .9s cubic-bezier(.25,.8,.25,1),opacity .9s cubic-bezier(.25,.8,.25,1),-webkit-transform .9s cubic-bezier(.25,.8,.25,1);transition:margin .9s cubic-bezier(.25,.8,.25,1),border .9s cubic-bezier(.25,.8,.25,1),width .9s cubic-bezier(.25,.8,.25,1),height .9s cubic-bezier(.25,.8,.25,1),opacity .9s cubic-bezier(.25,.8,.25,1),-webkit-transform .9s cubic-bezier(.25,.8,.25,1);transition:margin .9s cubic-bezier(.25,.8,.25,1),border .9s cubic-bezier(.25,.8,.25,1),width .9s cubic-bezier(.25,.8,.25,1),height .9s cubic-bezier(.25,.8,.25,1),opacity .9s cubic-bezier(.25,.8,.25,1),transform .9s cubic-bezier(.25,.8,.25,1);transition:margin .9s cubic-bezier(.25,.8,.25,1),border .9s cubic-bezier(.25,.8,.25,1),width .9s cubic-bezier(.25,.8,.25,1),height .9s cubic-bezier(.25,.8,.25,1),opacity .9s cubic-bezier(.25,.8,.25,1),transform .9s cubic-bezier(.25,.8,.25,1),-webkit-transform .9s cubic-bezier(.25,.8,.25,1)}.md-ripple.md-ripple-scaled{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.md-ripple.md-ripple-active,.md-ripple.md-ripple-full,.md-ripple.md-ripple-visible{opacity:.2}.md-ripple.md-ripple-remove{-webkit-animation:md-remove-ripple .9s cubic-bezier(.25,.8,.25,1);animation:md-remove-ripple .9s cubic-bezier(.25,.8,.25,1)}@-webkit-keyframes md-remove-ripple{0%{opacity:.15}to{opacity:0}}@keyframes md-remove-ripple{0%{opacity:.15}to{opacity:0}}.md-padding{padding:8px}.md-margin{margin:8px}.md-scroll-mask{position:absolute;background-color:transparent;top:0;right:0;bottom:0;left:0;z-index:50}.md-scroll-mask>.md-scroll-mask-bar{display:block;position:absolute;background-color:#fafafa;right:0;top:0;bottom:0;z-index:65;box-shadow:inset 0 0 1px rgba(0,0,0,.3)}.md-no-momentum{-webkit-overflow-scrolling:auto}.md-no-flicker{-webkit-filter:blur(0)}@media (min-width:960px){.md-padding{padding:16px}}body[dir=ltr],body[dir=rtl],html[dir=ltr],html[dir=rtl]{unicode-bidi:embed}bdo[dir=rtl]{direction:rtl}bdo[dir=ltr],bdo[dir=rtl]{unicode-bidi:bidi-override}bdo[dir=ltr]{direction:ltr}body,html{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;min-height:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.md-display-4{font-size:112px;font-weight:300;letter-spacing:-.01em;line-height:112px}.md-display-3{font-size:56px;font-weight:400;letter-spacing:-.005em;line-height:56px}.md-display-2{font-size:45px;font-weight:400;line-height:64px}.md-display-1{font-size:34px;font-weight:400;line-height:40px}.md-headline{font-size:24px;font-weight:400;line-height:32px}.md-title{font-size:20px;font-weight:500;letter-spacing:.005em}.md-subhead{font-size:16px;line-height:24px}.md-body-1,.md-subhead{font-weight:400;letter-spacing:.01em}.md-body-1{font-size:14px;line-height:20px}.md-body-2{font-size:14px;font-weight:500;letter-spacing:.01em;line-height:24px}.md-caption{font-size:12px;letter-spacing:.02em}.md-button{letter-spacing:.01em}button,html,input,select,textarea{font-family:Roboto,Helvetica Neue,sans-serif}button,input,select,textarea{font-size:100%}@-webkit-keyframes md-autocomplete-list-out{0%{-webkit-animation-timing-function:linear;animation-timing-function:linear}50%{opacity:0;height:40px;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}to{height:0;opacity:0}}@keyframes md-autocomplete-list-out{0%{-webkit-animation-timing-function:linear;animation-timing-function:linear}50%{opacity:0;height:40px;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}to{height:0;opacity:0}}@-webkit-keyframes md-autocomplete-list-in{0%{opacity:0;height:0;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{opacity:0;height:40px}to{opacity:1;height:40px}}@keyframes md-autocomplete-list-in{0%{opacity:0;height:0;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{opacity:0;height:40px}to{opacity:1;height:40px}}md-autocomplete{border-radius:2px;display:block;height:40px;position:relative;overflow:visible;min-width:190px}md-autocomplete[disabled] input{cursor:default}md-autocomplete[md-floating-label]{border-radius:0;background:transparent;height:auto}md-autocomplete[md-floating-label] md-input-container{padding-bottom:0}md-autocomplete[md-floating-label] md-autocomplete-wrap{height:auto}md-autocomplete[md-floating-label] button{position:absolute;top:auto;bottom:0;right:0;width:30px;height:30px}md-autocomplete md-autocomplete-wrap{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row;box-sizing:border-box;position:relative;overflow:visible;height:40px}md-autocomplete md-autocomplete-wrap.md-menu-showing{z-index:51}md-autocomplete md-autocomplete-wrap input,md-autocomplete md-autocomplete-wrap md-input-container{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box;min-width:0}md-autocomplete md-autocomplete-wrap md-progress-linear{position:absolute;bottom:-2px;left:0}md-autocomplete md-autocomplete-wrap md-progress-linear.md-inline{bottom:40px;right:2px;left:2px;width:auto}md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate{position:absolute;top:0;left:0;width:100%;height:3px;-webkit-transition:none;transition:none}md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate .md-container{-webkit-transition:none;transition:none;height:3px}md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-enter{-webkit-transition:opacity .15s linear;transition:opacity .15s linear}md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-enter.ng-enter-active{opacity:1}md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-leave{-webkit-transition:opacity .15s linear;transition:opacity .15s linear}md-autocomplete md-autocomplete-wrap md-progress-linear .md-mode-indeterminate.ng-leave.ng-leave-active{opacity:0}md-autocomplete input:not(.md-input){font-size:14px;box-sizing:border-box;border:none;box-shadow:none;outline:none;background:transparent;width:100%;padding:0 15px;line-height:40px;height:40px}md-autocomplete input:not(.md-input)::-ms-clear{display:none}md-autocomplete button{position:relative;line-height:20px;text-align:center;width:30px;height:30px;cursor:pointer;border:none;border-radius:50%;padding:0;font-size:12px;background:transparent;margin:auto 5px}md-autocomplete button:after{content:'';position:absolute;top:-6px;right:-6px;bottom:-6px;left:-6px;border-radius:50%;-webkit-transform:scale(0);transform:scale(0);opacity:0;-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1)}md-autocomplete button:focus{outline:none}md-autocomplete button:focus:after{-webkit-transform:scale(1);transform:scale(1);opacity:1}md-autocomplete button md-icon{position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0) scale(.9);transform:translate3d(-50%,-50%,0) scale(.9)}md-autocomplete button md-icon path{stroke-width:0}md-autocomplete button.ng-enter{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .15s ease-out;transition:-webkit-transform .15s ease-out;transition:transform .15s ease-out;transition:transform .15s ease-out,-webkit-transform .15s ease-out}md-autocomplete button.ng-enter.ng-enter-active{-webkit-transform:scale(1);transform:scale(1)}md-autocomplete button.ng-leave{-webkit-transition:-webkit-transform .15s ease-out;transition:-webkit-transform .15s ease-out;transition:transform .15s ease-out;transition:transform .15s ease-out,-webkit-transform .15s ease-out}md-autocomplete button.ng-leave.ng-leave-active{-webkit-transform:scale(0);transform:scale(0)}@media screen and (-ms-high-contrast:active){md-autocomplete input{border:1px solid #fff}md-autocomplete li:focus{color:#fff}}.md-virtual-repeat-container.md-autocomplete-suggestions-container{position:absolute;box-shadow:0 2px 5px rgba(0,0,0,.25);height:225.5px;max-height:225.5px;z-index:100}.md-virtual-repeat-container.md-not-found{height:48px}.md-autocomplete-suggestions{margin:0;list-style:none;padding:0}.md-autocomplete-suggestions li{font-size:14px;overflow:hidden;padding:0 15px;line-height:48px;height:48px;-webkit-transition:background .15s linear;transition:background .15s linear;margin:0;white-space:nowrap;text-overflow:ellipsis}.md-autocomplete-suggestions li:focus{outline:none}.md-autocomplete-suggestions li:not(.md-not-found-wrapper){cursor:pointer}@media screen and (-ms-high-contrast:active){.md-autocomplete-suggestions,md-autocomplete{border:1px solid #fff}}md-backdrop{-webkit-transition:opacity .45s;transition:opacity .45s;position:absolute;top:0;bottom:0;left:0;right:0;z-index:50}md-backdrop.md-menu-backdrop{position:fixed!important;z-index:99}md-backdrop.md-select-backdrop{z-index:81;-webkit-transition-duration:0;transition-duration:0}md-backdrop.md-dialog-backdrop{z-index:79}md-backdrop.md-bottom-sheet-backdrop{z-index:69}md-backdrop.md-sidenav-backdrop{z-index:59}md-backdrop.md-click-catcher{position:absolute}md-backdrop.md-opaque{opacity:.48}md-backdrop.md-opaque.ng-enter{opacity:0}md-backdrop.md-opaque.ng-enter.md-opaque.ng-enter-active{opacity:.48}md-backdrop.md-opaque.ng-leave{opacity:.48;-webkit-transition:opacity .4s;transition:opacity .4s}md-backdrop.md-opaque.ng-leave.md-opaque.ng-leave-active{opacity:0}md-bottom-sheet{position:absolute;left:0;right:0;bottom:0;padding:8px 16px 88px;z-index:70;border-top-width:1px;border-top-style:solid;-webkit-transform:translate3d(0,80px,0);transform:translate3d(0,80px,0);-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-property:-webkit-transform;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform}md-bottom-sheet.md-has-header{padding-top:0}md-bottom-sheet.ng-enter{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}md-bottom-sheet.ng-enter-active{opacity:1;display:block;-webkit-transform:translate3d(0,80px,0)!important;transform:translate3d(0,80px,0)!important}md-bottom-sheet.ng-leave-active{-webkit-transform:translate3d(0,100%,0)!important;transform:translate3d(0,100%,0)!important;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-bottom-sheet .md-subheader{background-color:transparent;font-family:Roboto,Helvetica Neue,sans-serif;line-height:56px;padding:0;white-space:nowrap}md-bottom-sheet md-inline-icon{display:inline-block;height:24px;width:24px;fill:#444}md-bottom-sheet md-list-item{display:-webkit-box;display:-webkit-flex;display:flex;outline:none}md-bottom-sheet md-list-item:hover{cursor:pointer}md-bottom-sheet.md-list md-list-item{padding:0;-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;height:48px}md-bottom-sheet.md-grid{padding-left:24px;padding-right:24px;padding-top:0}md-bottom-sheet.md-grid md-list{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap}md-bottom-sheet.md-grid md-list,md-bottom-sheet.md-grid md-list-item{-webkit-box-direction:normal;-webkit-transition:all .5s;transition:all .5s;-webkit-box-align:center;-webkit-align-items:center;align-items:center}md-bottom-sheet.md-grid md-list-item{-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column;-ms-grid-row-align:center;height:96px;margin-top:8px;margin-bottom:8px}@media (max-width:960px){md-bottom-sheet.md-grid md-list-item{-webkit-box-flex:1;-webkit-flex:1 1 33.33333%;flex:1 1 33.33333%;max-width:33.33333%}md-bottom-sheet.md-grid md-list-item:nth-of-type(3n+1){-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start}md-bottom-sheet.md-grid md-list-item:nth-of-type(3n){-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end}}@media (min-width:960px) and (max-width:1279px){md-bottom-sheet.md-grid md-list-item{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;max-width:25%}}@media (min-width:1280px) and (max-width:1919px){md-bottom-sheet.md-grid md-list-item{-webkit-box-flex:1;-webkit-flex:1 1 16.66667%;flex:1 1 16.66667%;max-width:16.66667%}}@media (min-width:1920px){md-bottom-sheet.md-grid md-list-item{-webkit-box-flex:1;-webkit-flex:1 1 14.28571%;flex:1 1 14.28571%;max-width:14.28571%}}md-bottom-sheet.md-grid md-list-item:before{display:none}md-bottom-sheet.md-grid md-list-item .md-list-item-content{width:48px;padding-bottom:16px}md-bottom-sheet.md-grid md-list-item .md-grid-item-content,md-bottom-sheet.md-grid md-list-item .md-list-item-content{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;-webkit-box-align:center;-webkit-align-items:center;align-items:center}md-bottom-sheet.md-grid md-list-item .md-grid-item-content{border:1px solid transparent;width:80px}md-bottom-sheet.md-grid md-list-item .md-grid-text{font-weight:400;line-height:16px;font-size:13px;margin:0;white-space:nowrap;width:64px;text-align:center;text-transform:none;padding-top:8px}@media screen and (-ms-high-contrast:active){md-bottom-sheet{border:1px solid #fff}}button.md-button::-moz-focus-inner{border:0}.md-button{display:inline-block;position:relative;cursor:pointer;min-height:36px;min-width:88px;line-height:36px;vertical-align:middle;-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;text-align:center;border-radius:3px;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:0;padding:0 6px;margin:6px 8px;background:transparent;color:currentColor;white-space:nowrap;text-transform:uppercase;font-weight:500;font-size:14px;font-style:inherit;font-variant:inherit;font-family:inherit;text-decoration:none;overflow:hidden;-webkit-transition:box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1);transition:box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1)}.md-button,.md-button:focus{outline:none}.md-button:focus,.md-button:hover{text-decoration:none}.md-button.ng-hide,.md-button.ng-leave{-webkit-transition:none;transition:none}.md-button.md-cornered{border-radius:0}.md-button.md-icon{padding:0;background:none}.md-button.md-raised:not([disabled]){box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.md-button.md-icon-button{margin:0 6px;height:40px;min-width:0;line-height:24px;padding:8px;width:40px;border-radius:50%}.md-button.md-icon-button .md-ripple-container{border-radius:50%;background-clip:padding-box;overflow:hidden;-webkit-mask-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC")}.md-button.md-fab{z-index:20;line-height:56px;min-width:0;width:56px;height:56px;vertical-align:middle;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);border-radius:50%;background-clip:padding-box;overflow:hidden;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-property:background-color,box-shadow,-webkit-transform;transition-property:background-color,box-shadow,-webkit-transform;transition-property:background-color,box-shadow,transform;transition-property:background-color,box-shadow,transform,-webkit-transform}.md-button.md-fab.md-fab-bottom-right{top:auto;right:20px;bottom:20px;left:auto;position:absolute}.md-button.md-fab.md-fab-bottom-left{top:auto;right:auto;bottom:20px;left:20px;position:absolute}.md-button.md-fab.md-fab-top-right{top:20px;right:20px;bottom:auto;left:auto;position:absolute}.md-button.md-fab.md-fab-top-left{top:20px;right:auto;bottom:auto;left:20px;position:absolute}.md-button.md-fab .md-ripple-container{border-radius:50%;background-clip:padding-box;overflow:hidden;-webkit-mask-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC")}.md-button.md-fab.md-mini{line-height:40px;width:40px;height:40px}.md-button.md-fab.ng-hide,.md-button.md-fab.ng-leave{-webkit-transition:none;transition:none}.md-button:not([disabled]).md-fab.md-focused,.md-button:not([disabled]).md-raised.md-focused{box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.md-button:not([disabled]).md-fab:active,.md-button:not([disabled]).md-raised:active{box-shadow:0 4px 8px 0 rgba(0,0,0,.4)}.md-button .md-ripple-container{border-radius:3px;background-clip:padding-box;overflow:hidden;-webkit-mask-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC")}.md-button.md-icon-button md-icon,button.md-button.md-fab md-icon{display:block}.md-toast-open-top .md-button.md-fab-top-left,.md-toast-open-top .md-button.md-fab-top-right{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transform:translate3d(0,42px,0);transform:translate3d(0,42px,0)}.md-toast-open-top .md-button.md-fab-top-left:not([disabled]).md-focused,.md-toast-open-top .md-button.md-fab-top-left:not([disabled]):hover,.md-toast-open-top .md-button.md-fab-top-right:not([disabled]).md-focused,.md-toast-open-top .md-button.md-fab-top-right:not([disabled]):hover{-webkit-transform:translate3d(0,41px,0);transform:translate3d(0,41px,0)}.md-toast-open-bottom .md-button.md-fab-bottom-left,.md-toast-open-bottom .md-button.md-fab-bottom-right{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transform:translate3d(0,-42px,0);transform:translate3d(0,-42px,0)}.md-toast-open-bottom .md-button.md-fab-bottom-left:not([disabled]).md-focused,.md-toast-open-bottom .md-button.md-fab-bottom-left:not([disabled]):hover,.md-toast-open-bottom .md-button.md-fab-bottom-right:not([disabled]).md-focused,.md-toast-open-bottom .md-button.md-fab-bottom-right:not([disabled]):hover{-webkit-transform:translate3d(0,-43px,0);transform:translate3d(0,-43px,0)}.md-button-group{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-flex:1;-webkit-flex:1;flex:1;width:100%}.md-button-group>.md-button{-webkit-box-flex:1;-webkit-flex:1;flex:1;display:block;overflow:hidden;width:0;border-width:1px 0 1px 1px;border-radius:0;text-align:center;text-overflow:ellipsis;white-space:nowrap}.md-button-group>.md-button:first-child{border-radius:2px 0 0 2px}.md-button-group>.md-button:last-child{border-right-width:1px;border-radius:0 2px 2px 0}@media screen and (-ms-high-contrast:active){.md-button.md-fab,.md-button.md-raised{border:1px solid #fff}}md-card{box-sizing:border-box;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column;margin:8px;box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)}md-card,md-card md-card-header{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-direction:normal}md-card md-card-header{padding:16px;-webkit-box-orient:horizontal;-webkit-flex-direction:row;flex-direction:row}md-card md-card-header:first-child md-card-avatar{margin-right:12px}[dir=rtl] md-card md-card-header:first-child md-card-avatar{margin-right:auto;margin-left:12px}md-card md-card-header:last-child md-card-avatar{margin-left:12px}[dir=rtl] md-card md-card-header:last-child md-card-avatar{margin-left:auto;margin-right:12px}md-card md-card-header md-card-avatar{width:40px;height:40px}md-card md-card-header md-card-avatar .md-user-avatar,md-card md-card-header md-card-avatar md-icon{border-radius:50%}md-card md-card-header md-card-avatar md-icon{padding:8px}md-card md-card-header md-card-avatar+md-card-header-text{max-height:40px}md-card md-card-header md-card-avatar+md-card-header-text .md-title{font-size:14px}md-card md-card-header md-card-header-text{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-flex:1;-webkit-flex:1;flex:1;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}md-card md-card-header md-card-header-text .md-subhead{font-size:14px}md-card>img,md-card>md-card-header img,md-card md-card-title-media img{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;width:100%;height:auto}md-card md-card-title{padding:24px 16px 16px;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-card md-card-title+md-card-content{padding-top:0}md-card md-card-title md-card-title-text{-webkit-box-flex:1;-webkit-flex:1;flex:1;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;display:-webkit-box;display:-webkit-flex;display:flex}md-card md-card-title md-card-title-text .md-subhead{padding-top:0;font-size:14px}md-card md-card-title md-card-title-text:only-child .md-subhead{padding-top:12px}md-card md-card-title md-card-title-media{margin-top:-8px}md-card md-card-title md-card-title-media .md-media-sm{height:80px;width:80px}md-card md-card-title md-card-title-media .md-media-md{height:112px;width:112px}md-card md-card-title md-card-title-media .md-media-lg{height:152px;width:152px}md-card md-card-content{display:block;padding:16px}md-card md-card-content>p:first-child{margin-top:0}md-card md-card-content>p:last-child{margin-bottom:0}md-card md-card-content .md-media-xl{height:240px;width:240px}md-card .md-actions,md-card md-card-actions{margin:8px}md-card .md-actions.layout-column .md-button:not(.md-icon-button),md-card md-card-actions.layout-column .md-button:not(.md-icon-button){margin:2px 0}md-card .md-actions.layout-column .md-button:not(.md-icon-button):first-of-type,md-card md-card-actions.layout-column .md-button:not(.md-icon-button):first-of-type{margin-top:0}md-card .md-actions.layout-column .md-button:not(.md-icon-button):last-of-type,md-card md-card-actions.layout-column .md-button:not(.md-icon-button):last-of-type{margin-bottom:0}md-card .md-actions.layout-column .md-button.md-icon-button,md-card md-card-actions.layout-column .md-button.md-icon-button{margin-top:6px;margin-bottom:6px}md-card .md-actions md-card-icon-actions,md-card md-card-actions md-card-icon-actions{-webkit-box-flex:1;-webkit-flex:1;flex:1;-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button),md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button){margin:0 4px}md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type,md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type{margin-left:0}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type{margin-left:auto;margin-right:0}md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type,md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type{margin-right:0}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type{margin-right:auto;margin-left:0}md-card .md-actions:not(.layout-column) .md-button.md-icon-button,md-card md-card-actions:not(.layout-column) .md-button.md-icon-button{margin-left:6px;margin-right:6px}md-card .md-actions:not(.layout-column) .md-button.md-icon-button:first-of-type,md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:first-of-type{margin-left:12px}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button.md-icon-button:first-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:first-of-type{margin-left:auto;margin-right:12px}md-card .md-actions:not(.layout-column) .md-button.md-icon-button:last-of-type,md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:last-of-type{margin-right:12px}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button.md-icon-button:last-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:last-of-type{margin-right:auto;margin-left:12px}md-card .md-actions:not(.layout-column) .md-button+md-card-icon-actions,md-card md-card-actions:not(.layout-column) .md-button+md-card-icon-actions{-webkit-box-flex:1;-webkit-flex:1;flex:1;-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-card md-card-footer{margin-top:auto;padding:16px}@media screen and (-ms-high-contrast:active){md-card{border:1px solid #fff}}.md-image-no-fill>img{width:auto;height:auto}.md-contact-chips .md-chips md-chip{padding:0 25px 0 0}[dir=rtl] .md-contact-chips .md-chips md-chip{padding:0 0 0 25px}.md-contact-chips .md-chips md-chip .md-contact-avatar{float:left}[dir=rtl] .md-contact-chips .md-chips md-chip .md-contact-avatar{float:right}.md-contact-chips .md-chips md-chip .md-contact-avatar img{height:32px;border-radius:16px}.md-contact-chips .md-chips md-chip .md-contact-name{display:inline-block;height:32px;margin-left:8px}[dir=rtl] .md-contact-chips .md-chips md-chip .md-contact-name{margin-left:auto;margin-right:8px}.md-contact-suggestion{height:56px}.md-contact-suggestion img{height:40px;border-radius:20px;margin-top:8px}.md-contact-suggestion .md-contact-name{margin-left:8px;width:120px}[dir=rtl] .md-contact-suggestion .md-contact-name{margin-left:auto;margin-right:8px}.md-contact-suggestion .md-contact-email,.md-contact-suggestion .md-contact-name{display:inline-block;overflow:hidden;text-overflow:ellipsis}.md-contact-chips-suggestions li{height:100%}.md-chips{display:block;font-family:Roboto,Helvetica Neue,sans-serif;font-size:16px;padding:0 0 8px 3px;vertical-align:middle}.md-chips:after{content:'';display:table;clear:both}[dir=rtl] .md-chips{padding:0 3px 8px 0}.md-chips.md-readonly .md-chip-input-container{min-height:32px}.md-chips:not(.md-readonly){cursor:text}.md-chips.md-removable md-chip{padding-right:22px}[dir=rtl] .md-chips.md-removable md-chip{padding-right:0;padding-left:22px}.md-chips.md-removable md-chip .md-chip-content{padding-right:4px}[dir=rtl] .md-chips.md-removable md-chip .md-chip-content{padding-right:0;padding-left:4px}.md-chips md-chip{cursor:default;border-radius:16px;display:block;height:32px;line-height:32px;margin:8px 8px 0 0;padding:0 12px;float:left;box-sizing:border-box;max-width:100%;position:relative}[dir=rtl] .md-chips md-chip{margin:8px 0 0 8px;float:right}.md-chips md-chip .md-chip-content{display:block;float:left;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}[dir=rtl] .md-chips md-chip .md-chip-content{float:right}.md-chips md-chip .md-chip-content:focus{outline:none}.md-chips md-chip._md-chip-content-edit-is-enabled{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}.md-chips md-chip .md-chip-remove-container{position:absolute;right:0;line-height:22px}[dir=rtl] .md-chips md-chip .md-chip-remove-container{right:auto;left:0}.md-chips md-chip .md-chip-remove{text-align:center;width:32px;height:32px;min-width:0;padding:0;background:transparent;border:none;box-shadow:none;margin:0;position:relative}.md-chips md-chip .md-chip-remove md-icon{height:18px;width:18px;position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.md-chips .md-chip-input-container{display:block;line-height:32px;margin:8px 8px 0 0;padding:0;float:left}[dir=rtl] .md-chips .md-chip-input-container{margin:8px 0 0 8px;float:right}.md-chips .md-chip-input-container input:not([type]),.md-chips .md-chip-input-container input[type=email],.md-chips .md-chip-input-container input[type=number],.md-chips .md-chip-input-container input[type=tel],.md-chips .md-chip-input-container input[type=text],.md-chips .md-chip-input-container input[type=url]{border:0;height:32px;line-height:32px;padding:0}.md-chips .md-chip-input-container input:not([type]):focus,.md-chips .md-chip-input-container input[type=email]:focus,.md-chips .md-chip-input-container input[type=number]:focus,.md-chips .md-chip-input-container input[type=tel]:focus,.md-chips .md-chip-input-container input[type=text]:focus,.md-chips .md-chip-input-container input[type=url]:focus{outline:none}.md-chips .md-chip-input-container md-autocomplete,.md-chips .md-chip-input-container md-autocomplete-wrap{background:transparent;height:32px}.md-chips .md-chip-input-container md-autocomplete md-autocomplete-wrap{box-shadow:none}.md-chips .md-chip-input-container input{border:0;height:32px;line-height:32px;padding:0}.md-chips .md-chip-input-container input:focus{outline:none}.md-chips .md-chip-input-container md-autocomplete,.md-chips .md-chip-input-container md-autocomplete-wrap{height:32px}.md-chips .md-chip-input-container md-autocomplete{box-shadow:none}.md-chips .md-chip-input-container md-autocomplete input{position:relative}.md-chips .md-chip-input-container:not(:first-child){margin:8px 8px 0 0}[dir=rtl] .md-chips .md-chip-input-container:not(:first-child){margin:8px 0 0 8px}.md-chips .md-chip-input-container input{background:transparent;border-width:0}.md-chips md-autocomplete button{display:none}@media screen and (-ms-high-contrast:active){.md-chip-input-container,md-chip{border:1px solid #fff}.md-chip-input-container md-autocomplete{border:none}}.md-inline-form md-checkbox{margin:19px 0 18px}md-checkbox{box-sizing:border-box;display:inline-block;margin-bottom:16px;white-space:nowrap;cursor:pointer;outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;min-width:20px;min-height:20px;margin-left:0;margin-right:16px}[dir=rtl] md-checkbox{margin-left:16px;margin-right:0}md-checkbox:last-of-type{margin-left:0;margin-right:0}md-checkbox.md-focused:not([disabled]) .md-container:before{left:-8px;top:-8px;right:-8px;bottom:-8px}md-checkbox.md-focused:not([disabled]):not(.md-checked) .md-container:before{background-color:rgba(0,0,0,.12)}md-checkbox.md-align-top-left>div.md-container{top:12px}md-checkbox .md-container{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);box-sizing:border-box;display:inline-block;width:20px;height:20px;left:0;right:auto}[dir=rtl] md-checkbox .md-container{left:auto;right:0}md-checkbox .md-container:before{box-sizing:border-box;background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;-webkit-transition:all .5s;transition:all .5s;width:auto}md-checkbox .md-container:after{box-sizing:border-box;content:'';position:absolute;top:-10px;right:-10px;bottom:-10px;left:-10px}md-checkbox .md-container .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-15px;top:-15px;right:-15px;bottom:-15px}md-checkbox .md-icon{box-sizing:border-box;-webkit-transition:.24s;transition:.24s;position:absolute;top:0;left:0;width:20px;height:20px;border-width:2px;border-style:solid;border-radius:2px}md-checkbox.md-checked .md-icon{border-color:transparent}md-checkbox.md-checked .md-icon:after{box-sizing:border-box;-webkit-transform:rotate(45deg);transform:rotate(45deg);position:absolute;left:4.66667px;top:.22222px;display:table;width:6.66667px;height:13.33333px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-checkbox[disabled]{cursor:default}md-checkbox.md-indeterminate .md-icon:after{box-sizing:border-box;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:table;width:12px;height:2px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-checkbox .md-label{box-sizing:border-box;position:relative;display:inline-block;vertical-align:middle;white-space:normal;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;margin-left:30px;margin-right:0}[dir=rtl] md-checkbox .md-label{margin-left:0;margin-right:30px}md-content{display:block;position:relative;overflow:auto;-webkit-overflow-scrolling:touch}md-content[md-scroll-y]{overflow-y:auto;overflow-x:hidden}md-content[md-scroll-x]{overflow-x:auto;overflow-y:hidden}@media print{md-content{overflow:visible!important}}md-calendar{font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-calendar-scroll-mask{display:inline-block;overflow:hidden;height:308px}.md-calendar-scroll-mask .md-virtual-repeat-scroller{overflow-y:scroll;-webkit-overflow-scrolling:touch}.md-calendar-scroll-mask .md-virtual-repeat-scroller::-webkit-scrollbar{display:none}.md-calendar-scroll-mask .md-virtual-repeat-offsetter{width:100%}.md-calendar-scroll-container{box-shadow:inset -3px 3px 6px rgba(0,0,0,.2);display:inline-block;height:308px;width:346px}.md-calendar-date{height:44px;width:44px;text-align:center;padding:0;border:none;box-sizing:content-box}.md-calendar-date:first-child{padding-left:16px}[dir=rtl] .md-calendar-date:first-child{padding-left:0;padding-right:16px}.md-calendar-date:last-child{padding-right:16px}[dir=rtl] .md-calendar-date:last-child{padding-right:0;padding-left:16px}.md-calendar-date.md-calendar-date-disabled{cursor:default}.md-calendar-date-selection-indicator{-webkit-transition:background-color,color .4s cubic-bezier(.25,.8,.25,1);transition:background-color,color .4s cubic-bezier(.25,.8,.25,1);border-radius:50%;display:inline-block;width:40px;height:40px;line-height:40px}.md-calendar-date:not(.md-disabled) .md-calendar-date-selection-indicator{cursor:pointer}.md-calendar-month-label{height:44px;font-size:14px;font-weight:500;padding:0 0 0 24px}[dir=rtl] .md-calendar-month-label{padding:0 24px 0 0}md-calendar-month .md-calendar-month-label:not(.md-calendar-month-label-disabled){cursor:pointer}.md-calendar-month-label md-icon{-webkit-transform:rotate(180deg);transform:rotate(180deg)}[dir=rtl] .md-calendar-month-label md-icon{-webkit-transform:none;transform:none}.md-calendar-month-label span{vertical-align:middle}.md-calendar-day-header{table-layout:fixed;border-spacing:0;border-collapse:collapse}.md-calendar-day-header th{height:40px;width:44px;text-align:center;padding:0;border:none;box-sizing:content-box;font-weight:400}.md-calendar-day-header th:first-child{padding-left:16px}[dir=rtl] .md-calendar-day-header th:first-child{padding-left:0;padding-right:16px}.md-calendar-day-header th:last-child{padding-right:16px}[dir=rtl] .md-calendar-day-header th:last-child{padding-right:0;padding-left:16px}.md-calendar{table-layout:fixed;border-spacing:0;border-collapse:collapse}.md-calendar tr:last-child td{border-bottom-width:1px;border-bottom-style:solid}.md-calendar:first-child{border-top:1px solid transparent}.md-calendar tbody,.md-calendar td,.md-calendar tr{vertical-align:middle;box-sizing:content-box}md-datepicker{white-space:nowrap;overflow:hidden;padding-right:18px;margin-right:-18px;vertical-align:middle}[dir=rtl] md-datepicker{padding-right:0;padding-left:18px;margin-right:auto;margin-left:-18px}.md-inline-form md-datepicker{margin-top:12px}.md-datepicker-button{display:inline-block;box-sizing:border-box;background:none;vertical-align:middle;position:relative}.md-datepicker-button:before{top:0;left:0;bottom:0;right:0;position:absolute;content:'';speak:none}.md-datepicker-input{font-size:14px;box-sizing:border-box;border:none;box-shadow:none;outline:none;background:transparent;min-width:120px;max-width:328px;padding:0 0 5px}.md-datepicker-input::-ms-clear{display:none}._md-datepicker-floating-label>md-datepicker{overflow:visible}._md-datepicker-floating-label>md-datepicker .md-datepicker-input-container{border:none}._md-datepicker-floating-label>md-datepicker .md-datepicker-button{float:left;margin-top:-2.5px}[dir=rtl] ._md-datepicker-floating-label>md-datepicker .md-datepicker-button{float:right}._md-datepicker-floating-label._md-datepicker-has-calendar-icon>label:not(.md-no-float):not(.md-container-ignore){right:18px;left:auto;width:calc(100% - 84px)}[dir=rtl] ._md-datepicker-floating-label._md-datepicker-has-calendar-icon>label:not(.md-no-float):not(.md-container-ignore){right:auto;left:18px}._md-datepicker-floating-label._md-datepicker-has-calendar-icon .md-input-message-animation{margin-left:64px}[dir=rtl] ._md-datepicker-floating-label._md-datepicker-has-calendar-icon .md-input-message-animation{margin-left:auto;margin-right:64px}.md-datepicker-input-container{position:relative;border-bottom-width:1px;border-bottom-style:solid;display:inline-block;width:auto}.md-icon-button+.md-datepicker-input-container{margin-left:12px}[dir=rtl] .md-icon-button+.md-datepicker-input-container{margin-left:auto;margin-right:12px}.md-datepicker-input-container.md-datepicker-focused{border-bottom-width:2px}.md-datepicker-is-showing .md-scroll-mask{z-index:99}.md-datepicker-calendar-pane{position:absolute;top:0;left:-100%;z-index:100;border-width:1px;border-style:solid;background:transparent;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transition:-webkit-transform .2s cubic-bezier(.25,.8,.25,1);transition:-webkit-transform .2s cubic-bezier(.25,.8,.25,1);transition:transform .2s cubic-bezier(.25,.8,.25,1);transition:transform .2s cubic-bezier(.25,.8,.25,1),-webkit-transform .2s cubic-bezier(.25,.8,.25,1)}.md-datepicker-calendar-pane.md-pane-open{-webkit-transform:scale(1);transform:scale(1)}.md-datepicker-input-mask{height:40px;width:340px;position:relative;overflow:hidden;background:transparent;pointer-events:none;cursor:text}.md-datepicker-calendar{opacity:0;-webkit-transition:opacity .2s cubic-bezier(.5,0,.25,1);transition:opacity .2s cubic-bezier(.5,0,.25,1)}.md-pane-open .md-datepicker-calendar{opacity:1}.md-datepicker-calendar md-calendar:focus{outline:none}.md-datepicker-expand-triangle{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid}.md-datepicker-triangle-button{position:absolute;right:0;top:5px;-webkit-transform:translateY(-25%) translateX(45%);transform:translateY(-25%) translateX(45%)}[dir=rtl] .md-datepicker-triangle-button{right:auto;left:0;-webkit-transform:translateY(-25%) translateX(-45%);transform:translateY(-25%) translateX(-45%)}.md-datepicker-triangle-button.md-button.md-icon-button{height:36px;width:36px;position:absolute;padding:8px}md-datepicker[disabled] .md-datepicker-input-container{border-bottom-color:transparent}md-datepicker[disabled] .md-datepicker-triangle-button{display:none}.md-datepicker-open{overflow:hidden}.md-datepicker-open .md-datepicker-input-container,.md-datepicker-open input.md-input{border-bottom-color:transparent}.md-datepicker-open .md-datepicker-triangle-button,.md-datepicker-open.md-input-has-placeholder>label,.md-datepicker-open.md-input-has-value>label,.md-datepicker-pos-adjusted .md-datepicker-input-mask{display:none}.md-datepicker-calendar-pane .md-calendar{-webkit-transform:translateY(-85px);transform:translateY(-85px);-webkit-transition:-webkit-transform .65s cubic-bezier(.25,.8,.25,1);transition:-webkit-transform .65s cubic-bezier(.25,.8,.25,1);transition:transform .65s cubic-bezier(.25,.8,.25,1);transition:transform .65s cubic-bezier(.25,.8,.25,1),-webkit-transform .65s cubic-bezier(.25,.8,.25,1);-webkit-transition-delay:.125s;transition-delay:.125s}.md-datepicker-calendar-pane.md-pane-open .md-calendar{-webkit-transform:translateY(0);transform:translateY(0)}.md-dialog-is-showing{max-height:100%}.md-dialog-container{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;align-items:center;position:absolute;top:0;left:0;width:100%;height:100%;z-index:80;overflow:hidden}.md-dialog-container,md-dialog{display:-webkit-box;display:-webkit-flex;display:flex}md-dialog{opacity:0;min-width:240px;max-width:80%;max-height:80%;position:relative;overflow:auto;box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12);-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}md-dialog.md-transition-in{opacity:1;-webkit-transform:translate(0,0) scale(1);transform:translate(0,0) scale(1)}md-dialog.md-transition-in,md-dialog.md-transition-out{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1)}md-dialog.md-transition-out{opacity:0;-webkit-transform:translate(0,100%) scale(.2);transform:translate(0,100%) scale(.2)}md-dialog>form{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;overflow:auto}md-dialog .md-dialog-content{padding:24px}md-dialog md-dialog-content{-webkit-box-ordinal-group:2;-webkit-order:1;order:1;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;overflow:auto;-webkit-overflow-scrolling:touch}md-dialog md-dialog-content:not([layout=row])>:first-child:not(.md-subheader){margin-top:0}md-dialog md-dialog-content:focus{outline:none}md-dialog md-dialog-content .md-subheader{margin:0}md-dialog md-dialog-content .md-dialog-content-body{width:100%}md-dialog md-dialog-content .md-prompt-input-container{width:100%;box-sizing:border-box}md-dialog .md-actions,md-dialog md-dialog-actions{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-ordinal-group:3;-webkit-order:2;order:2;box-sizing:border-box;-webkit-box-align:center;-webkit-align-items:center;align-items:center;-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end;margin-bottom:0;padding-right:8px;padding-left:16px;min-height:52px;overflow:hidden}[dir=rtl] md-dialog .md-actions,[dir=rtl] md-dialog md-dialog-actions{padding-right:16px;padding-left:8px}md-dialog .md-actions .md-button,md-dialog md-dialog-actions .md-button{margin:8px 0 8px 8px}[dir=rtl] md-dialog .md-actions .md-button,[dir=rtl] md-dialog md-dialog-actions .md-button{margin-left:0;margin-right:8px}md-dialog.md-content-overflow .md-actions,md-dialog.md-content-overflow md-dialog-actions{border-top-width:1px;border-top-style:solid}@media screen and (-ms-high-contrast:active){md-dialog{border:1px solid #fff}}@media (max-width:959px){md-dialog.md-dialog-fullscreen{min-height:100%;min-width:100%;border-radius:0}}md-divider{display:block;border-top-width:1px;border-top-style:solid;margin:0}md-divider[md-inset]{margin-left:80px}[dir=rtl] md-divider[md-inset]{margin-left:auto;margin-right:80px}.layout-gt-lg-row>md-divider,.layout-gt-md-row>md-divider,.layout-gt-sm-row>md-divider,.layout-gt-xs-row>md-divider,.layout-lg-row>md-divider,.layout-md-row>md-divider,.layout-row>md-divider,.layout-sm-row>md-divider,.layout-xl-row>md-divider,.layout-xs-row>md-divider{border-top-width:0;border-right-width:1px;border-right-style:solid}md-fab-speed-dial{position:relative;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;z-index:20}md-fab-speed-dial.md-fab-bottom-right{top:auto;right:20px;bottom:20px;left:auto;position:absolute}md-fab-speed-dial.md-fab-bottom-left{top:auto;right:auto;bottom:20px;left:20px;position:absolute}md-fab-speed-dial.md-fab-top-right{top:20px;right:20px;bottom:auto;left:auto;position:absolute}md-fab-speed-dial.md-fab-top-left{top:20px;right:auto;bottom:auto;left:20px;position:absolute}md-fab-speed-dial:not(.md-hover-full){pointer-events:none}md-fab-speed-dial:not(.md-hover-full) .md-fab-action-item,md-fab-speed-dial:not(.md-hover-full).md-is-open,md-fab-speed-dial:not(.md-hover-full) md-fab-trigger{pointer-events:auto}md-fab-speed-dial ._md-css-variables{z-index:20}md-fab-speed-dial.md-is-open .md-fab-action-item{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center}md-fab-speed-dial md-fab-actions{display:-webkit-box;display:-webkit-flex;display:flex;height:auto}md-fab-speed-dial md-fab-actions .md-fab-action-item{-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-speed-dial.md-down{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}md-fab-speed-dial.md-down md-fab-trigger{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}md-fab-speed-dial.md-down md-fab-actions{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;-webkit-box-ordinal-group:3;-webkit-order:2;order:2}md-fab-speed-dial.md-up{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}md-fab-speed-dial.md-up md-fab-trigger{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}md-fab-speed-dial.md-up md-fab-actions{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;flex-direction:column-reverse;-webkit-box-ordinal-group:2;-webkit-order:1;order:1}md-fab-speed-dial.md-left{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-fab-speed-dial.md-left md-fab-trigger{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}md-fab-speed-dial.md-left md-fab-actions{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-box-ordinal-group:2;-webkit-order:1;order:1}md-fab-speed-dial.md-left md-fab-actions .md-fab-action-item{-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-speed-dial.md-right{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-fab-speed-dial.md-right md-fab-trigger{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}md-fab-speed-dial.md-right md-fab-actions{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row;-webkit-box-ordinal-group:3;-webkit-order:2;order:2}md-fab-speed-dial.md-right md-fab-actions .md-fab-action-item{-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-speed-dial.md-fling-remove .md-fab-action-item>*,md-fab-speed-dial.md-scale-remove .md-fab-action-item>*{visibility:hidden}md-fab-speed-dial.md-fling .md-fab-action-item{opacity:1}md-fab-speed-dial.md-fling.md-animations-waiting .md-fab-action-item{opacity:0;-webkit-transition-duration:0s;transition-duration:0s}md-fab-speed-dial.md-scale .md-fab-action-item{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-duration:.14286s;transition-duration:.14286s}md-fab-toolbar{display:block}md-fab-toolbar.md-fab-bottom-right{top:auto;right:20px;bottom:20px;left:auto;position:absolute}md-fab-toolbar.md-fab-bottom-left{top:auto;right:auto;bottom:20px;left:20px;position:absolute}md-fab-toolbar.md-fab-top-right{top:20px;right:20px;bottom:auto;left:auto;position:absolute}md-fab-toolbar.md-fab-top-left{top:20px;right:auto;bottom:auto;left:20px;position:absolute}md-fab-toolbar .md-fab-toolbar-wrapper{display:block;position:relative;overflow:hidden;height:68px}md-fab-toolbar md-fab-trigger{position:absolute;z-index:20}md-fab-toolbar md-fab-trigger button{overflow:visible!important}md-fab-toolbar md-fab-trigger .md-fab-toolbar-background{display:block;position:absolute;z-index:21;opacity:1;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-toolbar md-fab-trigger md-icon{position:relative;z-index:22;opacity:1;-webkit-transition:all .2s ease-in;transition:all .2s ease-in}md-fab-toolbar.md-left md-fab-trigger{right:0}[dir=rtl] md-fab-toolbar.md-left md-fab-trigger{right:auto;left:0}md-fab-toolbar.md-left .md-toolbar-tools{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;flex-direction:row-reverse}md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-right:.6rem}[dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-right:auto;margin-left:.6rem}md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-left:-.8rem}[dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-left:auto;margin-right:-.8rem}md-fab-toolbar.md-left .md-toolbar-tools>.md-button:last-child{margin-right:8px}[dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools>.md-button:last-child{margin-right:auto;margin-left:8px}md-fab-toolbar.md-right md-fab-trigger{left:0}[dir=rtl] md-fab-toolbar.md-right md-fab-trigger{left:auto;right:0}md-fab-toolbar.md-right .md-toolbar-tools{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-fab-toolbar md-toolbar{background-color:transparent!important;pointer-events:none;z-index:23}md-fab-toolbar md-toolbar .md-toolbar-tools{padding:0 20px;margin-top:3px}md-fab-toolbar md-toolbar .md-fab-action-item{opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-duration:.15s;transition-duration:.15s}md-fab-toolbar.md-is-open md-fab-trigger>button{box-shadow:none}md-fab-toolbar.md-is-open md-fab-trigger>button md-icon{opacity:0}md-fab-toolbar.md-is-open .md-fab-action-item{opacity:1;-webkit-transform:scale(1);transform:scale(1)}md-grid-list{display:block;position:relative}md-grid-list,md-grid-list md-grid-tile,md-grid-list md-grid-tile-footer,md-grid-list md-grid-tile-header,md-grid-list md-grid-tile>figure{box-sizing:border-box}md-grid-list md-grid-tile{display:block;position:absolute}md-grid-list md-grid-tile figure{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center;height:100%;top:0;bottom:0;padding:0;margin:0}md-grid-list md-grid-tile figure,md-grid-list md-grid-tile md-grid-tile-footer,md-grid-list md-grid-tile md-grid-tile-header{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;position:absolute;right:0;left:0}md-grid-list md-grid-tile md-grid-tile-footer,md-grid-list md-grid-tile md-grid-tile-header{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row;height:48px;color:#fff;background:rgba(0,0,0,.18);overflow:hidden}md-grid-list md-grid-tile md-grid-tile-footer h3,md-grid-list md-grid-tile md-grid-tile-footer h4,md-grid-list md-grid-tile md-grid-tile-header h3,md-grid-list md-grid-tile md-grid-tile-header h4{font-weight:400;margin:0 0 0 16px}md-grid-list md-grid-tile md-grid-tile-footer h3,md-grid-list md-grid-tile md-grid-tile-header h3{font-size:14px}md-grid-list md-grid-tile md-grid-tile-footer h4,md-grid-list md-grid-tile md-grid-tile-header h4{font-size:12px}md-grid-list md-grid-tile md-grid-tile-header{top:0}md-grid-list md-grid-tile md-grid-tile-footer{bottom:0}@media screen and (-ms-high-contrast:active){md-grid-tile{border:1px solid #fff}md-grid-tile-footer{border-top:1px solid #fff}}md-icon{margin:auto;background-repeat:no-repeat;display:inline-block;vertical-align:middle;fill:currentColor;height:24px;width:24px;min-height:24px;min-width:24px}md-icon svg{pointer-events:none;display:block}md-icon[md-font-icon]{line-height:24px;width:auto}md-input-container{display:inline-block;position:relative;padding:2px;margin:18px 0;vertical-align:middle}md-input-container:after{content:'';display:table;clear:both}md-input-container.md-block{display:block}md-input-container .md-errors-spacer{float:right;min-height:24px;min-width:1px}[dir=rtl] md-input-container .md-errors-spacer{float:left}md-input-container>md-icon{position:absolute;top:8px;left:2px;right:auto}[dir=rtl] md-input-container>md-icon{left:auto;right:2px}md-input-container input[type=color],md-input-container input[type=date],md-input-container input[type=datetime-local],md-input-container input[type=datetime],md-input-container input[type=email],md-input-container input[type=month],md-input-container input[type=number],md-input-container input[type=password],md-input-container input[type=search],md-input-container input[type=tel],md-input-container input[type=text],md-input-container input[type=time],md-input-container input[type=url],md-input-container input[type=week],md-input-container textarea{-moz-appearance:none;-webkit-appearance:none}md-input-container input[type=date],md-input-container input[type=datetime-local],md-input-container input[type=month],md-input-container input[type=time],md-input-container input[type=week]{min-height:26px}md-input-container textarea{resize:none;overflow:hidden}md-input-container textarea.md-input{min-height:26px;-ms-flex-preferred-size:auto}md-input-container textarea[md-no-autogrow]{height:auto;overflow:auto}md-input-container label:not(.md-container-ignore){position:absolute;bottom:100%;left:0;right:auto}[dir=rtl] md-input-container label:not(.md-container-ignore){left:auto;right:0}md-input-container label:not(.md-container-ignore).md-required:after{content:' *';font-size:13px;vertical-align:top}md-input-container .md-placeholder,md-input-container label:not(.md-no-float):not(.md-container-ignore){overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%;-webkit-box-ordinal-group:2;-webkit-order:1;order:1;pointer-events:none;-webkit-font-smoothing:antialiased;padding-left:3px;padding-right:0;z-index:1;-webkit-transform:translate3d(0,28px,0) scale(1);transform:translate3d(0,28px,0) scale(1);-webkit-transition:-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1);max-width:100%;-webkit-transform-origin:left top;transform-origin:left top}[dir=rtl] md-input-container .md-placeholder,[dir=rtl] md-input-container label:not(.md-no-float):not(.md-container-ignore){padding-left:0;padding-right:3px;-webkit-transform-origin:right top;transform-origin:right top}md-input-container .md-placeholder{position:absolute;top:0;opacity:0;-webkit-transition-property:opacity,-webkit-transform;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;-webkit-transform:translate3d(0,30px,0);transform:translate3d(0,30px,0)}md-input-container.md-input-focused .md-placeholder{opacity:1;-webkit-transform:translate3d(0,24px,0);transform:translate3d(0,24px,0)}md-input-container.md-input-has-value .md-placeholder{-webkit-transition:none;transition:none;opacity:0}md-input-container:not(.md-input-has-value) input:not(:focus),md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-ampm-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-day-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-hour-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-millisecond-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-minute-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-month-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-second-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-text,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-week-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-year-field{color:transparent}md-input-container .md-input{-webkit-box-ordinal-group:3;-webkit-order:2;order:2;display:block;margin-top:0;background:none;padding:2px 2px 1px;border-width:0 0 1px;line-height:26px;height:30px;-ms-flex-preferred-size:26px;border-radius:0;border-style:solid;width:100%;box-sizing:border-box;float:left}[dir=rtl] md-input-container .md-input{float:right}md-input-container .md-input:focus{outline:none}md-input-container .md-input:invalid{outline:none;box-shadow:none}md-input-container .md-input.md-no-flex{-webkit-box-flex:0!important;-webkit-flex:none!important;flex:none!important}md-input-container .md-char-counter{text-align:right;padding-right:2px;padding-left:0}[dir=rtl] md-input-container .md-char-counter{text-align:left;padding-right:0;padding-left:2px}md-input-container .md-input-messages-animation{position:relative;-webkit-box-ordinal-group:5;-webkit-order:4;order:4;overflow:hidden;clear:left}[dir=rtl] md-input-container .md-input-messages-animation{clear:right}md-input-container .md-input-messages-animation.ng-enter .md-input-message-animation{opacity:0;margin-top:-100px}md-input-container .md-char-counter,md-input-container .md-input-message-animation{font-size:12px;line-height:14px;overflow:hidden;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);opacity:1;margin-top:0;padding-top:5px}md-input-container .md-char-counter:not(.md-char-counter),md-input-container .md-input-message-animation:not(.md-char-counter){padding-right:5px;padding-left:0}[dir=rtl] md-input-container .md-char-counter:not(.md-char-counter),[dir=rtl] md-input-container .md-input-message-animation:not(.md-char-counter){padding-right:0;padding-left:5px}md-input-container .md-input-message-animation.ng-enter,md-input-container .md-input-message-animation:not(.ng-animate),md-input-container:not(.md-input-invalid) .md-auto-hide .md-input-message-animation{opacity:0;margin-top:-100px}md-input-container.md-input-focused label:not(.md-no-float),md-input-container.md-input-has-placeholder label:not(.md-no-float),md-input-container.md-input-has-value label:not(.md-no-float){-webkit-transform:translate3d(0,6px,0) scale(.75);transform:translate3d(0,6px,0) scale(.75);-webkit-transition:width .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:width .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1),width .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1),width .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1)}md-input-container.md-input-has-value label{-webkit-transition:none;transition:none}md-input-container.md-input-focused .md-input,md-input-container.md-input-resized .md-input,md-input-container .md-input.ng-invalid.ng-dirty{padding-bottom:0;border-width:0 0 2px}[disabled] md-input-container .md-input,md-input-container .md-input[disabled]{background-position:bottom -1px left 0;background-size:4px 1px;background-repeat:repeat-x}md-input-container.md-icon-float{-webkit-transition:margin-top .4s cubic-bezier(.25,.8,.25,1);transition:margin-top .4s cubic-bezier(.25,.8,.25,1)}md-input-container.md-icon-float>label{pointer-events:none;position:absolute}md-input-container.md-icon-float>md-icon{top:8px;left:2px;right:auto}[dir=rtl] md-input-container.md-icon-float>md-icon{left:auto;right:2px}md-input-container.md-icon-left>label .md-placeholder,md-input-container.md-icon-left>label:not(.md-no-float):not(.md-container-ignore),md-input-container.md-icon-right>label .md-placeholder,md-input-container.md-icon-right>label:not(.md-no-float):not(.md-container-ignore){width:calc(100% - 36px - 18px)}md-input-container.md-icon-left{padding-left:36px;padding-right:0}[dir=rtl] md-input-container.md-icon-left{padding-left:0;padding-right:36px}md-input-container.md-icon-left>label{left:36px;right:auto}[dir=rtl] md-input-container.md-icon-left>label{left:auto;right:36px}md-input-container.md-icon-right{padding-left:0;padding-right:36px}[dir=rtl] md-input-container.md-icon-right{padding-left:36px;padding-right:0}md-input-container.md-icon-right>md-icon:last-of-type{margin:0;right:2px;left:auto}[dir=rtl] md-input-container.md-icon-right>md-icon:last-of-type{right:auto;left:2px}md-input-container.md-icon-left.md-icon-right{padding-left:36px;padding-right:36px}md-input-container.md-icon-left.md-icon-right>label .md-placeholder,md-input-container.md-icon-left.md-icon-right>label:not(.md-no-float):not(.md-container-ignore){width:calc(100% - 72px)}.md-resize-wrapper{position:relative}.md-resize-wrapper:after{content:'';display:table;clear:both}.md-resize-handle{position:absolute;bottom:-5px;left:0;height:10px;background:transparent;width:100%;cursor:ns-resize}@media screen and (-ms-high-contrast:active){md-input-container.md-default-theme>md-icon{fill:#fff}}md-list{display:block;padding:8px 0}md-list .md-subheader{font-size:14px;font-weight:500;letter-spacing:.01em;line-height:1.2em}md-list.md-dense md-list-item,md-list.md-dense md-list-item .md-list-item-inner{min-height:48px}md-list.md-dense md-list-item .md-list-item-inner:before,md-list.md-dense md-list-item:before{content:'';min-height:48px;visibility:hidden;display:inline-block}md-list.md-dense md-list-item .md-list-item-inner md-icon:first-child,md-list.md-dense md-list-item md-icon:first-child{width:20px;height:20px}md-list.md-dense md-list-item .md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),md-list.md-dense md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:36px}[dir=rtl] md-list.md-dense md-list-item .md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),[dir=rtl] md-list.md-dense md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:auto;margin-left:36px}md-list.md-dense md-list-item .md-avatar,md-list.md-dense md-list-item .md-avatar-icon,md-list.md-dense md-list-item .md-list-item-inner .md-avatar,md-list.md-dense md-list-item .md-list-item-inner .md-avatar-icon{margin-right:20px}[dir=rtl] md-list.md-dense md-list-item .md-avatar,[dir=rtl] md-list.md-dense md-list-item .md-avatar-icon,[dir=rtl] md-list.md-dense md-list-item .md-list-item-inner .md-avatar,[dir=rtl] md-list.md-dense md-list-item .md-list-item-inner .md-avatar-icon{margin-right:auto;margin-left:20px}md-list.md-dense md-list-item .md-avatar,md-list.md-dense md-list-item .md-list-item-inner .md-avatar{-webkit-box-flex:0;-webkit-flex:none;flex:none;width:36px;height:36px}md-list.md-dense md-list-item.md-2-line .md-list-item-text.md-offset,md-list.md-dense md-list-item.md-2-line>.md-no-style .md-list-item-text.md-offset,md-list.md-dense md-list-item.md-3-line .md-list-item-text.md-offset,md-list.md-dense md-list-item.md-3-line>.md-no-style .md-list-item-text.md-offset{margin-left:56px}[dir=rtl] md-list.md-dense md-list-item.md-2-line .md-list-item-text.md-offset,[dir=rtl] md-list.md-dense md-list-item.md-2-line>.md-no-style .md-list-item-text.md-offset,[dir=rtl] md-list.md-dense md-list-item.md-3-line .md-list-item-text.md-offset,[dir=rtl] md-list.md-dense md-list-item.md-3-line>.md-no-style .md-list-item-text.md-offset{margin-left:auto;margin-right:56px}md-list.md-dense md-list-item.md-2-line .md-list-item-text h3,md-list.md-dense md-list-item.md-2-line .md-list-item-text h4,md-list.md-dense md-list-item.md-2-line .md-list-item-text p,md-list.md-dense md-list-item.md-2-line>.md-no-style .md-list-item-text h3,md-list.md-dense md-list-item.md-2-line>.md-no-style .md-list-item-text h4,md-list.md-dense md-list-item.md-2-line>.md-no-style .md-list-item-text p,md-list.md-dense md-list-item.md-3-line .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line .md-list-item-text h4,md-list.md-dense md-list-item.md-3-line .md-list-item-text p,md-list.md-dense md-list-item.md-3-line>.md-no-style .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line>.md-no-style .md-list-item-text h4,md-list.md-dense md-list-item.md-3-line>.md-no-style .md-list-item-text p{line-height:1.05;font-size:12px}md-list.md-dense md-list-item.md-2-line .md-list-item-text h3,md-list.md-dense md-list-item.md-2-line>.md-no-style .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line>.md-no-style .md-list-item-text h3{font-size:13px}md-list.md-dense md-list-item.md-2-line,md-list.md-dense md-list-item.md-2-line>.md-no-style{min-height:60px}md-list.md-dense md-list-item.md-2-line:before,md-list.md-dense md-list-item.md-2-line>.md-no-style:before{content:'';min-height:60px;visibility:hidden;display:inline-block}md-list.md-dense md-list-item.md-2-line .md-avatar-icon,md-list.md-dense md-list-item.md-2-line>.md-avatar,md-list.md-dense md-list-item.md-2-line>.md-no-style .md-avatar-icon,md-list.md-dense md-list-item.md-2-line>.md-no-style>.md-avatar{margin-top:12px}md-list.md-dense md-list-item.md-3-line,md-list.md-dense md-list-item.md-3-line>.md-no-style{min-height:76px}md-list.md-dense md-list-item.md-3-line:before,md-list.md-dense md-list-item.md-3-line>.md-no-style:before{content:'';min-height:76px;visibility:hidden;display:inline-block}md-list.md-dense md-list-item.md-3-line>.md-avatar,md-list.md-dense md-list-item.md-3-line>.md-no-style>.md-avatar,md-list.md-dense md-list-item.md-3-line>.md-no-style>md-icon:first-child,md-list.md-dense md-list-item.md-3-line>md-icon:first-child{margin-top:16px}md-list-item{position:relative}md-list-item.md-proxy-focus.md-focused .md-no-style{-webkit-transition:background-color .15s linear;transition:background-color .15s linear}md-list-item._md-button-wrap{position:relative}md-list-item._md-button-wrap>div.md-button:first-child{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;padding:0 16px;margin:0;font-weight:400;text-align:left;border:medium none}[dir=rtl] md-list-item._md-button-wrap>div.md-button:first-child{text-align:right}md-list-item._md-button-wrap>div.md-button:first-child>.md-button:first-child{position:absolute;top:0;left:0;height:100%;margin:0;padding:0}md-list-item._md-button-wrap>div.md-button:first-child .md-list-item-inner{width:100%;min-height:inherit}md-list-item.md-no-proxy,md-list-item .md-no-style{position:relative;padding:0 16px;-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto}md-list-item.md-no-proxy.md-button,md-list-item .md-no-style.md-button{font-size:inherit;height:inherit;text-align:left;text-transform:none;width:100%;white-space:normal;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:inherit;flex-direction:inherit;-webkit-box-align:inherit;-webkit-align-items:inherit;-ms-grid-row-align:inherit;align-items:inherit;border-radius:0;margin:0}[dir=rtl] md-list-item.md-no-proxy.md-button,[dir=rtl] md-list-item .md-no-style.md-button{text-align:right}md-list-item.md-no-proxy.md-button>.md-ripple-container,md-list-item .md-no-style.md-button>.md-ripple-container{border-radius:0}md-list-item.md-no-proxy:focus,md-list-item .md-no-style:focus{outline:none}md-list-item.md-clickable:hover{cursor:pointer}md-list-item md-divider{position:absolute;bottom:0;left:0;width:100%}[dir=rtl] md-list-item md-divider{left:auto;right:0}md-list-item md-divider[md-inset]{left:72px;width:calc(100% - 72px);margin:0!important}[dir=rtl] md-list-item md-divider[md-inset]{left:auto;right:72px}md-list-item,md-list-item .md-list-item-inner{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;-webkit-box-align:center;-webkit-align-items:center;align-items:center;min-height:48px;height:auto}md-list-item .md-list-item-inner:before,md-list-item:before{content:'';min-height:48px;visibility:hidden;display:inline-block}md-list-item .md-list-item-inner>div.md-primary>md-icon:not(.md-avatar-icon),md-list-item .md-list-item-inner>div.md-secondary>md-icon:not(.md-avatar-icon),md-list-item .md-list-item-inner>md-icon.md-secondary:not(.md-avatar-icon),md-list-item .md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),md-list-item>div.md-primary>md-icon:not(.md-avatar-icon),md-list-item>div.md-secondary>md-icon:not(.md-avatar-icon),md-list-item>md-icon.md-secondary:not(.md-avatar-icon),md-list-item>md-icon:first-child:not(.md-avatar-icon){width:24px;margin-top:16px;margin-bottom:12px;box-sizing:content-box}md-list-item .md-list-item-inner>div.md-primary>md-checkbox,md-list-item .md-list-item-inner>div.md-secondary>md-checkbox,md-list-item .md-list-item-inner>md-checkbox,md-list-item .md-list-item-inner md-checkbox.md-secondary,md-list-item>div.md-primary>md-checkbox,md-list-item>div.md-secondary>md-checkbox,md-list-item>md-checkbox,md-list-item md-checkbox.md-secondary{-webkit-align-self:center;align-self:center}md-list-item .md-list-item-inner>div.md-primary>md-checkbox .md-label,md-list-item .md-list-item-inner>div.md-secondary>md-checkbox .md-label,md-list-item .md-list-item-inner>md-checkbox .md-label,md-list-item .md-list-item-inner md-checkbox.md-secondary .md-label,md-list-item>div.md-primary>md-checkbox .md-label,md-list-item>div.md-secondary>md-checkbox .md-label,md-list-item>md-checkbox .md-label,md-list-item md-checkbox.md-secondary .md-label{display:none}md-list-item .md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:32px}[dir=rtl] md-list-item .md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),[dir=rtl] md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:auto;margin-left:32px}md-list-item .md-avatar,md-list-item .md-avatar-icon,md-list-item .md-list-item-inner .md-avatar,md-list-item .md-list-item-inner .md-avatar-icon{margin-top:8px;margin-bottom:8px;margin-right:16px;border-radius:50%;box-sizing:content-box}[dir=rtl] md-list-item .md-avatar,[dir=rtl] md-list-item .md-avatar-icon,[dir=rtl] md-list-item .md-list-item-inner .md-avatar,[dir=rtl] md-list-item .md-list-item-inner .md-avatar-icon{margin-right:auto;margin-left:16px}md-list-item .md-avatar,md-list-item .md-list-item-inner .md-avatar{-webkit-box-flex:0;-webkit-flex:none;flex:none;width:40px;height:40px}md-list-item .md-avatar-icon,md-list-item .md-list-item-inner .md-avatar-icon{padding:8px}md-list-item .md-avatar-icon svg,md-list-item .md-list-item-inner .md-avatar-icon svg{width:24px;height:24px}md-list-item .md-list-item-inner>md-checkbox,md-list-item>md-checkbox{width:24px;margin-left:3px;margin-right:29px;margin-top:16px}[dir=rtl] md-list-item .md-list-item-inner>md-checkbox,[dir=rtl] md-list-item>md-checkbox{margin-left:29px;margin-right:3px}md-list-item .md-list-item-inner .md-secondary-container,md-list-item .md-secondary-container{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;-webkit-flex-shrink:0;flex-shrink:0;margin:auto;margin-right:0;margin-left:auto}[dir=rtl] md-list-item .md-list-item-inner .md-secondary-container,[dir=rtl] md-list-item .md-secondary-container{margin-right:auto;margin-left:0}md-list-item .md-list-item-inner .md-secondary-container .md-button:last-of-type,md-list-item .md-list-item-inner .md-secondary-container .md-icon-button:last-of-type,md-list-item .md-secondary-container .md-button:last-of-type,md-list-item .md-secondary-container .md-icon-button:last-of-type{margin-right:0}[dir=rtl] md-list-item .md-list-item-inner .md-secondary-container .md-button:last-of-type,[dir=rtl] md-list-item .md-list-item-inner .md-secondary-container .md-icon-button:last-of-type,[dir=rtl] md-list-item .md-secondary-container .md-button:last-of-type,[dir=rtl] md-list-item .md-secondary-container .md-icon-button:last-of-type{margin-right:auto;margin-left:0}md-list-item .md-list-item-inner .md-secondary-container md-checkbox,md-list-item .md-secondary-container md-checkbox{margin-top:0;margin-bottom:0}md-list-item .md-list-item-inner .md-secondary-container md-checkbox:last-child,md-list-item .md-secondary-container md-checkbox:last-child{width:24px;margin-right:0}[dir=rtl] md-list-item .md-list-item-inner .md-secondary-container md-checkbox:last-child,[dir=rtl] md-list-item .md-secondary-container md-checkbox:last-child{margin-right:auto;margin-left:0}md-list-item .md-list-item-inner .md-secondary-container md-switch,md-list-item .md-secondary-container md-switch{margin-top:0;margin-bottom:0;margin-right:-6px}[dir=rtl] md-list-item .md-list-item-inner .md-secondary-container md-switch,[dir=rtl] md-list-item .md-secondary-container md-switch{margin-right:auto;margin-left:-6px}md-list-item .md-list-item-inner>.md-list-item-inner>p,md-list-item .md-list-item-inner>p,md-list-item>.md-list-item-inner>p,md-list-item>p{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;margin:0}md-list-item.md-2-line,md-list-item.md-2-line>.md-no-style,md-list-item.md-3-line,md-list-item.md-3-line>.md-no-style{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}md-list-item.md-2-line.md-long-text,md-list-item.md-2-line>.md-no-style.md-long-text,md-list-item.md-3-line.md-long-text,md-list-item.md-3-line>.md-no-style.md-long-text{margin-top:8px;margin-bottom:8px}md-list-item.md-2-line .md-list-item-text,md-list-item.md-2-line>.md-no-style .md-list-item-text,md-list-item.md-3-line .md-list-item-text,md-list-item.md-3-line>.md-no-style .md-list-item-text{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;margin:auto;text-overflow:ellipsis;overflow:hidden}md-list-item.md-2-line .md-list-item-text.md-offset,md-list-item.md-2-line>.md-no-style .md-list-item-text.md-offset,md-list-item.md-3-line .md-list-item-text.md-offset,md-list-item.md-3-line>.md-no-style .md-list-item-text.md-offset{margin-left:56px}[dir=rtl] md-list-item.md-2-line .md-list-item-text.md-offset,[dir=rtl] md-list-item.md-2-line>.md-no-style .md-list-item-text.md-offset,[dir=rtl] md-list-item.md-3-line .md-list-item-text.md-offset,[dir=rtl] md-list-item.md-3-line>.md-no-style .md-list-item-text.md-offset{margin-left:auto;margin-right:56px}md-list-item.md-2-line .md-list-item-text h3,md-list-item.md-2-line>.md-no-style .md-list-item-text h3,md-list-item.md-3-line .md-list-item-text h3,md-list-item.md-3-line>.md-no-style .md-list-item-text h3{font-size:16px;font-weight:400;letter-spacing:.01em;margin:0;line-height:1.2em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}md-list-item.md-2-line .md-list-item-text h4,md-list-item.md-2-line>.md-no-style .md-list-item-text h4,md-list-item.md-3-line .md-list-item-text h4,md-list-item.md-3-line>.md-no-style .md-list-item-text h4{font-size:14px;letter-spacing:.01em;margin:3px 0 1px;font-weight:400;line-height:1.2em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}md-list-item.md-2-line .md-list-item-text p,md-list-item.md-2-line>.md-no-style .md-list-item-text p,md-list-item.md-3-line .md-list-item-text p,md-list-item.md-3-line>.md-no-style .md-list-item-text p{font-size:14px;font-weight:500;letter-spacing:.01em;margin:0;line-height:1.6em}md-list-item.md-2-line,md-list-item.md-2-line>.md-no-style{height:auto;min-height:72px}md-list-item.md-2-line:before,md-list-item.md-2-line>.md-no-style:before{content:'';min-height:72px;visibility:hidden;display:inline-block}md-list-item.md-2-line .md-avatar-icon,md-list-item.md-2-line>.md-avatar,md-list-item.md-2-line>.md-no-style .md-avatar-icon,md-list-item.md-2-line>.md-no-style>.md-avatar{margin-top:12px}md-list-item.md-2-line>.md-no-style>md-icon:first-child,md-list-item.md-2-line>md-icon:first-child{-webkit-align-self:flex-start;align-self:flex-start}md-list-item.md-2-line .md-list-item-text,md-list-item.md-2-line>.md-no-style .md-list-item-text{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto}md-list-item.md-3-line,md-list-item.md-3-line>.md-no-style{height:auto;min-height:88px}md-list-item.md-3-line:before,md-list-item.md-3-line>.md-no-style:before{content:'';min-height:88px;visibility:hidden;display:inline-block}md-list-item.md-3-line>.md-avatar,md-list-item.md-3-line>.md-no-style>.md-avatar,md-list-item.md-3-line>.md-no-style>md-icon:first-child,md-list-item.md-3-line>md-icon:first-child{margin-top:16px}.md-open-menu-container{position:fixed;left:0;top:0;z-index:100;opacity:0;border-radius:2px}.md-open-menu-container md-menu-divider{margin-top:4px;margin-bottom:4px;height:1px;min-height:1px;max-height:1px;width:100%}.md-open-menu-container md-menu-content>*{opacity:0}.md-open-menu-container:not(.md-clickable){pointer-events:none}.md-open-menu-container.md-active{opacity:1;-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-duration:.2s;transition-duration:.2s}.md-open-menu-container.md-active>md-menu-content>*{opacity:1;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-delay:.1s;transition-delay:.1s}.md-open-menu-container.md-leave{opacity:0;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-duration:.25s;transition-duration:.25s}md-menu-content{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;padding:8px 0;max-height:304px;overflow-y:auto}md-menu-content.md-dense{max-height:208px}md-menu-content.md-dense md-menu-item{height:32px;min-height:0}md-menu-item{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row;min-height:48px;height:48px;-webkit-align-content:center;align-content:center;-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}md-menu-item>*{width:100%;margin:auto 0;padding-left:16px;padding-right:16px}md-menu-item>a.md-button{padding-top:5px}md-menu-item>.md-button{text-align:left;display:inline-block;border-radius:0;margin:auto 0;font-size:15px;text-transform:none;font-weight:400;height:100%;padding-left:16px;padding-right:16px;width:100%}md-menu-item>.md-button::-moz-focus-inner{padding:0;border:0}[dir=rtl] md-menu-item>.md-button{text-align:right}md-menu-item>.md-button md-icon{margin:auto 16px auto 0}[dir=rtl] md-menu-item>.md-button md-icon{margin:auto 0 auto 16px}md-menu-item>.md-button p{display:inline-block;margin:auto}md-menu-item>.md-button span{margin-top:auto;margin-bottom:auto}md-menu-item>.md-button .md-ripple-container{border-radius:inherit}md-toolbar .md-menu{height:auto;margin:auto;padding:0}@media (max-width:959px){md-menu-content{min-width:112px}md-menu-content[width="3"]{min-width:168px}md-menu-content[width="4"]{min-width:224px}md-menu-content[width="5"]{min-width:280px}md-menu-content[width="6"]{min-width:336px}md-menu-content[width="7"]{min-width:392px}}@media (min-width:960px){md-menu-content{min-width:96px}md-menu-content[width="3"]{min-width:192px}md-menu-content[width="4"]{min-width:256px}md-menu-content[width="5"]{min-width:320px}md-menu-content[width="6"]{min-width:384px}md-menu-content[width="7"]{min-width:448px}}md-toolbar.md-menu-toolbar h2.md-toolbar-tools{line-height:1rem;height:auto;padding:28px;padding-bottom:12px}md-toolbar.md-has-open-menu{position:relative;z-index:100}md-menu-bar{padding:0 20px;display:block;position:relative;z-index:2}md-menu-bar .md-menu{display:inline-block;padding:0;position:relative}md-menu-bar button{font-size:14px;padding:0 10px;margin:0;border:0;background-color:transparent;height:40px}md-menu-bar md-backdrop.md-menu-backdrop{z-index:-2}md-menu-content.md-menu-bar-menu.md-dense{max-height:none;padding:16px 0}md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent{position:relative}md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent>md-icon{position:absolute;padding:0;width:24px;top:6px;left:24px}[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent>md-icon{left:auto;right:24px}md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent .md-menu>.md-button,md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent>.md-button{padding:0 32px 0 64px}[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent .md-menu>.md-button,[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense md-menu-item.md-indent>.md-button{padding:0 64px 0 32px}md-menu-content.md-menu-bar-menu.md-dense .md-button{min-height:0;height:32px;display:-webkit-box;display:-webkit-flex;display:flex}md-menu-content.md-menu-bar-menu.md-dense .md-button span{-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}md-menu-content.md-menu-bar-menu.md-dense .md-button span.md-alt-text{-webkit-box-flex:0;-webkit-flex-grow:0;flex-grow:0;-webkit-align-self:flex-end;align-self:flex-end;margin:0 8px}md-menu-content.md-menu-bar-menu.md-dense md-menu-divider{margin:8px 0}md-menu-content.md-menu-bar-menu.md-dense .md-menu>.md-button,md-menu-content.md-menu-bar-menu.md-dense md-menu-item>.md-button{text-align:left}[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense .md-menu>.md-button,[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense md-menu-item>.md-button{text-align:right}md-menu-content.md-menu-bar-menu.md-dense .md-menu{padding:0}md-menu-content.md-menu-bar-menu.md-dense .md-menu>.md-button{position:relative;margin:0;width:100%;text-transform:none;font-weight:400;border-radius:0;padding-left:16px}[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense .md-menu>.md-button{padding-left:0;padding-right:16px}md-menu-content.md-menu-bar-menu.md-dense .md-menu>.md-button:after{display:block;content:'\25BC';position:absolute;top:0;speak:none;-webkit-transform:rotate(270deg) scaleY(.45) scaleX(.9);transform:rotate(270deg) scaleY(.45) scaleX(.9);right:28px}[dir=rtl] md-menu-content.md-menu-bar-menu.md-dense .md-menu>.md-button:after{-webkit-transform:rotate(90deg) scaleY(.45) scaleX(.9);transform:rotate(90deg) scaleY(.45) scaleX(.9);right:auto;left:28px}.md-nav-bar{border-style:solid;border-width:0 0 1px;height:48px;position:relative}._md-nav-bar-list{outline:none;list-style:none;margin:0;padding:0;box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}.md-nav-item:first-of-type{margin-left:8px}.md-button._md-nav-button{line-height:24px;margin:0 4px;padding:12px 16px;-webkit-transition:background-color .35s cubic-bezier(.35,0,.25,1);transition:background-color .35s cubic-bezier(.35,0,.25,1)}.md-button._md-nav-button:focus{outline:none}.md-button._md-nav-button:hover{background-color:inherit}md-nav-ink-bar{bottom:0;height:2px;left:auto;position:absolute;right:auto;background-color:#000}md-nav-ink-bar._md-left{-webkit-transition:left .125s cubic-bezier(.35,0,.25,1),right .25s cubic-bezier(.35,0,.25,1);transition:left .125s cubic-bezier(.35,0,.25,1),right .25s cubic-bezier(.35,0,.25,1)}md-nav-ink-bar._md-right{-webkit-transition:left .25s cubic-bezier(.35,0,.25,1),right .125s cubic-bezier(.35,0,.25,1);transition:left .25s cubic-bezier(.35,0,.25,1),right .125s cubic-bezier(.35,0,.25,1)}md-nav-extra-content{min-height:48px;padding-right:12px}.md-panel-outer-wrapper{height:100%;left:0;position:absolute;top:0;width:100%}._md-panel-hidden{display:none}._md-panel-fullscreen{border-radius:0;left:0;min-height:100%;min-width:100%;position:fixed;top:0}._md-panel-shown .md-panel{opacity:1;-webkit-transition:none;transition:none}.md-panel{opacity:0;position:fixed}.md-panel._md-panel-shown{opacity:1;-webkit-transition:none;transition:none}.md-panel._md-panel-animate-enter{opacity:1;-webkit-transition:all .3s cubic-bezier(0,0,.2,1);transition:all .3s cubic-bezier(0,0,.2,1)}.md-panel._md-panel-animate-leave{opacity:1;-webkit-transition:all .3s cubic-bezier(.4,0,1,1);transition:all .3s cubic-bezier(.4,0,1,1)}.md-panel._md-panel-animate-fade-out,.md-panel._md-panel-animate-scale-out{opacity:0}.md-panel._md-panel-backdrop{height:100%;position:absolute;width:100%}.md-panel._md-opaque-enter{opacity:.48;-webkit-transition:opacity .3s cubic-bezier(0,0,.2,1);transition:opacity .3s cubic-bezier(0,0,.2,1)}.md-panel._md-opaque-leave{-webkit-transition:opacity .3s cubic-bezier(.4,0,1,1);transition:opacity .3s cubic-bezier(.4,0,1,1)}@-webkit-keyframes indeterminate-rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes indeterminate-rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}md-progress-circular{position:relative;display:block}md-progress-circular._md-progress-circular-disabled{visibility:hidden}md-progress-circular.md-mode-indeterminate svg{-webkit-animation:indeterminate-rotate 2.9s linear infinite;animation:indeterminate-rotate 2.9s linear infinite}md-progress-circular svg{position:absolute;overflow:visible;top:0;left:0}md-progress-linear{display:block;position:relative;width:100%;height:5px;padding-top:0!important;margin-bottom:0!important}md-progress-linear._md-progress-linear-disabled{visibility:hidden}md-progress-linear .md-container{display:block;position:relative;overflow:hidden;width:100%;height:5px;-webkit-transform:translate(0,0) scale(1,1);transform:translate(0,0) scale(1,1)}md-progress-linear .md-container .md-bar{position:absolute;left:0;top:0;bottom:0;width:100%;height:5px}md-progress-linear .md-container .md-dashed:before{content:"";display:none;position:absolute;margin-top:0;height:5px;width:100%;background-color:transparent;background-size:10px 10px!important;background-position:0 -23px}md-progress-linear .md-container .md-bar1,md-progress-linear .md-container .md-bar2{-webkit-transition:-webkit-transform .2s linear;transition:-webkit-transform .2s linear;transition:transform .2s linear;transition:transform .2s linear,-webkit-transform .2s linear}md-progress-linear .md-container.md-mode-query .md-bar1{display:none}md-progress-linear .md-container.md-mode-query .md-bar2{-webkit-transition:all .2s linear;transition:all .2s linear;-webkit-animation:query .8s infinite cubic-bezier(.39,.575,.565,1);animation:query .8s infinite cubic-bezier(.39,.575,.565,1)}md-progress-linear .md-container.md-mode-determinate .md-bar1{display:none}md-progress-linear .md-container.md-mode-indeterminate .md-bar1{-webkit-animation:md-progress-linear-indeterminate-scale-1 4s infinite,md-progress-linear-indeterminate-1 4s infinite;animation:md-progress-linear-indeterminate-scale-1 4s infinite,md-progress-linear-indeterminate-1 4s infinite}md-progress-linear .md-container.md-mode-indeterminate .md-bar2{-webkit-animation:md-progress-linear-indeterminate-scale-2 4s infinite,md-progress-linear-indeterminate-2 4s infinite;animation:md-progress-linear-indeterminate-scale-2 4s infinite,md-progress-linear-indeterminate-2 4s infinite}md-progress-linear .md-container.ng-hide ._md-progress-linear-disabled md-progress-linear .md-container{-webkit-animation:none;animation:none}md-progress-linear .md-container.ng-hide ._md-progress-linear-disabled md-progress-linear .md-container .md-bar1,md-progress-linear .md-container.ng-hide ._md-progress-linear-disabled md-progress-linear .md-container .md-bar2{-webkit-animation-name:none;animation-name:none}md-progress-linear .md-container.md-mode-buffer{background-color:transparent!important;-webkit-transition:all .2s linear;transition:all .2s linear}md-progress-linear .md-container.md-mode-buffer .md-dashed:before{display:block;-webkit-animation:buffer 3s infinite linear;animation:buffer 3s infinite linear}@-webkit-keyframes query{0%{opacity:1;-webkit-transform:translateX(35%) scale(.3,1);transform:translateX(35%) scale(.3,1)}to{opacity:0;-webkit-transform:translateX(-50%) scale(0,1);transform:translateX(-50%) scale(0,1)}}@keyframes query{0%{opacity:1;-webkit-transform:translateX(35%) scale(.3,1);transform:translateX(35%) scale(.3,1)}to{opacity:0;-webkit-transform:translateX(-50%) scale(0,1);transform:translateX(-50%) scale(0,1)}}@-webkit-keyframes buffer{0%{opacity:1;background-position:0 -23px}50%{opacity:0}to{opacity:1;background-position:-200px -23px}}@keyframes buffer{0%{opacity:1;background-position:0 -23px}50%{opacity:0}to{opacity:1;background-position:-200px -23px}}@-webkit-keyframes md-progress-linear-indeterminate-scale-1{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:linear;animation-timing-function:linear}36.6%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.33473,.12482,.78584,1);animation-timing-function:cubic-bezier(.33473,.12482,.78584,1)}69.15%{-webkit-transform:scaleX(.83);transform:scaleX(.83);-webkit-animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098);animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098)}to{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@keyframes md-progress-linear-indeterminate-scale-1{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:linear;animation-timing-function:linear}36.6%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.33473,.12482,.78584,1);animation-timing-function:cubic-bezier(.33473,.12482,.78584,1)}69.15%{-webkit-transform:scaleX(.83);transform:scaleX(.83);-webkit-animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098);animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098)}to{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@-webkit-keyframes md-progress-linear-indeterminate-1{0%{left:-105.16667%;-webkit-animation-timing-function:linear;animation-timing-function:linear}20%{left:-105.16667%;-webkit-animation-timing-function:cubic-bezier(.5,0,.70173,.49582);animation-timing-function:cubic-bezier(.5,0,.70173,.49582)}69.15%{left:21.5%;-webkit-animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635);animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635)}to{left:95.44444%}}@keyframes md-progress-linear-indeterminate-1{0%{left:-105.16667%;-webkit-animation-timing-function:linear;animation-timing-function:linear}20%{left:-105.16667%;-webkit-animation-timing-function:cubic-bezier(.5,0,.70173,.49582);animation-timing-function:cubic-bezier(.5,0,.70173,.49582)}69.15%{left:21.5%;-webkit-animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635);animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635)}to{left:95.44444%}}@-webkit-keyframes md-progress-linear-indeterminate-scale-2{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397);animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397)}19.15%{-webkit-transform:scaleX(.57);transform:scaleX(.57);-webkit-animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432);animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432)}44.15%{-webkit-transform:scaleX(.91);transform:scaleX(.91);-webkit-animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179);animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179)}to{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@keyframes md-progress-linear-indeterminate-scale-2{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397);animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397)}19.15%{-webkit-transform:scaleX(.57);transform:scaleX(.57);-webkit-animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432);animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432)}44.15%{-webkit-transform:scaleX(.91);transform:scaleX(.91);-webkit-animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179);animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179)}to{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@-webkit-keyframes md-progress-linear-indeterminate-2{0%{left:-54.88889%;-webkit-animation-timing-function:cubic-bezier(.15,0,.51506,.40968);animation-timing-function:cubic-bezier(.15,0,.51506,.40968)}25%{left:-17.25%;-webkit-animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372);animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372)}48.35%{left:29.5%;-webkit-animation-timing-function:cubic-bezier(.4,.62703,.6,.90203);animation-timing-function:cubic-bezier(.4,.62703,.6,.90203)}to{left:117.38889%}}@keyframes md-progress-linear-indeterminate-2{0%{left:-54.88889%;-webkit-animation-timing-function:cubic-bezier(.15,0,.51506,.40968);animation-timing-function:cubic-bezier(.15,0,.51506,.40968)}25%{left:-17.25%;-webkit-animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372);animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372)}48.35%{left:29.5%;-webkit-animation-timing-function:cubic-bezier(.4,.62703,.6,.90203);animation-timing-function:cubic-bezier(.4,.62703,.6,.90203)}to{left:117.38889%}}md-radio-button{box-sizing:border-box;display:block;margin-bottom:16px;white-space:nowrap;cursor:pointer;position:relative}md-radio-button[disabled],md-radio-button[disabled] .md-container{cursor:default}md-radio-button .md-container{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);box-sizing:border-box;display:inline-block;width:20px;height:20px;cursor:pointer;left:0;right:auto}[dir=rtl] md-radio-button .md-container{left:auto;right:0}md-radio-button .md-container .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-15px;top:-15px;right:-15px;bottom:-15px}md-radio-button .md-container:before{box-sizing:border-box;background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;-webkit-transition:all .5s;transition:all .5s;width:auto}md-radio-button.md-align-top-left>div.md-container{top:12px}md-radio-button .md-off{border-style:solid;border-width:2px;-webkit-transition:border-color .28s ease;transition:border-color .28s ease}md-radio-button .md-off,md-radio-button .md-on{box-sizing:border-box;position:absolute;top:0;left:0;width:20px;height:20px;border-radius:50%}md-radio-button .md-on{-webkit-transition:-webkit-transform .28s ease;transition:-webkit-transform .28s ease;transition:transform .28s ease;transition:transform .28s ease,-webkit-transform .28s ease;-webkit-transform:scale(0);transform:scale(0)}md-radio-button.md-checked .md-on{-webkit-transform:scale(.5);transform:scale(.5)}md-radio-button .md-label{box-sizing:border-box;position:relative;display:inline-block;margin-left:30px;margin-right:0;vertical-align:middle;white-space:normal;pointer-events:none;width:auto}[dir=rtl] md-radio-button .md-label{margin-left:0;margin-right:30px}md-radio-group.layout-column md-radio-button,md-radio-group.layout-gt-lg-column md-radio-button,md-radio-group.layout-gt-md-column md-radio-button,md-radio-group.layout-gt-sm-column md-radio-button,md-radio-group.layout-gt-xs-column md-radio-button,md-radio-group.layout-lg-column md-radio-button,md-radio-group.layout-md-column md-radio-button,md-radio-group.layout-sm-column md-radio-button,md-radio-group.layout-xl-column md-radio-button,md-radio-group.layout-xs-column md-radio-button{margin-bottom:16px}md-radio-group.layout-gt-lg-row md-radio-button,md-radio-group.layout-gt-md-row md-radio-button,md-radio-group.layout-gt-sm-row md-radio-button,md-radio-group.layout-gt-xs-row md-radio-button,md-radio-group.layout-lg-row md-radio-button,md-radio-group.layout-md-row md-radio-button,md-radio-group.layout-row md-radio-button,md-radio-group.layout-sm-row md-radio-button,md-radio-group.layout-xl-row md-radio-button,md-radio-group.layout-xs-row md-radio-button{margin:0 16px 0 0}[dir=rtl] md-radio-group.layout-gt-lg-row md-radio-button,[dir=rtl] md-radio-group.layout-gt-md-row md-radio-button,[dir=rtl] md-radio-group.layout-gt-sm-row md-radio-button,[dir=rtl] md-radio-group.layout-gt-xs-row md-radio-button,[dir=rtl] md-radio-group.layout-lg-row md-radio-button,[dir=rtl] md-radio-group.layout-md-row md-radio-button,[dir=rtl] md-radio-group.layout-row md-radio-button,[dir=rtl] md-radio-group.layout-sm-row md-radio-button,[dir=rtl] md-radio-group.layout-xl-row md-radio-button,[dir=rtl] md-radio-group.layout-xs-row md-radio-button{margin-left:16px;margin-right:0}md-radio-group.layout-gt-lg-row md-radio-button:last-of-type,md-radio-group.layout-gt-md-row md-radio-button:last-of-type,md-radio-group.layout-gt-sm-row md-radio-button:last-of-type,md-radio-group.layout-gt-xs-row md-radio-button:last-of-type,md-radio-group.layout-lg-row md-radio-button:last-of-type,md-radio-group.layout-md-row md-radio-button:last-of-type,md-radio-group.layout-row md-radio-button:last-of-type,md-radio-group.layout-sm-row md-radio-button:last-of-type,md-radio-group.layout-xl-row md-radio-button:last-of-type,md-radio-group.layout-xs-row md-radio-button:last-of-type{margin-left:0;margin-right:0}md-radio-group:focus{outline:none}md-radio-group.md-focused .md-checked .md-container:before{left:-8px;top:-8px;right:-8px;bottom:-8px}md-radio-group[disabled] md-radio-button,md-radio-group[disabled] md-radio-button .md-container{cursor:default}.md-inline-form md-radio-group{margin:18px 0 19px}.md-inline-form md-radio-group md-radio-button{display:inline-block;height:30px;padding:2px;box-sizing:border-box;margin-top:0;margin-bottom:0}@media screen and (-ms-high-contrast:active){md-radio-button.md-default-theme .md-on{background-color:#fff}}md-input-container:not([md-no-float]) .md-select-placeholder span:first-child{-webkit-transition:-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1),-webkit-transform .4s cubic-bezier(.25,.8,.25,1);-webkit-transform-origin:left top;transform-origin:left top}[dir=rtl] md-input-container:not([md-no-float]) .md-select-placeholder span:first-child{-webkit-transform-origin:right top;transform-origin:right top}md-input-container.md-input-focused:not([md-no-float]) .md-select-placeholder span:first-child{-webkit-transform:translateY(-22px) translateX(-2px) scale(.75);transform:translateY(-22px) translateX(-2px) scale(.75)}.md-select-menu-container{position:fixed;left:0;top:0;z-index:90;opacity:0;display:none;-webkit-transform:translateY(-1px);transform:translateY(-1px)}.md-select-menu-container:not(.md-clickable){pointer-events:none}.md-select-menu-container md-progress-circular{display:table;margin:24px auto!important}.md-select-menu-container.md-active{display:block;opacity:1}.md-select-menu-container.md-active md-select-menu{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-duration:.15s;transition-duration:.15s}.md-select-menu-container.md-active md-select-menu>*{opacity:1;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-duration:.15s;transition-duration:.15s;-webkit-transition-delay:.1s;transition-delay:.1s}.md-select-menu-container.md-leave{opacity:0;-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2);-webkit-transition-duration:.25s;transition-duration:.25s}md-input-container>md-select{margin:0;-webkit-box-ordinal-group:3;-webkit-order:2;order:2}md-input-container:not(.md-input-has-value) md-select.ng-required:not(.md-no-asterisk) .md-select-value span:first-child:after,md-input-container:not(.md-input-has-value) md-select[required]:not(.md-no-asterisk) .md-select-value span:first-child:after{content:' *';font-size:13px;vertical-align:top}md-input-container.md-input-invalid md-select .md-select-value{border-bottom-style:solid;padding-bottom:1px}md-select{display:-webkit-box;display:-webkit-flex;display:flex;margin:20px 0 26px}md-select.ng-required.ng-invalid:not(.md-no-asterisk) .md-select-value span:first-child:after,md-select[required].ng-invalid:not(.md-no-asterisk) .md-select-value span:first-child:after{content:' *';font-size:13px;vertical-align:top}md-select[disabled] .md-select-value{background-position:0 bottom;background-size:4px 1px;background-repeat:repeat-x;margin-bottom:-1px}md-select:focus{outline:none}md-select[disabled]:hover{cursor:default}md-select:not([disabled]):hover{cursor:pointer}md-select:not([disabled]).ng-invalid.ng-touched .md-select-value{border-bottom-style:solid;padding-bottom:1px}md-select:not([disabled]):focus .md-select-value{border-bottom-width:2px;border-bottom-style:solid;padding-bottom:0}md-select:not([disabled]):focus.ng-invalid.ng-touched .md-select-value{padding-bottom:0}md-input-container.md-input-has-value .md-select-value>span:not(.md-select-icon){-webkit-transform:translate3d(0,1px,0);transform:translate3d(0,1px,0)}.md-select-value{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;padding:2px 2px 1px;border-bottom-width:1px;border-bottom-style:solid;background-color:transparent;position:relative;box-sizing:content-box;min-width:64px;min-height:26px;-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}.md-select-value>span:not(.md-select-icon){max-width:100%;-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-select-value>span:not(.md-select-icon) .md-text{display:inline}.md-select-value .md-select-icon{display:block;-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;text-align:end;width:24px;margin:0 4px;-webkit-transform:translate3d(0,-2px,0);transform:translate3d(0,-2px,0);font-size:1.2rem}.md-select-value .md-select-icon:after{display:block;content:'\25BC';position:relative;top:2px;speak:none;font-size:13px;-webkit-transform:scaleY(.5) scaleX(1);transform:scaleY(.5) scaleX(1)}.md-select-value.md-select-placeholder{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-ordinal-group:2;-webkit-order:1;order:1;pointer-events:none;-webkit-font-smoothing:antialiased;padding-left:2px;z-index:1}md-select-menu{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12);max-height:256px;min-height:48px;overflow-y:hidden;-webkit-transform-origin:left top;transform-origin:left top;-webkit-transform:scale(1);transform:scale(1)}md-select-menu.md-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;flex-direction:column-reverse}md-select-menu:not(.md-overflow) md-content{padding-top:8px;padding-bottom:8px}[dir=rtl] md-select-menu{-webkit-transform-origin:right top;transform-origin:right top}md-select-menu md-content{min-width:136px;min-height:48px;max-height:256px;overflow-y:auto}md-select-menu>*{opacity:0}md-option{cursor:pointer;position:relative;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;width:auto;-webkit-transition:background .15s linear;transition:background .15s linear;padding:0 16px;height:48px}md-option[disabled]{cursor:default}md-option:focus{outline:none}md-option .md-text{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}md-optgroup{display:block}md-optgroup label{display:block;font-size:14px;text-transform:uppercase;padding:16px;font-weight:500}md-optgroup md-option{padding-left:32px;padding-right:32px}@media screen and (-ms-high-contrast:active){.md-select-backdrop{background-color:transparent}md-select-menu{border:1px solid #fff}}md-select-menu[multiple] md-option.md-checkbox-enabled{padding-left:40px;padding-right:16px}[dir=rtl] md-select-menu[multiple] md-option.md-checkbox-enabled{padding-left:16px;padding-right:40px}md-select-menu[multiple] md-option.md-checkbox-enabled .md-container{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);box-sizing:border-box;display:inline-block;width:20px;height:20px;left:0;right:auto}[dir=rtl] md-select-menu[multiple] md-option.md-checkbox-enabled .md-container{left:auto;right:0}md-select-menu[multiple] md-option.md-checkbox-enabled .md-container:before{box-sizing:border-box;background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;-webkit-transition:all .5s;transition:all .5s;width:auto}md-select-menu[multiple] md-option.md-checkbox-enabled .md-container:after{box-sizing:border-box;content:'';position:absolute;top:-10px;right:-10px;bottom:-10px;left:-10px}md-select-menu[multiple] md-option.md-checkbox-enabled .md-container .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-15px;top:-15px;right:-15px;bottom:-15px}md-select-menu[multiple] md-option.md-checkbox-enabled .md-icon{box-sizing:border-box;-webkit-transition:.24s;transition:.24s;position:absolute;top:0;left:0;width:20px;height:20px;border-width:2px;border-style:solid;border-radius:2px}md-select-menu[multiple] md-option.md-checkbox-enabled[selected] .md-icon{border-color:transparent}md-select-menu[multiple] md-option.md-checkbox-enabled[selected] .md-icon:after{box-sizing:border-box;-webkit-transform:rotate(45deg);transform:rotate(45deg);position:absolute;left:4.66667px;top:.22222px;display:table;width:6.66667px;height:13.33333px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-select-menu[multiple] md-option.md-checkbox-enabled[disabled]{cursor:default}md-select-menu[multiple] md-option.md-checkbox-enabled.md-indeterminate .md-icon:after{box-sizing:border-box;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:table;width:12px;height:2px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-select-menu[multiple] md-option.md-checkbox-enabled .md-container{margin-left:10.66667px;margin-right:auto}[dir=rtl] md-select-menu[multiple] md-option.md-checkbox-enabled .md-container{margin-left:auto;margin-right:10.66667px}md-sidenav{box-sizing:border-box;position:absolute;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;z-index:60;width:320px;max-width:320px;bottom:0;overflow:auto;-webkit-overflow-scrolling:touch}md-sidenav ul{list-style:none}md-sidenav.md-closed{display:none}md-sidenav.md-closed-add,md-sidenav.md-closed-remove{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-transition:all .2s ease-in;transition:all .2s ease-in}md-sidenav.md-closed-add.md-closed-add-active,md-sidenav.md-closed-remove.md-closed-remove-active{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1)}md-sidenav.md-locked-open,md-sidenav.md-locked-open-add,md-sidenav.md-locked-open-remove,md-sidenav.md-locked-open-remove.md-closed,md-sidenav.md-locked-open.md-closed,md-sidenav.md-locked-open.md-closed.md-sidenav-left,md-sidenav.md-locked-open.md-closed.md-sidenav-right{position:static;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-sidenav.md-locked-open-remove-active{-webkit-transition:width .3s cubic-bezier(.55,0,.55,.2),min-width .3s cubic-bezier(.55,0,.55,.2);transition:width .3s cubic-bezier(.55,0,.55,.2),min-width .3s cubic-bezier(.55,0,.55,.2);width:0!important;min-width:0!important}md-sidenav.md-closed.md-locked-open-add{width:0!important;min-width:0!important;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-sidenav.md-closed.md-locked-open-add-active{-webkit-transition:width .3s cubic-bezier(.55,0,.55,.2),min-width .3s cubic-bezier(.55,0,.55,.2);transition:width .3s cubic-bezier(.55,0,.55,.2),min-width .3s cubic-bezier(.55,0,.55,.2);width:320px;min-width:320px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.md-sidenav-backdrop.md-locked-open{display:none}.md-sidenav-left,md-sidenav{left:0;top:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.md-sidenav-left.md-closed,md-sidenav.md-closed{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.md-sidenav-right{left:100%;top:0;-webkit-transform:translate(-100%,0);transform:translate(-100%,0)}.md-sidenav-right.md-closed{-webkit-transform:translate(0,0);transform:translate(0,0)}@media (min-width:600px){md-sidenav{max-width:400px}}@media (max-width:456px){md-sidenav{width:calc(100% - 56px);min-width:calc(100% - 56px);max-width:calc(100% - 56px)}}@media screen and (-ms-high-contrast:active){.md-sidenav-left,md-sidenav{border-right:1px solid #fff}.md-sidenav-right{border-left:1px solid #fff}}@-webkit-keyframes sliderFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1);transform:scale(1)}to{-webkit-transform:scale(.7);transform:scale(.7)}}@keyframes sliderFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1);transform:scale(1)}to{-webkit-transform:scale(.7);transform:scale(.7)}}@-webkit-keyframes sliderDiscreteFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}50%{-webkit-transform:scale(.8);transform:scale(.8)}to{-webkit-transform:scale(0);transform:scale(0)}}@keyframes sliderDiscreteFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}50%{-webkit-transform:scale(.8);transform:scale(.8)}to{-webkit-transform:scale(0);transform:scale(0)}}@-webkit-keyframes sliderDiscreteFocusRing{0%{-webkit-transform:scale(.7);transform:scale(.7);opacity:0}50%{-webkit-transform:scale(1);transform:scale(1);opacity:1}to{-webkit-transform:scale(0);transform:scale(0)}}@keyframes sliderDiscreteFocusRing{0%{-webkit-transform:scale(.7);transform:scale(.7);opacity:0}50%{-webkit-transform:scale(1);transform:scale(1);opacity:1}to{-webkit-transform:scale(0);transform:scale(0)}}md-slider{height:48px;min-width:128px;position:relative;margin-left:4px;margin-right:4px;padding:0;display:block;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-slider *,md-slider :after{box-sizing:border-box}md-slider .md-slider-wrapper{outline:none;width:100%;height:100%}md-slider .md-slider-content{position:relative}md-slider .md-track-container{width:100%;position:absolute;top:23px;height:2px}md-slider .md-track{position:absolute;left:0;right:0;height:100%}md-slider .md-track-fill{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-property:width,height;transition-property:width,height}md-slider .md-track-ticks{position:absolute;left:0;right:0;height:100%}md-slider .md-track-ticks canvas{width:100%;height:100%}md-slider .md-thumb-container{position:absolute;left:0;top:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0);-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-property:left,right,bottom;transition-property:left,right,bottom}[dir=rtl] md-slider .md-thumb-container{left:auto;right:0}md-slider .md-thumb{z-index:1;position:absolute;left:-10px;top:14px;width:20px;height:20px;border-radius:20px;-webkit-transform:scale(.7);transform:scale(.7);-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1)}[dir=rtl] md-slider .md-thumb{left:auto;right:-10px}md-slider .md-thumb:after{content:'';position:absolute;width:20px;height:20px;border-radius:20px;border-width:3px;border-style:solid;-webkit-transition:inherit;transition:inherit}md-slider .md-sign{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center;position:absolute;left:-14px;top:-17px;width:28px;height:28px;border-radius:28px;-webkit-transform:scale(.4) translate3d(0,67.5px,0);transform:scale(.4) translate3d(0,67.5px,0);-webkit-transition:all .3s cubic-bezier(.35,0,.25,1);transition:all .3s cubic-bezier(.35,0,.25,1)}md-slider .md-sign:after{position:absolute;content:'';left:0;border-radius:16px;top:19px;border-left:14px solid transparent;border-right:14px solid transparent;border-top-width:16px;border-top-style:solid;opacity:0;-webkit-transform:translate3d(0,-8px,0);transform:translate3d(0,-8px,0);-webkit-transition:all .2s cubic-bezier(.35,0,.25,1);transition:all .2s cubic-bezier(.35,0,.25,1)}[dir=rtl] md-slider .md-sign:after{left:auto;right:0}md-slider .md-sign .md-thumb-text{z-index:1;font-size:12px;font-weight:700}md-slider .md-focus-ring{position:absolute;left:-17px;top:7px;width:34px;height:34px;border-radius:34px;-webkit-transform:scale(.7);transform:scale(.7);opacity:0;-webkit-transition:all .35s cubic-bezier(.35,0,.25,1);transition:all .35s cubic-bezier(.35,0,.25,1)}[dir=rtl] md-slider .md-focus-ring{left:auto;right:-17px}md-slider .md-disabled-thumb{position:absolute;left:-14px;top:10px;width:28px;height:28px;border-radius:28px;-webkit-transform:scale(.5);transform:scale(.5);border-width:4px;border-style:solid;display:none}[dir=rtl] md-slider .md-disabled-thumb{left:auto;right:-14px}md-slider.md-min .md-sign{opacity:0}md-slider:focus{outline:none}md-slider.md-dragging .md-thumb-container,md-slider.md-dragging .md-track-fill{-webkit-transition:none;transition:none}md-slider:not([md-discrete]) .md-sign,md-slider:not([md-discrete]) .md-track-ticks{display:none}md-slider:not([md-discrete]):not([disabled]) .md-slider-wrapper .md-thumb:hover{-webkit-transform:scale(.8);transform:scale(.8)}md-slider:not([md-discrete]):not([disabled]) .md-slider-wrapper.md-focused .md-focus-ring{-webkit-transform:scale(1);transform:scale(1);opacity:1}md-slider:not([md-discrete]):not([disabled]) .md-slider-wrapper.md-focused .md-thumb{-webkit-animation:sliderFocusThumb .7s cubic-bezier(.35,0,.25,1);animation:sliderFocusThumb .7s cubic-bezier(.35,0,.25,1)}md-slider:not([md-discrete]):not([disabled]).md-active .md-slider-wrapper .md-thumb{-webkit-transform:scale(1);transform:scale(1)}md-slider[md-discrete]:not([disabled]) .md-slider-wrapper.md-focused .md-focus-ring{-webkit-transform:scale(0);transform:scale(0);-webkit-animation:sliderDiscreteFocusRing .5s cubic-bezier(.35,0,.25,1);animation:sliderDiscreteFocusRing .5s cubic-bezier(.35,0,.25,1)}md-slider[md-discrete]:not([disabled]) .md-slider-wrapper.md-focused .md-thumb{-webkit-animation:sliderDiscreteFocusThumb .5s cubic-bezier(.35,0,.25,1);animation:sliderDiscreteFocusThumb .5s cubic-bezier(.35,0,.25,1)}md-slider[md-discrete]:not([disabled]).md-active .md-thumb,md-slider[md-discrete]:not([disabled]) .md-slider-wrapper.md-focused .md-thumb{-webkit-transform:scale(0);transform:scale(0)}md-slider[md-discrete]:not([disabled]).md-active .md-sign,md-slider[md-discrete]:not([disabled]).md-active .md-sign:after,md-slider[md-discrete]:not([disabled]) .md-slider-wrapper.md-focused .md-sign,md-slider[md-discrete]:not([disabled]) .md-slider-wrapper.md-focused .md-sign:after{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}md-slider[md-discrete][disabled][readonly] .md-thumb{-webkit-transform:scale(0);transform:scale(0)}md-slider[md-discrete][disabled][readonly] .md-sign,md-slider[md-discrete][disabled][readonly] .md-sign:after{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}md-slider[disabled] .md-track-fill{display:none}md-slider[disabled] .md-track-ticks,md-slider[disabled]:not([readonly]) .md-sign{opacity:0}md-slider[disabled] .md-thumb{-webkit-transform:scale(.5);transform:scale(.5)}md-slider[disabled] .md-disabled-thumb{display:block}md-slider[md-vertical]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;min-height:128px;min-width:0}md-slider[md-vertical] .md-slider-wrapper{-webkit-box-flex:1;-webkit-flex:1;flex:1;padding-top:12px;padding-bottom:12px;width:48px;-webkit-align-self:center;align-self:center;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}md-slider[md-vertical] .md-track-container{height:100%;width:2px;top:0;left:calc(50% - 1px)}md-slider[md-vertical] .md-thumb-container{top:auto;margin-bottom:23px;left:calc(50% - 1px);bottom:0}md-slider[md-vertical] .md-thumb-container .md-thumb:after{left:1px}md-slider[md-vertical] .md-thumb-container .md-focus-ring{left:-16px}md-slider[md-vertical] .md-track-fill{bottom:0}md-slider[md-vertical][md-discrete] .md-sign{left:-40px;top:9.5px;-webkit-transform:scale(.4) translate3d(67.5px,0,0);transform:scale(.4) translate3d(67.5px,0,0)}md-slider[md-vertical][md-discrete] .md-sign:after{top:9.5px;left:19px;border-top:14px solid transparent;border-right:0;border-bottom:14px solid transparent;border-left-width:16px;border-left-style:solid;opacity:0;-webkit-transform:translate3d(0,-8px,0);transform:translate3d(0,-8px,0);-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}md-slider[md-vertical][md-discrete] .md-sign .md-thumb-text{z-index:1;font-size:12px;font-weight:700}md-slider[md-vertical][md-discrete].md-active .md-sign:after,md-slider[md-vertical][md-discrete] .md-focused .md-sign:after,md-slider[md-vertical][md-discrete][disabled][readonly] .md-sign:after{top:0}md-slider[md-vertical][disabled][readonly] .md-thumb{-webkit-transform:scale(0);transform:scale(0)}md-slider[md-vertical][disabled][readonly] .md-sign,md-slider[md-vertical][disabled][readonly] .md-sign:after{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}md-slider[md-invert]:not([md-vertical]) .md-track-fill{left:auto;right:0}[dir=rtl] md-slider[md-invert]:not([md-vertical]) .md-track-fill{left:0;right:auto}md-slider[md-invert][md-vertical] .md-track-fill{bottom:auto;top:0}md-slider-container{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-slider-container>:first-child:not(md-slider),md-slider-container>:last-child:not(md-slider){min-width:25px;max-width:42px;height:25px;-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-property:color,max-width;transition-property:color,max-width}md-slider-container>:first-child:not(md-slider){margin-right:16px}[dir=rtl] md-slider-container>:first-child:not(md-slider){margin-right:auto;margin-left:16px}md-slider-container>:last-child:not(md-slider){margin-left:16px}[dir=rtl] md-slider-container>:last-child:not(md-slider){margin-left:auto;margin-right:16px}md-slider-container[md-vertical]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}md-slider-container[md-vertical]>:first-child:not(md-slider),md-slider-container[md-vertical]>:last-child:not(md-slider){margin-right:0;margin-left:0;text-align:center}md-slider-container md-input-container input[type=number]{text-align:center;padding-left:15px;height:50px;margin-top:-25px}[dir=rtl] md-slider-container md-input-container input[type=number]{padding-left:0;padding-right:15px}@media screen and (-ms-high-contrast:active){md-slider.md-default-theme .md-track{border-bottom:1px solid #fff}}.md-sticky-clone{z-index:2;top:0;left:0;right:0;position:absolute!important;-webkit-transform:translate3d(-9999px,-9999px,0);transform:translate3d(-9999px,-9999px,0)}.md-sticky-clone[sticky-state=active]{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.md-sticky-clone[sticky-state=active]:not(.md-sticky-no-effect) .md-subheader-inner{-webkit-animation:subheaderStickyHoverIn .3s ease-out both;animation:subheaderStickyHoverIn .3s ease-out both}@-webkit-keyframes subheaderStickyHoverIn{0%{box-shadow:0 0 0 0 transparent}to{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}}@keyframes subheaderStickyHoverIn{0%{box-shadow:0 0 0 0 transparent}to{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}}@-webkit-keyframes subheaderStickyHoverOut{0%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}to{box-shadow:0 0 0 0 transparent}}@keyframes subheaderStickyHoverOut{0%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}to{box-shadow:0 0 0 0 transparent}}.md-subheader-wrapper:not(.md-sticky-no-effect){-webkit-transition:margin .2s ease-out;transition:margin .2s ease-out}.md-subheader-wrapper:not(.md-sticky-no-effect) .md-subheader{margin:0}.md-subheader-wrapper:not(.md-sticky-no-effect).md-sticky-clone{z-index:2}.md-subheader-wrapper:not(.md-sticky-no-effect)[sticky-state=active]{margin-top:-2px}.md-subheader-wrapper:not(.md-sticky-no-effect):not(.md-sticky-clone)[sticky-prev-state=active] .md-subheader-inner:after{-webkit-animation:subheaderStickyHoverOut .3s ease-out both;animation:subheaderStickyHoverOut .3s ease-out both}.md-subheader{display:block;font-size:14px;font-weight:500;line-height:1em;margin:0;position:relative}.md-subheader .md-subheader-inner{display:block;padding:16px}.md-subheader .md-subheader-content{display:block;z-index:1;position:relative}.md-inline-form md-switch{margin-top:18px;margin-bottom:19px}md-switch{margin:16px 0;white-space:nowrap;cursor:pointer;outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;height:30px;line-height:28px;-webkit-box-align:center;-webkit-align-items:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:flex;margin-left:inherit;margin-right:16px}[dir=rtl] md-switch{margin-left:16px;margin-right:inherit}md-switch:last-of-type{margin-left:inherit;margin-right:0}[dir=rtl] md-switch:last-of-type{margin-left:0;margin-right:inherit}md-switch[disabled],md-switch[disabled] .md-container{cursor:default}md-switch .md-container{cursor:-webkit-grab;cursor:grab;width:36px;height:24px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-right:8px;float:left}[dir=rtl] md-switch .md-container{margin-right:auto;margin-left:8px}md-switch:not([disabled]) .md-dragging,md-switch:not([disabled]).md-dragging .md-container{cursor:-webkit-grabbing;cursor:grabbing}md-switch.md-focused:not([disabled]) .md-thumb:before{left:-8px;top:-8px;right:-8px;bottom:-8px}md-switch.md-focused:not([disabled]):not(.md-checked) .md-thumb:before{background-color:rgba(0,0,0,.12)}md-switch .md-label{border-color:transparent;border-width:0;float:left}md-switch .md-bar{left:1px;width:34px;top:5px;height:14px;border-radius:8px;position:absolute}md-switch .md-thumb-container{top:2px;left:0;width:16px;position:absolute;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);z-index:1}md-switch.md-checked .md-thumb-container{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}md-switch .md-thumb{margin:0;outline:none;height:20px;width:20px;box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)}md-switch .md-thumb,md-switch .md-thumb:before{position:absolute;left:0;top:0;border-radius:50%}md-switch .md-thumb:before{background-color:transparent;content:'';display:block;height:auto;right:0;bottom:0;-webkit-transition:all .5s;transition:all .5s;width:auto}md-switch .md-thumb .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-20px;top:-20px;right:-20px;bottom:-20px}md-switch:not(.md-dragging) .md-bar,md-switch:not(.md-dragging) .md-thumb,md-switch:not(.md-dragging) .md-thumb-container{-webkit-transition:all .08s linear;transition:all .08s linear;-webkit-transition-property:background-color,-webkit-transform;transition-property:background-color,-webkit-transform;transition-property:transform,background-color;transition-property:transform,background-color,-webkit-transform}md-switch:not(.md-dragging) .md-bar,md-switch:not(.md-dragging) .md-thumb{-webkit-transition-delay:.05s;transition-delay:.05s}@media screen and (-ms-high-contrast:active){md-switch.md-default-theme .md-bar{background-color:#666}md-switch.md-default-theme.md-checked .md-bar{background-color:#9e9e9e}md-switch.md-default-theme .md-thumb{background-color:#fff}}@-webkit-keyframes md-tab-content-hide{0%{opacity:1}50%{opacity:1}to{opacity:0}}@keyframes md-tab-content-hide{0%{opacity:1}50%{opacity:1}to{opacity:0}}md-tab-data{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1;opacity:0}md-tabs{display:block;margin:0;border-radius:2px;overflow:hidden;position:relative;-webkit-flex-shrink:0;flex-shrink:0}md-tabs:not(.md-no-tab-content):not(.md-dynamic-height){min-height:248px}md-tabs[md-align-tabs=bottom]{padding-bottom:48px}md-tabs[md-align-tabs=bottom] md-tabs-wrapper{position:absolute;bottom:0;left:0;right:0;height:48px;z-index:2}md-tabs[md-align-tabs=bottom] md-tabs-content-wrapper{top:0;bottom:48px}md-tabs.md-dynamic-height md-tabs-content-wrapper{min-height:0;position:relative;top:auto;left:auto;right:auto;bottom:auto;overflow:visible}md-tabs.md-dynamic-height md-tab-content.md-active{position:relative}md-tabs[md-border-bottom] md-tabs-wrapper{border-width:0 0 1px;border-style:solid}md-tabs[md-border-bottom]:not(.md-dynamic-height) md-tabs-content-wrapper{top:49px}md-tabs-wrapper{display:block;position:relative;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-tabs-wrapper md-next-button,md-tabs-wrapper md-prev-button{height:100%;width:32px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);line-height:1em;z-index:2;cursor:pointer;font-size:16px;background:transparent no-repeat 50%;-webkit-transition:all .5s cubic-bezier(.35,0,.25,1);transition:all .5s cubic-bezier(.35,0,.25,1)}md-tabs-wrapper md-next-button:focus,md-tabs-wrapper md-prev-button:focus{outline:none}md-tabs-wrapper md-next-button.md-disabled,md-tabs-wrapper md-prev-button.md-disabled{opacity:.25;cursor:default}md-tabs-wrapper md-next-button.ng-leave,md-tabs-wrapper md-prev-button.ng-leave{-webkit-transition:none;transition:none}md-tabs-wrapper md-next-button md-icon,md-tabs-wrapper md-prev-button md-icon{position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}md-tabs-wrapper md-prev-button{left:0;background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMjA4IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyIAkJIiBzdHlsZT0iZmlsbDp3aGl0ZTsiLz4gPHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+IDwvZz4gPC9nPiA8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+IDxnIGRpc3BsYXk9ImlubGluZSI+IDwvZz4gPC9nPiA8L3N2Zz4NCg==")}[dir=rtl] md-tabs-wrapper md-prev-button{left:auto;right:0}md-tabs-wrapper md-next-button{right:0;background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMzM2IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTAsNiA4LjYsNy40IDEzLjIsMTIgOC42LDE2LjYgMTAsMTggMTYsMTIgCQkiIHN0eWxlPSJmaWxsOndoaXRlOyIvPiA8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz4gPC9nPiA8L2c+IDxnIGlkPSJHcmlkIiBkaXNwbGF5PSJub25lIj4gPGcgZGlzcGxheT0iaW5saW5lIj4gPC9nPiA8L2c+IDwvc3ZnPg0K")}[dir=rtl] md-tabs-wrapper md-next-button{right:auto;left:0}md-tabs-wrapper md-next-button md-icon{-webkit-transform:translate3d(-50%,-50%,0) rotate(180deg);transform:translate3d(-50%,-50%,0) rotate(180deg)}md-tabs-wrapper.md-stretch-tabs md-pagination-wrapper{width:100%;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}md-tabs-wrapper.md-stretch-tabs md-pagination-wrapper md-tab-item{-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}md-tabs-canvas{position:relative;overflow:hidden;display:block;height:48px}md-tabs-canvas:after{content:'';display:table;clear:both}md-tabs-canvas .md-dummy-wrapper{position:absolute;top:0;left:0}[dir=rtl] md-tabs-canvas .md-dummy-wrapper{left:auto;right:0}md-tabs-canvas.md-paginated{margin:0 32px}md-tabs-canvas.md-center-tabs{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;text-align:center}md-tabs-canvas.md-center-tabs .md-tab{float:none;display:inline-block}md-pagination-wrapper{height:48px;display:block;-webkit-transition:-webkit-transform .5s cubic-bezier(.35,0,.25,1);transition:-webkit-transform .5s cubic-bezier(.35,0,.25,1);transition:transform .5s cubic-bezier(.35,0,.25,1);transition:transform .5s cubic-bezier(.35,0,.25,1),-webkit-transform .5s cubic-bezier(.35,0,.25,1);position:absolute;width:999999px;left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-pagination-wrapper:after{content:'';display:table;clear:both}[dir=rtl] md-pagination-wrapper{left:auto;right:0}md-pagination-wrapper.md-center-tabs{position:relative;width:auto;margin:0 auto}md-tabs-content-wrapper{top:48px;overflow:hidden}md-tab-content,md-tabs-content-wrapper{display:block;position:absolute;left:0;right:0;bottom:0}md-tab-content{top:0;-webkit-transition:-webkit-transform .5s cubic-bezier(.35,0,.25,1);transition:-webkit-transform .5s cubic-bezier(.35,0,.25,1);transition:transform .5s cubic-bezier(.35,0,.25,1);transition:transform .5s cubic-bezier(.35,0,.25,1),-webkit-transform .5s cubic-bezier(.35,0,.25,1);overflow:auto;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-tab-content.md-no-scroll{bottom:auto;overflow:hidden}md-tab-content.md-no-transition,md-tab-content.ng-leave{-webkit-transition:none;transition:none}md-tab-content.md-left:not(.md-active){-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-animation:1s md-tab-content-hide;animation:1s md-tab-content-hide;opacity:0}[dir=rtl] md-tab-content.md-left:not(.md-active){-webkit-transform:translateX(100%);transform:translateX(100%)}md-tab-content.md-left:not(.md-active) *{-webkit-transition:visibility 0s linear;transition:visibility 0s linear;-webkit-transition-delay:.5s;transition-delay:.5s;visibility:hidden}md-tab-content.md-right:not(.md-active){-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-animation:1s md-tab-content-hide;animation:1s md-tab-content-hide;opacity:0}[dir=rtl] md-tab-content.md-right:not(.md-active){-webkit-transform:translateX(-100%);transform:translateX(-100%)}md-tab-content.md-right:not(.md-active) *{-webkit-transition:visibility 0s linear;transition:visibility 0s linear;-webkit-transition-delay:.5s;transition-delay:.5s;visibility:hidden}md-tab-content>div.ng-leave{-webkit-animation:1s md-tab-content-hide;animation:1s md-tab-content-hide}md-ink-bar{position:absolute;left:auto;right:auto;bottom:0;height:2px}md-ink-bar.md-left{-webkit-transition:left .125s cubic-bezier(.35,0,.25,1),right .25s cubic-bezier(.35,0,.25,1);transition:left .125s cubic-bezier(.35,0,.25,1),right .25s cubic-bezier(.35,0,.25,1)}md-ink-bar.md-right{-webkit-transition:left .25s cubic-bezier(.35,0,.25,1),right .125s cubic-bezier(.35,0,.25,1);transition:left .25s cubic-bezier(.35,0,.25,1),right .125s cubic-bezier(.35,0,.25,1)}md-tab{position:absolute;z-index:-1;left:-9999px}.md-tab{font-size:14px;text-align:center;line-height:24px;padding:12px 24px;-webkit-transition:background-color .35s cubic-bezier(.35,0,.25,1);transition:background-color .35s cubic-bezier(.35,0,.25,1);cursor:pointer;white-space:nowrap;position:relative;text-transform:uppercase;float:left;font-weight:500;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis}[dir=rtl] .md-tab{float:right}.md-tab.md-focused{box-shadow:none;outline:none}.md-tab.md-active{cursor:default}.md-tab.md-disabled{pointer-events:none;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-drag:none;opacity:.5;cursor:default}.md-tab.ng-leave{-webkit-transition:none;transition:none}md-toolbar+md-tabs{border-top-left-radius:0;border-top-right-radius:0}.md-toast-text{padding:0 6px}md-toast{position:absolute;z-index:105;box-sizing:border-box;cursor:default;padding:8px;opacity:1}md-toast,md-toast .md-toast-content{overflow:hidden;-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1)}md-toast .md-toast-content{display:-webkit-box;display:-webkit-flex;display:flex;direction:row;-webkit-box-align:center;-webkit-align-items:center;align-items:center;max-height:168px;max-width:100%;min-height:48px;padding:0 18px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);border-radius:2px;font-size:14px;-webkit-transform:translate3d(0,0,0) rotateZ(0deg);transform:translate3d(0,0,0) rotateZ(0deg);-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}md-toast .md-toast-content:before{content:'';min-height:48px;visibility:hidden;display:inline-block}[dir=rtl] md-toast .md-toast-content{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}md-toast .md-toast-content span{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box;min-width:0}md-toast.md-capsule,md-toast.md-capsule .md-toast-content{border-radius:24px}md-toast.ng-leave-active .md-toast-content{-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-toast.md-swipedown .md-toast-content,md-toast.md-swipeleft .md-toast-content,md-toast.md-swiperight .md-toast-content,md-toast.md-swipeup .md-toast-content{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1)}md-toast.ng-enter{opacity:0}md-toast.ng-enter .md-toast-content{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}md-toast.ng-enter.md-top .md-toast-content{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}md-toast.ng-enter.ng-enter-active{opacity:1}md-toast.ng-enter.ng-enter-active .md-toast-content{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-toast.ng-leave.ng-leave-active .md-toast-content{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}md-toast.ng-leave.ng-leave-active.md-swipeup .md-toast-content{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}md-toast.ng-leave.ng-leave-active.md-swipedown .md-toast-content{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}md-toast.ng-leave.ng-leave-active.md-top .md-toast-content{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}md-toast .md-action{line-height:19px;margin-left:24px;margin-right:0;cursor:pointer;text-transform:uppercase;float:right}md-toast .md-button{min-width:0;margin-right:0;margin-left:12px}[dir=rtl] md-toast .md-button{margin-right:12px;margin-left:0}@media (max-width:959px){md-toast{left:0;right:0;width:100%;max-width:100%;min-width:0;border-radius:0;bottom:0;padding:0}md-toast.ng-leave.ng-leave-active.md-swipeup .md-toast-content{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}md-toast.ng-leave.ng-leave-active.md-swipedown .md-toast-content{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}}@media (min-width:960px){md-toast{min-width:304px}md-toast.md-bottom{bottom:0}md-toast.md-left{left:0}md-toast.md-right{right:0}md-toast.md-top{top:0}md-toast._md-start{left:0}[dir=rtl] md-toast._md-start{left:auto;right:0}md-toast._md-end{right:0}[dir=rtl] md-toast._md-end{right:auto;left:0}md-toast.ng-leave.ng-leave-active.md-swipeleft .md-toast-content{-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}md-toast.ng-leave.ng-leave-active.md-swiperight .md-toast-content{-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0)}}@media (min-width:1920px){md-toast .md-toast-content{max-width:568px}}@media screen and (-ms-high-contrast:active){md-toast{border:1px solid #fff}}.md-toast-animating{overflow:hidden!important}md-toolbar{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column;position:relative;z-index:2;font-size:20px;min-height:64px;width:100%}md-toolbar._md-toolbar-transitions{-webkit-transition-duration:.5s;transition-duration:.5s;-webkit-transition-timing-function:cubic-bezier(.35,0,.25,1);transition-timing-function:cubic-bezier(.35,0,.25,1);-webkit-transition-property:background-color,fill,color;transition-property:background-color,fill,color}md-toolbar.md-whiteframe-z1-add,md-toolbar.md-whiteframe-z1-remove{-webkit-transition:box-shadow .5s linear;transition:box-shadow .5s linear}md-toolbar md-toolbar-filler{width:72px}md-toolbar *,md-toolbar :after,md-toolbar :before{box-sizing:border-box}md-toolbar.ng-animate{-webkit-transition:none;transition:none}md-toolbar.md-tall{height:128px;min-height:128px;max-height:128px}md-toolbar.md-medium-tall{height:88px;min-height:88px;max-height:88px}md-toolbar.md-medium-tall .md-toolbar-tools{height:48px;min-height:48px;max-height:48px}md-toolbar>.md-indent{margin-left:64px}[dir=rtl] md-toolbar>.md-indent{margin-left:auto;margin-right:64px}md-toolbar~md-content>md-list{padding:0}md-toolbar~md-content>md-list md-list-item:last-child md-divider{display:none}.md-toolbar-tools{font-size:20px;letter-spacing:.005em;box-sizing:border-box;font-weight:400;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row;width:100%;height:64px;max-height:64px;padding:0 16px;margin:0}.md-toolbar-tools h1,.md-toolbar-tools h2,.md-toolbar-tools h3{font-size:inherit;font-weight:inherit;margin:inherit}.md-toolbar-tools a{color:inherit;text-decoration:none}.md-toolbar-tools .fill-height{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center}.md-toolbar-tools .md-button{margin-top:0;margin-bottom:0}.md-toolbar-tools .md-button,.md-toolbar-tools .md-button.md-icon-button md-icon{-webkit-transition-duration:.5s;transition-duration:.5s;-webkit-transition-timing-function:cubic-bezier(.35,0,.25,1);transition-timing-function:cubic-bezier(.35,0,.25,1);-webkit-transition-property:background-color,fill,color;transition-property:background-color,fill,color}.md-toolbar-tools .md-button.md-icon-button md-icon.ng-animate,.md-toolbar-tools .md-button.ng-animate{-webkit-transition:none;transition:none}.md-toolbar-tools>.md-button:first-child{margin-left:-8px}[dir=rtl] .md-toolbar-tools>.md-button:first-child{margin-left:auto;margin-right:-8px}.md-toolbar-tools>.md-button:last-child{margin-right:-8px}[dir=rtl] .md-toolbar-tools>.md-button:last-child{margin-right:auto;margin-left:-8px}.md-toolbar-tools>md-menu:last-child{margin-right:-8px}[dir=rtl] .md-toolbar-tools>md-menu:last-child{margin-right:auto;margin-left:-8px}.md-toolbar-tools>md-menu:last-child>.md-button{margin-right:0}[dir=rtl] .md-toolbar-tools>md-menu:last-child>.md-button{margin-right:auto;margin-left:0}@media screen and (-ms-high-contrast:active){.md-toolbar-tools{border-bottom:1px solid #fff}}@media (min-width:0) and (max-width:959px) and (orientation:portrait){md-toolbar{min-height:56px}.md-toolbar-tools{height:56px;max-height:56px}}@media (min-width:0) and (max-width:959px) and (orientation:landscape){md-toolbar{min-height:48px}.md-toolbar-tools{height:48px;max-height:48px}}md-tooltip{position:absolute;z-index:100;overflow:hidden;pointer-events:none;border-radius:4px;font-weight:500;font-size:14px}@media (min-width:960px){md-tooltip{font-size:10px}}md-tooltip .md-content{position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-transform-origin:center top;transform-origin:center top;-webkit-transform:scale(0);transform:scale(0);opacity:0;height:32px;line-height:32px;padding-left:16px;padding-right:16px}@media (min-width:960px){md-tooltip .md-content{height:22px;line-height:22px;padding-left:8px;padding-right:8px}}md-tooltip .md-content.md-show-add{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transform:scale(0);transform:scale(0);opacity:0}md-tooltip .md-content.md-show,md-tooltip .md-content.md-show-add-active{-webkit-transform:scale(1);transform:scale(1);opacity:.9;-webkit-transform-origin:center top;transform-origin:center top}md-tooltip .md-content.md-show-remove{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transition-duration:.2s;transition-duration:.2s}md-tooltip .md-content.md-show-remove.md-show-remove-active{-webkit-transform:scale(0);transform:scale(0);opacity:0}md-tooltip.md-hide{-webkit-transition:all .3s cubic-bezier(.55,0,.55,.2);transition:all .3s cubic-bezier(.55,0,.55,.2)}md-tooltip.md-show{-webkit-transition:all .4s cubic-bezier(.25,.8,.25,1);transition:all .4s cubic-bezier(.25,.8,.25,1);pointer-events:auto}.md-virtual-repeat-container{box-sizing:border-box;display:block;margin:0;overflow:hidden;padding:0;position:relative}.md-virtual-repeat-container .md-virtual-repeat-scroller{bottom:0;box-sizing:border-box;left:0;margin:0;overflow-x:hidden;padding:0;position:absolute;right:0;top:0;-webkit-overflow-scrolling:touch}.md-virtual-repeat-container .md-virtual-repeat-sizer{box-sizing:border-box;height:1px;display:block;margin:0;padding:0;width:1px}.md-virtual-repeat-container .md-virtual-repeat-offsetter{box-sizing:border-box;left:0;margin:0;padding:0;position:absolute;right:0;top:0}.md-virtual-repeat-container.md-orient-horizontal .md-virtual-repeat-scroller{overflow-x:auto;overflow-y:hidden}.md-virtual-repeat-container.md-orient-horizontal .md-virtual-repeat-offsetter{bottom:16px;right:auto;white-space:nowrap}[dir=rtl] .md-virtual-repeat-container.md-orient-horizontal .md-virtual-repeat-offsetter{right:auto;left:auto}.md-whiteframe-1dp,.md-whiteframe-z1{box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)}.md-whiteframe-2dp{box-shadow:0 1px 5px 0 rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12)}.md-whiteframe-3dp{box-shadow:0 1px 8px 0 rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.12)}.md-whiteframe-4dp,.md-whiteframe-z2{box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)}.md-whiteframe-5dp{box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 5px 8px 0 rgba(0,0,0,.14),0 1px 14px 0 rgba(0,0,0,.12)}.md-whiteframe-6dp{box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)}.md-whiteframe-7dp,.md-whiteframe-z3{box-shadow:0 4px 5px -2px rgba(0,0,0,.2),0 7px 10px 1px rgba(0,0,0,.14),0 2px 16px 1px rgba(0,0,0,.12)}.md-whiteframe-8dp{box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12)}.md-whiteframe-9dp{box-shadow:0 5px 6px -3px rgba(0,0,0,.2),0 9px 12px 1px rgba(0,0,0,.14),0 3px 16px 2px rgba(0,0,0,.12)}.md-whiteframe-10dp,.md-whiteframe-z4{box-shadow:0 6px 6px -3px rgba(0,0,0,.2),0 10px 14px 1px rgba(0,0,0,.14),0 4px 18px 3px rgba(0,0,0,.12)}.md-whiteframe-11dp{box-shadow:0 6px 7px -4px rgba(0,0,0,.2),0 11px 15px 1px rgba(0,0,0,.14),0 4px 20px 3px rgba(0,0,0,.12)}.md-whiteframe-12dp{box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 12px 17px 2px rgba(0,0,0,.14),0 5px 22px 4px rgba(0,0,0,.12)}.md-whiteframe-13dp,.md-whiteframe-z5{box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12)}.md-whiteframe-14dp{box-shadow:0 7px 9px -4px rgba(0,0,0,.2),0 14px 21px 2px rgba(0,0,0,.14),0 5px 26px 4px rgba(0,0,0,.12)}.md-whiteframe-15dp{box-shadow:0 8px 9px -5px rgba(0,0,0,.2),0 15px 22px 2px rgba(0,0,0,.14),0 6px 28px 5px rgba(0,0,0,.12)}.md-whiteframe-16dp{box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)}.md-whiteframe-17dp{box-shadow:0 8px 11px -5px rgba(0,0,0,.2),0 17px 26px 2px rgba(0,0,0,.14),0 6px 32px 5px rgba(0,0,0,.12)}.md-whiteframe-18dp{box-shadow:0 9px 11px -5px rgba(0,0,0,.2),0 18px 28px 2px rgba(0,0,0,.14),0 7px 34px 6px rgba(0,0,0,.12)}.md-whiteframe-19dp{box-shadow:0 9px 12px -6px rgba(0,0,0,.2),0 19px 29px 2px rgba(0,0,0,.14),0 7px 36px 6px rgba(0,0,0,.12)}.md-whiteframe-20dp{box-shadow:0 10px 13px -6px rgba(0,0,0,.2),0 20px 31px 3px rgba(0,0,0,.14),0 8px 38px 7px rgba(0,0,0,.12)}.md-whiteframe-21dp{box-shadow:0 10px 13px -6px rgba(0,0,0,.2),0 21px 33px 3px rgba(0,0,0,.14),0 8px 40px 7px rgba(0,0,0,.12)}.md-whiteframe-22dp{box-shadow:0 10px 14px -6px rgba(0,0,0,.2),0 22px 35px 3px rgba(0,0,0,.14),0 8px 42px 7px rgba(0,0,0,.12)}.md-whiteframe-23dp{box-shadow:0 11px 14px -7px rgba(0,0,0,.2),0 23px 36px 3px rgba(0,0,0,.14),0 9px 44px 8px rgba(0,0,0,.12)}.md-whiteframe-24dp{box-shadow:0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12)}@media screen and (-ms-high-contrast:active){md-whiteframe{border:1px solid #fff}}@media print{[md-whiteframe],md-whiteframe{background-color:#fff}}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}@-moz-document url-prefix(){.layout-fill{margin:0;width:100%;min-height:100%;height:100%}}.flex-order{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-0,.offset-0{margin-left:0}[dir=rtl] .flex-offset-0,[dir=rtl] .offset-0{margin-left:auto;margin-right:0}.flex-offset-5,.offset-5{margin-left:5%}[dir=rtl] .flex-offset-5,[dir=rtl] .offset-5{margin-left:auto;margin-right:5%}.flex-offset-10,.offset-10{margin-left:10%}[dir=rtl] .flex-offset-10,[dir=rtl] .offset-10{margin-left:auto;margin-right:10%}.flex-offset-15,.offset-15{margin-left:15%}[dir=rtl] .flex-offset-15,[dir=rtl] .offset-15{margin-left:auto;margin-right:15%}.flex-offset-20,.offset-20{margin-left:20%}[dir=rtl] .flex-offset-20,[dir=rtl] .offset-20{margin-left:auto;margin-right:20%}.flex-offset-25,.offset-25{margin-left:25%}[dir=rtl] .flex-offset-25,[dir=rtl] .offset-25{margin-left:auto;margin-right:25%}.flex-offset-30,.offset-30{margin-left:30%}[dir=rtl] .flex-offset-30,[dir=rtl] .offset-30{margin-left:auto;margin-right:30%}.flex-offset-35,.offset-35{margin-left:35%}[dir=rtl] .flex-offset-35,[dir=rtl] .offset-35{margin-left:auto;margin-right:35%}.flex-offset-40,.offset-40{margin-left:40%}[dir=rtl] .flex-offset-40,[dir=rtl] .offset-40{margin-left:auto;margin-right:40%}.flex-offset-45,.offset-45{margin-left:45%}[dir=rtl] .flex-offset-45,[dir=rtl] .offset-45{margin-left:auto;margin-right:45%}.flex-offset-50,.offset-50{margin-left:50%}[dir=rtl] .flex-offset-50,[dir=rtl] .offset-50{margin-left:auto;margin-right:50%}.flex-offset-55,.offset-55{margin-left:55%}[dir=rtl] .flex-offset-55,[dir=rtl] .offset-55{margin-left:auto;margin-right:55%}.flex-offset-60,.offset-60{margin-left:60%}[dir=rtl] .flex-offset-60,[dir=rtl] .offset-60{margin-left:auto;margin-right:60%}.flex-offset-65,.offset-65{margin-left:65%}[dir=rtl] .flex-offset-65,[dir=rtl] .offset-65{margin-left:auto;margin-right:65%}.flex-offset-70,.offset-70{margin-left:70%}[dir=rtl] .flex-offset-70,[dir=rtl] .offset-70{margin-left:auto;margin-right:70%}.flex-offset-75,.offset-75{margin-left:75%}[dir=rtl] .flex-offset-75,[dir=rtl] .offset-75{margin-left:auto;margin-right:75%}.flex-offset-80,.offset-80{margin-left:80%}[dir=rtl] .flex-offset-80,[dir=rtl] .offset-80{margin-left:auto;margin-right:80%}.flex-offset-85,.offset-85{margin-left:85%}[dir=rtl] .flex-offset-85,[dir=rtl] .offset-85{margin-left:auto;margin-right:85%}.flex-offset-90,.offset-90{margin-left:90%}[dir=rtl] .flex-offset-90,[dir=rtl] .offset-90{margin-left:auto;margin-right:90%}.flex-offset-95,.offset-95{margin-left:95%}[dir=rtl] .flex-offset-95,[dir=rtl] .offset-95{margin-left:auto;margin-right:95%}.flex-offset-33,.offset-33{margin-left:33.33333%}.flex-offset-66,.offset-66{margin-left:66.66667%}[dir=rtl] .flex-offset-66,[dir=rtl] .offset-66{margin-left:auto;margin-right:66.66667%}.layout-align,.layout-align-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align,.layout-align-start,.layout-align-start-center,.layout-align-start-end,.layout-align-start-start,.layout-align-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-center,.layout-align-center-center,.layout-align-center-end,.layout-align-center-start,.layout-align-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-end,.layout-align-end-center,.layout-align-end-end,.layout-align-end-start,.layout-align-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-space-around,.layout-align-space-around-center,.layout-align-space-around-end,.layout-align-space-around-start,.layout-align-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-space-between,.layout-align-space-between-center,.layout-align-space-between-end,.layout-align-space-between-start,.layout-align-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-center-start,.layout-align-end-start,.layout-align-space-around-start,.layout-align-space-between-start,.layout-align-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-center-center,.layout-align-end-center,.layout-align-space-around-center,.layout-align-space-between-center,.layout-align-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-center-center>*,.layout-align-end-center>*,.layout-align-space-around-center>*,.layout-align-space-between-center>*,.layout-align-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-center-end,.layout-align-end-end,.layout-align-space-around-end,.layout-align-space-between-end,.layout-align-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-center-stretch,.layout-align-end-stretch,.layout-align-space-around-stretch,.layout-align-space-between-stretch,.layout-align-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex{-webkit-flex:1;flex:1}.flex,.flex-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-0{max-width:0;max-height:100%}.flex-0,.layout-column>.flex-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-column>.flex-0{max-width:100%;max-height:0%}.layout-row>.flex-0{max-width:0;max-height:100%;min-width:0}.layout-column>.flex-0,.layout-row>.flex-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-column>.flex-0{max-width:100%;max-height:0%;min-height:0}.flex-5,.layout-row>.flex-5{max-width:5%;max-height:100%}.flex-5,.layout-column>.flex-5,.layout-row>.flex-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-5{max-width:100%;max-height:5%}.flex-10,.layout-row>.flex-10{max-width:10%;max-height:100%}.flex-10,.layout-column>.flex-10,.layout-row>.flex-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-10{max-width:100%;max-height:10%}.flex-15,.layout-row>.flex-15{max-width:15%;max-height:100%}.flex-15,.layout-column>.flex-15,.layout-row>.flex-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-15{max-width:100%;max-height:15%}.flex-20,.layout-row>.flex-20{max-width:20%;max-height:100%}.flex-20,.layout-column>.flex-20,.layout-row>.flex-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-20{max-width:100%;max-height:20%}.flex-25,.layout-row>.flex-25{max-width:25%;max-height:100%}.flex-25,.layout-column>.flex-25,.layout-row>.flex-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-25{max-width:100%;max-height:25%}.flex-30,.layout-row>.flex-30{max-width:30%;max-height:100%}.flex-30,.layout-column>.flex-30,.layout-row>.flex-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-30{max-width:100%;max-height:30%}.flex-35,.layout-row>.flex-35{max-width:35%;max-height:100%}.flex-35,.layout-column>.flex-35,.layout-row>.flex-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-35{max-width:100%;max-height:35%}.flex-40,.layout-row>.flex-40{max-width:40%;max-height:100%}.flex-40,.layout-column>.flex-40,.layout-row>.flex-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-40{max-width:100%;max-height:40%}.flex-45,.layout-row>.flex-45{max-width:45%;max-height:100%}.flex-45,.layout-column>.flex-45,.layout-row>.flex-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-45{max-width:100%;max-height:45%}.flex-50,.layout-row>.flex-50{max-width:50%;max-height:100%}.flex-50,.layout-column>.flex-50,.layout-row>.flex-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-50{max-width:100%;max-height:50%}.flex-55,.layout-row>.flex-55{max-width:55%;max-height:100%}.flex-55,.layout-column>.flex-55,.layout-row>.flex-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-55{max-width:100%;max-height:55%}.flex-60,.layout-row>.flex-60{max-width:60%;max-height:100%}.flex-60,.layout-column>.flex-60,.layout-row>.flex-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-60{max-width:100%;max-height:60%}.flex-65,.layout-row>.flex-65{max-width:65%;max-height:100%}.flex-65,.layout-column>.flex-65,.layout-row>.flex-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-65{max-width:100%;max-height:65%}.flex-70,.layout-row>.flex-70{max-width:70%;max-height:100%}.flex-70,.layout-column>.flex-70,.layout-row>.flex-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-70{max-width:100%;max-height:70%}.flex-75,.layout-row>.flex-75{max-width:75%;max-height:100%}.flex-75,.layout-column>.flex-75,.layout-row>.flex-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-75{max-width:100%;max-height:75%}.flex-80,.layout-row>.flex-80{max-width:80%;max-height:100%}.flex-80,.layout-column>.flex-80,.layout-row>.flex-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-80{max-width:100%;max-height:80%}.flex-85,.layout-row>.flex-85{max-width:85%;max-height:100%}.flex-85,.layout-column>.flex-85,.layout-row>.flex-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-85{max-width:100%;max-height:85%}.flex-90,.layout-row>.flex-90{max-width:90%;max-height:100%}.flex-90,.layout-column>.flex-90,.layout-row>.flex-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-90{max-width:100%;max-height:90%}.flex-95,.layout-row>.flex-95{max-width:95%;max-height:100%}.flex-95,.layout-column>.flex-95,.layout-row>.flex-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-95{max-width:100%;max-height:95%}.flex-100,.layout-column>.flex-100,.layout-row>.flex-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-row>.flex{min-width:0}.layout-column>.flex-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex{min-height:0}.layout,.layout-column,.layout-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-column{-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}.layout-column,.layout-row{-webkit-box-direction:normal}.layout-row{-webkit-box-orient:horizontal;-webkit-flex-direction:row;flex-direction:row}.layout-padding-sm>*,.layout-padding>.flex-sm{padding:4px}.layout-padding,.layout-padding-gt-sm,.layout-padding-gt-sm>*,.layout-padding-md,.layout-padding-md>*,.layout-padding>*,.layout-padding>.flex,.layout-padding>.flex-gt-sm,.layout-padding>.flex-md{padding:8px}.layout-padding-gt-lg>*,.layout-padding-gt-md>*,.layout-padding-lg>*,.layout-padding>.flex-gt-lg,.layout-padding>.flex-gt-md,.layout-padding>.flex-lg{padding:16px}.layout-margin-sm>*,.layout-margin>.flex-sm{margin:4px}.layout-margin,.layout-margin-gt-sm,.layout-margin-gt-sm>*,.layout-margin-md,.layout-margin-md>*,.layout-margin>*,.layout-margin>.flex,.layout-margin>.flex-gt-sm,.layout-margin>.flex-md{margin:8px}.layout-margin-gt-lg>*,.layout-margin-gt-md>*,.layout-margin-lg>*,.layout-margin>.flex-gt-lg,.layout-margin>.flex-gt-md,.layout-margin>.flex-lg{margin:16px}.layout-wrap{-webkit-flex-wrap:wrap;flex-wrap:wrap}.layout-nowrap{-webkit-flex-wrap:nowrap;flex-wrap:nowrap}.layout-fill{margin:0;width:100%;min-height:100%;height:100%}@media (max-width:599px){.hide-xs:not(.show-xs):not(.show),.hide:not(.show-xs):not(.show){display:none}.flex-order-xs--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-xs--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-xs--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-xs--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-xs--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-xs--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-xs--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-xs--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-xs--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-xs--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-xs--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-xs--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-xs--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-xs--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-xs--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-xs--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-xs--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-xs--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-xs--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-xs--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-xs-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-xs-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-xs-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-xs-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-xs-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-xs-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-xs-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-xs-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-xs-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-xs-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-xs-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-xs-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-xs-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-xs-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-xs-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-xs-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-xs-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-xs-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-xs-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-xs-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-xs-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-xs-0,.offset-xs-0{margin-left:0}[dir=rtl] .flex-offset-xs-0,[dir=rtl] .offset-xs-0{margin-left:auto;margin-right:0}.flex-offset-xs-5,.offset-xs-5{margin-left:5%}[dir=rtl] .flex-offset-xs-5,[dir=rtl] .offset-xs-5{margin-left:auto;margin-right:5%}.flex-offset-xs-10,.offset-xs-10{margin-left:10%}[dir=rtl] .flex-offset-xs-10,[dir=rtl] .offset-xs-10{margin-left:auto;margin-right:10%}.flex-offset-xs-15,.offset-xs-15{margin-left:15%}[dir=rtl] .flex-offset-xs-15,[dir=rtl] .offset-xs-15{margin-left:auto;margin-right:15%}.flex-offset-xs-20,.offset-xs-20{margin-left:20%}[dir=rtl] .flex-offset-xs-20,[dir=rtl] .offset-xs-20{margin-left:auto;margin-right:20%}.flex-offset-xs-25,.offset-xs-25{margin-left:25%}[dir=rtl] .flex-offset-xs-25,[dir=rtl] .offset-xs-25{margin-left:auto;margin-right:25%}.flex-offset-xs-30,.offset-xs-30{margin-left:30%}[dir=rtl] .flex-offset-xs-30,[dir=rtl] .offset-xs-30{margin-left:auto;margin-right:30%}.flex-offset-xs-35,.offset-xs-35{margin-left:35%}[dir=rtl] .flex-offset-xs-35,[dir=rtl] .offset-xs-35{margin-left:auto;margin-right:35%}.flex-offset-xs-40,.offset-xs-40{margin-left:40%}[dir=rtl] .flex-offset-xs-40,[dir=rtl] .offset-xs-40{margin-left:auto;margin-right:40%}.flex-offset-xs-45,.offset-xs-45{margin-left:45%}[dir=rtl] .flex-offset-xs-45,[dir=rtl] .offset-xs-45{margin-left:auto;margin-right:45%}.flex-offset-xs-50,.offset-xs-50{margin-left:50%}[dir=rtl] .flex-offset-xs-50,[dir=rtl] .offset-xs-50{margin-left:auto;margin-right:50%}.flex-offset-xs-55,.offset-xs-55{margin-left:55%}[dir=rtl] .flex-offset-xs-55,[dir=rtl] .offset-xs-55{margin-left:auto;margin-right:55%}.flex-offset-xs-60,.offset-xs-60{margin-left:60%}[dir=rtl] .flex-offset-xs-60,[dir=rtl] .offset-xs-60{margin-left:auto;margin-right:60%}.flex-offset-xs-65,.offset-xs-65{margin-left:65%}[dir=rtl] .flex-offset-xs-65,[dir=rtl] .offset-xs-65{margin-left:auto;margin-right:65%}.flex-offset-xs-70,.offset-xs-70{margin-left:70%}[dir=rtl] .flex-offset-xs-70,[dir=rtl] .offset-xs-70{margin-left:auto;margin-right:70%}.flex-offset-xs-75,.offset-xs-75{margin-left:75%}[dir=rtl] .flex-offset-xs-75,[dir=rtl] .offset-xs-75{margin-left:auto;margin-right:75%}.flex-offset-xs-80,.offset-xs-80{margin-left:80%}[dir=rtl] .flex-offset-xs-80,[dir=rtl] .offset-xs-80{margin-left:auto;margin-right:80%}.flex-offset-xs-85,.offset-xs-85{margin-left:85%}[dir=rtl] .flex-offset-xs-85,[dir=rtl] .offset-xs-85{margin-left:auto;margin-right:85%}.flex-offset-xs-90,.offset-xs-90{margin-left:90%}[dir=rtl] .flex-offset-xs-90,[dir=rtl] .offset-xs-90{margin-left:auto;margin-right:90%}.flex-offset-xs-95,.offset-xs-95{margin-left:95%}[dir=rtl] .flex-offset-xs-95,[dir=rtl] .offset-xs-95{margin-left:auto;margin-right:95%}.flex-offset-xs-33,.offset-xs-33{margin-left:33.33333%}.flex-offset-xs-66,.offset-xs-66{margin-left:66.66667%}[dir=rtl] .flex-offset-xs-66,[dir=rtl] .offset-xs-66{margin-left:auto;margin-right:66.66667%}.layout-align-xs,.layout-align-xs-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-xs,.layout-align-xs-start,.layout-align-xs-start-center,.layout-align-xs-start-end,.layout-align-xs-start-start,.layout-align-xs-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-xs-center,.layout-align-xs-center-center,.layout-align-xs-center-end,.layout-align-xs-center-start,.layout-align-xs-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-xs-end,.layout-align-xs-end-center,.layout-align-xs-end-end,.layout-align-xs-end-start,.layout-align-xs-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-xs-space-around,.layout-align-xs-space-around-center,.layout-align-xs-space-around-end,.layout-align-xs-space-around-start,.layout-align-xs-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-xs-space-between,.layout-align-xs-space-between-center,.layout-align-xs-space-between-end,.layout-align-xs-space-between-start,.layout-align-xs-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-xs-center-start,.layout-align-xs-end-start,.layout-align-xs-space-around-start,.layout-align-xs-space-between-start,.layout-align-xs-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-xs-center-center,.layout-align-xs-end-center,.layout-align-xs-space-around-center,.layout-align-xs-space-between-center,.layout-align-xs-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-xs-center-center>*,.layout-align-xs-end-center>*,.layout-align-xs-space-around-center>*,.layout-align-xs-space-between-center>*,.layout-align-xs-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-xs-center-end,.layout-align-xs-end-end,.layout-align-xs-space-around-end,.layout-align-xs-space-between-end,.layout-align-xs-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-xs-center-stretch,.layout-align-xs-end-stretch,.layout-align-xs-space-around-stretch,.layout-align-xs-space-between-stretch,.layout-align-xs-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-xs{-webkit-flex:1;flex:1}.flex-xs,.flex-xs-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-xs-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-xs-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xs-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-xs-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-xs-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-xs-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xs-0,.layout-row>.flex-xs-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-xs-0{min-width:0}.layout-column>.flex-xs-0{max-width:100%;max-height:0%}.layout-column>.flex-xs-0,.layout-xs-row>.flex-xs-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-xs-row>.flex-xs-0{max-width:0;max-height:100%;min-width:0}.layout-xs-column>.flex-xs-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-xs-5,.layout-row>.flex-xs-5{max-width:5%;max-height:100%}.flex-xs-5,.layout-column>.flex-xs-5,.layout-row>.flex-xs-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-xs-5{max-width:100%;max-height:5%}.layout-xs-row>.flex-xs-5{max-width:5%;max-height:100%}.layout-xs-column>.flex-xs-5,.layout-xs-row>.flex-xs-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-xs-column>.flex-xs-5{max-width:100%;max-height:5%}.flex-xs-10,.layout-row>.flex-xs-10{max-width:10%;max-height:100%}.flex-xs-10,.layout-column>.flex-xs-10,.layout-row>.flex-xs-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-xs-10{max-width:100%;max-height:10%}.layout-xs-row>.flex-xs-10{max-width:10%;max-height:100%}.layout-xs-column>.flex-xs-10,.layout-xs-row>.flex-xs-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-xs-column>.flex-xs-10{max-width:100%;max-height:10%}.flex-xs-15,.layout-row>.flex-xs-15{max-width:15%;max-height:100%}.flex-xs-15,.layout-column>.flex-xs-15,.layout-row>.flex-xs-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-xs-15{max-width:100%;max-height:15%}.layout-xs-row>.flex-xs-15{max-width:15%;max-height:100%}.layout-xs-column>.flex-xs-15,.layout-xs-row>.flex-xs-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-xs-column>.flex-xs-15{max-width:100%;max-height:15%}.flex-xs-20,.layout-row>.flex-xs-20{max-width:20%;max-height:100%}.flex-xs-20,.layout-column>.flex-xs-20,.layout-row>.flex-xs-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-xs-20{max-width:100%;max-height:20%}.layout-xs-row>.flex-xs-20{max-width:20%;max-height:100%}.layout-xs-column>.flex-xs-20,.layout-xs-row>.flex-xs-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-xs-column>.flex-xs-20{max-width:100%;max-height:20%}.flex-xs-25,.layout-row>.flex-xs-25{max-width:25%;max-height:100%}.flex-xs-25,.layout-column>.flex-xs-25,.layout-row>.flex-xs-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-xs-25{max-width:100%;max-height:25%}.layout-xs-row>.flex-xs-25{max-width:25%;max-height:100%}.layout-xs-column>.flex-xs-25,.layout-xs-row>.flex-xs-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-xs-column>.flex-xs-25{max-width:100%;max-height:25%}.flex-xs-30,.layout-row>.flex-xs-30{max-width:30%;max-height:100%}.flex-xs-30,.layout-column>.flex-xs-30,.layout-row>.flex-xs-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-xs-30{max-width:100%;max-height:30%}.layout-xs-row>.flex-xs-30{max-width:30%;max-height:100%}.layout-xs-column>.flex-xs-30,.layout-xs-row>.flex-xs-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-xs-column>.flex-xs-30{max-width:100%;max-height:30%}.flex-xs-35,.layout-row>.flex-xs-35{max-width:35%;max-height:100%}.flex-xs-35,.layout-column>.flex-xs-35,.layout-row>.flex-xs-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-xs-35{max-width:100%;max-height:35%}.layout-xs-row>.flex-xs-35{max-width:35%;max-height:100%}.layout-xs-column>.flex-xs-35,.layout-xs-row>.flex-xs-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-xs-column>.flex-xs-35{max-width:100%;max-height:35%}.flex-xs-40,.layout-row>.flex-xs-40{max-width:40%;max-height:100%}.flex-xs-40,.layout-column>.flex-xs-40,.layout-row>.flex-xs-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-xs-40{max-width:100%;max-height:40%}.layout-xs-row>.flex-xs-40{max-width:40%;max-height:100%}.layout-xs-column>.flex-xs-40,.layout-xs-row>.flex-xs-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-xs-column>.flex-xs-40{max-width:100%;max-height:40%}.flex-xs-45,.layout-row>.flex-xs-45{max-width:45%;max-height:100%}.flex-xs-45,.layout-column>.flex-xs-45,.layout-row>.flex-xs-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-xs-45{max-width:100%;max-height:45%}.layout-xs-row>.flex-xs-45{max-width:45%;max-height:100%}.layout-xs-column>.flex-xs-45,.layout-xs-row>.flex-xs-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-xs-column>.flex-xs-45{max-width:100%;max-height:45%}.flex-xs-50,.layout-row>.flex-xs-50{max-width:50%;max-height:100%}.flex-xs-50,.layout-column>.flex-xs-50,.layout-row>.flex-xs-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-xs-50{max-width:100%;max-height:50%}.layout-xs-row>.flex-xs-50{max-width:50%;max-height:100%}.layout-xs-column>.flex-xs-50,.layout-xs-row>.flex-xs-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-xs-column>.flex-xs-50{max-width:100%;max-height:50%}.flex-xs-55,.layout-row>.flex-xs-55{max-width:55%;max-height:100%}.flex-xs-55,.layout-column>.flex-xs-55,.layout-row>.flex-xs-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-xs-55{max-width:100%;max-height:55%}.layout-xs-row>.flex-xs-55{max-width:55%;max-height:100%}.layout-xs-column>.flex-xs-55,.layout-xs-row>.flex-xs-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-xs-column>.flex-xs-55{max-width:100%;max-height:55%}.flex-xs-60,.layout-row>.flex-xs-60{max-width:60%;max-height:100%}.flex-xs-60,.layout-column>.flex-xs-60,.layout-row>.flex-xs-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-xs-60{max-width:100%;max-height:60%}.layout-xs-row>.flex-xs-60{max-width:60%;max-height:100%}.layout-xs-column>.flex-xs-60,.layout-xs-row>.flex-xs-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-xs-column>.flex-xs-60{max-width:100%;max-height:60%}.flex-xs-65,.layout-row>.flex-xs-65{max-width:65%;max-height:100%}.flex-xs-65,.layout-column>.flex-xs-65,.layout-row>.flex-xs-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-xs-65{max-width:100%;max-height:65%}.layout-xs-row>.flex-xs-65{max-width:65%;max-height:100%}.layout-xs-column>.flex-xs-65,.layout-xs-row>.flex-xs-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-xs-column>.flex-xs-65{max-width:100%;max-height:65%}.flex-xs-70,.layout-row>.flex-xs-70{max-width:70%;max-height:100%}.flex-xs-70,.layout-column>.flex-xs-70,.layout-row>.flex-xs-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-xs-70{max-width:100%;max-height:70%}.layout-xs-row>.flex-xs-70{max-width:70%;max-height:100%}.layout-xs-column>.flex-xs-70,.layout-xs-row>.flex-xs-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-xs-column>.flex-xs-70{max-width:100%;max-height:70%}.flex-xs-75,.layout-row>.flex-xs-75{max-width:75%;max-height:100%}.flex-xs-75,.layout-column>.flex-xs-75,.layout-row>.flex-xs-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-xs-75{max-width:100%;max-height:75%}.layout-xs-row>.flex-xs-75{max-width:75%;max-height:100%}.layout-xs-column>.flex-xs-75,.layout-xs-row>.flex-xs-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-xs-column>.flex-xs-75{max-width:100%;max-height:75%}.flex-xs-80,.layout-row>.flex-xs-80{max-width:80%;max-height:100%}.flex-xs-80,.layout-column>.flex-xs-80,.layout-row>.flex-xs-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-xs-80{max-width:100%;max-height:80%}.layout-xs-row>.flex-xs-80{max-width:80%;max-height:100%}.layout-xs-column>.flex-xs-80,.layout-xs-row>.flex-xs-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-xs-column>.flex-xs-80{max-width:100%;max-height:80%}.flex-xs-85,.layout-row>.flex-xs-85{max-width:85%;max-height:100%}.flex-xs-85,.layout-column>.flex-xs-85,.layout-row>.flex-xs-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-xs-85{max-width:100%;max-height:85%}.layout-xs-row>.flex-xs-85{max-width:85%;max-height:100%}.layout-xs-column>.flex-xs-85,.layout-xs-row>.flex-xs-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-xs-column>.flex-xs-85{max-width:100%;max-height:85%}.flex-xs-90,.layout-row>.flex-xs-90{max-width:90%;max-height:100%}.flex-xs-90,.layout-column>.flex-xs-90,.layout-row>.flex-xs-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-xs-90{max-width:100%;max-height:90%}.layout-xs-row>.flex-xs-90{max-width:90%;max-height:100%}.layout-xs-column>.flex-xs-90,.layout-xs-row>.flex-xs-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-xs-column>.flex-xs-90{max-width:100%;max-height:90%}.flex-xs-95,.layout-row>.flex-xs-95{max-width:95%;max-height:100%}.flex-xs-95,.layout-column>.flex-xs-95,.layout-row>.flex-xs-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-xs-95{max-width:100%;max-height:95%}.layout-xs-row>.flex-xs-95{max-width:95%;max-height:100%}.layout-xs-column>.flex-xs-95,.layout-xs-row>.flex-xs-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-xs-column>.flex-xs-95{max-width:100%;max-height:95%}.flex-xs-100,.layout-column>.flex-xs-100,.layout-row>.flex-xs-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-xs-column>.flex-xs-100,.layout-xs-row>.flex-xs-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-xs-row>.flex-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-xs-row>.flex-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-xs-row>.flex{min-width:0}.layout-xs-column>.flex-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-xs-column>.flex-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-xs-column>.flex{min-height:0}.layout-xs,.layout-xs-column,.layout-xs-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-xs-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-xs-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:600px){.flex-order-gt-xs--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-gt-xs--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-gt-xs--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-gt-xs--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-gt-xs--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-gt-xs--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-gt-xs--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-gt-xs--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-gt-xs--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-gt-xs--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-gt-xs--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-gt-xs--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-gt-xs--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-gt-xs--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-gt-xs--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-gt-xs--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-gt-xs--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-gt-xs--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-gt-xs--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-gt-xs--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-gt-xs-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-gt-xs-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-gt-xs-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-gt-xs-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-gt-xs-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-gt-xs-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-gt-xs-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-gt-xs-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-gt-xs-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-gt-xs-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-gt-xs-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-gt-xs-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-gt-xs-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-gt-xs-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-gt-xs-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-gt-xs-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-gt-xs-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-gt-xs-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-gt-xs-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-gt-xs-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-gt-xs-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-gt-xs-0,.offset-gt-xs-0{margin-left:0}[dir=rtl] .flex-offset-gt-xs-0,[dir=rtl] .offset-gt-xs-0{margin-left:auto;margin-right:0}.flex-offset-gt-xs-5,.offset-gt-xs-5{margin-left:5%}[dir=rtl] .flex-offset-gt-xs-5,[dir=rtl] .offset-gt-xs-5{margin-left:auto;margin-right:5%}.flex-offset-gt-xs-10,.offset-gt-xs-10{margin-left:10%}[dir=rtl] .flex-offset-gt-xs-10,[dir=rtl] .offset-gt-xs-10{margin-left:auto;margin-right:10%}.flex-offset-gt-xs-15,.offset-gt-xs-15{margin-left:15%}[dir=rtl] .flex-offset-gt-xs-15,[dir=rtl] .offset-gt-xs-15{margin-left:auto;margin-right:15%}.flex-offset-gt-xs-20,.offset-gt-xs-20{margin-left:20%}[dir=rtl] .flex-offset-gt-xs-20,[dir=rtl] .offset-gt-xs-20{margin-left:auto;margin-right:20%}.flex-offset-gt-xs-25,.offset-gt-xs-25{margin-left:25%}[dir=rtl] .flex-offset-gt-xs-25,[dir=rtl] .offset-gt-xs-25{margin-left:auto;margin-right:25%}.flex-offset-gt-xs-30,.offset-gt-xs-30{margin-left:30%}[dir=rtl] .flex-offset-gt-xs-30,[dir=rtl] .offset-gt-xs-30{margin-left:auto;margin-right:30%}.flex-offset-gt-xs-35,.offset-gt-xs-35{margin-left:35%}[dir=rtl] .flex-offset-gt-xs-35,[dir=rtl] .offset-gt-xs-35{margin-left:auto;margin-right:35%}.flex-offset-gt-xs-40,.offset-gt-xs-40{margin-left:40%}[dir=rtl] .flex-offset-gt-xs-40,[dir=rtl] .offset-gt-xs-40{margin-left:auto;margin-right:40%}.flex-offset-gt-xs-45,.offset-gt-xs-45{margin-left:45%}[dir=rtl] .flex-offset-gt-xs-45,[dir=rtl] .offset-gt-xs-45{margin-left:auto;margin-right:45%}.flex-offset-gt-xs-50,.offset-gt-xs-50{margin-left:50%}[dir=rtl] .flex-offset-gt-xs-50,[dir=rtl] .offset-gt-xs-50{margin-left:auto;margin-right:50%}.flex-offset-gt-xs-55,.offset-gt-xs-55{margin-left:55%}[dir=rtl] .flex-offset-gt-xs-55,[dir=rtl] .offset-gt-xs-55{margin-left:auto;margin-right:55%}.flex-offset-gt-xs-60,.offset-gt-xs-60{margin-left:60%}[dir=rtl] .flex-offset-gt-xs-60,[dir=rtl] .offset-gt-xs-60{margin-left:auto;margin-right:60%}.flex-offset-gt-xs-65,.offset-gt-xs-65{margin-left:65%}[dir=rtl] .flex-offset-gt-xs-65,[dir=rtl] .offset-gt-xs-65{margin-left:auto;margin-right:65%}.flex-offset-gt-xs-70,.offset-gt-xs-70{margin-left:70%}[dir=rtl] .flex-offset-gt-xs-70,[dir=rtl] .offset-gt-xs-70{margin-left:auto;margin-right:70%}.flex-offset-gt-xs-75,.offset-gt-xs-75{margin-left:75%}[dir=rtl] .flex-offset-gt-xs-75,[dir=rtl] .offset-gt-xs-75{margin-left:auto;margin-right:75%}.flex-offset-gt-xs-80,.offset-gt-xs-80{margin-left:80%}[dir=rtl] .flex-offset-gt-xs-80,[dir=rtl] .offset-gt-xs-80{margin-left:auto;margin-right:80%}.flex-offset-gt-xs-85,.offset-gt-xs-85{margin-left:85%}[dir=rtl] .flex-offset-gt-xs-85,[dir=rtl] .offset-gt-xs-85{margin-left:auto;margin-right:85%}.flex-offset-gt-xs-90,.offset-gt-xs-90{margin-left:90%}[dir=rtl] .flex-offset-gt-xs-90,[dir=rtl] .offset-gt-xs-90{margin-left:auto;margin-right:90%}.flex-offset-gt-xs-95,.offset-gt-xs-95{margin-left:95%}[dir=rtl] .flex-offset-gt-xs-95,[dir=rtl] .offset-gt-xs-95{margin-left:auto;margin-right:95%}.flex-offset-gt-xs-33,.offset-gt-xs-33{margin-left:33.33333%}.flex-offset-gt-xs-66,.offset-gt-xs-66{margin-left:66.66667%}[dir=rtl] .flex-offset-gt-xs-66,[dir=rtl] .offset-gt-xs-66{margin-left:auto;margin-right:66.66667%}.layout-align-gt-xs,.layout-align-gt-xs-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-gt-xs,.layout-align-gt-xs-start,.layout-align-gt-xs-start-center,.layout-align-gt-xs-start-end,.layout-align-gt-xs-start-start,.layout-align-gt-xs-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-gt-xs-center,.layout-align-gt-xs-center-center,.layout-align-gt-xs-center-end,.layout-align-gt-xs-center-start,.layout-align-gt-xs-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-gt-xs-end,.layout-align-gt-xs-end-center,.layout-align-gt-xs-end-end,.layout-align-gt-xs-end-start,.layout-align-gt-xs-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-gt-xs-space-around,.layout-align-gt-xs-space-around-center,.layout-align-gt-xs-space-around-end,.layout-align-gt-xs-space-around-start,.layout-align-gt-xs-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-gt-xs-space-between,.layout-align-gt-xs-space-between-center,.layout-align-gt-xs-space-between-end,.layout-align-gt-xs-space-between-start,.layout-align-gt-xs-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-gt-xs-center-start,.layout-align-gt-xs-end-start,.layout-align-gt-xs-space-around-start,.layout-align-gt-xs-space-between-start,.layout-align-gt-xs-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-gt-xs-center-center,.layout-align-gt-xs-end-center,.layout-align-gt-xs-space-around-center,.layout-align-gt-xs-space-between-center,.layout-align-gt-xs-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-gt-xs-center-center>*,.layout-align-gt-xs-end-center>*,.layout-align-gt-xs-space-around-center>*,.layout-align-gt-xs-space-between-center>*,.layout-align-gt-xs-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-xs-center-end,.layout-align-gt-xs-end-end,.layout-align-gt-xs-space-around-end,.layout-align-gt-xs-space-between-end,.layout-align-gt-xs-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-gt-xs-center-stretch,.layout-align-gt-xs-end-stretch,.layout-align-gt-xs-space-around-stretch,.layout-align-gt-xs-space-between-stretch,.layout-align-gt-xs-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-gt-xs{-webkit-flex:1;flex:1}.flex-gt-xs,.flex-gt-xs-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-gt-xs-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-gt-xs-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-xs-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-xs-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-xs-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-xs-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-xs-0,.layout-row>.flex-gt-xs-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-xs-0{min-width:0}.layout-column>.flex-gt-xs-0{max-width:100%;max-height:0%}.layout-column>.flex-gt-xs-0,.layout-gt-xs-row>.flex-gt-xs-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-gt-xs-row>.flex-gt-xs-0{max-width:0;max-height:100%;min-width:0}.layout-gt-xs-column>.flex-gt-xs-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-gt-xs-5,.layout-row>.flex-gt-xs-5{max-width:5%;max-height:100%}.flex-gt-xs-5,.layout-column>.flex-gt-xs-5,.layout-row>.flex-gt-xs-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-gt-xs-5{max-width:100%;max-height:5%}.layout-gt-xs-row>.flex-gt-xs-5{max-width:5%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-5,.layout-gt-xs-row>.flex-gt-xs-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-5{max-width:100%;max-height:5%}.flex-gt-xs-10,.layout-row>.flex-gt-xs-10{max-width:10%;max-height:100%}.flex-gt-xs-10,.layout-column>.flex-gt-xs-10,.layout-row>.flex-gt-xs-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-gt-xs-10{max-width:100%;max-height:10%}.layout-gt-xs-row>.flex-gt-xs-10{max-width:10%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-10,.layout-gt-xs-row>.flex-gt-xs-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-10{max-width:100%;max-height:10%}.flex-gt-xs-15,.layout-row>.flex-gt-xs-15{max-width:15%;max-height:100%}.flex-gt-xs-15,.layout-column>.flex-gt-xs-15,.layout-row>.flex-gt-xs-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-gt-xs-15{max-width:100%;max-height:15%}.layout-gt-xs-row>.flex-gt-xs-15{max-width:15%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-15,.layout-gt-xs-row>.flex-gt-xs-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-15{max-width:100%;max-height:15%}.flex-gt-xs-20,.layout-row>.flex-gt-xs-20{max-width:20%;max-height:100%}.flex-gt-xs-20,.layout-column>.flex-gt-xs-20,.layout-row>.flex-gt-xs-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-gt-xs-20{max-width:100%;max-height:20%}.layout-gt-xs-row>.flex-gt-xs-20{max-width:20%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-20,.layout-gt-xs-row>.flex-gt-xs-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-20{max-width:100%;max-height:20%}.flex-gt-xs-25,.layout-row>.flex-gt-xs-25{max-width:25%;max-height:100%}.flex-gt-xs-25,.layout-column>.flex-gt-xs-25,.layout-row>.flex-gt-xs-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-gt-xs-25{max-width:100%;max-height:25%}.layout-gt-xs-row>.flex-gt-xs-25{max-width:25%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-25,.layout-gt-xs-row>.flex-gt-xs-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-25{max-width:100%;max-height:25%}.flex-gt-xs-30,.layout-row>.flex-gt-xs-30{max-width:30%;max-height:100%}.flex-gt-xs-30,.layout-column>.flex-gt-xs-30,.layout-row>.flex-gt-xs-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-gt-xs-30{max-width:100%;max-height:30%}.layout-gt-xs-row>.flex-gt-xs-30{max-width:30%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-30,.layout-gt-xs-row>.flex-gt-xs-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-30{max-width:100%;max-height:30%}.flex-gt-xs-35,.layout-row>.flex-gt-xs-35{max-width:35%;max-height:100%}.flex-gt-xs-35,.layout-column>.flex-gt-xs-35,.layout-row>.flex-gt-xs-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-gt-xs-35{max-width:100%;max-height:35%}.layout-gt-xs-row>.flex-gt-xs-35{max-width:35%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-35,.layout-gt-xs-row>.flex-gt-xs-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-35{max-width:100%;max-height:35%}.flex-gt-xs-40,.layout-row>.flex-gt-xs-40{max-width:40%;max-height:100%}.flex-gt-xs-40,.layout-column>.flex-gt-xs-40,.layout-row>.flex-gt-xs-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-gt-xs-40{max-width:100%;max-height:40%}.layout-gt-xs-row>.flex-gt-xs-40{max-width:40%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-40,.layout-gt-xs-row>.flex-gt-xs-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-40{max-width:100%;max-height:40%}.flex-gt-xs-45,.layout-row>.flex-gt-xs-45{max-width:45%;max-height:100%}.flex-gt-xs-45,.layout-column>.flex-gt-xs-45,.layout-row>.flex-gt-xs-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-gt-xs-45{max-width:100%;max-height:45%}.layout-gt-xs-row>.flex-gt-xs-45{max-width:45%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-45,.layout-gt-xs-row>.flex-gt-xs-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-45{max-width:100%;max-height:45%}.flex-gt-xs-50,.layout-row>.flex-gt-xs-50{max-width:50%;max-height:100%}.flex-gt-xs-50,.layout-column>.flex-gt-xs-50,.layout-row>.flex-gt-xs-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-gt-xs-50{max-width:100%;max-height:50%}.layout-gt-xs-row>.flex-gt-xs-50{max-width:50%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-50,.layout-gt-xs-row>.flex-gt-xs-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-50{max-width:100%;max-height:50%}.flex-gt-xs-55,.layout-row>.flex-gt-xs-55{max-width:55%;max-height:100%}.flex-gt-xs-55,.layout-column>.flex-gt-xs-55,.layout-row>.flex-gt-xs-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-gt-xs-55{max-width:100%;max-height:55%}.layout-gt-xs-row>.flex-gt-xs-55{max-width:55%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-55,.layout-gt-xs-row>.flex-gt-xs-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-55{max-width:100%;max-height:55%}.flex-gt-xs-60,.layout-row>.flex-gt-xs-60{max-width:60%;max-height:100%}.flex-gt-xs-60,.layout-column>.flex-gt-xs-60,.layout-row>.flex-gt-xs-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-gt-xs-60{max-width:100%;max-height:60%}.layout-gt-xs-row>.flex-gt-xs-60{max-width:60%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-60,.layout-gt-xs-row>.flex-gt-xs-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-60{max-width:100%;max-height:60%}.flex-gt-xs-65,.layout-row>.flex-gt-xs-65{max-width:65%;max-height:100%}.flex-gt-xs-65,.layout-column>.flex-gt-xs-65,.layout-row>.flex-gt-xs-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-gt-xs-65{max-width:100%;max-height:65%}.layout-gt-xs-row>.flex-gt-xs-65{max-width:65%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-65,.layout-gt-xs-row>.flex-gt-xs-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-65{max-width:100%;max-height:65%}.flex-gt-xs-70,.layout-row>.flex-gt-xs-70{max-width:70%;max-height:100%}.flex-gt-xs-70,.layout-column>.flex-gt-xs-70,.layout-row>.flex-gt-xs-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-gt-xs-70{max-width:100%;max-height:70%}.layout-gt-xs-row>.flex-gt-xs-70{max-width:70%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-70,.layout-gt-xs-row>.flex-gt-xs-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-70{max-width:100%;max-height:70%}.flex-gt-xs-75,.layout-row>.flex-gt-xs-75{max-width:75%;max-height:100%}.flex-gt-xs-75,.layout-column>.flex-gt-xs-75,.layout-row>.flex-gt-xs-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-gt-xs-75{max-width:100%;max-height:75%}.layout-gt-xs-row>.flex-gt-xs-75{max-width:75%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-75,.layout-gt-xs-row>.flex-gt-xs-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-75{max-width:100%;max-height:75%}.flex-gt-xs-80,.layout-row>.flex-gt-xs-80{max-width:80%;max-height:100%}.flex-gt-xs-80,.layout-column>.flex-gt-xs-80,.layout-row>.flex-gt-xs-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-gt-xs-80{max-width:100%;max-height:80%}.layout-gt-xs-row>.flex-gt-xs-80{max-width:80%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-80,.layout-gt-xs-row>.flex-gt-xs-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-80{max-width:100%;max-height:80%}.flex-gt-xs-85,.layout-row>.flex-gt-xs-85{max-width:85%;max-height:100%}.flex-gt-xs-85,.layout-column>.flex-gt-xs-85,.layout-row>.flex-gt-xs-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-gt-xs-85{max-width:100%;max-height:85%}.layout-gt-xs-row>.flex-gt-xs-85{max-width:85%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-85,.layout-gt-xs-row>.flex-gt-xs-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-85{max-width:100%;max-height:85%}.flex-gt-xs-90,.layout-row>.flex-gt-xs-90{max-width:90%;max-height:100%}.flex-gt-xs-90,.layout-column>.flex-gt-xs-90,.layout-row>.flex-gt-xs-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-gt-xs-90{max-width:100%;max-height:90%}.layout-gt-xs-row>.flex-gt-xs-90{max-width:90%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-90,.layout-gt-xs-row>.flex-gt-xs-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-90{max-width:100%;max-height:90%}.flex-gt-xs-95,.layout-row>.flex-gt-xs-95{max-width:95%;max-height:100%}.flex-gt-xs-95,.layout-column>.flex-gt-xs-95,.layout-row>.flex-gt-xs-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-gt-xs-95{max-width:100%;max-height:95%}.layout-gt-xs-row>.flex-gt-xs-95{max-width:95%;max-height:100%}.layout-gt-xs-column>.flex-gt-xs-95,.layout-gt-xs-row>.flex-gt-xs-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-95{max-width:100%;max-height:95%}.flex-gt-xs-100,.layout-column>.flex-gt-xs-100,.layout-row>.flex-gt-xs-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-100,.layout-gt-xs-row>.flex-gt-xs-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex-gt-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex-gt-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex{min-width:0}.layout-gt-xs-column>.flex-gt-xs-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-gt-xs-column>.flex-gt-xs-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-xs-column>.flex{min-height:0}.layout-gt-xs,.layout-gt-xs-column,.layout-gt-xs-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-gt-xs-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-gt-xs-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:600px) and (max-width:959px){.hide-gt-xs:not(.show-gt-xs):not(.show-sm):not(.show),.hide-sm:not(.show-gt-xs):not(.show-sm):not(.show),.hide:not(.show-gt-xs):not(.show-sm):not(.show){display:none}.flex-order-sm--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-sm--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-sm--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-sm--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-sm--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-sm--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-sm--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-sm--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-sm--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-sm--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-sm--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-sm--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-sm--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-sm--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-sm--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-sm--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-sm--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-sm--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-sm--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-sm--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-sm-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-sm-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-sm-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-sm-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-sm-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-sm-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-sm-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-sm-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-sm-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-sm-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-sm-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-sm-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-sm-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-sm-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-sm-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-sm-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-sm-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-sm-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-sm-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-sm-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-sm-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-sm-0,.offset-sm-0{margin-left:0}[dir=rtl] .flex-offset-sm-0,[dir=rtl] .offset-sm-0{margin-left:auto;margin-right:0}.flex-offset-sm-5,.offset-sm-5{margin-left:5%}[dir=rtl] .flex-offset-sm-5,[dir=rtl] .offset-sm-5{margin-left:auto;margin-right:5%}.flex-offset-sm-10,.offset-sm-10{margin-left:10%}[dir=rtl] .flex-offset-sm-10,[dir=rtl] .offset-sm-10{margin-left:auto;margin-right:10%}.flex-offset-sm-15,.offset-sm-15{margin-left:15%}[dir=rtl] .flex-offset-sm-15,[dir=rtl] .offset-sm-15{margin-left:auto;margin-right:15%}.flex-offset-sm-20,.offset-sm-20{margin-left:20%}[dir=rtl] .flex-offset-sm-20,[dir=rtl] .offset-sm-20{margin-left:auto;margin-right:20%}.flex-offset-sm-25,.offset-sm-25{margin-left:25%}[dir=rtl] .flex-offset-sm-25,[dir=rtl] .offset-sm-25{margin-left:auto;margin-right:25%}.flex-offset-sm-30,.offset-sm-30{margin-left:30%}[dir=rtl] .flex-offset-sm-30,[dir=rtl] .offset-sm-30{margin-left:auto;margin-right:30%}.flex-offset-sm-35,.offset-sm-35{margin-left:35%}[dir=rtl] .flex-offset-sm-35,[dir=rtl] .offset-sm-35{margin-left:auto;margin-right:35%}.flex-offset-sm-40,.offset-sm-40{margin-left:40%}[dir=rtl] .flex-offset-sm-40,[dir=rtl] .offset-sm-40{margin-left:auto;margin-right:40%}.flex-offset-sm-45,.offset-sm-45{margin-left:45%}[dir=rtl] .flex-offset-sm-45,[dir=rtl] .offset-sm-45{margin-left:auto;margin-right:45%}.flex-offset-sm-50,.offset-sm-50{margin-left:50%}[dir=rtl] .flex-offset-sm-50,[dir=rtl] .offset-sm-50{margin-left:auto;margin-right:50%}.flex-offset-sm-55,.offset-sm-55{margin-left:55%}[dir=rtl] .flex-offset-sm-55,[dir=rtl] .offset-sm-55{margin-left:auto;margin-right:55%}.flex-offset-sm-60,.offset-sm-60{margin-left:60%}[dir=rtl] .flex-offset-sm-60,[dir=rtl] .offset-sm-60{margin-left:auto;margin-right:60%}.flex-offset-sm-65,.offset-sm-65{margin-left:65%}[dir=rtl] .flex-offset-sm-65,[dir=rtl] .offset-sm-65{margin-left:auto;margin-right:65%}.flex-offset-sm-70,.offset-sm-70{margin-left:70%}[dir=rtl] .flex-offset-sm-70,[dir=rtl] .offset-sm-70{margin-left:auto;margin-right:70%}.flex-offset-sm-75,.offset-sm-75{margin-left:75%}[dir=rtl] .flex-offset-sm-75,[dir=rtl] .offset-sm-75{margin-left:auto;margin-right:75%}.flex-offset-sm-80,.offset-sm-80{margin-left:80%}[dir=rtl] .flex-offset-sm-80,[dir=rtl] .offset-sm-80{margin-left:auto;margin-right:80%}.flex-offset-sm-85,.offset-sm-85{margin-left:85%}[dir=rtl] .flex-offset-sm-85,[dir=rtl] .offset-sm-85{margin-left:auto;margin-right:85%}.flex-offset-sm-90,.offset-sm-90{margin-left:90%}[dir=rtl] .flex-offset-sm-90,[dir=rtl] .offset-sm-90{margin-left:auto;margin-right:90%}.flex-offset-sm-95,.offset-sm-95{margin-left:95%}[dir=rtl] .flex-offset-sm-95,[dir=rtl] .offset-sm-95{margin-left:auto;margin-right:95%}.flex-offset-sm-33,.offset-sm-33{margin-left:33.33333%}.flex-offset-sm-66,.offset-sm-66{margin-left:66.66667%}[dir=rtl] .flex-offset-sm-66,[dir=rtl] .offset-sm-66{margin-left:auto;margin-right:66.66667%}.layout-align-sm,.layout-align-sm-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-sm,.layout-align-sm-start,.layout-align-sm-start-center,.layout-align-sm-start-end,.layout-align-sm-start-start,.layout-align-sm-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-sm-center,.layout-align-sm-center-center,.layout-align-sm-center-end,.layout-align-sm-center-start,.layout-align-sm-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-sm-end,.layout-align-sm-end-center,.layout-align-sm-end-end,.layout-align-sm-end-start,.layout-align-sm-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-sm-space-around,.layout-align-sm-space-around-center,.layout-align-sm-space-around-end,.layout-align-sm-space-around-start,.layout-align-sm-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-sm-space-between,.layout-align-sm-space-between-center,.layout-align-sm-space-between-end,.layout-align-sm-space-between-start,.layout-align-sm-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-sm-center-start,.layout-align-sm-end-start,.layout-align-sm-space-around-start,.layout-align-sm-space-between-start,.layout-align-sm-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-sm-center-center,.layout-align-sm-end-center,.layout-align-sm-space-around-center,.layout-align-sm-space-between-center,.layout-align-sm-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-sm-center-center>*,.layout-align-sm-end-center>*,.layout-align-sm-space-around-center>*,.layout-align-sm-space-between-center>*,.layout-align-sm-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-sm-center-end,.layout-align-sm-end-end,.layout-align-sm-space-around-end,.layout-align-sm-space-between-end,.layout-align-sm-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-sm-center-stretch,.layout-align-sm-end-stretch,.layout-align-sm-space-around-stretch,.layout-align-sm-space-between-stretch,.layout-align-sm-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-sm{-webkit-flex:1;flex:1}.flex-sm,.flex-sm-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-sm-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-sm-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-sm-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-sm-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-sm-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-sm-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-sm-0,.layout-row>.flex-sm-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-sm-0{min-width:0}.layout-column>.flex-sm-0{max-width:100%;max-height:0%}.layout-column>.flex-sm-0,.layout-sm-row>.flex-sm-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-sm-row>.flex-sm-0{max-width:0;max-height:100%;min-width:0}.layout-sm-column>.flex-sm-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-sm-5,.layout-row>.flex-sm-5{max-width:5%;max-height:100%}.flex-sm-5,.layout-column>.flex-sm-5,.layout-row>.flex-sm-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-sm-5{max-width:100%;max-height:5%}.layout-sm-row>.flex-sm-5{max-width:5%;max-height:100%}.layout-sm-column>.flex-sm-5,.layout-sm-row>.flex-sm-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-sm-column>.flex-sm-5{max-width:100%;max-height:5%}.flex-sm-10,.layout-row>.flex-sm-10{max-width:10%;max-height:100%}.flex-sm-10,.layout-column>.flex-sm-10,.layout-row>.flex-sm-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-sm-10{max-width:100%;max-height:10%}.layout-sm-row>.flex-sm-10{max-width:10%;max-height:100%}.layout-sm-column>.flex-sm-10,.layout-sm-row>.flex-sm-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-sm-column>.flex-sm-10{max-width:100%;max-height:10%}.flex-sm-15,.layout-row>.flex-sm-15{max-width:15%;max-height:100%}.flex-sm-15,.layout-column>.flex-sm-15,.layout-row>.flex-sm-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-sm-15{max-width:100%;max-height:15%}.layout-sm-row>.flex-sm-15{max-width:15%;max-height:100%}.layout-sm-column>.flex-sm-15,.layout-sm-row>.flex-sm-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-sm-column>.flex-sm-15{max-width:100%;max-height:15%}.flex-sm-20,.layout-row>.flex-sm-20{max-width:20%;max-height:100%}.flex-sm-20,.layout-column>.flex-sm-20,.layout-row>.flex-sm-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-sm-20{max-width:100%;max-height:20%}.layout-sm-row>.flex-sm-20{max-width:20%;max-height:100%}.layout-sm-column>.flex-sm-20,.layout-sm-row>.flex-sm-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-sm-column>.flex-sm-20{max-width:100%;max-height:20%}.flex-sm-25,.layout-row>.flex-sm-25{max-width:25%;max-height:100%}.flex-sm-25,.layout-column>.flex-sm-25,.layout-row>.flex-sm-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-sm-25{max-width:100%;max-height:25%}.layout-sm-row>.flex-sm-25{max-width:25%;max-height:100%}.layout-sm-column>.flex-sm-25,.layout-sm-row>.flex-sm-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-sm-column>.flex-sm-25{max-width:100%;max-height:25%}.flex-sm-30,.layout-row>.flex-sm-30{max-width:30%;max-height:100%}.flex-sm-30,.layout-column>.flex-sm-30,.layout-row>.flex-sm-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-sm-30{max-width:100%;max-height:30%}.layout-sm-row>.flex-sm-30{max-width:30%;max-height:100%}.layout-sm-column>.flex-sm-30,.layout-sm-row>.flex-sm-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-sm-column>.flex-sm-30{max-width:100%;max-height:30%}.flex-sm-35,.layout-row>.flex-sm-35{max-width:35%;max-height:100%}.flex-sm-35,.layout-column>.flex-sm-35,.layout-row>.flex-sm-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-sm-35{max-width:100%;max-height:35%}.layout-sm-row>.flex-sm-35{max-width:35%;max-height:100%}.layout-sm-column>.flex-sm-35,.layout-sm-row>.flex-sm-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-sm-column>.flex-sm-35{max-width:100%;max-height:35%}.flex-sm-40,.layout-row>.flex-sm-40{max-width:40%;max-height:100%}.flex-sm-40,.layout-column>.flex-sm-40,.layout-row>.flex-sm-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-sm-40{max-width:100%;max-height:40%}.layout-sm-row>.flex-sm-40{max-width:40%;max-height:100%}.layout-sm-column>.flex-sm-40,.layout-sm-row>.flex-sm-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-sm-column>.flex-sm-40{max-width:100%;max-height:40%}.flex-sm-45,.layout-row>.flex-sm-45{max-width:45%;max-height:100%}.flex-sm-45,.layout-column>.flex-sm-45,.layout-row>.flex-sm-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-sm-45{max-width:100%;max-height:45%}.layout-sm-row>.flex-sm-45{max-width:45%;max-height:100%}.layout-sm-column>.flex-sm-45,.layout-sm-row>.flex-sm-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-sm-column>.flex-sm-45{max-width:100%;max-height:45%}.flex-sm-50,.layout-row>.flex-sm-50{max-width:50%;max-height:100%}.flex-sm-50,.layout-column>.flex-sm-50,.layout-row>.flex-sm-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-sm-50{max-width:100%;max-height:50%}.layout-sm-row>.flex-sm-50{max-width:50%;max-height:100%}.layout-sm-column>.flex-sm-50,.layout-sm-row>.flex-sm-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-sm-column>.flex-sm-50{max-width:100%;max-height:50%}.flex-sm-55,.layout-row>.flex-sm-55{max-width:55%;max-height:100%}.flex-sm-55,.layout-column>.flex-sm-55,.layout-row>.flex-sm-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-sm-55{max-width:100%;max-height:55%}.layout-sm-row>.flex-sm-55{max-width:55%;max-height:100%}.layout-sm-column>.flex-sm-55,.layout-sm-row>.flex-sm-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-sm-column>.flex-sm-55{max-width:100%;max-height:55%}.flex-sm-60,.layout-row>.flex-sm-60{max-width:60%;max-height:100%}.flex-sm-60,.layout-column>.flex-sm-60,.layout-row>.flex-sm-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-sm-60{max-width:100%;max-height:60%}.layout-sm-row>.flex-sm-60{max-width:60%;max-height:100%}.layout-sm-column>.flex-sm-60,.layout-sm-row>.flex-sm-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-sm-column>.flex-sm-60{max-width:100%;max-height:60%}.flex-sm-65,.layout-row>.flex-sm-65{max-width:65%;max-height:100%}.flex-sm-65,.layout-column>.flex-sm-65,.layout-row>.flex-sm-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-sm-65{max-width:100%;max-height:65%}.layout-sm-row>.flex-sm-65{max-width:65%;max-height:100%}.layout-sm-column>.flex-sm-65,.layout-sm-row>.flex-sm-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-sm-column>.flex-sm-65{max-width:100%;max-height:65%}.flex-sm-70,.layout-row>.flex-sm-70{max-width:70%;max-height:100%}.flex-sm-70,.layout-column>.flex-sm-70,.layout-row>.flex-sm-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-sm-70{max-width:100%;max-height:70%}.layout-sm-row>.flex-sm-70{max-width:70%;max-height:100%}.layout-sm-column>.flex-sm-70,.layout-sm-row>.flex-sm-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-sm-column>.flex-sm-70{max-width:100%;max-height:70%}.flex-sm-75,.layout-row>.flex-sm-75{max-width:75%;max-height:100%}.flex-sm-75,.layout-column>.flex-sm-75,.layout-row>.flex-sm-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-sm-75{max-width:100%;max-height:75%}.layout-sm-row>.flex-sm-75{max-width:75%;max-height:100%}.layout-sm-column>.flex-sm-75,.layout-sm-row>.flex-sm-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-sm-column>.flex-sm-75{max-width:100%;max-height:75%}.flex-sm-80,.layout-row>.flex-sm-80{max-width:80%;max-height:100%}.flex-sm-80,.layout-column>.flex-sm-80,.layout-row>.flex-sm-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-sm-80{max-width:100%;max-height:80%}.layout-sm-row>.flex-sm-80{max-width:80%;max-height:100%}.layout-sm-column>.flex-sm-80,.layout-sm-row>.flex-sm-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-sm-column>.flex-sm-80{max-width:100%;max-height:80%}.flex-sm-85,.layout-row>.flex-sm-85{max-width:85%;max-height:100%}.flex-sm-85,.layout-column>.flex-sm-85,.layout-row>.flex-sm-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-sm-85{max-width:100%;max-height:85%}.layout-sm-row>.flex-sm-85{max-width:85%;max-height:100%}.layout-sm-column>.flex-sm-85,.layout-sm-row>.flex-sm-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-sm-column>.flex-sm-85{max-width:100%;max-height:85%}.flex-sm-90,.layout-row>.flex-sm-90{max-width:90%;max-height:100%}.flex-sm-90,.layout-column>.flex-sm-90,.layout-row>.flex-sm-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-sm-90{max-width:100%;max-height:90%}.layout-sm-row>.flex-sm-90{max-width:90%;max-height:100%}.layout-sm-column>.flex-sm-90,.layout-sm-row>.flex-sm-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-sm-column>.flex-sm-90{max-width:100%;max-height:90%}.flex-sm-95,.layout-row>.flex-sm-95{max-width:95%;max-height:100%}.flex-sm-95,.layout-column>.flex-sm-95,.layout-row>.flex-sm-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-sm-95{max-width:100%;max-height:95%}.layout-sm-row>.flex-sm-95{max-width:95%;max-height:100%}.layout-sm-column>.flex-sm-95,.layout-sm-row>.flex-sm-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-sm-column>.flex-sm-95{max-width:100%;max-height:95%}.flex-sm-100,.layout-column>.flex-sm-100,.layout-row>.flex-sm-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-sm-column>.flex-sm-100,.layout-sm-row>.flex-sm-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-sm-row>.flex-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-sm-row>.flex-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-sm-row>.flex{min-width:0}.layout-sm-column>.flex-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-sm-column>.flex-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-sm-column>.flex{min-height:0}.layout-sm,.layout-sm-column,.layout-sm-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-sm-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-sm-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:960px){.flex-order-gt-sm--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-gt-sm--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-gt-sm--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-gt-sm--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-gt-sm--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-gt-sm--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-gt-sm--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-gt-sm--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-gt-sm--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-gt-sm--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-gt-sm--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-gt-sm--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-gt-sm--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-gt-sm--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-gt-sm--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-gt-sm--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-gt-sm--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-gt-sm--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-gt-sm--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-gt-sm--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-gt-sm-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-gt-sm-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-gt-sm-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-gt-sm-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-gt-sm-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-gt-sm-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-gt-sm-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-gt-sm-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-gt-sm-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-gt-sm-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-gt-sm-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-gt-sm-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-gt-sm-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-gt-sm-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-gt-sm-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-gt-sm-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-gt-sm-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-gt-sm-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-gt-sm-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-gt-sm-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-gt-sm-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-gt-sm-0,.offset-gt-sm-0{margin-left:0}[dir=rtl] .flex-offset-gt-sm-0,[dir=rtl] .offset-gt-sm-0{margin-left:auto;margin-right:0}.flex-offset-gt-sm-5,.offset-gt-sm-5{margin-left:5%}[dir=rtl] .flex-offset-gt-sm-5,[dir=rtl] .offset-gt-sm-5{margin-left:auto;margin-right:5%}.flex-offset-gt-sm-10,.offset-gt-sm-10{margin-left:10%}[dir=rtl] .flex-offset-gt-sm-10,[dir=rtl] .offset-gt-sm-10{margin-left:auto;margin-right:10%}.flex-offset-gt-sm-15,.offset-gt-sm-15{margin-left:15%}[dir=rtl] .flex-offset-gt-sm-15,[dir=rtl] .offset-gt-sm-15{margin-left:auto;margin-right:15%}.flex-offset-gt-sm-20,.offset-gt-sm-20{margin-left:20%}[dir=rtl] .flex-offset-gt-sm-20,[dir=rtl] .offset-gt-sm-20{margin-left:auto;margin-right:20%}.flex-offset-gt-sm-25,.offset-gt-sm-25{margin-left:25%}[dir=rtl] .flex-offset-gt-sm-25,[dir=rtl] .offset-gt-sm-25{margin-left:auto;margin-right:25%}.flex-offset-gt-sm-30,.offset-gt-sm-30{margin-left:30%}[dir=rtl] .flex-offset-gt-sm-30,[dir=rtl] .offset-gt-sm-30{margin-left:auto;margin-right:30%}.flex-offset-gt-sm-35,.offset-gt-sm-35{margin-left:35%}[dir=rtl] .flex-offset-gt-sm-35,[dir=rtl] .offset-gt-sm-35{margin-left:auto;margin-right:35%}.flex-offset-gt-sm-40,.offset-gt-sm-40{margin-left:40%}[dir=rtl] .flex-offset-gt-sm-40,[dir=rtl] .offset-gt-sm-40{margin-left:auto;margin-right:40%}.flex-offset-gt-sm-45,.offset-gt-sm-45{margin-left:45%}[dir=rtl] .flex-offset-gt-sm-45,[dir=rtl] .offset-gt-sm-45{margin-left:auto;margin-right:45%}.flex-offset-gt-sm-50,.offset-gt-sm-50{margin-left:50%}[dir=rtl] .flex-offset-gt-sm-50,[dir=rtl] .offset-gt-sm-50{margin-left:auto;margin-right:50%}.flex-offset-gt-sm-55,.offset-gt-sm-55{margin-left:55%}[dir=rtl] .flex-offset-gt-sm-55,[dir=rtl] .offset-gt-sm-55{margin-left:auto;margin-right:55%}.flex-offset-gt-sm-60,.offset-gt-sm-60{margin-left:60%}[dir=rtl] .flex-offset-gt-sm-60,[dir=rtl] .offset-gt-sm-60{margin-left:auto;margin-right:60%}.flex-offset-gt-sm-65,.offset-gt-sm-65{margin-left:65%}[dir=rtl] .flex-offset-gt-sm-65,[dir=rtl] .offset-gt-sm-65{margin-left:auto;margin-right:65%}.flex-offset-gt-sm-70,.offset-gt-sm-70{margin-left:70%}[dir=rtl] .flex-offset-gt-sm-70,[dir=rtl] .offset-gt-sm-70{margin-left:auto;margin-right:70%}.flex-offset-gt-sm-75,.offset-gt-sm-75{margin-left:75%}[dir=rtl] .flex-offset-gt-sm-75,[dir=rtl] .offset-gt-sm-75{margin-left:auto;margin-right:75%}.flex-offset-gt-sm-80,.offset-gt-sm-80{margin-left:80%}[dir=rtl] .flex-offset-gt-sm-80,[dir=rtl] .offset-gt-sm-80{margin-left:auto;margin-right:80%}.flex-offset-gt-sm-85,.offset-gt-sm-85{margin-left:85%}[dir=rtl] .flex-offset-gt-sm-85,[dir=rtl] .offset-gt-sm-85{margin-left:auto;margin-right:85%}.flex-offset-gt-sm-90,.offset-gt-sm-90{margin-left:90%}[dir=rtl] .flex-offset-gt-sm-90,[dir=rtl] .offset-gt-sm-90{margin-left:auto;margin-right:90%}.flex-offset-gt-sm-95,.offset-gt-sm-95{margin-left:95%}[dir=rtl] .flex-offset-gt-sm-95,[dir=rtl] .offset-gt-sm-95{margin-left:auto;margin-right:95%}.flex-offset-gt-sm-33,.offset-gt-sm-33{margin-left:33.33333%}.flex-offset-gt-sm-66,.offset-gt-sm-66{margin-left:66.66667%}[dir=rtl] .flex-offset-gt-sm-66,[dir=rtl] .offset-gt-sm-66{margin-left:auto;margin-right:66.66667%}.layout-align-gt-sm,.layout-align-gt-sm-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-gt-sm,.layout-align-gt-sm-start,.layout-align-gt-sm-start-center,.layout-align-gt-sm-start-end,.layout-align-gt-sm-start-start,.layout-align-gt-sm-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-gt-sm-center,.layout-align-gt-sm-center-center,.layout-align-gt-sm-center-end,.layout-align-gt-sm-center-start,.layout-align-gt-sm-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-gt-sm-end,.layout-align-gt-sm-end-center,.layout-align-gt-sm-end-end,.layout-align-gt-sm-end-start,.layout-align-gt-sm-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-gt-sm-space-around,.layout-align-gt-sm-space-around-center,.layout-align-gt-sm-space-around-end,.layout-align-gt-sm-space-around-start,.layout-align-gt-sm-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-gt-sm-space-between,.layout-align-gt-sm-space-between-center,.layout-align-gt-sm-space-between-end,.layout-align-gt-sm-space-between-start,.layout-align-gt-sm-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-gt-sm-center-start,.layout-align-gt-sm-end-start,.layout-align-gt-sm-space-around-start,.layout-align-gt-sm-space-between-start,.layout-align-gt-sm-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-gt-sm-center-center,.layout-align-gt-sm-end-center,.layout-align-gt-sm-space-around-center,.layout-align-gt-sm-space-between-center,.layout-align-gt-sm-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-gt-sm-center-center>*,.layout-align-gt-sm-end-center>*,.layout-align-gt-sm-space-around-center>*,.layout-align-gt-sm-space-between-center>*,.layout-align-gt-sm-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-sm-center-end,.layout-align-gt-sm-end-end,.layout-align-gt-sm-space-around-end,.layout-align-gt-sm-space-between-end,.layout-align-gt-sm-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-gt-sm-center-stretch,.layout-align-gt-sm-end-stretch,.layout-align-gt-sm-space-around-stretch,.layout-align-gt-sm-space-between-stretch,.layout-align-gt-sm-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-gt-sm{-webkit-flex:1;flex:1}.flex-gt-sm,.flex-gt-sm-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-gt-sm-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-gt-sm-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-sm-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-sm-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-sm-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-sm-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-sm-0,.layout-row>.flex-gt-sm-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-sm-0{min-width:0}.layout-column>.flex-gt-sm-0{max-width:100%;max-height:0%}.layout-column>.flex-gt-sm-0,.layout-gt-sm-row>.flex-gt-sm-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-gt-sm-row>.flex-gt-sm-0{max-width:0;max-height:100%;min-width:0}.layout-gt-sm-column>.flex-gt-sm-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-gt-sm-5,.layout-row>.flex-gt-sm-5{max-width:5%;max-height:100%}.flex-gt-sm-5,.layout-column>.flex-gt-sm-5,.layout-row>.flex-gt-sm-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-gt-sm-5{max-width:100%;max-height:5%}.layout-gt-sm-row>.flex-gt-sm-5{max-width:5%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-5,.layout-gt-sm-row>.flex-gt-sm-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-5{max-width:100%;max-height:5%}.flex-gt-sm-10,.layout-row>.flex-gt-sm-10{max-width:10%;max-height:100%}.flex-gt-sm-10,.layout-column>.flex-gt-sm-10,.layout-row>.flex-gt-sm-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-gt-sm-10{max-width:100%;max-height:10%}.layout-gt-sm-row>.flex-gt-sm-10{max-width:10%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-10,.layout-gt-sm-row>.flex-gt-sm-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-10{max-width:100%;max-height:10%}.flex-gt-sm-15,.layout-row>.flex-gt-sm-15{max-width:15%;max-height:100%}.flex-gt-sm-15,.layout-column>.flex-gt-sm-15,.layout-row>.flex-gt-sm-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-gt-sm-15{max-width:100%;max-height:15%}.layout-gt-sm-row>.flex-gt-sm-15{max-width:15%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-15,.layout-gt-sm-row>.flex-gt-sm-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-15{max-width:100%;max-height:15%}.flex-gt-sm-20,.layout-row>.flex-gt-sm-20{max-width:20%;max-height:100%}.flex-gt-sm-20,.layout-column>.flex-gt-sm-20,.layout-row>.flex-gt-sm-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-gt-sm-20{max-width:100%;max-height:20%}.layout-gt-sm-row>.flex-gt-sm-20{max-width:20%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-20,.layout-gt-sm-row>.flex-gt-sm-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-20{max-width:100%;max-height:20%}.flex-gt-sm-25,.layout-row>.flex-gt-sm-25{max-width:25%;max-height:100%}.flex-gt-sm-25,.layout-column>.flex-gt-sm-25,.layout-row>.flex-gt-sm-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-gt-sm-25{max-width:100%;max-height:25%}.layout-gt-sm-row>.flex-gt-sm-25{max-width:25%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-25,.layout-gt-sm-row>.flex-gt-sm-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-25{max-width:100%;max-height:25%}.flex-gt-sm-30,.layout-row>.flex-gt-sm-30{max-width:30%;max-height:100%}.flex-gt-sm-30,.layout-column>.flex-gt-sm-30,.layout-row>.flex-gt-sm-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-gt-sm-30{max-width:100%;max-height:30%}.layout-gt-sm-row>.flex-gt-sm-30{max-width:30%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-30,.layout-gt-sm-row>.flex-gt-sm-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-30{max-width:100%;max-height:30%}.flex-gt-sm-35,.layout-row>.flex-gt-sm-35{max-width:35%;max-height:100%}.flex-gt-sm-35,.layout-column>.flex-gt-sm-35,.layout-row>.flex-gt-sm-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-gt-sm-35{max-width:100%;max-height:35%}.layout-gt-sm-row>.flex-gt-sm-35{max-width:35%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-35,.layout-gt-sm-row>.flex-gt-sm-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-35{max-width:100%;max-height:35%}.flex-gt-sm-40,.layout-row>.flex-gt-sm-40{max-width:40%;max-height:100%}.flex-gt-sm-40,.layout-column>.flex-gt-sm-40,.layout-row>.flex-gt-sm-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-gt-sm-40{max-width:100%;max-height:40%}.layout-gt-sm-row>.flex-gt-sm-40{max-width:40%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-40,.layout-gt-sm-row>.flex-gt-sm-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-40{max-width:100%;max-height:40%}.flex-gt-sm-45,.layout-row>.flex-gt-sm-45{max-width:45%;max-height:100%}.flex-gt-sm-45,.layout-column>.flex-gt-sm-45,.layout-row>.flex-gt-sm-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-gt-sm-45{max-width:100%;max-height:45%}.layout-gt-sm-row>.flex-gt-sm-45{max-width:45%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-45,.layout-gt-sm-row>.flex-gt-sm-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-45{max-width:100%;max-height:45%}.flex-gt-sm-50,.layout-row>.flex-gt-sm-50{max-width:50%;max-height:100%}.flex-gt-sm-50,.layout-column>.flex-gt-sm-50,.layout-row>.flex-gt-sm-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-gt-sm-50{max-width:100%;max-height:50%}.layout-gt-sm-row>.flex-gt-sm-50{max-width:50%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-50,.layout-gt-sm-row>.flex-gt-sm-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-50{max-width:100%;max-height:50%}.flex-gt-sm-55,.layout-row>.flex-gt-sm-55{max-width:55%;max-height:100%}.flex-gt-sm-55,.layout-column>.flex-gt-sm-55,.layout-row>.flex-gt-sm-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-gt-sm-55{max-width:100%;max-height:55%}.layout-gt-sm-row>.flex-gt-sm-55{max-width:55%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-55,.layout-gt-sm-row>.flex-gt-sm-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-55{max-width:100%;max-height:55%}.flex-gt-sm-60,.layout-row>.flex-gt-sm-60{max-width:60%;max-height:100%}.flex-gt-sm-60,.layout-column>.flex-gt-sm-60,.layout-row>.flex-gt-sm-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-gt-sm-60{max-width:100%;max-height:60%}.layout-gt-sm-row>.flex-gt-sm-60{max-width:60%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-60,.layout-gt-sm-row>.flex-gt-sm-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-60{max-width:100%;max-height:60%}.flex-gt-sm-65,.layout-row>.flex-gt-sm-65{max-width:65%;max-height:100%}.flex-gt-sm-65,.layout-column>.flex-gt-sm-65,.layout-row>.flex-gt-sm-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-gt-sm-65{max-width:100%;max-height:65%}.layout-gt-sm-row>.flex-gt-sm-65{max-width:65%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-65,.layout-gt-sm-row>.flex-gt-sm-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-65{max-width:100%;max-height:65%}.flex-gt-sm-70,.layout-row>.flex-gt-sm-70{max-width:70%;max-height:100%}.flex-gt-sm-70,.layout-column>.flex-gt-sm-70,.layout-row>.flex-gt-sm-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-gt-sm-70{max-width:100%;max-height:70%}.layout-gt-sm-row>.flex-gt-sm-70{max-width:70%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-70,.layout-gt-sm-row>.flex-gt-sm-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-70{max-width:100%;max-height:70%}.flex-gt-sm-75,.layout-row>.flex-gt-sm-75{max-width:75%;max-height:100%}.flex-gt-sm-75,.layout-column>.flex-gt-sm-75,.layout-row>.flex-gt-sm-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-gt-sm-75{max-width:100%;max-height:75%}.layout-gt-sm-row>.flex-gt-sm-75{max-width:75%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-75,.layout-gt-sm-row>.flex-gt-sm-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-75{max-width:100%;max-height:75%}.flex-gt-sm-80,.layout-row>.flex-gt-sm-80{max-width:80%;max-height:100%}.flex-gt-sm-80,.layout-column>.flex-gt-sm-80,.layout-row>.flex-gt-sm-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-gt-sm-80{max-width:100%;max-height:80%}.layout-gt-sm-row>.flex-gt-sm-80{max-width:80%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-80,.layout-gt-sm-row>.flex-gt-sm-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-80{max-width:100%;max-height:80%}.flex-gt-sm-85,.layout-row>.flex-gt-sm-85{max-width:85%;max-height:100%}.flex-gt-sm-85,.layout-column>.flex-gt-sm-85,.layout-row>.flex-gt-sm-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-gt-sm-85{max-width:100%;max-height:85%}.layout-gt-sm-row>.flex-gt-sm-85{max-width:85%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-85,.layout-gt-sm-row>.flex-gt-sm-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-85{max-width:100%;max-height:85%}.flex-gt-sm-90,.layout-row>.flex-gt-sm-90{max-width:90%;max-height:100%}.flex-gt-sm-90,.layout-column>.flex-gt-sm-90,.layout-row>.flex-gt-sm-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-gt-sm-90{max-width:100%;max-height:90%}.layout-gt-sm-row>.flex-gt-sm-90{max-width:90%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-90,.layout-gt-sm-row>.flex-gt-sm-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-90{max-width:100%;max-height:90%}.flex-gt-sm-95,.layout-row>.flex-gt-sm-95{max-width:95%;max-height:100%}.flex-gt-sm-95,.layout-column>.flex-gt-sm-95,.layout-row>.flex-gt-sm-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-gt-sm-95{max-width:100%;max-height:95%}.layout-gt-sm-row>.flex-gt-sm-95{max-width:95%;max-height:100%}.layout-gt-sm-column>.flex-gt-sm-95,.layout-gt-sm-row>.flex-gt-sm-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-95{max-width:100%;max-height:95%}.flex-gt-sm-100,.layout-column>.flex-gt-sm-100,.layout-row>.flex-gt-sm-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-100,.layout-gt-sm-row>.flex-gt-sm-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex-gt-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex-gt-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex{min-width:0}.layout-gt-sm-column>.flex-gt-sm-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-gt-sm-column>.flex-gt-sm-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-sm-column>.flex{min-height:0}.layout-gt-sm,.layout-gt-sm-column,.layout-gt-sm-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-gt-sm-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-gt-sm-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:960px) and (max-width:1279px){.hide-gt-sm:not(.show-gt-xs):not(.show-gt-sm):not(.show-md):not(.show),.hide-gt-xs:not(.show-gt-xs):not(.show-gt-sm):not(.show-md):not(.show),.hide-md:not(.show-md):not(.show-gt-sm):not(.show-gt-xs):not(.show),.hide:not(.show-gt-xs):not(.show-gt-sm):not(.show-md):not(.show){display:none}.flex-order-md--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-md--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-md--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-md--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-md--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-md--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-md--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-md--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-md--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-md--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-md--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-md--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-md--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-md--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-md--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-md--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-md--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-md--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-md--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-md--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-md-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-md-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-md-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-md-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-md-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-md-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-md-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-md-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-md-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-md-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-md-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-md-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-md-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-md-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-md-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-md-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-md-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-md-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-md-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-md-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-md-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-md-0,.offset-md-0{margin-left:0}[dir=rtl] .flex-offset-md-0,[dir=rtl] .offset-md-0{margin-left:auto;margin-right:0}.flex-offset-md-5,.offset-md-5{margin-left:5%}[dir=rtl] .flex-offset-md-5,[dir=rtl] .offset-md-5{margin-left:auto;margin-right:5%}.flex-offset-md-10,.offset-md-10{margin-left:10%}[dir=rtl] .flex-offset-md-10,[dir=rtl] .offset-md-10{margin-left:auto;margin-right:10%}.flex-offset-md-15,.offset-md-15{margin-left:15%}[dir=rtl] .flex-offset-md-15,[dir=rtl] .offset-md-15{margin-left:auto;margin-right:15%}.flex-offset-md-20,.offset-md-20{margin-left:20%}[dir=rtl] .flex-offset-md-20,[dir=rtl] .offset-md-20{margin-left:auto;margin-right:20%}.flex-offset-md-25,.offset-md-25{margin-left:25%}[dir=rtl] .flex-offset-md-25,[dir=rtl] .offset-md-25{margin-left:auto;margin-right:25%}.flex-offset-md-30,.offset-md-30{margin-left:30%}[dir=rtl] .flex-offset-md-30,[dir=rtl] .offset-md-30{margin-left:auto;margin-right:30%}.flex-offset-md-35,.offset-md-35{margin-left:35%}[dir=rtl] .flex-offset-md-35,[dir=rtl] .offset-md-35{margin-left:auto;margin-right:35%}.flex-offset-md-40,.offset-md-40{margin-left:40%}[dir=rtl] .flex-offset-md-40,[dir=rtl] .offset-md-40{margin-left:auto;margin-right:40%}.flex-offset-md-45,.offset-md-45{margin-left:45%}[dir=rtl] .flex-offset-md-45,[dir=rtl] .offset-md-45{margin-left:auto;margin-right:45%}.flex-offset-md-50,.offset-md-50{margin-left:50%}[dir=rtl] .flex-offset-md-50,[dir=rtl] .offset-md-50{margin-left:auto;margin-right:50%}.flex-offset-md-55,.offset-md-55{margin-left:55%}[dir=rtl] .flex-offset-md-55,[dir=rtl] .offset-md-55{margin-left:auto;margin-right:55%}.flex-offset-md-60,.offset-md-60{margin-left:60%}[dir=rtl] .flex-offset-md-60,[dir=rtl] .offset-md-60{margin-left:auto;margin-right:60%}.flex-offset-md-65,.offset-md-65{margin-left:65%}[dir=rtl] .flex-offset-md-65,[dir=rtl] .offset-md-65{margin-left:auto;margin-right:65%}.flex-offset-md-70,.offset-md-70{margin-left:70%}[dir=rtl] .flex-offset-md-70,[dir=rtl] .offset-md-70{margin-left:auto;margin-right:70%}.flex-offset-md-75,.offset-md-75{margin-left:75%}[dir=rtl] .flex-offset-md-75,[dir=rtl] .offset-md-75{margin-left:auto;margin-right:75%}.flex-offset-md-80,.offset-md-80{margin-left:80%}[dir=rtl] .flex-offset-md-80,[dir=rtl] .offset-md-80{margin-left:auto;margin-right:80%}.flex-offset-md-85,.offset-md-85{margin-left:85%}[dir=rtl] .flex-offset-md-85,[dir=rtl] .offset-md-85{margin-left:auto;margin-right:85%}.flex-offset-md-90,.offset-md-90{margin-left:90%}[dir=rtl] .flex-offset-md-90,[dir=rtl] .offset-md-90{margin-left:auto;margin-right:90%}.flex-offset-md-95,.offset-md-95{margin-left:95%}[dir=rtl] .flex-offset-md-95,[dir=rtl] .offset-md-95{margin-left:auto;margin-right:95%}.flex-offset-md-33,.offset-md-33{margin-left:33.33333%}.flex-offset-md-66,.offset-md-66{margin-left:66.66667%}[dir=rtl] .flex-offset-md-66,[dir=rtl] .offset-md-66{margin-left:auto;margin-right:66.66667%}.layout-align-md,.layout-align-md-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-md,.layout-align-md-start,.layout-align-md-start-center,.layout-align-md-start-end,.layout-align-md-start-start,.layout-align-md-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-md-center,.layout-align-md-center-center,.layout-align-md-center-end,.layout-align-md-center-start,.layout-align-md-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-md-end,.layout-align-md-end-center,.layout-align-md-end-end,.layout-align-md-end-start,.layout-align-md-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-md-space-around,.layout-align-md-space-around-center,.layout-align-md-space-around-end,.layout-align-md-space-around-start,.layout-align-md-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-md-space-between,.layout-align-md-space-between-center,.layout-align-md-space-between-end,.layout-align-md-space-between-start,.layout-align-md-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-md-center-start,.layout-align-md-end-start,.layout-align-md-space-around-start,.layout-align-md-space-between-start,.layout-align-md-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-md-center-center,.layout-align-md-end-center,.layout-align-md-space-around-center,.layout-align-md-space-between-center,.layout-align-md-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-md-center-center>*,.layout-align-md-end-center>*,.layout-align-md-space-around-center>*,.layout-align-md-space-between-center>*,.layout-align-md-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-md-center-end,.layout-align-md-end-end,.layout-align-md-space-around-end,.layout-align-md-space-between-end,.layout-align-md-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-md-center-stretch,.layout-align-md-end-stretch,.layout-align-md-space-around-stretch,.layout-align-md-space-between-stretch,.layout-align-md-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-md{-webkit-flex:1;flex:1}.flex-md,.flex-md-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-md-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-md-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-md-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-md-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-md-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-md-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-md-0,.layout-row>.flex-md-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-md-0{min-width:0}.layout-column>.flex-md-0{max-width:100%;max-height:0%}.layout-column>.flex-md-0,.layout-md-row>.flex-md-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-md-row>.flex-md-0{max-width:0;max-height:100%;min-width:0}.layout-md-column>.flex-md-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-md-5,.layout-row>.flex-md-5{max-width:5%;max-height:100%}.flex-md-5,.layout-column>.flex-md-5,.layout-row>.flex-md-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-md-5{max-width:100%;max-height:5%}.layout-md-row>.flex-md-5{max-width:5%;max-height:100%}.layout-md-column>.flex-md-5,.layout-md-row>.flex-md-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-md-column>.flex-md-5{max-width:100%;max-height:5%}.flex-md-10,.layout-row>.flex-md-10{max-width:10%;max-height:100%}.flex-md-10,.layout-column>.flex-md-10,.layout-row>.flex-md-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-md-10{max-width:100%;max-height:10%}.layout-md-row>.flex-md-10{max-width:10%;max-height:100%}.layout-md-column>.flex-md-10,.layout-md-row>.flex-md-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-md-column>.flex-md-10{max-width:100%;max-height:10%}.flex-md-15,.layout-row>.flex-md-15{max-width:15%;max-height:100%}.flex-md-15,.layout-column>.flex-md-15,.layout-row>.flex-md-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-md-15{max-width:100%;max-height:15%}.layout-md-row>.flex-md-15{max-width:15%;max-height:100%}.layout-md-column>.flex-md-15,.layout-md-row>.flex-md-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-md-column>.flex-md-15{max-width:100%;max-height:15%}.flex-md-20,.layout-row>.flex-md-20{max-width:20%;max-height:100%}.flex-md-20,.layout-column>.flex-md-20,.layout-row>.flex-md-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-md-20{max-width:100%;max-height:20%}.layout-md-row>.flex-md-20{max-width:20%;max-height:100%}.layout-md-column>.flex-md-20,.layout-md-row>.flex-md-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-md-column>.flex-md-20{max-width:100%;max-height:20%}.flex-md-25,.layout-row>.flex-md-25{max-width:25%;max-height:100%}.flex-md-25,.layout-column>.flex-md-25,.layout-row>.flex-md-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-md-25{max-width:100%;max-height:25%}.layout-md-row>.flex-md-25{max-width:25%;max-height:100%}.layout-md-column>.flex-md-25,.layout-md-row>.flex-md-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-md-column>.flex-md-25{max-width:100%;max-height:25%}.flex-md-30,.layout-row>.flex-md-30{max-width:30%;max-height:100%}.flex-md-30,.layout-column>.flex-md-30,.layout-row>.flex-md-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-md-30{max-width:100%;max-height:30%}.layout-md-row>.flex-md-30{max-width:30%;max-height:100%}.layout-md-column>.flex-md-30,.layout-md-row>.flex-md-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-md-column>.flex-md-30{max-width:100%;max-height:30%}.flex-md-35,.layout-row>.flex-md-35{max-width:35%;max-height:100%}.flex-md-35,.layout-column>.flex-md-35,.layout-row>.flex-md-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-md-35{max-width:100%;max-height:35%}.layout-md-row>.flex-md-35{max-width:35%;max-height:100%}.layout-md-column>.flex-md-35,.layout-md-row>.flex-md-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-md-column>.flex-md-35{max-width:100%;max-height:35%}.flex-md-40,.layout-row>.flex-md-40{max-width:40%;max-height:100%}.flex-md-40,.layout-column>.flex-md-40,.layout-row>.flex-md-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-md-40{max-width:100%;max-height:40%}.layout-md-row>.flex-md-40{max-width:40%;max-height:100%}.layout-md-column>.flex-md-40,.layout-md-row>.flex-md-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-md-column>.flex-md-40{max-width:100%;max-height:40%}.flex-md-45,.layout-row>.flex-md-45{max-width:45%;max-height:100%}.flex-md-45,.layout-column>.flex-md-45,.layout-row>.flex-md-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-md-45{max-width:100%;max-height:45%}.layout-md-row>.flex-md-45{max-width:45%;max-height:100%}.layout-md-column>.flex-md-45,.layout-md-row>.flex-md-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-md-column>.flex-md-45{max-width:100%;max-height:45%}.flex-md-50,.layout-row>.flex-md-50{max-width:50%;max-height:100%}.flex-md-50,.layout-column>.flex-md-50,.layout-row>.flex-md-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-md-50{max-width:100%;max-height:50%}.layout-md-row>.flex-md-50{max-width:50%;max-height:100%}.layout-md-column>.flex-md-50,.layout-md-row>.flex-md-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-md-column>.flex-md-50{max-width:100%;max-height:50%}.flex-md-55,.layout-row>.flex-md-55{max-width:55%;max-height:100%}.flex-md-55,.layout-column>.flex-md-55,.layout-row>.flex-md-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-md-55{max-width:100%;max-height:55%}.layout-md-row>.flex-md-55{max-width:55%;max-height:100%}.layout-md-column>.flex-md-55,.layout-md-row>.flex-md-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-md-column>.flex-md-55{max-width:100%;max-height:55%}.flex-md-60,.layout-row>.flex-md-60{max-width:60%;max-height:100%}.flex-md-60,.layout-column>.flex-md-60,.layout-row>.flex-md-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-md-60{max-width:100%;max-height:60%}.layout-md-row>.flex-md-60{max-width:60%;max-height:100%}.layout-md-column>.flex-md-60,.layout-md-row>.flex-md-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-md-column>.flex-md-60{max-width:100%;max-height:60%}.flex-md-65,.layout-row>.flex-md-65{max-width:65%;max-height:100%}.flex-md-65,.layout-column>.flex-md-65,.layout-row>.flex-md-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-md-65{max-width:100%;max-height:65%}.layout-md-row>.flex-md-65{max-width:65%;max-height:100%}.layout-md-column>.flex-md-65,.layout-md-row>.flex-md-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-md-column>.flex-md-65{max-width:100%;max-height:65%}.flex-md-70,.layout-row>.flex-md-70{max-width:70%;max-height:100%}.flex-md-70,.layout-column>.flex-md-70,.layout-row>.flex-md-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-md-70{max-width:100%;max-height:70%}.layout-md-row>.flex-md-70{max-width:70%;max-height:100%}.layout-md-column>.flex-md-70,.layout-md-row>.flex-md-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-md-column>.flex-md-70{max-width:100%;max-height:70%}.flex-md-75,.layout-row>.flex-md-75{max-width:75%;max-height:100%}.flex-md-75,.layout-column>.flex-md-75,.layout-row>.flex-md-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-md-75{max-width:100%;max-height:75%}.layout-md-row>.flex-md-75{max-width:75%;max-height:100%}.layout-md-column>.flex-md-75,.layout-md-row>.flex-md-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-md-column>.flex-md-75{max-width:100%;max-height:75%}.flex-md-80,.layout-row>.flex-md-80{max-width:80%;max-height:100%}.flex-md-80,.layout-column>.flex-md-80,.layout-row>.flex-md-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-md-80{max-width:100%;max-height:80%}.layout-md-row>.flex-md-80{max-width:80%;max-height:100%}.layout-md-column>.flex-md-80,.layout-md-row>.flex-md-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-md-column>.flex-md-80{max-width:100%;max-height:80%}.flex-md-85,.layout-row>.flex-md-85{max-width:85%;max-height:100%}.flex-md-85,.layout-column>.flex-md-85,.layout-row>.flex-md-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-md-85{max-width:100%;max-height:85%}.layout-md-row>.flex-md-85{max-width:85%;max-height:100%}.layout-md-column>.flex-md-85,.layout-md-row>.flex-md-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-md-column>.flex-md-85{max-width:100%;max-height:85%}.flex-md-90,.layout-row>.flex-md-90{max-width:90%;max-height:100%}.flex-md-90,.layout-column>.flex-md-90,.layout-row>.flex-md-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-md-90{max-width:100%;max-height:90%}.layout-md-row>.flex-md-90{max-width:90%;max-height:100%}.layout-md-column>.flex-md-90,.layout-md-row>.flex-md-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-md-column>.flex-md-90{max-width:100%;max-height:90%}.flex-md-95,.layout-row>.flex-md-95{max-width:95%;max-height:100%}.flex-md-95,.layout-column>.flex-md-95,.layout-row>.flex-md-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-md-95{max-width:100%;max-height:95%}.layout-md-row>.flex-md-95{max-width:95%;max-height:100%}.layout-md-column>.flex-md-95,.layout-md-row>.flex-md-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-md-column>.flex-md-95{max-width:100%;max-height:95%}.flex-md-100,.layout-column>.flex-md-100,.layout-row>.flex-md-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-md-column>.flex-md-100,.layout-md-row>.flex-md-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-md-row>.flex-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-md-row>.flex-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-md-row>.flex{min-width:0}.layout-md-column>.flex-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-md-column>.flex-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-md-column>.flex{min-height:0}.layout-md,.layout-md-column,.layout-md-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-md-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-md-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:1280px){.flex-order-gt-md--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-gt-md--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-gt-md--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-gt-md--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-gt-md--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-gt-md--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-gt-md--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-gt-md--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-gt-md--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-gt-md--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-gt-md--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-gt-md--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-gt-md--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-gt-md--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-gt-md--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-gt-md--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-gt-md--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-gt-md--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-gt-md--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-gt-md--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-gt-md-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-gt-md-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-gt-md-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-gt-md-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-gt-md-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-gt-md-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-gt-md-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-gt-md-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-gt-md-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-gt-md-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-gt-md-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-gt-md-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-gt-md-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-gt-md-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-gt-md-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-gt-md-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-gt-md-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-gt-md-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-gt-md-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-gt-md-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-gt-md-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-gt-md-0,.offset-gt-md-0{margin-left:0}[dir=rtl] .flex-offset-gt-md-0,[dir=rtl] .offset-gt-md-0{margin-left:auto;margin-right:0}.flex-offset-gt-md-5,.offset-gt-md-5{margin-left:5%}[dir=rtl] .flex-offset-gt-md-5,[dir=rtl] .offset-gt-md-5{margin-left:auto;margin-right:5%}.flex-offset-gt-md-10,.offset-gt-md-10{margin-left:10%}[dir=rtl] .flex-offset-gt-md-10,[dir=rtl] .offset-gt-md-10{margin-left:auto;margin-right:10%}.flex-offset-gt-md-15,.offset-gt-md-15{margin-left:15%}[dir=rtl] .flex-offset-gt-md-15,[dir=rtl] .offset-gt-md-15{margin-left:auto;margin-right:15%}.flex-offset-gt-md-20,.offset-gt-md-20{margin-left:20%}[dir=rtl] .flex-offset-gt-md-20,[dir=rtl] .offset-gt-md-20{margin-left:auto;margin-right:20%}.flex-offset-gt-md-25,.offset-gt-md-25{margin-left:25%}[dir=rtl] .flex-offset-gt-md-25,[dir=rtl] .offset-gt-md-25{margin-left:auto;margin-right:25%}.flex-offset-gt-md-30,.offset-gt-md-30{margin-left:30%}[dir=rtl] .flex-offset-gt-md-30,[dir=rtl] .offset-gt-md-30{margin-left:auto;margin-right:30%}.flex-offset-gt-md-35,.offset-gt-md-35{margin-left:35%}[dir=rtl] .flex-offset-gt-md-35,[dir=rtl] .offset-gt-md-35{margin-left:auto;margin-right:35%}.flex-offset-gt-md-40,.offset-gt-md-40{margin-left:40%}[dir=rtl] .flex-offset-gt-md-40,[dir=rtl] .offset-gt-md-40{margin-left:auto;margin-right:40%}.flex-offset-gt-md-45,.offset-gt-md-45{margin-left:45%}[dir=rtl] .flex-offset-gt-md-45,[dir=rtl] .offset-gt-md-45{margin-left:auto;margin-right:45%}.flex-offset-gt-md-50,.offset-gt-md-50{margin-left:50%}[dir=rtl] .flex-offset-gt-md-50,[dir=rtl] .offset-gt-md-50{margin-left:auto;margin-right:50%}.flex-offset-gt-md-55,.offset-gt-md-55{margin-left:55%}[dir=rtl] .flex-offset-gt-md-55,[dir=rtl] .offset-gt-md-55{margin-left:auto;margin-right:55%}.flex-offset-gt-md-60,.offset-gt-md-60{margin-left:60%}[dir=rtl] .flex-offset-gt-md-60,[dir=rtl] .offset-gt-md-60{margin-left:auto;margin-right:60%}.flex-offset-gt-md-65,.offset-gt-md-65{margin-left:65%}[dir=rtl] .flex-offset-gt-md-65,[dir=rtl] .offset-gt-md-65{margin-left:auto;margin-right:65%}.flex-offset-gt-md-70,.offset-gt-md-70{margin-left:70%}[dir=rtl] .flex-offset-gt-md-70,[dir=rtl] .offset-gt-md-70{margin-left:auto;margin-right:70%}.flex-offset-gt-md-75,.offset-gt-md-75{margin-left:75%}[dir=rtl] .flex-offset-gt-md-75,[dir=rtl] .offset-gt-md-75{margin-left:auto;margin-right:75%}.flex-offset-gt-md-80,.offset-gt-md-80{margin-left:80%}[dir=rtl] .flex-offset-gt-md-80,[dir=rtl] .offset-gt-md-80{margin-left:auto;margin-right:80%}.flex-offset-gt-md-85,.offset-gt-md-85{margin-left:85%}[dir=rtl] .flex-offset-gt-md-85,[dir=rtl] .offset-gt-md-85{margin-left:auto;margin-right:85%}.flex-offset-gt-md-90,.offset-gt-md-90{margin-left:90%}[dir=rtl] .flex-offset-gt-md-90,[dir=rtl] .offset-gt-md-90{margin-left:auto;margin-right:90%}.flex-offset-gt-md-95,.offset-gt-md-95{margin-left:95%}[dir=rtl] .flex-offset-gt-md-95,[dir=rtl] .offset-gt-md-95{margin-left:auto;margin-right:95%}.flex-offset-gt-md-33,.offset-gt-md-33{margin-left:33.33333%}.flex-offset-gt-md-66,.offset-gt-md-66{margin-left:66.66667%}[dir=rtl] .flex-offset-gt-md-66,[dir=rtl] .offset-gt-md-66{margin-left:auto;margin-right:66.66667%}.layout-align-gt-md,.layout-align-gt-md-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-gt-md,.layout-align-gt-md-start,.layout-align-gt-md-start-center,.layout-align-gt-md-start-end,.layout-align-gt-md-start-start,.layout-align-gt-md-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-gt-md-center,.layout-align-gt-md-center-center,.layout-align-gt-md-center-end,.layout-align-gt-md-center-start,.layout-align-gt-md-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-gt-md-end,.layout-align-gt-md-end-center,.layout-align-gt-md-end-end,.layout-align-gt-md-end-start,.layout-align-gt-md-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-gt-md-space-around,.layout-align-gt-md-space-around-center,.layout-align-gt-md-space-around-end,.layout-align-gt-md-space-around-start,.layout-align-gt-md-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-gt-md-space-between,.layout-align-gt-md-space-between-center,.layout-align-gt-md-space-between-end,.layout-align-gt-md-space-between-start,.layout-align-gt-md-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-gt-md-center-start,.layout-align-gt-md-end-start,.layout-align-gt-md-space-around-start,.layout-align-gt-md-space-between-start,.layout-align-gt-md-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-gt-md-center-center,.layout-align-gt-md-end-center,.layout-align-gt-md-space-around-center,.layout-align-gt-md-space-between-center,.layout-align-gt-md-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-gt-md-center-center>*,.layout-align-gt-md-end-center>*,.layout-align-gt-md-space-around-center>*,.layout-align-gt-md-space-between-center>*,.layout-align-gt-md-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-md-center-end,.layout-align-gt-md-end-end,.layout-align-gt-md-space-around-end,.layout-align-gt-md-space-between-end,.layout-align-gt-md-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-gt-md-center-stretch,.layout-align-gt-md-end-stretch,.layout-align-gt-md-space-around-stretch,.layout-align-gt-md-space-between-stretch,.layout-align-gt-md-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-gt-md{-webkit-flex:1;flex:1}.flex-gt-md,.flex-gt-md-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-gt-md-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-gt-md-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-md-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-md-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-md-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-md-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-md-0,.layout-row>.flex-gt-md-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-md-0{min-width:0}.layout-column>.flex-gt-md-0{max-width:100%;max-height:0%}.layout-column>.flex-gt-md-0,.layout-gt-md-row>.flex-gt-md-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-gt-md-row>.flex-gt-md-0{max-width:0;max-height:100%;min-width:0}.layout-gt-md-column>.flex-gt-md-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-gt-md-5,.layout-row>.flex-gt-md-5{max-width:5%;max-height:100%}.flex-gt-md-5,.layout-column>.flex-gt-md-5,.layout-row>.flex-gt-md-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-gt-md-5{max-width:100%;max-height:5%}.layout-gt-md-row>.flex-gt-md-5{max-width:5%;max-height:100%}.layout-gt-md-column>.flex-gt-md-5,.layout-gt-md-row>.flex-gt-md-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-5{max-width:100%;max-height:5%}.flex-gt-md-10,.layout-row>.flex-gt-md-10{max-width:10%;max-height:100%}.flex-gt-md-10,.layout-column>.flex-gt-md-10,.layout-row>.flex-gt-md-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-gt-md-10{max-width:100%;max-height:10%}.layout-gt-md-row>.flex-gt-md-10{max-width:10%;max-height:100%}.layout-gt-md-column>.flex-gt-md-10,.layout-gt-md-row>.flex-gt-md-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-10{max-width:100%;max-height:10%}.flex-gt-md-15,.layout-row>.flex-gt-md-15{max-width:15%;max-height:100%}.flex-gt-md-15,.layout-column>.flex-gt-md-15,.layout-row>.flex-gt-md-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-gt-md-15{max-width:100%;max-height:15%}.layout-gt-md-row>.flex-gt-md-15{max-width:15%;max-height:100%}.layout-gt-md-column>.flex-gt-md-15,.layout-gt-md-row>.flex-gt-md-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-15{max-width:100%;max-height:15%}.flex-gt-md-20,.layout-row>.flex-gt-md-20{max-width:20%;max-height:100%}.flex-gt-md-20,.layout-column>.flex-gt-md-20,.layout-row>.flex-gt-md-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-gt-md-20{max-width:100%;max-height:20%}.layout-gt-md-row>.flex-gt-md-20{max-width:20%;max-height:100%}.layout-gt-md-column>.flex-gt-md-20,.layout-gt-md-row>.flex-gt-md-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-20{max-width:100%;max-height:20%}.flex-gt-md-25,.layout-row>.flex-gt-md-25{max-width:25%;max-height:100%}.flex-gt-md-25,.layout-column>.flex-gt-md-25,.layout-row>.flex-gt-md-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-gt-md-25{max-width:100%;max-height:25%}.layout-gt-md-row>.flex-gt-md-25{max-width:25%;max-height:100%}.layout-gt-md-column>.flex-gt-md-25,.layout-gt-md-row>.flex-gt-md-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-25{max-width:100%;max-height:25%}.flex-gt-md-30,.layout-row>.flex-gt-md-30{max-width:30%;max-height:100%}.flex-gt-md-30,.layout-column>.flex-gt-md-30,.layout-row>.flex-gt-md-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-gt-md-30{max-width:100%;max-height:30%}.layout-gt-md-row>.flex-gt-md-30{max-width:30%;max-height:100%}.layout-gt-md-column>.flex-gt-md-30,.layout-gt-md-row>.flex-gt-md-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-30{max-width:100%;max-height:30%}.flex-gt-md-35,.layout-row>.flex-gt-md-35{max-width:35%;max-height:100%}.flex-gt-md-35,.layout-column>.flex-gt-md-35,.layout-row>.flex-gt-md-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-gt-md-35{max-width:100%;max-height:35%}.layout-gt-md-row>.flex-gt-md-35{max-width:35%;max-height:100%}.layout-gt-md-column>.flex-gt-md-35,.layout-gt-md-row>.flex-gt-md-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-35{max-width:100%;max-height:35%}.flex-gt-md-40,.layout-row>.flex-gt-md-40{max-width:40%;max-height:100%}.flex-gt-md-40,.layout-column>.flex-gt-md-40,.layout-row>.flex-gt-md-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-gt-md-40{max-width:100%;max-height:40%}.layout-gt-md-row>.flex-gt-md-40{max-width:40%;max-height:100%}.layout-gt-md-column>.flex-gt-md-40,.layout-gt-md-row>.flex-gt-md-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-40{max-width:100%;max-height:40%}.flex-gt-md-45,.layout-row>.flex-gt-md-45{max-width:45%;max-height:100%}.flex-gt-md-45,.layout-column>.flex-gt-md-45,.layout-row>.flex-gt-md-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-gt-md-45{max-width:100%;max-height:45%}.layout-gt-md-row>.flex-gt-md-45{max-width:45%;max-height:100%}.layout-gt-md-column>.flex-gt-md-45,.layout-gt-md-row>.flex-gt-md-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-45{max-width:100%;max-height:45%}.flex-gt-md-50,.layout-row>.flex-gt-md-50{max-width:50%;max-height:100%}.flex-gt-md-50,.layout-column>.flex-gt-md-50,.layout-row>.flex-gt-md-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-gt-md-50{max-width:100%;max-height:50%}.layout-gt-md-row>.flex-gt-md-50{max-width:50%;max-height:100%}.layout-gt-md-column>.flex-gt-md-50,.layout-gt-md-row>.flex-gt-md-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-50{max-width:100%;max-height:50%}.flex-gt-md-55,.layout-row>.flex-gt-md-55{max-width:55%;max-height:100%}.flex-gt-md-55,.layout-column>.flex-gt-md-55,.layout-row>.flex-gt-md-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-gt-md-55{max-width:100%;max-height:55%}.layout-gt-md-row>.flex-gt-md-55{max-width:55%;max-height:100%}.layout-gt-md-column>.flex-gt-md-55,.layout-gt-md-row>.flex-gt-md-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-55{max-width:100%;max-height:55%}.flex-gt-md-60,.layout-row>.flex-gt-md-60{max-width:60%;max-height:100%}.flex-gt-md-60,.layout-column>.flex-gt-md-60,.layout-row>.flex-gt-md-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-gt-md-60{max-width:100%;max-height:60%}.layout-gt-md-row>.flex-gt-md-60{max-width:60%;max-height:100%}.layout-gt-md-column>.flex-gt-md-60,.layout-gt-md-row>.flex-gt-md-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-60{max-width:100%;max-height:60%}.flex-gt-md-65,.layout-row>.flex-gt-md-65{max-width:65%;max-height:100%}.flex-gt-md-65,.layout-column>.flex-gt-md-65,.layout-row>.flex-gt-md-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-gt-md-65{max-width:100%;max-height:65%}.layout-gt-md-row>.flex-gt-md-65{max-width:65%;max-height:100%}.layout-gt-md-column>.flex-gt-md-65,.layout-gt-md-row>.flex-gt-md-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-65{max-width:100%;max-height:65%}.flex-gt-md-70,.layout-row>.flex-gt-md-70{max-width:70%;max-height:100%}.flex-gt-md-70,.layout-column>.flex-gt-md-70,.layout-row>.flex-gt-md-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-gt-md-70{max-width:100%;max-height:70%}.layout-gt-md-row>.flex-gt-md-70{max-width:70%;max-height:100%}.layout-gt-md-column>.flex-gt-md-70,.layout-gt-md-row>.flex-gt-md-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-70{max-width:100%;max-height:70%}.flex-gt-md-75,.layout-row>.flex-gt-md-75{max-width:75%;max-height:100%}.flex-gt-md-75,.layout-column>.flex-gt-md-75,.layout-row>.flex-gt-md-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-gt-md-75{max-width:100%;max-height:75%}.layout-gt-md-row>.flex-gt-md-75{max-width:75%;max-height:100%}.layout-gt-md-column>.flex-gt-md-75,.layout-gt-md-row>.flex-gt-md-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-75{max-width:100%;max-height:75%}.flex-gt-md-80,.layout-row>.flex-gt-md-80{max-width:80%;max-height:100%}.flex-gt-md-80,.layout-column>.flex-gt-md-80,.layout-row>.flex-gt-md-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-gt-md-80{max-width:100%;max-height:80%}.layout-gt-md-row>.flex-gt-md-80{max-width:80%;max-height:100%}.layout-gt-md-column>.flex-gt-md-80,.layout-gt-md-row>.flex-gt-md-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-80{max-width:100%;max-height:80%}.flex-gt-md-85,.layout-row>.flex-gt-md-85{max-width:85%;max-height:100%}.flex-gt-md-85,.layout-column>.flex-gt-md-85,.layout-row>.flex-gt-md-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-gt-md-85{max-width:100%;max-height:85%}.layout-gt-md-row>.flex-gt-md-85{max-width:85%;max-height:100%}.layout-gt-md-column>.flex-gt-md-85,.layout-gt-md-row>.flex-gt-md-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-85{max-width:100%;max-height:85%}.flex-gt-md-90,.layout-row>.flex-gt-md-90{max-width:90%;max-height:100%}.flex-gt-md-90,.layout-column>.flex-gt-md-90,.layout-row>.flex-gt-md-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-gt-md-90{max-width:100%;max-height:90%}.layout-gt-md-row>.flex-gt-md-90{max-width:90%;max-height:100%}.layout-gt-md-column>.flex-gt-md-90,.layout-gt-md-row>.flex-gt-md-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-90{max-width:100%;max-height:90%}.flex-gt-md-95,.layout-row>.flex-gt-md-95{max-width:95%;max-height:100%}.flex-gt-md-95,.layout-column>.flex-gt-md-95,.layout-row>.flex-gt-md-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-gt-md-95{max-width:100%;max-height:95%}.layout-gt-md-row>.flex-gt-md-95{max-width:95%;max-height:100%}.layout-gt-md-column>.flex-gt-md-95,.layout-gt-md-row>.flex-gt-md-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-95{max-width:100%;max-height:95%}.flex-gt-md-100,.layout-column>.flex-gt-md-100,.layout-row>.flex-gt-md-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-100,.layout-gt-md-row>.flex-gt-md-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex-gt-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex-gt-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex{min-width:0}.layout-gt-md-column>.flex-gt-md-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-gt-md-column>.flex-gt-md-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-md-column>.flex{min-height:0}.layout-gt-md,.layout-gt-md-column,.layout-gt-md-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-gt-md-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-gt-md-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:1280px) and (max-width:1919px){.hide-gt-md:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show),.hide-gt-sm:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show),.hide-gt-xs:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show),.hide-lg:not(.show-lg):not(.show-gt-md):not(.show-gt-sm):not(.show-gt-xs):not(.show),.hide:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show){display:none}.flex-order-lg--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-lg--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-lg--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-lg--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-lg--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-lg--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-lg--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-lg--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-lg--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-lg--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-lg--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-lg--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-lg--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-lg--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-lg--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-lg--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-lg--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-lg--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-lg--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-lg--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-lg-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-lg-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-lg-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-lg-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-lg-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-lg-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-lg-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-lg-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-lg-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-lg-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-lg-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-lg-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-lg-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-lg-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-lg-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-lg-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-lg-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-lg-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-lg-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-lg-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-lg-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-lg-0,.offset-lg-0{margin-left:0}[dir=rtl] .flex-offset-lg-0,[dir=rtl] .offset-lg-0{margin-left:auto;margin-right:0}.flex-offset-lg-5,.offset-lg-5{margin-left:5%}[dir=rtl] .flex-offset-lg-5,[dir=rtl] .offset-lg-5{margin-left:auto;margin-right:5%}.flex-offset-lg-10,.offset-lg-10{margin-left:10%}[dir=rtl] .flex-offset-lg-10,[dir=rtl] .offset-lg-10{margin-left:auto;margin-right:10%}.flex-offset-lg-15,.offset-lg-15{margin-left:15%}[dir=rtl] .flex-offset-lg-15,[dir=rtl] .offset-lg-15{margin-left:auto;margin-right:15%}.flex-offset-lg-20,.offset-lg-20{margin-left:20%}[dir=rtl] .flex-offset-lg-20,[dir=rtl] .offset-lg-20{margin-left:auto;margin-right:20%}.flex-offset-lg-25,.offset-lg-25{margin-left:25%}[dir=rtl] .flex-offset-lg-25,[dir=rtl] .offset-lg-25{margin-left:auto;margin-right:25%}.flex-offset-lg-30,.offset-lg-30{margin-left:30%}[dir=rtl] .flex-offset-lg-30,[dir=rtl] .offset-lg-30{margin-left:auto;margin-right:30%}.flex-offset-lg-35,.offset-lg-35{margin-left:35%}[dir=rtl] .flex-offset-lg-35,[dir=rtl] .offset-lg-35{margin-left:auto;margin-right:35%}.flex-offset-lg-40,.offset-lg-40{margin-left:40%}[dir=rtl] .flex-offset-lg-40,[dir=rtl] .offset-lg-40{margin-left:auto;margin-right:40%}.flex-offset-lg-45,.offset-lg-45{margin-left:45%}[dir=rtl] .flex-offset-lg-45,[dir=rtl] .offset-lg-45{margin-left:auto;margin-right:45%}.flex-offset-lg-50,.offset-lg-50{margin-left:50%}[dir=rtl] .flex-offset-lg-50,[dir=rtl] .offset-lg-50{margin-left:auto;margin-right:50%}.flex-offset-lg-55,.offset-lg-55{margin-left:55%}[dir=rtl] .flex-offset-lg-55,[dir=rtl] .offset-lg-55{margin-left:auto;margin-right:55%}.flex-offset-lg-60,.offset-lg-60{margin-left:60%}[dir=rtl] .flex-offset-lg-60,[dir=rtl] .offset-lg-60{margin-left:auto;margin-right:60%}.flex-offset-lg-65,.offset-lg-65{margin-left:65%}[dir=rtl] .flex-offset-lg-65,[dir=rtl] .offset-lg-65{margin-left:auto;margin-right:65%}.flex-offset-lg-70,.offset-lg-70{margin-left:70%}[dir=rtl] .flex-offset-lg-70,[dir=rtl] .offset-lg-70{margin-left:auto;margin-right:70%}.flex-offset-lg-75,.offset-lg-75{margin-left:75%}[dir=rtl] .flex-offset-lg-75,[dir=rtl] .offset-lg-75{margin-left:auto;margin-right:75%}.flex-offset-lg-80,.offset-lg-80{margin-left:80%}[dir=rtl] .flex-offset-lg-80,[dir=rtl] .offset-lg-80{margin-left:auto;margin-right:80%}.flex-offset-lg-85,.offset-lg-85{margin-left:85%}[dir=rtl] .flex-offset-lg-85,[dir=rtl] .offset-lg-85{margin-left:auto;margin-right:85%}.flex-offset-lg-90,.offset-lg-90{margin-left:90%}[dir=rtl] .flex-offset-lg-90,[dir=rtl] .offset-lg-90{margin-left:auto;margin-right:90%}.flex-offset-lg-95,.offset-lg-95{margin-left:95%}[dir=rtl] .flex-offset-lg-95,[dir=rtl] .offset-lg-95{margin-left:auto;margin-right:95%}.flex-offset-lg-33,.offset-lg-33{margin-left:33.33333%}.flex-offset-lg-66,.offset-lg-66{margin-left:66.66667%}[dir=rtl] .flex-offset-lg-66,[dir=rtl] .offset-lg-66{margin-left:auto;margin-right:66.66667%}.layout-align-lg,.layout-align-lg-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-lg,.layout-align-lg-start,.layout-align-lg-start-center,.layout-align-lg-start-end,.layout-align-lg-start-start,.layout-align-lg-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-lg-center,.layout-align-lg-center-center,.layout-align-lg-center-end,.layout-align-lg-center-start,.layout-align-lg-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-lg-end,.layout-align-lg-end-center,.layout-align-lg-end-end,.layout-align-lg-end-start,.layout-align-lg-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-lg-space-around,.layout-align-lg-space-around-center,.layout-align-lg-space-around-end,.layout-align-lg-space-around-start,.layout-align-lg-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-lg-space-between,.layout-align-lg-space-between-center,.layout-align-lg-space-between-end,.layout-align-lg-space-between-start,.layout-align-lg-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-lg-center-start,.layout-align-lg-end-start,.layout-align-lg-space-around-start,.layout-align-lg-space-between-start,.layout-align-lg-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-lg-center-center,.layout-align-lg-end-center,.layout-align-lg-space-around-center,.layout-align-lg-space-between-center,.layout-align-lg-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-lg-center-center>*,.layout-align-lg-end-center>*,.layout-align-lg-space-around-center>*,.layout-align-lg-space-between-center>*,.layout-align-lg-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-lg-center-end,.layout-align-lg-end-end,.layout-align-lg-space-around-end,.layout-align-lg-space-between-end,.layout-align-lg-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-lg-center-stretch,.layout-align-lg-end-stretch,.layout-align-lg-space-around-stretch,.layout-align-lg-space-between-stretch,.layout-align-lg-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-lg{-webkit-flex:1;flex:1}.flex-lg,.flex-lg-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-lg-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-lg-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-lg-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-lg-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-lg-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-lg-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-lg-0,.layout-row>.flex-lg-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-lg-0{min-width:0}.layout-column>.flex-lg-0{max-width:100%;max-height:0%}.layout-column>.flex-lg-0,.layout-lg-row>.flex-lg-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-lg-row>.flex-lg-0{max-width:0;max-height:100%;min-width:0}.layout-lg-column>.flex-lg-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-lg-5,.layout-row>.flex-lg-5{max-width:5%;max-height:100%}.flex-lg-5,.layout-column>.flex-lg-5,.layout-row>.flex-lg-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-lg-5{max-width:100%;max-height:5%}.layout-lg-row>.flex-lg-5{max-width:5%;max-height:100%}.layout-lg-column>.flex-lg-5,.layout-lg-row>.flex-lg-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-lg-column>.flex-lg-5{max-width:100%;max-height:5%}.flex-lg-10,.layout-row>.flex-lg-10{max-width:10%;max-height:100%}.flex-lg-10,.layout-column>.flex-lg-10,.layout-row>.flex-lg-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-lg-10{max-width:100%;max-height:10%}.layout-lg-row>.flex-lg-10{max-width:10%;max-height:100%}.layout-lg-column>.flex-lg-10,.layout-lg-row>.flex-lg-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-lg-column>.flex-lg-10{max-width:100%;max-height:10%}.flex-lg-15,.layout-row>.flex-lg-15{max-width:15%;max-height:100%}.flex-lg-15,.layout-column>.flex-lg-15,.layout-row>.flex-lg-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-lg-15{max-width:100%;max-height:15%}.layout-lg-row>.flex-lg-15{max-width:15%;max-height:100%}.layout-lg-column>.flex-lg-15,.layout-lg-row>.flex-lg-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-lg-column>.flex-lg-15{max-width:100%;max-height:15%}.flex-lg-20,.layout-row>.flex-lg-20{max-width:20%;max-height:100%}.flex-lg-20,.layout-column>.flex-lg-20,.layout-row>.flex-lg-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-lg-20{max-width:100%;max-height:20%}.layout-lg-row>.flex-lg-20{max-width:20%;max-height:100%}.layout-lg-column>.flex-lg-20,.layout-lg-row>.flex-lg-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-lg-column>.flex-lg-20{max-width:100%;max-height:20%}.flex-lg-25,.layout-row>.flex-lg-25{max-width:25%;max-height:100%}.flex-lg-25,.layout-column>.flex-lg-25,.layout-row>.flex-lg-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-lg-25{max-width:100%;max-height:25%}.layout-lg-row>.flex-lg-25{max-width:25%;max-height:100%}.layout-lg-column>.flex-lg-25,.layout-lg-row>.flex-lg-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-lg-column>.flex-lg-25{max-width:100%;max-height:25%}.flex-lg-30,.layout-row>.flex-lg-30{max-width:30%;max-height:100%}.flex-lg-30,.layout-column>.flex-lg-30,.layout-row>.flex-lg-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-lg-30{max-width:100%;max-height:30%}.layout-lg-row>.flex-lg-30{max-width:30%;max-height:100%}.layout-lg-column>.flex-lg-30,.layout-lg-row>.flex-lg-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-lg-column>.flex-lg-30{max-width:100%;max-height:30%}.flex-lg-35,.layout-row>.flex-lg-35{max-width:35%;max-height:100%}.flex-lg-35,.layout-column>.flex-lg-35,.layout-row>.flex-lg-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-lg-35{max-width:100%;max-height:35%}.layout-lg-row>.flex-lg-35{max-width:35%;max-height:100%}.layout-lg-column>.flex-lg-35,.layout-lg-row>.flex-lg-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-lg-column>.flex-lg-35{max-width:100%;max-height:35%}.flex-lg-40,.layout-row>.flex-lg-40{max-width:40%;max-height:100%}.flex-lg-40,.layout-column>.flex-lg-40,.layout-row>.flex-lg-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-lg-40{max-width:100%;max-height:40%}.layout-lg-row>.flex-lg-40{max-width:40%;max-height:100%}.layout-lg-column>.flex-lg-40,.layout-lg-row>.flex-lg-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-lg-column>.flex-lg-40{max-width:100%;max-height:40%}.flex-lg-45,.layout-row>.flex-lg-45{max-width:45%;max-height:100%}.flex-lg-45,.layout-column>.flex-lg-45,.layout-row>.flex-lg-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-lg-45{max-width:100%;max-height:45%}.layout-lg-row>.flex-lg-45{max-width:45%;max-height:100%}.layout-lg-column>.flex-lg-45,.layout-lg-row>.flex-lg-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-lg-column>.flex-lg-45{max-width:100%;max-height:45%}.flex-lg-50,.layout-row>.flex-lg-50{max-width:50%;max-height:100%}.flex-lg-50,.layout-column>.flex-lg-50,.layout-row>.flex-lg-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-lg-50{max-width:100%;max-height:50%}.layout-lg-row>.flex-lg-50{max-width:50%;max-height:100%}.layout-lg-column>.flex-lg-50,.layout-lg-row>.flex-lg-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-lg-column>.flex-lg-50{max-width:100%;max-height:50%}.flex-lg-55,.layout-row>.flex-lg-55{max-width:55%;max-height:100%}.flex-lg-55,.layout-column>.flex-lg-55,.layout-row>.flex-lg-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-lg-55{max-width:100%;max-height:55%}.layout-lg-row>.flex-lg-55{max-width:55%;max-height:100%}.layout-lg-column>.flex-lg-55,.layout-lg-row>.flex-lg-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-lg-column>.flex-lg-55{max-width:100%;max-height:55%}.flex-lg-60,.layout-row>.flex-lg-60{max-width:60%;max-height:100%}.flex-lg-60,.layout-column>.flex-lg-60,.layout-row>.flex-lg-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-lg-60{max-width:100%;max-height:60%}.layout-lg-row>.flex-lg-60{max-width:60%;max-height:100%}.layout-lg-column>.flex-lg-60,.layout-lg-row>.flex-lg-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-lg-column>.flex-lg-60{max-width:100%;max-height:60%}.flex-lg-65,.layout-row>.flex-lg-65{max-width:65%;max-height:100%}.flex-lg-65,.layout-column>.flex-lg-65,.layout-row>.flex-lg-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-lg-65{max-width:100%;max-height:65%}.layout-lg-row>.flex-lg-65{max-width:65%;max-height:100%}.layout-lg-column>.flex-lg-65,.layout-lg-row>.flex-lg-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-lg-column>.flex-lg-65{max-width:100%;max-height:65%}.flex-lg-70,.layout-row>.flex-lg-70{max-width:70%;max-height:100%}.flex-lg-70,.layout-column>.flex-lg-70,.layout-row>.flex-lg-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-lg-70{max-width:100%;max-height:70%}.layout-lg-row>.flex-lg-70{max-width:70%;max-height:100%}.layout-lg-column>.flex-lg-70,.layout-lg-row>.flex-lg-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-lg-column>.flex-lg-70{max-width:100%;max-height:70%}.flex-lg-75,.layout-row>.flex-lg-75{max-width:75%;max-height:100%}.flex-lg-75,.layout-column>.flex-lg-75,.layout-row>.flex-lg-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-lg-75{max-width:100%;max-height:75%}.layout-lg-row>.flex-lg-75{max-width:75%;max-height:100%}.layout-lg-column>.flex-lg-75,.layout-lg-row>.flex-lg-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-lg-column>.flex-lg-75{max-width:100%;max-height:75%}.flex-lg-80,.layout-row>.flex-lg-80{max-width:80%;max-height:100%}.flex-lg-80,.layout-column>.flex-lg-80,.layout-row>.flex-lg-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-lg-80{max-width:100%;max-height:80%}.layout-lg-row>.flex-lg-80{max-width:80%;max-height:100%}.layout-lg-column>.flex-lg-80,.layout-lg-row>.flex-lg-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-lg-column>.flex-lg-80{max-width:100%;max-height:80%}.flex-lg-85,.layout-row>.flex-lg-85{max-width:85%;max-height:100%}.flex-lg-85,.layout-column>.flex-lg-85,.layout-row>.flex-lg-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-lg-85{max-width:100%;max-height:85%}.layout-lg-row>.flex-lg-85{max-width:85%;max-height:100%}.layout-lg-column>.flex-lg-85,.layout-lg-row>.flex-lg-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-lg-column>.flex-lg-85{max-width:100%;max-height:85%}.flex-lg-90,.layout-row>.flex-lg-90{max-width:90%;max-height:100%}.flex-lg-90,.layout-column>.flex-lg-90,.layout-row>.flex-lg-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-lg-90{max-width:100%;max-height:90%}.layout-lg-row>.flex-lg-90{max-width:90%;max-height:100%}.layout-lg-column>.flex-lg-90,.layout-lg-row>.flex-lg-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-lg-column>.flex-lg-90{max-width:100%;max-height:90%}.flex-lg-95,.layout-row>.flex-lg-95{max-width:95%;max-height:100%}.flex-lg-95,.layout-column>.flex-lg-95,.layout-row>.flex-lg-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-lg-95{max-width:100%;max-height:95%}.layout-lg-row>.flex-lg-95{max-width:95%;max-height:100%}.layout-lg-column>.flex-lg-95,.layout-lg-row>.flex-lg-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-lg-column>.flex-lg-95{max-width:100%;max-height:95%}.flex-lg-100,.layout-column>.flex-lg-100,.layout-row>.flex-lg-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-lg-column>.flex-lg-100,.layout-lg-row>.flex-lg-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex{min-width:0}.layout-lg-column>.flex-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-lg-column>.flex-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-lg-column>.flex{min-height:0}.layout-lg,.layout-lg-column,.layout-lg-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-lg-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-lg-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}}@media (min-width:1920px){.flex-order-gt-lg--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-gt-lg--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-gt-lg--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-gt-lg--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-gt-lg--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-gt-lg--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-gt-lg--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-gt-lg--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-gt-lg--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-gt-lg--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-gt-lg--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-gt-lg--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-gt-lg--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-gt-lg--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-gt-lg--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-gt-lg--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-gt-lg--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-gt-lg--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-gt-lg--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-gt-lg--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-gt-lg-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-gt-lg-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-gt-lg-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-gt-lg-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-gt-lg-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-gt-lg-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-gt-lg-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-gt-lg-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-gt-lg-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-gt-lg-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-gt-lg-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-gt-lg-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-gt-lg-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-gt-lg-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-gt-lg-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-gt-lg-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-gt-lg-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-gt-lg-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-gt-lg-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-gt-lg-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-gt-lg-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-gt-lg-0,.offset-gt-lg-0{margin-left:0}[dir=rtl] .flex-offset-gt-lg-0,[dir=rtl] .offset-gt-lg-0{margin-left:auto;margin-right:0}.flex-offset-gt-lg-5,.offset-gt-lg-5{margin-left:5%}[dir=rtl] .flex-offset-gt-lg-5,[dir=rtl] .offset-gt-lg-5{margin-left:auto;margin-right:5%}.flex-offset-gt-lg-10,.offset-gt-lg-10{margin-left:10%}[dir=rtl] .flex-offset-gt-lg-10,[dir=rtl] .offset-gt-lg-10{margin-left:auto;margin-right:10%}.flex-offset-gt-lg-15,.offset-gt-lg-15{margin-left:15%}[dir=rtl] .flex-offset-gt-lg-15,[dir=rtl] .offset-gt-lg-15{margin-left:auto;margin-right:15%}.flex-offset-gt-lg-20,.offset-gt-lg-20{margin-left:20%}[dir=rtl] .flex-offset-gt-lg-20,[dir=rtl] .offset-gt-lg-20{margin-left:auto;margin-right:20%}.flex-offset-gt-lg-25,.offset-gt-lg-25{margin-left:25%}[dir=rtl] .flex-offset-gt-lg-25,[dir=rtl] .offset-gt-lg-25{margin-left:auto;margin-right:25%}.flex-offset-gt-lg-30,.offset-gt-lg-30{margin-left:30%}[dir=rtl] .flex-offset-gt-lg-30,[dir=rtl] .offset-gt-lg-30{margin-left:auto;margin-right:30%}.flex-offset-gt-lg-35,.offset-gt-lg-35{margin-left:35%}[dir=rtl] .flex-offset-gt-lg-35,[dir=rtl] .offset-gt-lg-35{margin-left:auto;margin-right:35%}.flex-offset-gt-lg-40,.offset-gt-lg-40{margin-left:40%}[dir=rtl] .flex-offset-gt-lg-40,[dir=rtl] .offset-gt-lg-40{margin-left:auto;margin-right:40%}.flex-offset-gt-lg-45,.offset-gt-lg-45{margin-left:45%}[dir=rtl] .flex-offset-gt-lg-45,[dir=rtl] .offset-gt-lg-45{margin-left:auto;margin-right:45%}.flex-offset-gt-lg-50,.offset-gt-lg-50{margin-left:50%}[dir=rtl] .flex-offset-gt-lg-50,[dir=rtl] .offset-gt-lg-50{margin-left:auto;margin-right:50%}.flex-offset-gt-lg-55,.offset-gt-lg-55{margin-left:55%}[dir=rtl] .flex-offset-gt-lg-55,[dir=rtl] .offset-gt-lg-55{margin-left:auto;margin-right:55%}.flex-offset-gt-lg-60,.offset-gt-lg-60{margin-left:60%}[dir=rtl] .flex-offset-gt-lg-60,[dir=rtl] .offset-gt-lg-60{margin-left:auto;margin-right:60%}.flex-offset-gt-lg-65,.offset-gt-lg-65{margin-left:65%}[dir=rtl] .flex-offset-gt-lg-65,[dir=rtl] .offset-gt-lg-65{margin-left:auto;margin-right:65%}.flex-offset-gt-lg-70,.offset-gt-lg-70{margin-left:70%}[dir=rtl] .flex-offset-gt-lg-70,[dir=rtl] .offset-gt-lg-70{margin-left:auto;margin-right:70%}.flex-offset-gt-lg-75,.offset-gt-lg-75{margin-left:75%}[dir=rtl] .flex-offset-gt-lg-75,[dir=rtl] .offset-gt-lg-75{margin-left:auto;margin-right:75%}.flex-offset-gt-lg-80,.offset-gt-lg-80{margin-left:80%}[dir=rtl] .flex-offset-gt-lg-80,[dir=rtl] .offset-gt-lg-80{margin-left:auto;margin-right:80%}.flex-offset-gt-lg-85,.offset-gt-lg-85{margin-left:85%}[dir=rtl] .flex-offset-gt-lg-85,[dir=rtl] .offset-gt-lg-85{margin-left:auto;margin-right:85%}.flex-offset-gt-lg-90,.offset-gt-lg-90{margin-left:90%}[dir=rtl] .flex-offset-gt-lg-90,[dir=rtl] .offset-gt-lg-90{margin-left:auto;margin-right:90%}.flex-offset-gt-lg-95,.offset-gt-lg-95{margin-left:95%}[dir=rtl] .flex-offset-gt-lg-95,[dir=rtl] .offset-gt-lg-95{margin-left:auto;margin-right:95%}.flex-offset-gt-lg-33,.offset-gt-lg-33{margin-left:33.33333%}.flex-offset-gt-lg-66,.offset-gt-lg-66{margin-left:66.66667%}[dir=rtl] .flex-offset-gt-lg-66,[dir=rtl] .offset-gt-lg-66{margin-left:auto;margin-right:66.66667%}.layout-align-gt-lg,.layout-align-gt-lg-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-gt-lg,.layout-align-gt-lg-start,.layout-align-gt-lg-start-center,.layout-align-gt-lg-start-end,.layout-align-gt-lg-start-start,.layout-align-gt-lg-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-gt-lg-center,.layout-align-gt-lg-center-center,.layout-align-gt-lg-center-end,.layout-align-gt-lg-center-start,.layout-align-gt-lg-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-gt-lg-end,.layout-align-gt-lg-end-center,.layout-align-gt-lg-end-end,.layout-align-gt-lg-end-start,.layout-align-gt-lg-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-gt-lg-space-around,.layout-align-gt-lg-space-around-center,.layout-align-gt-lg-space-around-end,.layout-align-gt-lg-space-around-start,.layout-align-gt-lg-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-gt-lg-space-between,.layout-align-gt-lg-space-between-center,.layout-align-gt-lg-space-between-end,.layout-align-gt-lg-space-between-start,.layout-align-gt-lg-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-gt-lg-center-start,.layout-align-gt-lg-end-start,.layout-align-gt-lg-space-around-start,.layout-align-gt-lg-space-between-start,.layout-align-gt-lg-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-gt-lg-center-center,.layout-align-gt-lg-end-center,.layout-align-gt-lg-space-around-center,.layout-align-gt-lg-space-between-center,.layout-align-gt-lg-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-gt-lg-center-center>*,.layout-align-gt-lg-end-center>*,.layout-align-gt-lg-space-around-center>*,.layout-align-gt-lg-space-between-center>*,.layout-align-gt-lg-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-lg-center-end,.layout-align-gt-lg-end-end,.layout-align-gt-lg-space-around-end,.layout-align-gt-lg-space-between-end,.layout-align-gt-lg-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-gt-lg-center-stretch,.layout-align-gt-lg-end-stretch,.layout-align-gt-lg-space-around-stretch,.layout-align-gt-lg-space-between-stretch,.layout-align-gt-lg-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-gt-lg{-webkit-flex:1;flex:1}.flex-gt-lg,.flex-gt-lg-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-gt-lg-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-gt-lg-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-lg-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-lg-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-lg-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-lg-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-lg-0,.layout-row>.flex-gt-lg-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-lg-0{min-width:0}.layout-column>.flex-gt-lg-0{max-width:100%;max-height:0%}.layout-column>.flex-gt-lg-0,.layout-gt-lg-row>.flex-gt-lg-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-gt-lg-row>.flex-gt-lg-0{max-width:0;max-height:100%;min-width:0}.layout-gt-lg-column>.flex-gt-lg-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-gt-lg-5,.layout-row>.flex-gt-lg-5{max-width:5%;max-height:100%}.flex-gt-lg-5,.layout-column>.flex-gt-lg-5,.layout-row>.flex-gt-lg-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-gt-lg-5{max-width:100%;max-height:5%}.layout-gt-lg-row>.flex-gt-lg-5{max-width:5%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-5,.layout-gt-lg-row>.flex-gt-lg-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-5{max-width:100%;max-height:5%}.flex-gt-lg-10,.layout-row>.flex-gt-lg-10{max-width:10%;max-height:100%}.flex-gt-lg-10,.layout-column>.flex-gt-lg-10,.layout-row>.flex-gt-lg-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-gt-lg-10{max-width:100%;max-height:10%}.layout-gt-lg-row>.flex-gt-lg-10{max-width:10%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-10,.layout-gt-lg-row>.flex-gt-lg-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-10{max-width:100%;max-height:10%}.flex-gt-lg-15,.layout-row>.flex-gt-lg-15{max-width:15%;max-height:100%}.flex-gt-lg-15,.layout-column>.flex-gt-lg-15,.layout-row>.flex-gt-lg-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-gt-lg-15{max-width:100%;max-height:15%}.layout-gt-lg-row>.flex-gt-lg-15{max-width:15%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-15,.layout-gt-lg-row>.flex-gt-lg-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-15{max-width:100%;max-height:15%}.flex-gt-lg-20,.layout-row>.flex-gt-lg-20{max-width:20%;max-height:100%}.flex-gt-lg-20,.layout-column>.flex-gt-lg-20,.layout-row>.flex-gt-lg-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-gt-lg-20{max-width:100%;max-height:20%}.layout-gt-lg-row>.flex-gt-lg-20{max-width:20%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-20,.layout-gt-lg-row>.flex-gt-lg-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-20{max-width:100%;max-height:20%}.flex-gt-lg-25,.layout-row>.flex-gt-lg-25{max-width:25%;max-height:100%}.flex-gt-lg-25,.layout-column>.flex-gt-lg-25,.layout-row>.flex-gt-lg-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-gt-lg-25{max-width:100%;max-height:25%}.layout-gt-lg-row>.flex-gt-lg-25{max-width:25%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-25,.layout-gt-lg-row>.flex-gt-lg-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-25{max-width:100%;max-height:25%}.flex-gt-lg-30,.layout-row>.flex-gt-lg-30{max-width:30%;max-height:100%}.flex-gt-lg-30,.layout-column>.flex-gt-lg-30,.layout-row>.flex-gt-lg-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-gt-lg-30{max-width:100%;max-height:30%}.layout-gt-lg-row>.flex-gt-lg-30{max-width:30%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-30,.layout-gt-lg-row>.flex-gt-lg-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-30{max-width:100%;max-height:30%}.flex-gt-lg-35,.layout-row>.flex-gt-lg-35{max-width:35%;max-height:100%}.flex-gt-lg-35,.layout-column>.flex-gt-lg-35,.layout-row>.flex-gt-lg-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-gt-lg-35{max-width:100%;max-height:35%}.layout-gt-lg-row>.flex-gt-lg-35{max-width:35%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-35,.layout-gt-lg-row>.flex-gt-lg-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-35{max-width:100%;max-height:35%}.flex-gt-lg-40,.layout-row>.flex-gt-lg-40{max-width:40%;max-height:100%}.flex-gt-lg-40,.layout-column>.flex-gt-lg-40,.layout-row>.flex-gt-lg-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-gt-lg-40{max-width:100%;max-height:40%}.layout-gt-lg-row>.flex-gt-lg-40{max-width:40%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-40,.layout-gt-lg-row>.flex-gt-lg-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-40{max-width:100%;max-height:40%}.flex-gt-lg-45,.layout-row>.flex-gt-lg-45{max-width:45%;max-height:100%}.flex-gt-lg-45,.layout-column>.flex-gt-lg-45,.layout-row>.flex-gt-lg-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-gt-lg-45{max-width:100%;max-height:45%}.layout-gt-lg-row>.flex-gt-lg-45{max-width:45%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-45,.layout-gt-lg-row>.flex-gt-lg-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-45{max-width:100%;max-height:45%}.flex-gt-lg-50,.layout-row>.flex-gt-lg-50{max-width:50%;max-height:100%}.flex-gt-lg-50,.layout-column>.flex-gt-lg-50,.layout-row>.flex-gt-lg-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-gt-lg-50{max-width:100%;max-height:50%}.layout-gt-lg-row>.flex-gt-lg-50{max-width:50%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-50,.layout-gt-lg-row>.flex-gt-lg-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-50{max-width:100%;max-height:50%}.flex-gt-lg-55,.layout-row>.flex-gt-lg-55{max-width:55%;max-height:100%}.flex-gt-lg-55,.layout-column>.flex-gt-lg-55,.layout-row>.flex-gt-lg-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-gt-lg-55{max-width:100%;max-height:55%}.layout-gt-lg-row>.flex-gt-lg-55{max-width:55%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-55,.layout-gt-lg-row>.flex-gt-lg-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-55{max-width:100%;max-height:55%}.flex-gt-lg-60,.layout-row>.flex-gt-lg-60{max-width:60%;max-height:100%}.flex-gt-lg-60,.layout-column>.flex-gt-lg-60,.layout-row>.flex-gt-lg-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-gt-lg-60{max-width:100%;max-height:60%}.layout-gt-lg-row>.flex-gt-lg-60{max-width:60%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-60,.layout-gt-lg-row>.flex-gt-lg-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-60{max-width:100%;max-height:60%}.flex-gt-lg-65,.layout-row>.flex-gt-lg-65{max-width:65%;max-height:100%}.flex-gt-lg-65,.layout-column>.flex-gt-lg-65,.layout-row>.flex-gt-lg-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-gt-lg-65{max-width:100%;max-height:65%}.layout-gt-lg-row>.flex-gt-lg-65{max-width:65%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-65,.layout-gt-lg-row>.flex-gt-lg-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-65{max-width:100%;max-height:65%}.flex-gt-lg-70,.layout-row>.flex-gt-lg-70{max-width:70%;max-height:100%}.flex-gt-lg-70,.layout-column>.flex-gt-lg-70,.layout-row>.flex-gt-lg-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-gt-lg-70{max-width:100%;max-height:70%}.layout-gt-lg-row>.flex-gt-lg-70{max-width:70%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-70,.layout-gt-lg-row>.flex-gt-lg-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-70{max-width:100%;max-height:70%}.flex-gt-lg-75,.layout-row>.flex-gt-lg-75{max-width:75%;max-height:100%}.flex-gt-lg-75,.layout-column>.flex-gt-lg-75,.layout-row>.flex-gt-lg-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-gt-lg-75{max-width:100%;max-height:75%}.layout-gt-lg-row>.flex-gt-lg-75{max-width:75%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-75,.layout-gt-lg-row>.flex-gt-lg-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-75{max-width:100%;max-height:75%}.flex-gt-lg-80,.layout-row>.flex-gt-lg-80{max-width:80%;max-height:100%}.flex-gt-lg-80,.layout-column>.flex-gt-lg-80,.layout-row>.flex-gt-lg-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-gt-lg-80{max-width:100%;max-height:80%}.layout-gt-lg-row>.flex-gt-lg-80{max-width:80%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-80,.layout-gt-lg-row>.flex-gt-lg-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-80{max-width:100%;max-height:80%}.flex-gt-lg-85,.layout-row>.flex-gt-lg-85{max-width:85%;max-height:100%}.flex-gt-lg-85,.layout-column>.flex-gt-lg-85,.layout-row>.flex-gt-lg-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-gt-lg-85{max-width:100%;max-height:85%}.layout-gt-lg-row>.flex-gt-lg-85{max-width:85%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-85,.layout-gt-lg-row>.flex-gt-lg-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-85{max-width:100%;max-height:85%}.flex-gt-lg-90,.layout-row>.flex-gt-lg-90{max-width:90%;max-height:100%}.flex-gt-lg-90,.layout-column>.flex-gt-lg-90,.layout-row>.flex-gt-lg-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-gt-lg-90{max-width:100%;max-height:90%}.layout-gt-lg-row>.flex-gt-lg-90{max-width:90%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-90,.layout-gt-lg-row>.flex-gt-lg-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-90{max-width:100%;max-height:90%}.flex-gt-lg-95,.layout-row>.flex-gt-lg-95{max-width:95%;max-height:100%}.flex-gt-lg-95,.layout-column>.flex-gt-lg-95,.layout-row>.flex-gt-lg-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-gt-lg-95{max-width:100%;max-height:95%}.layout-gt-lg-row>.flex-gt-lg-95{max-width:95%;max-height:100%}.layout-gt-lg-column>.flex-gt-lg-95,.layout-gt-lg-row>.flex-gt-lg-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-95{max-width:100%;max-height:95%}.flex-gt-lg-100,.layout-column>.flex-gt-lg-100,.layout-row>.flex-gt-lg-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-gt-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-100,.layout-gt-lg-row>.flex-gt-lg-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex-gt-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex-gt-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex{min-width:0}.layout-gt-lg-column>.flex-gt-lg-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-gt-lg-column>.flex-gt-lg-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-gt-lg-column>.flex{min-height:0}.layout-gt-lg,.layout-gt-lg-column,.layout-gt-lg-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-gt-lg-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-gt-lg-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}.flex-order-xl--20{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}.flex-order-xl--19{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}.flex-order-xl--18{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}.flex-order-xl--17{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}.flex-order-xl--16{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}.flex-order-xl--15{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}.flex-order-xl--14{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}.flex-order-xl--13{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}.flex-order-xl--12{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}.flex-order-xl--11{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}.flex-order-xl--10{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}.flex-order-xl--9{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}.flex-order-xl--8{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}.flex-order-xl--7{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}.flex-order-xl--6{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}.flex-order-xl--5{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}.flex-order-xl--4{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}.flex-order-xl--3{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}.flex-order-xl--2{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}.flex-order-xl--1{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}.flex-order-xl-0{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}.flex-order-xl-1{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}.flex-order-xl-2{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}.flex-order-xl-3{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.flex-order-xl-4{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}.flex-order-xl-5{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}.flex-order-xl-6{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}.flex-order-xl-7{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}.flex-order-xl-8{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}.flex-order-xl-9{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}.flex-order-xl-10{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}.flex-order-xl-11{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}.flex-order-xl-12{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}.flex-order-xl-13{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}.flex-order-xl-14{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}.flex-order-xl-15{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}.flex-order-xl-16{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}.flex-order-xl-17{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}.flex-order-xl-18{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}.flex-order-xl-19{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}.flex-order-xl-20{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}.flex-offset-xl-0,.offset-xl-0{margin-left:0}[dir=rtl] .flex-offset-xl-0,[dir=rtl] .offset-xl-0{margin-left:auto;margin-right:0}.flex-offset-xl-5,.offset-xl-5{margin-left:5%}[dir=rtl] .flex-offset-xl-5,[dir=rtl] .offset-xl-5{margin-left:auto;margin-right:5%}.flex-offset-xl-10,.offset-xl-10{margin-left:10%}[dir=rtl] .flex-offset-xl-10,[dir=rtl] .offset-xl-10{margin-left:auto;margin-right:10%}.flex-offset-xl-15,.offset-xl-15{margin-left:15%}[dir=rtl] .flex-offset-xl-15,[dir=rtl] .offset-xl-15{margin-left:auto;margin-right:15%}.flex-offset-xl-20,.offset-xl-20{margin-left:20%}[dir=rtl] .flex-offset-xl-20,[dir=rtl] .offset-xl-20{margin-left:auto;margin-right:20%}.flex-offset-xl-25,.offset-xl-25{margin-left:25%}[dir=rtl] .flex-offset-xl-25,[dir=rtl] .offset-xl-25{margin-left:auto;margin-right:25%}.flex-offset-xl-30,.offset-xl-30{margin-left:30%}[dir=rtl] .flex-offset-xl-30,[dir=rtl] .offset-xl-30{margin-left:auto;margin-right:30%}.flex-offset-xl-35,.offset-xl-35{margin-left:35%}[dir=rtl] .flex-offset-xl-35,[dir=rtl] .offset-xl-35{margin-left:auto;margin-right:35%}.flex-offset-xl-40,.offset-xl-40{margin-left:40%}[dir=rtl] .flex-offset-xl-40,[dir=rtl] .offset-xl-40{margin-left:auto;margin-right:40%}.flex-offset-xl-45,.offset-xl-45{margin-left:45%}[dir=rtl] .flex-offset-xl-45,[dir=rtl] .offset-xl-45{margin-left:auto;margin-right:45%}.flex-offset-xl-50,.offset-xl-50{margin-left:50%}[dir=rtl] .flex-offset-xl-50,[dir=rtl] .offset-xl-50{margin-left:auto;margin-right:50%}.flex-offset-xl-55,.offset-xl-55{margin-left:55%}[dir=rtl] .flex-offset-xl-55,[dir=rtl] .offset-xl-55{margin-left:auto;margin-right:55%}.flex-offset-xl-60,.offset-xl-60{margin-left:60%}[dir=rtl] .flex-offset-xl-60,[dir=rtl] .offset-xl-60{margin-left:auto;margin-right:60%}.flex-offset-xl-65,.offset-xl-65{margin-left:65%}[dir=rtl] .flex-offset-xl-65,[dir=rtl] .offset-xl-65{margin-left:auto;margin-right:65%}.flex-offset-xl-70,.offset-xl-70{margin-left:70%}[dir=rtl] .flex-offset-xl-70,[dir=rtl] .offset-xl-70{margin-left:auto;margin-right:70%}.flex-offset-xl-75,.offset-xl-75{margin-left:75%}[dir=rtl] .flex-offset-xl-75,[dir=rtl] .offset-xl-75{margin-left:auto;margin-right:75%}.flex-offset-xl-80,.offset-xl-80{margin-left:80%}[dir=rtl] .flex-offset-xl-80,[dir=rtl] .offset-xl-80{margin-left:auto;margin-right:80%}.flex-offset-xl-85,.offset-xl-85{margin-left:85%}[dir=rtl] .flex-offset-xl-85,[dir=rtl] .offset-xl-85{margin-left:auto;margin-right:85%}.flex-offset-xl-90,.offset-xl-90{margin-left:90%}[dir=rtl] .flex-offset-xl-90,[dir=rtl] .offset-xl-90{margin-left:auto;margin-right:90%}.flex-offset-xl-95,.offset-xl-95{margin-left:95%}[dir=rtl] .flex-offset-xl-95,[dir=rtl] .offset-xl-95{margin-left:auto;margin-right:95%}.flex-offset-xl-33,.offset-xl-33{margin-left:33.33333%}.flex-offset-xl-66,.offset-xl-66{margin-left:66.66667%}[dir=rtl] .flex-offset-xl-66,[dir=rtl] .offset-xl-66{margin-left:auto;margin-right:66.66667%}.layout-align-xl,.layout-align-xl-start-stretch{-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch}.layout-align-xl,.layout-align-xl-start,.layout-align-xl-start-center,.layout-align-xl-start-end,.layout-align-xl-start-start,.layout-align-xl-start-stretch{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}.layout-align-xl-center,.layout-align-xl-center-center,.layout-align-xl-center-end,.layout-align-xl-center-start,.layout-align-xl-center-stretch{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}.layout-align-xl-end,.layout-align-xl-end-center,.layout-align-xl-end-end,.layout-align-xl-end-start,.layout-align-xl-end-stretch{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}.layout-align-xl-space-around,.layout-align-xl-space-around-center,.layout-align-xl-space-around-end,.layout-align-xl-space-around-start,.layout-align-xl-space-around-stretch{-webkit-justify-content:space-around;justify-content:space-around}.layout-align-xl-space-between,.layout-align-xl-space-between-center,.layout-align-xl-space-between-end,.layout-align-xl-space-between-start,.layout-align-xl-space-between-stretch{-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.layout-align-xl-center-start,.layout-align-xl-end-start,.layout-align-xl-space-around-start,.layout-align-xl-space-between-start,.layout-align-xl-start-start{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-grid-row-align:flex-start;align-items:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.layout-align-xl-center-center,.layout-align-xl-end-center,.layout-align-xl-space-around-center,.layout-align-xl-space-between-center,.layout-align-xl-start-center{-webkit-box-align:center;-webkit-align-items:center;-ms-grid-row-align:center;align-items:center;-webkit-align-content:center;align-content:center;max-width:100%}.layout-align-xl-center-center>*,.layout-align-xl-end-center>*,.layout-align-xl-space-around-center>*,.layout-align-xl-space-between-center>*,.layout-align-xl-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-xl-center-end,.layout-align-xl-end-end,.layout-align-xl-space-around-end,.layout-align-xl-space-between-end,.layout-align-xl-start-end{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-grid-row-align:flex-end;align-items:flex-end;-webkit-align-content:flex-end;align-content:flex-end}.layout-align-xl-center-stretch,.layout-align-xl-end-stretch,.layout-align-xl-space-around-stretch,.layout-align-xl-space-between-stretch,.layout-align-xl-start-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-grid-row-align:stretch;align-items:stretch;-webkit-align-content:stretch;align-content:stretch}.flex-xl{-webkit-flex:1;flex:1}.flex-xl,.flex-xl-grow{-webkit-box-flex:1;box-sizing:border-box}.flex-xl-grow{-webkit-flex:1 1 100%;flex:1 1 100%}.flex-xl-initial{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xl-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-xl-none{-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-xl-noshrink{-webkit-box-flex:1;-webkit-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-xl-nogrow{-webkit-box-flex:0;-webkit-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xl-0,.layout-row>.flex-xl-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-xl-0{min-width:0}.layout-column>.flex-xl-0{max-width:100%;max-height:0%}.layout-column>.flex-xl-0,.layout-xl-row>.flex-xl-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;box-sizing:border-box}.layout-xl-row>.flex-xl-0{max-width:0;max-height:100%;min-width:0}.layout-xl-column>.flex-xl-0{-webkit-box-flex:1;-webkit-flex:1 1 0%;flex:1 1 0%;max-width:100%;max-height:0%;box-sizing:border-box;min-height:0}.flex-xl-5,.layout-row>.flex-xl-5{max-width:5%;max-height:100%}.flex-xl-5,.layout-column>.flex-xl-5,.layout-row>.flex-xl-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-column>.flex-xl-5{max-width:100%;max-height:5%}.layout-xl-row>.flex-xl-5{max-width:5%;max-height:100%}.layout-xl-column>.flex-xl-5,.layout-xl-row>.flex-xl-5{-webkit-box-flex:1;-webkit-flex:1 1 5%;flex:1 1 5%;box-sizing:border-box}.layout-xl-column>.flex-xl-5{max-width:100%;max-height:5%}.flex-xl-10,.layout-row>.flex-xl-10{max-width:10%;max-height:100%}.flex-xl-10,.layout-column>.flex-xl-10,.layout-row>.flex-xl-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-column>.flex-xl-10{max-width:100%;max-height:10%}.layout-xl-row>.flex-xl-10{max-width:10%;max-height:100%}.layout-xl-column>.flex-xl-10,.layout-xl-row>.flex-xl-10{-webkit-box-flex:1;-webkit-flex:1 1 10%;flex:1 1 10%;box-sizing:border-box}.layout-xl-column>.flex-xl-10{max-width:100%;max-height:10%}.flex-xl-15,.layout-row>.flex-xl-15{max-width:15%;max-height:100%}.flex-xl-15,.layout-column>.flex-xl-15,.layout-row>.flex-xl-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-column>.flex-xl-15{max-width:100%;max-height:15%}.layout-xl-row>.flex-xl-15{max-width:15%;max-height:100%}.layout-xl-column>.flex-xl-15,.layout-xl-row>.flex-xl-15{-webkit-box-flex:1;-webkit-flex:1 1 15%;flex:1 1 15%;box-sizing:border-box}.layout-xl-column>.flex-xl-15{max-width:100%;max-height:15%}.flex-xl-20,.layout-row>.flex-xl-20{max-width:20%;max-height:100%}.flex-xl-20,.layout-column>.flex-xl-20,.layout-row>.flex-xl-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-column>.flex-xl-20{max-width:100%;max-height:20%}.layout-xl-row>.flex-xl-20{max-width:20%;max-height:100%}.layout-xl-column>.flex-xl-20,.layout-xl-row>.flex-xl-20{-webkit-box-flex:1;-webkit-flex:1 1 20%;flex:1 1 20%;box-sizing:border-box}.layout-xl-column>.flex-xl-20{max-width:100%;max-height:20%}.flex-xl-25,.layout-row>.flex-xl-25{max-width:25%;max-height:100%}.flex-xl-25,.layout-column>.flex-xl-25,.layout-row>.flex-xl-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-column>.flex-xl-25{max-width:100%;max-height:25%}.layout-xl-row>.flex-xl-25{max-width:25%;max-height:100%}.layout-xl-column>.flex-xl-25,.layout-xl-row>.flex-xl-25{-webkit-box-flex:1;-webkit-flex:1 1 25%;flex:1 1 25%;box-sizing:border-box}.layout-xl-column>.flex-xl-25{max-width:100%;max-height:25%}.flex-xl-30,.layout-row>.flex-xl-30{max-width:30%;max-height:100%}.flex-xl-30,.layout-column>.flex-xl-30,.layout-row>.flex-xl-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-column>.flex-xl-30{max-width:100%;max-height:30%}.layout-xl-row>.flex-xl-30{max-width:30%;max-height:100%}.layout-xl-column>.flex-xl-30,.layout-xl-row>.flex-xl-30{-webkit-box-flex:1;-webkit-flex:1 1 30%;flex:1 1 30%;box-sizing:border-box}.layout-xl-column>.flex-xl-30{max-width:100%;max-height:30%}.flex-xl-35,.layout-row>.flex-xl-35{max-width:35%;max-height:100%}.flex-xl-35,.layout-column>.flex-xl-35,.layout-row>.flex-xl-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-column>.flex-xl-35{max-width:100%;max-height:35%}.layout-xl-row>.flex-xl-35{max-width:35%;max-height:100%}.layout-xl-column>.flex-xl-35,.layout-xl-row>.flex-xl-35{-webkit-box-flex:1;-webkit-flex:1 1 35%;flex:1 1 35%;box-sizing:border-box}.layout-xl-column>.flex-xl-35{max-width:100%;max-height:35%}.flex-xl-40,.layout-row>.flex-xl-40{max-width:40%;max-height:100%}.flex-xl-40,.layout-column>.flex-xl-40,.layout-row>.flex-xl-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-column>.flex-xl-40{max-width:100%;max-height:40%}.layout-xl-row>.flex-xl-40{max-width:40%;max-height:100%}.layout-xl-column>.flex-xl-40,.layout-xl-row>.flex-xl-40{-webkit-box-flex:1;-webkit-flex:1 1 40%;flex:1 1 40%;box-sizing:border-box}.layout-xl-column>.flex-xl-40{max-width:100%;max-height:40%}.flex-xl-45,.layout-row>.flex-xl-45{max-width:45%;max-height:100%}.flex-xl-45,.layout-column>.flex-xl-45,.layout-row>.flex-xl-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-column>.flex-xl-45{max-width:100%;max-height:45%}.layout-xl-row>.flex-xl-45{max-width:45%;max-height:100%}.layout-xl-column>.flex-xl-45,.layout-xl-row>.flex-xl-45{-webkit-box-flex:1;-webkit-flex:1 1 45%;flex:1 1 45%;box-sizing:border-box}.layout-xl-column>.flex-xl-45{max-width:100%;max-height:45%}.flex-xl-50,.layout-row>.flex-xl-50{max-width:50%;max-height:100%}.flex-xl-50,.layout-column>.flex-xl-50,.layout-row>.flex-xl-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-column>.flex-xl-50{max-width:100%;max-height:50%}.layout-xl-row>.flex-xl-50{max-width:50%;max-height:100%}.layout-xl-column>.flex-xl-50,.layout-xl-row>.flex-xl-50{-webkit-box-flex:1;-webkit-flex:1 1 50%;flex:1 1 50%;box-sizing:border-box}.layout-xl-column>.flex-xl-50{max-width:100%;max-height:50%}.flex-xl-55,.layout-row>.flex-xl-55{max-width:55%;max-height:100%}.flex-xl-55,.layout-column>.flex-xl-55,.layout-row>.flex-xl-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-column>.flex-xl-55{max-width:100%;max-height:55%}.layout-xl-row>.flex-xl-55{max-width:55%;max-height:100%}.layout-xl-column>.flex-xl-55,.layout-xl-row>.flex-xl-55{-webkit-box-flex:1;-webkit-flex:1 1 55%;flex:1 1 55%;box-sizing:border-box}.layout-xl-column>.flex-xl-55{max-width:100%;max-height:55%}.flex-xl-60,.layout-row>.flex-xl-60{max-width:60%;max-height:100%}.flex-xl-60,.layout-column>.flex-xl-60,.layout-row>.flex-xl-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-column>.flex-xl-60{max-width:100%;max-height:60%}.layout-xl-row>.flex-xl-60{max-width:60%;max-height:100%}.layout-xl-column>.flex-xl-60,.layout-xl-row>.flex-xl-60{-webkit-box-flex:1;-webkit-flex:1 1 60%;flex:1 1 60%;box-sizing:border-box}.layout-xl-column>.flex-xl-60{max-width:100%;max-height:60%}.flex-xl-65,.layout-row>.flex-xl-65{max-width:65%;max-height:100%}.flex-xl-65,.layout-column>.flex-xl-65,.layout-row>.flex-xl-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-column>.flex-xl-65{max-width:100%;max-height:65%}.layout-xl-row>.flex-xl-65{max-width:65%;max-height:100%}.layout-xl-column>.flex-xl-65,.layout-xl-row>.flex-xl-65{-webkit-box-flex:1;-webkit-flex:1 1 65%;flex:1 1 65%;box-sizing:border-box}.layout-xl-column>.flex-xl-65{max-width:100%;max-height:65%}.flex-xl-70,.layout-row>.flex-xl-70{max-width:70%;max-height:100%}.flex-xl-70,.layout-column>.flex-xl-70,.layout-row>.flex-xl-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-column>.flex-xl-70{max-width:100%;max-height:70%}.layout-xl-row>.flex-xl-70{max-width:70%;max-height:100%}.layout-xl-column>.flex-xl-70,.layout-xl-row>.flex-xl-70{-webkit-box-flex:1;-webkit-flex:1 1 70%;flex:1 1 70%;box-sizing:border-box}.layout-xl-column>.flex-xl-70{max-width:100%;max-height:70%}.flex-xl-75,.layout-row>.flex-xl-75{max-width:75%;max-height:100%}.flex-xl-75,.layout-column>.flex-xl-75,.layout-row>.flex-xl-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-column>.flex-xl-75{max-width:100%;max-height:75%}.layout-xl-row>.flex-xl-75{max-width:75%;max-height:100%}.layout-xl-column>.flex-xl-75,.layout-xl-row>.flex-xl-75{-webkit-box-flex:1;-webkit-flex:1 1 75%;flex:1 1 75%;box-sizing:border-box}.layout-xl-column>.flex-xl-75{max-width:100%;max-height:75%}.flex-xl-80,.layout-row>.flex-xl-80{max-width:80%;max-height:100%}.flex-xl-80,.layout-column>.flex-xl-80,.layout-row>.flex-xl-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-column>.flex-xl-80{max-width:100%;max-height:80%}.layout-xl-row>.flex-xl-80{max-width:80%;max-height:100%}.layout-xl-column>.flex-xl-80,.layout-xl-row>.flex-xl-80{-webkit-box-flex:1;-webkit-flex:1 1 80%;flex:1 1 80%;box-sizing:border-box}.layout-xl-column>.flex-xl-80{max-width:100%;max-height:80%}.flex-xl-85,.layout-row>.flex-xl-85{max-width:85%;max-height:100%}.flex-xl-85,.layout-column>.flex-xl-85,.layout-row>.flex-xl-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-column>.flex-xl-85{max-width:100%;max-height:85%}.layout-xl-row>.flex-xl-85{max-width:85%;max-height:100%}.layout-xl-column>.flex-xl-85,.layout-xl-row>.flex-xl-85{-webkit-box-flex:1;-webkit-flex:1 1 85%;flex:1 1 85%;box-sizing:border-box}.layout-xl-column>.flex-xl-85{max-width:100%;max-height:85%}.flex-xl-90,.layout-row>.flex-xl-90{max-width:90%;max-height:100%}.flex-xl-90,.layout-column>.flex-xl-90,.layout-row>.flex-xl-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-column>.flex-xl-90{max-width:100%;max-height:90%}.layout-xl-row>.flex-xl-90{max-width:90%;max-height:100%}.layout-xl-column>.flex-xl-90,.layout-xl-row>.flex-xl-90{-webkit-box-flex:1;-webkit-flex:1 1 90%;flex:1 1 90%;box-sizing:border-box}.layout-xl-column>.flex-xl-90{max-width:100%;max-height:90%}.flex-xl-95,.layout-row>.flex-xl-95{max-width:95%;max-height:100%}.flex-xl-95,.layout-column>.flex-xl-95,.layout-row>.flex-xl-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-column>.flex-xl-95{max-width:100%;max-height:95%}.layout-xl-row>.flex-xl-95{max-width:95%;max-height:100%}.layout-xl-column>.flex-xl-95,.layout-xl-row>.flex-xl-95{-webkit-box-flex:1;-webkit-flex:1 1 95%;flex:1 1 95%;box-sizing:border-box}.layout-xl-column>.flex-xl-95{max-width:100%;max-height:95%}.flex-xl-100,.layout-column>.flex-xl-100,.layout-row>.flex-xl-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xl-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xl-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-xl-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-xl-column>.flex-xl-100,.layout-xl-row>.flex-xl-100{-webkit-box-flex:1;-webkit-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-xl-row>.flex-xl-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-xl-row>.flex-xl-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-xl-row>.flex{min-width:0}.layout-xl-column>.flex-xl-33{-webkit-box-flex:1;-webkit-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-xl-column>.flex-xl-66{-webkit-box-flex:1;-webkit-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-xl-column>.flex{min-height:0}.layout-xl,.layout-xl-column,.layout-xl-row{box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:flex}.layout-xl-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;flex-direction:column}.layout-xl-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;flex-direction:row}.hide-gt-lg:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-gt-md:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-gt-sm:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-gt-xs:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-xl:not(.show-xl):not(.show-gt-lg):not(.show-gt-md):not(.show-gt-sm):not(.show-gt-xs):not(.show),.hide:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show){display:none}}@media print{.hide-print:not(.show-print):not(.show){display:none!important}} +#loading-bar,#loading-bar-spinner{pointer-events:none;-webkit-pointer-events:none;-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar-spinner.ng-enter,#loading-bar-spinner.ng-leave.ng-leave-active,#loading-bar.ng-enter,#loading-bar.ng-leave.ng-leave-active{opacity:0}#loading-bar-spinner.ng-enter.ng-enter-active,#loading-bar-spinner.ng-leave,#loading-bar.ng-enter.ng-enter-active,#loading-bar.ng-leave{opacity:1}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:10002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px;opacity:.45;-moz-box-shadow:#29d 1px 0 6px 1px;-ms-box-shadow:#29d 1px 0 6px 1px;-webkit-box-shadow:#29d 1px 0 6px 1px;box-shadow:#29d 1px 0 6px 1px;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}#loading-bar-spinner{display:block;position:fixed;z-index:10002;top:10px;left:10px}#loading-bar-spinner .spinner-icon{width:14px;height:14px;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:loading-bar-spinner 400ms linear infinite;-moz-animation:loading-bar-spinner 400ms linear infinite;-ms-animation:loading-bar-spinner 400ms linear infinite;-o-animation:loading-bar-spinner 400ms linear infinite;animation:loading-bar-spinner 400ms linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0);transform:rotate(0)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}} +/* MaterialDesignIcons.com */@font-face{font-family:"Material Design Icons";src:url("../fonts/materialdesignicons-webfont.eot?v=1.8.36");src:url("../fonts/materialdesignicons-webfont.eot?#iefix&v=1.8.36") format("embedded-opentype"),url("../fonts/materialdesignicons-webfont.woff2?v=1.8.36") format("woff2"),url("../fonts/materialdesignicons-webfont.woff?v=1.8.36") format("woff"),url("../fonts/materialdesignicons-webfont.ttf?v=1.8.36") format("truetype"),url("../fonts/materialdesignicons-webfont.svg?v=1.8.36#materialdesigniconsregular") format("svg");font-weight:normal;font-style:normal}.mdi:before,.mdi-set{display:inline-block;font:normal normal normal 24px/1 "Material Design Icons";font-size:inherit;text-rendering:auto;line-height:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.mdi-access-point:before{content:"\F002"}.mdi-access-point-network:before{content:"\F003"}.mdi-account:before{content:"\F004"}.mdi-account-alert:before{content:"\F005"}.mdi-account-box:before{content:"\F006"}.mdi-account-box-outline:before{content:"\F007"}.mdi-account-card-details:before{content:"\F5D2"}.mdi-account-check:before{content:"\F008"}.mdi-account-circle:before{content:"\F009"}.mdi-account-convert:before{content:"\F00A"}.mdi-account-edit:before{content:"\F6BB"}.mdi-account-key:before{content:"\F00B"}.mdi-account-location:before{content:"\F00C"}.mdi-account-minus:before{content:"\F00D"}.mdi-account-multiple:before{content:"\F00E"}.mdi-account-multiple-minus:before{content:"\F5D3"}.mdi-account-multiple-outline:before{content:"\F00F"}.mdi-account-multiple-plus:before{content:"\F010"}.mdi-account-network:before{content:"\F011"}.mdi-account-off:before{content:"\F012"}.mdi-account-outline:before{content:"\F013"}.mdi-account-plus:before{content:"\F014"}.mdi-account-remove:before{content:"\F015"}.mdi-account-search:before{content:"\F016"}.mdi-account-settings:before{content:"\F630"}.mdi-account-settings-variant:before{content:"\F631"}.mdi-account-star:before{content:"\F017"}.mdi-account-star-variant:before{content:"\F018"}.mdi-account-switch:before{content:"\F019"}.mdi-adjust:before{content:"\F01A"}.mdi-air-conditioner:before{content:"\F01B"}.mdi-airballoon:before{content:"\F01C"}.mdi-airplane:before{content:"\F01D"}.mdi-airplane-landing:before{content:"\F5D4"}.mdi-airplane-off:before{content:"\F01E"}.mdi-airplane-takeoff:before{content:"\F5D5"}.mdi-airplay:before{content:"\F01F"}.mdi-alarm:before{content:"\F020"}.mdi-alarm-check:before{content:"\F021"}.mdi-alarm-multiple:before{content:"\F022"}.mdi-alarm-off:before{content:"\F023"}.mdi-alarm-plus:before{content:"\F024"}.mdi-alarm-snooze:before{content:"\F68D"}.mdi-album:before{content:"\F025"}.mdi-alert:before{content:"\F026"}.mdi-alert-box:before{content:"\F027"}.mdi-alert-circle:before{content:"\F028"}.mdi-alert-circle-outline:before{content:"\F5D6"}.mdi-alert-octagon:before{content:"\F029"}.mdi-alert-octagram:before{content:"\F6BC"}.mdi-alert-outline:before{content:"\F02A"}.mdi-all-inclusive:before{content:"\F6BD"}.mdi-alpha:before{content:"\F02B"}.mdi-alphabetical:before{content:"\F02C"}.mdi-altimeter:before{content:"\F5D7"}.mdi-amazon:before{content:"\F02D"}.mdi-amazon-clouddrive:before{content:"\F02E"}.mdi-ambulance:before{content:"\F02F"}.mdi-amplifier:before{content:"\F030"}.mdi-anchor:before{content:"\F031"}.mdi-android:before{content:"\F032"}.mdi-android-debug-bridge:before{content:"\F033"}.mdi-android-studio:before{content:"\F034"}.mdi-angular:before{content:"\F6B1"}.mdi-angularjs:before{content:"\F6BE"}.mdi-animation:before{content:"\F5D8"}.mdi-apple:before{content:"\F035"}.mdi-apple-finder:before{content:"\F036"}.mdi-apple-ios:before{content:"\F037"}.mdi-apple-keyboard-caps:before{content:"\F632"}.mdi-apple-keyboard-command:before{content:"\F633"}.mdi-apple-keyboard-control:before{content:"\F634"}.mdi-apple-keyboard-option:before{content:"\F635"}.mdi-apple-keyboard-shift:before{content:"\F636"}.mdi-apple-mobileme:before{content:"\F038"}.mdi-apple-safari:before{content:"\F039"}.mdi-application:before{content:"\F614"}.mdi-apps:before{content:"\F03B"}.mdi-archive:before{content:"\F03C"}.mdi-arrange-bring-forward:before{content:"\F03D"}.mdi-arrange-bring-to-front:before{content:"\F03E"}.mdi-arrange-send-backward:before{content:"\F03F"}.mdi-arrange-send-to-back:before{content:"\F040"}.mdi-arrow-all:before{content:"\F041"}.mdi-arrow-bottom-left:before{content:"\F042"}.mdi-arrow-bottom-right:before{content:"\F043"}.mdi-arrow-compress:before{content:"\F615"}.mdi-arrow-compress-all:before{content:"\F044"}.mdi-arrow-down:before{content:"\F045"}.mdi-arrow-down-bold:before{content:"\F046"}.mdi-arrow-down-bold-circle:before{content:"\F047"}.mdi-arrow-down-bold-circle-outline:before{content:"\F048"}.mdi-arrow-down-bold-hexagon-outline:before{content:"\F049"}.mdi-arrow-down-box:before{content:"\F6BF"}.mdi-arrow-down-drop-circle:before{content:"\F04A"}.mdi-arrow-down-drop-circle-outline:before{content:"\F04B"}.mdi-arrow-expand:before{content:"\F616"}.mdi-arrow-expand-all:before{content:"\F04C"}.mdi-arrow-left:before{content:"\F04D"}.mdi-arrow-left-bold:before{content:"\F04E"}.mdi-arrow-left-bold-circle:before{content:"\F04F"}.mdi-arrow-left-bold-circle-outline:before{content:"\F050"}.mdi-arrow-left-bold-hexagon-outline:before{content:"\F051"}.mdi-arrow-left-box:before{content:"\F6C0"}.mdi-arrow-left-drop-circle:before{content:"\F052"}.mdi-arrow-left-drop-circle-outline:before{content:"\F053"}.mdi-arrow-right:before{content:"\F054"}.mdi-arrow-right-bold:before{content:"\F055"}.mdi-arrow-right-bold-circle:before{content:"\F056"}.mdi-arrow-right-bold-circle-outline:before{content:"\F057"}.mdi-arrow-right-bold-hexagon-outline:before{content:"\F058"}.mdi-arrow-right-box:before{content:"\F6C1"}.mdi-arrow-right-drop-circle:before{content:"\F059"}.mdi-arrow-right-drop-circle-outline:before{content:"\F05A"}.mdi-arrow-top-left:before{content:"\F05B"}.mdi-arrow-top-right:before{content:"\F05C"}.mdi-arrow-up:before{content:"\F05D"}.mdi-arrow-up-bold:before{content:"\F05E"}.mdi-arrow-up-bold-circle:before{content:"\F05F"}.mdi-arrow-up-bold-circle-outline:before{content:"\F060"}.mdi-arrow-up-bold-hexagon-outline:before{content:"\F061"}.mdi-arrow-up-box:before{content:"\F6C2"}.mdi-arrow-up-drop-circle:before{content:"\F062"}.mdi-arrow-up-drop-circle-outline:before{content:"\F063"}.mdi-assistant:before{content:"\F064"}.mdi-asterisk:before{content:"\F6C3"}.mdi-at:before{content:"\F065"}.mdi-attachment:before{content:"\F066"}.mdi-audiobook:before{content:"\F067"}.mdi-auto-fix:before{content:"\F068"}.mdi-auto-upload:before{content:"\F069"}.mdi-autorenew:before{content:"\F06A"}.mdi-av-timer:before{content:"\F06B"}.mdi-baby:before{content:"\F06C"}.mdi-baby-buggy:before{content:"\F68E"}.mdi-backburger:before{content:"\F06D"}.mdi-backspace:before{content:"\F06E"}.mdi-backup-restore:before{content:"\F06F"}.mdi-bandcamp:before{content:"\F674"}.mdi-bank:before{content:"\F070"}.mdi-barcode:before{content:"\F071"}.mdi-barcode-scan:before{content:"\F072"}.mdi-barley:before{content:"\F073"}.mdi-barrel:before{content:"\F074"}.mdi-basecamp:before{content:"\F075"}.mdi-basket:before{content:"\F076"}.mdi-basket-fill:before{content:"\F077"}.mdi-basket-unfill:before{content:"\F078"}.mdi-battery:before{content:"\F079"}.mdi-battery-10:before{content:"\F07A"}.mdi-battery-20:before{content:"\F07B"}.mdi-battery-30:before{content:"\F07C"}.mdi-battery-40:before{content:"\F07D"}.mdi-battery-50:before{content:"\F07E"}.mdi-battery-60:before{content:"\F07F"}.mdi-battery-70:before{content:"\F080"}.mdi-battery-80:before{content:"\F081"}.mdi-battery-90:before{content:"\F082"}.mdi-battery-alert:before{content:"\F083"}.mdi-battery-charging:before{content:"\F084"}.mdi-battery-charging-100:before{content:"\F085"}.mdi-battery-charging-20:before{content:"\F086"}.mdi-battery-charging-30:before{content:"\F087"}.mdi-battery-charging-40:before{content:"\F088"}.mdi-battery-charging-60:before{content:"\F089"}.mdi-battery-charging-80:before{content:"\F08A"}.mdi-battery-charging-90:before{content:"\F08B"}.mdi-battery-minus:before{content:"\F08C"}.mdi-battery-negative:before{content:"\F08D"}.mdi-battery-outline:before{content:"\F08E"}.mdi-battery-plus:before{content:"\F08F"}.mdi-battery-positive:before{content:"\F090"}.mdi-battery-unknown:before{content:"\F091"}.mdi-beach:before{content:"\F092"}.mdi-beaker:before{content:"\F68F"}.mdi-beats:before{content:"\F097"}.mdi-beer:before{content:"\F098"}.mdi-behance:before{content:"\F099"}.mdi-bell:before{content:"\F09A"}.mdi-bell-off:before{content:"\F09B"}.mdi-bell-outline:before{content:"\F09C"}.mdi-bell-plus:before{content:"\F09D"}.mdi-bell-ring:before{content:"\F09E"}.mdi-bell-ring-outline:before{content:"\F09F"}.mdi-bell-sleep:before{content:"\F0A0"}.mdi-beta:before{content:"\F0A1"}.mdi-bible:before{content:"\F0A2"}.mdi-bike:before{content:"\F0A3"}.mdi-bing:before{content:"\F0A4"}.mdi-binoculars:before{content:"\F0A5"}.mdi-bio:before{content:"\F0A6"}.mdi-biohazard:before{content:"\F0A7"}.mdi-bitbucket:before{content:"\F0A8"}.mdi-black-mesa:before{content:"\F0A9"}.mdi-blackberry:before{content:"\F0AA"}.mdi-blender:before{content:"\F0AB"}.mdi-blinds:before{content:"\F0AC"}.mdi-block-helper:before{content:"\F0AD"}.mdi-blogger:before{content:"\F0AE"}.mdi-bluetooth:before{content:"\F0AF"}.mdi-bluetooth-audio:before{content:"\F0B0"}.mdi-bluetooth-connect:before{content:"\F0B1"}.mdi-bluetooth-off:before{content:"\F0B2"}.mdi-bluetooth-settings:before{content:"\F0B3"}.mdi-bluetooth-transfer:before{content:"\F0B4"}.mdi-blur:before{content:"\F0B5"}.mdi-blur-linear:before{content:"\F0B6"}.mdi-blur-off:before{content:"\F0B7"}.mdi-blur-radial:before{content:"\F0B8"}.mdi-bomb:before{content:"\F690"}.mdi-bomb-off:before{content:"\F6C4"}.mdi-bone:before{content:"\F0B9"}.mdi-book:before{content:"\F0BA"}.mdi-book-minus:before{content:"\F5D9"}.mdi-book-multiple:before{content:"\F0BB"}.mdi-book-multiple-variant:before{content:"\F0BC"}.mdi-book-open:before{content:"\F0BD"}.mdi-book-open-page-variant:before{content:"\F5DA"}.mdi-book-open-variant:before{content:"\F0BE"}.mdi-book-plus:before{content:"\F5DB"}.mdi-book-variant:before{content:"\F0BF"}.mdi-bookmark:before{content:"\F0C0"}.mdi-bookmark-check:before{content:"\F0C1"}.mdi-bookmark-music:before{content:"\F0C2"}.mdi-bookmark-outline:before{content:"\F0C3"}.mdi-bookmark-plus:before{content:"\F0C5"}.mdi-bookmark-plus-outline:before{content:"\F0C4"}.mdi-bookmark-remove:before{content:"\F0C6"}.mdi-boombox:before{content:"\F5DC"}.mdi-bootstrap:before{content:"\F6C5"}.mdi-border-all:before{content:"\F0C7"}.mdi-border-bottom:before{content:"\F0C8"}.mdi-border-color:before{content:"\F0C9"}.mdi-border-horizontal:before{content:"\F0CA"}.mdi-border-inside:before{content:"\F0CB"}.mdi-border-left:before{content:"\F0CC"}.mdi-border-none:before{content:"\F0CD"}.mdi-border-outside:before{content:"\F0CE"}.mdi-border-right:before{content:"\F0CF"}.mdi-border-style:before{content:"\F0D0"}.mdi-border-top:before{content:"\F0D1"}.mdi-border-vertical:before{content:"\F0D2"}.mdi-bow-tie:before{content:"\F677"}.mdi-bowl:before{content:"\F617"}.mdi-bowling:before{content:"\F0D3"}.mdi-box:before{content:"\F0D4"}.mdi-box-cutter:before{content:"\F0D5"}.mdi-box-shadow:before{content:"\F637"}.mdi-bridge:before{content:"\F618"}.mdi-briefcase:before{content:"\F0D6"}.mdi-briefcase-check:before{content:"\F0D7"}.mdi-briefcase-download:before{content:"\F0D8"}.mdi-briefcase-upload:before{content:"\F0D9"}.mdi-brightness-1:before{content:"\F0DA"}.mdi-brightness-2:before{content:"\F0DB"}.mdi-brightness-3:before{content:"\F0DC"}.mdi-brightness-4:before{content:"\F0DD"}.mdi-brightness-5:before{content:"\F0DE"}.mdi-brightness-6:before{content:"\F0DF"}.mdi-brightness-7:before{content:"\F0E0"}.mdi-brightness-auto:before{content:"\F0E1"}.mdi-broom:before{content:"\F0E2"}.mdi-brush:before{content:"\F0E3"}.mdi-buffer:before{content:"\F619"}.mdi-bug:before{content:"\F0E4"}.mdi-bulletin-board:before{content:"\F0E5"}.mdi-bullhorn:before{content:"\F0E6"}.mdi-bullseye:before{content:"\F5DD"}.mdi-burst-mode:before{content:"\F5DE"}.mdi-bus:before{content:"\F0E7"}.mdi-cached:before{content:"\F0E8"}.mdi-cake:before{content:"\F0E9"}.mdi-cake-layered:before{content:"\F0EA"}.mdi-cake-variant:before{content:"\F0EB"}.mdi-calculator:before{content:"\F0EC"}.mdi-calendar:before{content:"\F0ED"}.mdi-calendar-blank:before{content:"\F0EE"}.mdi-calendar-check:before{content:"\F0EF"}.mdi-calendar-clock:before{content:"\F0F0"}.mdi-calendar-multiple:before{content:"\F0F1"}.mdi-calendar-multiple-check:before{content:"\F0F2"}.mdi-calendar-plus:before{content:"\F0F3"}.mdi-calendar-question:before{content:"\F691"}.mdi-calendar-range:before{content:"\F678"}.mdi-calendar-remove:before{content:"\F0F4"}.mdi-calendar-text:before{content:"\F0F5"}.mdi-calendar-today:before{content:"\F0F6"}.mdi-call-made:before{content:"\F0F7"}.mdi-call-merge:before{content:"\F0F8"}.mdi-call-missed:before{content:"\F0F9"}.mdi-call-received:before{content:"\F0FA"}.mdi-call-split:before{content:"\F0FB"}.mdi-camcorder:before{content:"\F0FC"}.mdi-camcorder-box:before{content:"\F0FD"}.mdi-camcorder-box-off:before{content:"\F0FE"}.mdi-camcorder-off:before{content:"\F0FF"}.mdi-camera:before{content:"\F100"}.mdi-camera-burst:before{content:"\F692"}.mdi-camera-enhance:before{content:"\F101"}.mdi-camera-front:before{content:"\F102"}.mdi-camera-front-variant:before{content:"\F103"}.mdi-camera-iris:before{content:"\F104"}.mdi-camera-off:before{content:"\F5DF"}.mdi-camera-party-mode:before{content:"\F105"}.mdi-camera-rear:before{content:"\F106"}.mdi-camera-rear-variant:before{content:"\F107"}.mdi-camera-switch:before{content:"\F108"}.mdi-camera-timer:before{content:"\F109"}.mdi-candle:before{content:"\F5E2"}.mdi-candycane:before{content:"\F10A"}.mdi-car:before{content:"\F10B"}.mdi-car-battery:before{content:"\F10C"}.mdi-car-connected:before{content:"\F10D"}.mdi-car-wash:before{content:"\F10E"}.mdi-cards:before{content:"\F638"}.mdi-cards-outline:before{content:"\F639"}.mdi-cards-playing-outline:before{content:"\F63A"}.mdi-cards-variant:before{content:"\F6C6"}.mdi-carrot:before{content:"\F10F"}.mdi-cart:before{content:"\F110"}.mdi-cart-off:before{content:"\F66B"}.mdi-cart-outline:before{content:"\F111"}.mdi-cart-plus:before{content:"\F112"}.mdi-case-sensitive-alt:before{content:"\F113"}.mdi-cash:before{content:"\F114"}.mdi-cash-100:before{content:"\F115"}.mdi-cash-multiple:before{content:"\F116"}.mdi-cash-usd:before{content:"\F117"}.mdi-cast:before{content:"\F118"}.mdi-cast-connected:before{content:"\F119"}.mdi-castle:before{content:"\F11A"}.mdi-cat:before{content:"\F11B"}.mdi-cellphone:before{content:"\F11C"}.mdi-cellphone-android:before{content:"\F11D"}.mdi-cellphone-basic:before{content:"\F11E"}.mdi-cellphone-dock:before{content:"\F11F"}.mdi-cellphone-iphone:before{content:"\F120"}.mdi-cellphone-link:before{content:"\F121"}.mdi-cellphone-link-off:before{content:"\F122"}.mdi-cellphone-settings:before{content:"\F123"}.mdi-certificate:before{content:"\F124"}.mdi-chair-school:before{content:"\F125"}.mdi-chart-arc:before{content:"\F126"}.mdi-chart-areaspline:before{content:"\F127"}.mdi-chart-bar:before{content:"\F128"}.mdi-chart-bubble:before{content:"\F5E3"}.mdi-chart-gantt:before{content:"\F66C"}.mdi-chart-histogram:before{content:"\F129"}.mdi-chart-line:before{content:"\F12A"}.mdi-chart-pie:before{content:"\F12B"}.mdi-chart-scatterplot-hexbin:before{content:"\F66D"}.mdi-chart-timeline:before{content:"\F66E"}.mdi-check:before{content:"\F12C"}.mdi-check-all:before{content:"\F12D"}.mdi-check-circle:before{content:"\F5E0"}.mdi-check-circle-outline:before{content:"\F5E1"}.mdi-checkbox-blank:before{content:"\F12E"}.mdi-checkbox-blank-circle:before{content:"\F12F"}.mdi-checkbox-blank-circle-outline:before{content:"\F130"}.mdi-checkbox-blank-outline:before{content:"\F131"}.mdi-checkbox-marked:before{content:"\F132"}.mdi-checkbox-marked-circle:before{content:"\F133"}.mdi-checkbox-marked-circle-outline:before{content:"\F134"}.mdi-checkbox-marked-outline:before{content:"\F135"}.mdi-checkbox-multiple-blank:before{content:"\F136"}.mdi-checkbox-multiple-blank-circle:before{content:"\F63B"}.mdi-checkbox-multiple-blank-circle-outline:before{content:"\F63C"}.mdi-checkbox-multiple-blank-outline:before{content:"\F137"}.mdi-checkbox-multiple-marked:before{content:"\F138"}.mdi-checkbox-multiple-marked-circle:before{content:"\F63D"}.mdi-checkbox-multiple-marked-circle-outline:before{content:"\F63E"}.mdi-checkbox-multiple-marked-outline:before{content:"\F139"}.mdi-checkerboard:before{content:"\F13A"}.mdi-chemical-weapon:before{content:"\F13B"}.mdi-chevron-double-down:before{content:"\F13C"}.mdi-chevron-double-left:before{content:"\F13D"}.mdi-chevron-double-right:before{content:"\F13E"}.mdi-chevron-double-up:before{content:"\F13F"}.mdi-chevron-down:before{content:"\F140"}.mdi-chevron-left:before{content:"\F141"}.mdi-chevron-right:before{content:"\F142"}.mdi-chevron-up:before{content:"\F143"}.mdi-chip:before{content:"\F61A"}.mdi-church:before{content:"\F144"}.mdi-cisco-webex:before{content:"\F145"}.mdi-city:before{content:"\F146"}.mdi-clipboard:before{content:"\F147"}.mdi-clipboard-account:before{content:"\F148"}.mdi-clipboard-alert:before{content:"\F149"}.mdi-clipboard-arrow-down:before{content:"\F14A"}.mdi-clipboard-arrow-left:before{content:"\F14B"}.mdi-clipboard-check:before{content:"\F14C"}.mdi-clipboard-flow:before{content:"\F6C7"}.mdi-clipboard-outline:before{content:"\F14D"}.mdi-clipboard-text:before{content:"\F14E"}.mdi-clippy:before{content:"\F14F"}.mdi-clock:before{content:"\F150"}.mdi-clock-alert:before{content:"\F5CE"}.mdi-clock-end:before{content:"\F151"}.mdi-clock-fast:before{content:"\F152"}.mdi-clock-in:before{content:"\F153"}.mdi-clock-out:before{content:"\F154"}.mdi-clock-start:before{content:"\F155"}.mdi-close:before{content:"\F156"}.mdi-close-box:before{content:"\F157"}.mdi-close-box-outline:before{content:"\F158"}.mdi-close-circle:before{content:"\F159"}.mdi-close-circle-outline:before{content:"\F15A"}.mdi-close-network:before{content:"\F15B"}.mdi-close-octagon:before{content:"\F15C"}.mdi-close-octagon-outline:before{content:"\F15D"}.mdi-close-outline:before{content:"\F6C8"}.mdi-closed-caption:before{content:"\F15E"}.mdi-cloud:before{content:"\F15F"}.mdi-cloud-check:before{content:"\F160"}.mdi-cloud-circle:before{content:"\F161"}.mdi-cloud-download:before{content:"\F162"}.mdi-cloud-outline:before{content:"\F163"}.mdi-cloud-outline-off:before{content:"\F164"}.mdi-cloud-print:before{content:"\F165"}.mdi-cloud-print-outline:before{content:"\F166"}.mdi-cloud-sync:before{content:"\F63F"}.mdi-cloud-upload:before{content:"\F167"}.mdi-code-array:before{content:"\F168"}.mdi-code-braces:before{content:"\F169"}.mdi-code-brackets:before{content:"\F16A"}.mdi-code-equal:before{content:"\F16B"}.mdi-code-greater-than:before{content:"\F16C"}.mdi-code-greater-than-or-equal:before{content:"\F16D"}.mdi-code-less-than:before{content:"\F16E"}.mdi-code-less-than-or-equal:before{content:"\F16F"}.mdi-code-not-equal:before{content:"\F170"}.mdi-code-not-equal-variant:before{content:"\F171"}.mdi-code-parentheses:before{content:"\F172"}.mdi-code-string:before{content:"\F173"}.mdi-code-tags:before{content:"\F174"}.mdi-code-tags-check:before{content:"\F693"}.mdi-codepen:before{content:"\F175"}.mdi-coffee:before{content:"\F176"}.mdi-coffee-outline:before{content:"\F6C9"}.mdi-coffee-to-go:before{content:"\F177"}.mdi-coin:before{content:"\F178"}.mdi-coins:before{content:"\F694"}.mdi-collage:before{content:"\F640"}.mdi-color-helper:before{content:"\F179"}.mdi-comment:before{content:"\F17A"}.mdi-comment-account:before{content:"\F17B"}.mdi-comment-account-outline:before{content:"\F17C"}.mdi-comment-alert:before{content:"\F17D"}.mdi-comment-alert-outline:before{content:"\F17E"}.mdi-comment-check:before{content:"\F17F"}.mdi-comment-check-outline:before{content:"\F180"}.mdi-comment-multiple-outline:before{content:"\F181"}.mdi-comment-outline:before{content:"\F182"}.mdi-comment-plus-outline:before{content:"\F183"}.mdi-comment-processing:before{content:"\F184"}.mdi-comment-processing-outline:before{content:"\F185"}.mdi-comment-question-outline:before{content:"\F186"}.mdi-comment-remove-outline:before{content:"\F187"}.mdi-comment-text:before{content:"\F188"}.mdi-comment-text-outline:before{content:"\F189"}.mdi-compare:before{content:"\F18A"}.mdi-compass:before{content:"\F18B"}.mdi-compass-outline:before{content:"\F18C"}.mdi-console:before{content:"\F18D"}.mdi-contact-mail:before{content:"\F18E"}.mdi-contacts:before{content:"\F6CA"}.mdi-content-copy:before{content:"\F18F"}.mdi-content-cut:before{content:"\F190"}.mdi-content-duplicate:before{content:"\F191"}.mdi-content-paste:before{content:"\F192"}.mdi-content-save:before{content:"\F193"}.mdi-content-save-all:before{content:"\F194"}.mdi-content-save-settings:before{content:"\F61B"}.mdi-contrast:before{content:"\F195"}.mdi-contrast-box:before{content:"\F196"}.mdi-contrast-circle:before{content:"\F197"}.mdi-cookie:before{content:"\F198"}.mdi-copyright:before{content:"\F5E6"}.mdi-counter:before{content:"\F199"}.mdi-cow:before{content:"\F19A"}.mdi-creation:before{content:"\F1C9"}.mdi-credit-card:before{content:"\F19B"}.mdi-credit-card-multiple:before{content:"\F19C"}.mdi-credit-card-off:before{content:"\F5E4"}.mdi-credit-card-plus:before{content:"\F675"}.mdi-credit-card-scan:before{content:"\F19D"}.mdi-crop:before{content:"\F19E"}.mdi-crop-free:before{content:"\F19F"}.mdi-crop-landscape:before{content:"\F1A0"}.mdi-crop-portrait:before{content:"\F1A1"}.mdi-crop-rotate:before{content:"\F695"}.mdi-crop-square:before{content:"\F1A2"}.mdi-crosshairs:before{content:"\F1A3"}.mdi-crosshairs-gps:before{content:"\F1A4"}.mdi-crown:before{content:"\F1A5"}.mdi-cube:before{content:"\F1A6"}.mdi-cube-outline:before{content:"\F1A7"}.mdi-cube-send:before{content:"\F1A8"}.mdi-cube-unfolded:before{content:"\F1A9"}.mdi-cup:before{content:"\F1AA"}.mdi-cup-off:before{content:"\F5E5"}.mdi-cup-water:before{content:"\F1AB"}.mdi-currency-btc:before{content:"\F1AC"}.mdi-currency-eur:before{content:"\F1AD"}.mdi-currency-gbp:before{content:"\F1AE"}.mdi-currency-inr:before{content:"\F1AF"}.mdi-currency-ngn:before{content:"\F1B0"}.mdi-currency-rub:before{content:"\F1B1"}.mdi-currency-try:before{content:"\F1B2"}.mdi-currency-usd:before{content:"\F1B3"}.mdi-currency-usd-off:before{content:"\F679"}.mdi-cursor-default:before{content:"\F1B4"}.mdi-cursor-default-outline:before{content:"\F1B5"}.mdi-cursor-move:before{content:"\F1B6"}.mdi-cursor-pointer:before{content:"\F1B7"}.mdi-cursor-text:before{content:"\F5E7"}.mdi-database:before{content:"\F1B8"}.mdi-database-minus:before{content:"\F1B9"}.mdi-database-plus:before{content:"\F1BA"}.mdi-debug-step-into:before{content:"\F1BB"}.mdi-debug-step-out:before{content:"\F1BC"}.mdi-debug-step-over:before{content:"\F1BD"}.mdi-decimal-decrease:before{content:"\F1BE"}.mdi-decimal-increase:before{content:"\F1BF"}.mdi-delete:before{content:"\F1C0"}.mdi-delete-circle:before{content:"\F682"}.mdi-delete-empty:before{content:"\F6CB"}.mdi-delete-forever:before{content:"\F5E8"}.mdi-delete-sweep:before{content:"\F5E9"}.mdi-delete-variant:before{content:"\F1C1"}.mdi-delta:before{content:"\F1C2"}.mdi-deskphone:before{content:"\F1C3"}.mdi-desktop-mac:before{content:"\F1C4"}.mdi-desktop-tower:before{content:"\F1C5"}.mdi-details:before{content:"\F1C6"}.mdi-developer-board:before{content:"\F696"}.mdi-deviantart:before{content:"\F1C7"}.mdi-dialpad:before{content:"\F61C"}.mdi-diamond:before{content:"\F1C8"}.mdi-dice-1:before{content:"\F1CA"}.mdi-dice-2:before{content:"\F1CB"}.mdi-dice-3:before{content:"\F1CC"}.mdi-dice-4:before{content:"\F1CD"}.mdi-dice-5:before{content:"\F1CE"}.mdi-dice-6:before{content:"\F1CF"}.mdi-dice-d20:before{content:"\F5EA"}.mdi-dice-d4:before{content:"\F5EB"}.mdi-dice-d6:before{content:"\F5EC"}.mdi-dice-d8:before{content:"\F5ED"}.mdi-dictionary:before{content:"\F61D"}.mdi-directions:before{content:"\F1D0"}.mdi-directions-fork:before{content:"\F641"}.mdi-discord:before{content:"\F66F"}.mdi-disk:before{content:"\F5EE"}.mdi-disk-alert:before{content:"\F1D1"}.mdi-disqus:before{content:"\F1D2"}.mdi-disqus-outline:before{content:"\F1D3"}.mdi-division:before{content:"\F1D4"}.mdi-division-box:before{content:"\F1D5"}.mdi-dna:before{content:"\F683"}.mdi-dns:before{content:"\F1D6"}.mdi-do-not-disturb:before{content:"\F697"}.mdi-do-not-disturb-off:before{content:"\F698"}.mdi-dolby:before{content:"\F6B2"}.mdi-domain:before{content:"\F1D7"}.mdi-dots-horizontal:before{content:"\F1D8"}.mdi-dots-vertical:before{content:"\F1D9"}.mdi-douban:before{content:"\F699"}.mdi-download:before{content:"\F1DA"}.mdi-drag:before{content:"\F1DB"}.mdi-drag-horizontal:before{content:"\F1DC"}.mdi-drag-vertical:before{content:"\F1DD"}.mdi-drawing:before{content:"\F1DE"}.mdi-drawing-box:before{content:"\F1DF"}.mdi-dribbble:before{content:"\F1E0"}.mdi-dribbble-box:before{content:"\F1E1"}.mdi-drone:before{content:"\F1E2"}.mdi-dropbox:before{content:"\F1E3"}.mdi-drupal:before{content:"\F1E4"}.mdi-duck:before{content:"\F1E5"}.mdi-dumbbell:before{content:"\F1E6"}.mdi-earth:before{content:"\F1E7"}.mdi-earth-box:before{content:"\F6CC"}.mdi-earth-box-off:before{content:"\F6CD"}.mdi-earth-off:before{content:"\F1E8"}.mdi-edge:before{content:"\F1E9"}.mdi-eject:before{content:"\F1EA"}.mdi-elevation-decline:before{content:"\F1EB"}.mdi-elevation-rise:before{content:"\F1EC"}.mdi-elevator:before{content:"\F1ED"}.mdi-email:before{content:"\F1EE"}.mdi-email-alert:before{content:"\F6CE"}.mdi-email-open:before{content:"\F1EF"}.mdi-email-open-outline:before{content:"\F5EF"}.mdi-email-outline:before{content:"\F1F0"}.mdi-email-secure:before{content:"\F1F1"}.mdi-email-variant:before{content:"\F5F0"}.mdi-emby:before{content:"\F6B3"}.mdi-emoticon:before{content:"\F1F2"}.mdi-emoticon-cool:before{content:"\F1F3"}.mdi-emoticon-dead:before{content:"\F69A"}.mdi-emoticon-devil:before{content:"\F1F4"}.mdi-emoticon-excited:before{content:"\F69B"}.mdi-emoticon-happy:before{content:"\F1F5"}.mdi-emoticon-neutral:before{content:"\F1F6"}.mdi-emoticon-poop:before{content:"\F1F7"}.mdi-emoticon-sad:before{content:"\F1F8"}.mdi-emoticon-tongue:before{content:"\F1F9"}.mdi-engine:before{content:"\F1FA"}.mdi-engine-outline:before{content:"\F1FB"}.mdi-equal:before{content:"\F1FC"}.mdi-equal-box:before{content:"\F1FD"}.mdi-eraser:before{content:"\F1FE"}.mdi-eraser-variant:before{content:"\F642"}.mdi-escalator:before{content:"\F1FF"}.mdi-ethernet:before{content:"\F200"}.mdi-ethernet-cable:before{content:"\F201"}.mdi-ethernet-cable-off:before{content:"\F202"}.mdi-etsy:before{content:"\F203"}.mdi-ev-station:before{content:"\F5F1"}.mdi-evernote:before{content:"\F204"}.mdi-exclamation:before{content:"\F205"}.mdi-exit-to-app:before{content:"\F206"}.mdi-export:before{content:"\F207"}.mdi-eye:before{content:"\F208"}.mdi-eye-off:before{content:"\F209"}.mdi-eye-outline:before{content:"\F6CF"}.mdi-eye-outline-off:before{content:"\F6D0"}.mdi-eyedropper:before{content:"\F20A"}.mdi-eyedropper-variant:before{content:"\F20B"}.mdi-face:before{content:"\F643"}.mdi-face-profile:before{content:"\F644"}.mdi-facebook:before{content:"\F20C"}.mdi-facebook-box:before{content:"\F20D"}.mdi-facebook-messenger:before{content:"\F20E"}.mdi-factory:before{content:"\F20F"}.mdi-fan:before{content:"\F210"}.mdi-fast-forward:before{content:"\F211"}.mdi-fast-forward-outline:before{content:"\F6D1"}.mdi-fax:before{content:"\F212"}.mdi-feather:before{content:"\F6D2"}.mdi-ferry:before{content:"\F213"}.mdi-file:before{content:"\F214"}.mdi-file-chart:before{content:"\F215"}.mdi-file-check:before{content:"\F216"}.mdi-file-cloud:before{content:"\F217"}.mdi-file-delimited:before{content:"\F218"}.mdi-file-document:before{content:"\F219"}.mdi-file-document-box:before{content:"\F21A"}.mdi-file-excel:before{content:"\F21B"}.mdi-file-excel-box:before{content:"\F21C"}.mdi-file-export:before{content:"\F21D"}.mdi-file-find:before{content:"\F21E"}.mdi-file-hidden:before{content:"\F613"}.mdi-file-image:before{content:"\F21F"}.mdi-file-import:before{content:"\F220"}.mdi-file-lock:before{content:"\F221"}.mdi-file-multiple:before{content:"\F222"}.mdi-file-music:before{content:"\F223"}.mdi-file-outline:before{content:"\F224"}.mdi-file-pdf:before{content:"\F225"}.mdi-file-pdf-box:before{content:"\F226"}.mdi-file-powerpoint:before{content:"\F227"}.mdi-file-powerpoint-box:before{content:"\F228"}.mdi-file-presentation-box:before{content:"\F229"}.mdi-file-restore:before{content:"\F670"}.mdi-file-send:before{content:"\F22A"}.mdi-file-tree:before{content:"\F645"}.mdi-file-video:before{content:"\F22B"}.mdi-file-word:before{content:"\F22C"}.mdi-file-word-box:before{content:"\F22D"}.mdi-file-xml:before{content:"\F22E"}.mdi-film:before{content:"\F22F"}.mdi-filmstrip:before{content:"\F230"}.mdi-filmstrip-off:before{content:"\F231"}.mdi-filter:before{content:"\F232"}.mdi-filter-outline:before{content:"\F233"}.mdi-filter-remove:before{content:"\F234"}.mdi-filter-remove-outline:before{content:"\F235"}.mdi-filter-variant:before{content:"\F236"}.mdi-find-replace:before{content:"\F6D3"}.mdi-fingerprint:before{content:"\F237"}.mdi-fire:before{content:"\F238"}.mdi-firefox:before{content:"\F239"}.mdi-fish:before{content:"\F23A"}.mdi-flag:before{content:"\F23B"}.mdi-flag-checkered:before{content:"\F23C"}.mdi-flag-outline:before{content:"\F23D"}.mdi-flag-outline-variant:before{content:"\F23E"}.mdi-flag-triangle:before{content:"\F23F"}.mdi-flag-variant:before{content:"\F240"}.mdi-flash:before{content:"\F241"}.mdi-flash-auto:before{content:"\F242"}.mdi-flash-off:before{content:"\F243"}.mdi-flash-outline:before{content:"\F6D4"}.mdi-flash-red-eye:before{content:"\F67A"}.mdi-flashlight:before{content:"\F244"}.mdi-flashlight-off:before{content:"\F245"}.mdi-flask:before{content:"\F093"}.mdi-flask-empty:before{content:"\F094"}.mdi-flask-empty-outline:before{content:"\F095"}.mdi-flask-outline:before{content:"\F096"}.mdi-flattr:before{content:"\F246"}.mdi-flip-to-back:before{content:"\F247"}.mdi-flip-to-front:before{content:"\F248"}.mdi-floppy:before{content:"\F249"}.mdi-flower:before{content:"\F24A"}.mdi-folder:before{content:"\F24B"}.mdi-folder-account:before{content:"\F24C"}.mdi-folder-download:before{content:"\F24D"}.mdi-folder-google-drive:before{content:"\F24E"}.mdi-folder-image:before{content:"\F24F"}.mdi-folder-lock:before{content:"\F250"}.mdi-folder-lock-open:before{content:"\F251"}.mdi-folder-move:before{content:"\F252"}.mdi-folder-multiple:before{content:"\F253"}.mdi-folder-multiple-image:before{content:"\F254"}.mdi-folder-multiple-outline:before{content:"\F255"}.mdi-folder-outline:before{content:"\F256"}.mdi-folder-plus:before{content:"\F257"}.mdi-folder-remove:before{content:"\F258"}.mdi-folder-star:before{content:"\F69C"}.mdi-folder-upload:before{content:"\F259"}.mdi-font-awesome:before{content:"\F03A"}.mdi-food:before{content:"\F25A"}.mdi-food-apple:before{content:"\F25B"}.mdi-food-fork-drink:before{content:"\F5F2"}.mdi-food-off:before{content:"\F5F3"}.mdi-food-variant:before{content:"\F25C"}.mdi-football:before{content:"\F25D"}.mdi-football-australian:before{content:"\F25E"}.mdi-football-helmet:before{content:"\F25F"}.mdi-format-align-center:before{content:"\F260"}.mdi-format-align-justify:before{content:"\F261"}.mdi-format-align-left:before{content:"\F262"}.mdi-format-align-right:before{content:"\F263"}.mdi-format-annotation-plus:before{content:"\F646"}.mdi-format-bold:before{content:"\F264"}.mdi-format-clear:before{content:"\F265"}.mdi-format-color-fill:before{content:"\F266"}.mdi-format-color-text:before{content:"\F69D"}.mdi-format-float-center:before{content:"\F267"}.mdi-format-float-left:before{content:"\F268"}.mdi-format-float-none:before{content:"\F269"}.mdi-format-float-right:before{content:"\F26A"}.mdi-format-font:before{content:"\F6D5"}.mdi-format-header-1:before{content:"\F26B"}.mdi-format-header-2:before{content:"\F26C"}.mdi-format-header-3:before{content:"\F26D"}.mdi-format-header-4:before{content:"\F26E"}.mdi-format-header-5:before{content:"\F26F"}.mdi-format-header-6:before{content:"\F270"}.mdi-format-header-decrease:before{content:"\F271"}.mdi-format-header-equal:before{content:"\F272"}.mdi-format-header-increase:before{content:"\F273"}.mdi-format-header-pound:before{content:"\F274"}.mdi-format-horizontal-align-center:before{content:"\F61E"}.mdi-format-horizontal-align-left:before{content:"\F61F"}.mdi-format-horizontal-align-right:before{content:"\F620"}.mdi-format-indent-decrease:before{content:"\F275"}.mdi-format-indent-increase:before{content:"\F276"}.mdi-format-italic:before{content:"\F277"}.mdi-format-line-spacing:before{content:"\F278"}.mdi-format-line-style:before{content:"\F5C8"}.mdi-format-line-weight:before{content:"\F5C9"}.mdi-format-list-bulleted:before{content:"\F279"}.mdi-format-list-bulleted-type:before{content:"\F27A"}.mdi-format-list-numbers:before{content:"\F27B"}.mdi-format-page-break:before{content:"\F6D6"}.mdi-format-paint:before{content:"\F27C"}.mdi-format-paragraph:before{content:"\F27D"}.mdi-format-pilcrow:before{content:"\F6D7"}.mdi-format-quote:before{content:"\F27E"}.mdi-format-rotate-90:before{content:"\F6A9"}.mdi-format-section:before{content:"\F69E"}.mdi-format-size:before{content:"\F27F"}.mdi-format-strikethrough:before{content:"\F280"}.mdi-format-strikethrough-variant:before{content:"\F281"}.mdi-format-subscript:before{content:"\F282"}.mdi-format-superscript:before{content:"\F283"}.mdi-format-text:before{content:"\F284"}.mdi-format-textdirection-l-to-r:before{content:"\F285"}.mdi-format-textdirection-r-to-l:before{content:"\F286"}.mdi-format-title:before{content:"\F5F4"}.mdi-format-underline:before{content:"\F287"}.mdi-format-vertical-align-bottom:before{content:"\F621"}.mdi-format-vertical-align-center:before{content:"\F622"}.mdi-format-vertical-align-top:before{content:"\F623"}.mdi-format-wrap-inline:before{content:"\F288"}.mdi-format-wrap-square:before{content:"\F289"}.mdi-format-wrap-tight:before{content:"\F28A"}.mdi-format-wrap-top-bottom:before{content:"\F28B"}.mdi-forum:before{content:"\F28C"}.mdi-forward:before{content:"\F28D"}.mdi-foursquare:before{content:"\F28E"}.mdi-fridge:before{content:"\F28F"}.mdi-fridge-filled:before{content:"\F290"}.mdi-fridge-filled-bottom:before{content:"\F291"}.mdi-fridge-filled-top:before{content:"\F292"}.mdi-fullscreen:before{content:"\F293"}.mdi-fullscreen-exit:before{content:"\F294"}.mdi-function:before{content:"\F295"}.mdi-gamepad:before{content:"\F296"}.mdi-gamepad-variant:before{content:"\F297"}.mdi-garage:before{content:"\F6D8"}.mdi-garage-open:before{content:"\F6D9"}.mdi-gas-cylinder:before{content:"\F647"}.mdi-gas-station:before{content:"\F298"}.mdi-gate:before{content:"\F299"}.mdi-gauge:before{content:"\F29A"}.mdi-gavel:before{content:"\F29B"}.mdi-gender-female:before{content:"\F29C"}.mdi-gender-male:before{content:"\F29D"}.mdi-gender-male-female:before{content:"\F29E"}.mdi-gender-transgender:before{content:"\F29F"}.mdi-ghost:before{content:"\F2A0"}.mdi-gift:before{content:"\F2A1"}.mdi-git:before{content:"\F2A2"}.mdi-github-box:before{content:"\F2A3"}.mdi-github-circle:before{content:"\F2A4"}.mdi-github-face:before{content:"\F6DA"}.mdi-glass-flute:before{content:"\F2A5"}.mdi-glass-mug:before{content:"\F2A6"}.mdi-glass-stange:before{content:"\F2A7"}.mdi-glass-tulip:before{content:"\F2A8"}.mdi-glassdoor:before{content:"\F2A9"}.mdi-glasses:before{content:"\F2AA"}.mdi-gmail:before{content:"\F2AB"}.mdi-gnome:before{content:"\F2AC"}.mdi-gondola:before{content:"\F685"}.mdi-google:before{content:"\F2AD"}.mdi-google-cardboard:before{content:"\F2AE"}.mdi-google-chrome:before{content:"\F2AF"}.mdi-google-circles:before{content:"\F2B0"}.mdi-google-circles-communities:before{content:"\F2B1"}.mdi-google-circles-extended:before{content:"\F2B2"}.mdi-google-circles-group:before{content:"\F2B3"}.mdi-google-controller:before{content:"\F2B4"}.mdi-google-controller-off:before{content:"\F2B5"}.mdi-google-drive:before{content:"\F2B6"}.mdi-google-earth:before{content:"\F2B7"}.mdi-google-glass:before{content:"\F2B8"}.mdi-google-keep:before{content:"\F6DB"}.mdi-google-maps:before{content:"\F5F5"}.mdi-google-nearby:before{content:"\F2B9"}.mdi-google-pages:before{content:"\F2BA"}.mdi-google-photos:before{content:"\F6DC"}.mdi-google-physical-web:before{content:"\F2BB"}.mdi-google-play:before{content:"\F2BC"}.mdi-google-plus:before{content:"\F2BD"}.mdi-google-plus-box:before{content:"\F2BE"}.mdi-google-translate:before{content:"\F2BF"}.mdi-google-wallet:before{content:"\F2C0"}.mdi-gradient:before{content:"\F69F"}.mdi-grease-pencil:before{content:"\F648"}.mdi-grid:before{content:"\F2C1"}.mdi-grid-off:before{content:"\F2C2"}.mdi-group:before{content:"\F2C3"}.mdi-guitar-electric:before{content:"\F2C4"}.mdi-guitar-pick:before{content:"\F2C5"}.mdi-guitar-pick-outline:before{content:"\F2C6"}.mdi-hackernews:before{content:"\F624"}.mdi-hamburger:before{content:"\F684"}.mdi-hand-pointing-right:before{content:"\F2C7"}.mdi-hanger:before{content:"\F2C8"}.mdi-hangouts:before{content:"\F2C9"}.mdi-harddisk:before{content:"\F2CA"}.mdi-headphones:before{content:"\F2CB"}.mdi-headphones-box:before{content:"\F2CC"}.mdi-headphones-settings:before{content:"\F2CD"}.mdi-headset:before{content:"\F2CE"}.mdi-headset-dock:before{content:"\F2CF"}.mdi-headset-off:before{content:"\F2D0"}.mdi-heart:before{content:"\F2D1"}.mdi-heart-box:before{content:"\F2D2"}.mdi-heart-box-outline:before{content:"\F2D3"}.mdi-heart-broken:before{content:"\F2D4"}.mdi-heart-half-outline:before{content:"\F6DD"}.mdi-heart-half-part:before{content:"\F6DE"}.mdi-heart-half-part-outline:before{content:"\F6DF"}.mdi-heart-outline:before{content:"\F2D5"}.mdi-heart-pulse:before{content:"\F5F6"}.mdi-help:before{content:"\F2D6"}.mdi-help-circle:before{content:"\F2D7"}.mdi-help-circle-outline:before{content:"\F625"}.mdi-hexagon:before{content:"\F2D8"}.mdi-hexagon-multiple:before{content:"\F6E0"}.mdi-hexagon-outline:before{content:"\F2D9"}.mdi-highway:before{content:"\F5F7"}.mdi-history:before{content:"\F2DA"}.mdi-hololens:before{content:"\F2DB"}.mdi-home:before{content:"\F2DC"}.mdi-home-map-marker:before{content:"\F5F8"}.mdi-home-modern:before{content:"\F2DD"}.mdi-home-outline:before{content:"\F6A0"}.mdi-home-variant:before{content:"\F2DE"}.mdi-hook:before{content:"\F6E1"}.mdi-hook-off:before{content:"\F6E2"}.mdi-hops:before{content:"\F2DF"}.mdi-hospital:before{content:"\F2E0"}.mdi-hospital-building:before{content:"\F2E1"}.mdi-hospital-marker:before{content:"\F2E2"}.mdi-hotel:before{content:"\F2E3"}.mdi-houzz:before{content:"\F2E4"}.mdi-houzz-box:before{content:"\F2E5"}.mdi-human:before{content:"\F2E6"}.mdi-human-child:before{content:"\F2E7"}.mdi-human-female:before{content:"\F649"}.mdi-human-greeting:before{content:"\F64A"}.mdi-human-handsdown:before{content:"\F64B"}.mdi-human-handsup:before{content:"\F64C"}.mdi-human-male:before{content:"\F64D"}.mdi-human-male-female:before{content:"\F2E8"}.mdi-human-pregnant:before{content:"\F5CF"}.mdi-image:before{content:"\F2E9"}.mdi-image-album:before{content:"\F2EA"}.mdi-image-area:before{content:"\F2EB"}.mdi-image-area-close:before{content:"\F2EC"}.mdi-image-broken:before{content:"\F2ED"}.mdi-image-broken-variant:before{content:"\F2EE"}.mdi-image-filter:before{content:"\F2EF"}.mdi-image-filter-black-white:before{content:"\F2F0"}.mdi-image-filter-center-focus:before{content:"\F2F1"}.mdi-image-filter-center-focus-weak:before{content:"\F2F2"}.mdi-image-filter-drama:before{content:"\F2F3"}.mdi-image-filter-frames:before{content:"\F2F4"}.mdi-image-filter-hdr:before{content:"\F2F5"}.mdi-image-filter-none:before{content:"\F2F6"}.mdi-image-filter-tilt-shift:before{content:"\F2F7"}.mdi-image-filter-vintage:before{content:"\F2F8"}.mdi-image-multiple:before{content:"\F2F9"}.mdi-import:before{content:"\F2FA"}.mdi-inbox:before{content:"\F686"}.mdi-inbox-arrow-down:before{content:"\F2FB"}.mdi-inbox-arrow-up:before{content:"\F3D1"}.mdi-incognito:before{content:"\F5F9"}.mdi-infinity:before{content:"\F6E3"}.mdi-information:before{content:"\F2FC"}.mdi-information-outline:before{content:"\F2FD"}.mdi-information-variant:before{content:"\F64E"}.mdi-instagram:before{content:"\F2FE"}.mdi-instapaper:before{content:"\F2FF"}.mdi-internet-explorer:before{content:"\F300"}.mdi-invert-colors:before{content:"\F301"}.mdi-itunes:before{content:"\F676"}.mdi-jeepney:before{content:"\F302"}.mdi-jira:before{content:"\F303"}.mdi-jsfiddle:before{content:"\F304"}.mdi-json:before{content:"\F626"}.mdi-keg:before{content:"\F305"}.mdi-kettle:before{content:"\F5FA"}.mdi-key:before{content:"\F306"}.mdi-key-change:before{content:"\F307"}.mdi-key-minus:before{content:"\F308"}.mdi-key-plus:before{content:"\F309"}.mdi-key-remove:before{content:"\F30A"}.mdi-key-variant:before{content:"\F30B"}.mdi-keyboard:before{content:"\F30C"}.mdi-keyboard-backspace:before{content:"\F30D"}.mdi-keyboard-caps:before{content:"\F30E"}.mdi-keyboard-close:before{content:"\F30F"}.mdi-keyboard-off:before{content:"\F310"}.mdi-keyboard-return:before{content:"\F311"}.mdi-keyboard-tab:before{content:"\F312"}.mdi-keyboard-variant:before{content:"\F313"}.mdi-kodi:before{content:"\F314"}.mdi-label:before{content:"\F315"}.mdi-label-outline:before{content:"\F316"}.mdi-lambda:before{content:"\F627"}.mdi-lamp:before{content:"\F6B4"}.mdi-lan:before{content:"\F317"}.mdi-lan-connect:before{content:"\F318"}.mdi-lan-disconnect:before{content:"\F319"}.mdi-lan-pending:before{content:"\F31A"}.mdi-language-c:before{content:"\F671"}.mdi-language-cpp:before{content:"\F672"}.mdi-language-csharp:before{content:"\F31B"}.mdi-language-css3:before{content:"\F31C"}.mdi-language-html5:before{content:"\F31D"}.mdi-language-javascript:before{content:"\F31E"}.mdi-language-php:before{content:"\F31F"}.mdi-language-python:before{content:"\F320"}.mdi-language-python-text:before{content:"\F321"}.mdi-language-swift:before{content:"\F6E4"}.mdi-language-typescript:before{content:"\F6E5"}.mdi-laptop:before{content:"\F322"}.mdi-laptop-chromebook:before{content:"\F323"}.mdi-laptop-mac:before{content:"\F324"}.mdi-laptop-off:before{content:"\F6E6"}.mdi-laptop-windows:before{content:"\F325"}.mdi-lastfm:before{content:"\F326"}.mdi-launch:before{content:"\F327"}.mdi-layers:before{content:"\F328"}.mdi-layers-off:before{content:"\F329"}.mdi-lead-pencil:before{content:"\F64F"}.mdi-leaf:before{content:"\F32A"}.mdi-led-off:before{content:"\F32B"}.mdi-led-on:before{content:"\F32C"}.mdi-led-outline:before{content:"\F32D"}.mdi-led-variant-off:before{content:"\F32E"}.mdi-led-variant-on:before{content:"\F32F"}.mdi-led-variant-outline:before{content:"\F330"}.mdi-library:before{content:"\F331"}.mdi-library-books:before{content:"\F332"}.mdi-library-music:before{content:"\F333"}.mdi-library-plus:before{content:"\F334"}.mdi-lightbulb:before{content:"\F335"}.mdi-lightbulb-on:before{content:"\F6E7"}.mdi-lightbulb-on-outline:before{content:"\F6E8"}.mdi-lightbulb-outline:before{content:"\F336"}.mdi-link:before{content:"\F337"}.mdi-link-off:before{content:"\F338"}.mdi-link-variant:before{content:"\F339"}.mdi-link-variant-off:before{content:"\F33A"}.mdi-linkedin:before{content:"\F33B"}.mdi-linkedin-box:before{content:"\F33C"}.mdi-linux:before{content:"\F33D"}.mdi-lock:before{content:"\F33E"}.mdi-lock-open:before{content:"\F33F"}.mdi-lock-open-outline:before{content:"\F340"}.mdi-lock-outline:before{content:"\F341"}.mdi-lock-pattern:before{content:"\F6E9"}.mdi-lock-plus:before{content:"\F5FB"}.mdi-login:before{content:"\F342"}.mdi-login-variant:before{content:"\F5FC"}.mdi-logout:before{content:"\F343"}.mdi-logout-variant:before{content:"\F5FD"}.mdi-looks:before{content:"\F344"}.mdi-loop:before{content:"\F6EA"}.mdi-loupe:before{content:"\F345"}.mdi-lumx:before{content:"\F346"}.mdi-magnet:before{content:"\F347"}.mdi-magnet-on:before{content:"\F348"}.mdi-magnify:before{content:"\F349"}.mdi-magnify-minus:before{content:"\F34A"}.mdi-magnify-minus-outline:before{content:"\F6EB"}.mdi-magnify-plus:before{content:"\F34B"}.mdi-magnify-plus-outline:before{content:"\F6EC"}.mdi-mail-ru:before{content:"\F34C"}.mdi-mailbox:before{content:"\F6ED"}.mdi-map:before{content:"\F34D"}.mdi-map-marker:before{content:"\F34E"}.mdi-map-marker-circle:before{content:"\F34F"}.mdi-map-marker-minus:before{content:"\F650"}.mdi-map-marker-multiple:before{content:"\F350"}.mdi-map-marker-off:before{content:"\F351"}.mdi-map-marker-plus:before{content:"\F651"}.mdi-map-marker-radius:before{content:"\F352"}.mdi-margin:before{content:"\F353"}.mdi-markdown:before{content:"\F354"}.mdi-marker:before{content:"\F652"}.mdi-marker-check:before{content:"\F355"}.mdi-martini:before{content:"\F356"}.mdi-material-ui:before{content:"\F357"}.mdi-math-compass:before{content:"\F358"}.mdi-matrix:before{content:"\F628"}.mdi-maxcdn:before{content:"\F359"}.mdi-medical-bag:before{content:"\F6EE"}.mdi-medium:before{content:"\F35A"}.mdi-memory:before{content:"\F35B"}.mdi-menu:before{content:"\F35C"}.mdi-menu-down:before{content:"\F35D"}.mdi-menu-down-outline:before{content:"\F6B5"}.mdi-menu-left:before{content:"\F35E"}.mdi-menu-right:before{content:"\F35F"}.mdi-menu-up:before{content:"\F360"}.mdi-menu-up-outline:before{content:"\F6B6"}.mdi-message:before{content:"\F361"}.mdi-message-alert:before{content:"\F362"}.mdi-message-bulleted:before{content:"\F6A1"}.mdi-message-bulleted-off:before{content:"\F6A2"}.mdi-message-draw:before{content:"\F363"}.mdi-message-image:before{content:"\F364"}.mdi-message-outline:before{content:"\F365"}.mdi-message-plus:before{content:"\F653"}.mdi-message-processing:before{content:"\F366"}.mdi-message-reply:before{content:"\F367"}.mdi-message-reply-text:before{content:"\F368"}.mdi-message-settings:before{content:"\F6EF"}.mdi-message-settings-variant:before{content:"\F6F0"}.mdi-message-text:before{content:"\F369"}.mdi-message-text-outline:before{content:"\F36A"}.mdi-message-video:before{content:"\F36B"}.mdi-meteor:before{content:"\F629"}.mdi-microphone:before{content:"\F36C"}.mdi-microphone-off:before{content:"\F36D"}.mdi-microphone-outline:before{content:"\F36E"}.mdi-microphone-settings:before{content:"\F36F"}.mdi-microphone-variant:before{content:"\F370"}.mdi-microphone-variant-off:before{content:"\F371"}.mdi-microscope:before{content:"\F654"}.mdi-microsoft:before{content:"\F372"}.mdi-minecraft:before{content:"\F373"}.mdi-minus:before{content:"\F374"}.mdi-minus-box:before{content:"\F375"}.mdi-minus-box-outline:before{content:"\F6F1"}.mdi-minus-circle:before{content:"\F376"}.mdi-minus-circle-outline:before{content:"\F377"}.mdi-minus-network:before{content:"\F378"}.mdi-mixcloud:before{content:"\F62A"}.mdi-monitor:before{content:"\F379"}.mdi-monitor-multiple:before{content:"\F37A"}.mdi-more:before{content:"\F37B"}.mdi-motorbike:before{content:"\F37C"}.mdi-mouse:before{content:"\F37D"}.mdi-mouse-off:before{content:"\F37E"}.mdi-mouse-variant:before{content:"\F37F"}.mdi-mouse-variant-off:before{content:"\F380"}.mdi-move-resize:before{content:"\F655"}.mdi-move-resize-variant:before{content:"\F656"}.mdi-movie:before{content:"\F381"}.mdi-multiplication:before{content:"\F382"}.mdi-multiplication-box:before{content:"\F383"}.mdi-music-box:before{content:"\F384"}.mdi-music-box-outline:before{content:"\F385"}.mdi-music-circle:before{content:"\F386"}.mdi-music-note:before{content:"\F387"}.mdi-music-note-bluetooth:before{content:"\F5FE"}.mdi-music-note-bluetooth-off:before{content:"\F5FF"}.mdi-music-note-eighth:before{content:"\F388"}.mdi-music-note-half:before{content:"\F389"}.mdi-music-note-off:before{content:"\F38A"}.mdi-music-note-quarter:before{content:"\F38B"}.mdi-music-note-sixteenth:before{content:"\F38C"}.mdi-music-note-whole:before{content:"\F38D"}.mdi-nature:before{content:"\F38E"}.mdi-nature-people:before{content:"\F38F"}.mdi-navigation:before{content:"\F390"}.mdi-near-me:before{content:"\F5CD"}.mdi-needle:before{content:"\F391"}.mdi-nest-protect:before{content:"\F392"}.mdi-nest-thermostat:before{content:"\F393"}.mdi-network:before{content:"\F6F2"}.mdi-network-download:before{content:"\F6F3"}.mdi-network-question:before{content:"\F6F4"}.mdi-network-upload:before{content:"\F6F5"}.mdi-new-box:before{content:"\F394"}.mdi-newspaper:before{content:"\F395"}.mdi-nfc:before{content:"\F396"}.mdi-nfc-tap:before{content:"\F397"}.mdi-nfc-variant:before{content:"\F398"}.mdi-nodejs:before{content:"\F399"}.mdi-note:before{content:"\F39A"}.mdi-note-multiple:before{content:"\F6B7"}.mdi-note-multiple-outline:before{content:"\F6B8"}.mdi-note-outline:before{content:"\F39B"}.mdi-note-plus:before{content:"\F39C"}.mdi-note-plus-outline:before{content:"\F39D"}.mdi-note-text:before{content:"\F39E"}.mdi-notification-clear-all:before{content:"\F39F"}.mdi-npm:before{content:"\F6F6"}.mdi-nuke:before{content:"\F6A3"}.mdi-numeric:before{content:"\F3A0"}.mdi-numeric-0-box:before{content:"\F3A1"}.mdi-numeric-0-box-multiple-outline:before{content:"\F3A2"}.mdi-numeric-0-box-outline:before{content:"\F3A3"}.mdi-numeric-1-box:before{content:"\F3A4"}.mdi-numeric-1-box-multiple-outline:before{content:"\F3A5"}.mdi-numeric-1-box-outline:before{content:"\F3A6"}.mdi-numeric-2-box:before{content:"\F3A7"}.mdi-numeric-2-box-multiple-outline:before{content:"\F3A8"}.mdi-numeric-2-box-outline:before{content:"\F3A9"}.mdi-numeric-3-box:before{content:"\F3AA"}.mdi-numeric-3-box-multiple-outline:before{content:"\F3AB"}.mdi-numeric-3-box-outline:before{content:"\F3AC"}.mdi-numeric-4-box:before{content:"\F3AD"}.mdi-numeric-4-box-multiple-outline:before{content:"\F3AE"}.mdi-numeric-4-box-outline:before{content:"\F3AF"}.mdi-numeric-5-box:before{content:"\F3B0"}.mdi-numeric-5-box-multiple-outline:before{content:"\F3B1"}.mdi-numeric-5-box-outline:before{content:"\F3B2"}.mdi-numeric-6-box:before{content:"\F3B3"}.mdi-numeric-6-box-multiple-outline:before{content:"\F3B4"}.mdi-numeric-6-box-outline:before{content:"\F3B5"}.mdi-numeric-7-box:before{content:"\F3B6"}.mdi-numeric-7-box-multiple-outline:before{content:"\F3B7"}.mdi-numeric-7-box-outline:before{content:"\F3B8"}.mdi-numeric-8-box:before{content:"\F3B9"}.mdi-numeric-8-box-multiple-outline:before{content:"\F3BA"}.mdi-numeric-8-box-outline:before{content:"\F3BB"}.mdi-numeric-9-box:before{content:"\F3BC"}.mdi-numeric-9-box-multiple-outline:before{content:"\F3BD"}.mdi-numeric-9-box-outline:before{content:"\F3BE"}.mdi-numeric-9-plus-box:before{content:"\F3BF"}.mdi-numeric-9-plus-box-multiple-outline:before{content:"\F3C0"}.mdi-numeric-9-plus-box-outline:before{content:"\F3C1"}.mdi-nut:before{content:"\F6F7"}.mdi-nutrition:before{content:"\F3C2"}.mdi-oar:before{content:"\F67B"}.mdi-octagon:before{content:"\F3C3"}.mdi-octagon-outline:before{content:"\F3C4"}.mdi-octagram:before{content:"\F6F8"}.mdi-odnoklassniki:before{content:"\F3C5"}.mdi-office:before{content:"\F3C6"}.mdi-oil:before{content:"\F3C7"}.mdi-oil-temperature:before{content:"\F3C8"}.mdi-omega:before{content:"\F3C9"}.mdi-onedrive:before{content:"\F3CA"}.mdi-opacity:before{content:"\F5CC"}.mdi-open-in-app:before{content:"\F3CB"}.mdi-open-in-new:before{content:"\F3CC"}.mdi-openid:before{content:"\F3CD"}.mdi-opera:before{content:"\F3CE"}.mdi-ornament:before{content:"\F3CF"}.mdi-ornament-variant:before{content:"\F3D0"}.mdi-owl:before{content:"\F3D2"}.mdi-package:before{content:"\F3D3"}.mdi-package-down:before{content:"\F3D4"}.mdi-package-up:before{content:"\F3D5"}.mdi-package-variant:before{content:"\F3D6"}.mdi-package-variant-closed:before{content:"\F3D7"}.mdi-page-first:before{content:"\F600"}.mdi-page-last:before{content:"\F601"}.mdi-page-layout-body:before{content:"\F6F9"}.mdi-page-layout-footer:before{content:"\F6FA"}.mdi-page-layout-header:before{content:"\F6FB"}.mdi-page-layout-sidebar-left:before{content:"\F6FC"}.mdi-page-layout-sidebar-right:before{content:"\F6FD"}.mdi-palette:before{content:"\F3D8"}.mdi-palette-advanced:before{content:"\F3D9"}.mdi-panda:before{content:"\F3DA"}.mdi-pandora:before{content:"\F3DB"}.mdi-panorama:before{content:"\F3DC"}.mdi-panorama-fisheye:before{content:"\F3DD"}.mdi-panorama-horizontal:before{content:"\F3DE"}.mdi-panorama-vertical:before{content:"\F3DF"}.mdi-panorama-wide-angle:before{content:"\F3E0"}.mdi-paper-cut-vertical:before{content:"\F3E1"}.mdi-paperclip:before{content:"\F3E2"}.mdi-parking:before{content:"\F3E3"}.mdi-pause:before{content:"\F3E4"}.mdi-pause-circle:before{content:"\F3E5"}.mdi-pause-circle-outline:before{content:"\F3E6"}.mdi-pause-octagon:before{content:"\F3E7"}.mdi-pause-octagon-outline:before{content:"\F3E8"}.mdi-paw:before{content:"\F3E9"}.mdi-paw-off:before{content:"\F657"}.mdi-pen:before{content:"\F3EA"}.mdi-pencil:before{content:"\F3EB"}.mdi-pencil-box:before{content:"\F3EC"}.mdi-pencil-box-outline:before{content:"\F3ED"}.mdi-pencil-circle:before{content:"\F6FE"}.mdi-pencil-lock:before{content:"\F3EE"}.mdi-pencil-off:before{content:"\F3EF"}.mdi-pentagon:before{content:"\F6FF"}.mdi-pentagon-outline:before{content:"\F700"}.mdi-percent:before{content:"\F3F0"}.mdi-pharmacy:before{content:"\F3F1"}.mdi-phone:before{content:"\F3F2"}.mdi-phone-bluetooth:before{content:"\F3F3"}.mdi-phone-classic:before{content:"\F602"}.mdi-phone-forward:before{content:"\F3F4"}.mdi-phone-hangup:before{content:"\F3F5"}.mdi-phone-in-talk:before{content:"\F3F6"}.mdi-phone-incoming:before{content:"\F3F7"}.mdi-phone-locked:before{content:"\F3F8"}.mdi-phone-log:before{content:"\F3F9"}.mdi-phone-minus:before{content:"\F658"}.mdi-phone-missed:before{content:"\F3FA"}.mdi-phone-outgoing:before{content:"\F3FB"}.mdi-phone-paused:before{content:"\F3FC"}.mdi-phone-plus:before{content:"\F659"}.mdi-phone-settings:before{content:"\F3FD"}.mdi-phone-voip:before{content:"\F3FE"}.mdi-pi:before{content:"\F3FF"}.mdi-pi-box:before{content:"\F400"}.mdi-piano:before{content:"\F67C"}.mdi-pig:before{content:"\F401"}.mdi-pill:before{content:"\F402"}.mdi-pillar:before{content:"\F701"}.mdi-pin:before{content:"\F403"}.mdi-pin-off:before{content:"\F404"}.mdi-pine-tree:before{content:"\F405"}.mdi-pine-tree-box:before{content:"\F406"}.mdi-pinterest:before{content:"\F407"}.mdi-pinterest-box:before{content:"\F408"}.mdi-pistol:before{content:"\F702"}.mdi-pizza:before{content:"\F409"}.mdi-plane-shield:before{content:"\F6BA"}.mdi-play:before{content:"\F40A"}.mdi-play-box-outline:before{content:"\F40B"}.mdi-play-circle:before{content:"\F40C"}.mdi-play-circle-outline:before{content:"\F40D"}.mdi-play-pause:before{content:"\F40E"}.mdi-play-protected-content:before{content:"\F40F"}.mdi-playlist-check:before{content:"\F5C7"}.mdi-playlist-minus:before{content:"\F410"}.mdi-playlist-play:before{content:"\F411"}.mdi-playlist-plus:before{content:"\F412"}.mdi-playlist-remove:before{content:"\F413"}.mdi-playstation:before{content:"\F414"}.mdi-plex:before{content:"\F6B9"}.mdi-plus:before{content:"\F415"}.mdi-plus-box:before{content:"\F416"}.mdi-plus-box-outline:before{content:"\F703"}.mdi-plus-circle:before{content:"\F417"}.mdi-plus-circle-multiple-outline:before{content:"\F418"}.mdi-plus-circle-outline:before{content:"\F419"}.mdi-plus-network:before{content:"\F41A"}.mdi-plus-one:before{content:"\F41B"}.mdi-plus-outline:before{content:"\F704"}.mdi-pocket:before{content:"\F41C"}.mdi-pokeball:before{content:"\F41D"}.mdi-polaroid:before{content:"\F41E"}.mdi-poll:before{content:"\F41F"}.mdi-poll-box:before{content:"\F420"}.mdi-polymer:before{content:"\F421"}.mdi-pool:before{content:"\F606"}.mdi-popcorn:before{content:"\F422"}.mdi-pot:before{content:"\F65A"}.mdi-pot-mix:before{content:"\F65B"}.mdi-pound:before{content:"\F423"}.mdi-pound-box:before{content:"\F424"}.mdi-power:before{content:"\F425"}.mdi-power-plug:before{content:"\F6A4"}.mdi-power-plug-off:before{content:"\F6A5"}.mdi-power-settings:before{content:"\F426"}.mdi-power-socket:before{content:"\F427"}.mdi-prescription:before{content:"\F705"}.mdi-presentation:before{content:"\F428"}.mdi-presentation-play:before{content:"\F429"}.mdi-printer:before{content:"\F42A"}.mdi-printer-3d:before{content:"\F42B"}.mdi-printer-alert:before{content:"\F42C"}.mdi-printer-settings:before{content:"\F706"}.mdi-priority-high:before{content:"\F603"}.mdi-priority-low:before{content:"\F604"}.mdi-professional-hexagon:before{content:"\F42D"}.mdi-projector:before{content:"\F42E"}.mdi-projector-screen:before{content:"\F42F"}.mdi-publish:before{content:"\F6A6"}.mdi-pulse:before{content:"\F430"}.mdi-puzzle:before{content:"\F431"}.mdi-qqchat:before{content:"\F605"}.mdi-qrcode:before{content:"\F432"}.mdi-qrcode-scan:before{content:"\F433"}.mdi-quadcopter:before{content:"\F434"}.mdi-quality-high:before{content:"\F435"}.mdi-quicktime:before{content:"\F436"}.mdi-radar:before{content:"\F437"}.mdi-radiator:before{content:"\F438"}.mdi-radio:before{content:"\F439"}.mdi-radio-handheld:before{content:"\F43A"}.mdi-radio-tower:before{content:"\F43B"}.mdi-radioactive:before{content:"\F43C"}.mdi-radiobox-blank:before{content:"\F43D"}.mdi-radiobox-marked:before{content:"\F43E"}.mdi-raspberrypi:before{content:"\F43F"}.mdi-ray-end:before{content:"\F440"}.mdi-ray-end-arrow:before{content:"\F441"}.mdi-ray-start:before{content:"\F442"}.mdi-ray-start-arrow:before{content:"\F443"}.mdi-ray-start-end:before{content:"\F444"}.mdi-ray-vertex:before{content:"\F445"}.mdi-rdio:before{content:"\F446"}.mdi-react:before{content:"\F707"}.mdi-read:before{content:"\F447"}.mdi-readability:before{content:"\F448"}.mdi-receipt:before{content:"\F449"}.mdi-record:before{content:"\F44A"}.mdi-record-rec:before{content:"\F44B"}.mdi-recycle:before{content:"\F44C"}.mdi-reddit:before{content:"\F44D"}.mdi-redo:before{content:"\F44E"}.mdi-redo-variant:before{content:"\F44F"}.mdi-refresh:before{content:"\F450"}.mdi-regex:before{content:"\F451"}.mdi-relative-scale:before{content:"\F452"}.mdi-reload:before{content:"\F453"}.mdi-remote:before{content:"\F454"}.mdi-rename-box:before{content:"\F455"}.mdi-reorder-horizontal:before{content:"\F687"}.mdi-reorder-vertical:before{content:"\F688"}.mdi-repeat:before{content:"\F456"}.mdi-repeat-off:before{content:"\F457"}.mdi-repeat-once:before{content:"\F458"}.mdi-replay:before{content:"\F459"}.mdi-reply:before{content:"\F45A"}.mdi-reply-all:before{content:"\F45B"}.mdi-reproduction:before{content:"\F45C"}.mdi-resize-bottom-right:before{content:"\F45D"}.mdi-responsive:before{content:"\F45E"}.mdi-restart:before{content:"\F708"}.mdi-restore:before{content:"\F6A7"}.mdi-rewind:before{content:"\F45F"}.mdi-rewind-outline:before{content:"\F709"}.mdi-rhombus:before{content:"\F70A"}.mdi-rhombus-outline:before{content:"\F70B"}.mdi-ribbon:before{content:"\F460"}.mdi-road:before{content:"\F461"}.mdi-road-variant:before{content:"\F462"}.mdi-robot:before{content:"\F6A8"}.mdi-rocket:before{content:"\F463"}.mdi-roomba:before{content:"\F70C"}.mdi-rotate-3d:before{content:"\F464"}.mdi-rotate-left:before{content:"\F465"}.mdi-rotate-left-variant:before{content:"\F466"}.mdi-rotate-right:before{content:"\F467"}.mdi-rotate-right-variant:before{content:"\F468"}.mdi-rounded-corner:before{content:"\F607"}.mdi-router-wireless:before{content:"\F469"}.mdi-routes:before{content:"\F46A"}.mdi-rowing:before{content:"\F608"}.mdi-rss:before{content:"\F46B"}.mdi-rss-box:before{content:"\F46C"}.mdi-ruler:before{content:"\F46D"}.mdi-run:before{content:"\F70D"}.mdi-run-fast:before{content:"\F46E"}.mdi-sale:before{content:"\F46F"}.mdi-satellite:before{content:"\F470"}.mdi-satellite-variant:before{content:"\F471"}.mdi-saxophone:before{content:"\F609"}.mdi-scale:before{content:"\F472"}.mdi-scale-balance:before{content:"\F5D1"}.mdi-scale-bathroom:before{content:"\F473"}.mdi-scanner:before{content:"\F6AA"}.mdi-school:before{content:"\F474"}.mdi-screen-rotation:before{content:"\F475"}.mdi-screen-rotation-lock:before{content:"\F476"}.mdi-screwdriver:before{content:"\F477"}.mdi-script:before{content:"\F478"}.mdi-sd:before{content:"\F479"}.mdi-seal:before{content:"\F47A"}.mdi-search-web:before{content:"\F70E"}.mdi-seat-flat:before{content:"\F47B"}.mdi-seat-flat-angled:before{content:"\F47C"}.mdi-seat-individual-suite:before{content:"\F47D"}.mdi-seat-legroom-extra:before{content:"\F47E"}.mdi-seat-legroom-normal:before{content:"\F47F"}.mdi-seat-legroom-reduced:before{content:"\F480"}.mdi-seat-recline-extra:before{content:"\F481"}.mdi-seat-recline-normal:before{content:"\F482"}.mdi-security:before{content:"\F483"}.mdi-security-home:before{content:"\F689"}.mdi-security-network:before{content:"\F484"}.mdi-select:before{content:"\F485"}.mdi-select-all:before{content:"\F486"}.mdi-select-inverse:before{content:"\F487"}.mdi-select-off:before{content:"\F488"}.mdi-selection:before{content:"\F489"}.mdi-send:before{content:"\F48A"}.mdi-serial-port:before{content:"\F65C"}.mdi-server:before{content:"\F48B"}.mdi-server-minus:before{content:"\F48C"}.mdi-server-network:before{content:"\F48D"}.mdi-server-network-off:before{content:"\F48E"}.mdi-server-off:before{content:"\F48F"}.mdi-server-plus:before{content:"\F490"}.mdi-server-remove:before{content:"\F491"}.mdi-server-security:before{content:"\F492"}.mdi-settings:before{content:"\F493"}.mdi-settings-box:before{content:"\F494"}.mdi-shape-circle-plus:before{content:"\F65D"}.mdi-shape-plus:before{content:"\F495"}.mdi-shape-polygon-plus:before{content:"\F65E"}.mdi-shape-rectangle-plus:before{content:"\F65F"}.mdi-shape-square-plus:before{content:"\F660"}.mdi-share:before{content:"\F496"}.mdi-share-variant:before{content:"\F497"}.mdi-shield:before{content:"\F498"}.mdi-shield-outline:before{content:"\F499"}.mdi-shopping:before{content:"\F49A"}.mdi-shopping-music:before{content:"\F49B"}.mdi-shovel:before{content:"\F70F"}.mdi-shovel-off:before{content:"\F710"}.mdi-shredder:before{content:"\F49C"}.mdi-shuffle:before{content:"\F49D"}.mdi-shuffle-disabled:before{content:"\F49E"}.mdi-shuffle-variant:before{content:"\F49F"}.mdi-sigma:before{content:"\F4A0"}.mdi-sigma-lower:before{content:"\F62B"}.mdi-sign-caution:before{content:"\F4A1"}.mdi-signal:before{content:"\F4A2"}.mdi-signal-2g:before{content:"\F711"}.mdi-signal-3g:before{content:"\F712"}.mdi-signal-4g:before{content:"\F713"}.mdi-signal-hspa:before{content:"\F714"}.mdi-signal-hspa-plus:before{content:"\F715"}.mdi-signal-variant:before{content:"\F60A"}.mdi-silverware:before{content:"\F4A3"}.mdi-silverware-fork:before{content:"\F4A4"}.mdi-silverware-spoon:before{content:"\F4A5"}.mdi-silverware-variant:before{content:"\F4A6"}.mdi-sim:before{content:"\F4A7"}.mdi-sim-alert:before{content:"\F4A8"}.mdi-sim-off:before{content:"\F4A9"}.mdi-sitemap:before{content:"\F4AA"}.mdi-skip-backward:before{content:"\F4AB"}.mdi-skip-forward:before{content:"\F4AC"}.mdi-skip-next:before{content:"\F4AD"}.mdi-skip-next-circle:before{content:"\F661"}.mdi-skip-next-circle-outline:before{content:"\F662"}.mdi-skip-previous:before{content:"\F4AE"}.mdi-skip-previous-circle:before{content:"\F663"}.mdi-skip-previous-circle-outline:before{content:"\F664"}.mdi-skull:before{content:"\F68B"}.mdi-skype:before{content:"\F4AF"}.mdi-skype-business:before{content:"\F4B0"}.mdi-slack:before{content:"\F4B1"}.mdi-sleep:before{content:"\F4B2"}.mdi-sleep-off:before{content:"\F4B3"}.mdi-smoking:before{content:"\F4B4"}.mdi-smoking-off:before{content:"\F4B5"}.mdi-snapchat:before{content:"\F4B6"}.mdi-snowflake:before{content:"\F716"}.mdi-snowman:before{content:"\F4B7"}.mdi-soccer:before{content:"\F4B8"}.mdi-sofa:before{content:"\F4B9"}.mdi-solid:before{content:"\F68C"}.mdi-sort:before{content:"\F4BA"}.mdi-sort-alphabetical:before{content:"\F4BB"}.mdi-sort-ascending:before{content:"\F4BC"}.mdi-sort-descending:before{content:"\F4BD"}.mdi-sort-numeric:before{content:"\F4BE"}.mdi-sort-variant:before{content:"\F4BF"}.mdi-soundcloud:before{content:"\F4C0"}.mdi-source-branch:before{content:"\F62C"}.mdi-source-commit:before{content:"\F717"}.mdi-source-commit-end:before{content:"\F718"}.mdi-source-commit-end-local:before{content:"\F719"}.mdi-source-commit-local:before{content:"\F71A"}.mdi-source-commit-next-local:before{content:"\F71B"}.mdi-source-commit-start:before{content:"\F71C"}.mdi-source-commit-start-next-local:before{content:"\F71D"}.mdi-source-fork:before{content:"\F4C1"}.mdi-source-merge:before{content:"\F62D"}.mdi-source-pull:before{content:"\F4C2"}.mdi-speaker:before{content:"\F4C3"}.mdi-speaker-off:before{content:"\F4C4"}.mdi-speaker-wireless:before{content:"\F71E"}.mdi-speedometer:before{content:"\F4C5"}.mdi-spellcheck:before{content:"\F4C6"}.mdi-spotify:before{content:"\F4C7"}.mdi-spotlight:before{content:"\F4C8"}.mdi-spotlight-beam:before{content:"\F4C9"}.mdi-spray:before{content:"\F665"}.mdi-square-inc:before{content:"\F4CA"}.mdi-square-inc-cash:before{content:"\F4CB"}.mdi-stackexchange:before{content:"\F60B"}.mdi-stackoverflow:before{content:"\F4CC"}.mdi-stadium:before{content:"\F71F"}.mdi-stairs:before{content:"\F4CD"}.mdi-star:before{content:"\F4CE"}.mdi-star-circle:before{content:"\F4CF"}.mdi-star-half:before{content:"\F4D0"}.mdi-star-off:before{content:"\F4D1"}.mdi-star-outline:before{content:"\F4D2"}.mdi-steam:before{content:"\F4D3"}.mdi-steering:before{content:"\F4D4"}.mdi-step-backward:before{content:"\F4D5"}.mdi-step-backward-2:before{content:"\F4D6"}.mdi-step-forward:before{content:"\F4D7"}.mdi-step-forward-2:before{content:"\F4D8"}.mdi-stethoscope:before{content:"\F4D9"}.mdi-sticker:before{content:"\F5D0"}.mdi-stocking:before{content:"\F4DA"}.mdi-stop:before{content:"\F4DB"}.mdi-stop-circle:before{content:"\F666"}.mdi-stop-circle-outline:before{content:"\F667"}.mdi-store:before{content:"\F4DC"}.mdi-store-24-hour:before{content:"\F4DD"}.mdi-stove:before{content:"\F4DE"}.mdi-subdirectory-arrow-left:before{content:"\F60C"}.mdi-subdirectory-arrow-right:before{content:"\F60D"}.mdi-subway:before{content:"\F6AB"}.mdi-subway-variant:before{content:"\F4DF"}.mdi-sunglasses:before{content:"\F4E0"}.mdi-surround-sound:before{content:"\F5C5"}.mdi-svg:before{content:"\F720"}.mdi-swap-horizontal:before{content:"\F4E1"}.mdi-swap-vertical:before{content:"\F4E2"}.mdi-swim:before{content:"\F4E3"}.mdi-switch:before{content:"\F4E4"}.mdi-sword:before{content:"\F4E5"}.mdi-sync:before{content:"\F4E6"}.mdi-sync-alert:before{content:"\F4E7"}.mdi-sync-off:before{content:"\F4E8"}.mdi-tab:before{content:"\F4E9"}.mdi-tab-unselected:before{content:"\F4EA"}.mdi-table:before{content:"\F4EB"}.mdi-table-column-plus-after:before{content:"\F4EC"}.mdi-table-column-plus-before:before{content:"\F4ED"}.mdi-table-column-remove:before{content:"\F4EE"}.mdi-table-column-width:before{content:"\F4EF"}.mdi-table-edit:before{content:"\F4F0"}.mdi-table-large:before{content:"\F4F1"}.mdi-table-row-height:before{content:"\F4F2"}.mdi-table-row-plus-after:before{content:"\F4F3"}.mdi-table-row-plus-before:before{content:"\F4F4"}.mdi-table-row-remove:before{content:"\F4F5"}.mdi-tablet:before{content:"\F4F6"}.mdi-tablet-android:before{content:"\F4F7"}.mdi-tablet-ipad:before{content:"\F4F8"}.mdi-tag:before{content:"\F4F9"}.mdi-tag-faces:before{content:"\F4FA"}.mdi-tag-heart:before{content:"\F68A"}.mdi-tag-multiple:before{content:"\F4FB"}.mdi-tag-outline:before{content:"\F4FC"}.mdi-tag-plus:before{content:"\F721"}.mdi-tag-remove:before{content:"\F722"}.mdi-tag-text-outline:before{content:"\F4FD"}.mdi-target:before{content:"\F4FE"}.mdi-taxi:before{content:"\F4FF"}.mdi-teamviewer:before{content:"\F500"}.mdi-telegram:before{content:"\F501"}.mdi-television:before{content:"\F502"}.mdi-television-guide:before{content:"\F503"}.mdi-temperature-celsius:before{content:"\F504"}.mdi-temperature-fahrenheit:before{content:"\F505"}.mdi-temperature-kelvin:before{content:"\F506"}.mdi-tennis:before{content:"\F507"}.mdi-tent:before{content:"\F508"}.mdi-terrain:before{content:"\F509"}.mdi-test-tube:before{content:"\F668"}.mdi-text-shadow:before{content:"\F669"}.mdi-text-to-speech:before{content:"\F50A"}.mdi-text-to-speech-off:before{content:"\F50B"}.mdi-textbox:before{content:"\F60E"}.mdi-texture:before{content:"\F50C"}.mdi-theater:before{content:"\F50D"}.mdi-theme-light-dark:before{content:"\F50E"}.mdi-thermometer:before{content:"\F50F"}.mdi-thermometer-lines:before{content:"\F510"}.mdi-thumb-down:before{content:"\F511"}.mdi-thumb-down-outline:before{content:"\F512"}.mdi-thumb-up:before{content:"\F513"}.mdi-thumb-up-outline:before{content:"\F514"}.mdi-thumbs-up-down:before{content:"\F515"}.mdi-ticket:before{content:"\F516"}.mdi-ticket-account:before{content:"\F517"}.mdi-ticket-confirmation:before{content:"\F518"}.mdi-ticket-percent:before{content:"\F723"}.mdi-tie:before{content:"\F519"}.mdi-tilde:before{content:"\F724"}.mdi-timelapse:before{content:"\F51A"}.mdi-timer:before{content:"\F51B"}.mdi-timer-10:before{content:"\F51C"}.mdi-timer-3:before{content:"\F51D"}.mdi-timer-off:before{content:"\F51E"}.mdi-timer-sand:before{content:"\F51F"}.mdi-timer-sand-empty:before{content:"\F6AC"}.mdi-timetable:before{content:"\F520"}.mdi-toggle-switch:before{content:"\F521"}.mdi-toggle-switch-off:before{content:"\F522"}.mdi-tooltip:before{content:"\F523"}.mdi-tooltip-edit:before{content:"\F524"}.mdi-tooltip-image:before{content:"\F525"}.mdi-tooltip-outline:before{content:"\F526"}.mdi-tooltip-outline-plus:before{content:"\F527"}.mdi-tooltip-text:before{content:"\F528"}.mdi-tooth:before{content:"\F529"}.mdi-tor:before{content:"\F52A"}.mdi-tower-beach:before{content:"\F680"}.mdi-tower-fire:before{content:"\F681"}.mdi-traffic-light:before{content:"\F52B"}.mdi-train:before{content:"\F52C"}.mdi-tram:before{content:"\F52D"}.mdi-transcribe:before{content:"\F52E"}.mdi-transcribe-close:before{content:"\F52F"}.mdi-transfer:before{content:"\F530"}.mdi-transit-transfer:before{content:"\F6AD"}.mdi-translate:before{content:"\F5CA"}.mdi-treasure-chest:before{content:"\F725"}.mdi-tree:before{content:"\F531"}.mdi-trello:before{content:"\F532"}.mdi-trending-down:before{content:"\F533"}.mdi-trending-neutral:before{content:"\F534"}.mdi-trending-up:before{content:"\F535"}.mdi-triangle:before{content:"\F536"}.mdi-triangle-outline:before{content:"\F537"}.mdi-trophy:before{content:"\F538"}.mdi-trophy-award:before{content:"\F539"}.mdi-trophy-outline:before{content:"\F53A"}.mdi-trophy-variant:before{content:"\F53B"}.mdi-trophy-variant-outline:before{content:"\F53C"}.mdi-truck:before{content:"\F53D"}.mdi-truck-delivery:before{content:"\F53E"}.mdi-truck-trailer:before{content:"\F726"}.mdi-tshirt-crew:before{content:"\F53F"}.mdi-tshirt-v:before{content:"\F540"}.mdi-tumblr:before{content:"\F541"}.mdi-tumblr-reblog:before{content:"\F542"}.mdi-tune:before{content:"\F62E"}.mdi-tune-vertical:before{content:"\F66A"}.mdi-twitch:before{content:"\F543"}.mdi-twitter:before{content:"\F544"}.mdi-twitter-box:before{content:"\F545"}.mdi-twitter-circle:before{content:"\F546"}.mdi-twitter-retweet:before{content:"\F547"}.mdi-ubuntu:before{content:"\F548"}.mdi-umbraco:before{content:"\F549"}.mdi-umbrella:before{content:"\F54A"}.mdi-umbrella-outline:before{content:"\F54B"}.mdi-undo:before{content:"\F54C"}.mdi-undo-variant:before{content:"\F54D"}.mdi-unfold-less:before{content:"\F54E"}.mdi-unfold-more:before{content:"\F54F"}.mdi-ungroup:before{content:"\F550"}.mdi-unity:before{content:"\F6AE"}.mdi-untappd:before{content:"\F551"}.mdi-update:before{content:"\F6AF"}.mdi-upload:before{content:"\F552"}.mdi-usb:before{content:"\F553"}.mdi-vector-arrange-above:before{content:"\F554"}.mdi-vector-arrange-below:before{content:"\F555"}.mdi-vector-circle:before{content:"\F556"}.mdi-vector-circle-variant:before{content:"\F557"}.mdi-vector-combine:before{content:"\F558"}.mdi-vector-curve:before{content:"\F559"}.mdi-vector-difference:before{content:"\F55A"}.mdi-vector-difference-ab:before{content:"\F55B"}.mdi-vector-difference-ba:before{content:"\F55C"}.mdi-vector-intersection:before{content:"\F55D"}.mdi-vector-line:before{content:"\F55E"}.mdi-vector-point:before{content:"\F55F"}.mdi-vector-polygon:before{content:"\F560"}.mdi-vector-polyline:before{content:"\F561"}.mdi-vector-rectangle:before{content:"\F5C6"}.mdi-vector-selection:before{content:"\F562"}.mdi-vector-square:before{content:"\F001"}.mdi-vector-triangle:before{content:"\F563"}.mdi-vector-union:before{content:"\F564"}.mdi-verified:before{content:"\F565"}.mdi-vibrate:before{content:"\F566"}.mdi-video:before{content:"\F567"}.mdi-video-off:before{content:"\F568"}.mdi-video-switch:before{content:"\F569"}.mdi-view-agenda:before{content:"\F56A"}.mdi-view-array:before{content:"\F56B"}.mdi-view-carousel:before{content:"\F56C"}.mdi-view-column:before{content:"\F56D"}.mdi-view-dashboard:before{content:"\F56E"}.mdi-view-day:before{content:"\F56F"}.mdi-view-grid:before{content:"\F570"}.mdi-view-headline:before{content:"\F571"}.mdi-view-list:before{content:"\F572"}.mdi-view-module:before{content:"\F573"}.mdi-view-parallel:before{content:"\F727"}.mdi-view-quilt:before{content:"\F574"}.mdi-view-sequential:before{content:"\F728"}.mdi-view-stream:before{content:"\F575"}.mdi-view-week:before{content:"\F576"}.mdi-vimeo:before{content:"\F577"}.mdi-vine:before{content:"\F578"}.mdi-violin:before{content:"\F60F"}.mdi-visualstudio:before{content:"\F610"}.mdi-vk:before{content:"\F579"}.mdi-vk-box:before{content:"\F57A"}.mdi-vk-circle:before{content:"\F57B"}.mdi-vlc:before{content:"\F57C"}.mdi-voice:before{content:"\F5CB"}.mdi-voicemail:before{content:"\F57D"}.mdi-volume-high:before{content:"\F57E"}.mdi-volume-low:before{content:"\F57F"}.mdi-volume-medium:before{content:"\F580"}.mdi-volume-off:before{content:"\F581"}.mdi-vpn:before{content:"\F582"}.mdi-walk:before{content:"\F583"}.mdi-wallet:before{content:"\F584"}.mdi-wallet-giftcard:before{content:"\F585"}.mdi-wallet-membership:before{content:"\F586"}.mdi-wallet-travel:before{content:"\F587"}.mdi-wan:before{content:"\F588"}.mdi-washing-machine:before{content:"\F729"}.mdi-watch:before{content:"\F589"}.mdi-watch-export:before{content:"\F58A"}.mdi-watch-import:before{content:"\F58B"}.mdi-watch-vibrate:before{content:"\F6B0"}.mdi-water:before{content:"\F58C"}.mdi-water-off:before{content:"\F58D"}.mdi-water-percent:before{content:"\F58E"}.mdi-water-pump:before{content:"\F58F"}.mdi-watermark:before{content:"\F612"}.mdi-weather-cloudy:before{content:"\F590"}.mdi-weather-fog:before{content:"\F591"}.mdi-weather-hail:before{content:"\F592"}.mdi-weather-lightning:before{content:"\F593"}.mdi-weather-lightning-rainy:before{content:"\F67D"}.mdi-weather-night:before{content:"\F594"}.mdi-weather-partlycloudy:before{content:"\F595"}.mdi-weather-pouring:before{content:"\F596"}.mdi-weather-rainy:before{content:"\F597"}.mdi-weather-snowy:before{content:"\F598"}.mdi-weather-snowy-rainy:before{content:"\F67E"}.mdi-weather-sunny:before{content:"\F599"}.mdi-weather-sunset:before{content:"\F59A"}.mdi-weather-sunset-down:before{content:"\F59B"}.mdi-weather-sunset-up:before{content:"\F59C"}.mdi-weather-windy:before{content:"\F59D"}.mdi-weather-windy-variant:before{content:"\F59E"}.mdi-web:before{content:"\F59F"}.mdi-webcam:before{content:"\F5A0"}.mdi-webhook:before{content:"\F62F"}.mdi-webpack:before{content:"\F72A"}.mdi-wechat:before{content:"\F611"}.mdi-weight:before{content:"\F5A1"}.mdi-weight-kilogram:before{content:"\F5A2"}.mdi-whatsapp:before{content:"\F5A3"}.mdi-wheelchair-accessibility:before{content:"\F5A4"}.mdi-white-balance-auto:before{content:"\F5A5"}.mdi-white-balance-incandescent:before{content:"\F5A6"}.mdi-white-balance-iridescent:before{content:"\F5A7"}.mdi-white-balance-sunny:before{content:"\F5A8"}.mdi-widgets:before{content:"\F72B"}.mdi-wifi:before{content:"\F5A9"}.mdi-wifi-off:before{content:"\F5AA"}.mdi-wii:before{content:"\F5AB"}.mdi-wiiu:before{content:"\F72C"}.mdi-wikipedia:before{content:"\F5AC"}.mdi-window-close:before{content:"\F5AD"}.mdi-window-closed:before{content:"\F5AE"}.mdi-window-maximize:before{content:"\F5AF"}.mdi-window-minimize:before{content:"\F5B0"}.mdi-window-open:before{content:"\F5B1"}.mdi-window-restore:before{content:"\F5B2"}.mdi-windows:before{content:"\F5B3"}.mdi-wordpress:before{content:"\F5B4"}.mdi-worker:before{content:"\F5B5"}.mdi-wrap:before{content:"\F5B6"}.mdi-wrench:before{content:"\F5B7"}.mdi-wunderlist:before{content:"\F5B8"}.mdi-xaml:before{content:"\F673"}.mdi-xbox:before{content:"\F5B9"}.mdi-xbox-controller:before{content:"\F5BA"}.mdi-xbox-controller-off:before{content:"\F5BB"}.mdi-xda:before{content:"\F5BC"}.mdi-xing:before{content:"\F5BD"}.mdi-xing-box:before{content:"\F5BE"}.mdi-xing-circle:before{content:"\F5BF"}.mdi-xml:before{content:"\F5C0"}.mdi-yeast:before{content:"\F5C1"}.mdi-yelp:before{content:"\F5C2"}.mdi-yin-yang:before{content:"\F67F"}.mdi-youtube-play:before{content:"\F5C3"}.mdi-zip-box:before{content:"\F5C4"}.mdi-18px.mdi-set,.mdi-18px.mdi:before{font-size:18px}.mdi-24px.mdi-set,.mdi-24px.mdi:before{font-size:24px}.mdi-36px.mdi-set,.mdi-36px.mdi:before{font-size:36px}.mdi-48px.mdi-set,.mdi-48px.mdi:before{font-size:48px}.mdi-dark{color:rgba(0,0,0,0.54)}.mdi-dark.mdi-inactive{color:rgba(0,0,0,0.26)}.mdi-light{color:#fff}.mdi-light.mdi-inactive{color:rgba(255,255,255,0.3)}.mdi-rotate-45{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.mdi-rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.mdi-rotate-135{-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg)}.mdi-rotate-180{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.mdi-rotate-225{-webkit-transform:rotate(225deg);-ms-transform:rotate(225deg);transform:rotate(225deg)}.mdi-rotate-270{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.mdi-rotate-315{-webkit-transform:rotate(315deg);-ms-transform:rotate(315deg);transform:rotate(315deg)}.mdi-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH"}.mdi-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1);filter:FlipV;-ms-filter:"FlipV"} +/*# sourceMappingURL=materialdesignicons.min.css.map */ diff --git a/wi/wi-front/dist/fonts/materialdesignicons-webfont.eot b/wi/wi-front/dist/fonts/materialdesignicons-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..21884daf9c5c34da8831b16353797fb3846f61c6 GIT binary patch literal 261608 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%0ANW&Lw;FEJLW44MZ`3k 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�bY{%R9LwYW0ZyA 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

nM_5(}nK*!7=>;1L}i7L>M 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wi/wi-front/dist/fonts/materialdesignicons-webfont.ttf b/wi/wi-front/dist/fonts/materialdesignicons-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..313b5b7f8a78538c9963ce7c87478036fcae59d2 GIT binary patch literal 261388 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 P;p+!x-ravSWd?}NHdwzolDCtC}3-HD|Y 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 x=z>HGy2ici!L$uCP(!o literal 0 HcmV?d00001 diff --git a/wi/wi-front/dist/fonts/materialdesignicons-webfont.woff2 b/wi/wi-front/dist/fonts/materialdesignicons-webfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f654283a95e6bbf6402364f6c9f7249b66ed8045 GIT binary patch literal 99736 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%0DLRAuFhPk39c4>`?iT*xs^0y<)H!@JLPF0W(i* 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%0D#QOu9rKRgXpTg})#$5yk-L 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 vVF1XZh@RioAmBavu}Sd%4`e0q<3U=97c*cb#A + + + + + + + EQEmu WI Front + + + + + + + Loading... + + + + diff --git a/wi/wi-front/dist/js/bundle.js b/wi/wi-front/dist/js/bundle.js new file mode 100644 index 000000000..9f7215830 --- /dev/null +++ b/wi/wi-front/dist/js/bundle.js @@ -0,0 +1,76151 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + className += (i > 0) ? ' ' : ''; + className += isPrefix ? fix + klass + : klass + fix; + } + }); + return className; +} + +function removeFromArray(arr, val) { + var index = arr.indexOf(val); + if (val >= 0) { + arr.splice(index, 1); + } +} + +function stripCommentsFromElement(element) { + if (element instanceof jqLite) { + switch (element.length) { + case 0: + return element; + + case 1: + // there is no point of stripping anything if the element + // is the only element within the jqLite wrapper. + // (it's important that we retain the element instance.) + if (element[0].nodeType === ELEMENT_NODE) { + return element; + } + break; + + default: + return jqLite(extractElementNode(element)); + } + } + + if (element.nodeType === ELEMENT_NODE) { + return jqLite(element); + } +} + +function extractElementNode(element) { + if (!element[0]) return element; + for (var i = 0; i < element.length; i++) { + var elm = element[i]; + if (elm.nodeType === ELEMENT_NODE) { + return elm; + } + } +} + +function $$addClass($$jqLite, element, className) { + forEach(element, function(elm) { + $$jqLite.addClass(elm, className); + }); +} + +function $$removeClass($$jqLite, element, className) { + forEach(element, function(elm) { + $$jqLite.removeClass(elm, className); + }); +} + +function applyAnimationClassesFactory($$jqLite) { + return function(element, options) { + if (options.addClass) { + $$addClass($$jqLite, element, options.addClass); + options.addClass = null; + } + if (options.removeClass) { + $$removeClass($$jqLite, element, options.removeClass); + options.removeClass = null; + } + }; +} + +function prepareAnimationOptions(options) { + options = options || {}; + if (!options.$$prepared) { + var domOperation = options.domOperation || noop; + options.domOperation = function() { + options.$$domOperationFired = true; + domOperation(); + domOperation = noop; + }; + options.$$prepared = true; + } + return options; +} + +function applyAnimationStyles(element, options) { + applyAnimationFromStyles(element, options); + applyAnimationToStyles(element, options); +} + +function applyAnimationFromStyles(element, options) { + if (options.from) { + element.css(options.from); + options.from = null; + } +} + +function applyAnimationToStyles(element, options) { + if (options.to) { + element.css(options.to); + options.to = null; + } +} + +function mergeAnimationDetails(element, oldAnimation, newAnimation) { + var target = oldAnimation.options || {}; + var newOptions = newAnimation.options || {}; + + var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); + var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); + var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); + + if (newOptions.preparationClasses) { + target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses); + delete newOptions.preparationClasses; + } + + // noop is basically when there is no callback; otherwise something has been set + var realDomOperation = target.domOperation !== noop ? target.domOperation : null; + + extend(target, newOptions); + + // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this. + if (realDomOperation) { + target.domOperation = realDomOperation; + } + + if (classes.addClass) { + target.addClass = classes.addClass; + } else { + target.addClass = null; + } + + if (classes.removeClass) { + target.removeClass = classes.removeClass; + } else { + target.removeClass = null; + } + + oldAnimation.addClass = target.addClass; + oldAnimation.removeClass = target.removeClass; + + return target; +} + +function resolveElementClasses(existing, toAdd, toRemove) { + var ADD_CLASS = 1; + var REMOVE_CLASS = -1; + + var flags = {}; + existing = splitClassesToLookup(existing); + + toAdd = splitClassesToLookup(toAdd); + forEach(toAdd, function(value, key) { + flags[key] = ADD_CLASS; + }); + + toRemove = splitClassesToLookup(toRemove); + forEach(toRemove, function(value, key) { + flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS; + }); + + var classes = { + addClass: '', + removeClass: '' + }; + + forEach(flags, function(val, klass) { + var prop, allow; + if (val === ADD_CLASS) { + prop = 'addClass'; + allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX]; + } else if (val === REMOVE_CLASS) { + prop = 'removeClass'; + allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX]; + } + if (allow) { + if (classes[prop].length) { + classes[prop] += ' '; + } + classes[prop] += klass; + } + }); + + function splitClassesToLookup(classes) { + if (isString(classes)) { + classes = classes.split(' '); + } + + var obj = {}; + forEach(classes, function(klass) { + // sometimes the split leaves empty string values + // incase extra spaces were applied to the options + if (klass.length) { + obj[klass] = true; + } + }); + return obj; + } + + return classes; +} + +function getDomNode(element) { + return (element instanceof jqLite) ? element[0] : element; +} + +function applyGeneratedPreparationClasses(element, event, options) { + var classes = ''; + if (event) { + classes = pendClasses(event, EVENT_CLASS_PREFIX, true); + } + if (options.addClass) { + classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX)); + } + if (options.removeClass) { + classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX)); + } + if (classes.length) { + options.preparationClasses = classes; + element.addClass(classes); + } +} + +function clearGeneratedClasses(element, options) { + if (options.preparationClasses) { + element.removeClass(options.preparationClasses); + options.preparationClasses = null; + } + if (options.activeClasses) { + element.removeClass(options.activeClasses); + options.activeClasses = null; + } +} + +function blockTransitions(node, duration) { + // we use a negative delay value since it performs blocking + // yet it doesn't kill any existing transitions running on the + // same element which makes this safe for class-based animations + var value = duration ? '-' + duration + 's' : ''; + applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]); + return [TRANSITION_DELAY_PROP, value]; +} + +function blockKeyframeAnimations(node, applyBlock) { + var value = applyBlock ? 'paused' : ''; + var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; + applyInlineStyle(node, [key, value]); + return [key, value]; +} + +function applyInlineStyle(node, styleTuple) { + var prop = styleTuple[0]; + var value = styleTuple[1]; + node.style[prop] = value; +} + +function concatWithSpace(a,b) { + if (!a) return b; + if (!b) return a; + return a + ' ' + b; +} + +var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { + var queue, cancelFn; + + function scheduler(tasks) { + // we make a copy since RAFScheduler mutates the state + // of the passed in array variable and this would be difficult + // to track down on the outside code + queue = queue.concat(tasks); + nextTick(); + } + + queue = scheduler.queue = []; + + /* waitUntilQuiet does two things: + * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through + * 2. It will delay the next wave of tasks from running until the quiet `fn` has run. + * + * The motivation here is that animation code can request more time from the scheduler + * before the next wave runs. This allows for certain DOM properties such as classes to + * be resolved in time for the next animation to run. + */ + scheduler.waitUntilQuiet = function(fn) { + if (cancelFn) cancelFn(); + + cancelFn = $$rAF(function() { + cancelFn = null; + fn(); + nextTick(); + }); + }; + + return scheduler; + + function nextTick() { + if (!queue.length) return; + + var items = queue.shift(); + for (var i = 0; i < items.length; i++) { + items[i](); + } + + if (!cancelFn) { + $$rAF(function() { + if (!cancelFn) nextTick(); + }); + } + } +}]; + +/** + * @ngdoc directive + * @name ngAnimateChildren + * @restrict AE + * @element ANY + * + * @description + * + * ngAnimateChildren allows you to specify that children of this element should animate even if any + * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` + * (structural) animation, child elements that also have an active structural animation are not animated. + * + * Note that even if `ngAnimateChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). + * + * + * @param {string} ngAnimateChildren If the value is empty, `true` or `on`, + * then child animations are allowed. If the value is `false`, child animations are not allowed. + * + * @example + * + +

+ + + + .container.ng-enter, + .container.ng-leave { + transition: all ease 1.5s; + } + + .container.ng-enter, + .container.ng-leave-active { + opacity: 0; + } + + .container.ng-leave, + .container.ng-enter-active { + opacity: 1; + } + + .item { + background: firebrick; + color: #FFF; + margin-bottom: 10px; + } + + .item.ng-enter, + .item.ng-leave { + transition: transform 1.5s ease; + } + + .item.ng-enter { + transform: translateX(50px); + } + + .item.ng-enter-active { + transform: translateX(0); + } + + + angular.module('ngAnimateChildren', ['ngAnimate']) + .controller('MainController', function MainController() { + this.animateChildren = false; + this.enterElement = false; + }); + + + */ +var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { + return { + link: function(scope, element, attrs) { + var val = attrs.ngAnimateChildren; + if (isString(val) && val.length === 0) { //empty attribute + element.data(NG_ANIMATE_CHILDREN_DATA, true); + } else { + // Interpolate and set the value, so that it is available to + // animations that run right after compilation + setData($interpolate(val)(scope)); + attrs.$observe('ngAnimateChildren', setData); + } + + function setData(value) { + value = value === 'on' || value === 'true'; + element.data(NG_ANIMATE_CHILDREN_DATA, value); + } + } + }; +}]; + +/* exported $AnimateCssProvider */ + +var ANIMATE_TIMER_KEY = '$$animateCss'; + +/** + * @ngdoc service + * @name $animateCss + * @kind object + * + * @description + * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes + * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT + * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or + * directives to create more complex animations that can be purely driven using CSS code. + * + * Note that only browsers that support CSS transitions and/or keyframe animations are capable of + * rendering animations triggered via `$animateCss` (bad news for IE9 and lower). + * + * ## Usage + * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that + * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, + * any automatic control over cancelling animations and/or preventing animations from being run on + * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to + * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger + * the CSS animation. + * + * The example below shows how we can create a folding animation on an element using `ng-if`: + * + * ```html + * + *
+ * This element will go BOOM + *
+ * + * ``` + * + * Now we create the **JavaScript animation** that will trigger the CSS transition: + * + * ```js + * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { + * return { + * enter: function(element, doneFn) { + * var height = element[0].offsetHeight; + * return $animateCss(element, { + * from: { height:'0px' }, + * to: { height:height + 'px' }, + * duration: 1 // one second + * }); + * } + * } + * }]); + * ``` + * + * ## More Advanced Uses + * + * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks + * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code. + * + * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation, + * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with + * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order + * to provide a working animation that will run in CSS. + * + * The example below showcases a more advanced version of the `.fold-animation` from the example above: + * + * ```js + * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { + * return { + * enter: function(element, doneFn) { + * var height = element[0].offsetHeight; + * return $animateCss(element, { + * addClass: 'red large-text pulse-twice', + * easing: 'ease-out', + * from: { height:'0px' }, + * to: { height:height + 'px' }, + * duration: 1 // one second + * }); + * } + * } + * }]); + * ``` + * + * Since we're adding/removing CSS classes then the CSS transition will also pick those up: + * + * ```css + * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code, + * the CSS classes below will be transitioned despite them being defined as regular CSS classes */ + * .red { background:red; } + * .large-text { font-size:20px; } + * + * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */ + * .pulse-twice { + * animation: 0.5s pulse linear 2; + * -webkit-animation: 0.5s pulse linear 2; + * } + * + * @keyframes pulse { + * from { transform: scale(0.5); } + * to { transform: scale(1.5); } + * } + * + * @-webkit-keyframes pulse { + * from { -webkit-transform: scale(0.5); } + * to { -webkit-transform: scale(1.5); } + * } + * ``` + * + * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen. + * + * ## How the Options are handled + * + * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation + * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline + * styles using the `from` and `to` properties. + * + * ```js + * var animator = $animateCss(element, { + * from: { background:'red' }, + * to: { background:'blue' } + * }); + * animator.start(); + * ``` + * + * ```css + * .rotating-animation { + * animation:0.5s rotate linear; + * -webkit-animation:0.5s rotate linear; + * } + * + * @keyframes rotate { + * from { transform: rotate(0deg); } + * to { transform: rotate(360deg); } + * } + * + * @-webkit-keyframes rotate { + * from { -webkit-transform: rotate(0deg); } + * to { -webkit-transform: rotate(360deg); } + * } + * ``` + * + * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is + * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition + * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition + * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied + * and spread across the transition and keyframe animation. + * + * ## What is returned + * + * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually + * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are + * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties: + * + * ```js + * var animator = $animateCss(element, { ... }); + * ``` + * + * Now what do the contents of our `animator` variable look like: + * + * ```js + * { + * // starts the animation + * start: Function, + * + * // ends (aborts) the animation + * end: Function + * } + * ``` + * + * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends. + * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been + * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties + * and that changing them will not reconfigure the parameters of the animation. + * + * ### runner.done() vs runner.then() + * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the + * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**. + * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()` + * unless you really need a digest to kick off afterwards. + * + * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss + * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). + * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works. + * + * @param {DOMElement} element the element that will be animated + * @param {object} options the animation-related options that will be applied during the animation + * + * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied + * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.) + * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and + * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted. + * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both). + * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`). + * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`). + * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation. + * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition. + * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation. + * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation. + * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0` + * is provided then the animation will be skipped entirely. + * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is + * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value + * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same + * CSS delay value. + * * `stagger` - A numeric time value representing the delay between successively animated elements + * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.}) + * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a + * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`) + * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.) + * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once + * the animation is closed. This is useful for when the styles are used purely for the sake of + * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation). + * By default this value is set to `false`. + * + * @return {object} an object with start and end methods and details about the animation. + * + * * `start` - The method to start the animation. This will return a `Promise` when called. + * * `end` - This method will cancel the animation and remove all applied CSS classes and styles. + */ +var ONE_SECOND = 1000; + +var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; +var CLOSING_TIME_BUFFER = 1.5; + +var DETECT_CSS_PROPERTIES = { + transitionDuration: TRANSITION_DURATION_PROP, + transitionDelay: TRANSITION_DELAY_PROP, + transitionProperty: TRANSITION_PROP + PROPERTY_KEY, + animationDuration: ANIMATION_DURATION_PROP, + animationDelay: ANIMATION_DELAY_PROP, + animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY +}; + +var DETECT_STAGGER_CSS_PROPERTIES = { + transitionDuration: TRANSITION_DURATION_PROP, + transitionDelay: TRANSITION_DELAY_PROP, + animationDuration: ANIMATION_DURATION_PROP, + animationDelay: ANIMATION_DELAY_PROP +}; + +function getCssKeyframeDurationStyle(duration) { + return [ANIMATION_DURATION_PROP, duration + 's']; +} + +function getCssDelayStyle(delay, isKeyframeAnimation) { + var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; + return [prop, delay + 's']; +} + +function computeCssStyles($window, element, properties) { + var styles = Object.create(null); + var detectedStyles = $window.getComputedStyle(element) || {}; + forEach(properties, function(formalStyleName, actualStyleName) { + var val = detectedStyles[formalStyleName]; + if (val) { + var c = val.charAt(0); + + // only numerical-based values have a negative sign or digit as the first value + if (c === '-' || c === '+' || c >= 0) { + val = parseMaxTime(val); + } + + // by setting this to null in the event that the delay is not set or is set directly as 0 + // then we can still allow for negative values to be used later on and not mistake this + // value for being greater than any other negative value. + if (val === 0) { + val = null; + } + styles[actualStyleName] = val; + } + }); + + return styles; +} + +function parseMaxTime(str) { + var maxValue = 0; + var values = str.split(/\s*,\s*/); + forEach(values, function(value) { + // it's always safe to consider only second values and omit `ms` values since + // getComputedStyle will always handle the conversion for us + if (value.charAt(value.length - 1) === 's') { + value = value.substring(0, value.length - 1); + } + value = parseFloat(value) || 0; + maxValue = maxValue ? Math.max(value, maxValue) : value; + }); + return maxValue; +} + +function truthyTimingValue(val) { + return val === 0 || val != null; +} + +function getCssTransitionDurationStyle(duration, applyOnlyDuration) { + var style = TRANSITION_PROP; + var value = duration + 's'; + if (applyOnlyDuration) { + style += DURATION_KEY; + } else { + value += ' linear all'; + } + return [style, value]; +} + +function createLocalCacheLookup() { + var cache = Object.create(null); + return { + flush: function() { + cache = Object.create(null); + }, + + count: function(key) { + var entry = cache[key]; + return entry ? entry.total : 0; + }, + + get: function(key) { + var entry = cache[key]; + return entry && entry.value; + }, + + put: function(key, value) { + if (!cache[key]) { + cache[key] = { total: 1, value: value }; + } else { + cache[key].total++; + } + } + }; +} + +// we do not reassign an already present style value since +// if we detect the style property value again we may be +// detecting styles that were added via the `from` styles. +// We make use of `isDefined` here since an empty string +// or null value (which is what getPropertyValue will return +// for a non-existing style) will still be marked as a valid +// value for the style (a falsy value implies that the style +// is to be removed at the end of the animation). If we had a simple +// "OR" statement then it would not be enough to catch that. +function registerRestorableStyles(backup, node, properties) { + forEach(properties, function(prop) { + backup[prop] = isDefined(backup[prop]) + ? backup[prop] + : node.style.getPropertyValue(prop); + }); +} + +var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) { + var gcsLookup = createLocalCacheLookup(); + var gcsStaggerLookup = createLocalCacheLookup(); + + this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', + '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', + function($window, $$jqLite, $$AnimateRunner, $timeout, + $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { + + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + + var parentCounter = 0; + function gcsHashFn(node, extraClasses) { + var KEY = '$$ngAnimateParentKey'; + var parentNode = node.parentNode; + var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); + return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; + } + + function computeCachedCssStyles(node, className, cacheKey, properties) { + var timings = gcsLookup.get(cacheKey); + + if (!timings) { + timings = computeCssStyles($window, node, properties); + if (timings.animationIterationCount === 'infinite') { + timings.animationIterationCount = 1; + } + } + + // we keep putting this in multiple times even though the value and the cacheKey are the same + // because we're keeping an internal tally of how many duplicate animations are detected. + gcsLookup.put(cacheKey, timings); + return timings; + } + + function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { + var stagger; + + // if we have one or more existing matches of matching elements + // containing the same parent + CSS styles (which is how cacheKey works) + // then staggering is possible + if (gcsLookup.count(cacheKey) > 0) { + stagger = gcsStaggerLookup.get(cacheKey); + + if (!stagger) { + var staggerClassName = pendClasses(className, '-stagger'); + + $$jqLite.addClass(node, staggerClassName); + + stagger = computeCssStyles($window, node, properties); + + // force the conversion of a null value to zero incase not set + stagger.animationDuration = Math.max(stagger.animationDuration, 0); + stagger.transitionDuration = Math.max(stagger.transitionDuration, 0); + + $$jqLite.removeClass(node, staggerClassName); + + gcsStaggerLookup.put(cacheKey, stagger); + } + } + + return stagger || {}; + } + + var rafWaitQueue = []; + function waitUntilQuiet(callback) { + rafWaitQueue.push(callback); + $$rAFScheduler.waitUntilQuiet(function() { + gcsLookup.flush(); + gcsStaggerLookup.flush(); + + // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. + // PLEASE EXAMINE THE `$$forceReflow` service to understand why. + var pageWidth = $$forceReflow(); + + // we use a for loop to ensure that if the queue is changed + // during this looping then it will consider new requests + for (var i = 0; i < rafWaitQueue.length; i++) { + rafWaitQueue[i](pageWidth); + } + rafWaitQueue.length = 0; + }); + } + + function computeTimings(node, className, cacheKey) { + var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); + var aD = timings.animationDelay; + var tD = timings.transitionDelay; + timings.maxDelay = aD && tD + ? Math.max(aD, tD) + : (aD || tD); + timings.maxDuration = Math.max( + timings.animationDuration * timings.animationIterationCount, + timings.transitionDuration); + + return timings; + } + + return function init(element, initialOptions) { + // all of the animation functions should create + // a copy of the options data, however, if a + // parent service has already created a copy then + // we should stick to using that + var options = initialOptions || {}; + if (!options.$$prepared) { + options = prepareAnimationOptions(copy(options)); + } + + var restoreStyles = {}; + var node = getDomNode(element); + if (!node + || !node.parentNode + || !$$animateQueue.enabled()) { + return closeAndReturnNoopAnimator(); + } + + var temporaryStyles = []; + var classes = element.attr('class'); + var styles = packageStyles(options); + var animationClosed; + var animationPaused; + var animationCompleted; + var runner; + var runnerHost; + var maxDelay; + var maxDelayTime; + var maxDuration; + var maxDurationTime; + var startTime; + var events = []; + + if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) { + return closeAndReturnNoopAnimator(); + } + + var method = options.event && isArray(options.event) + ? options.event.join(' ') + : options.event; + + var isStructural = method && options.structural; + var structuralClassName = ''; + var addRemoveClassName = ''; + + if (isStructural) { + structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true); + } else if (method) { + structuralClassName = method; + } + + if (options.addClass) { + addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX); + } + + if (options.removeClass) { + if (addRemoveClassName.length) { + addRemoveClassName += ' '; + } + addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX); + } + + // there may be a situation where a structural animation is combined together + // with CSS classes that need to resolve before the animation is computed. + // However this means that there is no explicit CSS code to block the animation + // from happening (by setting 0s none in the class name). If this is the case + // we need to apply the classes before the first rAF so we know to continue if + // there actually is a detected transition or keyframe animation + if (options.applyClassesEarly && addRemoveClassName.length) { + applyAnimationClasses(element, options); + } + + var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); + var fullClassName = classes + ' ' + preparationClasses; + var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); + var hasToStyles = styles.to && Object.keys(styles.to).length > 0; + var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; + + // there is no way we can trigger an animation if no styles and + // no classes are being applied which would then trigger a transition, + // unless there a is raw keyframe value that is applied to the element. + if (!containsKeyframeAnimation + && !hasToStyles + && !preparationClasses) { + return closeAndReturnNoopAnimator(); + } + + var cacheKey, stagger; + if (options.stagger > 0) { + var staggerVal = parseFloat(options.stagger); + stagger = { + transitionDelay: staggerVal, + animationDelay: staggerVal, + transitionDuration: 0, + animationDuration: 0 + }; + } else { + cacheKey = gcsHashFn(node, fullClassName); + stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); + } + + if (!options.$$skipPreparationClasses) { + $$jqLite.addClass(element, preparationClasses); + } + + var applyOnlyDuration; + + if (options.transitionStyle) { + var transitionStyle = [TRANSITION_PROP, options.transitionStyle]; + applyInlineStyle(node, transitionStyle); + temporaryStyles.push(transitionStyle); + } + + if (options.duration >= 0) { + applyOnlyDuration = node.style[TRANSITION_PROP].length > 0; + var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration); + + // we set the duration so that it will be picked up by getComputedStyle later + applyInlineStyle(node, durationStyle); + temporaryStyles.push(durationStyle); + } + + if (options.keyframeStyle) { + var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle]; + applyInlineStyle(node, keyframeStyle); + temporaryStyles.push(keyframeStyle); + } + + var itemIndex = stagger + ? options.staggerIndex >= 0 + ? options.staggerIndex + : gcsLookup.count(cacheKey) + : 0; + + var isFirst = itemIndex === 0; + + // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY + // without causing any combination of transitions to kick in. By adding a negative delay value + // it forces the setup class' transition to end immediately. We later then remove the negative + // transition delay to allow for the transition to naturally do it's thing. The beauty here is + // that if there is no transition defined then nothing will happen and this will also allow + // other transitions to be stacked on top of each other without any chopping them out. + if (isFirst && !options.skipBlocking) { + blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); + } + + var timings = computeTimings(node, fullClassName, cacheKey); + var relativeDelay = timings.maxDelay; + maxDelay = Math.max(relativeDelay, 0); + maxDuration = timings.maxDuration; + + var flags = {}; + flags.hasTransitions = timings.transitionDuration > 0; + flags.hasAnimations = timings.animationDuration > 0; + flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty === 'all'; + flags.applyTransitionDuration = hasToStyles && ( + (flags.hasTransitions && !flags.hasTransitionAll) + || (flags.hasAnimations && !flags.hasTransitions)); + flags.applyAnimationDuration = options.duration && flags.hasAnimations; + flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions); + flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations; + flags.recalculateTimingStyles = addRemoveClassName.length > 0; + + if (flags.applyTransitionDuration || flags.applyAnimationDuration) { + maxDuration = options.duration ? parseFloat(options.duration) : maxDuration; + + if (flags.applyTransitionDuration) { + flags.hasTransitions = true; + timings.transitionDuration = maxDuration; + applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0; + temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration)); + } + + if (flags.applyAnimationDuration) { + flags.hasAnimations = true; + timings.animationDuration = maxDuration; + temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)); + } + } + + if (maxDuration === 0 && !flags.recalculateTimingStyles) { + return closeAndReturnNoopAnimator(); + } + + if (options.delay != null) { + var delayStyle; + if (typeof options.delay !== 'boolean') { + delayStyle = parseFloat(options.delay); + // number in options.delay means we have to recalculate the delay for the closing timeout + maxDelay = Math.max(delayStyle, 0); + } + + if (flags.applyTransitionDelay) { + temporaryStyles.push(getCssDelayStyle(delayStyle)); + } + + if (flags.applyAnimationDelay) { + temporaryStyles.push(getCssDelayStyle(delayStyle, true)); + } + } + + // we need to recalculate the delay value since we used a pre-emptive negative + // delay value and the delay value is required for the final event checking. This + // property will ensure that this will happen after the RAF phase has passed. + if (options.duration == null && timings.transitionDuration > 0) { + flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst; + } + + maxDelayTime = maxDelay * ONE_SECOND; + maxDurationTime = maxDuration * ONE_SECOND; + if (!options.skipBlocking) { + flags.blockTransition = timings.transitionDuration > 0; + flags.blockKeyframeAnimation = timings.animationDuration > 0 && + stagger.animationDelay > 0 && + stagger.animationDuration === 0; + } + + if (options.from) { + if (options.cleanupStyles) { + registerRestorableStyles(restoreStyles, node, Object.keys(options.from)); + } + applyAnimationFromStyles(element, options); + } + + if (flags.blockTransition || flags.blockKeyframeAnimation) { + applyBlocking(maxDuration); + } else if (!options.skipBlocking) { + blockTransitions(node, false); + } + + // TODO(matsko): for 1.5 change this code to have an animator object for better debugging + return { + $$willAnimate: true, + end: endFn, + start: function() { + if (animationClosed) return; + + runnerHost = { + end: endFn, + cancel: cancelFn, + resume: null, //this will be set during the start() phase + pause: null + }; + + runner = new $$AnimateRunner(runnerHost); + + waitUntilQuiet(start); + + // we don't have access to pause/resume the animation + // since it hasn't run yet. AnimateRunner will therefore + // set noop functions for resume and pause and they will + // later be overridden once the animation is triggered + return runner; + } + }; + + function endFn() { + close(); + } + + function cancelFn() { + close(true); + } + + function close(rejected) { + // if the promise has been called already then we shouldn't close + // the animation again + if (animationClosed || (animationCompleted && animationPaused)) return; + animationClosed = true; + animationPaused = false; + + if (!options.$$skipPreparationClasses) { + $$jqLite.removeClass(element, preparationClasses); + } + $$jqLite.removeClass(element, activeClasses); + + blockKeyframeAnimations(node, false); + blockTransitions(node, false); + + forEach(temporaryStyles, function(entry) { + // There is only one way to remove inline style properties entirely from elements. + // By using `removeProperty` this works, but we need to convert camel-cased CSS + // styles down to hyphenated values. + node.style[entry[0]] = ''; + }); + + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + + if (Object.keys(restoreStyles).length) { + forEach(restoreStyles, function(value, prop) { + if (value) { + node.style.setProperty(prop, value); + } else { + node.style.removeProperty(prop); + } + }); + } + + // the reason why we have this option is to allow a synchronous closing callback + // that is fired as SOON as the animation ends (when the CSS is removed) or if + // the animation never takes off at all. A good example is a leave animation since + // the element must be removed just after the animation is over or else the element + // will appear on screen for one animation frame causing an overbearing flicker. + if (options.onDone) { + options.onDone(); + } + + if (events && events.length) { + // Remove the transitionend / animationend listener(s) + element.off(events.join(' '), onAnimationProgress); + } + + //Cancel the fallback closing timeout and remove the timer data + var animationTimerData = element.data(ANIMATE_TIMER_KEY); + if (animationTimerData) { + $timeout.cancel(animationTimerData[0].timer); + element.removeData(ANIMATE_TIMER_KEY); + } + + // if the preparation function fails then the promise is not setup + if (runner) { + runner.complete(!rejected); + } + } + + function applyBlocking(duration) { + if (flags.blockTransition) { + blockTransitions(node, duration); + } + + if (flags.blockKeyframeAnimation) { + blockKeyframeAnimations(node, !!duration); + } + } + + function closeAndReturnNoopAnimator() { + runner = new $$AnimateRunner({ + end: endFn, + cancel: cancelFn + }); + + // should flush the cache animation + waitUntilQuiet(noop); + close(); + + return { + $$willAnimate: false, + start: function() { + return runner; + }, + end: endFn + }; + } + + function onAnimationProgress(event) { + event.stopPropagation(); + var ev = event.originalEvent || event; + + // we now always use `Date.now()` due to the recent changes with + // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) + var timeStamp = ev.$manualTimeStamp || Date.now(); + + /* Firefox (or possibly just Gecko) likes to not round values up + * when a ms measurement is used for the animation */ + var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); + + /* $manualTimeStamp is a mocked timeStamp value which is set + * within browserTrigger(). This is only here so that tests can + * mock animations properly. Real events fallback to event.timeStamp, + * or, if they don't, then a timeStamp is automatically created for them. + * We're checking to see if the timeStamp surpasses the expected delay, + * but we're using elapsedTime instead of the timeStamp on the 2nd + * pre-condition since animationPauseds sometimes close off early */ + if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { + // we set this flag to ensure that if the transition is paused then, when resumed, + // the animation will automatically close itself since transitions cannot be paused. + animationCompleted = true; + close(); + } + } + + function start() { + if (animationClosed) return; + if (!node.parentNode) { + close(); + return; + } + + // even though we only pause keyframe animations here the pause flag + // will still happen when transitions are used. Only the transition will + // not be paused since that is not possible. If the animation ends when + // paused then it will not complete until unpaused or cancelled. + var playPause = function(playAnimation) { + if (!animationCompleted) { + animationPaused = !playAnimation; + if (timings.animationDuration) { + var value = blockKeyframeAnimations(node, animationPaused); + if (animationPaused) { + temporaryStyles.push(value); + } else { + removeFromArray(temporaryStyles, value); + } + } + } else if (animationPaused && playAnimation) { + animationPaused = false; + close(); + } + }; + + // checking the stagger duration prevents an accidentally cascade of the CSS delay style + // being inherited from the parent. If the transition duration is zero then we can safely + // rely that the delay value is an intentional stagger delay style. + var maxStagger = itemIndex > 0 + && ((timings.transitionDuration && stagger.transitionDuration === 0) || + (timings.animationDuration && stagger.animationDuration === 0)) + && Math.max(stagger.animationDelay, stagger.transitionDelay); + if (maxStagger) { + $timeout(triggerAnimationStart, + Math.floor(maxStagger * itemIndex * ONE_SECOND), + false); + } else { + triggerAnimationStart(); + } + + // this will decorate the existing promise runner with pause/resume methods + runnerHost.resume = function() { + playPause(true); + }; + + runnerHost.pause = function() { + playPause(false); + }; + + function triggerAnimationStart() { + // just incase a stagger animation kicks in when the animation + // itself was cancelled entirely + if (animationClosed) return; + + applyBlocking(false); + + forEach(temporaryStyles, function(entry) { + var key = entry[0]; + var value = entry[1]; + node.style[key] = value; + }); + + applyAnimationClasses(element, options); + $$jqLite.addClass(element, activeClasses); + + if (flags.recalculateTimingStyles) { + fullClassName = node.getAttribute('class') + ' ' + preparationClasses; + cacheKey = gcsHashFn(node, fullClassName); + + timings = computeTimings(node, fullClassName, cacheKey); + relativeDelay = timings.maxDelay; + maxDelay = Math.max(relativeDelay, 0); + maxDuration = timings.maxDuration; + + if (maxDuration === 0) { + close(); + return; + } + + flags.hasTransitions = timings.transitionDuration > 0; + flags.hasAnimations = timings.animationDuration > 0; + } + + if (flags.applyAnimationDelay) { + relativeDelay = typeof options.delay !== 'boolean' && truthyTimingValue(options.delay) + ? parseFloat(options.delay) + : relativeDelay; + + maxDelay = Math.max(relativeDelay, 0); + timings.animationDelay = relativeDelay; + delayStyle = getCssDelayStyle(relativeDelay, true); + temporaryStyles.push(delayStyle); + node.style[delayStyle[0]] = delayStyle[1]; + } + + maxDelayTime = maxDelay * ONE_SECOND; + maxDurationTime = maxDuration * ONE_SECOND; + + if (options.easing) { + var easeProp, easeVal = options.easing; + if (flags.hasTransitions) { + easeProp = TRANSITION_PROP + TIMING_KEY; + temporaryStyles.push([easeProp, easeVal]); + node.style[easeProp] = easeVal; + } + if (flags.hasAnimations) { + easeProp = ANIMATION_PROP + TIMING_KEY; + temporaryStyles.push([easeProp, easeVal]); + node.style[easeProp] = easeVal; + } + } + + if (timings.transitionDuration) { + events.push(TRANSITIONEND_EVENT); + } + + if (timings.animationDuration) { + events.push(ANIMATIONEND_EVENT); + } + + startTime = Date.now(); + var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime; + var endTime = startTime + timerTime; + + var animationsData = element.data(ANIMATE_TIMER_KEY) || []; + var setupFallbackTimer = true; + if (animationsData.length) { + var currentTimerData = animationsData[0]; + setupFallbackTimer = endTime > currentTimerData.expectedEndTime; + if (setupFallbackTimer) { + $timeout.cancel(currentTimerData.timer); + } else { + animationsData.push(close); + } + } + + if (setupFallbackTimer) { + var timer = $timeout(onAnimationExpired, timerTime, false); + animationsData[0] = { + timer: timer, + expectedEndTime: endTime + }; + animationsData.push(close); + element.data(ANIMATE_TIMER_KEY, animationsData); + } + + if (events.length) { + element.on(events.join(' '), onAnimationProgress); + } + + if (options.to) { + if (options.cleanupStyles) { + registerRestorableStyles(restoreStyles, node, Object.keys(options.to)); + } + applyAnimationToStyles(element, options); + } + } + + function onAnimationExpired() { + var animationsData = element.data(ANIMATE_TIMER_KEY); + + // this will be false in the event that the element was + // removed from the DOM (via a leave animation or something + // similar) + if (animationsData) { + for (var i = 1; i < animationsData.length; i++) { + animationsData[i](); + } + element.removeData(ANIMATE_TIMER_KEY); + } + } + } + }; + }]; +}]; + +var $$AnimateCssDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { + $$animationProvider.drivers.push('$$animateCssDriver'); + + var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; + var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor'; + + var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out'; + var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in'; + + function isDocumentFragment(node) { + return node.parentNode && node.parentNode.nodeType === 11; + } + + this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document', + function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) { + + // only browsers that support these properties can render animations + if (!$sniffer.animations && !$sniffer.transitions) return noop; + + var bodyNode = $document[0].body; + var rootNode = getDomNode($rootElement); + + var rootBodyElement = jqLite( + // this is to avoid using something that exists outside of the body + // we also special case the doc fragment case because our unit test code + // appends the $rootElement to the body after the app has been bootstrapped + isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode + ); + + return function initDriverFn(animationDetails) { + return animationDetails.from && animationDetails.to + ? prepareFromToAnchorAnimation(animationDetails.from, + animationDetails.to, + animationDetails.classes, + animationDetails.anchors) + : prepareRegularAnimation(animationDetails); + }; + + function filterCssClasses(classes) { + //remove all the `ng-` stuff + return classes.replace(/\bng-\S+\b/g, ''); + } + + function getUniqueValues(a, b) { + if (isString(a)) a = a.split(' '); + if (isString(b)) b = b.split(' '); + return a.filter(function(val) { + return b.indexOf(val) === -1; + }).join(' '); + } + + function prepareAnchoredAnimation(classes, outAnchor, inAnchor) { + var clone = jqLite(getDomNode(outAnchor).cloneNode(true)); + var startingClasses = filterCssClasses(getClassVal(clone)); + + outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); + inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); + + clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME); + + rootBodyElement.append(clone); + + var animatorIn, animatorOut = prepareOutAnimation(); + + // the user may not end up using the `out` animation and + // only making use of the `in` animation or vice-versa. + // In either case we should allow this and not assume the + // animation is over unless both animations are not used. + if (!animatorOut) { + animatorIn = prepareInAnimation(); + if (!animatorIn) { + return end(); + } + } + + var startingAnimator = animatorOut || animatorIn; + + return { + start: function() { + var runner; + + var currentAnimation = startingAnimator.start(); + currentAnimation.done(function() { + currentAnimation = null; + if (!animatorIn) { + animatorIn = prepareInAnimation(); + if (animatorIn) { + currentAnimation = animatorIn.start(); + currentAnimation.done(function() { + currentAnimation = null; + end(); + runner.complete(); + }); + return currentAnimation; + } + } + // in the event that there is no `in` animation + end(); + runner.complete(); + }); + + runner = new $$AnimateRunner({ + end: endFn, + cancel: endFn + }); + + return runner; + + function endFn() { + if (currentAnimation) { + currentAnimation.end(); + } + } + } + }; + + function calculateAnchorStyles(anchor) { + var styles = {}; + + var coords = getDomNode(anchor).getBoundingClientRect(); + + // we iterate directly since safari messes up and doesn't return + // all the keys for the coords object when iterated + forEach(['width','height','top','left'], function(key) { + var value = coords[key]; + switch (key) { + case 'top': + value += bodyNode.scrollTop; + break; + case 'left': + value += bodyNode.scrollLeft; + break; + } + styles[key] = Math.floor(value) + 'px'; + }); + return styles; + } + + function prepareOutAnimation() { + var animator = $animateCss(clone, { + addClass: NG_OUT_ANCHOR_CLASS_NAME, + delay: true, + from: calculateAnchorStyles(outAnchor) + }); + + // read the comment within `prepareRegularAnimation` to understand + // why this check is necessary + return animator.$$willAnimate ? animator : null; + } + + function getClassVal(element) { + return element.attr('class') || ''; + } + + function prepareInAnimation() { + var endingClasses = filterCssClasses(getClassVal(inAnchor)); + var toAdd = getUniqueValues(endingClasses, startingClasses); + var toRemove = getUniqueValues(startingClasses, endingClasses); + + var animator = $animateCss(clone, { + to: calculateAnchorStyles(inAnchor), + addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd, + removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove, + delay: true + }); + + // read the comment within `prepareRegularAnimation` to understand + // why this check is necessary + return animator.$$willAnimate ? animator : null; + } + + function end() { + clone.remove(); + outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); + inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); + } + } + + function prepareFromToAnchorAnimation(from, to, classes, anchors) { + var fromAnimation = prepareRegularAnimation(from, noop); + var toAnimation = prepareRegularAnimation(to, noop); + + var anchorAnimations = []; + forEach(anchors, function(anchor) { + var outElement = anchor['out']; + var inElement = anchor['in']; + var animator = prepareAnchoredAnimation(classes, outElement, inElement); + if (animator) { + anchorAnimations.push(animator); + } + }); + + // no point in doing anything when there are no elements to animate + if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return; + + return { + start: function() { + var animationRunners = []; + + if (fromAnimation) { + animationRunners.push(fromAnimation.start()); + } + + if (toAnimation) { + animationRunners.push(toAnimation.start()); + } + + forEach(anchorAnimations, function(animation) { + animationRunners.push(animation.start()); + }); + + var runner = new $$AnimateRunner({ + end: endFn, + cancel: endFn // CSS-driven animations cannot be cancelled, only ended + }); + + $$AnimateRunner.all(animationRunners, function(status) { + runner.complete(status); + }); + + return runner; + + function endFn() { + forEach(animationRunners, function(runner) { + runner.end(); + }); + } + } + }; + } + + function prepareRegularAnimation(animationDetails) { + var element = animationDetails.element; + var options = animationDetails.options || {}; + + if (animationDetails.structural) { + options.event = animationDetails.event; + options.structural = true; + options.applyClassesEarly = true; + + // we special case the leave animation since we want to ensure that + // the element is removed as soon as the animation is over. Otherwise + // a flicker might appear or the element may not be removed at all + if (animationDetails.event === 'leave') { + options.onDone = options.domOperation; + } + } + + // We assign the preparationClasses as the actual animation event since + // the internals of $animateCss will just suffix the event token values + // with `-active` to trigger the animation. + if (options.preparationClasses) { + options.event = concatWithSpace(options.event, options.preparationClasses); + } + + var animator = $animateCss(element, options); + + // the driver lookup code inside of $$animation attempts to spawn a + // driver one by one until a driver returns a.$$willAnimate animator object. + // $animateCss will always return an object, however, it will pass in + // a flag as a hint as to whether an animation was detected or not + return animator.$$willAnimate ? animator : null; + } + }]; +}]; + +// TODO(matsko): use caching here to speed things up for detection +// TODO(matsko): add documentation +// by the time... + +var $$AnimateJsProvider = ['$animateProvider', /** @this */ function($animateProvider) { + this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', + function($injector, $$AnimateRunner, $$jqLite) { + + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + // $animateJs(element, 'enter'); + return function(element, event, classes, options) { + var animationClosed = false; + + // the `classes` argument is optional and if it is not used + // then the classes will be resolved from the element's className + // property as well as options.addClass/options.removeClass. + if (arguments.length === 3 && isObject(classes)) { + options = classes; + classes = null; + } + + options = prepareAnimationOptions(options); + if (!classes) { + classes = element.attr('class') || ''; + if (options.addClass) { + classes += ' ' + options.addClass; + } + if (options.removeClass) { + classes += ' ' + options.removeClass; + } + } + + var classesToAdd = options.addClass; + var classesToRemove = options.removeClass; + + // the lookupAnimations function returns a series of animation objects that are + // matched up with one or more of the CSS classes. These animation objects are + // defined via the module.animation factory function. If nothing is detected then + // we don't return anything which then makes $animation query the next driver. + var animations = lookupAnimations(classes); + var before, after; + if (animations.length) { + var afterFn, beforeFn; + if (event === 'leave') { + beforeFn = 'leave'; + afterFn = 'afterLeave'; // TODO(matsko): get rid of this + } else { + beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1); + afterFn = event; + } + + if (event !== 'enter' && event !== 'move') { + before = packageAnimations(element, event, options, animations, beforeFn); + } + after = packageAnimations(element, event, options, animations, afterFn); + } + + // no matching animations + if (!before && !after) return; + + function applyOptions() { + options.domOperation(); + applyAnimationClasses(element, options); + } + + function close() { + animationClosed = true; + applyOptions(); + applyAnimationStyles(element, options); + } + + var runner; + + return { + $$willAnimate: true, + end: function() { + if (runner) { + runner.end(); + } else { + close(); + runner = new $$AnimateRunner(); + runner.complete(true); + } + return runner; + }, + start: function() { + if (runner) { + return runner; + } + + runner = new $$AnimateRunner(); + var closeActiveAnimations; + var chain = []; + + if (before) { + chain.push(function(fn) { + closeActiveAnimations = before(fn); + }); + } + + if (chain.length) { + chain.push(function(fn) { + applyOptions(); + fn(true); + }); + } else { + applyOptions(); + } + + if (after) { + chain.push(function(fn) { + closeActiveAnimations = after(fn); + }); + } + + runner.setHost({ + end: function() { + endAnimations(); + }, + cancel: function() { + endAnimations(true); + } + }); + + $$AnimateRunner.chain(chain, onComplete); + return runner; + + function onComplete(success) { + close(success); + runner.complete(success); + } + + function endAnimations(cancelled) { + if (!animationClosed) { + (closeActiveAnimations || noop)(cancelled); + onComplete(cancelled); + } + } + } + }; + + function executeAnimationFn(fn, element, event, options, onDone) { + var args; + switch (event) { + case 'animate': + args = [element, options.from, options.to, onDone]; + break; + + case 'setClass': + args = [element, classesToAdd, classesToRemove, onDone]; + break; + + case 'addClass': + args = [element, classesToAdd, onDone]; + break; + + case 'removeClass': + args = [element, classesToRemove, onDone]; + break; + + default: + args = [element, onDone]; + break; + } + + args.push(options); + + var value = fn.apply(fn, args); + if (value) { + if (isFunction(value.start)) { + value = value.start(); + } + + if (value instanceof $$AnimateRunner) { + value.done(onDone); + } else if (isFunction(value)) { + // optional onEnd / onCancel callback + return value; + } + } + + return noop; + } + + function groupEventedAnimations(element, event, options, animations, fnName) { + var operations = []; + forEach(animations, function(ani) { + var animation = ani[fnName]; + if (!animation) return; + + // note that all of these animations will run in parallel + operations.push(function() { + var runner; + var endProgressCb; + + var resolved = false; + var onAnimationComplete = function(rejected) { + if (!resolved) { + resolved = true; + (endProgressCb || noop)(rejected); + runner.complete(!rejected); + } + }; + + runner = new $$AnimateRunner({ + end: function() { + onAnimationComplete(); + }, + cancel: function() { + onAnimationComplete(true); + } + }); + + endProgressCb = executeAnimationFn(animation, element, event, options, function(result) { + var cancelled = result === false; + onAnimationComplete(cancelled); + }); + + return runner; + }); + }); + + return operations; + } + + function packageAnimations(element, event, options, animations, fnName) { + var operations = groupEventedAnimations(element, event, options, animations, fnName); + if (operations.length === 0) { + var a, b; + if (fnName === 'beforeSetClass') { + a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); + b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); + } else if (fnName === 'setClass') { + a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass'); + b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass'); + } + + if (a) { + operations = operations.concat(a); + } + if (b) { + operations = operations.concat(b); + } + } + + if (operations.length === 0) return; + + // TODO(matsko): add documentation + return function startAnimation(callback) { + var runners = []; + if (operations.length) { + forEach(operations, function(animateFn) { + runners.push(animateFn()); + }); + } + + if (runners.length) { + $$AnimateRunner.all(runners, callback); + } else { + callback(); + } + + return function endFn(reject) { + forEach(runners, function(runner) { + if (reject) { + runner.cancel(); + } else { + runner.end(); + } + }); + }; + }; + } + }; + + function lookupAnimations(classes) { + classes = isArray(classes) ? classes : classes.split(' '); + var matches = [], flagMap = {}; + for (var i = 0; i < classes.length; i++) { + var klass = classes[i], + animationFactory = $animateProvider.$$registeredAnimations[klass]; + if (animationFactory && !flagMap[klass]) { + matches.push($injector.get(animationFactory)); + flagMap[klass] = true; + } + } + return matches; + } + }]; +}]; + +var $$AnimateJsDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { + $$animationProvider.drivers.push('$$animateJsDriver'); + this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { + return function initDriverFn(animationDetails) { + if (animationDetails.from && animationDetails.to) { + var fromAnimation = prepareAnimation(animationDetails.from); + var toAnimation = prepareAnimation(animationDetails.to); + if (!fromAnimation && !toAnimation) return; + + return { + start: function() { + var animationRunners = []; + + if (fromAnimation) { + animationRunners.push(fromAnimation.start()); + } + + if (toAnimation) { + animationRunners.push(toAnimation.start()); + } + + $$AnimateRunner.all(animationRunners, done); + + var runner = new $$AnimateRunner({ + end: endFnFactory(), + cancel: endFnFactory() + }); + + return runner; + + function endFnFactory() { + return function() { + forEach(animationRunners, function(runner) { + // at this point we cannot cancel animations for groups just yet. 1.5+ + runner.end(); + }); + }; + } + + function done(status) { + runner.complete(status); + } + } + }; + } else { + return prepareAnimation(animationDetails); + } + }; + + function prepareAnimation(animationDetails) { + // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations + var element = animationDetails.element; + var event = animationDetails.event; + var options = animationDetails.options; + var classes = animationDetails.classes; + return $$animateJs(element, event, classes, options); + } + }]; +}]; + +var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; +var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; +var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animateProvider) { + var PRE_DIGEST_STATE = 1; + var RUNNING_STATE = 2; + var ONE_SPACE = ' '; + + var rules = this.rules = { + skip: [], + cancel: [], + join: [] + }; + + function makeTruthyCssClassMap(classString) { + if (!classString) { + return null; + } + + var keys = classString.split(ONE_SPACE); + var map = Object.create(null); + + forEach(keys, function(key) { + map[key] = true; + }); + return map; + } + + function hasMatchingClasses(newClassString, currentClassString) { + if (newClassString && currentClassString) { + var currentClassMap = makeTruthyCssClassMap(currentClassString); + return newClassString.split(ONE_SPACE).some(function(className) { + return currentClassMap[className]; + }); + } + } + + function isAllowed(ruleType, element, currentAnimation, previousAnimation) { + return rules[ruleType].some(function(fn) { + return fn(element, currentAnimation, previousAnimation); + }); + } + + function hasAnimationClasses(animation, and) { + var a = (animation.addClass || '').length > 0; + var b = (animation.removeClass || '').length > 0; + return and ? a && b : a || b; + } + + rules.join.push(function(element, newAnimation, currentAnimation) { + // if the new animation is class-based then we can just tack that on + return !newAnimation.structural && hasAnimationClasses(newAnimation); + }); + + rules.skip.push(function(element, newAnimation, currentAnimation) { + // there is no need to animate anything if no classes are being added and + // there is no structural animation that will be triggered + return !newAnimation.structural && !hasAnimationClasses(newAnimation); + }); + + rules.skip.push(function(element, newAnimation, currentAnimation) { + // why should we trigger a new structural animation if the element will + // be removed from the DOM anyway? + return currentAnimation.event === 'leave' && newAnimation.structural; + }); + + rules.skip.push(function(element, newAnimation, currentAnimation) { + // if there is an ongoing current animation then don't even bother running the class-based animation + return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural; + }); + + rules.cancel.push(function(element, newAnimation, currentAnimation) { + // there can never be two structural animations running at the same time + return currentAnimation.structural && newAnimation.structural; + }); + + rules.cancel.push(function(element, newAnimation, currentAnimation) { + // if the previous animation is already running, but the new animation will + // be triggered, but the new animation is structural + return currentAnimation.state === RUNNING_STATE && newAnimation.structural; + }); + + rules.cancel.push(function(element, newAnimation, currentAnimation) { + // cancel the animation if classes added / removed in both animation cancel each other out, + // but only if the current animation isn't structural + + if (currentAnimation.structural) return false; + + var nA = newAnimation.addClass; + var nR = newAnimation.removeClass; + var cA = currentAnimation.addClass; + var cR = currentAnimation.removeClass; + + // early detection to save the global CPU shortage :) + if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { + return false; + } + + return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); + }); + + this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', + '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', + '$$isDocumentHidden', + function($$rAF, $rootScope, $rootElement, $document, $$HashMap, + $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow, + $$isDocumentHidden) { + + var activeAnimationsLookup = new $$HashMap(); + var disabledElementsLookup = new $$HashMap(); + var animationsEnabled = null; + + function postDigestTaskFactory() { + var postDigestCalled = false; + return function(fn) { + // we only issue a call to postDigest before + // it has first passed. This prevents any callbacks + // from not firing once the animation has completed + // since it will be out of the digest cycle. + if (postDigestCalled) { + fn(); + } else { + $rootScope.$$postDigest(function() { + postDigestCalled = true; + fn(); + }); + } + }; + } + + // Wait until all directive and route-related templates are downloaded and + // compiled. The $templateRequest.totalPendingRequests variable keeps track of + // all of the remote templates being currently downloaded. If there are no + // templates currently downloading then the watcher will still fire anyway. + var deregisterWatch = $rootScope.$watch( + function() { return $templateRequest.totalPendingRequests === 0; }, + function(isEmpty) { + if (!isEmpty) return; + deregisterWatch(); + + // Now that all templates have been downloaded, $animate will wait until + // the post digest queue is empty before enabling animations. By having two + // calls to $postDigest calls we can ensure that the flag is enabled at the + // very end of the post digest queue. Since all of the animations in $animate + // use $postDigest, it's important that the code below executes at the end. + // This basically means that the page is fully downloaded and compiled before + // any animations are triggered. + $rootScope.$$postDigest(function() { + $rootScope.$$postDigest(function() { + // we check for null directly in the event that the application already called + // .enabled() with whatever arguments that it provided it with + if (animationsEnabled === null) { + animationsEnabled = true; + } + }); + }); + } + ); + + var callbackRegistry = Object.create(null); + + // remember that the classNameFilter is set during the provider/config + // stage therefore we can optimize here and setup a helper function + var classNameFilter = $animateProvider.classNameFilter(); + var isAnimatableClassName = !classNameFilter + ? function() { return true; } + : function(className) { + return classNameFilter.test(className); + }; + + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + + function normalizeAnimationDetails(element, animation) { + return mergeAnimationDetails(element, animation, {}); + } + + // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. + var contains = window.Node.prototype.contains || /** @this */ function(arg) { + // eslint-disable-next-line no-bitwise + return this === arg || !!(this.compareDocumentPosition(arg) & 16); + }; + + function findCallbacks(parent, element, event) { + var targetNode = getDomNode(element); + var targetParentNode = getDomNode(parent); + + var matches = []; + var entries = callbackRegistry[event]; + if (entries) { + forEach(entries, function(entry) { + if (contains.call(entry.node, targetNode)) { + matches.push(entry.callback); + } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) { + matches.push(entry.callback); + } + }); + } + + return matches; + } + + function filterFromRegistry(list, matchContainer, matchCallback) { + var containerNode = extractElementNode(matchContainer); + return list.filter(function(entry) { + var isMatch = entry.node === containerNode && + (!matchCallback || entry.callback === matchCallback); + return !isMatch; + }); + } + + function cleanupEventListeners(phase, element) { + if (phase === 'close' && !element[0].parentNode) { + // If the element is not attached to a parentNode, it has been removed by + // the domOperation, and we can safely remove the event callbacks + $animate.off(element); + } + } + + var $animate = { + on: function(event, container, callback) { + var node = extractElementNode(container); + callbackRegistry[event] = callbackRegistry[event] || []; + callbackRegistry[event].push({ + node: node, + callback: callback + }); + + // Remove the callback when the element is removed from the DOM + jqLite(container).on('$destroy', function() { + var animationDetails = activeAnimationsLookup.get(node); + + if (!animationDetails) { + // If there's an animation ongoing, the callback calling code will remove + // the event listeners. If we'd remove here, the callbacks would be removed + // before the animation ends + $animate.off(event, container, callback); + } + }); + }, + + off: function(event, container, callback) { + if (arguments.length === 1 && !isString(arguments[0])) { + container = arguments[0]; + for (var eventType in callbackRegistry) { + callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container); + } + + return; + } + + var entries = callbackRegistry[event]; + if (!entries) return; + + callbackRegistry[event] = arguments.length === 1 + ? null + : filterFromRegistry(entries, container, callback); + }, + + pin: function(element, parentElement) { + assertArg(isElement(element), 'element', 'not an element'); + assertArg(isElement(parentElement), 'parentElement', 'not an element'); + element.data(NG_ANIMATE_PIN_DATA, parentElement); + }, + + push: function(element, event, options, domOperation) { + options = options || {}; + options.domOperation = domOperation; + return queueAnimation(element, event, options); + }, + + // this method has four signatures: + // () - global getter + // (bool) - global setter + // (element) - element getter + // (element, bool) - element setter + enabled: function(element, bool) { + var argCount = arguments.length; + + if (argCount === 0) { + // () - Global getter + bool = !!animationsEnabled; + } else { + var hasElement = isElement(element); + + if (!hasElement) { + // (bool) - Global setter + bool = animationsEnabled = !!element; + } else { + var node = getDomNode(element); + + if (argCount === 1) { + // (element) - Element getter + bool = !disabledElementsLookup.get(node); + } else { + // (element, bool) - Element setter + disabledElementsLookup.put(node, !bool); + } + } + } + + return bool; + } + }; + + return $animate; + + function queueAnimation(element, event, initialOptions) { + // we always make a copy of the options since + // there should never be any side effects on + // the input data when running `$animateCss`. + var options = copy(initialOptions); + + var node, parent; + element = stripCommentsFromElement(element); + if (element) { + node = getDomNode(element); + parent = element.parent(); + } + + options = prepareAnimationOptions(options); + + // we create a fake runner with a working promise. + // These methods will become available after the digest has passed + var runner = new $$AnimateRunner(); + + // this is used to trigger callbacks in postDigest mode + var runInNextPostDigestOrNow = postDigestTaskFactory(); + + if (isArray(options.addClass)) { + options.addClass = options.addClass.join(' '); + } + + if (options.addClass && !isString(options.addClass)) { + options.addClass = null; + } + + if (isArray(options.removeClass)) { + options.removeClass = options.removeClass.join(' '); + } + + if (options.removeClass && !isString(options.removeClass)) { + options.removeClass = null; + } + + if (options.from && !isObject(options.from)) { + options.from = null; + } + + if (options.to && !isObject(options.to)) { + options.to = null; + } + + // there are situations where a directive issues an animation for + // a jqLite wrapper that contains only comment nodes... If this + // happens then there is no way we can perform an animation + if (!node) { + close(); + return runner; + } + + var className = [node.getAttribute('class'), options.addClass, options.removeClass].join(' '); + if (!isAnimatableClassName(className)) { + close(); + return runner; + } + + var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; + + var documentHidden = $$isDocumentHidden(); + + // this is a hard disable of all animations for the application or on + // the element itself, therefore there is no need to continue further + // past this point if not enabled + // Animations are also disabled if the document is currently hidden (page is not visible + // to the user), because browsers slow down or do not flush calls to requestAnimationFrame + var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node); + var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; + var hasExistingAnimation = !!existingAnimation.state; + + // there is no point in traversing the same collection of parent ancestors if a followup + // animation will be run on the same element that already did all that checking work + if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) { + skipAnimations = !areAnimationsAllowed(element, parent, event); + } + + if (skipAnimations) { + // Callbacks should fire even if the document is hidden (regression fix for issue #14120) + if (documentHidden) notifyProgress(runner, event, 'start'); + close(); + if (documentHidden) notifyProgress(runner, event, 'close'); + return runner; + } + + if (isStructural) { + closeChildAnimations(element); + } + + var newAnimation = { + structural: isStructural, + element: element, + event: event, + addClass: options.addClass, + removeClass: options.removeClass, + close: close, + options: options, + runner: runner + }; + + if (hasExistingAnimation) { + var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); + if (skipAnimationFlag) { + if (existingAnimation.state === RUNNING_STATE) { + close(); + return runner; + } else { + mergeAnimationDetails(element, existingAnimation, newAnimation); + return existingAnimation.runner; + } + } + var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); + if (cancelAnimationFlag) { + if (existingAnimation.state === RUNNING_STATE) { + // this will end the animation right away and it is safe + // to do so since the animation is already running and the + // runner callback code will run in async + existingAnimation.runner.end(); + } else if (existingAnimation.structural) { + // this means that the animation is queued into a digest, but + // hasn't started yet. Therefore it is safe to run the close + // method which will call the runner methods in async. + existingAnimation.close(); + } else { + // this will merge the new animation options into existing animation options + mergeAnimationDetails(element, existingAnimation, newAnimation); + + return existingAnimation.runner; + } + } else { + // a joined animation means that this animation will take over the existing one + // so an example would involve a leave animation taking over an enter. Then when + // the postDigest kicks in the enter will be ignored. + var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); + if (joinAnimationFlag) { + if (existingAnimation.state === RUNNING_STATE) { + normalizeAnimationDetails(element, newAnimation); + } else { + applyGeneratedPreparationClasses(element, isStructural ? event : null, options); + + event = newAnimation.event = existingAnimation.event; + options = mergeAnimationDetails(element, existingAnimation, newAnimation); + + //we return the same runner since only the option values of this animation will + //be fed into the `existingAnimation`. + return existingAnimation.runner; + } + } + } + } else { + // normalization in this case means that it removes redundant CSS classes that + // already exist (addClass) or do not exist (removeClass) on the element + normalizeAnimationDetails(element, newAnimation); + } + + // when the options are merged and cleaned up we may end up not having to do + // an animation at all, therefore we should check this before issuing a post + // digest callback. Structural animations will always run no matter what. + var isValidAnimation = newAnimation.structural; + if (!isValidAnimation) { + // animate (from/to) can be quickly checked first, otherwise we check if any classes are present + isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) + || hasAnimationClasses(newAnimation); + } + + if (!isValidAnimation) { + close(); + clearElementAnimationState(element); + return runner; + } + + // the counter keeps track of cancelled animations + var counter = (existingAnimation.counter || 0) + 1; + newAnimation.counter = counter; + + markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); + + $rootScope.$$postDigest(function() { + var animationDetails = activeAnimationsLookup.get(node); + var animationCancelled = !animationDetails; + animationDetails = animationDetails || {}; + + // if addClass/removeClass is called before something like enter then the + // registered parent element may not be present. The code below will ensure + // that a final value for parent element is obtained + var parentElement = element.parent() || []; + + // animate/structural/class-based animations all have requirements. Otherwise there + // is no point in performing an animation. The parent node must also be set. + var isValidAnimation = parentElement.length > 0 + && (animationDetails.event === 'animate' + || animationDetails.structural + || hasAnimationClasses(animationDetails)); + + // this means that the previous animation was cancelled + // even if the follow-up animation is the same event + if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) { + // if another animation did not take over then we need + // to make sure that the domOperation and options are + // handled accordingly + if (animationCancelled) { + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + } + + // if the event changed from something like enter to leave then we do + // it, otherwise if it's the same then the end result will be the same too + if (animationCancelled || (isStructural && animationDetails.event !== event)) { + options.domOperation(); + runner.end(); + } + + // in the event that the element animation was not cancelled or a follow-up animation + // isn't allowed to animate from here then we need to clear the state of the element + // so that any future animations won't read the expired animation data. + if (!isValidAnimation) { + clearElementAnimationState(element); + } + + return; + } + + // this combined multiple class to addClass / removeClass into a setClass event + // so long as a structural event did not take over the animation + event = !animationDetails.structural && hasAnimationClasses(animationDetails, true) + ? 'setClass' + : animationDetails.event; + + markElementAnimationState(element, RUNNING_STATE); + var realRunner = $$animation(element, event, animationDetails.options); + + // this will update the runner's flow-control events based on + // the `realRunner` object. + runner.setHost(realRunner); + notifyProgress(runner, event, 'start', {}); + + realRunner.done(function(status) { + close(!status); + var animationDetails = activeAnimationsLookup.get(node); + if (animationDetails && animationDetails.counter === counter) { + clearElementAnimationState(getDomNode(element)); + } + notifyProgress(runner, event, 'close', {}); + }); + }); + + return runner; + + function notifyProgress(runner, event, phase, data) { + runInNextPostDigestOrNow(function() { + var callbacks = findCallbacks(parent, element, event); + if (callbacks.length) { + // do not optimize this call here to RAF because + // we don't know how heavy the callback code here will + // be and if this code is buffered then this can + // lead to a performance regression. + $$rAF(function() { + forEach(callbacks, function(callback) { + callback(element, phase, data); + }); + cleanupEventListeners(phase, element); + }); + } else { + cleanupEventListeners(phase, element); + } + }); + runner.progress(event, phase, data); + } + + function close(reject) { + clearGeneratedClasses(element, options); + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + options.domOperation(); + runner.complete(!reject); + } + } + + function closeChildAnimations(element) { + var node = getDomNode(element); + var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']'); + forEach(children, function(child) { + var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10); + var animationDetails = activeAnimationsLookup.get(child); + if (animationDetails) { + switch (state) { + case RUNNING_STATE: + animationDetails.runner.end(); + /* falls through */ + case PRE_DIGEST_STATE: + activeAnimationsLookup.remove(child); + break; + } + } + }); + } + + function clearElementAnimationState(element) { + var node = getDomNode(element); + node.removeAttribute(NG_ANIMATE_ATTR_NAME); + activeAnimationsLookup.remove(node); + } + + function isMatchingElement(nodeOrElmA, nodeOrElmB) { + return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB); + } + + /** + * This fn returns false if any of the following is true: + * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed + * b) a parent element has an ongoing structural animation, and animateChildren is false + * c) the element is not a child of the body + * d) the element is not a child of the $rootElement + */ + function areAnimationsAllowed(element, parentElement, event) { + var bodyElement = jqLite($document[0].body); + var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; + var rootElementDetected = isMatchingElement(element, $rootElement); + var parentAnimationDetected = false; + var animateChildren; + var elementDisabled = disabledElementsLookup.get(getDomNode(element)); + + var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA); + if (parentHost) { + parentElement = parentHost; + } + + parentElement = getDomNode(parentElement); + + while (parentElement) { + if (!rootElementDetected) { + // angular doesn't want to attempt to animate elements outside of the application + // therefore we need to ensure that the rootElement is an ancestor of the current element + rootElementDetected = isMatchingElement(parentElement, $rootElement); + } + + if (parentElement.nodeType !== ELEMENT_NODE) { + // no point in inspecting the #document element + break; + } + + var details = activeAnimationsLookup.get(parentElement) || {}; + // either an enter, leave or move animation will commence + // therefore we can't allow any animations to take place + // but if a parent animation is class-based then that's ok + if (!parentAnimationDetected) { + var parentElementDisabled = disabledElementsLookup.get(parentElement); + + if (parentElementDisabled === true && elementDisabled !== false) { + // disable animations if the user hasn't explicitly enabled animations on the + // current element + elementDisabled = true; + // element is disabled via parent element, no need to check anything else + break; + } else if (parentElementDisabled === false) { + elementDisabled = false; + } + parentAnimationDetected = details.structural; + } + + if (isUndefined(animateChildren) || animateChildren === true) { + var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA); + if (isDefined(value)) { + animateChildren = value; + } + } + + // there is no need to continue traversing at this point + if (parentAnimationDetected && animateChildren === false) break; + + if (!bodyElementDetected) { + // we also need to ensure that the element is or will be a part of the body element + // otherwise it is pointless to even issue an animation to be rendered + bodyElementDetected = isMatchingElement(parentElement, bodyElement); + } + + if (bodyElementDetected && rootElementDetected) { + // If both body and root have been found, any other checks are pointless, + // as no animation data should live outside the application + break; + } + + if (!rootElementDetected) { + // If no rootElement is detected, check if the parentElement is pinned to another element + parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA); + if (parentHost) { + // The pin target element becomes the next parent element + parentElement = getDomNode(parentHost); + continue; + } + } + + parentElement = parentElement.parentNode; + } + + var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; + return allowAnimation && rootElementDetected && bodyElementDetected; + } + + function markElementAnimationState(element, state, details) { + details = details || {}; + details.state = state; + + var node = getDomNode(element); + node.setAttribute(NG_ANIMATE_ATTR_NAME, state); + + var oldValue = activeAnimationsLookup.get(node); + var newValue = oldValue + ? extend(oldValue, details) + : details; + activeAnimationsLookup.put(node, newValue); + } + }]; +}]; + +/* exported $$AnimationProvider */ + +var $$AnimationProvider = ['$animateProvider', /** @this */ function($animateProvider) { + var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; + + var drivers = this.drivers = []; + + var RUNNER_STORAGE_KEY = '$$animationRunner'; + + function setRunner(element, runner) { + element.data(RUNNER_STORAGE_KEY, runner); + } + + function removeRunner(element) { + element.removeData(RUNNER_STORAGE_KEY); + } + + function getRunner(element) { + return element.data(RUNNER_STORAGE_KEY); + } + + this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', + function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { + + var animationQueue = []; + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + + function sortAnimations(animations) { + var tree = { children: [] }; + var i, lookup = new $$HashMap(); + + // this is done first beforehand so that the hashmap + // is filled with a list of the elements that will be animated + for (i = 0; i < animations.length; i++) { + var animation = animations[i]; + lookup.put(animation.domNode, animations[i] = { + domNode: animation.domNode, + fn: animation.fn, + children: [] + }); + } + + for (i = 0; i < animations.length; i++) { + processNode(animations[i]); + } + + return flatten(tree); + + function processNode(entry) { + if (entry.processed) return entry; + entry.processed = true; + + var elementNode = entry.domNode; + var parentNode = elementNode.parentNode; + lookup.put(elementNode, entry); + + var parentEntry; + while (parentNode) { + parentEntry = lookup.get(parentNode); + if (parentEntry) { + if (!parentEntry.processed) { + parentEntry = processNode(parentEntry); + } + break; + } + parentNode = parentNode.parentNode; + } + + (parentEntry || tree).children.push(entry); + return entry; + } + + function flatten(tree) { + var result = []; + var queue = []; + var i; + + for (i = 0; i < tree.children.length; i++) { + queue.push(tree.children[i]); + } + + var remainingLevelEntries = queue.length; + var nextLevelEntries = 0; + var row = []; + + for (i = 0; i < queue.length; i++) { + var entry = queue[i]; + if (remainingLevelEntries <= 0) { + remainingLevelEntries = nextLevelEntries; + nextLevelEntries = 0; + result.push(row); + row = []; + } + row.push(entry.fn); + entry.children.forEach(function(childEntry) { + nextLevelEntries++; + queue.push(childEntry); + }); + remainingLevelEntries--; + } + + if (row.length) { + result.push(row); + } + + return result; + } + } + + // TODO(matsko): document the signature in a better way + return function(element, event, options) { + options = prepareAnimationOptions(options); + var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; + + // there is no animation at the current moment, however + // these runner methods will get later updated with the + // methods leading into the driver's end/cancel methods + // for now they just stop the animation from starting + var runner = new $$AnimateRunner({ + end: function() { close(); }, + cancel: function() { close(true); } + }); + + if (!drivers.length) { + close(); + return runner; + } + + setRunner(element, runner); + + var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); + var tempClasses = options.tempClasses; + if (tempClasses) { + classes += ' ' + tempClasses; + options.tempClasses = null; + } + + var prepareClassName; + if (isStructural) { + prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; + $$jqLite.addClass(element, prepareClassName); + } + + animationQueue.push({ + // this data is used by the postDigest code and passed into + // the driver step function + element: element, + classes: classes, + event: event, + structural: isStructural, + options: options, + beforeStart: beforeStart, + close: close + }); + + element.on('$destroy', handleDestroyedElement); + + // we only want there to be one function called within the post digest + // block. This way we can group animations for all the animations that + // were apart of the same postDigest flush call. + if (animationQueue.length > 1) return runner; + + $rootScope.$$postDigest(function() { + var animations = []; + forEach(animationQueue, function(entry) { + // the element was destroyed early on which removed the runner + // form its storage. This means we can't animate this element + // at all and it already has been closed due to destruction. + if (getRunner(entry.element)) { + animations.push(entry); + } else { + entry.close(); + } + }); + + // now any future animations will be in another postDigest + animationQueue.length = 0; + + var groupedAnimations = groupAnimations(animations); + var toBeSortedAnimations = []; + + forEach(groupedAnimations, function(animationEntry) { + toBeSortedAnimations.push({ + domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), + fn: function triggerAnimationStart() { + // it's important that we apply the `ng-animate` CSS class and the + // temporary classes before we do any driver invoking since these + // CSS classes may be required for proper CSS detection. + animationEntry.beforeStart(); + + var startAnimationFn, closeFn = animationEntry.close; + + // in the event that the element was removed before the digest runs or + // during the RAF sequencing then we should not trigger the animation. + var targetElement = animationEntry.anchors + ? (animationEntry.from.element || animationEntry.to.element) + : animationEntry.element; + + if (getRunner(targetElement)) { + var operation = invokeFirstDriver(animationEntry); + if (operation) { + startAnimationFn = operation.start; + } + } + + if (!startAnimationFn) { + closeFn(); + } else { + var animationRunner = startAnimationFn(); + animationRunner.done(function(status) { + closeFn(!status); + }); + updateAnimationRunners(animationEntry, animationRunner); + } + } + }); + }); + + // we need to sort each of the animations in order of parent to child + // relationships. This ensures that the child classes are applied at the + // right time. + $$rAFScheduler(sortAnimations(toBeSortedAnimations)); + }); + + return runner; + + // TODO(matsko): change to reference nodes + function getAnchorNodes(node) { + var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']'; + var items = node.hasAttribute(NG_ANIMATE_REF_ATTR) + ? [node] + : node.querySelectorAll(SELECTOR); + var anchors = []; + forEach(items, function(node) { + var attr = node.getAttribute(NG_ANIMATE_REF_ATTR); + if (attr && attr.length) { + anchors.push(node); + } + }); + return anchors; + } + + function groupAnimations(animations) { + var preparedAnimations = []; + var refLookup = {}; + forEach(animations, function(animation, index) { + var element = animation.element; + var node = getDomNode(element); + var event = animation.event; + var enterOrMove = ['enter', 'move'].indexOf(event) >= 0; + var anchorNodes = animation.structural ? getAnchorNodes(node) : []; + + if (anchorNodes.length) { + var direction = enterOrMove ? 'to' : 'from'; + + forEach(anchorNodes, function(anchor) { + var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR); + refLookup[key] = refLookup[key] || {}; + refLookup[key][direction] = { + animationID: index, + element: jqLite(anchor) + }; + }); + } else { + preparedAnimations.push(animation); + } + }); + + var usedIndicesLookup = {}; + var anchorGroups = {}; + forEach(refLookup, function(operations, key) { + var from = operations.from; + var to = operations.to; + + if (!from || !to) { + // only one of these is set therefore we can't have an + // anchor animation since all three pieces are required + var index = from ? from.animationID : to.animationID; + var indexKey = index.toString(); + if (!usedIndicesLookup[indexKey]) { + usedIndicesLookup[indexKey] = true; + preparedAnimations.push(animations[index]); + } + return; + } + + var fromAnimation = animations[from.animationID]; + var toAnimation = animations[to.animationID]; + var lookupKey = from.animationID.toString(); + if (!anchorGroups[lookupKey]) { + var group = anchorGroups[lookupKey] = { + structural: true, + beforeStart: function() { + fromAnimation.beforeStart(); + toAnimation.beforeStart(); + }, + close: function() { + fromAnimation.close(); + toAnimation.close(); + }, + classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes), + from: fromAnimation, + to: toAnimation, + anchors: [] // TODO(matsko): change to reference nodes + }; + + // the anchor animations require that the from and to elements both have at least + // one shared CSS class which effectively marries the two elements together to use + // the same animation driver and to properly sequence the anchor animation. + if (group.classes.length) { + preparedAnimations.push(group); + } else { + preparedAnimations.push(fromAnimation); + preparedAnimations.push(toAnimation); + } + } + + anchorGroups[lookupKey].anchors.push({ + 'out': from.element, 'in': to.element + }); + }); + + return preparedAnimations; + } + + function cssClassesIntersection(a,b) { + a = a.split(' '); + b = b.split(' '); + var matches = []; + + for (var i = 0; i < a.length; i++) { + var aa = a[i]; + if (aa.substring(0,3) === 'ng-') continue; + + for (var j = 0; j < b.length; j++) { + if (aa === b[j]) { + matches.push(aa); + break; + } + } + } + + return matches.join(' '); + } + + function invokeFirstDriver(animationDetails) { + // we loop in reverse order since the more general drivers (like CSS and JS) + // may attempt more elements, but custom drivers are more particular + for (var i = drivers.length - 1; i >= 0; i--) { + var driverName = drivers[i]; + var factory = $injector.get(driverName); + var driver = factory(animationDetails); + if (driver) { + return driver; + } + } + } + + function beforeStart() { + element.addClass(NG_ANIMATE_CLASSNAME); + if (tempClasses) { + $$jqLite.addClass(element, tempClasses); + } + if (prepareClassName) { + $$jqLite.removeClass(element, prepareClassName); + prepareClassName = null; + } + } + + function updateAnimationRunners(animation, newRunner) { + if (animation.from && animation.to) { + update(animation.from.element); + update(animation.to.element); + } else { + update(animation.element); + } + + function update(element) { + var runner = getRunner(element); + if (runner) runner.setHost(newRunner); + } + } + + function handleDestroyedElement() { + var runner = getRunner(element); + if (runner && (event !== 'leave' || !options.$$domOperationFired)) { + runner.end(); + } + } + + function close(rejected) { + element.off('$destroy', handleDestroyedElement); + removeRunner(element); + + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + options.domOperation(); + + if (tempClasses) { + $$jqLite.removeClass(element, tempClasses); + } + + element.removeClass(NG_ANIMATE_CLASSNAME); + runner.complete(!rejected); + } + }; + }]; +}]; + +/** + * @ngdoc directive + * @name ngAnimateSwap + * @restrict A + * @scope + * + * @description + * + * ngAnimateSwap is a animation-oriented directive that allows for the container to + * be removed and entered in whenever the associated expression changes. A + * common usecase for this directive is a rotating banner or slider component which + * contains one image being present at a time. When the active image changes + * then the old image will perform a `leave` animation and the new element + * will be inserted via an `enter` animation. + * + * @animations + * | Animation | Occurs | + * |----------------------------------|--------------------------------------| + * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM | + * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM | + * + * @example + * + * + *
+ *
+ * {{ number }} + *
+ *
+ *
+ * + * angular.module('ngAnimateSwapExample', ['ngAnimate']) + * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) { + * $scope.number = 0; + * $interval(function() { + * $scope.number++; + * }, 1000); + * + * var colors = ['red','blue','green','yellow','orange']; + * $scope.colorClass = function(number) { + * return colors[number % colors.length]; + * }; + * }]); + * + * + * .container { + * height:250px; + * width:250px; + * position:relative; + * overflow:hidden; + * border:2px solid black; + * } + * .container .cell { + * font-size:150px; + * text-align:center; + * line-height:250px; + * position:absolute; + * top:0; + * left:0; + * right:0; + * border-bottom:2px solid black; + * } + * .swap-animation.ng-enter, .swap-animation.ng-leave { + * transition:0.5s linear all; + * } + * .swap-animation.ng-enter { + * top:-250px; + * } + * .swap-animation.ng-enter-active { + * top:0px; + * } + * .swap-animation.ng-leave { + * top:0px; + * } + * .swap-animation.ng-leave-active { + * top:250px; + * } + * .red { background:red; } + * .green { background:green; } + * .blue { background:blue; } + * .yellow { background:yellow; } + * .orange { background:orange; } + * + *
+ */ +var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) { + return { + restrict: 'A', + transclude: 'element', + terminal: true, + priority: 600, // we use 600 here to ensure that the directive is caught before others + link: function(scope, $element, attrs, ctrl, $transclude) { + var previousElement, previousScope; + scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) { + if (previousElement) { + $animate.leave(previousElement); + } + if (previousScope) { + previousScope.$destroy(); + previousScope = null; + } + if (value || value === 0) { + previousScope = scope.$new(); + $transclude(previousScope, function(element) { + previousElement = element; + $animate.enter(element, null, $element); + }); + } + }); + } + }; +}]; + +/** + * @ngdoc module + * @name ngAnimate + * @description + * + * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via + * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. + * + *
+ * + * # Usage + * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based + * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For + * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within + * the HTML element that the animation will be triggered on. + * + * ## Directive Support + * The following directives are "animation aware": + * + * | Directive | Supported Animations | + * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| + * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | + * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | + * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | + * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | + * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | + * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | + * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | + * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | + * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | + * | {@link module:ngMessages#animations ngMessage} | enter and leave | + * + * (More information can be found by visiting each the documentation associated with each directive.) + * + * ## CSS-based Animations + * + * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML + * and CSS code we can create an animation that will be picked up by Angular when an underlying directive performs an operation. + * + * The example below shows how an `enter` animation can be made possible on an element using `ng-if`: + * + * ```html + *
+ * Fade me in out + *
+ * + * + * ``` + * + * Notice the CSS class **fade**? We can now create the CSS transition code that references this class: + * + * ```css + * /* The starting CSS styles for the enter animation */ + * .fade.ng-enter { + * transition:0.5s linear all; + * opacity:0; + * } + * + * /* The finishing CSS styles for the enter animation */ + * .fade.ng-enter.ng-enter-active { + * opacity:1; + * } + * ``` + * + * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two + * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition + * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards. + * + * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions: + * + * ```css + * /* now the element will fade out before it is removed from the DOM */ + * .fade.ng-leave { + * transition:0.5s linear all; + * opacity:1; + * } + * .fade.ng-leave.ng-leave-active { + * opacity:0; + * } + * ``` + * + * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class: + * + * ```css + * /* there is no need to define anything inside of the destination + * CSS class since the keyframe will take charge of the animation */ + * .fade.ng-leave { + * animation: my_fade_animation 0.5s linear; + * -webkit-animation: my_fade_animation 0.5s linear; + * } + * + * @keyframes my_fade_animation { + * from { opacity:1; } + * to { opacity:0; } + * } + * + * @-webkit-keyframes my_fade_animation { + * from { opacity:1; } + * to { opacity:0; } + * } + * ``` + * + * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element. + * + * ### CSS Class-based Animations + * + * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different + * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added + * and removed. + * + * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class: + * + * ```html + *
+ * Show and hide me + *
+ * + * + * + * ``` + * + * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since + * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest. + * + * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation + * with CSS styles. + * + * ```html + *
+ * Highlight this box + *
+ * + * + * + * ``` + * + * We can also make use of CSS keyframes by placing them within the CSS classes. + * + * + * ### CSS Staggering Animations + * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a + * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be + * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for + * the animation. The style property expected within the stagger class can either be a **transition-delay** or an + * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). + * + * ```css + * .my-animation.ng-enter { + * /* standard transition code */ + * transition: 1s linear all; + * opacity:0; + * } + * .my-animation.ng-enter-stagger { + * /* this will have a 100ms delay between each successive leave animation */ + * transition-delay: 0.1s; + * + * /* As of 1.4.4, this must always be set: it signals ngAnimate + * to not accidentally inherit a delay property from another CSS class */ + * transition-duration: 0s; + * } + * .my-animation.ng-enter.ng-enter-active { + * /* standard transition styles */ + * opacity:1; + * } + * ``` + * + * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations + * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this + * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation + * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired. + * + * The following code will issue the **ng-leave-stagger** event on the element provided: + * + * ```js + * var kids = parent.children(); + * + * $animate.leave(kids[0]); //stagger index=0 + * $animate.leave(kids[1]); //stagger index=1 + * $animate.leave(kids[2]); //stagger index=2 + * $animate.leave(kids[3]); //stagger index=3 + * $animate.leave(kids[4]); //stagger index=4 + * + * window.requestAnimationFrame(function() { + * //stagger has reset itself + * $animate.leave(kids[5]); //stagger index=0 + * $animate.leave(kids[6]); //stagger index=1 + * + * $scope.$digest(); + * }); + * ``` + * + * Stagger animations are currently only supported within CSS-defined animations. + * + * ### The `ng-animate` CSS class + * + * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation. + * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations). + * + * Therefore, animations can be applied to an element using this temporary class directly via CSS. + * + * ```css + * .zipper.ng-animate { + * transition:0.5s linear all; + * } + * .zipper.ng-enter { + * opacity:0; + * } + * .zipper.ng-enter.ng-enter-active { + * opacity:1; + * } + * .zipper.ng-leave { + * opacity:1; + * } + * .zipper.ng-leave.ng-leave-active { + * opacity:0; + * } + * ``` + * + * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove + * the CSS class once an animation has completed.) + * + * + * ### The `ng-[event]-prepare` class + * + * This is a special class that can be used to prevent unwanted flickering / flash of content before + * the actual animation starts. The class is added as soon as an animation is initialized, but removed + * before the actual animation starts (after waiting for a $digest). + * It is also only added for *structural* animations (`enter`, `move`, and `leave`). + * + * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` + * into elements that have class-based animations such as `ngClass`. + * + * ```html + *
+ *
+ *
+ *
+ *
+ * ``` + * + * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. + * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: + * + * ```css + * .message.ng-enter-prepare { + * opacity: 0; + * } + * + * ``` + * + * ## JavaScript-based Animations + * + * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared + * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the + * `module.animation()` module function we can register the animation. + * + * Let's see an example of a enter/leave animation using `ngRepeat`: + * + * ```html + *
+ * {{ item }} + *
+ * ``` + * + * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`: + * + * ```js + * myModule.animation('.slide', [function() { + * return { + * // make note that other events (like addClass/removeClass) + * // have different function input parameters + * enter: function(element, doneFn) { + * jQuery(element).fadeIn(1000, doneFn); + * + * // remember to call doneFn so that angular + * // knows that the animation has concluded + * }, + * + * move: function(element, doneFn) { + * jQuery(element).fadeIn(1000, doneFn); + * }, + * + * leave: function(element, doneFn) { + * jQuery(element).fadeOut(1000, doneFn); + * } + * } + * }]); + * ``` + * + * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as + * greensock.js and velocity.js. + * + * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define + * our animations inside of the same registered animation, however, the function input arguments are a bit different: + * + * ```html + *
+ * this box is moody + *
+ * + * + * + * ``` + * + * ```js + * myModule.animation('.colorful', [function() { + * return { + * addClass: function(element, className, doneFn) { + * // do some cool animation and call the doneFn + * }, + * removeClass: function(element, className, doneFn) { + * // do some cool animation and call the doneFn + * }, + * setClass: function(element, addedClass, removedClass, doneFn) { + * // do some cool animation and call the doneFn + * } + * } + * }]); + * ``` + * + * ## CSS + JS Animations Together + * + * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular, + * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking + * charge of the animation**: + * + * ```html + *
+ * Slide in and out + *
+ * ``` + * + * ```js + * myModule.animation('.slide', [function() { + * return { + * enter: function(element, doneFn) { + * jQuery(element).slideIn(1000, doneFn); + * } + * } + * }]); + * ``` + * + * ```css + * .slide.ng-enter { + * transition:0.5s linear all; + * transform:translateY(-100px); + * } + * .slide.ng-enter.ng-enter-active { + * transform:translateY(0); + * } + * ``` + * + * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the + * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from + * our own JS-based animation code: + * + * ```js + * myModule.animation('.slide', ['$animateCss', function($animateCss) { + * return { + * enter: function(element) { +* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. + * return $animateCss(element, { + * event: 'enter', + * structural: true + * }); + * } + * } + * }]); + * ``` + * + * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework. + * + * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or + * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that + * data into `$animateCss` directly: + * + * ```js + * myModule.animation('.slide', ['$animateCss', function($animateCss) { + * return { + * enter: function(element) { + * return $animateCss(element, { + * event: 'enter', + * structural: true, + * addClass: 'maroon-setting', + * from: { height:0 }, + * to: { height: 200 } + * }); + * } + * } + * }]); + * ``` + * + * Now we can fill in the rest via our transition CSS code: + * + * ```css + * /* the transition tells ngAnimate to make the animation happen */ + * .slide.ng-enter { transition:0.5s linear all; } + * + * /* this extra CSS class will be absorbed into the transition + * since the $animateCss code is adding the class */ + * .maroon-setting { background:red; } + * ``` + * + * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over. + * + * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}. + * + * ## Animation Anchoring (via `ng-animate-ref`) + * + * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between + * structural areas of an application (like views) by pairing up elements using an attribute + * called `ng-animate-ref`. + * + * Let's say for example we have two views that are managed by `ng-view` and we want to show + * that there is a relationship between two components situated in within these views. By using the + * `ng-animate-ref` attribute we can identify that the two components are paired together and we + * can then attach an animation, which is triggered when the view changes. + * + * Say for example we have the following template code: + * + * ```html + * + *
+ *
+ * + * + *
+ * + * + * + * + * + * ``` + * + * Now, when the view changes (once the link is clicked), ngAnimate will examine the + * HTML contents to see if there is a match reference between any components in the view + * that is leaving and the view that is entering. It will scan both the view which is being + * removed (leave) and inserted (enter) to see if there are any paired DOM elements that + * contain a matching ref value. + * + * The two images match since they share the same ref value. ngAnimate will now create a + * transport element (which is a clone of the first image element) and it will then attempt + * to animate to the position of the second image element in the next view. For the animation to + * work a special CSS class called `ng-anchor` will be added to the transported element. + * + * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then + * ngAnimate will handle the entire transition for us as well as the addition and removal of + * any changes of CSS classes between the elements: + * + * ```css + * .banner.ng-anchor { + * /* this animation will last for 1 second since there are + * two phases to the animation (an `in` and an `out` phase) */ + * transition:0.5s linear all; + * } + * ``` + * + * We also **must** include animations for the views that are being entered and removed + * (otherwise anchoring wouldn't be possible since the new view would be inserted right away). + * + * ```css + * .view-animation.ng-enter, .view-animation.ng-leave { + * transition:0.5s linear all; + * position:fixed; + * left:0; + * top:0; + * width:100%; + * } + * .view-animation.ng-enter { + * transform:translateX(100%); + * } + * .view-animation.ng-leave, + * .view-animation.ng-enter.ng-enter-active { + * transform:translateX(0%); + * } + * .view-animation.ng-leave.ng-leave-active { + * transform:translateX(-100%); + * } + * ``` + * + * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur: + * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away + * from its origin. Once that animation is over then the `in` stage occurs which animates the + * element to its destination. The reason why there are two animations is to give enough time + * for the enter animation on the new element to be ready. + * + * The example above sets up a transition for both the in and out phases, but we can also target the out or + * in phases directly via `ng-anchor-out` and `ng-anchor-in`. + * + * ```css + * .banner.ng-anchor-out { + * transition: 0.5s linear all; + * + * /* the scale will be applied during the out animation, + * but will be animated away when the in animation runs */ + * transform: scale(1.2); + * } + * + * .banner.ng-anchor-in { + * transition: 1s linear all; + * } + * ``` + * + * + * + * + * ### Anchoring Demo + * + + + Home +
+
+
+
+
+ + angular.module('anchoringExample', ['ngAnimate', 'ngRoute']) + .config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/', { + templateUrl: 'home.html', + controller: 'HomeController as home' + }); + $routeProvider.when('/profile/:id', { + templateUrl: 'profile.html', + controller: 'ProfileController as profile' + }); + }]) + .run(['$rootScope', function($rootScope) { + $rootScope.records = [ + { id: 1, title: 'Miss Beulah Roob' }, + { id: 2, title: 'Trent Morissette' }, + { id: 3, title: 'Miss Ava Pouros' }, + { id: 4, title: 'Rod Pouros' }, + { id: 5, title: 'Abdul Rice' }, + { id: 6, title: 'Laurie Rutherford Sr.' }, + { id: 7, title: 'Nakia McLaughlin' }, + { id: 8, title: 'Jordon Blanda DVM' }, + { id: 9, title: 'Rhoda Hand' }, + { id: 10, title: 'Alexandrea Sauer' } + ]; + }]) + .controller('HomeController', [function() { + //empty + }]) + .controller('ProfileController', ['$rootScope', '$routeParams', + function ProfileController($rootScope, $routeParams) { + var index = parseInt($routeParams.id, 10); + var record = $rootScope.records[index - 1]; + + this.title = record.title; + this.id = record.id; + }]); + + +

Welcome to the home page

+

Please click on an element

+ + {{ record.title }} + +
+ +
+ {{ profile.title }} +
+
+ + .record { + display:block; + font-size:20px; + } + .profile { + background:black; + color:white; + font-size:100px; + } + .view-container { + position:relative; + } + .view-container > .view.ng-animate { + position:absolute; + top:0; + left:0; + width:100%; + min-height:500px; + } + .view.ng-enter, .view.ng-leave, + .record.ng-anchor { + transition:0.5s linear all; + } + .view.ng-enter { + transform:translateX(100%); + } + .view.ng-enter.ng-enter-active, .view.ng-leave { + transform:translateX(0%); + } + .view.ng-leave.ng-leave-active { + transform:translateX(-100%); + } + .record.ng-anchor-out { + background:red; + } + +
+ * + * ### How is the element transported? + * + * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting + * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element + * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The + * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match + * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied + * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class + * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element + * will become visible since the shim class will be removed. + * + * ### How is the morphing handled? + * + * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out + * what CSS classes differ between the starting element and the destination element. These different CSS classes + * will be added/removed on the anchor element and a transition will be applied (the transition that is provided + * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will + * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that + * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since + * the cloned element is placed inside of root element which is likely close to the body element). + * + * Note that if the root element is on the `` element then the cloned node will be placed inside of body. + * + * + * ## Using $animate in your directive code + * + * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? + * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's + * imagine we have a greeting box that shows and hides itself when the data changes + * + * ```html + * Hi there + * ``` + * + * ```js + * ngModule.directive('greetingBox', ['$animate', function($animate) { + * return function(scope, element, attrs) { + * attrs.$observe('active', function(value) { + * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); + * }); + * }); + * }]); + * ``` + * + * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element + * in our HTML code then we can trigger a CSS or JS animation to happen. + * + * ```css + * /* normally we would create a CSS class to reference on the element */ + * greeting-box.on { transition:0.5s linear all; background:green; color:white; } + * ``` + * + * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's + * possible be sure to visit the {@link ng.$animate $animate service API page}. + * + * + * ## Callbacks and Promises + * + * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger + * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has + * ended by chaining onto the returned promise that animation method returns. + * + * ```js + * // somewhere within the depths of the directive + * $animate.enter(element, parent).then(function() { + * //the animation has completed + * }); + * ``` + * + * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case + * anymore.) + * + * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering + * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view + * routing controller to hook into that: + * + * ```js + * ngModule.controller('HomePageController', ['$animate', function($animate) { + * $animate.on('enter', ngViewElement, function(element) { + * // the animation for this route has completed + * }]); + * }]) + * ``` + * + * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) + */ + +var copy; +var extend; +var forEach; +var isArray; +var isDefined; +var isElement; +var isFunction; +var isObject; +var isString; +var isUndefined; +var jqLite; +var noop; + +/** + * @ngdoc service + * @name $animate + * @kind object + * + * @description + * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. + * + * Click here {@link ng.$animate to learn more about animations with `$animate`}. + */ +angular.module('ngAnimate', [], function initAngularHelpers() { + // Access helpers from angular core. + // Do it inside a `config` block to ensure `window.angular` is available. + noop = angular.noop; + copy = angular.copy; + extend = angular.extend; + jqLite = angular.element; + forEach = angular.forEach; + isArray = angular.isArray; + isString = angular.isString; + isObject = angular.isObject; + isUndefined = angular.isUndefined; + isDefined = angular.isDefined; + isFunction = angular.isFunction; + isElement = angular.isElement; +}) + .directive('ngAnimateSwap', ngAnimateSwapDirective) + + .directive('ngAnimateChildren', $$AnimateChildrenDirective) + .factory('$$rAFScheduler', $$rAFSchedulerFactory) + + .provider('$$animateQueue', $$AnimateQueueProvider) + .provider('$$animation', $$AnimationProvider) + + .provider('$animateCss', $AnimateCssProvider) + .provider('$$animateCssDriver', $$AnimateCssDriverProvider) + + .provider('$$animateJs', $$AnimateJsProvider) + .provider('$$animateJsDriver', $$AnimateJsDriverProvider); + + +})(window, window.angular); + +},{}],2:[function(require,module,exports){ +require('./angular-animate'); +module.exports = 'ngAnimate'; + +},{"./angular-animate":1}],3:[function(require,module,exports){ +/** + * @license AngularJS v1.6.1 + * (c) 2010-2016 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular) {'use strict'; + +/** + * @ngdoc module + * @name ngAria + * @description + * + * The `ngAria` module provides support for common + * [ARIA](http://www.w3.org/TR/wai-aria/) + * attributes that convey state or semantic information about the application for users + * of assistive technologies, such as screen readers. + * + *
+ * + * ## Usage + * + * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following + * directives are supported: + * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, + * `ngDblClick`, and `ngMessages`. + * + * Below is a more detailed breakdown of the attributes handled by ngAria: + * + * | Directive | Supported Attributes | + * |---------------------------------------------|-----------------------------------------------------------------------------------------------------| + * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | + * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | + * | {@link ng.directive:ngRequired ngRequired} | aria-required | + * | {@link ng.directive:ngChecked ngChecked} | aria-checked | + * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | + * | {@link ng.directive:ngValue ngValue} | aria-checked | + * | {@link ng.directive:ngShow ngShow} | aria-hidden | + * | {@link ng.directive:ngHide ngHide} | aria-hidden | + * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | + * | {@link module:ngMessages ngMessages} | aria-live | + * | {@link ng.directive:ngClick ngClick} | tabindex, keydown event, button role | + * + * Find out more information about each directive by reading the + * {@link guide/accessibility ngAria Developer Guide}. + * + * ## Example + * Using ngDisabled with ngAria: + * ```html + * + * ``` + * Becomes: + * ```html + * + * ``` + * + * ## Disabling Attributes + * It's possible to disable individual attributes added by ngAria with the + * {@link ngAria.$ariaProvider#config config} method. For more details, see the + * {@link guide/accessibility Developer Guide}. + */ +var ngAriaModule = angular.module('ngAria', ['ng']). + provider('$aria', $AriaProvider); + +/** +* Internal Utilities +*/ +var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; + +var isNodeOneOf = function(elem, nodeTypeArray) { + if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { + return true; + } +}; +/** + * @ngdoc provider + * @name $ariaProvider + * @this + * + * @description + * + * Used for configuring the ARIA attributes injected and managed by ngAria. + * + * ```js + * angular.module('myApp', ['ngAria'], function config($ariaProvider) { + * $ariaProvider.config({ + * ariaValue: true, + * tabindex: false + * }); + * }); + *``` + * + * ## Dependencies + * Requires the {@link ngAria} module to be installed. + * + */ +function $AriaProvider() { + var config = { + ariaHidden: true, + ariaChecked: true, + ariaReadonly: true, + ariaDisabled: true, + ariaRequired: true, + ariaInvalid: true, + ariaValue: true, + tabindex: true, + bindKeydown: true, + bindRoleForClick: true + }; + + /** + * @ngdoc method + * @name $ariaProvider#config + * + * @param {object} config object to enable/disable specific ARIA attributes + * + * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags + * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags + * - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags + * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags + * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags + * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags + * - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and + * aria-valuenow tags + * - **tabindex** – `{boolean}` – Enables/disables tabindex tags + * - **bindKeydown** – `{boolean}` – Enables/disables keyboard event binding on non-interactive + * elements (such as `div` or `li`) using ng-click, making them more accessible to users of + * assistive technologies + * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements (such as + * `div` or `li`) using ng-click, making them more accessible to users of assistive + * technologies + * + * @description + * Enables/disables various ARIA attributes + */ + this.config = function(newConfig) { + config = angular.extend(config, newConfig); + }; + + function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { + return function(scope, elem, attr) { + var ariaCamelName = attr.$normalize(ariaAttr); + if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { + scope.$watch(attr[attrName], function(boolVal) { + // ensure boolean value + boolVal = negate ? !boolVal : !!boolVal; + elem.attr(ariaAttr, boolVal); + }); + } + }; + } + /** + * @ngdoc service + * @name $aria + * + * @description + * @priority 200 + * + * The $aria service contains helper methods for applying common + * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. + * + * ngAria injects common accessibility attributes that tell assistive technologies when HTML + * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, + * let's review a code snippet from ngAria itself: + * + *```js + * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { + * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); + * }]) + *``` + * Shown above, the ngAria module creates a directive with the same signature as the + * traditional `ng-disabled` directive. But this ngAria version is dedicated to + * solely managing accessibility attributes on custom elements. The internal `$aria` service is + * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the + * developer, `aria-disabled` is injected as an attribute with its value synchronized to the + * value in `ngDisabled`. + * + * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do + * anything to enable this feature. The `aria-disabled` attribute is automatically managed + * simply as a silent side-effect of using `ng-disabled` with the ngAria module. + * + * The full list of directives that interface with ngAria: + * * **ngModel** + * * **ngChecked** + * * **ngReadonly** + * * **ngRequired** + * * **ngDisabled** + * * **ngValue** + * * **ngShow** + * * **ngHide** + * * **ngClick** + * * **ngDblclick** + * * **ngMessages** + * + * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each + * directive. + * + * + * ## Dependencies + * Requires the {@link ngAria} module to be installed. + */ + this.$get = function() { + return { + config: function(key) { + return config[key]; + }, + $$watchExpr: watchExpr + }; + }; +} + + +ngAriaModule.directive('ngShow', ['$aria', function($aria) { + return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); +}]) +.directive('ngHide', ['$aria', function($aria) { + return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); +}]) +.directive('ngValue', ['$aria', function($aria) { + return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false); +}]) +.directive('ngChecked', ['$aria', function($aria) { + return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); +}]) +.directive('ngReadonly', ['$aria', function($aria) { + return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); +}]) +.directive('ngRequired', ['$aria', function($aria) { + return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); +}]) +.directive('ngModel', ['$aria', function($aria) { + + function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) { + return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)); + } + + function shouldAttachRole(role, elem) { + // if element does not have role attribute + // AND element type is equal to role (if custom element has a type equaling shape) <-- remove? + // AND element is not in nodeBlackList + return !elem.attr('role') && (elem.attr('type') === role) && !isNodeOneOf(elem, nodeBlackList); + } + + function getShape(attr, elem) { + var type = attr.type, + role = attr.role; + + return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : + ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : + (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : ''; + } + + return { + restrict: 'A', + require: 'ngModel', + priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value + compile: function(elem, attr) { + var shape = getShape(attr, elem); + + return { + post: function(scope, elem, attr, ngModel) { + var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); + + function ngAriaWatchModelValue() { + return ngModel.$modelValue; + } + + function getRadioReaction(newVal) { + // Strict comparison would cause a BC + // eslint-disable-next-line eqeqeq + var boolVal = (attr.value == ngModel.$viewValue); + elem.attr('aria-checked', boolVal); + } + + function getCheckboxReaction() { + elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); + } + + switch (shape) { + case 'radio': + case 'checkbox': + if (shouldAttachRole(shape, elem)) { + elem.attr('role', shape); + } + if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) { + scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? + getRadioReaction : getCheckboxReaction); + } + if (needsTabIndex) { + elem.attr('tabindex', 0); + } + break; + case 'range': + if (shouldAttachRole(shape, elem)) { + elem.attr('role', 'slider'); + } + if ($aria.config('ariaValue')) { + var needsAriaValuemin = !elem.attr('aria-valuemin') && + (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); + var needsAriaValuemax = !elem.attr('aria-valuemax') && + (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); + var needsAriaValuenow = !elem.attr('aria-valuenow'); + + if (needsAriaValuemin) { + attr.$observe('min', function ngAriaValueMinReaction(newVal) { + elem.attr('aria-valuemin', newVal); + }); + } + if (needsAriaValuemax) { + attr.$observe('max', function ngAriaValueMinReaction(newVal) { + elem.attr('aria-valuemax', newVal); + }); + } + if (needsAriaValuenow) { + scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { + elem.attr('aria-valuenow', newVal); + }); + } + } + if (needsTabIndex) { + elem.attr('tabindex', 0); + } + break; + } + + if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required + && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) { + // ngModel.$error.required is undefined on custom controls + attr.$observe('required', function() { + elem.attr('aria-required', !!attr['required']); + }); + } + + if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) { + scope.$watch(function ngAriaInvalidWatch() { + return ngModel.$invalid; + }, function ngAriaInvalidReaction(newVal) { + elem.attr('aria-invalid', !!newVal); + }); + } + } + }; + } + }; +}]) +.directive('ngDisabled', ['$aria', function($aria) { + return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); +}]) +.directive('ngMessages', function() { + return { + restrict: 'A', + require: '?ngMessages', + link: function(scope, elem, attr, ngMessages) { + if (!elem.attr('aria-live')) { + elem.attr('aria-live', 'assertive'); + } + } + }; +}) +.directive('ngClick',['$aria', '$parse', function($aria, $parse) { + return { + restrict: 'A', + compile: function(elem, attr) { + var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true); + return function(scope, elem, attr) { + + if (!isNodeOneOf(elem, nodeBlackList)) { + + if ($aria.config('bindRoleForClick') && !elem.attr('role')) { + elem.attr('role', 'button'); + } + + if ($aria.config('tabindex') && !elem.attr('tabindex')) { + elem.attr('tabindex', 0); + } + + if ($aria.config('bindKeydown') && !attr.ngKeydown && !attr.ngKeypress && !attr.ngKeyup) { + elem.on('keydown', function(event) { + var keyCode = event.which || event.keyCode; + if (keyCode === 32 || keyCode === 13) { + scope.$apply(callback); + } + + function callback() { + fn(scope, { $event: event }); + } + }); + } + } + }; + } + }; +}]) +.directive('ngDblclick', ['$aria', function($aria) { + return function(scope, elem, attr) { + if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { + elem.attr('tabindex', 0); + } + }; +}]); + + +})(window, window.angular); + +},{}],4:[function(require,module,exports){ +require('./angular-aria'); +module.exports = 'ngAria'; + +},{"./angular-aria":3}],5:[function(require,module,exports){ +/*! + * angular-loading-bar v0.9.0 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2016 Wes Cruver + * License: MIT + */ +/* + * angular-loading-bar + * + * intercepts XHR requests and creates a loading bar. + * Based on the excellent nprogress work by rstacruz (more info in readme) + * + * (c) 2013 Wes Cruver + * License: MIT + */ + + +(function() { + +'use strict'; + +// Alias the loading bar for various backwards compatibilities since the project has matured: +angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); +angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); + + +/** + * loadingBarInterceptor service + * + * Registers itself as an Angular interceptor and listens for XHR requests. + */ +angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) + .config(['$httpProvider', function ($httpProvider) { + + var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', '$log', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, $log, cfpLoadingBar) { + + /** + * The total number of requests made + */ + var reqsTotal = 0; + + /** + * The number of requests completed (either successfully or not) + */ + var reqsCompleted = 0; + + /** + * The amount of time spent fetching before showing the loading bar + */ + var latencyThreshold = cfpLoadingBar.latencyThreshold; + + /** + * $timeout handle for latencyThreshold + */ + var startTimeout; + + + /** + * calls cfpLoadingBar.complete() which removes the + * loading bar from the DOM. + */ + function setComplete() { + $timeout.cancel(startTimeout); + cfpLoadingBar.complete(); + reqsCompleted = 0; + reqsTotal = 0; + } + + /** + * Determine if the response has already been cached + * @param {Object} config the config option from the request + * @return {Boolean} retrns true if cached, otherwise false + */ + function isCached(config) { + var cache; + var defaultCache = $cacheFactory.get('$http'); + var defaults = $httpProvider.defaults; + + // Choose the proper cache source. Borrowed from angular: $http service + if ((config.cache || defaults.cache) && config.cache !== false && + (config.method === 'GET' || config.method === 'JSONP')) { + cache = angular.isObject(config.cache) ? config.cache + : angular.isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + var cached = cache !== undefined ? + cache.get(config.url) !== undefined : false; + + if (config.cached !== undefined && cached !== config.cached) { + return config.cached; + } + config.cached = cached; + return cached; + } + + + return { + 'request': function(config) { + // Check to make sure this request hasn't already been cached and that + // the requester didn't explicitly ask us to ignore this request: + if (!config.ignoreLoadingBar && !isCached(config)) { + $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); + if (reqsTotal === 0) { + startTimeout = $timeout(function() { + cfpLoadingBar.start(); + }, latencyThreshold); + } + reqsTotal++; + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + return config; + }, + + 'response': function(response) { + if (!response || !response.config) { + $log.error('Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); + return response; + } + + if (!response.config.ignoreLoadingBar && !isCached(response.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return response; + }, + + 'responseError': function(rejection) { + if (!rejection || !rejection.config) { + $log.error('Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); + return $q.reject(rejection); + } + + if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return $q.reject(rejection); + } + }; + }]; + + $httpProvider.interceptors.push(interceptor); + }]); + + +/** + * Loading Bar + * + * This service handles adding and removing the actual element in the DOM. + * Generally, best practices for DOM manipulation is to take place in a + * directive, but because the element itself is injected in the DOM only upon + * XHR requests, and it's likely needed on every view, the best option is to + * use a service. + */ +angular.module('cfp.loadingBar', []) + .provider('cfpLoadingBar', function() { + + this.autoIncrement = true; + this.includeSpinner = true; + this.includeBar = true; + this.latencyThreshold = 100; + this.startSize = 0.02; + this.parentSelector = 'body'; + this.spinnerTemplate = '
'; + this.loadingBarTemplate = '
'; + + this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { + var $animate; + var $parentSelector = this.parentSelector, + loadingBarContainer = angular.element(this.loadingBarTemplate), + loadingBar = loadingBarContainer.find('div').eq(0), + spinner = angular.element(this.spinnerTemplate); + + var incTimeout, + completeTimeout, + started = false, + status = 0; + + var autoIncrement = this.autoIncrement; + var includeSpinner = this.includeSpinner; + var includeBar = this.includeBar; + var startSize = this.startSize; + + /** + * Inserts the loading bar element into the dom, and sets it to 2% + */ + function _start() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + $timeout.cancel(completeTimeout); + + // do not continually broadcast the started event: + if (started) { + return; + } + + var document = $document[0]; + var parent = document.querySelector ? + document.querySelector($parentSelector) + : $document.find($parentSelector)[0] + ; + + if (! parent) { + parent = document.getElementsByTagName('body')[0]; + } + + var $parent = angular.element(parent); + var $after = parent.lastChild && angular.element(parent.lastChild); + + $rootScope.$broadcast('cfpLoadingBar:started'); + started = true; + + if (includeBar) { + $animate.enter(loadingBarContainer, $parent, $after); + } + + if (includeSpinner) { + $animate.enter(spinner, $parent, loadingBarContainer); + } + + _set(startSize); + } + + /** + * Set the loading bar's width to a certain percent. + * + * @param n any value between 0 and 1 + */ + function _set(n) { + if (!started) { + return; + } + var pct = (n * 100) + '%'; + loadingBar.css('width', pct); + status = n; + + // increment loadingbar to give the illusion that there is always + // progress but make sure to cancel the previous timeouts so we don't + // have multiple incs running at the same time. + if (autoIncrement) { + $timeout.cancel(incTimeout); + incTimeout = $timeout(function() { + _inc(); + }, 250); + } + } + + /** + * Increments the loading bar by a random amount + * but slows down as it progresses + */ + function _inc() { + if (_status() >= 1) { + return; + } + + var rnd = 0; + + // TODO: do this mathmatically instead of through conditions + + var stat = _status(); + if (stat >= 0 && stat < 0.25) { + // Start out between 3 - 6% increments + rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; + } else if (stat >= 0.25 && stat < 0.65) { + // increment between 0 - 3% + rnd = (Math.random() * 3) / 100; + } else if (stat >= 0.65 && stat < 0.9) { + // increment between 0 - 2% + rnd = (Math.random() * 2) / 100; + } else if (stat >= 0.9 && stat < 0.99) { + // finally, increment it .5 % + rnd = 0.005; + } else { + // after 99%, don't increment: + rnd = 0; + } + + var pct = _status() + rnd; + _set(pct); + } + + function _status() { + return status; + } + + function _completeAnimation() { + status = 0; + started = false; + } + + function _complete() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + $rootScope.$broadcast('cfpLoadingBar:completed'); + _set(1); + + $timeout.cancel(completeTimeout); + + // Attempt to aggregate any start/complete calls within 500ms: + completeTimeout = $timeout(function() { + var promise = $animate.leave(loadingBarContainer, _completeAnimation); + if (promise && promise.then) { + promise.then(_completeAnimation); + } + $animate.leave(spinner); + }, 500); + } + + return { + start : _start, + set : _set, + status : _status, + inc : _inc, + complete : _complete, + autoIncrement : this.autoIncrement, + includeSpinner : this.includeSpinner, + latencyThreshold : this.latencyThreshold, + parentSelector : this.parentSelector, + startSize : this.startSize + }; + + + }]; // + }); // wtf javascript. srsly +})(); // + +},{}],6:[function(require,module,exports){ +require('./build/loading-bar'); +module.exports = 'angular-loading-bar'; + +},{"./build/loading-bar":5}],7:[function(require,module,exports){ +/*! + * Angular Material Design + * https://github.com/angular/material + * @license MIT + * v1.1.1 + */ +(function( window, angular, undefined ){ +"use strict"; + +(function(){ +"use strict"; + +angular.module('ngMaterial', ["ng","ngAnimate","ngAria","material.core","material.core.gestures","material.core.layout","material.core.meta","material.core.theming.palette","material.core.theming","material.core.animate","material.components.autocomplete","material.components.backdrop","material.components.bottomSheet","material.components.button","material.components.card","material.components.chips","material.components.checkbox","material.components.colors","material.components.content","material.components.datepicker","material.components.dialog","material.components.divider","material.components.fabActions","material.components.fabShared","material.components.fabSpeedDial","material.components.fabToolbar","material.components.gridList","material.components.icon","material.components.input","material.components.list","material.components.menu","material.components.menuBar","material.components.navBar","material.components.panel","material.components.progressCircular","material.components.progressLinear","material.components.radioButton","material.components.select","material.components.showHide","material.components.sidenav","material.components.slider","material.components.sticky","material.components.subheader","material.components.swipe","material.components.switch","material.components.tabs","material.components.toast","material.components.toolbar","material.components.tooltip","material.components.virtualRepeat","material.components.whiteframe"]); +})(); +(function(){ +"use strict"; + +/** + * Initialization function that validates environment + * requirements. + */ +DetectNgTouch.$inject = ["$log", "$injector"]; +MdCoreConfigure.$inject = ["$provide", "$mdThemingProvider"]; +rAFDecorator.$inject = ["$delegate"]; +angular + .module('material.core', [ + 'ngAnimate', + 'material.core.animate', + 'material.core.layout', + 'material.core.gestures', + 'material.core.theming' + ]) + .config(MdCoreConfigure) + .run(DetectNgTouch); + + +/** + * Detect if the ng-Touch module is also being used. + * Warn if detected. + * @ngInject + */ +function DetectNgTouch($log, $injector) { + if ( $injector.has('$swipe') ) { + var msg = "" + + "You are using the ngTouch module. \n" + + "Angular Material already has mobile click, tap, and swipe support... \n" + + "ngTouch is not supported with Angular Material!"; + $log.warn(msg); + } +} + +/** + * @ngInject + */ +function MdCoreConfigure($provide, $mdThemingProvider) { + + $provide.decorator('$$rAF', ["$delegate", rAFDecorator]); + + $mdThemingProvider.theme('default') + .primaryPalette('indigo') + .accentPalette('pink') + .warnPalette('deep-orange') + .backgroundPalette('grey'); +} + +/** + * @ngInject + */ +function rAFDecorator($delegate) { + /** + * Use this to throttle events that come in often. + * The throttled function will always use the *last* invocation before the + * coming frame. + * + * For example, window resize events that fire many times a second: + * If we set to use an raf-throttled callback on window resize, then + * our callback will only be fired once per frame, with the last resize + * event that happened before that frame. + * + * @param {function} callback function to debounce + */ + $delegate.throttle = function(cb) { + var queuedArgs, alreadyQueued, queueCb, context; + return function debounced() { + queuedArgs = arguments; + context = this; + queueCb = cb; + if (!alreadyQueued) { + alreadyQueued = true; + $delegate(function() { + queueCb.apply(context, Array.prototype.slice.call(queuedArgs)); + alreadyQueued = false; + }); + } + }; + }; + return $delegate; +} + +})(); +(function(){ +"use strict"; + +angular.module('material.core') + .directive('mdAutofocus', MdAutofocusDirective) + + // Support the deprecated md-auto-focus and md-sidenav-focus as well + .directive('mdAutoFocus', MdAutofocusDirective) + .directive('mdSidenavFocus', MdAutofocusDirective); + +/** + * @ngdoc directive + * @name mdAutofocus + * @module material.core.util + * + * @description + * + * `[md-autofocus]` provides an optional way to identify the focused element when a `$mdDialog`, + * `$mdBottomSheet`, `$mdMenu` or `$mdSidenav` opens or upon page load for input-like elements. + * + * When one of these opens, it will find the first nested element with the `[md-autofocus]` + * attribute directive and optional expression. An expression may be specified as the directive + * value to enable conditional activation of the autofocus. + * + * @usage + * + * ### Dialog + * + * + *
+ * + * + * + * + *
+ *
+ *
+ * + * ### Bottomsheet + * + * + * Comment Actions + * + * + * + * + * + * {{ item.name }} + * + * + * + * + * + * + * + * ### Autocomplete + * + * + * {{item.display}} + * + * + * + * ### Sidenav + * + *
+ * + * Left Nav! + * + * + * + * Center Content + * + * Open Left Menu + * + * + * + * + *
+ * + * + * + * + *
+ *
+ *
+ *
+ **/ +function MdAutofocusDirective() { + return { + restrict: 'A', + + link: postLink + } +} + +function postLink(scope, element, attrs) { + var attr = attrs.mdAutoFocus || attrs.mdAutofocus || attrs.mdSidenavFocus; + + // Setup a watcher on the proper attribute to update a class we can check for in $mdUtil + scope.$watch(attr, function(canAutofocus) { + element.toggleClass('md-autofocus', canAutofocus); + }); +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.core.colorUtil + * @description + * Color Util + */ +angular + .module('material.core') + .factory('$mdColorUtil', ColorUtilFactory); + +function ColorUtilFactory() { + /** + * Converts hex value to RGBA string + * @param color {string} + * @returns {string} + */ + function hexToRgba (color) { + var hex = color[ 0 ] === '#' ? color.substr(1) : color, + dig = hex.length / 3, + red = hex.substr(0, dig), + green = hex.substr(dig, dig), + blue = hex.substr(dig * 2); + if (dig === 1) { + red += red; + green += green; + blue += blue; + } + return 'rgba(' + parseInt(red, 16) + ',' + parseInt(green, 16) + ',' + parseInt(blue, 16) + ',0.1)'; + } + + /** + * Converts rgba value to hex string + * @param color {string} + * @returns {string} + */ + function rgbaToHex(color) { + color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); + + var hex = (color && color.length === 4) ? "#" + + ("0" + parseInt(color[1],10).toString(16)).slice(-2) + + ("0" + parseInt(color[2],10).toString(16)).slice(-2) + + ("0" + parseInt(color[3],10).toString(16)).slice(-2) : ''; + + return hex.toUpperCase(); + } + + /** + * Converts an RGB color to RGBA + * @param color {string} + * @returns {string} + */ + function rgbToRgba (color) { + return color.replace(')', ', 0.1)').replace('(', 'a('); + } + + /** + * Converts an RGBA color to RGB + * @param color {string} + * @returns {string} + */ + function rgbaToRgb (color) { + return color + ? color.replace('rgba', 'rgb').replace(/,[^\),]+\)/, ')') + : 'rgb(0,0,0)'; + } + + return { + rgbaToHex: rgbaToHex, + hexToRgba: hexToRgba, + rgbToRgba: rgbToRgba, + rgbaToRgb: rgbaToRgb + } +} +})(); +(function(){ +"use strict"; + + +MdConstantFactory.$inject = ["$sniffer", "$window", "$document"];angular.module('material.core') +.factory('$mdConstant', MdConstantFactory); + +/** + * Factory function that creates the grab-bag $mdConstant service. + * @ngInject + */ +function MdConstantFactory($sniffer, $window, $document) { + + var vendorPrefix = $sniffer.vendorPrefix; + var isWebkit = /webkit/i.test(vendorPrefix); + var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g; + var prefixTestEl = document.createElement('div'); + + function vendorProperty(name) { + // Add a dash between the prefix and name, to be able to transform the string into camelcase. + var prefixedName = vendorPrefix + '-' + name; + var ucPrefix = camelCase(prefixedName); + var lcPrefix = ucPrefix.charAt(0).toLowerCase() + ucPrefix.substring(1); + + return hasStyleProperty(name) ? name : // The current browser supports the un-prefixed property + hasStyleProperty(ucPrefix) ? ucPrefix : // The current browser only supports the prefixed property. + hasStyleProperty(lcPrefix) ? lcPrefix : name; // Some browsers are only supporting the prefix in lowercase. + } + + function hasStyleProperty(property) { + return angular.isDefined(prefixTestEl.style[property]); + } + + function camelCase(input) { + return input.replace(SPECIAL_CHARS_REGEXP, function(matches, separator, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }); + } + + var self = { + isInputKey : function(e) { return (e.keyCode >= 31 && e.keyCode <= 90); }, + isNumPadKey : function (e){ return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); }, + isNavigationKey : function(e) { + var kc = self.KEY_CODE, NAVIGATION_KEYS = [kc.SPACE, kc.ENTER, kc.UP_ARROW, kc.DOWN_ARROW]; + return (NAVIGATION_KEYS.indexOf(e.keyCode) != -1); + }, + + KEY_CODE: { + COMMA: 188, + SEMICOLON : 186, + ENTER: 13, + ESCAPE: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT_ARROW : 37, + UP_ARROW : 38, + RIGHT_ARROW : 39, + DOWN_ARROW : 40, + TAB : 9, + BACKSPACE: 8, + DELETE: 46 + }, + CSS: { + /* Constants */ + TRANSITIONEND: 'transitionend' + (isWebkit ? ' webkitTransitionEnd' : ''), + ANIMATIONEND: 'animationend' + (isWebkit ? ' webkitAnimationEnd' : ''), + + TRANSFORM: vendorProperty('transform'), + TRANSFORM_ORIGIN: vendorProperty('transformOrigin'), + TRANSITION: vendorProperty('transition'), + TRANSITION_DURATION: vendorProperty('transitionDuration'), + ANIMATION_PLAY_STATE: vendorProperty('animationPlayState'), + ANIMATION_DURATION: vendorProperty('animationDuration'), + ANIMATION_NAME: vendorProperty('animationName'), + ANIMATION_TIMING: vendorProperty('animationTimingFunction'), + ANIMATION_DIRECTION: vendorProperty('animationDirection') + }, + /** + * As defined in core/style/variables.scss + * + * $layout-breakpoint-xs: 600px !default; + * $layout-breakpoint-sm: 960px !default; + * $layout-breakpoint-md: 1280px !default; + * $layout-breakpoint-lg: 1920px !default; + * + */ + MEDIA: { + 'xs' : '(max-width: 599px)' , + 'gt-xs' : '(min-width: 600px)' , + 'sm' : '(min-width: 600px) and (max-width: 959px)' , + 'gt-sm' : '(min-width: 960px)' , + 'md' : '(min-width: 960px) and (max-width: 1279px)' , + 'gt-md' : '(min-width: 1280px)' , + 'lg' : '(min-width: 1280px) and (max-width: 1919px)', + 'gt-lg' : '(min-width: 1920px)' , + 'xl' : '(min-width: 1920px)' , + 'landscape' : '(orientation: landscape)' , + 'portrait' : '(orientation: portrait)' , + 'print' : 'print' + }, + MEDIA_PRIORITY: [ + 'xl', + 'gt-lg', + 'lg', + 'gt-md', + 'md', + 'gt-sm', + 'sm', + 'gt-xs', + 'xs', + 'landscape', + 'portrait', + 'print' + ] + }; + + return self; +} + +})(); +(function(){ +"use strict"; + + angular + .module('material.core') + .config( ["$provide", function($provide){ + $provide.decorator('$mdUtil', ['$delegate', function ($delegate){ + /** + * Inject the iterator facade to easily support iteration and accessors + * @see iterator below + */ + $delegate.iterator = MdIterator; + + return $delegate; + } + ]); + }]); + + /** + * iterator is a list facade to easily support iteration and accessors + * + * @param items Array list which this iterator will enumerate + * @param reloop Boolean enables iterator to consider the list as an endless reloop + */ + function MdIterator(items, reloop) { + var trueFn = function() { return true; }; + + if (items && !angular.isArray(items)) { + items = Array.prototype.slice.call(items); + } + + reloop = !!reloop; + var _items = items || [ ]; + + // Published API + return { + items: getItems, + count: count, + + inRange: inRange, + contains: contains, + indexOf: indexOf, + itemAt: itemAt, + + findBy: findBy, + + add: add, + remove: remove, + + first: first, + last: last, + next: angular.bind(null, findSubsequentItem, false), + previous: angular.bind(null, findSubsequentItem, true), + + hasPrevious: hasPrevious, + hasNext: hasNext + + }; + + /** + * Publish copy of the enumerable set + * @returns {Array|*} + */ + function getItems() { + return [].concat(_items); + } + + /** + * Determine length of the list + * @returns {Array.length|*|number} + */ + function count() { + return _items.length; + } + + /** + * Is the index specified valid + * @param index + * @returns {Array.length|*|number|boolean} + */ + function inRange(index) { + return _items.length && ( index > -1 ) && (index < _items.length ); + } + + /** + * Can the iterator proceed to the next item in the list; relative to + * the specified item. + * + * @param item + * @returns {Array.length|*|number|boolean} + */ + function hasNext(item) { + return item ? inRange(indexOf(item) + 1) : false; + } + + /** + * Can the iterator proceed to the previous item in the list; relative to + * the specified item. + * + * @param item + * @returns {Array.length|*|number|boolean} + */ + function hasPrevious(item) { + return item ? inRange(indexOf(item) - 1) : false; + } + + /** + * Get item at specified index/position + * @param index + * @returns {*} + */ + function itemAt(index) { + return inRange(index) ? _items[index] : null; + } + + /** + * Find all elements matching the key/value pair + * otherwise return null + * + * @param val + * @param key + * + * @return array + */ + function findBy(key, val) { + return _items.filter(function(item) { + return item[key] === val; + }); + } + + /** + * Add item to list + * @param item + * @param index + * @returns {*} + */ + function add(item, index) { + if ( !item ) return -1; + + if (!angular.isNumber(index)) { + index = _items.length; + } + + _items.splice(index, 0, item); + + return indexOf(item); + } + + /** + * Remove item from list... + * @param item + */ + function remove(item) { + if ( contains(item) ){ + _items.splice(indexOf(item), 1); + } + } + + /** + * Get the zero-based index of the target item + * @param item + * @returns {*} + */ + function indexOf(item) { + return _items.indexOf(item); + } + + /** + * Boolean existence check + * @param item + * @returns {boolean} + */ + function contains(item) { + return item && (indexOf(item) > -1); + } + + /** + * Return first item in the list + * @returns {*} + */ + function first() { + return _items.length ? _items[0] : null; + } + + /** + * Return last item in the list... + * @returns {*} + */ + function last() { + return _items.length ? _items[_items.length - 1] : null; + } + + /** + * Find the next item. If reloop is true and at the end of the list, it will go back to the + * first item. If given, the `validate` callback will be used to determine whether the next item + * is valid. If not valid, it will try to find the next item again. + * + * @param {boolean} backwards Specifies the direction of searching (forwards/backwards) + * @param {*} item The item whose subsequent item we are looking for + * @param {Function=} validate The `validate` function + * @param {integer=} limit The recursion limit + * + * @returns {*} The subsequent item or null + */ + function findSubsequentItem(backwards, item, validate, limit) { + validate = validate || trueFn; + + var curIndex = indexOf(item); + while (true) { + if (!inRange(curIndex)) return null; + + var nextIndex = curIndex + (backwards ? -1 : 1); + var foundItem = null; + if (inRange(nextIndex)) { + foundItem = _items[nextIndex]; + } else if (reloop) { + foundItem = backwards ? last() : first(); + nextIndex = indexOf(foundItem); + } + + if ((foundItem === null) || (nextIndex === limit)) return null; + if (validate(foundItem)) return foundItem; + + if (angular.isUndefined(limit)) limit = nextIndex; + + curIndex = nextIndex; + } + } + } + + +})(); +(function(){ +"use strict"; + + +mdMediaFactory.$inject = ["$mdConstant", "$rootScope", "$window"];angular.module('material.core') +.factory('$mdMedia', mdMediaFactory); + +/** + * @ngdoc service + * @name $mdMedia + * @module material.core + * + * @description + * `$mdMedia` is used to evaluate whether a given media query is true or false given the + * current device's screen / window size. The media query will be re-evaluated on resize, allowing + * you to register a watch. + * + * `$mdMedia` also has pre-programmed support for media queries that match the layout breakpoints: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
BreakpointmediaQuery
xs(max-width: 599px)
gt-xs(min-width: 600px)
sm(min-width: 600px) and (max-width: 959px)
gt-sm(min-width: 960px)
md(min-width: 960px) and (max-width: 1279px)
gt-md(min-width: 1280px)
lg(min-width: 1280px) and (max-width: 1919px)
gt-lg(min-width: 1920px)
xl(min-width: 1920px)
landscapelandscape
portraitportrait
printprint
+ * + * See Material Design's Layout - Adaptive UI for more details. + * + * + * + * + * + * @returns {boolean} a boolean representing whether or not the given media query is true or false. + * + * @usage + * + * app.controller('MyController', function($mdMedia, $scope) { + * $scope.$watch(function() { return $mdMedia('lg'); }, function(big) { + * $scope.bigScreen = big; + * }); + * + * $scope.screenIsSmall = $mdMedia('sm'); + * $scope.customQuery = $mdMedia('(min-width: 1234px)'); + * $scope.anotherCustom = $mdMedia('max-width: 300px'); + * }); + * + */ + +/* @ngInject */ +function mdMediaFactory($mdConstant, $rootScope, $window) { + var queries = {}; + var mqls = {}; + var results = {}; + var normalizeCache = {}; + + $mdMedia.getResponsiveAttribute = getResponsiveAttribute; + $mdMedia.getQuery = getQuery; + $mdMedia.watchResponsiveAttributes = watchResponsiveAttributes; + + return $mdMedia; + + function $mdMedia(query) { + var validated = queries[query]; + if (angular.isUndefined(validated)) { + validated = queries[query] = validate(query); + } + + var result = results[validated]; + if (angular.isUndefined(result)) { + result = add(validated); + } + + return result; + } + + function validate(query) { + return $mdConstant.MEDIA[query] || + ((query.charAt(0) !== '(') ? ('(' + query + ')') : query); + } + + function add(query) { + var result = mqls[query]; + if ( !result ) { + result = mqls[query] = $window.matchMedia(query); + } + + result.addListener(onQueryChange); + return (results[result.media] = !!result.matches); + } + + function onQueryChange(query) { + $rootScope.$evalAsync(function() { + results[query.media] = !!query.matches; + }); + } + + function getQuery(name) { + return mqls[name]; + } + + function getResponsiveAttribute(attrs, attrName) { + for (var i = 0; i < $mdConstant.MEDIA_PRIORITY.length; i++) { + var mediaName = $mdConstant.MEDIA_PRIORITY[i]; + if (!mqls[queries[mediaName]].matches) { + continue; + } + + var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName); + if (attrs[normalizedName]) { + return attrs[normalizedName]; + } + } + + // fallback on unprefixed + return attrs[getNormalizedName(attrs, attrName)]; + } + + function watchResponsiveAttributes(attrNames, attrs, watchFn) { + var unwatchFns = []; + attrNames.forEach(function(attrName) { + var normalizedName = getNormalizedName(attrs, attrName); + if (angular.isDefined(attrs[normalizedName])) { + unwatchFns.push( + attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null))); + } + + for (var mediaName in $mdConstant.MEDIA) { + normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName); + if (angular.isDefined(attrs[normalizedName])) { + unwatchFns.push( + attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName))); + } + } + }); + + return function unwatch() { + unwatchFns.forEach(function(fn) { fn(); }) + }; + } + + // Improves performance dramatically + function getNormalizedName(attrs, attrName) { + return normalizeCache[attrName] || + (normalizeCache[attrName] = attrs.$normalize(attrName)); + } +} + +})(); +(function(){ +"use strict"; + +angular + .module('material.core') + .config( ["$provide", function($provide) { + $provide.decorator('$mdUtil', ['$delegate', function ($delegate) { + + // Inject the prefixer into our original $mdUtil service. + $delegate.prefixer = MdPrefixer; + + return $delegate; + }]); + }]); + +function MdPrefixer(initialAttributes, buildSelector) { + var PREFIXES = ['data', 'x']; + + if (initialAttributes) { + // The prefixer also accepts attributes as a parameter, and immediately builds a list or selector for + // the specified attributes. + return buildSelector ? _buildSelector(initialAttributes) : _buildList(initialAttributes); + } + + return { + buildList: _buildList, + buildSelector: _buildSelector, + hasAttribute: _hasAttribute, + removeAttribute: _removeAttribute + }; + + function _buildList(attributes) { + attributes = angular.isArray(attributes) ? attributes : [attributes]; + + attributes.forEach(function(item) { + PREFIXES.forEach(function(prefix) { + attributes.push(prefix + '-' + item); + }); + }); + + return attributes; + } + + function _buildSelector(attributes) { + attributes = angular.isArray(attributes) ? attributes : [attributes]; + + return _buildList(attributes) + .map(function(item) { + return '[' + item + ']' + }) + .join(','); + } + + function _hasAttribute(element, attribute) { + element = _getNativeElement(element); + + if (!element) { + return false; + } + + var prefixedAttrs = _buildList(attribute); + + for (var i = 0; i < prefixedAttrs.length; i++) { + if (element.hasAttribute(prefixedAttrs[i])) { + return true; + } + } + + return false; + } + + function _removeAttribute(element, attribute) { + element = _getNativeElement(element); + + if (!element) { + return; + } + + _buildList(attribute).forEach(function(prefixedAttribute) { + element.removeAttribute(prefixedAttribute); + }); + } + + /** + * Transforms a jqLite or DOM element into a HTML element. + * This is useful when supporting jqLite elements and DOM elements at + * same time. + * @param element {JQLite|Element} Element to be parsed + * @returns {HTMLElement} Parsed HTMLElement + */ + function _getNativeElement(element) { + element = element[0] || element; + + if (element.nodeType) { + return element; + } + } + +} +})(); +(function(){ +"use strict"; + +/* + * This var has to be outside the angular factory, otherwise when + * there are multiple material apps on the same page, each app + * will create its own instance of this array and the app's IDs + * will not be unique. + */ +UtilFactory.$inject = ["$document", "$timeout", "$compile", "$rootScope", "$$mdAnimate", "$interpolate", "$log", "$rootElement", "$window", "$$rAF"]; +var nextUniqueId = 0; + +/** + * @ngdoc module + * @name material.core.util + * @description + * Util + */ +angular + .module('material.core') + .factory('$mdUtil', UtilFactory); + +/** + * @ngInject + */ +function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $interpolate, $log, $rootElement, $window, $$rAF) { + // Setup some core variables for the processTemplate method + var startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + usesStandardSymbols = ((startSymbol === '{{') && (endSymbol === '}}')); + + /** + * Checks if the target element has the requested style by key + * @param {DOMElement|JQLite} target Target element + * @param {string} key Style key + * @param {string=} expectedVal Optional expected value + * @returns {boolean} Whether the target element has the style or not + */ + var hasComputedStyle = function (target, key, expectedVal) { + var hasValue = false; + + if ( target && target.length ) { + var computedStyles = $window.getComputedStyle(target[0]); + hasValue = angular.isDefined(computedStyles[key]) && (expectedVal ? computedStyles[key] == expectedVal : true); + } + + return hasValue; + }; + + function validateCssValue(value) { + return !value ? '0' : + hasPx(value) || hasPercent(value) ? value : value + 'px'; + } + + function hasPx(value) { + return String(value).indexOf('px') > -1; + } + + function hasPercent(value) { + return String(value).indexOf('%') > -1; + + } + + var $mdUtil = { + dom: {}, + now: window.performance ? + angular.bind(window.performance, window.performance.now) : Date.now || function() { + return new Date().getTime(); + }, + + /** + * Bi-directional accessor/mutator used to easily update an element's + * property based on the current 'dir'ectional value. + */ + bidi : function(element, property, lValue, rValue) { + var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl'); + + // If accessor + if ( arguments.length == 0 ) return ltr ? 'ltr' : 'rtl'; + + // If mutator + var elem = angular.element(element); + + if ( ltr && angular.isDefined(lValue)) { + elem.css(property, validateCssValue(lValue)); + } + else if ( !ltr && angular.isDefined(rValue)) { + elem.css(property, validateCssValue(rValue) ); + } + }, + + bidiProperty: function (element, lProperty, rProperty, value) { + var ltr = !($document[0].dir == 'rtl' || $document[0].body.dir == 'rtl'); + + var elem = angular.element(element); + + if ( ltr && angular.isDefined(lProperty)) { + elem.css(lProperty, validateCssValue(value)); + elem.css(rProperty, ''); + } + else if ( !ltr && angular.isDefined(rProperty)) { + elem.css(rProperty, validateCssValue(value) ); + elem.css(lProperty, ''); + } + }, + + clientRect: function(element, offsetParent, isOffsetRect) { + var node = getNode(element); + offsetParent = getNode(offsetParent || node.offsetParent || document.body); + var nodeRect = node.getBoundingClientRect(); + + // The user can ask for an offsetRect: a rect relative to the offsetParent, + // or a clientRect: a rect relative to the page + var offsetRect = isOffsetRect ? + offsetParent.getBoundingClientRect() : + {left: 0, top: 0, width: 0, height: 0}; + return { + left: nodeRect.left - offsetRect.left, + top: nodeRect.top - offsetRect.top, + width: nodeRect.width, + height: nodeRect.height + }; + }, + offsetRect: function(element, offsetParent) { + return $mdUtil.clientRect(element, offsetParent, true); + }, + + // Annoying method to copy nodes to an array, thanks to IE + nodesToArray: function(nodes) { + nodes = nodes || []; + + var results = []; + for (var i = 0; i < nodes.length; ++i) { + results.push(nodes.item(i)); + } + return results; + }, + + /** + * Calculate the positive scroll offset + * TODO: Check with pinch-zoom in IE/Chrome; + * https://code.google.com/p/chromium/issues/detail?id=496285 + */ + scrollTop: function(element) { + element = angular.element(element || $document[0].body); + + var body = (element[0] == $document[0].body) ? $document[0].body : undefined; + var scrollTop = body ? body.scrollTop + body.parentElement.scrollTop : 0; + + // Calculate the positive scroll offset + return scrollTop || Math.abs(element[0].getBoundingClientRect().top); + }, + + /** + * Finds the proper focus target by searching the DOM. + * + * @param containerEl + * @param attributeVal + * @returns {*} + */ + findFocusTarget: function(containerEl, attributeVal) { + var AUTO_FOCUS = this.prefixer('md-autofocus', true); + var elToFocus; + + elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS); + + if ( !elToFocus && attributeVal != AUTO_FOCUS) { + // Scan for deprecated attribute + elToFocus = scanForFocusable(containerEl, this.prefixer('md-auto-focus', true)); + + if ( !elToFocus ) { + // Scan for fallback to 'universal' API + elToFocus = scanForFocusable(containerEl, AUTO_FOCUS); + } + } + + return elToFocus; + + /** + * Can target and nested children for specified Selector (attribute) + * whose value may be an expression that evaluates to True/False. + */ + function scanForFocusable(target, selector) { + var elFound, items = target[0].querySelectorAll(selector); + + // Find the last child element with the focus attribute + if ( items && items.length ){ + items.length && angular.forEach(items, function(it) { + it = angular.element(it); + + // Check the element for the md-autofocus class to ensure any associated expression + // evaluated to true. + var isFocusable = it.hasClass('md-autofocus'); + if (isFocusable) elFound = it; + }); + } + return elFound; + } + }, + + /** + * Disables scroll around the passed parent element. + * @param element Unused + * @param {!Element|!angular.JQLite} parent Element to disable scrolling within. + * Defaults to body if none supplied. + * @param options Object of options to modify functionality + * - disableScrollMask Boolean of whether or not to create a scroll mask element or + * use the passed parent element. + */ + disableScrollAround: function(element, parent, options) { + $mdUtil.disableScrollAround._count = $mdUtil.disableScrollAround._count || 0; + ++$mdUtil.disableScrollAround._count; + if ($mdUtil.disableScrollAround._enableScrolling) return $mdUtil.disableScrollAround._enableScrolling; + var body = $document[0].body, + restoreBody = disableBodyScroll(), + restoreElement = disableElementScroll(parent); + + return $mdUtil.disableScrollAround._enableScrolling = function() { + if (!--$mdUtil.disableScrollAround._count) { + restoreBody(); + restoreElement(); + delete $mdUtil.disableScrollAround._enableScrolling; + } + }; + + // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events + function disableElementScroll(element) { + element = angular.element(element || body); + var scrollMask; + if (options && options.disableScrollMask) { + scrollMask = element; + } else { + element = element[0]; + scrollMask = angular.element( + '
' + + '
' + + '
'); + element.appendChild(scrollMask[0]); + } + + scrollMask.on('wheel', preventDefault); + scrollMask.on('touchmove', preventDefault); + + return function restoreScroll() { + scrollMask.off('wheel'); + scrollMask.off('touchmove'); + scrollMask[0].parentNode.removeChild(scrollMask[0]); + delete $mdUtil.disableScrollAround._enableScrolling; + }; + + function preventDefault(e) { + e.preventDefault(); + } + } + + // Converts the body to a position fixed block and translate it to the proper scroll position + function disableBodyScroll() { + var htmlNode = body.parentNode; + var restoreHtmlStyle = htmlNode.style.cssText || ''; + var restoreBodyStyle = body.style.cssText || ''; + var scrollOffset = $mdUtil.scrollTop(body); + var clientWidth = body.clientWidth; + + if (body.scrollHeight > body.clientHeight + 1) { + applyStyles(body, { + position: 'fixed', + width: '100%', + top: -scrollOffset + 'px' + }); + + htmlNode.style.overflowY = 'scroll'; + } + + if (body.clientWidth < clientWidth) applyStyles(body, {overflow: 'hidden'}); + + return function restoreScroll() { + body.style.cssText = restoreBodyStyle; + htmlNode.style.cssText = restoreHtmlStyle; + body.scrollTop = scrollOffset; + htmlNode.scrollTop = scrollOffset; + }; + } + + function applyStyles(el, styles) { + for (var key in styles) { + el.style[key] = styles[key]; + } + } + }, + enableScrolling: function() { + var method = this.disableScrollAround._enableScrolling; + method && method(); + }, + floatingScrollbars: function() { + if (this.floatingScrollbars.cached === undefined) { + var tempNode = angular.element('
').css({ + width: '100%', + 'z-index': -1, + position: 'absolute', + height: '35px', + 'overflow-y': 'scroll' + }); + tempNode.children().css('height', '60px'); + + $document[0].body.appendChild(tempNode[0]); + this.floatingScrollbars.cached = (tempNode[0].offsetWidth == tempNode[0].childNodes[0].offsetWidth); + tempNode.remove(); + } + return this.floatingScrollbars.cached; + }, + + // Mobile safari only allows you to set focus in click event listeners... + forceFocus: function(element) { + var node = element[0] || element; + + document.addEventListener('click', function focusOnClick(ev) { + if (ev.target === node && ev.$focus) { + node.focus(); + ev.stopImmediatePropagation(); + ev.preventDefault(); + node.removeEventListener('click', focusOnClick); + } + }, true); + + var newEvent = document.createEvent('MouseEvents'); + newEvent.initMouseEvent('click', false, true, window, {}, 0, 0, 0, 0, + false, false, false, false, 0, null); + newEvent.$material = true; + newEvent.$focus = true; + node.dispatchEvent(newEvent); + }, + + /** + * facade to build md-backdrop element with desired styles + * NOTE: Use $compile to trigger backdrop postLink function + */ + createBackdrop: function(scope, addClass) { + return $compile($mdUtil.supplant('', [addClass]))(scope); + }, + + /** + * supplant() method from Crockford's `Remedial Javascript` + * Equivalent to use of $interpolate; without dependency on + * interpolation symbols and scope. Note: the '{}' can + * be property names, property chains, or array indices. + */ + supplant: function(template, values, pattern) { + pattern = pattern || /\{([^\{\}]*)\}/g; + return template.replace(pattern, function(a, b) { + var p = b.split('.'), + r = values; + try { + for (var s in p) { + if (p.hasOwnProperty(s) ) { + r = r[p[s]]; + } + } + } catch (e) { + r = a; + } + return (typeof r === 'string' || typeof r === 'number') ? r : a; + }); + }, + + fakeNgModel: function() { + return { + $fake: true, + $setTouched: angular.noop, + $setViewValue: function(value) { + this.$viewValue = value; + this.$render(value); + this.$viewChangeListeners.forEach(function(cb) { + cb(); + }); + }, + $isEmpty: function(value) { + return ('' + value).length === 0; + }, + $parsers: [], + $formatters: [], + $viewChangeListeners: [], + $render: angular.noop + }; + }, + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. + // @param wait Integer value of msecs to delay (since last debounce reset); default value 10 msecs + // @param invokeApply should the $timeout trigger $digest() dirty checking + debounce: function(func, wait, scope, invokeApply) { + var timer; + + return function debounced() { + var context = scope, + args = Array.prototype.slice.call(arguments); + + $timeout.cancel(timer); + timer = $timeout(function() { + + timer = undefined; + func.apply(context, args); + + }, wait || 10, invokeApply); + }; + }, + + // Returns a function that can only be triggered every `delay` milliseconds. + // In other words, the function will not be called unless it has been more + // than `delay` milliseconds since the last call. + throttle: function throttle(func, delay) { + var recent; + return function throttled() { + var context = this; + var args = arguments; + var now = $mdUtil.now(); + + if (!recent || (now - recent > delay)) { + func.apply(context, args); + recent = now; + } + }; + }, + + /** + * Measures the number of milliseconds taken to run the provided callback + * function. Uses a high-precision timer if available. + */ + time: function time(cb) { + var start = $mdUtil.now(); + cb(); + return $mdUtil.now() - start; + }, + + /** + * Create an implicit getter that caches its `getter()` + * lookup value + */ + valueOnUse : function (scope, key, getter) { + var value = null, args = Array.prototype.slice.call(arguments); + var params = (args.length > 3) ? args.slice(3) : [ ]; + + Object.defineProperty(scope, key, { + get: function () { + if (value === null) value = getter.apply(scope, params); + return value; + } + }); + }, + + /** + * Get a unique ID. + * + * @returns {string} an unique numeric string + */ + nextUid: function() { + return '' + nextUniqueId++; + }, + + // Stop watchers and events from firing on a scope without destroying it, + // by disconnecting it from its parent and its siblings' linked lists. + disconnectScope: function disconnectScope(scope) { + if (!scope) return; + + // we can't destroy the root scope or a scope that has been already destroyed + if (scope.$root === scope) return; + if (scope.$$destroyed) return; + + var parent = scope.$parent; + scope.$$disconnected = true; + + // See Scope.$destroy + if (parent.$$childHead === scope) parent.$$childHead = scope.$$nextSibling; + if (parent.$$childTail === scope) parent.$$childTail = scope.$$prevSibling; + if (scope.$$prevSibling) scope.$$prevSibling.$$nextSibling = scope.$$nextSibling; + if (scope.$$nextSibling) scope.$$nextSibling.$$prevSibling = scope.$$prevSibling; + + scope.$$nextSibling = scope.$$prevSibling = null; + + }, + + // Undo the effects of disconnectScope above. + reconnectScope: function reconnectScope(scope) { + if (!scope) return; + + // we can't disconnect the root node or scope already disconnected + if (scope.$root === scope) return; + if (!scope.$$disconnected) return; + + var child = scope; + + var parent = child.$parent; + child.$$disconnected = false; + // See Scope.$new for this logic... + child.$$prevSibling = parent.$$childTail; + if (parent.$$childHead) { + parent.$$childTail.$$nextSibling = child; + parent.$$childTail = child; + } else { + parent.$$childHead = parent.$$childTail = child; + } + }, + + /* + * getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName + * + * @param el Element to start walking the DOM from + * @param check Either a string or a function. If a string is passed, it will be evaluated against + * each of the parent nodes' tag name. If a function is passed, the loop will call it with each of + * the parents and will use the return value to determine whether the node is a match. + * @param onlyParent Only start checking from the parent element, not `el`. + */ + getClosest: function getClosest(el, validateWith, onlyParent) { + if ( angular.isString(validateWith) ) { + var tagName = validateWith.toUpperCase(); + validateWith = function(el) { + return el.nodeName === tagName; + }; + } + + if (el instanceof angular.element) el = el[0]; + if (onlyParent) el = el.parentNode; + if (!el) return null; + + do { + if (validateWith(el)) { + return el; + } + } while (el = el.parentNode); + + return null; + }, + + /** + * Build polyfill for the Node.contains feature (if needed) + */ + elementContains: function(node, child) { + var hasContains = (window.Node && window.Node.prototype && Node.prototype.contains); + var findFn = hasContains ? angular.bind(node, node.contains) : angular.bind(node, function(arg) { + // compares the positions of two nodes and returns a bitmask + return (node === child) || !!(this.compareDocumentPosition(arg) & 16) + }); + + return findFn(child); + }, + + /** + * Functional equivalent for $element.filter(‘md-bottom-sheet’) + * useful with interimElements where the element and its container are important... + * + * @param {[]} elements to scan + * @param {string} name of node to find (e.g. 'md-dialog') + * @param {boolean=} optional flag to allow deep scans; defaults to 'false'. + * @param {boolean=} optional flag to enable log warnings; defaults to false + */ + extractElementByName: function(element, nodeName, scanDeep, warnNotFound) { + var found = scanTree(element); + if (!found && !!warnNotFound) { + $log.warn( $mdUtil.supplant("Unable to find node '{0}' in element '{1}'.",[nodeName, element[0].outerHTML]) ); + } + + return angular.element(found || element); + + /** + * Breadth-First tree scan for element with matching `nodeName` + */ + function scanTree(element) { + return scanLevel(element) || (!!scanDeep ? scanChildren(element) : null); + } + + /** + * Case-insensitive scan of current elements only (do not descend). + */ + function scanLevel(element) { + if ( element ) { + for (var i = 0, len = element.length; i < len; i++) { + if (element[i].nodeName.toLowerCase() === nodeName) { + return element[i]; + } + } + } + return null; + } + + /** + * Scan children of specified node + */ + function scanChildren(element) { + var found; + if ( element ) { + for (var i = 0, len = element.length; i < len; i++) { + var target = element[i]; + if ( !found ) { + for (var j = 0, numChild = target.childNodes.length; j < numChild; j++) { + found = found || scanTree([target.childNodes[j]]); + } + } + } + } + return found; + } + + }, + + /** + * Give optional properties with no value a boolean true if attr provided or false otherwise + */ + initOptionalProperties: function(scope, attr, defaults) { + defaults = defaults || {}; + angular.forEach(scope.$$isolateBindings, function(binding, key) { + if (binding.optional && angular.isUndefined(scope[key])) { + var attrIsDefined = angular.isDefined(attr[binding.attrName]); + scope[key] = angular.isDefined(defaults[key]) ? defaults[key] : attrIsDefined; + } + }); + }, + + /** + * Alternative to $timeout calls with 0 delay. + * nextTick() coalesces all calls within a single frame + * to minimize $digest thrashing + * + * @param callback + * @param digest + * @returns {*} + */ + nextTick: function(callback, digest, scope) { + //-- grab function reference for storing state details + var nextTick = $mdUtil.nextTick; + var timeout = nextTick.timeout; + var queue = nextTick.queue || []; + + //-- add callback to the queue + queue.push({scope: scope, callback: callback}); + + //-- set default value for digest + if (digest == null) digest = true; + + //-- store updated digest/queue values + nextTick.digest = nextTick.digest || digest; + nextTick.queue = queue; + + //-- either return existing timeout or create a new one + return timeout || (nextTick.timeout = $timeout(processQueue, 0, false)); + + /** + * Grab a copy of the current queue + * Clear the queue for future use + * Process the existing queue + * Trigger digest if necessary + */ + function processQueue() { + var queue = nextTick.queue; + var digest = nextTick.digest; + + nextTick.queue = []; + nextTick.timeout = null; + nextTick.digest = false; + + queue.forEach(function(queueItem) { + var skip = queueItem.scope && queueItem.scope.$$destroyed; + if (!skip) { + queueItem.callback(); + } + }); + + if (digest) $rootScope.$digest(); + } + }, + + /** + * Processes a template and replaces the start/end symbols if the application has + * overriden them. + * + * @param template The template to process whose start/end tags may be replaced. + * @returns {*} + */ + processTemplate: function(template) { + if (usesStandardSymbols) { + return template; + } else { + if (!template || !angular.isString(template)) return template; + return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + } + }, + + /** + * Scan up dom hierarchy for enabled parent; + */ + getParentWithPointerEvents: function (element) { + var parent = element.parent(); + + // jqLite might return a non-null, but still empty, parent; so check for parent and length + while (hasComputedStyle(parent, 'pointer-events', 'none')) { + parent = parent.parent(); + } + + return parent; + }, + + getNearestContentElement: function (element) { + var current = element.parent()[0]; + // Look for the nearest parent md-content, stopping at the rootElement. + while (current && current !== $rootElement[0] && current !== document.body && current.nodeName.toUpperCase() !== 'MD-CONTENT') { + current = current.parentNode; + } + return current; + }, + + /** + * Checks if the current browser is natively supporting the `sticky` position. + * @returns {string} supported sticky property name + */ + checkStickySupport: function() { + var stickyProp; + var testEl = angular.element('
'); + $document[0].body.appendChild(testEl[0]); + + var stickyProps = ['sticky', '-webkit-sticky']; + for (var i = 0; i < stickyProps.length; ++i) { + testEl.css({ + position: stickyProps[i], + top: 0, + 'z-index': 2 + }); + + if (testEl.css('position') == stickyProps[i]) { + stickyProp = stickyProps[i]; + break; + } + } + + testEl.remove(); + + return stickyProp; + }, + + /** + * Parses an attribute value, mostly a string. + * By default checks for negated values and returns `false´ if present. + * Negated values are: (native falsy) and negative strings like: + * `false` or `0`. + * @param value Attribute value which should be parsed. + * @param negatedCheck When set to false, won't check for negated values. + * @returns {boolean} + */ + parseAttributeBoolean: function(value, negatedCheck) { + return value === '' || !!value && (negatedCheck === false || value !== 'false' && value !== '0'); + }, + + hasComputedStyle: hasComputedStyle, + + /** + * Returns true if the parent form of the element has been submitted. + * + * @param element An Angular or HTML5 element. + * + * @returns {boolean} + */ + isParentFormSubmitted: function(element) { + var parent = $mdUtil.getClosest(element, 'form'); + var form = parent ? angular.element(parent).controller('form') : null; + + return form ? form.$submitted : false; + }, + + /** + * Animate the requested element's scrollTop to the requested scrollPosition with basic easing. + * + * @param element The element to scroll. + * @param scrollEnd The new/final scroll position. + */ + animateScrollTo: function(element, scrollEnd) { + var scrollStart = element.scrollTop; + var scrollChange = scrollEnd - scrollStart; + var scrollingDown = scrollStart < scrollEnd; + var startTime = $mdUtil.now(); + + $$rAF(scrollChunk); + + function scrollChunk() { + var newPosition = calculateNewPosition(); + + element.scrollTop = newPosition; + + if (scrollingDown ? newPosition < scrollEnd : newPosition > scrollEnd) { + $$rAF(scrollChunk); + } + } + + function calculateNewPosition() { + var duration = 1000; + var currentTime = $mdUtil.now() - startTime; + + return ease(currentTime, scrollStart, scrollChange, duration); + } + + function ease(currentTime, start, change, duration) { + // If the duration has passed (which can occur if our app loses focus due to $$rAF), jump + // straight to the proper position + if (currentTime > duration) { + return start + change; + } + + var ts = (currentTime /= duration) * currentTime; + var tc = ts * currentTime; + + return start + change * (-2 * tc + 3 * ts); + } + } + }; + + +// Instantiate other namespace utility methods + + $mdUtil.dom.animator = $$mdAnimate($mdUtil); + + return $mdUtil; + + function getNode(el) { + return el[0] || el; + } + +} + +/* + * Since removing jQuery from the demos, some code that uses `element.focus()` is broken. + * We need to add `element.focus()`, because it's testable unlike `element[0].focus`. + */ + +angular.element.prototype.focus = angular.element.prototype.focus || function() { + if (this.length) { + this[0].focus(); + } + return this; + }; +angular.element.prototype.blur = angular.element.prototype.blur || function() { + if (this.length) { + this[0].blur(); + } + return this; + }; + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.core.aria + * @description + * Aria Expectations for ngMaterial components. + */ +MdAriaService.$inject = ["$$rAF", "$log", "$window", "$interpolate"]; +angular + .module('material.core') + .provider('$mdAria', MdAriaProvider); + +/** + * @ngdoc service + * @name $mdAriaProvider + * @module material.core.aria + * + * @description + * + * Modify options of the `$mdAria` service, which will be used by most of the Angular Material + * components. + * + * You are able to disable `$mdAria` warnings, by using the following markup. + * + * + * app.config(function($mdAriaProvider) { + * // Globally disables all ARIA warnings. + * $mdAriaProvider.disableWarnings(); + * }); + * + * + */ +function MdAriaProvider() { + + var self = this; + + /** + * Whether we should show ARIA warnings in the console, if labels are missing on the element + * By default the warnings are enabled + */ + self.showWarnings = true; + + return { + disableWarnings: disableWarnings, + $get: ["$$rAF", "$log", "$window", "$interpolate", function($$rAF, $log, $window, $interpolate) { + return MdAriaService.apply(self, arguments); + }] + }; + + /** + * @ngdoc method + * @name $mdAriaProvider#disableWarnings + * @description Disables all ARIA warnings generated by Angular Material. + */ + function disableWarnings() { + self.showWarnings = false; + } +} + +/* + * @ngInject + */ +function MdAriaService($$rAF, $log, $window, $interpolate) { + + // Load the showWarnings option from the current context and store it inside of a scope variable, + // because the context will be probably lost in some function calls. + var showWarnings = this.showWarnings; + + return { + expect: expect, + expectAsync: expectAsync, + expectWithText: expectWithText, + expectWithoutText: expectWithoutText + }; + + /** + * Check if expected attribute has been specified on the target element or child + * @param element + * @param attrName + * @param {optional} defaultValue What to set the attr to if no value is found + */ + function expect(element, attrName, defaultValue) { + + var node = angular.element(element)[0] || element; + + // if node exists and neither it nor its children have the attribute + if (node && + ((!node.hasAttribute(attrName) || + node.getAttribute(attrName).length === 0) && + !childHasAttribute(node, attrName))) { + + defaultValue = angular.isString(defaultValue) ? defaultValue.trim() : ''; + if (defaultValue.length) { + element.attr(attrName, defaultValue); + } else if (showWarnings) { + $log.warn('ARIA: Attribute "', attrName, '", required for accessibility, is missing on node:', node); + } + + } + } + + function expectAsync(element, attrName, defaultValueGetter) { + // Problem: when retrieving the element's contents synchronously to find the label, + // the text may not be defined yet in the case of a binding. + // There is a higher chance that a binding will be defined if we wait one frame. + $$rAF(function() { + expect(element, attrName, defaultValueGetter()); + }); + } + + function expectWithText(element, attrName) { + var content = getText(element) || ""; + var hasBinding = content.indexOf($interpolate.startSymbol()) > -1; + + if ( hasBinding ) { + expectAsync(element, attrName, function() { + return getText(element); + }); + } else { + expect(element, attrName, content); + } + } + + function expectWithoutText(element, attrName) { + var content = getText(element); + var hasBinding = content.indexOf($interpolate.startSymbol()) > -1; + + if ( !hasBinding && !content) { + expect(element, attrName, content); + } + } + + function getText(element) { + element = element[0] || element; + var walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false); + var text = ''; + + var node; + while (node = walker.nextNode()) { + if (!isAriaHiddenNode(node)) { + text += node.textContent; + } + } + + return text.trim() || ''; + + function isAriaHiddenNode(node) { + while (node.parentNode && (node = node.parentNode) !== element) { + if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') { + return true; + } + } + } + } + + function childHasAttribute(node, attrName) { + var hasChildren = node.hasChildNodes(), + hasAttr = false; + + function isHidden(el) { + var style = el.currentStyle ? el.currentStyle : $window.getComputedStyle(el); + return (style.display === 'none'); + } + + if (hasChildren) { + var children = node.childNodes; + for (var i=0; i < children.length; i++) { + var child = children[i]; + if (child.nodeType === 1 && child.hasAttribute(attrName)) { + if (!isHidden(child)) { + hasAttr = true; + } + } + } + } + + return hasAttr; + } +} + +})(); +(function(){ +"use strict"; + + +mdCompilerService.$inject = ["$q", "$templateRequest", "$injector", "$compile", "$controller"];angular + .module('material.core') + .service('$mdCompiler', mdCompilerService); + +function mdCompilerService($q, $templateRequest, $injector, $compile, $controller) { + /* jshint validthis: true */ + + /* + * @ngdoc service + * @name $mdCompiler + * @module material.core + * @description + * The $mdCompiler service is an abstraction of angular's compiler, that allows the developer + * to easily compile an element with a templateUrl, controller, and locals. + * + * @usage + * + * $mdCompiler.compile({ + * templateUrl: 'modal.html', + * controller: 'ModalCtrl', + * locals: { + * modal: myModalInstance; + * } + * }).then(function(compileData) { + * compileData.element; // modal.html's template in an element + * compileData.link(myScope); //attach controller & scope to element + * }); + * + */ + + /* + * @ngdoc method + * @name $mdCompiler#compile + * @description A helper to compile an HTML template/templateUrl with a given controller, + * locals, and scope. + * @param {object} options An options object, with the following properties: + * + * - `controller` - `{(string=|function()=}` Controller fn that should be associated with + * newly created scope or the name of a registered controller if passed as a string. + * - `controllerAs` - `{string=}` A controller alias name. If present the controller will be + * published to scope under the `controllerAs` name. + * - `template` - `{string=}` An html template as a string. + * - `templateUrl` - `{string=}` A path to an html template. + * - `transformTemplate` - `{function(template)=}` A function which transforms the template after + * it is loaded. It will be given the template string as a parameter, and should + * return a a new string representing the transformed template. + * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, the compiler + * will wait for them all to be resolved, or if one is rejected before the controller is + * instantiated `compile()` will fail.. + * * `key` - `{string}`: a name of a dependency to be injected into the controller. + * * `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is injected and the return value is treated as the + * dependency. If the result is a promise, it is resolved before its value is + * injected into the controller. + * + * @returns {object=} promise A promise, which will be resolved with a `compileData` object. + * `compileData` has the following properties: + * + * - `element` - `{element}`: an uncompiled element matching the provided template. + * - `link` - `{function(scope)}`: A link function, which, when called, will compile + * the element and instantiate the provided controller (if given). + * - `locals` - `{object}`: The locals which will be passed into the controller once `link` is + * called. If `bindToController` is true, they will be coppied to the ctrl instead + * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in. + */ + this.compile = function(options) { + var templateUrl = options.templateUrl; + var template = options.template || ''; + var controller = options.controller; + var controllerAs = options.controllerAs; + var resolve = angular.extend({}, options.resolve || {}); + var locals = angular.extend({}, options.locals || {}); + var transformTemplate = options.transformTemplate || angular.identity; + var bindToController = options.bindToController; + + // Take resolve values and invoke them. + // Resolves can either be a string (value: 'MyRegisteredAngularConst'), + // or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {}) + angular.forEach(resolve, function(value, key) { + if (angular.isString(value)) { + resolve[key] = $injector.get(value); + } else { + resolve[key] = $injector.invoke(value); + } + }); + //Add the locals, which are just straight values to inject + //eg locals: { three: 3 }, will inject three into the controller + angular.extend(resolve, locals); + + if (templateUrl) { + resolve.$template = $templateRequest(templateUrl) + .then(function(response) { + return response; + }); + } else { + resolve.$template = $q.when(template); + } + + // Wait for all the resolves to finish if they are promises + return $q.all(resolve).then(function(locals) { + + var compiledData; + var template = transformTemplate(locals.$template, options); + var element = options.element || angular.element('
').html(template.trim()).contents(); + var linkFn = $compile(element); + + // Return a linking function that can be used later when the element is ready + return compiledData = { + locals: locals, + element: element, + link: function link(scope) { + locals.$scope = scope; + + //Instantiate controller if it exists, because we have scope + if (controller) { + var invokeCtrl = $controller(controller, locals, true, controllerAs); + if (bindToController) { + angular.extend(invokeCtrl.instance, locals); + } + var ctrl = invokeCtrl(); + //See angular-route source for this logic + element.data('$ngControllerController', ctrl); + element.children().data('$ngControllerController', ctrl); + + // Publish reference to this controller + compiledData.controller = ctrl; + } + return linkFn(scope); + } + }; + }); + + }; +} + +})(); +(function(){ +"use strict"; + + +MdGesture.$inject = ["$$MdGestureHandler", "$$rAF", "$timeout"]; +attachToDocument.$inject = ["$mdGesture", "$$MdGestureHandler"];var HANDLERS = {}; + +/* The state of the current 'pointer' + * The pointer represents the state of the current touch. + * It contains normalized x and y coordinates from DOM events, + * as well as other information abstracted from the DOM. + */ + +var pointer, lastPointer, forceSkipClickHijack = false; + +/** + * The position of the most recent click if that click was on a label element. + * @type {{x: number, y: number}?} + */ +var lastLabelClickPos = null; + +// Used to attach event listeners once when multiple ng-apps are running. +var isInitialized = false; + +angular + .module('material.core.gestures', [ ]) + .provider('$mdGesture', MdGestureProvider) + .factory('$$MdGestureHandler', MdGestureHandler) + .run( attachToDocument ); + +/** + * @ngdoc service + * @name $mdGestureProvider + * @module material.core.gestures + * + * @description + * In some scenarios on Mobile devices (without jQuery), the click events should NOT be hijacked. + * `$mdGestureProvider` is used to configure the Gesture module to ignore or skip click hijacking on mobile + * devices. + * + * + * app.config(function($mdGestureProvider) { + * + * // For mobile devices without jQuery loaded, do not + * // intercept click events during the capture phase. + * $mdGestureProvider.skipClickHijack(); + * + * }); + * + * + */ +function MdGestureProvider() { } + +MdGestureProvider.prototype = { + + // Publish access to setter to configure a variable BEFORE the + // $mdGesture service is instantiated... + skipClickHijack: function() { + return forceSkipClickHijack = true; + }, + + /** + * $get is used to build an instance of $mdGesture + * @ngInject + */ + $get : ["$$MdGestureHandler", "$$rAF", "$timeout", function($$MdGestureHandler, $$rAF, $timeout) { + return new MdGesture($$MdGestureHandler, $$rAF, $timeout); + }] +}; + + + +/** + * MdGesture factory construction function + * @ngInject + */ +function MdGesture($$MdGestureHandler, $$rAF, $timeout) { + var userAgent = navigator.userAgent || navigator.vendor || window.opera; + var isIos = userAgent.match(/ipad|iphone|ipod/i); + var isAndroid = userAgent.match(/android/i); + var touchActionProperty = getTouchAction(); + var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery); + + var self = { + handler: addHandler, + register: register, + isIos: isIos, + isAndroid: isAndroid, + // On mobile w/out jQuery, we normally intercept clicks. Should we skip that? + isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack + }; + + if (self.isHijackingClicks) { + var maxClickDistance = 6; + self.handler('click', { + options: { + maxDistance: maxClickDistance + }, + onEnd: checkDistanceAndEmit('click') + }); + + self.handler('focus', { + options: { + maxDistance: maxClickDistance + }, + onEnd: function(ev, pointer) { + if (pointer.distance < this.state.options.maxDistance) { + if (canFocus(ev.target)) { + this.dispatchEvent(ev, 'focus', pointer); + ev.target.focus(); + } + } + + function canFocus(element) { + var focusableElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA', 'VIDEO', 'AUDIO']; + + return (element.getAttribute('tabindex') != '-1') && + !element.hasAttribute('DISABLED') && + (element.hasAttribute('tabindex') || element.hasAttribute('href') || element.isContentEditable || + (focusableElements.indexOf(element.nodeName) != -1)); + } + } + }); + + self.handler('mouseup', { + options: { + maxDistance: maxClickDistance + }, + onEnd: checkDistanceAndEmit('mouseup') + }); + + self.handler('mousedown', { + onStart: function(ev) { + this.dispatchEvent(ev, 'mousedown'); + } + }); + } + + function checkDistanceAndEmit(eventName) { + return function(ev, pointer) { + if (pointer.distance < this.state.options.maxDistance) { + this.dispatchEvent(ev, eventName, pointer); + } + }; + } + + /* + * Register an element to listen for a handler. + * This allows an element to override the default options for a handler. + * Additionally, some handlers like drag and hold only dispatch events if + * the domEvent happens inside an element that's registered to listen for these events. + * + * @see GestureHandler for how overriding of default options works. + * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horziontal: false }) + */ + function register(element, handlerName, options) { + var handler = HANDLERS[handlerName.replace(/^\$md./, '')]; + if (!handler) { + throw new Error('Failed to register element with handler ' + handlerName + '. ' + + 'Available handlers: ' + Object.keys(HANDLERS).join(', ')); + } + return handler.registerElement(element, options); + } + + /* + * add a handler to $mdGesture. see below. + */ + function addHandler(name, definition) { + var handler = new $$MdGestureHandler(name); + angular.extend(handler, definition); + HANDLERS[name] = handler; + + return self; + } + + /* + * Register handlers. These listen to touch/start/move events, interpret them, + * and dispatch gesture events depending on options & conditions. These are all + * instances of GestureHandler. + * @see GestureHandler + */ + return self + /* + * The press handler dispatches an event on touchdown/touchend. + * It's a simple abstraction of touch/mouse/pointer start and end. + */ + .handler('press', { + onStart: function (ev, pointer) { + this.dispatchEvent(ev, '$md.pressdown'); + }, + onEnd: function (ev, pointer) { + this.dispatchEvent(ev, '$md.pressup'); + } + }) + + /* + * The hold handler dispatches an event if the user keeps their finger within + * the same area for ms. + * The hold handler will only run if a parent of the touch target is registered + * to listen for hold events through $mdGesture.register() + */ + .handler('hold', { + options: { + maxDistance: 6, + delay: 500 + }, + onCancel: function () { + $timeout.cancel(this.state.timeout); + }, + onStart: function (ev, pointer) { + // For hold, require a parent to be registered with $mdGesture.register() + // Because we prevent scroll events, this is necessary. + if (!this.state.registeredParent) return this.cancel(); + + this.state.pos = {x: pointer.x, y: pointer.y}; + this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() { + this.dispatchEvent(ev, '$md.hold'); + this.cancel(); //we're done! + }), this.state.options.delay, false); + }, + onMove: function (ev, pointer) { + // Don't scroll while waiting for hold. + // If we don't preventDefault touchmove events here, Android will assume we don't + // want to listen to anymore touch events. It will start scrolling and stop sending + // touchmove events. + if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault(); + + // If the user moves greater than pixels, stop the hold timer + // set in onStart + var dx = this.state.pos.x - pointer.x; + var dy = this.state.pos.y - pointer.y; + if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) { + this.cancel(); + } + }, + onEnd: function () { + this.onCancel(); + } + }) + + /* + * The drag handler dispatches a drag event if the user holds and moves his finger greater than + * px in the x or y direction, depending on options.horizontal. + * The drag will be cancelled if the user moves his finger greater than * in + * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger * + * pixels vertically, this handler won't consider the move part of a drag. + */ + .handler('drag', { + options: { + minDistance: 6, + horizontal: true, + cancelMultiplier: 1.5 + }, + onSetup: function(element, options) { + if (touchActionProperty) { + // We check for horizontal to be false, because otherwise we would overwrite the default opts. + this.oldTouchAction = element[0].style[touchActionProperty]; + element[0].style[touchActionProperty] = options.horizontal === false ? 'pan-y' : 'pan-x'; + } + }, + onCleanup: function(element) { + if (this.oldTouchAction) { + element[0].style[touchActionProperty] = this.oldTouchAction; + } + }, + onStart: function (ev) { + // For drag, require a parent to be registered with $mdGesture.register() + if (!this.state.registeredParent) this.cancel(); + }, + onMove: function (ev, pointer) { + var shouldStartDrag, shouldCancel; + // Don't scroll while deciding if this touchmove qualifies as a drag event. + // If we don't preventDefault touchmove events here, Android will assume we don't + // want to listen to anymore touch events. It will start scrolling and stop sending + // touchmove events. + if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault(); + + if (!this.state.dragPointer) { + if (this.state.options.horizontal) { + shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance; + shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier; + } else { + shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance; + shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier; + } + + if (shouldStartDrag) { + // Create a new pointer representing this drag, starting at this point where the drag started. + this.state.dragPointer = makeStartPointer(ev); + updatePointerState(ev, this.state.dragPointer); + this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer); + + } else if (shouldCancel) { + this.cancel(); + } + } else { + this.dispatchDragMove(ev); + } + }, + // Only dispatch dragmove events every frame; any more is unnecessary + dispatchDragMove: $$rAF.throttle(function (ev) { + // Make sure the drag didn't stop while waiting for the next frame + if (this.state.isRunning) { + updatePointerState(ev, this.state.dragPointer); + this.dispatchEvent(ev, '$md.drag', this.state.dragPointer); + } + }), + onEnd: function (ev, pointer) { + if (this.state.dragPointer) { + updatePointerState(ev, this.state.dragPointer); + this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer); + } + } + }) + + /* + * The swipe handler will dispatch a swipe event if, on the end of a touch, + * the velocity and distance were high enough. + */ + .handler('swipe', { + options: { + minVelocity: 0.65, + minDistance: 10 + }, + onEnd: function (ev, pointer) { + var eventType; + + if (Math.abs(pointer.velocityX) > this.state.options.minVelocity && + Math.abs(pointer.distanceX) > this.state.options.minDistance) { + eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight'; + this.dispatchEvent(ev, eventType); + } + else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity && + Math.abs(pointer.distanceY) > this.state.options.minDistance) { + eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown'; + this.dispatchEvent(ev, eventType); + } + } + }); + + function getTouchAction() { + var testEl = document.createElement('div'); + var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o']; + + for (var i = 0; i < vendorPrefixes.length; i++) { + var prefix = vendorPrefixes[i]; + var property = prefix ? prefix + 'TouchAction' : 'touchAction'; + if (angular.isDefined(testEl.style[property])) { + return property; + } + } + } + +} + +/** + * MdGestureHandler + * A GestureHandler is an object which is able to dispatch custom dom events + * based on native dom {touch,pointer,mouse}{start,move,end} events. + * + * A gesture will manage its lifecycle through the start,move,end, and cancel + * functions, which are called by native dom events. + * + * A gesture has the concept of 'options' (eg a swipe's required velocity), which can be + * overridden by elements registering through $mdGesture.register() + */ +function GestureHandler (name) { + this.name = name; + this.state = {}; +} + +function MdGestureHandler() { + var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery); + + GestureHandler.prototype = { + options: {}, + // jQuery listeners don't work with custom DOMEvents, so we have to dispatch events + // differently when jQuery is loaded + dispatchEvent: hasJQuery ? jQueryDispatchEvent : nativeDispatchEvent, + + // These are overridden by the registered handler + onSetup: angular.noop, + onCleanup: angular.noop, + onStart: angular.noop, + onMove: angular.noop, + onEnd: angular.noop, + onCancel: angular.noop, + + // onStart sets up a new state for the handler, which includes options from the + // nearest registered parent element of ev.target. + start: function (ev, pointer) { + if (this.state.isRunning) return; + var parentTarget = this.getNearestParent(ev.target); + // Get the options from the nearest registered parent + var parentTargetOptions = parentTarget && parentTarget.$mdGesture[this.name] || {}; + + this.state = { + isRunning: true, + // Override the default options with the nearest registered parent's options + options: angular.extend({}, this.options, parentTargetOptions), + // Pass in the registered parent node to the state so the onStart listener can use + registeredParent: parentTarget + }; + this.onStart(ev, pointer); + }, + move: function (ev, pointer) { + if (!this.state.isRunning) return; + this.onMove(ev, pointer); + }, + end: function (ev, pointer) { + if (!this.state.isRunning) return; + this.onEnd(ev, pointer); + this.state.isRunning = false; + }, + cancel: function (ev, pointer) { + this.onCancel(ev, pointer); + this.state = {}; + }, + + // Find and return the nearest parent element that has been registered to + // listen for this handler via $mdGesture.register(element, 'handlerName'). + getNearestParent: function (node) { + var current = node; + while (current) { + if ((current.$mdGesture || {})[this.name]) { + return current; + } + current = current.parentNode; + } + return null; + }, + + // Called from $mdGesture.register when an element registers itself with a handler. + // Store the options the user gave on the DOMElement itself. These options will + // be retrieved with getNearestParent when the handler starts. + registerElement: function (element, options) { + var self = this; + element[0].$mdGesture = element[0].$mdGesture || {}; + element[0].$mdGesture[this.name] = options || {}; + element.on('$destroy', onDestroy); + + self.onSetup(element, options || {}); + + return onDestroy; + + function onDestroy() { + delete element[0].$mdGesture[self.name]; + element.off('$destroy', onDestroy); + + self.onCleanup(element, options || {}); + } + } + }; + + return GestureHandler; + + /* + * Dispatch an event with jQuery + * TODO: Make sure this sends bubbling events + * + * @param srcEvent the original DOM touch event that started this. + * @param eventType the name of the custom event to send (eg 'click' or '$md.drag') + * @param eventPointer the pointer object that matches this event. + */ + function jQueryDispatchEvent(srcEvent, eventType, eventPointer) { + eventPointer = eventPointer || pointer; + var eventObj = new angular.element.Event(eventType); + + eventObj.$material = true; + eventObj.pointer = eventPointer; + eventObj.srcEvent = srcEvent; + + angular.extend(eventObj, { + clientX: eventPointer.x, + clientY: eventPointer.y, + screenX: eventPointer.x, + screenY: eventPointer.y, + pageX: eventPointer.x, + pageY: eventPointer.y, + ctrlKey: srcEvent.ctrlKey, + altKey: srcEvent.altKey, + shiftKey: srcEvent.shiftKey, + metaKey: srcEvent.metaKey + }); + angular.element(eventPointer.target).trigger(eventObj); + } + + /* + * NOTE: nativeDispatchEvent is very performance sensitive. + * @param srcEvent the original DOM touch event that started this. + * @param eventType the name of the custom event to send (eg 'click' or '$md.drag') + * @param eventPointer the pointer object that matches this event. + */ + function nativeDispatchEvent(srcEvent, eventType, eventPointer) { + eventPointer = eventPointer || pointer; + var eventObj; + + if (eventType === 'click' || eventType == 'mouseup' || eventType == 'mousedown' ) { + eventObj = document.createEvent('MouseEvents'); + eventObj.initMouseEvent( + eventType, true, true, window, srcEvent.detail, + eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y, + srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey, + srcEvent.button, srcEvent.relatedTarget || null + ); + + } else { + eventObj = document.createEvent('CustomEvent'); + eventObj.initCustomEvent(eventType, true, true, {}); + } + eventObj.$material = true; + eventObj.pointer = eventPointer; + eventObj.srcEvent = srcEvent; + eventPointer.target.dispatchEvent(eventObj); + } + +} + +/** + * Attach Gestures: hook document and check shouldHijack clicks + * @ngInject + */ +function attachToDocument( $mdGesture, $$MdGestureHandler ) { + + // Polyfill document.contains for IE11. + // TODO: move to util + document.contains || (document.contains = function (node) { + return document.body.contains(node); + }); + + if (!isInitialized && $mdGesture.isHijackingClicks ) { + /* + * If hijack clicks is true, we preventDefault any click that wasn't + * sent by ngMaterial. This is because on older Android & iOS, a false, or 'ghost', + * click event will be sent ~400ms after a touchend event happens. + * The only way to know if this click is real is to prevent any normal + * click events, and add a flag to events sent by material so we know not to prevent those. + * + * Two exceptions to click events that should be prevented are: + * - click events sent by the keyboard (eg form submit) + * - events that originate from an Ionic app + */ + document.addEventListener('click' , clickHijacker , true); + document.addEventListener('mouseup' , mouseInputHijacker, true); + document.addEventListener('mousedown', mouseInputHijacker, true); + document.addEventListener('focus' , mouseInputHijacker, true); + + isInitialized = true; + } + + function mouseInputHijacker(ev) { + var isKeyClick = !ev.clientX && !ev.clientY; + if (!isKeyClick && !ev.$material && !ev.isIonicTap + && !isInputEventFromLabelClick(ev)) { + ev.preventDefault(); + ev.stopPropagation(); + } + } + + function clickHijacker(ev) { + var isKeyClick = ev.clientX === 0 && ev.clientY === 0; + if (!isKeyClick && !ev.$material && !ev.isIonicTap + && !isInputEventFromLabelClick(ev)) { + ev.preventDefault(); + ev.stopPropagation(); + lastLabelClickPos = null; + } else { + lastLabelClickPos = null; + if (ev.target.tagName.toLowerCase() == 'label') { + lastLabelClickPos = {x: ev.x, y: ev.y}; + } + } + } + + + // Listen to all events to cover all platforms. + var START_EVENTS = 'mousedown touchstart pointerdown'; + var MOVE_EVENTS = 'mousemove touchmove pointermove'; + var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel'; + + angular.element(document) + .on(START_EVENTS, gestureStart) + .on(MOVE_EVENTS, gestureMove) + .on(END_EVENTS, gestureEnd) + // For testing + .on('$$mdGestureReset', function gestureClearCache () { + lastPointer = pointer = null; + }); + + /* + * When a DOM event happens, run all registered gesture handlers' lifecycle + * methods which match the DOM event. + * Eg when a 'touchstart' event happens, runHandlers('start') will call and + * run `handler.cancel()` and `handler.start()` on all registered handlers. + */ + function runHandlers(handlerEvent, event) { + var handler; + for (var name in HANDLERS) { + handler = HANDLERS[name]; + if( handler instanceof $$MdGestureHandler ) { + + if (handlerEvent === 'start') { + // Run cancel to reset any handlers' state + handler.cancel(); + } + handler[handlerEvent](event, pointer); + + } + } + } + + /* + * gestureStart vets if a start event is legitimate (and not part of a 'ghost click' from iOS/Android) + * If it is legitimate, we initiate the pointer state and mark the current pointer's type + * For example, for a touchstart event, mark the current pointer as a 'touch' pointer, so mouse events + * won't effect it. + */ + function gestureStart(ev) { + // If we're already touched down, abort + if (pointer) return; + + var now = +Date.now(); + + // iOS & old android bug: after a touch event, a click event is sent 350 ms later. + // If <400ms have passed, don't allow an event of a different type than the previous event + if (lastPointer && !typesMatch(ev, lastPointer) && (now - lastPointer.endTime < 1500)) { + return; + } + + pointer = makeStartPointer(ev); + + runHandlers('start', ev); + } + /* + * If a move event happens of the right type, update the pointer and run all the move handlers. + * "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing. + */ + function gestureMove(ev) { + if (!pointer || !typesMatch(ev, pointer)) return; + + updatePointerState(ev, pointer); + runHandlers('move', ev); + } + /* + * If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer' + */ + function gestureEnd(ev) { + if (!pointer || !typesMatch(ev, pointer)) return; + + updatePointerState(ev, pointer); + pointer.endTime = +Date.now(); + + runHandlers('end', ev); + + lastPointer = pointer; + pointer = null; + } + +} + +// ******************** +// Module Functions +// ******************** + +/* + * Initiate the pointer. x, y, and the pointer's type. + */ +function makeStartPointer(ev) { + var point = getEventPoint(ev); + var startPointer = { + startTime: +Date.now(), + target: ev.target, + // 'p' for pointer events, 'm' for mouse, 't' for touch + type: ev.type.charAt(0) + }; + startPointer.startX = startPointer.x = point.pageX; + startPointer.startY = startPointer.y = point.pageY; + return startPointer; +} + +/* + * return whether the pointer's type matches the event's type. + * Eg if a touch event happens but the pointer has a mouse type, return false. + */ +function typesMatch(ev, pointer) { + return ev && pointer && ev.type.charAt(0) === pointer.type; +} + +/** + * Gets whether the given event is an input event that was caused by clicking on an + * associated label element. + * + * This is necessary because the browser will, upon clicking on a label element, fire an + * *extra* click event on its associated input (if any). mdGesture is able to flag the label + * click as with `$material` correctly, but not the second input click. + * + * In order to determine whether an input event is from a label click, we compare the (x, y) for + * the event to the (x, y) for the most recent label click (which is cleared whenever a non-label + * click occurs). Unfortunately, there are no event properties that tie the input and the label + * together (such as relatedTarget). + * + * @param {MouseEvent} event + * @returns {boolean} + */ +function isInputEventFromLabelClick(event) { + return lastLabelClickPos + && lastLabelClickPos.x == event.x + && lastLabelClickPos.y == event.y; +} + +/* + * Update the given pointer based upon the given DOMEvent. + * Distance, velocity, direction, duration, etc + */ +function updatePointerState(ev, pointer) { + var point = getEventPoint(ev); + var x = pointer.x = point.pageX; + var y = pointer.y = point.pageY; + + pointer.distanceX = x - pointer.startX; + pointer.distanceY = y - pointer.startY; + pointer.distance = Math.sqrt( + pointer.distanceX * pointer.distanceX + pointer.distanceY * pointer.distanceY + ); + + pointer.directionX = pointer.distanceX > 0 ? 'right' : pointer.distanceX < 0 ? 'left' : ''; + pointer.directionY = pointer.distanceY > 0 ? 'down' : pointer.distanceY < 0 ? 'up' : ''; + + pointer.duration = +Date.now() - pointer.startTime; + pointer.velocityX = pointer.distanceX / pointer.duration; + pointer.velocityY = pointer.distanceY / pointer.duration; +} + +/* + * Normalize the point where the DOM event happened whether it's touch or mouse. + * @returns point event obj with pageX and pageY on it. + */ +function getEventPoint(ev) { + ev = ev.originalEvent || ev; // support jQuery events + return (ev.touches && ev.touches[0]) || + (ev.changedTouches && ev.changedTouches[0]) || + ev; +} + +})(); +(function(){ +"use strict"; + +angular.module('material.core') + .provider('$$interimElement', InterimElementProvider); + +/* + * @ngdoc service + * @name $$interimElement + * @module material.core + * + * @description + * + * Factory that contructs `$$interimElement.$service` services. + * Used internally in material design for elements that appear on screen temporarily. + * The service provides a promise-like API for interacting with the temporary + * elements. + * + * ```js + * app.service('$mdToast', function($$interimElement) { + * var $mdToast = $$interimElement(toastDefaultOptions); + * return $mdToast; + * }); + * ``` + * @param {object=} defaultOptions Options used by default for the `show` method on the service. + * + * @returns {$$interimElement.$service} + * + */ + +function InterimElementProvider() { + InterimElementFactory.$inject = ["$document", "$q", "$$q", "$rootScope", "$timeout", "$rootElement", "$animate", "$mdUtil", "$mdCompiler", "$mdTheming", "$injector"]; + createInterimElementProvider.$get = InterimElementFactory; + return createInterimElementProvider; + + /** + * Returns a new provider which allows configuration of a new interimElement + * service. Allows configuration of default options & methods for options, + * as well as configuration of 'preset' methods (eg dialog.basic(): basic is a preset method) + */ + function createInterimElementProvider(interimFactoryName) { + factory.$inject = ["$$interimElement", "$injector"]; + var EXPOSED_METHODS = ['onHide', 'onShow', 'onRemove']; + + var customMethods = {}; + var providerConfig = { + presets: {} + }; + + var provider = { + setDefaults: setDefaults, + addPreset: addPreset, + addMethod: addMethod, + $get: factory + }; + + /** + * all interim elements will come with the 'build' preset + */ + provider.addPreset('build', { + methods: ['controller', 'controllerAs', 'resolve', + 'template', 'templateUrl', 'themable', 'transformTemplate', 'parent'] + }); + + return provider; + + /** + * Save the configured defaults to be used when the factory is instantiated + */ + function setDefaults(definition) { + providerConfig.optionsFactory = definition.options; + providerConfig.methods = (definition.methods || []).concat(EXPOSED_METHODS); + return provider; + } + + /** + * Add a method to the factory that isn't specific to any interim element operations + */ + + function addMethod(name, fn) { + customMethods[name] = fn; + return provider; + } + + /** + * Save the configured preset to be used when the factory is instantiated + */ + function addPreset(name, definition) { + definition = definition || {}; + definition.methods = definition.methods || []; + definition.options = definition.options || function() { return {}; }; + + if (/^cancel|hide|show$/.test(name)) { + throw new Error("Preset '" + name + "' in " + interimFactoryName + " is reserved!"); + } + if (definition.methods.indexOf('_options') > -1) { + throw new Error("Method '_options' in " + interimFactoryName + " is reserved!"); + } + providerConfig.presets[name] = { + methods: definition.methods.concat(EXPOSED_METHODS), + optionsFactory: definition.options, + argOption: definition.argOption + }; + return provider; + } + + function addPresetMethod(presetName, methodName, method) { + providerConfig.presets[presetName][methodName] = method; + } + + /** + * Create a factory that has the given methods & defaults implementing interimElement + */ + /* @ngInject */ + function factory($$interimElement, $injector) { + var defaultMethods; + var defaultOptions; + var interimElementService = $$interimElement(); + + /* + * publicService is what the developer will be using. + * It has methods hide(), cancel(), show(), build(), and any other + * presets which were set during the config phase. + */ + var publicService = { + hide: interimElementService.hide, + cancel: interimElementService.cancel, + show: showInterimElement, + + // Special internal method to destroy an interim element without animations + // used when navigation changes causes a $scope.$destroy() action + destroy : destroyInterimElement + }; + + + defaultMethods = providerConfig.methods || []; + // This must be invoked after the publicService is initialized + defaultOptions = invokeFactory(providerConfig.optionsFactory, {}); + + // Copy over the simple custom methods + angular.forEach(customMethods, function(fn, name) { + publicService[name] = fn; + }); + + angular.forEach(providerConfig.presets, function(definition, name) { + var presetDefaults = invokeFactory(definition.optionsFactory, {}); + var presetMethods = (definition.methods || []).concat(defaultMethods); + + // Every interimElement built with a preset has a field called `$type`, + // which matches the name of the preset. + // Eg in preset 'confirm', options.$type === 'confirm' + angular.extend(presetDefaults, { $type: name }); + + // This creates a preset class which has setter methods for every + // method given in the `.addPreset()` function, as well as every + // method given in the `.setDefaults()` function. + // + // @example + // .setDefaults({ + // methods: ['hasBackdrop', 'clickOutsideToClose', 'escapeToClose', 'targetEvent'], + // options: dialogDefaultOptions + // }) + // .addPreset('alert', { + // methods: ['title', 'ok'], + // options: alertDialogOptions + // }) + // + // Set values will be passed to the options when interimElement.show() is called. + function Preset(opts) { + this._options = angular.extend({}, presetDefaults, opts); + } + angular.forEach(presetMethods, function(name) { + Preset.prototype[name] = function(value) { + this._options[name] = value; + return this; + }; + }); + + // Create shortcut method for one-linear methods + if (definition.argOption) { + var methodName = 'show' + name.charAt(0).toUpperCase() + name.slice(1); + publicService[methodName] = function(arg) { + var config = publicService[name](arg); + return publicService.show(config); + }; + } + + // eg $mdDialog.alert() will return a new alert preset + publicService[name] = function(arg) { + // If argOption is supplied, eg `argOption: 'content'`, then we assume + // if the argument is not an options object then it is the `argOption` option. + // + // @example `$mdToast.simple('hello')` // sets options.content to hello + // // because argOption === 'content' + if (arguments.length && definition.argOption && + !angular.isObject(arg) && !angular.isArray(arg)) { + + return (new Preset())[definition.argOption](arg); + + } else { + return new Preset(arg); + } + + }; + }); + + return publicService; + + /** + * + */ + function showInterimElement(opts) { + // opts is either a preset which stores its options on an _options field, + // or just an object made up of options + opts = opts || { }; + if (opts._options) opts = opts._options; + + return interimElementService.show( + angular.extend({}, defaultOptions, opts) + ); + } + + /** + * Special method to hide and destroy an interimElement WITHOUT + * any 'leave` or hide animations ( an immediate force hide/remove ) + * + * NOTE: This calls the onRemove() subclass method for each component... + * which must have code to respond to `options.$destroy == true` + */ + function destroyInterimElement(opts) { + return interimElementService.destroy(opts); + } + + /** + * Helper to call $injector.invoke with a local of the factory name for + * this provider. + * If an $mdDialog is providing options for a dialog and tries to inject + * $mdDialog, a circular dependency error will happen. + * We get around that by manually injecting $mdDialog as a local. + */ + function invokeFactory(factory, defaultVal) { + var locals = {}; + locals[interimFactoryName] = publicService; + return $injector.invoke(factory || function() { return defaultVal; }, {}, locals); + } + + } + + } + + /* @ngInject */ + function InterimElementFactory($document, $q, $$q, $rootScope, $timeout, $rootElement, $animate, + $mdUtil, $mdCompiler, $mdTheming, $injector ) { + return function createInterimElementService() { + var SHOW_CANCELLED = false; + + /* + * @ngdoc service + * @name $$interimElement.$service + * + * @description + * A service used to control inserting and removing an element into the DOM. + * + */ + var service, stack = []; + + // Publish instance $$interimElement service; + // ... used as $mdDialog, $mdToast, $mdMenu, and $mdSelect + + return service = { + show: show, + hide: hide, + cancel: cancel, + destroy : destroy, + $injector_: $injector + }; + + /* + * @ngdoc method + * @name $$interimElement.$service#show + * @kind function + * + * @description + * Adds the `$interimElement` to the DOM and returns a special promise that will be resolved or rejected + * with hide or cancel, respectively. To external cancel/hide, developers should use the + * + * @param {*} options is hashMap of settings + * @returns a Promise + * + */ + function show(options) { + options = options || {}; + var interimElement = new InterimElement(options || {}); + // When an interim element is currently showing, we have to cancel it. + // Just hiding it, will resolve the InterimElement's promise, the promise should be + // rejected instead. + var hideExisting = !options.skipHide && stack.length ? service.cancel() : $q.when(true); + + // This hide()s only the current interim element before showing the next, new one + // NOTE: this is not reversible (e.g. interim elements are not stackable) + + hideExisting.finally(function() { + + stack.push(interimElement); + interimElement + .show() + .catch(function( reason ) { + //$log.error("InterimElement.show() error: " + reason ); + return reason; + }); + + }); + + // Return a promise that will be resolved when the interim + // element is hidden or cancelled... + + return interimElement.deferred.promise; + } + + /* + * @ngdoc method + * @name $$interimElement.$service#hide + * @kind function + * + * @description + * Removes the `$interimElement` from the DOM and resolves the promise returned from `show` + * + * @param {*} resolveParam Data to resolve the promise with + * @returns a Promise that will be resolved after the element has been removed. + * + */ + function hide(reason, options) { + if ( !stack.length ) return $q.when(reason); + options = options || {}; + + if (options.closeAll) { + var promise = $q.all(stack.reverse().map(closeElement)); + stack = []; + return promise; + } else if (options.closeTo !== undefined) { + return $q.all(stack.splice(options.closeTo).map(closeElement)); + } else { + var interim = stack.pop(); + return closeElement(interim); + } + + function closeElement(interim) { + interim + .remove(reason, false, options || { }) + .catch(function( reason ) { + //$log.error("InterimElement.hide() error: " + reason ); + return reason; + }); + return interim.deferred.promise; + } + } + + /* + * @ngdoc method + * @name $$interimElement.$service#cancel + * @kind function + * + * @description + * Removes the `$interimElement` from the DOM and rejects the promise returned from `show` + * + * @param {*} reason Data to reject the promise with + * @returns Promise that will be resolved after the element has been removed. + * + */ + function cancel(reason, options) { + var interim = stack.pop(); + if ( !interim ) return $q.when(reason); + + interim + .remove(reason, true, options || { }) + .catch(function( reason ) { + //$log.error("InterimElement.cancel() error: " + reason ); + return reason; + }); + + // Since Angular 1.6.7, promises will be logged to $exceptionHandler when the promise + // is not handling the rejection. We create a pseudo catch handler, which will prevent the + // promise from being logged to the $exceptionHandler. + return interim.deferred.promise.catch(angular.noop); + } + + /* + * Special method to quick-remove the interim element without animations + * Note: interim elements are in "interim containers" + */ + function destroy(target) { + var interim = !target ? stack.shift() : null; + var cntr = angular.element(target).length ? angular.element(target)[0].parentNode : null; + + if (cntr) { + // Try to find the interim element in the stack which corresponds to the supplied DOM element. + var filtered = stack.filter(function(entry) { + var currNode = entry.options.element[0]; + return (currNode === cntr); + }); + + // Note: this function might be called when the element already has been removed, in which + // case we won't find any matches. That's ok. + if (filtered.length > 0) { + interim = filtered[0]; + stack.splice(stack.indexOf(interim), 1); + } + } + + return interim ? interim.remove(SHOW_CANCELLED, false, {'$destroy':true}) : $q.when(SHOW_CANCELLED); + } + + /* + * Internal Interim Element Object + * Used internally to manage the DOM element and related data + */ + function InterimElement(options) { + var self, element, showAction = $q.when(true); + + options = configureScopeAndTransitions(options); + + return self = { + options : options, + deferred: $q.defer(), + show : createAndTransitionIn, + remove : transitionOutAndRemove + }; + + /** + * Compile, link, and show this interim element + * Use optional autoHided and transition-in effects + */ + function createAndTransitionIn() { + return $q(function(resolve, reject) { + + // Trigger onCompiling callback before the compilation starts. + // This is useful, when modifying options, which can be influenced by developers. + options.onCompiling && options.onCompiling(options); + + compileElement(options) + .then(function( compiledData ) { + element = linkElement( compiledData, options ); + + showAction = showElement(element, options, compiledData.controller) + .then(resolve, rejectAll); + + }, rejectAll); + + function rejectAll(fault) { + // Force the '$md.show()' promise to reject + self.deferred.reject(fault); + + // Continue rejection propagation + reject(fault); + } + }); + } + + /** + * After the show process has finished/rejected: + * - announce 'removing', + * - perform the transition-out, and + * - perform optional clean up scope. + */ + function transitionOutAndRemove(response, isCancelled, opts) { + + // abort if the show() and compile failed + if ( !element ) return $q.when(false); + + options = angular.extend(options || {}, opts || {}); + options.cancelAutoHide && options.cancelAutoHide(); + options.element.triggerHandler('$mdInterimElementRemove'); + + if ( options.$destroy === true ) { + + return hideElement(options.element, options).then(function(){ + (isCancelled && rejectAll(response)) || resolveAll(response); + }); + + } else { + + $q.when(showAction) + .finally(function() { + hideElement(options.element, options).then(function() { + + (isCancelled && rejectAll(response)) || resolveAll(response); + + }, rejectAll); + }); + + return self.deferred.promise; + } + + + /** + * The `show()` returns a promise that will be resolved when the interim + * element is hidden or cancelled... + */ + function resolveAll(response) { + self.deferred.resolve(response); + } + + /** + * Force the '$md.show()' promise to reject + */ + function rejectAll(fault) { + self.deferred.reject(fault); + } + } + + /** + * Prepare optional isolated scope and prepare $animate with default enter and leave + * transitions for the new element instance. + */ + function configureScopeAndTransitions(options) { + options = options || { }; + if ( options.template ) { + options.template = $mdUtil.processTemplate(options.template); + } + + return angular.extend({ + preserveScope: false, + cancelAutoHide : angular.noop, + scope: options.scope || $rootScope.$new(options.isolateScope), + + /** + * Default usage to enable $animate to transition-in; can be easily overridden via 'options' + */ + onShow: function transitionIn(scope, element, options) { + return $animate.enter(element, options.parent); + }, + + /** + * Default usage to enable $animate to transition-out; can be easily overridden via 'options' + */ + onRemove: function transitionOut(scope, element) { + // Element could be undefined if a new element is shown before + // the old one finishes compiling. + return element && $animate.leave(element) || $q.when(); + } + }, options ); + + } + + /** + * Compile an element with a templateUrl, controller, and locals + */ + function compileElement(options) { + + var compiled = !options.skipCompile ? $mdCompiler.compile(options) : null; + + return compiled || $q(function (resolve) { + resolve({ + locals: {}, + link: function () { + return options.element; + } + }); + }); + } + + /** + * Link an element with compiled configuration + */ + function linkElement(compileData, options){ + angular.extend(compileData.locals, options); + + var element = compileData.link(options.scope); + + // Search for parent at insertion time, if not specified + options.element = element; + options.parent = findParent(element, options); + if (options.themable) $mdTheming(element); + + return element; + } + + /** + * Search for parent at insertion time, if not specified + */ + function findParent(element, options) { + var parent = options.parent; + + // Search for parent at insertion time, if not specified + if (angular.isFunction(parent)) { + parent = parent(options.scope, element, options); + } else if (angular.isString(parent)) { + parent = angular.element($document[0].querySelector(parent)); + } else { + parent = angular.element(parent); + } + + // If parent querySelector/getter function fails, or it's just null, + // find a default. + if (!(parent || {}).length) { + var el; + if ($rootElement[0] && $rootElement[0].querySelector) { + el = $rootElement[0].querySelector(':not(svg) > body'); + } + if (!el) el = $rootElement[0]; + if (el.nodeName == '#comment') { + el = $document[0].body; + } + return angular.element(el); + } + + return parent; + } + + /** + * If auto-hide is enabled, start timer and prepare cancel function + */ + function startAutoHide() { + var autoHideTimer, cancelAutoHide = angular.noop; + + if (options.hideDelay) { + autoHideTimer = $timeout(service.hide, options.hideDelay) ; + cancelAutoHide = function() { + $timeout.cancel(autoHideTimer); + } + } + + // Cache for subsequent use + options.cancelAutoHide = function() { + cancelAutoHide(); + options.cancelAutoHide = undefined; + } + } + + /** + * Show the element ( with transitions), notify complete and start + * optional auto-Hide + */ + function showElement(element, options, controller) { + // Trigger onShowing callback before the `show()` starts + var notifyShowing = options.onShowing || angular.noop; + // Trigger onComplete callback when the `show()` finishes + var notifyComplete = options.onComplete || angular.noop; + + notifyShowing(options.scope, element, options, controller); + + return $q(function (resolve, reject) { + try { + // Start transitionIn + $q.when(options.onShow(options.scope, element, options, controller)) + .then(function () { + notifyComplete(options.scope, element, options); + startAutoHide(); + + resolve(element); + + }, reject ); + + } catch(e) { + reject(e.message); + } + }); + } + + function hideElement(element, options) { + var announceRemoving = options.onRemoving || angular.noop; + + return $$q(function (resolve, reject) { + try { + // Start transitionIn + var action = $$q.when( options.onRemove(options.scope, element, options) || true ); + + // Trigger callback *before* the remove operation starts + announceRemoving(element, action); + + if ( options.$destroy == true ) { + + // For $destroy, onRemove should be synchronous + resolve(element); + + } else { + + // Wait until transition-out is done + action.then(function () { + + if (!options.preserveScope && options.scope ) { + options.scope.$destroy(); + } + + resolve(element); + + }, reject ); + } + + } catch(e) { + reject(e); + } + }); + } + + } + }; + + } + +} + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + var $mdUtil, $interpolate, $log; + + var SUFFIXES = /(-gt)?-(sm|md|lg|print)/g; + var WHITESPACE = /\s+/g; + + var FLEX_OPTIONS = ['grow', 'initial', 'auto', 'none', 'noshrink', 'nogrow' ]; + var LAYOUT_OPTIONS = ['row', 'column']; + var ALIGNMENT_MAIN_AXIS= [ "", "start", "center", "end", "stretch", "space-around", "space-between" ]; + var ALIGNMENT_CROSS_AXIS= [ "", "start", "center", "end", "stretch" ]; + + var config = { + /** + * Enable directive attribute-to-class conversions + * Developers can use `` to quickly + * disable the Layout directives and prohibit the injection of Layout classNames + */ + enabled: true, + + /** + * List of mediaQuery breakpoints and associated suffixes + * + * [ + * { suffix: "sm", mediaQuery: "screen and (max-width: 599px)" }, + * { suffix: "md", mediaQuery: "screen and (min-width: 600px) and (max-width: 959px)" } + * ] + */ + breakpoints: [] + }; + + registerLayoutAPI( angular.module('material.core.layout', ['ng']) ); + + /** + * registerLayoutAPI() + * + * The original ngMaterial Layout solution used attribute selectors and CSS. + * + * ```html + *
My Content
+ * ``` + * + * ```css + * [layout] { + * box-sizing: border-box; + * display:flex; + * } + * [layout=column] { + * flex-direction : column + * } + * ``` + * + * Use of attribute selectors creates significant performance impacts in some + * browsers... mainly IE. + * + * This module registers directives that allow the same layout attributes to be + * interpreted and converted to class selectors. The directive will add equivalent classes to each element that + * contains a Layout directive. + * + * ```html + *
My Content
+ *``` + * + * ```css + * .layout { + * box-sizing: border-box; + * display:flex; + * } + * .layout-column { + * flex-direction : column + * } + * ``` + */ + function registerLayoutAPI(module){ + var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; + var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; + + // NOTE: these are also defined in constants::MEDIA_PRIORITY and constants::MEDIA + var BREAKPOINTS = [ "", "xs", "gt-xs", "sm", "gt-sm", "md", "gt-md", "lg", "gt-lg", "xl", "print" ]; + var API_WITH_VALUES = [ "layout", "flex", "flex-order", "flex-offset", "layout-align" ]; + var API_NO_VALUES = [ "show", "hide", "layout-padding", "layout-margin" ]; + + + // Build directive registration functions for the standard Layout API... for all breakpoints. + angular.forEach(BREAKPOINTS, function(mqb) { + + // Attribute directives with expected, observable value(s) + angular.forEach( API_WITH_VALUES, function(name){ + var fullName = mqb ? name + "-" + mqb : name; + module.directive( directiveNormalize(fullName), attributeWithObserve(fullName)); + }); + + // Attribute directives with no expected value(s) + angular.forEach( API_NO_VALUES, function(name){ + var fullName = mqb ? name + "-" + mqb : name; + module.directive( directiveNormalize(fullName), attributeWithoutValue(fullName)); + }); + + }); + + // Register other, special directive functions for the Layout features: + module + + .provider('$$mdLayout' , function() { + // Publish internal service for Layouts + return { + $get : angular.noop, + validateAttributeValue : validateAttributeValue, + validateAttributeUsage : validateAttributeUsage, + /** + * Easy way to disable/enable the Layout API. + * When disabled, this stops all attribute-to-classname generations + */ + disableLayouts : function(isDisabled) { + config.enabled = (isDisabled !== true); + } + }; + }) + + .directive('mdLayoutCss' , disableLayoutDirective ) + .directive('ngCloak' , buildCloakInterceptor('ng-cloak')) + + .directive('layoutWrap' , attributeWithoutValue('layout-wrap')) + .directive('layoutNowrap' , attributeWithoutValue('layout-nowrap')) + .directive('layoutNoWrap' , attributeWithoutValue('layout-no-wrap')) + .directive('layoutFill' , attributeWithoutValue('layout-fill')) + + // !! Deprecated attributes: use the `-lt` (aka less-than) notations + + .directive('layoutLtMd' , warnAttrNotSupported('layout-lt-md', true)) + .directive('layoutLtLg' , warnAttrNotSupported('layout-lt-lg', true)) + .directive('flexLtMd' , warnAttrNotSupported('flex-lt-md', true)) + .directive('flexLtLg' , warnAttrNotSupported('flex-lt-lg', true)) + + .directive('layoutAlignLtMd', warnAttrNotSupported('layout-align-lt-md')) + .directive('layoutAlignLtLg', warnAttrNotSupported('layout-align-lt-lg')) + .directive('flexOrderLtMd' , warnAttrNotSupported('flex-order-lt-md')) + .directive('flexOrderLtLg' , warnAttrNotSupported('flex-order-lt-lg')) + .directive('offsetLtMd' , warnAttrNotSupported('flex-offset-lt-md')) + .directive('offsetLtLg' , warnAttrNotSupported('flex-offset-lt-lg')) + + .directive('hideLtMd' , warnAttrNotSupported('hide-lt-md')) + .directive('hideLtLg' , warnAttrNotSupported('hide-lt-lg')) + .directive('showLtMd' , warnAttrNotSupported('show-lt-md')) + .directive('showLtLg' , warnAttrNotSupported('show-lt-lg')) + + // Determine if + .config( detectDisabledLayouts ); + + /** + * Converts snake_case to camelCase. + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ + function directiveNormalize(name) { + return name + .replace(PREFIX_REGEXP, '') + .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }); + } + + } + + + /** + * Detect if any of the HTML tags has a [md-layouts-disabled] attribute; + * If yes, then immediately disable all layout API features + * + * Note: this attribute should be specified on either the HTML or BODY tags + */ + /** + * @ngInject + */ + function detectDisabledLayouts() { + var isDisabled = !!document.querySelector('[md-layouts-disabled]'); + config.enabled = !isDisabled; + } + + /** + * Special directive that will disable ALL Layout conversions of layout + * attribute(s) to classname(s). + * + * + * + * + * + * ... + * + * + * Note: Using md-layout-css directive requires the developer to load the Material + * Layout Attribute stylesheet (which only uses attribute selectors): + * + * `angular-material.layout.css` + * + * Another option is to use the LayoutProvider to configure and disable the attribute + * conversions; this would obviate the use of the `md-layout-css` directive + * + */ + function disableLayoutDirective() { + // Return a 1x-only, first-match attribute directive + config.enabled = false; + + return { + restrict : 'A', + priority : '900' + }; + } + + /** + * Tail-hook ngCloak to delay the uncloaking while Layout transformers + * finish processing. Eliminates flicker with Material.Layouts + */ + function buildCloakInterceptor(className) { + return [ '$timeout', function($timeout){ + return { + restrict : 'A', + priority : -10, // run after normal ng-cloak + compile : function( element ) { + if (!config.enabled) return angular.noop; + + // Re-add the cloak + element.addClass(className); + + return function( scope, element ) { + // Wait while layout injectors configure, then uncloak + // NOTE: $rAF does not delay enough... and this is a 1x-only event, + // $timeout is acceptable. + $timeout( function(){ + element.removeClass(className); + }, 10, false); + }; + } + }; + }]; + } + + + // ********************************************************************************* + // + // These functions create registration functions for ngMaterial Layout attribute directives + // This provides easy translation to switch ngMaterial attribute selectors to + // CLASS selectors and directives; which has huge performance implications + // for IE Browsers + // + // ********************************************************************************* + + /** + * Creates a directive registration function where a possible dynamic attribute + * value will be observed/watched. + * @param {string} className attribute name; eg `layout-gt-md` with value ="row" + */ + function attributeWithObserve(className) { + + return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) { + $mdUtil = _$mdUtil_; + $interpolate = _$interpolate_; + $log = _$log_; + + return { + restrict: 'A', + compile: function(element, attr) { + var linkFn; + if (config.enabled) { + // immediately replace static (non-interpolated) invalid values... + + validateAttributeUsage(className, attr, element, $log); + + validateAttributeValue( className, + getNormalizedAttrValue(className, attr, ""), + buildUpdateFn(element, className, attr) + ); + + linkFn = translateWithValueToCssClass; + } + + // Use for postLink to account for transforms after ng-transclude. + return linkFn || angular.noop; + } + }; + }]; + + /** + * Add as transformed class selector(s), then + * remove the deprecated attribute selector + */ + function translateWithValueToCssClass(scope, element, attrs) { + var updateFn = updateClassWithValue(element, className, attrs); + var unwatch = attrs.$observe(attrs.$normalize(className), updateFn); + + updateFn(getNormalizedAttrValue(className, attrs, "")); + scope.$on("$destroy", function() { unwatch(); }); + } + } + + /** + * Creates a registration function for ngMaterial Layout attribute directive. + * This is a `simple` transpose of attribute usage to class usage; where we ignore + * any attribute value + */ + function attributeWithoutValue(className) { + return ['$mdUtil', '$interpolate', "$log", function(_$mdUtil_, _$interpolate_, _$log_) { + $mdUtil = _$mdUtil_; + $interpolate = _$interpolate_; + $log = _$log_; + + return { + restrict: 'A', + compile: function(element, attr) { + var linkFn; + if (config.enabled) { + // immediately replace static (non-interpolated) invalid values... + + validateAttributeValue( className, + getNormalizedAttrValue(className, attr, ""), + buildUpdateFn(element, className, attr) + ); + + translateToCssClass(null, element); + + // Use for postLink to account for transforms after ng-transclude. + linkFn = translateToCssClass; + } + + return linkFn || angular.noop; + } + }; + }]; + + /** + * Add as transformed class selector, then + * remove the deprecated attribute selector + */ + function translateToCssClass(scope, element) { + element.addClass(className); + } + } + + + + /** + * After link-phase, do NOT remove deprecated layout attribute selector. + * Instead watch the attribute so interpolated data-bindings to layout + * selectors will continue to be supported. + * + * $observe() the className and update with new class (after removing the last one) + * + * e.g. `layout="{{layoutDemo.direction}}"` will update... + * + * NOTE: The value must match one of the specified styles in the CSS. + * For example `flex-gt-md="{{size}}` where `scope.size == 47` will NOT work since + * only breakpoints for 0, 5, 10, 15... 100, 33, 34, 66, 67 are defined. + * + */ + function updateClassWithValue(element, className) { + var lastClass; + + return function updateClassFn(newValue) { + var value = validateAttributeValue(className, newValue || ""); + if ( angular.isDefined(value) ) { + if (lastClass) element.removeClass(lastClass); + lastClass = !value ? className : className + "-" + value.replace(WHITESPACE, "-"); + element.addClass(lastClass); + } + }; + } + + /** + * Provide console warning that this layout attribute has been deprecated + * + */ + function warnAttrNotSupported(className) { + var parts = className.split("-"); + return ["$log", function($log) { + $log.warn(className + "has been deprecated. Please use a `" + parts[0] + "-gt-` variant."); + return angular.noop; + }]; + } + + /** + * Centralize warnings for known flexbox issues (especially IE-related issues) + */ + function validateAttributeUsage(className, attr, element, $log){ + var message, usage, url; + var nodeName = element[0].nodeName.toLowerCase(); + + switch(className.replace(SUFFIXES,"")) { + case "flex": + if ((nodeName == "md-button") || (nodeName == "fieldset")){ + // @see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers + // Use
wrapper inside (preferred) or outside + + usage = "<" + nodeName + " " + className + ">"; + url = "https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers"; + message = "Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details."; + + $log.warn( $mdUtil.supplant(message, [usage, url]) ); + } + } + + } + + + /** + * For the Layout attribute value, validate or replace with default + * fallback value + */ + function validateAttributeValue(className, value, updateFn) { + var origValue = value; + + if (!needsInterpolation(value)) { + switch (className.replace(SUFFIXES,"")) { + case 'layout' : + if ( !findIn(value, LAYOUT_OPTIONS) ) { + value = LAYOUT_OPTIONS[0]; // 'row'; + } + break; + + case 'flex' : + if (!findIn(value, FLEX_OPTIONS)) { + if (isNaN(value)) { + value = ''; + } + } + break; + + case 'flex-offset' : + case 'flex-order' : + if (!value || isNaN(+value)) { + value = '0'; + } + break; + + case 'layout-align' : + var axis = extractAlignAxis(value); + value = $mdUtil.supplant("{main}-{cross}",axis); + break; + + case 'layout-padding' : + case 'layout-margin' : + case 'layout-fill' : + case 'layout-wrap' : + case 'layout-nowrap' : + case 'layout-nowrap' : + value = ''; + break; + } + + if (value != origValue) { + (updateFn || angular.noop)(value); + } + } + + return value; + } + + /** + * Replace current attribute value with fallback value + */ + function buildUpdateFn(element, className, attrs) { + return function updateAttrValue(fallback) { + if (!needsInterpolation(fallback)) { + // Do not modify the element's attribute value; so + // uses '' will not + // be affected. Just update the attrs value. + attrs[attrs.$normalize(className)] = fallback; + } + }; + } + + /** + * See if the original value has interpolation symbols: + * e.g. flex-gt-md="{{triggerPoint}}" + */ + function needsInterpolation(value) { + return (value || "").indexOf($interpolate.startSymbol()) > -1; + } + + function getNormalizedAttrValue(className, attrs, defaultVal) { + var normalizedAttr = attrs.$normalize(className); + return attrs[normalizedAttr] ? attrs[normalizedAttr].replace(WHITESPACE, "-") : defaultVal || null; + } + + function findIn(item, list, replaceWith) { + item = replaceWith && item ? item.replace(WHITESPACE, replaceWith) : item; + + var found = false; + if (item) { + list.forEach(function(it) { + it = replaceWith ? it.replace(WHITESPACE, replaceWith) : it; + found = found || (it === item); + }); + } + return found; + } + + function extractAlignAxis(attrValue) { + var axis = { + main : "start", + cross: "stretch" + }, values; + + attrValue = (attrValue || ""); + + if ( attrValue.indexOf("-") === 0 || attrValue.indexOf(" ") === 0) { + // For missing main-axis values + attrValue = "none" + attrValue; + } + + values = attrValue.toLowerCase().trim().replace(WHITESPACE, "-").split("-"); + if ( values.length && (values[0] === "space") ) { + // for main-axis values of "space-around" or "space-between" + values = [ values[0]+"-"+values[1],values[2] ]; + } + + if ( values.length > 0 ) axis.main = values[0] || axis.main; + if ( values.length > 1 ) axis.cross = values[1] || axis.cross; + + if ( ALIGNMENT_MAIN_AXIS.indexOf(axis.main) < 0 ) axis.main = "start"; + if ( ALIGNMENT_CROSS_AXIS.indexOf(axis.cross) < 0 ) axis.cross = "stretch"; + + return axis; + } + + +})(); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc service + * @name $$mdMeta + * @module material.core.meta + * + * @description + * + * A provider and a service that simplifies meta tags access + * + * Note: This is intended only for use with dynamic meta tags such as browser color and title. + * Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`) + * will not work since `$$mdMeta` adds the tags after the page has already been loaded. + * + * ```js + * app.config(function($$mdMetaProvider) { + * var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content'); + * var metaValue = $$mdMetaProvider.getMeta('meta-name'); // -> 'content' + * + * removeMeta(); + * }); + * + * app.controller('myController', function($$mdMeta) { + * var removeMeta = $$mdMeta.setMeta('meta-name', 'content'); + * var metaValue = $$mdMeta.getMeta('meta-name'); // -> 'content' + * + * removeMeta(); + * }); + * ``` + * + * @returns {$$mdMeta.$service} + * + */ +angular.module('material.core.meta', []) + .provider('$$mdMeta', function () { + var head = angular.element(document.head); + var metaElements = {}; + + /** + * Checks if the requested element was written manually and maps it + * + * @param {string} name meta tag 'name' attribute value + * @returns {boolean} returns true if there is an element with the requested name + */ + function mapExistingElement(name) { + if (metaElements[name]) { + return true; + } + + var element = document.getElementsByName(name)[0]; + + if (!element) { + return false; + } + + metaElements[name] = angular.element(element); + + return true; + } + + /** + * @ngdoc method + * @name $$mdMeta#setMeta + * + * @description + * Creates meta element with the 'name' and 'content' attributes, + * if the meta tag is already created than we replace the 'content' value + * + * @param {string} name meta tag 'name' attribute value + * @param {string} content meta tag 'content' attribute value + * @returns {function} remove function + * + */ + function setMeta(name, content) { + mapExistingElement(name); + + if (!metaElements[name]) { + var newMeta = angular.element(''); + head.append(newMeta); + metaElements[name] = newMeta; + } + else { + metaElements[name].attr('content', content); + } + + return function () { + metaElements[name].attr('content', ''); + metaElements[name].remove(); + delete metaElements[name]; + }; + } + + /** + * @ngdoc method + * @name $$mdMeta#getMeta + * + * @description + * Gets the 'content' attribute value of the wanted meta element + * + * @param {string} name meta tag 'name' attribute value + * @returns {string} content attribute value + */ + function getMeta(name) { + if (!mapExistingElement(name)) { + throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\''); + } + + return metaElements[name].attr('content'); + } + + var module = { + setMeta: setMeta, + getMeta: getMeta + }; + + return angular.extend({}, module, { + $get: function () { + return module; + } + }); + }); +})(); +(function(){ +"use strict"; + + /** + * @ngdoc module + * @name material.core.componentRegistry + * + * @description + * A component instance registration service. + * Note: currently this as a private service in the SideNav component. + */ + ComponentRegistry.$inject = ["$log", "$q"]; + angular.module('material.core') + .factory('$mdComponentRegistry', ComponentRegistry); + + /* + * @private + * @ngdoc factory + * @name ComponentRegistry + * @module material.core.componentRegistry + * + */ + function ComponentRegistry($log, $q) { + + var self; + var instances = [ ]; + var pendings = { }; + + return self = { + /** + * Used to print an error when an instance for a handle isn't found. + */ + notFoundError: function(handle, msgContext) { + $log.error( (msgContext || "") + 'No instance found for handle', handle); + }, + /** + * Return all registered instances as an array. + */ + getInstances: function() { + return instances; + }, + + /** + * Get a registered instance. + * @param handle the String handle to look up for a registered instance. + */ + get: function(handle) { + if ( !isValidID(handle) ) return null; + + var i, j, instance; + for(i = 0, j = instances.length; i < j; i++) { + instance = instances[i]; + if(instance.$$mdHandle === handle) { + return instance; + } + } + return null; + }, + + /** + * Register an instance. + * @param instance the instance to register + * @param handle the handle to identify the instance under. + */ + register: function(instance, handle) { + if ( !handle ) return angular.noop; + + instance.$$mdHandle = handle; + instances.push(instance); + resolveWhen(); + + return deregister; + + /** + * Remove registration for an instance + */ + function deregister() { + var index = instances.indexOf(instance); + if (index !== -1) { + instances.splice(index, 1); + } + } + + /** + * Resolve any pending promises for this instance + */ + function resolveWhen() { + var dfd = pendings[handle]; + if ( dfd ) { + dfd.forEach(function (promise) { + promise.resolve(instance); + }); + delete pendings[handle]; + } + } + }, + + /** + * Async accessor to registered component instance + * If not available then a promise is created to notify + * all listeners when the instance is registered. + */ + when : function(handle) { + if ( isValidID(handle) ) { + var deferred = $q.defer(); + var instance = self.get(handle); + + if ( instance ) { + deferred.resolve( instance ); + } else { + if (pendings[handle] === undefined) { + pendings[handle] = []; + } + pendings[handle].push(deferred); + } + + return deferred.promise; + } + return $q.reject("Invalid `md-component-id` value."); + } + + }; + + function isValidID(handle){ + return handle && (handle !== ""); + } + + } + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdButtonInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-button. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the default ripple configuration + */ + + MdButtonInkRipple.$inject = ["$mdInkRipple"]; + angular.module('material.core') + .factory('$mdButtonInkRipple', MdButtonInkRipple); + + function MdButtonInkRipple($mdInkRipple) { + return { + attach: function attachRipple(scope, element, options) { + options = angular.extend(optionsForElement(element), options); + + return $mdInkRipple.attach(scope, element, options); + } + }; + + function optionsForElement(element) { + if (element.hasClass('md-icon-button')) { + return { + isMenuItem: element.hasClass('md-menu-item'), + fitRipple: true, + center: true + }; + } else { + return { + isMenuItem: element.hasClass('md-menu-item'), + dimBackground: true + } + } + }; + }; +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdCheckboxInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-checkbox. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + MdCheckboxInkRipple.$inject = ["$mdInkRipple"]; + angular.module('material.core') + .factory('$mdCheckboxInkRipple', MdCheckboxInkRipple); + + function MdCheckboxInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + return $mdInkRipple.attach(scope, element, angular.extend({ + center: true, + dimBackground: false, + fitRipple: true + }, options)); + }; + }; +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdListInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-list. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + MdListInkRipple.$inject = ["$mdInkRipple"]; + angular.module('material.core') + .factory('$mdListInkRipple', MdListInkRipple); + + function MdListInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + return $mdInkRipple.attach(scope, element, angular.extend({ + center: false, + dimBackground: true, + outline: false, + rippleSize: 'full' + }, options)); + }; + }; +})(); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.core.ripple + * @description + * Ripple + */ +InkRippleCtrl.$inject = ["$scope", "$element", "rippleOptions", "$window", "$timeout", "$mdUtil", "$mdColorUtil"]; +InkRippleDirective.$inject = ["$mdButtonInkRipple", "$mdCheckboxInkRipple"]; +angular.module('material.core') + .provider('$mdInkRipple', InkRippleProvider) + .directive('mdInkRipple', InkRippleDirective) + .directive('mdNoInk', attrNoDirective) + .directive('mdNoBar', attrNoDirective) + .directive('mdNoStretch', attrNoDirective); + +var DURATION = 450; + +/** + * @ngdoc directive + * @name mdInkRipple + * @module material.core.ripple + * + * @description + * The `md-ink-ripple` directive allows you to specify the ripple color or id a ripple is allowed. + * + * @param {string|boolean} md-ink-ripple A color string `#FF0000` or boolean (`false` or `0`) for preventing ripple + * + * @usage + * ### String values + * + * + * Ripples in red + * + * + * + * Not rippling + * + * + * + * ### Interpolated values + * + * + * Ripples with the return value of 'randomColor' function + * + * + * + * Ripples if 'canRipple' function return value is not 'false' or '0' + * + * + */ +function InkRippleDirective ($mdButtonInkRipple, $mdCheckboxInkRipple) { + return { + controller: angular.noop, + link: function (scope, element, attr) { + attr.hasOwnProperty('mdInkRippleCheckbox') + ? $mdCheckboxInkRipple.attach(scope, element) + : $mdButtonInkRipple.attach(scope, element); + } + }; +} + +/** + * @ngdoc service + * @name $mdInkRipple + * @module material.core.ripple + * + * @description + * `$mdInkRipple` is a service for adding ripples to any element + * + * @usage + * + * app.factory('$myElementInkRipple', function($mdInkRipple) { + * return { + * attach: function (scope, element, options) { + * return $mdInkRipple.attach(scope, element, angular.extend({ + * center: false, + * dimBackground: true + * }, options)); + * } + * }; + * }); + * + * app.controller('myController', function ($scope, $element, $myElementInkRipple) { + * $scope.onClick = function (ev) { + * $myElementInkRipple.attach($scope, angular.element(ev.target), { center: true }); + * } + * }); + * + * + * ### Disabling ripples globally + * If you want to disable ink ripples globally, for all components, you can call the + * `disableInkRipple` method in your app's config. + * + * + * app.config(function ($mdInkRippleProvider) { + * $mdInkRippleProvider.disableInkRipple(); + * }); + */ + +function InkRippleProvider () { + var isDisabledGlobally = false; + + return { + disableInkRipple: disableInkRipple, + $get: ["$injector", function($injector) { + return { attach: attach }; + + /** + * @ngdoc method + * @name $mdInkRipple#attach + * + * @description + * Attaching given scope, element and options to inkRipple controller + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultRipple configuration + * * `center` - Whether the ripple should start from the center of the container element + * * `dimBackground` - Whether the background should be dimmed with the ripple color + * * `colorElement` - The element the ripple should take its color from, defined by css property `color` + * * `fitRipple` - Whether the ripple should fill the element + */ + function attach (scope, element, options) { + if (isDisabledGlobally || element.controller('mdNoInk')) return angular.noop; + return $injector.instantiate(InkRippleCtrl, { + $scope: scope, + $element: element, + rippleOptions: options + }); + } + }] + }; + + /** + * @ngdoc method + * @name $mdInkRipple#disableInkRipple + * + * @description + * A config-time method that, when called, disables ripples globally. + */ + function disableInkRipple () { + isDisabledGlobally = true; + } +} + +/** + * Controller used by the ripple service in order to apply ripples + * @ngInject + */ +function InkRippleCtrl ($scope, $element, rippleOptions, $window, $timeout, $mdUtil, $mdColorUtil) { + this.$window = $window; + this.$timeout = $timeout; + this.$mdUtil = $mdUtil; + this.$mdColorUtil = $mdColorUtil; + this.$scope = $scope; + this.$element = $element; + this.options = rippleOptions; + this.mousedown = false; + this.ripples = []; + this.timeout = null; // Stores a reference to the most-recent ripple timeout + this.lastRipple = null; + + $mdUtil.valueOnUse(this, 'container', this.createContainer); + + this.$element.addClass('md-ink-ripple'); + + // attach method for unit tests + ($element.controller('mdInkRipple') || {}).createRipple = angular.bind(this, this.createRipple); + ($element.controller('mdInkRipple') || {}).setColor = angular.bind(this, this.color); + + this.bindEvents(); +} + + +/** + * Either remove or unlock any remaining ripples when the user mouses off of the element (either by + * mouseup or mouseleave event) + */ +function autoCleanup (self, cleanupFn) { + + if ( self.mousedown || self.lastRipple ) { + self.mousedown = false; + self.$mdUtil.nextTick( angular.bind(self, cleanupFn), false); + } + +} + + +/** + * Returns the color that the ripple should be (either based on CSS or hard-coded) + * @returns {string} + */ +InkRippleCtrl.prototype.color = function (value) { + var self = this; + + // If assigning a color value, apply it to background and the ripple color + if (angular.isDefined(value)) { + self._color = self._parseColor(value); + } + + // If color lookup, use assigned, defined, or inherited + return self._color || self._parseColor( self.inkRipple() ) || self._parseColor( getElementColor() ); + + /** + * Finds the color element and returns its text color for use as default ripple color + * @returns {string} + */ + function getElementColor () { + var items = self.options && self.options.colorElement ? self.options.colorElement : []; + var elem = items.length ? items[ 0 ] : self.$element[ 0 ]; + + return elem ? self.$window.getComputedStyle(elem).color : 'rgb(0,0,0)'; + } +}; + +/** + * Updating the ripple colors based on the current inkRipple value + * or the element's computed style color + */ +InkRippleCtrl.prototype.calculateColor = function () { + return this.color(); +}; + + +/** + * Takes a string color and converts it to RGBA format + * @param color {string} + * @param [multiplier] {int} + * @returns {string} + */ + +InkRippleCtrl.prototype._parseColor = function parseColor (color, multiplier) { + multiplier = multiplier || 1; + var colorUtil = this.$mdColorUtil; + + if (!color) return; + if (color.indexOf('rgba') === 0) return color.replace(/\d?\.?\d*\s*\)\s*$/, (0.1 * multiplier).toString() + ')'); + if (color.indexOf('rgb') === 0) return colorUtil.rgbToRgba(color); + if (color.indexOf('#') === 0) return colorUtil.hexToRgba(color); + +}; + +/** + * Binds events to the root element for + */ +InkRippleCtrl.prototype.bindEvents = function () { + this.$element.on('mousedown', angular.bind(this, this.handleMousedown)); + this.$element.on('mouseup touchend', angular.bind(this, this.handleMouseup)); + this.$element.on('mouseleave', angular.bind(this, this.handleMouseup)); + this.$element.on('touchmove', angular.bind(this, this.handleTouchmove)); +}; + +/** + * Create a new ripple on every mousedown event from the root element + * @param event {MouseEvent} + */ +InkRippleCtrl.prototype.handleMousedown = function (event) { + if ( this.mousedown ) return; + + // When jQuery is loaded, we have to get the original event + if (event.hasOwnProperty('originalEvent')) event = event.originalEvent; + this.mousedown = true; + if (this.options.center) { + this.createRipple(this.container.prop('clientWidth') / 2, this.container.prop('clientWidth') / 2); + } else { + + // We need to calculate the relative coordinates if the target is a sublayer of the ripple element + if (event.srcElement !== this.$element[0]) { + var layerRect = this.$element[0].getBoundingClientRect(); + var layerX = event.clientX - layerRect.left; + var layerY = event.clientY - layerRect.top; + + this.createRipple(layerX, layerY); + } else { + this.createRipple(event.offsetX, event.offsetY); + } + } +}; + +/** + * Either remove or unlock any remaining ripples when the user mouses off of the element (either by + * mouseup, touchend or mouseleave event) + */ +InkRippleCtrl.prototype.handleMouseup = function () { + autoCleanup(this, this.clearRipples); +}; + +/** + * Either remove or unlock any remaining ripples when the user mouses off of the element (by + * touchmove) + */ +InkRippleCtrl.prototype.handleTouchmove = function () { + autoCleanup(this, this.deleteRipples); +}; + +/** + * Cycles through all ripples and attempts to remove them. + */ +InkRippleCtrl.prototype.deleteRipples = function () { + for (var i = 0; i < this.ripples.length; i++) { + this.ripples[ i ].remove(); + } +}; + +/** + * Cycles through all ripples and attempts to remove them with fade. + * Depending on logic within `fadeInComplete`, some removals will be postponed. + */ +InkRippleCtrl.prototype.clearRipples = function () { + for (var i = 0; i < this.ripples.length; i++) { + this.fadeInComplete(this.ripples[ i ]); + } +}; + +/** + * Creates the ripple container element + * @returns {*} + */ +InkRippleCtrl.prototype.createContainer = function () { + var container = angular.element('
'); + this.$element.append(container); + return container; +}; + +InkRippleCtrl.prototype.clearTimeout = function () { + if (this.timeout) { + this.$timeout.cancel(this.timeout); + this.timeout = null; + } +}; + +InkRippleCtrl.prototype.isRippleAllowed = function () { + var element = this.$element[0]; + do { + if (!element.tagName || element.tagName === 'BODY') break; + + if (element && angular.isFunction(element.hasAttribute)) { + if (element.hasAttribute('disabled')) return false; + if (this.inkRipple() === 'false' || this.inkRipple() === '0') return false; + } + + } while (element = element.parentNode); + return true; +}; + +/** + * The attribute `md-ink-ripple` may be a static or interpolated + * color value OR a boolean indicator (used to disable ripples) + */ +InkRippleCtrl.prototype.inkRipple = function () { + return this.$element.attr('md-ink-ripple'); +}; + +/** + * Creates a new ripple and adds it to the container. Also tracks ripple in `this.ripples`. + * @param left + * @param top + */ +InkRippleCtrl.prototype.createRipple = function (left, top) { + if (!this.isRippleAllowed()) return; + + var ctrl = this; + var colorUtil = ctrl.$mdColorUtil; + var ripple = angular.element('
'); + var width = this.$element.prop('clientWidth'); + var height = this.$element.prop('clientHeight'); + var x = Math.max(Math.abs(width - left), left) * 2; + var y = Math.max(Math.abs(height - top), top) * 2; + var size = getSize(this.options.fitRipple, x, y); + var color = this.calculateColor(); + + ripple.css({ + left: left + 'px', + top: top + 'px', + background: 'black', + width: size + 'px', + height: size + 'px', + backgroundColor: colorUtil.rgbaToRgb(color), + borderColor: colorUtil.rgbaToRgb(color) + }); + this.lastRipple = ripple; + + // we only want one timeout to be running at a time + this.clearTimeout(); + this.timeout = this.$timeout(function () { + ctrl.clearTimeout(); + if (!ctrl.mousedown) ctrl.fadeInComplete(ripple); + }, DURATION * 0.35, false); + + if (this.options.dimBackground) this.container.css({ backgroundColor: color }); + this.container.append(ripple); + this.ripples.push(ripple); + ripple.addClass('md-ripple-placed'); + + this.$mdUtil.nextTick(function () { + + ripple.addClass('md-ripple-scaled md-ripple-active'); + ctrl.$timeout(function () { + ctrl.clearRipples(); + }, DURATION, false); + + }, false); + + function getSize (fit, x, y) { + return fit + ? Math.max(x, y) + : Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + } +}; + + + +/** + * After fadeIn finishes, either kicks off the fade-out animation or queues the element for removal on mouseup + * @param ripple + */ +InkRippleCtrl.prototype.fadeInComplete = function (ripple) { + if (this.lastRipple === ripple) { + if (!this.timeout && !this.mousedown) { + this.removeRipple(ripple); + } + } else { + this.removeRipple(ripple); + } +}; + +/** + * Kicks off the animation for removing a ripple + * @param ripple {Element} + */ +InkRippleCtrl.prototype.removeRipple = function (ripple) { + var ctrl = this; + var index = this.ripples.indexOf(ripple); + if (index < 0) return; + this.ripples.splice(this.ripples.indexOf(ripple), 1); + ripple.removeClass('md-ripple-active'); + ripple.addClass('md-ripple-remove'); + if (this.ripples.length === 0) this.container.css({ backgroundColor: '' }); + // use a 2-second timeout in order to allow for the animation to finish + // we don't actually care how long the animation takes + this.$timeout(function () { + ctrl.fadeOutComplete(ripple); + }, DURATION, false); +}; + +/** + * Removes the provided ripple from the DOM + * @param ripple + */ +InkRippleCtrl.prototype.fadeOutComplete = function (ripple) { + ripple.remove(); + this.lastRipple = null; +}; + +/** + * Used to create an empty directive. This is used to track flag-directives whose children may have + * functionality based on them. + * + * Example: `md-no-ink` will potentially be used by all child directives. + */ +function attrNoDirective () { + return { controller: angular.noop }; +} + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc service + * @name $mdTabInkRipple + * @module material.core + * + * @description + * Provides ripple effects for md-tabs. See $mdInkRipple service for all possible configuration options. + * + * @param {object=} scope Scope within the current context + * @param {object=} element The element the ripple effect should be applied to + * @param {object=} options (Optional) Configuration options to override the defaultripple configuration + */ + + MdTabInkRipple.$inject = ["$mdInkRipple"]; + angular.module('material.core') + .factory('$mdTabInkRipple', MdTabInkRipple); + + function MdTabInkRipple($mdInkRipple) { + return { + attach: attach + }; + + function attach(scope, element, options) { + return $mdInkRipple.attach(scope, element, angular.extend({ + center: false, + dimBackground: true, + outline: false, + rippleSize: 'full' + }, options)); + }; + }; +})(); + +})(); +(function(){ +"use strict"; + +angular.module('material.core.theming.palette', []) +.constant('$mdColorPalette', { + 'red': { + '50': '#ffebee', + '100': '#ffcdd2', + '200': '#ef9a9a', + '300': '#e57373', + '400': '#ef5350', + '500': '#f44336', + '600': '#e53935', + '700': '#d32f2f', + '800': '#c62828', + '900': '#b71c1c', + 'A100': '#ff8a80', + 'A200': '#ff5252', + 'A400': '#ff1744', + 'A700': '#d50000', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 A100', + 'contrastStrongLightColors': '400 500 600 700 A200 A400 A700' + }, + 'pink': { + '50': '#fce4ec', + '100': '#f8bbd0', + '200': '#f48fb1', + '300': '#f06292', + '400': '#ec407a', + '500': '#e91e63', + '600': '#d81b60', + '700': '#c2185b', + '800': '#ad1457', + '900': '#880e4f', + 'A100': '#ff80ab', + 'A200': '#ff4081', + 'A400': '#f50057', + 'A700': '#c51162', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '500 600 A200 A400 A700' + }, + 'purple': { + '50': '#f3e5f5', + '100': '#e1bee7', + '200': '#ce93d8', + '300': '#ba68c8', + '400': '#ab47bc', + '500': '#9c27b0', + '600': '#8e24aa', + '700': '#7b1fa2', + '800': '#6a1b9a', + '900': '#4a148c', + 'A100': '#ea80fc', + 'A200': '#e040fb', + 'A400': '#d500f9', + 'A700': '#aa00ff', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '300 400 A200 A400 A700' + }, + 'deep-purple': { + '50': '#ede7f6', + '100': '#d1c4e9', + '200': '#b39ddb', + '300': '#9575cd', + '400': '#7e57c2', + '500': '#673ab7', + '600': '#5e35b1', + '700': '#512da8', + '800': '#4527a0', + '900': '#311b92', + 'A100': '#b388ff', + 'A200': '#7c4dff', + 'A400': '#651fff', + 'A700': '#6200ea', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '300 400 A200' + }, + 'indigo': { + '50': '#e8eaf6', + '100': '#c5cae9', + '200': '#9fa8da', + '300': '#7986cb', + '400': '#5c6bc0', + '500': '#3f51b5', + '600': '#3949ab', + '700': '#303f9f', + '800': '#283593', + '900': '#1a237e', + 'A100': '#8c9eff', + 'A200': '#536dfe', + 'A400': '#3d5afe', + 'A700': '#304ffe', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100', + 'contrastStrongLightColors': '300 400 A200 A400' + }, + 'blue': { + '50': '#e3f2fd', + '100': '#bbdefb', + '200': '#90caf9', + '300': '#64b5f6', + '400': '#42a5f5', + '500': '#2196f3', + '600': '#1e88e5', + '700': '#1976d2', + '800': '#1565c0', + '900': '#0d47a1', + 'A100': '#82b1ff', + 'A200': '#448aff', + 'A400': '#2979ff', + 'A700': '#2962ff', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 400 A100', + 'contrastStrongLightColors': '500 600 700 A200 A400 A700' + }, + 'light-blue': { + '50': '#e1f5fe', + '100': '#b3e5fc', + '200': '#81d4fa', + '300': '#4fc3f7', + '400': '#29b6f6', + '500': '#03a9f4', + '600': '#039be5', + '700': '#0288d1', + '800': '#0277bd', + '900': '#01579b', + 'A100': '#80d8ff', + 'A200': '#40c4ff', + 'A400': '#00b0ff', + 'A700': '#0091ea', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '600 700 800 900 A700', + 'contrastStrongLightColors': '600 700 800 A700' + }, + 'cyan': { + '50': '#e0f7fa', + '100': '#b2ebf2', + '200': '#80deea', + '300': '#4dd0e1', + '400': '#26c6da', + '500': '#00bcd4', + '600': '#00acc1', + '700': '#0097a7', + '800': '#00838f', + '900': '#006064', + 'A100': '#84ffff', + 'A200': '#18ffff', + 'A400': '#00e5ff', + 'A700': '#00b8d4', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '700 800 900', + 'contrastStrongLightColors': '700 800 900' + }, + 'teal': { + '50': '#e0f2f1', + '100': '#b2dfdb', + '200': '#80cbc4', + '300': '#4db6ac', + '400': '#26a69a', + '500': '#009688', + '600': '#00897b', + '700': '#00796b', + '800': '#00695c', + '900': '#004d40', + 'A100': '#a7ffeb', + 'A200': '#64ffda', + 'A400': '#1de9b6', + 'A700': '#00bfa5', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '500 600 700 800 900', + 'contrastStrongLightColors': '500 600 700' + }, + 'green': { + '50': '#e8f5e9', + '100': '#c8e6c9', + '200': '#a5d6a7', + '300': '#81c784', + '400': '#66bb6a', + '500': '#4caf50', + '600': '#43a047', + '700': '#388e3c', + '800': '#2e7d32', + '900': '#1b5e20', + 'A100': '#b9f6ca', + 'A200': '#69f0ae', + 'A400': '#00e676', + 'A700': '#00c853', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '500 600 700 800 900', + 'contrastStrongLightColors': '500 600 700' + }, + 'light-green': { + '50': '#f1f8e9', + '100': '#dcedc8', + '200': '#c5e1a5', + '300': '#aed581', + '400': '#9ccc65', + '500': '#8bc34a', + '600': '#7cb342', + '700': '#689f38', + '800': '#558b2f', + '900': '#33691e', + 'A100': '#ccff90', + 'A200': '#b2ff59', + 'A400': '#76ff03', + 'A700': '#64dd17', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '700 800 900', + 'contrastStrongLightColors': '700 800 900' + }, + 'lime': { + '50': '#f9fbe7', + '100': '#f0f4c3', + '200': '#e6ee9c', + '300': '#dce775', + '400': '#d4e157', + '500': '#cddc39', + '600': '#c0ca33', + '700': '#afb42b', + '800': '#9e9d24', + '900': '#827717', + 'A100': '#f4ff81', + 'A200': '#eeff41', + 'A400': '#c6ff00', + 'A700': '#aeea00', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '900', + 'contrastStrongLightColors': '900' + }, + 'yellow': { + '50': '#fffde7', + '100': '#fff9c4', + '200': '#fff59d', + '300': '#fff176', + '400': '#ffee58', + '500': '#ffeb3b', + '600': '#fdd835', + '700': '#fbc02d', + '800': '#f9a825', + '900': '#f57f17', + 'A100': '#ffff8d', + 'A200': '#ffff00', + 'A400': '#ffea00', + 'A700': '#ffd600', + 'contrastDefaultColor': 'dark' + }, + 'amber': { + '50': '#fff8e1', + '100': '#ffecb3', + '200': '#ffe082', + '300': '#ffd54f', + '400': '#ffca28', + '500': '#ffc107', + '600': '#ffb300', + '700': '#ffa000', + '800': '#ff8f00', + '900': '#ff6f00', + 'A100': '#ffe57f', + 'A200': '#ffd740', + 'A400': '#ffc400', + 'A700': '#ffab00', + 'contrastDefaultColor': 'dark' + }, + 'orange': { + '50': '#fff3e0', + '100': '#ffe0b2', + '200': '#ffcc80', + '300': '#ffb74d', + '400': '#ffa726', + '500': '#ff9800', + '600': '#fb8c00', + '700': '#f57c00', + '800': '#ef6c00', + '900': '#e65100', + 'A100': '#ffd180', + 'A200': '#ffab40', + 'A400': '#ff9100', + 'A700': '#ff6d00', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '800 900', + 'contrastStrongLightColors': '800 900' + }, + 'deep-orange': { + '50': '#fbe9e7', + '100': '#ffccbc', + '200': '#ffab91', + '300': '#ff8a65', + '400': '#ff7043', + '500': '#ff5722', + '600': '#f4511e', + '700': '#e64a19', + '800': '#d84315', + '900': '#bf360c', + 'A100': '#ff9e80', + 'A200': '#ff6e40', + 'A400': '#ff3d00', + 'A700': '#dd2c00', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 400 A100 A200', + 'contrastStrongLightColors': '500 600 700 800 900 A400 A700' + }, + 'brown': { + '50': '#efebe9', + '100': '#d7ccc8', + '200': '#bcaaa4', + '300': '#a1887f', + '400': '#8d6e63', + '500': '#795548', + '600': '#6d4c41', + '700': '#5d4037', + '800': '#4e342e', + '900': '#3e2723', + 'A100': '#d7ccc8', + 'A200': '#bcaaa4', + 'A400': '#8d6e63', + 'A700': '#5d4037', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 A100 A200', + 'contrastStrongLightColors': '300 400' + }, + 'grey': { + '50': '#fafafa', + '100': '#f5f5f5', + '200': '#eeeeee', + '300': '#e0e0e0', + '400': '#bdbdbd', + '500': '#9e9e9e', + '600': '#757575', + '700': '#616161', + '800': '#424242', + '900': '#212121', + 'A100': '#ffffff', + 'A200': '#000000', + 'A400': '#303030', + 'A700': '#616161', + 'contrastDefaultColor': 'dark', + 'contrastLightColors': '600 700 800 900 A200 A400 A700' + }, + 'blue-grey': { + '50': '#eceff1', + '100': '#cfd8dc', + '200': '#b0bec5', + '300': '#90a4ae', + '400': '#78909c', + '500': '#607d8b', + '600': '#546e7a', + '700': '#455a64', + '800': '#37474f', + '900': '#263238', + 'A100': '#cfd8dc', + 'A200': '#b0bec5', + 'A400': '#78909c', + 'A700': '#455a64', + 'contrastDefaultColor': 'light', + 'contrastDarkColors': '50 100 200 300 A100 A200', + 'contrastStrongLightColors': '400 500 700' + } +}); + +})(); +(function(){ +"use strict"; + +(function(angular) { + 'use strict'; +/** + * @ngdoc module + * @name material.core.theming + * @description + * Theming + */ +detectDisabledThemes.$inject = ["$mdThemingProvider"]; +ThemingDirective.$inject = ["$mdTheming", "$interpolate", "$log"]; +ThemableDirective.$inject = ["$mdTheming"]; +ThemingProvider.$inject = ["$mdColorPalette", "$$mdMetaProvider"]; +generateAllThemes.$inject = ["$injector", "$mdTheming"]; +angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta']) + .directive('mdTheme', ThemingDirective) + .directive('mdThemable', ThemableDirective) + .directive('mdThemesDisabled', disableThemesDirective ) + .provider('$mdTheming', ThemingProvider) + .config( detectDisabledThemes ) + .run(generateAllThemes); + +/** + * Detect if the HTML or the BODY tags has a [md-themes-disabled] attribute + * If yes, then immediately disable all theme stylesheet generation and DOM injection + */ +/** + * @ngInject + */ +function detectDisabledThemes($mdThemingProvider) { + var isDisabled = !!document.querySelector('[md-themes-disabled]'); + $mdThemingProvider.disableTheming(isDisabled); +} + +/** + * @ngdoc service + * @name $mdThemingProvider + * @module material.core.theming + * + * @description Provider to configure the `$mdTheming` service. + * + * ### Default Theme + * The `$mdThemingProvider` uses by default the following theme configuration: + * + * - Primary Palette: `Primary` + * - Accent Palette: `Pink` + * - Warn Palette: `Deep-Orange` + * - Background Palette: `Grey` + * + * If you don't want to use the `md-theme` directive on the elements itself, you may want to overwrite + * the default theme.
+ * This can be done by using the following markup. + * + * + * myAppModule.config(function($mdThemingProvider) { + * $mdThemingProvider + * .theme('default') + * .primaryPalette('blue') + * .accentPalette('teal') + * .warnPalette('red') + * .backgroundPalette('grey'); + * }); + * + * + + * ### Dynamic Themes + * + * By default, if you change a theme at runtime, the `$mdTheming` service will not detect those changes.
+ * If you have an application, which changes its theme on runtime, you have to enable theme watching. + * + * + * myAppModule.config(function($mdThemingProvider) { + * // Enable theme watching. + * $mdThemingProvider.alwaysWatchTheme(true); + * }); + * + * + * ### Custom Theme Styles + * + * Sometimes you may want to use your own theme styles for some custom components.
+ * You are able to register your own styles by using the following markup. + * + * + * myAppModule.config(function($mdThemingProvider) { + * // Register our custom stylesheet into the theming provider. + * $mdThemingProvider.registerStyles(STYLESHEET); + * }); + * + * + * The `registerStyles` method only accepts strings as value, so you're actually not able to load an external + * stylesheet file into the `$mdThemingProvider`. + * + * If it's necessary to load an external stylesheet, we suggest using a bundler, which supports including raw content, + * like [raw-loader](https://github.com/webpack/raw-loader) for `webpack`. + * + * + * myAppModule.config(function($mdThemingProvider) { + * // Register your custom stylesheet into the theming provider. + * $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css')); + * }); + * + * + * ### Browser color + * + * Enables browser header coloring + * for more info please visit: + * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color + * + * Options parameter:
+ * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.
+ * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary', + * 'accent', 'background' and 'warn'. Default is `primary`.
+ * `hue` - The hue from the selected palette. Default is `800`
+ * + * + * myAppModule.config(function($mdThemingProvider) { + * // Enable browser color + * $mdThemingProvider.enableBrowserColor({ + * theme: 'myTheme', // Default is 'default' + * palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available + * hue: '200' // Default is '800' + * }); + * }); + * + */ + +/** + * @ngdoc method + * @name $mdThemingProvider#registerStyles + * @param {string} styles The styles to be appended to Angular Material's built in theme css. + */ +/** + * @ngdoc method + * @name $mdThemingProvider#setNonce + * @param {string} nonceValue The nonce to be added as an attribute to the theme style tags. + * Setting a value allows the use of CSP policy without using the unsafe-inline directive. + */ + +/** + * @ngdoc method + * @name $mdThemingProvider#setDefaultTheme + * @param {string} themeName Default theme name to be applied to elements. Default value is `default`. + */ + +/** + * @ngdoc method + * @name $mdThemingProvider#alwaysWatchTheme + * @param {boolean} watch Whether or not to always watch themes for changes and re-apply + * classes when they change. Default is `false`. Enabling can reduce performance. + */ + +/** + * @ngdoc method + * @name $mdThemingProvider#enableBrowserColor + * @param {Object=} options Options object for the browser color
+ * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme.
+ * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary', + * 'accent', 'background' and 'warn'. Default is `primary`.
+ * `hue` - The hue from the selected palette. Default is `800`
+ * @returns {Function} remove function of the browser color + */ + +/* Some Example Valid Theming Expressions + * ======================================= + * + * Intention group expansion: (valid for primary, accent, warn, background) + * + * {{primary-100}} - grab shade 100 from the primary palette + * {{primary-100-0.7}} - grab shade 100, apply opacity of 0.7 + * {{primary-100-contrast}} - grab shade 100's contrast color + * {{primary-hue-1}} - grab the shade assigned to hue-1 from the primary palette + * {{primary-hue-1-0.7}} - apply 0.7 opacity to primary-hue-1 + * {{primary-color}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured shades set for each hue + * {{primary-color-0.7}} - Apply 0.7 opacity to each of the above rules + * {{primary-contrast}} - Generates .md-hue-1, .md-hue-2, .md-hue-3 with configured contrast (ie. text) color shades set for each hue + * {{primary-contrast-0.7}} - Apply 0.7 opacity to each of the above rules + * + * Foreground expansion: Applies rgba to black/white foreground text + * + * {{foreground-1}} - used for primary text + * {{foreground-2}} - used for secondary text/divider + * {{foreground-3}} - used for disabled text + * {{foreground-4}} - used for dividers + * + */ + +// In memory generated CSS rules; registered by theme.name +var GENERATED = { }; + +// In memory storage of defined themes and color palettes (both loaded by CSS, and user specified) +var PALETTES; + +// Text Colors on light and dark backgrounds +// @see https://www.google.com/design/spec/style/color.html#color-text-background-colors +var DARK_FOREGROUND = { + name: 'dark', + '1': 'rgba(0,0,0,0.87)', + '2': 'rgba(0,0,0,0.54)', + '3': 'rgba(0,0,0,0.38)', + '4': 'rgba(0,0,0,0.12)' +}; +var LIGHT_FOREGROUND = { + name: 'light', + '1': 'rgba(255,255,255,1.0)', + '2': 'rgba(255,255,255,0.7)', + '3': 'rgba(255,255,255,0.5)', + '4': 'rgba(255,255,255,0.12)' +}; + +var DARK_SHADOW = '1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)'; +var LIGHT_SHADOW = ''; + +var DARK_CONTRAST_COLOR = colorToRgbaArray('rgba(0,0,0,0.87)'); +var LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgba(255,255,255,0.87)'); +var STRONG_LIGHT_CONTRAST_COLOR = colorToRgbaArray('rgb(255,255,255)'); + +var THEME_COLOR_TYPES = ['primary', 'accent', 'warn', 'background']; +var DEFAULT_COLOR_TYPE = 'primary'; + +// A color in a theme will use these hues by default, if not specified by user. +var LIGHT_DEFAULT_HUES = { + 'accent': { + 'default': 'A200', + 'hue-1': 'A100', + 'hue-2': 'A400', + 'hue-3': 'A700' + }, + 'background': { + 'default': '50', + 'hue-1': 'A100', + 'hue-2': '100', + 'hue-3': '300' + } +}; + +var DARK_DEFAULT_HUES = { + 'background': { + 'default': 'A400', + 'hue-1': '800', + 'hue-2': '900', + 'hue-3': 'A200' + } +}; +THEME_COLOR_TYPES.forEach(function(colorType) { + // Color types with unspecified default hues will use these default hue values + var defaultDefaultHues = { + 'default': '500', + 'hue-1': '300', + 'hue-2': '800', + 'hue-3': 'A100' + }; + if (!LIGHT_DEFAULT_HUES[colorType]) LIGHT_DEFAULT_HUES[colorType] = defaultDefaultHues; + if (!DARK_DEFAULT_HUES[colorType]) DARK_DEFAULT_HUES[colorType] = defaultDefaultHues; +}); + +var VALID_HUE_VALUES = [ + '50', '100', '200', '300', '400', '500', '600', + '700', '800', '900', 'A100', 'A200', 'A400', 'A700' +]; + +var themeConfig = { + disableTheming : false, // Generate our themes at run time; also disable stylesheet DOM injection + generateOnDemand : false, // Whether or not themes are to be generated on-demand (vs. eagerly). + registeredStyles : [], // Custom styles registered to be used in the theming of custom components. + nonce : null // Nonce to be added as an attribute to the generated themes style tags. +}; + +/** + * + */ +function ThemingProvider($mdColorPalette, $$mdMetaProvider) { + ThemingService.$inject = ["$rootScope", "$log"]; + PALETTES = { }; + var THEMES = { }; + + var themingProvider; + + var alwaysWatchTheme = false; + var defaultTheme = 'default'; + + // Load JS Defined Palettes + angular.extend(PALETTES, $mdColorPalette); + + // Default theme defined in core.js + + /** + * Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter + * @param {string} color Hex value of the wanted browser color + * @returns {Function} Remove function of the meta tags + */ + var setBrowserColor = function (color) { + // Chrome, Firefox OS and Opera + var removeChrome = $$mdMetaProvider.setMeta('theme-color', color); + // Windows Phone + var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color); + + return function () { + removeChrome(); + removeWindows(); + } + }; + + /** + * Enables browser header coloring + * for more info please visit: + * https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color + * + * The default color is `800` from `primary` palette of the `default` theme + * + * options are: + * `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme + * `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary', + * 'accent', 'background' and 'warn'. Default is `primary` + * `hue` - The hue from the selected palette. Default is `800` + * + * @param {Object=} options Options object for the browser color + * @returns {Function} remove function of the browser color + */ + var enableBrowserColor = function (options) { + options = angular.isObject(options) ? options : {}; + + var theme = options.theme || 'default'; + var hue = options.hue || '800'; + + var palette = PALETTES[options.palette] || + PALETTES[THEMES[theme].colors[options.palette || 'primary'].name]; + + var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue]; + + return setBrowserColor(color); + }; + + return themingProvider = { + definePalette: definePalette, + extendPalette: extendPalette, + theme: registerTheme, + + /** + * return a read-only clone of the current theme configuration + */ + configuration : function() { + return angular.extend( { }, themeConfig, { + defaultTheme : defaultTheme, + alwaysWatchTheme : alwaysWatchTheme, + registeredStyles : [].concat(themeConfig.registeredStyles) + }); + }, + + /** + * Easy way to disable theming without having to use + * `.constant("$MD_THEME_CSS","");` This disables + * all dynamic theme style sheet generations and injections... + */ + disableTheming: function(isDisabled) { + themeConfig.disableTheming = angular.isUndefined(isDisabled) || !!isDisabled; + }, + + registerStyles: function(styles) { + themeConfig.registeredStyles.push(styles); + }, + + setNonce: function(nonceValue) { + themeConfig.nonce = nonceValue; + }, + + generateThemesOnDemand: function(onDemand) { + themeConfig.generateOnDemand = onDemand; + }, + + setDefaultTheme: function(theme) { + defaultTheme = theme; + }, + + alwaysWatchTheme: function(alwaysWatch) { + alwaysWatchTheme = alwaysWatch; + }, + + enableBrowserColor: enableBrowserColor, + + $get: ThemingService, + _LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES, + _DARK_DEFAULT_HUES: DARK_DEFAULT_HUES, + _PALETTES: PALETTES, + _THEMES: THEMES, + _parseRules: parseRules, + _rgba: rgba + }; + + // Example: $mdThemingProvider.definePalette('neonRed', { 50: '#f5fafa', ... }); + function definePalette(name, map) { + map = map || {}; + PALETTES[name] = checkPaletteValid(name, map); + return themingProvider; + } + + // Returns an new object which is a copy of a given palette `name` with variables from + // `map` overwritten + // Example: var neonRedMap = $mdThemingProvider.extendPalette('red', { 50: '#f5fafafa' }); + function extendPalette(name, map) { + return checkPaletteValid(name, angular.extend({}, PALETTES[name] || {}, map) ); + } + + // Make sure that palette has all required hues + function checkPaletteValid(name, map) { + var missingColors = VALID_HUE_VALUES.filter(function(field) { + return !map[field]; + }); + if (missingColors.length) { + throw new Error("Missing colors %1 in palette %2!" + .replace('%1', missingColors.join(', ')) + .replace('%2', name)); + } + + return map; + } + + // Register a theme (which is a collection of color palettes to use with various states + // ie. warn, accent, primary ) + // Optionally inherit from an existing theme + // $mdThemingProvider.theme('custom-theme').primaryPalette('red'); + function registerTheme(name, inheritFrom) { + if (THEMES[name]) return THEMES[name]; + + inheritFrom = inheritFrom || 'default'; + + var parentTheme = typeof inheritFrom === 'string' ? THEMES[inheritFrom] : inheritFrom; + var theme = new Theme(name); + + if (parentTheme) { + angular.forEach(parentTheme.colors, function(color, colorType) { + theme.colors[colorType] = { + name: color.name, + // Make sure a COPY of the hues is given to the child color, + // not the same reference. + hues: angular.extend({}, color.hues) + }; + }); + } + THEMES[name] = theme; + + return theme; + } + + function Theme(name) { + var self = this; + self.name = name; + self.colors = {}; + + self.dark = setDark; + setDark(false); + + function setDark(isDark) { + isDark = arguments.length === 0 ? true : !!isDark; + + // If no change, abort + if (isDark === self.isDark) return; + + self.isDark = isDark; + + self.foregroundPalette = self.isDark ? LIGHT_FOREGROUND : DARK_FOREGROUND; + self.foregroundShadow = self.isDark ? DARK_SHADOW : LIGHT_SHADOW; + + // Light and dark themes have different default hues. + // Go through each existing color type for this theme, and for every + // hue value that is still the default hue value from the previous light/dark setting, + // set it to the default hue value from the new light/dark setting. + var newDefaultHues = self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES; + var oldDefaultHues = self.isDark ? LIGHT_DEFAULT_HUES : DARK_DEFAULT_HUES; + angular.forEach(newDefaultHues, function(newDefaults, colorType) { + var color = self.colors[colorType]; + var oldDefaults = oldDefaultHues[colorType]; + if (color) { + for (var hueName in color.hues) { + if (color.hues[hueName] === oldDefaults[hueName]) { + color.hues[hueName] = newDefaults[hueName]; + } + } + } + }); + + return self; + } + + THEME_COLOR_TYPES.forEach(function(colorType) { + var defaultHues = (self.isDark ? DARK_DEFAULT_HUES : LIGHT_DEFAULT_HUES)[colorType]; + self[colorType + 'Palette'] = function setPaletteType(paletteName, hues) { + var color = self.colors[colorType] = { + name: paletteName, + hues: angular.extend({}, defaultHues, hues) + }; + + Object.keys(color.hues).forEach(function(name) { + if (!defaultHues[name]) { + throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4" + .replace('%1', name) + .replace('%2', self.name) + .replace('%3', paletteName) + .replace('%4', Object.keys(defaultHues).join(', ')) + ); + } + }); + Object.keys(color.hues).map(function(key) { + return color.hues[key]; + }).forEach(function(hueValue) { + if (VALID_HUE_VALUES.indexOf(hueValue) == -1) { + throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5" + .replace('%1', hueValue) + .replace('%2', self.name) + .replace('%3', colorType) + .replace('%4', paletteName) + .replace('%5', VALID_HUE_VALUES.join(', ')) + ); + } + }); + return self; + }; + + self[colorType + 'Color'] = function() { + var args = Array.prototype.slice.call(arguments); + console.warn('$mdThemingProviderTheme.' + colorType + 'Color() has been deprecated. ' + + 'Use $mdThemingProviderTheme.' + colorType + 'Palette() instead.'); + return self[colorType + 'Palette'].apply(self, args); + }; + }); + } + + /** + * @ngdoc service + * @name $mdTheming + * + * @description + * + * Service that makes an element apply theming related classes to itself. + * + * ```js + * app.directive('myFancyDirective', function($mdTheming) { + * return { + * restrict: 'e', + * link: function(scope, el, attrs) { + * $mdTheming(el); + * } + * }; + * }); + * ``` + * @param {el=} element to apply theming to + */ + /* @ngInject */ + function ThemingService($rootScope, $log) { + // Allow us to be invoked via a linking function signature. + var applyTheme = function (scope, el) { + if (el === undefined) { el = scope; scope = undefined; } + if (scope === undefined) { scope = $rootScope; } + applyTheme.inherit(el, el); + }; + + applyTheme.THEMES = angular.extend({}, THEMES); + applyTheme.PALETTES = angular.extend({}, PALETTES); + applyTheme.inherit = inheritTheme; + applyTheme.registered = registered; + applyTheme.defaultTheme = function() { return defaultTheme; }; + applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); }; + applyTheme.setBrowserColor = enableBrowserColor; + + return applyTheme; + + /** + * Determine is specified theme name is a valid, registered theme + */ + function registered(themeName) { + if (themeName === undefined || themeName === '') return true; + return applyTheme.THEMES[themeName] !== undefined; + } + + /** + * Get theme name for the element, then update with Theme CSS class + */ + function inheritTheme (el, parent) { + var ctrl = parent.controller('mdTheme'); + var attrThemeValue = el.attr('md-theme-watch'); + var watchTheme = (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false'; + + updateThemeClass(lookupThemeName()); + + if ((alwaysWatchTheme && !registerChangeCallback()) || (!alwaysWatchTheme && watchTheme)) { + el.on('$destroy', $rootScope.$watch(lookupThemeName, updateThemeClass) ); + } + + /** + * Find the theme name from the parent controller or element data + */ + function lookupThemeName() { + // As a few components (dialog) add their controllers later, we should also watch for a controller init. + ctrl = parent.controller('mdTheme') || el.data('$mdThemeController'); + return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme); + } + + /** + * Remove old theme class and apply a new one + * NOTE: if not a valid theme name, then the current name is not changed + */ + function updateThemeClass(theme) { + if (!theme) return; + if (!registered(theme)) { + $log.warn('Attempted to use unregistered theme \'' + theme + '\'. ' + + 'Register it with $mdThemingProvider.theme().'); + } + + var oldTheme = el.data('$mdThemeName'); + if (oldTheme) el.removeClass('md-' + oldTheme +'-theme'); + el.addClass('md-' + theme + '-theme'); + el.data('$mdThemeName', theme); + if (ctrl) { + el.data('$mdThemeController', ctrl); + } + } + + /** + * Register change callback with parent mdTheme controller + */ + function registerChangeCallback() { + var parentController = parent.controller('mdTheme'); + if (!parentController) return false; + el.on('$destroy', parentController.registerChanges( function() { + updateThemeClass(lookupThemeName()); + })); + return true; + } + } + + } +} + +function ThemingDirective($mdTheming, $interpolate, $log) { + return { + priority: 100, + link: { + pre: function(scope, el, attrs) { + var registeredCallbacks = []; + var ctrl = { + registerChanges: function (cb, context) { + if (context) { + cb = angular.bind(context, cb); + } + + registeredCallbacks.push(cb); + + return function () { + var index = registeredCallbacks.indexOf(cb); + + if (index > -1) { + registeredCallbacks.splice(index, 1); + } + }; + }, + $setTheme: function (theme) { + if (!$mdTheming.registered(theme)) { + $log.warn('attempted to use unregistered theme \'' + theme + '\''); + } + ctrl.$mdTheme = theme; + + registeredCallbacks.forEach(function (cb) { + cb(); + }); + } + }; + el.data('$mdThemeController', ctrl); + ctrl.$setTheme($interpolate(attrs.mdTheme)(scope)); + attrs.$observe('mdTheme', ctrl.$setTheme); + } + } + }; +} + +/** + * Special directive that will disable ALL runtime Theme style generation and DOM injection + * + * + * + * + * + * ... + * + * + * Note: Using md-themes-css directive requires the developer to load external + * theme stylesheets; e.g. custom themes from Material-Tools: + * + * `angular-material.themes.css` + * + * Another option is to use the ThemingProvider to configure and disable the attribute + * conversions; this would obviate the use of the `md-themes-css` directive + * + */ +function disableThemesDirective() { + themeConfig.disableTheming = true; + + // Return a 1x-only, first-match attribute directive + return { + restrict : 'A', + priority : '900' + }; +} + +function ThemableDirective($mdTheming) { + return $mdTheming; +} + +function parseRules(theme, colorType, rules) { + checkValidPalette(theme, colorType); + + rules = rules.replace(/THEME_NAME/g, theme.name); + var generatedRules = []; + var color = theme.colors[colorType]; + + var themeNameRegex = new RegExp('\\.md-' + theme.name + '-theme', 'g'); + // Matches '{{ primary-color }}', etc + var hueRegex = new RegExp('(\'|")?{{\\s*(' + colorType + ')-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|\')?','g'); + var simpleVariableRegex = /'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g; + var palette = PALETTES[color.name]; + + // find and replace simple variables where we use a specific hue, not an entire palette + // eg. "{{primary-100}}" + //\(' + THEME_COLOR_TYPES.join('\|') + '\)' + rules = rules.replace(simpleVariableRegex, function(match, colorType, hue, opacity, contrast) { + if (colorType === 'foreground') { + if (hue == 'shadow') { + return theme.foregroundShadow; + } else { + return theme.foregroundPalette[hue] || theme.foregroundPalette['1']; + } + } + + // `default` is also accepted as a hue-value, because the background palettes are + // using it as a name for the default hue. + if (hue.indexOf('hue') === 0 || hue === 'default') { + hue = theme.colors[colorType].hues[hue]; + } + + return rgba( (PALETTES[ theme.colors[colorType].name ][hue] || '')[contrast ? 'contrast' : 'value'], opacity ); + }); + + // For each type, generate rules for each hue (ie. default, md-hue-1, md-hue-2, md-hue-3) + angular.forEach(color.hues, function(hueValue, hueName) { + var newRule = rules + .replace(hueRegex, function(match, _, colorType, hueType, opacity) { + return rgba(palette[hueValue][hueType === 'color' ? 'value' : 'contrast'], opacity); + }); + if (hueName !== 'default') { + newRule = newRule.replace(themeNameRegex, '.md-' + theme.name + '-theme.md-' + hueName); + } + + // Don't apply a selector rule to the default theme, making it easier to override + // styles of the base-component + if (theme.name == 'default') { + var themeRuleRegex = /((?:(?:(?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)+) )?)((?:(?:\w|\.|-)+)?)\.md-default-theme((?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g; + newRule = newRule.replace(themeRuleRegex, function(match, prefix, target, suffix) { + return match + ', ' + prefix + target + suffix; + }); + } + generatedRules.push(newRule); + }); + + return generatedRules; +} + +var rulesByType = {}; + +// Generate our themes at run time given the state of THEMES and PALETTES +function generateAllThemes($injector, $mdTheming) { + var head = document.head; + var firstChild = head ? head.firstElementChild : null; + var themeCss = !themeConfig.disableTheming && $injector.has('$MD_THEME_CSS') ? $injector.get('$MD_THEME_CSS') : ''; + + // Append our custom registered styles to the theme stylesheet. + themeCss += themeConfig.registeredStyles.join(''); + + if ( !firstChild ) return; + if (themeCss.length === 0) return; // no rules, so no point in running this expensive task + + // Expose contrast colors for palettes to ensure that text is always readable + angular.forEach(PALETTES, sanitizePalette); + + // MD_THEME_CSS is a string generated by the build process that includes all the themable + // components as templates + + // Break the CSS into individual rules + var rules = themeCss + .split(/\}(?!(\}|'|"|;))/) + .filter(function(rule) { return rule && rule.trim().length; }) + .map(function(rule) { return rule.trim() + '}'; }); + + + var ruleMatchRegex = new RegExp('md-(' + THEME_COLOR_TYPES.join('|') + ')', 'g'); + + THEME_COLOR_TYPES.forEach(function(type) { + rulesByType[type] = ''; + }); + + + // Sort the rules based on type, allowing us to do color substitution on a per-type basis + rules.forEach(function(rule) { + var match = rule.match(ruleMatchRegex); + // First: test that if the rule has '.md-accent', it goes into the accent set of rules + for (var i = 0, type; type = THEME_COLOR_TYPES[i]; i++) { + if (rule.indexOf('.md-' + type) > -1) { + return rulesByType[type] += rule; + } + } + + // If no eg 'md-accent' class is found, try to just find 'accent' in the rule and guess from + // there + for (i = 0; type = THEME_COLOR_TYPES[i]; i++) { + if (rule.indexOf(type) > -1) { + return rulesByType[type] += rule; + } + } + + // Default to the primary array + return rulesByType[DEFAULT_COLOR_TYPE] += rule; + }); + + // If themes are being generated on-demand, quit here. The user will later manually + // call generateTheme to do this on a theme-by-theme basis. + if (themeConfig.generateOnDemand) return; + + angular.forEach($mdTheming.THEMES, function(theme) { + if (!GENERATED[theme.name] && !($mdTheming.defaultTheme() !== 'default' && theme.name === 'default')) { + generateTheme(theme, theme.name, themeConfig.nonce); + } + }); + + + // ************************* + // Internal functions + // ************************* + + // The user specifies a 'default' contrast color as either light or dark, + // then explicitly lists which hues are the opposite contrast (eg. A100 has dark, A200 has light) + function sanitizePalette(palette, name) { + var defaultContrast = palette.contrastDefaultColor; + var lightColors = palette.contrastLightColors || []; + var strongLightColors = palette.contrastStrongLightColors || []; + var darkColors = palette.contrastDarkColors || []; + + // These colors are provided as space-separated lists + if (typeof lightColors === 'string') lightColors = lightColors.split(' '); + if (typeof strongLightColors === 'string') strongLightColors = strongLightColors.split(' '); + if (typeof darkColors === 'string') darkColors = darkColors.split(' '); + + // Cleanup after ourselves + delete palette.contrastDefaultColor; + delete palette.contrastLightColors; + delete palette.contrastStrongLightColors; + delete palette.contrastDarkColors; + + // Change { 'A100': '#fffeee' } to { 'A100': { value: '#fffeee', contrast:DARK_CONTRAST_COLOR } + angular.forEach(palette, function(hueValue, hueName) { + if (angular.isObject(hueValue)) return; // Already converted + // Map everything to rgb colors + var rgbValue = colorToRgbaArray(hueValue); + if (!rgbValue) { + throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected." + .replace('%1', hueValue) + .replace('%2', palette.name) + .replace('%3', hueName)); + } + + palette[hueName] = { + hex: palette[hueName], + value: rgbValue, + contrast: getContrastColor() + }; + function getContrastColor() { + if (defaultContrast === 'light') { + if (darkColors.indexOf(hueName) > -1) { + return DARK_CONTRAST_COLOR; + } else { + return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR + : LIGHT_CONTRAST_COLOR; + } + } else { + if (lightColors.indexOf(hueName) > -1) { + return strongLightColors.indexOf(hueName) > -1 ? STRONG_LIGHT_CONTRAST_COLOR + : LIGHT_CONTRAST_COLOR; + } else { + return DARK_CONTRAST_COLOR; + } + } + } + }); + } +} + +function generateTheme(theme, name, nonce) { + var head = document.head; + var firstChild = head ? head.firstElementChild : null; + + if (!GENERATED[name]) { + // For each theme, use the color palettes specified for + // `primary`, `warn` and `accent` to generate CSS rules. + THEME_COLOR_TYPES.forEach(function(colorType) { + var styleStrings = parseRules(theme, colorType, rulesByType[colorType]); + while (styleStrings.length) { + var styleContent = styleStrings.shift(); + if (styleContent) { + var style = document.createElement('style'); + style.setAttribute('md-theme-style', ''); + if (nonce) { + style.setAttribute('nonce', nonce); + } + style.appendChild(document.createTextNode(styleContent)); + head.insertBefore(style, firstChild); + } + } + }); + + GENERATED[theme.name] = true; + } + +} + + +function checkValidPalette(theme, colorType) { + // If theme attempts to use a palette that doesnt exist, throw error + if (!PALETTES[ (theme.colors[colorType] || {}).name ]) { + throw new Error( + "You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3" + .replace('%1', theme.name) + .replace('%2', colorType) + .replace('%3', Object.keys(PALETTES).join(', ')) + ); + } +} + +function colorToRgbaArray(clr) { + if (angular.isArray(clr) && clr.length == 3) return clr; + if (/^rgb/.test(clr)) { + return clr.replace(/(^\s*rgba?\(|\)\s*$)/g, '').split(',').map(function(value, i) { + return i == 3 ? parseFloat(value, 10) : parseInt(value, 10); + }); + } + if (clr.charAt(0) == '#') clr = clr.substring(1); + if (!/^([a-fA-F0-9]{3}){1,2}$/g.test(clr)) return; + + var dig = clr.length / 3; + var red = clr.substr(0, dig); + var grn = clr.substr(dig, dig); + var blu = clr.substr(dig * 2); + if (dig === 1) { + red += red; + grn += grn; + blu += blu; + } + return [parseInt(red, 16), parseInt(grn, 16), parseInt(blu, 16)]; +} + +function rgba(rgbArray, opacity) { + if ( !rgbArray ) return "rgb('0,0,0')"; + + if (rgbArray.length == 4) { + rgbArray = angular.copy(rgbArray); + opacity ? rgbArray.pop() : opacity = rgbArray.pop(); + } + return opacity && (typeof opacity == 'number' || (typeof opacity == 'string' && opacity.length)) ? + 'rgba(' + rgbArray.join(',') + ',' + opacity + ')' : + 'rgb(' + rgbArray.join(',') + ')'; +} + + +})(window.angular); + +})(); +(function(){ +"use strict"; + +// Polyfill angular < 1.4 (provide $animateCss) +angular + .module('material.core') + .factory('$$mdAnimate', ["$q", "$timeout", "$mdConstant", "$animateCss", function($q, $timeout, $mdConstant, $animateCss){ + + // Since $$mdAnimate is injected into $mdUtil... use a wrapper function + // to subsequently inject $mdUtil as an argument to the AnimateDomUtils + + return function($mdUtil) { + return AnimateDomUtils( $mdUtil, $q, $timeout, $mdConstant, $animateCss); + }; + }]); + +/** + * Factory function that requires special injections + */ +function AnimateDomUtils($mdUtil, $q, $timeout, $mdConstant, $animateCss) { + var self; + return self = { + /** + * + */ + translate3d : function( target, from, to, options ) { + return $animateCss(target,{ + from:from, + to:to, + addClass:options.transitionInClass, + removeClass:options.transitionOutClass + }) + .start() + .then(function(){ + // Resolve with reverser function... + return reverseTranslate; + }); + + /** + * Specific reversal of the request translate animation above... + */ + function reverseTranslate (newFrom) { + return $animateCss(target, { + to: newFrom || from, + addClass: options.transitionOutClass, + removeClass: options.transitionInClass + }).start(); + + } + }, + + /** + * Listen for transitionEnd event (with optional timeout) + * Announce completion or failure via promise handlers + */ + waitTransitionEnd: function (element, opts) { + var TIMEOUT = 3000; // fallback is 3 secs + + return $q(function(resolve, reject){ + opts = opts || { }; + + // If there is no transition is found, resolve immediately + // + // NOTE: using $mdUtil.nextTick() causes delays/issues + if (noTransitionFound(opts.cachedTransitionStyles)) { + TIMEOUT = 0; + } + + var timer = $timeout(finished, opts.timeout || TIMEOUT); + element.on($mdConstant.CSS.TRANSITIONEND, finished); + + /** + * Upon timeout or transitionEnd, reject or resolve (respectively) this promise. + * NOTE: Make sure this transitionEnd didn't bubble up from a child + */ + function finished(ev) { + if ( ev && ev.target !== element[0]) return; + + if ( ev ) $timeout.cancel(timer); + element.off($mdConstant.CSS.TRANSITIONEND, finished); + + // Never reject since ngAnimate may cause timeouts due missed transitionEnd events + resolve(); + + } + + /** + * Checks whether or not there is a transition. + * + * @param styles The cached styles to use for the calculation. If null, getComputedStyle() + * will be used. + * + * @returns {boolean} True if there is no transition/duration; false otherwise. + */ + function noTransitionFound(styles) { + styles = styles || window.getComputedStyle(element[0]); + + return styles.transitionDuration == '0s' || (!styles.transition && !styles.transitionProperty); + } + + }); + }, + + calculateTransformValues: function (element, originator) { + var origin = originator.element; + var bounds = originator.bounds; + + if (origin || bounds) { + var originBnds = origin ? self.clientRect(origin) || currentBounds() : self.copyRect(bounds); + var dialogRect = self.copyRect(element[0].getBoundingClientRect()); + var dialogCenterPt = self.centerPointFor(dialogRect); + var originCenterPt = self.centerPointFor(originBnds); + + return { + centerX: originCenterPt.x - dialogCenterPt.x, + centerY: originCenterPt.y - dialogCenterPt.y, + scaleX: Math.round(100 * Math.min(0.5, originBnds.width / dialogRect.width)) / 100, + scaleY: Math.round(100 * Math.min(0.5, originBnds.height / dialogRect.height)) / 100 + }; + } + return {centerX: 0, centerY: 0, scaleX: 0.5, scaleY: 0.5}; + + /** + * This is a fallback if the origin information is no longer valid, then the + * origin bounds simply becomes the current bounds for the dialogContainer's parent + */ + function currentBounds() { + var cntr = element ? element.parent() : null; + var parent = cntr ? cntr.parent() : null; + + return parent ? self.clientRect(parent) : null; + } + }, + + /** + * Calculate the zoom transform from dialog to origin. + * + * We use this to set the dialog position immediately; + * then the md-transition-in actually translates back to + * `translate3d(0,0,0) scale(1.0)`... + * + * NOTE: all values are rounded to the nearest integer + */ + calculateZoomToOrigin: function (element, originator) { + var zoomTemplate = "translate3d( {centerX}px, {centerY}px, 0 ) scale( {scaleX}, {scaleY} )"; + var buildZoom = angular.bind(null, $mdUtil.supplant, zoomTemplate); + + return buildZoom(self.calculateTransformValues(element, originator)); + }, + + /** + * Calculate the slide transform from panel to origin. + * NOTE: all values are rounded to the nearest integer + */ + calculateSlideToOrigin: function (element, originator) { + var slideTemplate = "translate3d( {centerX}px, {centerY}px, 0 )"; + var buildSlide = angular.bind(null, $mdUtil.supplant, slideTemplate); + + return buildSlide(self.calculateTransformValues(element, originator)); + }, + + /** + * Enhance raw values to represent valid css stylings... + */ + toCss : function( raw ) { + var css = { }; + var lookups = 'left top right bottom width height x y min-width min-height max-width max-height'; + + angular.forEach(raw, function(value,key) { + if ( angular.isUndefined(value) ) return; + + if ( lookups.indexOf(key) >= 0 ) { + css[key] = value + 'px'; + } else { + switch (key) { + case 'transition': + convertToVendor(key, $mdConstant.CSS.TRANSITION, value); + break; + case 'transform': + convertToVendor(key, $mdConstant.CSS.TRANSFORM, value); + break; + case 'transformOrigin': + convertToVendor(key, $mdConstant.CSS.TRANSFORM_ORIGIN, value); + break; + case 'font-size': + css['font-size'] = value; // font sizes aren't always in px + break; + } + } + }); + + return css; + + function convertToVendor(key, vendor, value) { + angular.forEach(vendor.split(' '), function (key) { + css[key] = value; + }); + } + }, + + /** + * Convert the translate CSS value to key/value pair(s). + */ + toTransformCss: function (transform, addTransition, transition) { + var css = {}; + angular.forEach($mdConstant.CSS.TRANSFORM.split(' '), function (key) { + css[key] = transform; + }); + + if (addTransition) { + transition = transition || "all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important"; + css.transition = transition; + } + + return css; + }, + + /** + * Clone the Rect and calculate the height/width if needed + */ + copyRect: function (source, destination) { + if (!source) return null; + + destination = destination || {}; + + angular.forEach('left top right bottom width height'.split(' '), function (key) { + destination[key] = Math.round(source[key]); + }); + + destination.width = destination.width || (destination.right - destination.left); + destination.height = destination.height || (destination.bottom - destination.top); + + return destination; + }, + + /** + * Calculate ClientRect of element; return null if hidden or zero size + */ + clientRect: function (element) { + var bounds = angular.element(element)[0].getBoundingClientRect(); + var isPositiveSizeClientRect = function (rect) { + return rect && (rect.width > 0) && (rect.height > 0); + }; + + // If the event origin element has zero size, it has probably been hidden. + return isPositiveSizeClientRect(bounds) ? self.copyRect(bounds) : null; + }, + + /** + * Calculate 'rounded' center point of Rect + */ + centerPointFor: function (targetRect) { + return targetRect ? { + x: Math.round(targetRect.left + (targetRect.width / 2)), + y: Math.round(targetRect.top + (targetRect.height / 2)) + } : { x : 0, y : 0 }; + } + + }; +} + + +})(); +(function(){ +"use strict"; + +"use strict"; + +if (angular.version.minor >= 4) { + angular.module('material.core.animate', []); +} else { +(function() { + + var forEach = angular.forEach; + + var WEBKIT = angular.isDefined(document.documentElement.style.WebkitAppearance); + var TRANSITION_PROP = WEBKIT ? 'WebkitTransition' : 'transition'; + var ANIMATION_PROP = WEBKIT ? 'WebkitAnimation' : 'animation'; + var PREFIX = WEBKIT ? '-webkit-' : ''; + + var TRANSITION_EVENTS = (WEBKIT ? 'webkitTransitionEnd ' : '') + 'transitionend'; + var ANIMATION_EVENTS = (WEBKIT ? 'webkitAnimationEnd ' : '') + 'animationend'; + + var $$ForceReflowFactory = ['$document', function($document) { + return function() { + return $document[0].body.clientWidth + 1; + } + }]; + + var $$rAFMutexFactory = ['$$rAF', function($$rAF) { + return function() { + var passed = false; + $$rAF(function() { + passed = true; + }); + return function(fn) { + passed ? fn() : $$rAF(fn); + }; + }; + }]; + + var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) { + var INITIAL_STATE = 0; + var DONE_PENDING_STATE = 1; + var DONE_COMPLETE_STATE = 2; + + function AnimateRunner(host) { + this.setHost(host); + + this._doneCallbacks = []; + this._runInAnimationFrame = $$rAFMutex(); + this._state = 0; + } + + AnimateRunner.prototype = { + setHost: function(host) { + this.host = host || {}; + }, + + done: function(fn) { + if (this._state === DONE_COMPLETE_STATE) { + fn(); + } else { + this._doneCallbacks.push(fn); + } + }, + + progress: angular.noop, + + getPromise: function() { + if (!this.promise) { + var self = this; + this.promise = $q(function(resolve, reject) { + self.done(function(status) { + status === false ? reject() : resolve(); + }); + }); + } + return this.promise; + }, + + then: function(resolveHandler, rejectHandler) { + return this.getPromise().then(resolveHandler, rejectHandler); + }, + + 'catch': function(handler) { + return this.getPromise()['catch'](handler); + }, + + 'finally': function(handler) { + return this.getPromise()['finally'](handler); + }, + + pause: function() { + if (this.host.pause) { + this.host.pause(); + } + }, + + resume: function() { + if (this.host.resume) { + this.host.resume(); + } + }, + + end: function() { + if (this.host.end) { + this.host.end(); + } + this._resolve(true); + }, + + cancel: function() { + if (this.host.cancel) { + this.host.cancel(); + } + this._resolve(false); + }, + + complete: function(response) { + var self = this; + if (self._state === INITIAL_STATE) { + self._state = DONE_PENDING_STATE; + self._runInAnimationFrame(function() { + self._resolve(response); + }); + } + }, + + _resolve: function(response) { + if (this._state !== DONE_COMPLETE_STATE) { + forEach(this._doneCallbacks, function(fn) { + fn(response); + }); + this._doneCallbacks.length = 0; + this._state = DONE_COMPLETE_STATE; + } + } + }; + + // Polyfill AnimateRunner.all which is used by input animations + AnimateRunner.all = function(runners, callback) { + var count = 0; + var status = true; + forEach(runners, function(runner) { + runner.done(onProgress); + }); + + function onProgress(response) { + status = status && response; + if (++count === runners.length) { + callback(status); + } + } + }; + + return AnimateRunner; + }]; + + angular + .module('material.core.animate', []) + .factory('$$forceReflow', $$ForceReflowFactory) + .factory('$$AnimateRunner', $$AnimateRunnerFactory) + .factory('$$rAFMutex', $$rAFMutexFactory) + .factory('$animateCss', ['$window', '$$rAF', '$$AnimateRunner', '$$forceReflow', '$$jqLite', '$timeout', '$animate', + function($window, $$rAF, $$AnimateRunner, $$forceReflow, $$jqLite, $timeout, $animate) { + + function init(element, options) { + + var temporaryStyles = []; + var node = getDomNode(element); + var areAnimationsAllowed = node && $animate.enabled(); + + var hasCompleteStyles = false; + var hasCompleteClasses = false; + + if (areAnimationsAllowed) { + if (options.transitionStyle) { + temporaryStyles.push([PREFIX + 'transition', options.transitionStyle]); + } + + if (options.keyframeStyle) { + temporaryStyles.push([PREFIX + 'animation', options.keyframeStyle]); + } + + if (options.delay) { + temporaryStyles.push([PREFIX + 'transition-delay', options.delay + 's']); + } + + if (options.duration) { + temporaryStyles.push([PREFIX + 'transition-duration', options.duration + 's']); + } + + hasCompleteStyles = options.keyframeStyle || + (options.to && (options.duration > 0 || options.transitionStyle)); + hasCompleteClasses = !!options.addClass || !!options.removeClass; + + blockTransition(element, true); + } + + var hasCompleteAnimation = areAnimationsAllowed && (hasCompleteStyles || hasCompleteClasses); + + applyAnimationFromStyles(element, options); + + var animationClosed = false; + var events, eventFn; + + return { + close: $window.close, + start: function() { + var runner = new $$AnimateRunner(); + waitUntilQuiet(function() { + blockTransition(element, false); + if (!hasCompleteAnimation) { + return close(); + } + + forEach(temporaryStyles, function(entry) { + var key = entry[0]; + var value = entry[1]; + node.style[camelCase(key)] = value; + }); + + applyClasses(element, options); + + var timings = computeTimings(element); + if (timings.duration === 0) { + return close(); + } + + var moreStyles = []; + + if (options.easing) { + if (timings.transitionDuration) { + moreStyles.push([PREFIX + 'transition-timing-function', options.easing]); + } + if (timings.animationDuration) { + moreStyles.push([PREFIX + 'animation-timing-function', options.easing]); + } + } + + if (options.delay && timings.animationDelay) { + moreStyles.push([PREFIX + 'animation-delay', options.delay + 's']); + } + + if (options.duration && timings.animationDuration) { + moreStyles.push([PREFIX + 'animation-duration', options.duration + 's']); + } + + forEach(moreStyles, function(entry) { + var key = entry[0]; + var value = entry[1]; + node.style[camelCase(key)] = value; + temporaryStyles.push(entry); + }); + + var maxDelay = timings.delay; + var maxDelayTime = maxDelay * 1000; + var maxDuration = timings.duration; + var maxDurationTime = maxDuration * 1000; + var startTime = Date.now(); + + events = []; + if (timings.transitionDuration) { + events.push(TRANSITION_EVENTS); + } + if (timings.animationDuration) { + events.push(ANIMATION_EVENTS); + } + events = events.join(' '); + eventFn = function(event) { + event.stopPropagation(); + var ev = event.originalEvent || event; + var timeStamp = ev.timeStamp || Date.now(); + var elapsedTime = parseFloat(ev.elapsedTime.toFixed(3)); + if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { + close(); + } + }; + element.on(events, eventFn); + + applyAnimationToStyles(element, options); + + $timeout(close, maxDelayTime + maxDurationTime * 1.5, false); + }); + + return runner; + + function close() { + if (animationClosed) return; + animationClosed = true; + + if (events && eventFn) { + element.off(events, eventFn); + } + applyClasses(element, options); + applyAnimationStyles(element, options); + forEach(temporaryStyles, function(entry) { + node.style[camelCase(entry[0])] = ''; + }); + runner.complete(true); + return runner; + } + } + } + } + + function applyClasses(element, options) { + if (options.addClass) { + $$jqLite.addClass(element, options.addClass); + options.addClass = null; + } + if (options.removeClass) { + $$jqLite.removeClass(element, options.removeClass); + options.removeClass = null; + } + } + + function computeTimings(element) { + var node = getDomNode(element); + var cs = $window.getComputedStyle(node) + var tdr = parseMaxTime(cs[prop('transitionDuration')]); + var adr = parseMaxTime(cs[prop('animationDuration')]); + var tdy = parseMaxTime(cs[prop('transitionDelay')]); + var ady = parseMaxTime(cs[prop('animationDelay')]); + + adr *= (parseInt(cs[prop('animationIterationCount')], 10) || 1); + var duration = Math.max(adr, tdr); + var delay = Math.max(ady, tdy); + + return { + duration: duration, + delay: delay, + animationDuration: adr, + transitionDuration: tdr, + animationDelay: ady, + transitionDelay: tdy + }; + + function prop(key) { + return WEBKIT ? 'Webkit' + key.charAt(0).toUpperCase() + key.substr(1) + : key; + } + } + + function parseMaxTime(str) { + var maxValue = 0; + var values = (str || "").split(/\s*,\s*/); + forEach(values, function(value) { + // it's always safe to consider only second values and omit `ms` values since + // getComputedStyle will always handle the conversion for us + if (value.charAt(value.length - 1) == 's') { + value = value.substring(0, value.length - 1); + } + value = parseFloat(value) || 0; + maxValue = maxValue ? Math.max(value, maxValue) : value; + }); + return maxValue; + } + + var cancelLastRAFRequest; + var rafWaitQueue = []; + function waitUntilQuiet(callback) { + if (cancelLastRAFRequest) { + cancelLastRAFRequest(); //cancels the request + } + rafWaitQueue.push(callback); + cancelLastRAFRequest = $$rAF(function() { + cancelLastRAFRequest = null; + + // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. + // PLEASE EXAMINE THE `$$forceReflow` service to understand why. + var pageWidth = $$forceReflow(); + + // we use a for loop to ensure that if the queue is changed + // during this looping then it will consider new requests + for (var i = 0; i < rafWaitQueue.length; i++) { + rafWaitQueue[i](pageWidth); + } + rafWaitQueue.length = 0; + }); + } + + function applyAnimationStyles(element, options) { + applyAnimationFromStyles(element, options); + applyAnimationToStyles(element, options); + } + + function applyAnimationFromStyles(element, options) { + if (options.from) { + element.css(options.from); + options.from = null; + } + } + + function applyAnimationToStyles(element, options) { + if (options.to) { + element.css(options.to); + options.to = null; + } + } + + function getDomNode(element) { + for (var i = 0; i < element.length; i++) { + if (element[i].nodeType === 1) return element[i]; + } + } + + function blockTransition(element, bool) { + var node = getDomNode(element); + var key = camelCase(PREFIX + 'transition-delay'); + node.style[key] = bool ? '-9999s' : ''; + } + + return init; + }]); + + /** + * Older browsers [FF31] expect camelCase + * property keys. + * e.g. + * animation-duration --> animationDuration + */ + function camelCase(str) { + return str.replace(/-[a-z]/g, function(str) { + return str.charAt(1).toUpperCase(); + }); + } + +})(); + +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.autocomplete + */ +/* + * @see js folder for autocomplete implementation + */ +angular.module('material.components.autocomplete', [ + 'material.core', + 'material.components.icon', + 'material.components.virtualRepeat' +]); + +})(); +(function(){ +"use strict"; + +/* + * @ngdoc module + * @name material.components.backdrop + * @description Backdrop + */ + +/** + * @ngdoc directive + * @name mdBackdrop + * @module material.components.backdrop + * + * @restrict E + * + * @description + * `` is a backdrop element used by other components, such as dialog and bottom sheet. + * Apply class `opaque` to make the backdrop use the theme backdrop color. + * + */ + +angular + .module('material.components.backdrop', ['material.core']) + .directive('mdBackdrop', ["$mdTheming", "$mdUtil", "$animate", "$rootElement", "$window", "$log", "$$rAF", "$document", function BackdropDirective($mdTheming, $mdUtil, $animate, $rootElement, $window, $log, $$rAF, $document) { + var ERROR_CSS_POSITION = ' may not work properly in a scrolled, static-positioned parent container.'; + + return { + restrict: 'E', + link: postLink + }; + + function postLink(scope, element, attrs) { + // backdrop may be outside the $rootElement, tell ngAnimate to animate regardless + if ($animate.pin) $animate.pin(element, $rootElement); + + var bodyStyles; + + $$rAF(function() { + // If body scrolling has been disabled using mdUtil.disableBodyScroll(), + // adjust the 'backdrop' height to account for the fixed 'body' top offset. + // Note that this can be pretty expensive and is better done inside the $$rAF. + bodyStyles = $window.getComputedStyle($document[0].body); + + if (bodyStyles.position === 'fixed') { + var resizeHandler = $mdUtil.debounce(function(){ + bodyStyles = $window.getComputedStyle($document[0].body); + resize(); + }, 60, null, false); + + resize(); + angular.element($window).on('resize', resizeHandler); + + scope.$on('$destroy', function() { + angular.element($window).off('resize', resizeHandler); + }); + } + + // Often $animate.enter() is used to append the backDrop element + // so let's wait until $animate is done... + var parent = element.parent(); + + if (parent.length) { + if (parent[0].nodeName === 'BODY') { + element.css('position', 'fixed'); + } + + var styles = $window.getComputedStyle(parent[0]); + + if (styles.position === 'static') { + // backdrop uses position:absolute and will not work properly with parent position:static (default) + $log.warn(ERROR_CSS_POSITION); + } + + // Only inherit the parent if the backdrop has a parent. + $mdTheming.inherit(element, parent); + } + }); + + function resize() { + var viewportHeight = parseInt(bodyStyles.height, 10) + Math.abs(parseInt(bodyStyles.top, 10)); + element.css('height', viewportHeight + 'px'); + } + } + + }]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.bottomSheet + * @description + * BottomSheet + */ +MdBottomSheetDirective.$inject = ["$mdBottomSheet"]; +MdBottomSheetProvider.$inject = ["$$interimElementProvider"]; +angular + .module('material.components.bottomSheet', [ + 'material.core', + 'material.components.backdrop' + ]) + .directive('mdBottomSheet', MdBottomSheetDirective) + .provider('$mdBottomSheet', MdBottomSheetProvider); + +/* @ngInject */ +function MdBottomSheetDirective($mdBottomSheet) { + return { + restrict: 'E', + link : function postLink(scope, element) { + element.addClass('_md'); // private md component indicator for styling + + // When navigation force destroys an interimElement, then + // listen and $destroy() that interim instance... + scope.$on('$destroy', function() { + $mdBottomSheet.destroy(); + }); + } + }; +} + + +/** + * @ngdoc service + * @name $mdBottomSheet + * @module material.components.bottomSheet + * + * @description + * `$mdBottomSheet` opens a bottom sheet over the app and provides a simple promise API. + * + * ## Restrictions + * + * - The bottom sheet's template must have an outer `` element. + * - Add the `md-grid` class to the bottom sheet for a grid layout. + * - Add the `md-list` class to the bottom sheet for a list layout. + * + * @usage + * + *
+ * + * Open a Bottom Sheet! + * + *
+ *
+ * + * var app = angular.module('app', ['ngMaterial']); + * app.controller('MyController', function($scope, $mdBottomSheet) { + * $scope.openBottomSheet = function() { + * $mdBottomSheet.show({ + * template: 'Hello!' + * }); + * }; + * }); + * + */ + + /** + * @ngdoc method + * @name $mdBottomSheet#show + * + * @description + * Show a bottom sheet with the specified options. + * + * @param {object} options An options object, with the following properties: + * + * - `templateUrl` - `{string=}`: The url of an html template file that will + * be used as the content of the bottom sheet. Restrictions: the template must + * have an outer `md-bottom-sheet` element. + * - `template` - `{string=}`: Same as templateUrl, except this is an actual + * template string. + * - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope. + * This scope will be destroyed when the bottom sheet is removed unless `preserveScope` is set to true. + * - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false + * - `controller` - `{string=}`: The controller to associate with this bottom sheet. + * - `locals` - `{string=}`: An object containing key/value pairs. The keys will + * be used as names of values to inject into the controller. For example, + * `locals: {three: 3}` would inject `three` into the controller with the value + * of 3. + * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the bottom sheet to + * close it. Default true. + * - `disableBackdrop` - `{boolean=}`: When set to true, the bottomsheet will not show a backdrop. + * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the bottom sheet. + * Default true. + * - `resolve` - `{object=}`: Similar to locals, except it takes promises as values + * and the bottom sheet will not open until the promises resolve. + * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope. + * - `parent` - `{element=}`: The element to append the bottom sheet to. The `parent` may be a `function`, `string`, + * `object`, or null. Defaults to appending to the body of the root element (or the root element) of the application. + * e.g. angular.element(document.getElementById('content')) or "#content" + * - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the bottom sheet is open. + * Default true. + * + * @returns {promise} A promise that can be resolved with `$mdBottomSheet.hide()` or + * rejected with `$mdBottomSheet.cancel()`. + */ + +/** + * @ngdoc method + * @name $mdBottomSheet#hide + * + * @description + * Hide the existing bottom sheet and resolve the promise returned from + * `$mdBottomSheet.show()`. This call will close the most recently opened/current bottomsheet (if any). + * + * @param {*=} response An argument for the resolved promise. + * + */ + +/** + * @ngdoc method + * @name $mdBottomSheet#cancel + * + * @description + * Hide the existing bottom sheet and reject the promise returned from + * `$mdBottomSheet.show()`. + * + * @param {*=} response An argument for the rejected promise. + * + */ + +function MdBottomSheetProvider($$interimElementProvider) { + // how fast we need to flick down to close the sheet, pixels/ms + bottomSheetDefaults.$inject = ["$animate", "$mdConstant", "$mdUtil", "$mdTheming", "$mdBottomSheet", "$rootElement", "$mdGesture", "$log"]; + var CLOSING_VELOCITY = 0.5; + var PADDING = 80; // same as css + + return $$interimElementProvider('$mdBottomSheet') + .setDefaults({ + methods: ['disableParentScroll', 'escapeToClose', 'clickOutsideToClose'], + options: bottomSheetDefaults + }); + + /* @ngInject */ + function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $mdTheming, $mdBottomSheet, $rootElement, + $mdGesture, $log) { + var backdrop; + + return { + themable: true, + onShow: onShow, + onRemove: onRemove, + disableBackdrop: false, + escapeToClose: true, + clickOutsideToClose: true, + disableParentScroll: true + }; + + + function onShow(scope, element, options, controller) { + + element = $mdUtil.extractElementByName(element, 'md-bottom-sheet'); + + // prevent tab focus or click focus on the bottom-sheet container + element.attr('tabindex',"-1"); + + // Once the md-bottom-sheet has `ng-cloak` applied on his template the opening animation will not work properly. + // This is a very common problem, so we have to notify the developer about this. + if (element.hasClass('ng-cloak')) { + var message = '$mdBottomSheet: using `` will affect the bottom-sheet opening animations.'; + $log.warn( message, element[0] ); + } + + if (!options.disableBackdrop) { + // Add a backdrop that will close on click + backdrop = $mdUtil.createBackdrop(scope, "md-bottom-sheet-backdrop md-opaque"); + + // Prevent mouse focus on backdrop; ONLY programatic focus allowed. + // This allows clicks on backdrop to propogate to the $rootElement and + // ESC key events to be detected properly. + + backdrop[0].tabIndex = -1; + + if (options.clickOutsideToClose) { + backdrop.on('click', function() { + $mdUtil.nextTick($mdBottomSheet.cancel,true); + }); + } + + $mdTheming.inherit(backdrop, options.parent); + + $animate.enter(backdrop, options.parent, null); + } + + var bottomSheet = new BottomSheet(element, options.parent); + options.bottomSheet = bottomSheet; + + $mdTheming.inherit(bottomSheet.element, options.parent); + + if (options.disableParentScroll) { + options.restoreScroll = $mdUtil.disableScrollAround(bottomSheet.element, options.parent); + } + + return $animate.enter(bottomSheet.element, options.parent, backdrop) + .then(function() { + var focusable = $mdUtil.findFocusTarget(element) || angular.element( + element[0].querySelector('button') || + element[0].querySelector('a') || + element[0].querySelector($mdUtil.prefixer('ng-click', true)) + ) || backdrop; + + if (options.escapeToClose) { + options.rootElementKeyupCallback = function(e) { + if (e.keyCode === $mdConstant.KEY_CODE.ESCAPE) { + $mdUtil.nextTick($mdBottomSheet.cancel,true); + } + }; + + $rootElement.on('keyup', options.rootElementKeyupCallback); + focusable && focusable.focus(); + } + }); + + } + + function onRemove(scope, element, options) { + + var bottomSheet = options.bottomSheet; + + if (!options.disableBackdrop) $animate.leave(backdrop); + return $animate.leave(bottomSheet.element).then(function() { + if (options.disableParentScroll) { + options.restoreScroll(); + delete options.restoreScroll; + } + + bottomSheet.cleanup(); + }); + } + + /** + * BottomSheet class to apply bottom-sheet behavior to an element + */ + function BottomSheet(element, parent) { + var deregister = $mdGesture.register(parent, 'drag', { horizontal: false }); + parent.on('$md.dragstart', onDragStart) + .on('$md.drag', onDrag) + .on('$md.dragend', onDragEnd); + + return { + element: element, + cleanup: function cleanup() { + deregister(); + parent.off('$md.dragstart', onDragStart); + parent.off('$md.drag', onDrag); + parent.off('$md.dragend', onDragEnd); + } + }; + + function onDragStart(ev) { + // Disable transitions on transform so that it feels fast + element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms'); + } + + function onDrag(ev) { + var transform = ev.pointer.distanceY; + if (transform < 5) { + // Slow down drag when trying to drag up, and stop after PADDING + transform = Math.max(-PADDING, transform / 2); + } + element.css($mdConstant.CSS.TRANSFORM, 'translate3d(0,' + (PADDING + transform) + 'px,0)'); + } + + function onDragEnd(ev) { + if (ev.pointer.distanceY > 0 && + (ev.pointer.distanceY > 20 || Math.abs(ev.pointer.velocityY) > CLOSING_VELOCITY)) { + var distanceRemaining = element.prop('offsetHeight') - ev.pointer.distanceY; + var transitionDuration = Math.min(distanceRemaining / ev.pointer.velocityY * 0.75, 500); + element.css($mdConstant.CSS.TRANSITION_DURATION, transitionDuration + 'ms'); + $mdUtil.nextTick($mdBottomSheet.cancel,true); + } else { + element.css($mdConstant.CSS.TRANSITION_DURATION, ''); + element.css($mdConstant.CSS.TRANSFORM, ''); + } + } + } + + } + +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.button + * @description + * + * Button + */ +MdButtonDirective.$inject = ["$mdButtonInkRipple", "$mdTheming", "$mdAria", "$timeout"]; +MdAnchorDirective.$inject = ["$mdTheming"]; +angular + .module('material.components.button', [ 'material.core' ]) + .directive('mdButton', MdButtonDirective) + .directive('a', MdAnchorDirective); + + +/** + * @private + * @restrict E + * + * @description + * `a` is an anchor directive used to inherit theme colors for md-primary, md-accent, etc. + * + * @usage + * + * + * + * + * + * + */ +function MdAnchorDirective($mdTheming) { + return { + restrict : 'E', + link : function postLink(scope, element) { + // Make sure to inherit theme so stand-alone anchors + // support theme colors for md-primary, md-accent, etc. + $mdTheming(element); + } + }; +} + + +/** + * @ngdoc directive + * @name mdButton + * @module material.components.button + * + * @restrict E + * + * @description + * `` is a button directive with optional ink ripples (default enabled). + * + * If you supply a `href` or `ng-href` attribute, it will become an `` element. Otherwise, it + * will become a `'; + } + } + + function postLink(scope, element, attr) { + $mdTheming(element); + $mdButtonInkRipple.attach(scope, element); + + // Use async expect to support possible bindings in the button label + $mdAria.expectWithoutText(element, 'aria-label'); + + // For anchor elements, we have to set tabindex manually when the + // element is disabled + if (isAnchor(attr) && angular.isDefined(attr.ngDisabled) ) { + scope.$watch(attr.ngDisabled, function(isDisabled) { + element.attr('tabindex', isDisabled ? -1 : 0); + }); + } + + // disabling click event when disabled is true + element.on('click', function(e){ + if (attr.disabled === true) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + }); + + if (!element.hasClass('md-no-focus')) { + // restrict focus styles to the keyboard + scope.mouseActive = false; + element.on('mousedown', function() { + scope.mouseActive = true; + $timeout(function(){ + scope.mouseActive = false; + }, 100); + }) + .on('focus', function() { + if (scope.mouseActive === false) { + element.addClass('md-focused'); + } + }) + .on('blur', function(ev) { + element.removeClass('md-focused'); + }); + } + + } + +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.card + * + * @description + * Card components. + */ +mdCardDirective.$inject = ["$mdTheming"]; +angular.module('material.components.card', [ + 'material.core' + ]) + .directive('mdCard', mdCardDirective); + + +/** + * @ngdoc directive + * @name mdCard + * @module material.components.card + * + * @restrict E + * + * @description + * The `` directive is a container element used within `` containers. + * + * An image included as a direct descendant will fill the card's width. If you want to avoid this, + * you can add the `md-image-no-fill` class to the parent element. The `` + * container will wrap text content and provide padding. An `` element can be + * optionally included to put content flush against the bottom edge of the card. + * + * Action buttons can be included in an `` element, similar to ``. + * You can then position buttons using layout attributes. + * + * Card is built with: + * * `` - Header for the card, holds avatar, text and squared image + * - `` - Card avatar + * - `md-user-avatar` - Class for user image + * - `` + * - `` - Contains elements for the card description + * - `md-title` - Class for the card title + * - `md-subhead` - Class for the card sub header + * * `` - Image for the card + * * `` - Card content title + * - `` + * - `md-headline` - Class for the card content title + * - `md-subhead` - Class for the card content sub header + * - `` - Squared image within the title + * - `md-media-sm` - Class for small image + * - `md-media-md` - Class for medium image + * - `md-media-lg` - Class for large image + * * `` - Card content + * - `md-media-xl` - Class for extra large image + * * `` - Card actions + * - `` - Icon actions + * + * Cards have constant width and variable heights; where the maximum height is limited to what can + * fit within a single view on a platform, but it can temporarily expand as needed. + * + * @usage + * ### Card with optional footer + * + * + * image caption + * + *

Card headline

+ *

Card content

+ *
+ * + * Card footer + * + *
+ *
+ * + * ### Card with actions + * + * + * image caption + * + *

Card headline

+ *

Card content

+ *
+ * + * Action 1 + * Action 2 + * + *
+ *
+ * + * ### Card with header, image, title actions and content + * + * + * + * + * + * + * + * Title + * Sub header + * + * + * image caption + * + * + * Card headline + * Card subheader + * + * + * + * Action 1 + * Action 2 + * + * + * + * + * + * + * + *

+ * Card content + *

+ *
+ *
+ *
+ */ +function mdCardDirective($mdTheming) { + return { + restrict: 'E', + link: function ($scope, $element, attr) { + $element.addClass('_md'); // private md component indicator for styling + $mdTheming($element); + } + }; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.chips + */ +/* + * @see js folder for chips implementation + */ +angular.module('material.components.chips', [ + 'material.core', + 'material.components.autocomplete' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.checkbox + * @description Checkbox module! + */ +MdCheckboxDirective.$inject = ["inputDirective", "$mdAria", "$mdConstant", "$mdTheming", "$mdUtil", "$timeout"]; +angular + .module('material.components.checkbox', ['material.core']) + .directive('mdCheckbox', MdCheckboxDirective); + +/** + * @ngdoc directive + * @name mdCheckbox + * @module material.components.checkbox + * @restrict E + * + * @description + * The checkbox directive is used like the normal [angular checkbox](https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D). + * + * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-color-schemes) + * the checkbox is in the accent color by default. The primary color palette may be used with + * the `md-primary` class. + * + * @param {string} ng-model Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {expression=} ng-true-value The value to which the expression should be set when selected. + * @param {expression=} ng-false-value The value to which the expression should be set when not selected. + * @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element. + * @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects + * @param {string=} aria-label Adds label to checkbox for accessibility. + * Defaults to checkbox's text. If no default text is found, a warning will be logged. + * @param {expression=} md-indeterminate This determines when the checkbox should be rendered as 'indeterminate'. + * If a truthy expression or no value is passed in the checkbox renders in the md-indeterminate state. + * If falsy expression is passed in it just looks like a normal unchecked checkbox. + * The indeterminate, checked, and unchecked states are mutually exclusive. A box cannot be in any two states at the same time. + * Adding the 'md-indeterminate' attribute overrides any checked/unchecked rendering logic. + * When using the 'md-indeterminate' attribute use 'ng-checked' to define rendering logic instead of using 'ng-model'. + * @param {expression=} ng-checked If this expression evaluates as truthy, the 'md-checked' css class is added to the checkbox and it + * will appear checked. + * + * @usage + * + * + * Finished ? + * + * + * + * No Ink Effects + * + * + * + * Disabled + * + * + * + * + */ +function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $mdUtil, $timeout) { + inputDirective = inputDirective[0]; + + return { + restrict: 'E', + transclude: true, + require: '?ngModel', + priority: 210, // Run before ngAria + template: + '
' + + '
' + + '
' + + '
', + compile: compile + }; + + // ********************************************************** + // Private Methods + // ********************************************************** + + function compile (tElement, tAttrs) { + tAttrs.$set('tabindex', tAttrs.tabindex || '0'); + tAttrs.$set('type', 'checkbox'); + tAttrs.$set('role', tAttrs.type); + + return { + pre: function(scope, element) { + // Attach a click handler during preLink, in order to immediately stop propagation + // (especially for ng-click) when the checkbox is disabled. + element.on('click', function(e) { + if (this.hasAttribute('disabled')) { + e.stopImmediatePropagation(); + } + }); + }, + post: postLink + }; + + function postLink(scope, element, attr, ngModelCtrl) { + var isIndeterminate; + ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel(); + $mdTheming(element); + + // Redirect focus events to the root element, because IE11 is always focusing the container element instead + // of the md-checkbox element. This causes issues when using ngModelOptions: `updateOnBlur` + element.children().on('focus', function() { + element.focus(); + }); + + if ($mdUtil.parseAttributeBoolean(attr.mdIndeterminate)) { + setIndeterminateState(); + scope.$watch(attr.mdIndeterminate, setIndeterminateState); + } + + if (attr.ngChecked) { + scope.$watch(scope.$eval.bind(scope, attr.ngChecked), function(value) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + }); + } + + $$watchExpr('ngDisabled', 'tabindex', { + true: '-1', + false: attr.tabindex + }); + + $mdAria.expectWithText(element, 'aria-label'); + + // Reuse the original input[type=checkbox] directive from Angular core. + // This is a bit hacky as we need our own event listener and own render + // function. + inputDirective.link.pre(scope, { + on: angular.noop, + 0: {} + }, attr, [ngModelCtrl]); + + scope.mouseActive = false; + element.on('click', listener) + .on('keypress', keypressHandler) + .on('mousedown', function() { + scope.mouseActive = true; + $timeout(function() { + scope.mouseActive = false; + }, 100); + }) + .on('focus', function() { + if (scope.mouseActive === false) { + element.addClass('md-focused'); + } + }) + .on('blur', function() { + element.removeClass('md-focused'); + }); + + ngModelCtrl.$render = render; + + function $$watchExpr(expr, htmlAttr, valueOpts) { + if (attr[expr]) { + scope.$watch(attr[expr], function(val) { + if (valueOpts[val]) { + element.attr(htmlAttr, valueOpts[val]); + } + }); + } + } + + function keypressHandler(ev) { + var keyCode = ev.which || ev.keyCode; + if (keyCode === $mdConstant.KEY_CODE.SPACE || keyCode === $mdConstant.KEY_CODE.ENTER) { + ev.preventDefault(); + element.addClass('md-focused'); + listener(ev); + } + } + + function listener(ev) { + // skipToggle boolean is used by the switch directive to prevent the click event + // when releasing the drag. There will be always a click if releasing the drag over the checkbox + if (element[0].hasAttribute('disabled') || scope.skipToggle) { + return; + } + + scope.$apply(function() { + // Toggle the checkbox value... + var viewValue = attr.ngChecked ? attr.checked : !ngModelCtrl.$viewValue; + + ngModelCtrl.$setViewValue(viewValue, ev && ev.type); + ngModelCtrl.$render(); + }); + } + + function render() { + // Cast the $viewValue to a boolean since it could be undefined + element.toggleClass('md-checked', !!ngModelCtrl.$viewValue && !isIndeterminate); + } + + function setIndeterminateState(newValue) { + isIndeterminate = newValue !== false; + if (isIndeterminate) { + element.attr('aria-checked', 'mixed'); + } + element.toggleClass('md-indeterminate', isIndeterminate); + } + } + } +} + +})(); +(function(){ +"use strict"; + +(function () { + "use strict"; + + /** + * Use a RegExp to check if the `md-colors=""` is static string + * or one that should be observed and dynamically interpolated. + */ + MdColorsDirective.$inject = ["$mdColors", "$mdUtil", "$log", "$parse"]; + MdColorsService.$inject = ["$mdTheming", "$mdUtil", "$log"]; + var STATIC_COLOR_EXPRESSION = /^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/; + var colorPalettes = null; + + /** + * @ngdoc module + * @name material.components.colors + * + * @description + * Define $mdColors service and a `md-colors=""` attribute directive + */ + angular + .module('material.components.colors', ['material.core']) + .directive('mdColors', MdColorsDirective) + .service('$mdColors', MdColorsService); + + /** + * @ngdoc service + * @name $mdColors + * @module material.components.colors + * + * @description + * With only defining themes, one couldn't get non ngMaterial elements colored with Material colors, + * `$mdColors` service is used by the md-color directive to convert the 1..n color expressions to RGBA values and will apply + * those values to element as CSS property values. + * + * @usage + * + * angular.controller('myCtrl', function ($mdColors) { + * var color = $mdColors.getThemeColor('myTheme-red-200-0.5'); + * ... + * }); + * + * + */ + function MdColorsService($mdTheming, $mdUtil, $log) { + colorPalettes = colorPalettes || Object.keys($mdTheming.PALETTES); + + // Publish service instance + return { + applyThemeColors: applyThemeColors, + getThemeColor: getThemeColor, + hasTheme: hasTheme + }; + + // ******************************************** + // Internal Methods + // ******************************************** + + /** + * @ngdoc method + * @name $mdColors#applyThemeColors + * + * @description + * Gets a color json object, keys are css properties and values are string of the wanted color + * Then calculate the rgba() values based on the theme color parts + * + * @param {DOMElement} element the element to apply the styles on. + * @param {object} colorExpression json object, keys are css properties and values are string of the wanted color, + * for example: `{color: 'red-A200-0.3'}`. + * + * @usage + * + * app.directive('myDirective', function($mdColors) { + * return { + * ... + * link: function (scope, elem) { + * $mdColors.applyThemeColors(elem, {color: 'red'}); + * } + * } + * }); + * + */ + function applyThemeColors(element, colorExpression) { + try { + if (colorExpression) { + // Assign the calculate RGBA color values directly as inline CSS + element.css(interpolateColors(colorExpression)); + } + } catch (e) { + $log.error(e.message); + } + + } + + /** + * @ngdoc method + * @name $mdColors#getThemeColor + * + * @description + * Get parsed color from expression + * + * @param {string} expression string of a color expression (for instance `'red-700-0.8'`) + * + * @returns {string} a css color expression (for instance `rgba(211, 47, 47, 0.8)`) + * + * @usage + * + * angular.controller('myCtrl', function ($mdColors) { + * var color = $mdColors.getThemeColor('myTheme-red-200-0.5'); + * ... + * }); + * + */ + function getThemeColor(expression) { + var color = extractColorOptions(expression); + + return parseColor(color); + } + + /** + * Return the parsed color + * @param color hashmap of color definitions + * @param contrast whether use contrast color for foreground + * @returns rgba color string + */ + function parseColor(color, contrast) { + contrast = contrast || false; + var rgbValues = $mdTheming.PALETTES[color.palette][color.hue]; + + rgbValues = contrast ? rgbValues.contrast : rgbValues.value; + + return $mdUtil.supplant('rgba({0}, {1}, {2}, {3})', + [rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3] || color.opacity] + ); + } + + /** + * Convert the color expression into an object with scope-interpolated values + * Then calculate the rgba() values based on the theme color parts + * + * @results Hashmap of CSS properties with associated `rgba( )` string vales + * + * + */ + function interpolateColors(themeColors) { + var rgbColors = {}; + + var hasColorProperty = themeColors.hasOwnProperty('color'); + + angular.forEach(themeColors, function (value, key) { + var color = extractColorOptions(value); + var hasBackground = key.indexOf('background') > -1; + + rgbColors[key] = parseColor(color); + if (hasBackground && !hasColorProperty) { + rgbColors.color = parseColor(color, true); + } + }); + + return rgbColors; + } + + /** + * Check if expression has defined theme + * e.g. + * 'myTheme-primary' => true + * 'red-800' => false + */ + function hasTheme(expression) { + return angular.isDefined($mdTheming.THEMES[expression.split('-')[0]]); + } + + /** + * For the evaluated expression, extract the color parts into a hash map + */ + function extractColorOptions(expression) { + var parts = expression.split('-'); + var hasTheme = angular.isDefined($mdTheming.THEMES[parts[0]]); + var theme = hasTheme ? parts.splice(0, 1)[0] : $mdTheming.defaultTheme(); + + return { + theme: theme, + palette: extractPalette(parts, theme), + hue: extractHue(parts, theme), + opacity: parts[2] || 1 + }; + } + + /** + * Calculate the theme palette name + */ + function extractPalette(parts, theme) { + // If the next section is one of the palettes we assume it's a two word palette + // Two word palette can be also written in camelCase, forming camelCase to dash-case + + var isTwoWord = parts.length > 1 && colorPalettes.indexOf(parts[1]) !== -1; + var palette = parts[0].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + + if (isTwoWord) palette = parts[0] + '-' + parts.splice(1, 1); + + if (colorPalettes.indexOf(palette) === -1) { + // If the palette is not in the palette list it's one of primary/accent/warn/background + var scheme = $mdTheming.THEMES[theme].colors[palette]; + if (!scheme) { + throw new Error($mdUtil.supplant('mdColors: couldn\'t find \'{palette}\' in the palettes.', {palette: palette})); + } + palette = scheme.name; + } + + return palette; + } + + function extractHue(parts, theme) { + var themeColors = $mdTheming.THEMES[theme].colors; + + if (parts[1] === 'hue') { + var hueNumber = parseInt(parts.splice(2, 1)[0], 10); + + if (hueNumber < 1 || hueNumber > 3) { + throw new Error($mdUtil.supplant('mdColors: \'hue-{hueNumber}\' is not a valid hue, can be only \'hue-1\', \'hue-2\' and \'hue-3\'', {hueNumber: hueNumber})); + } + parts[1] = 'hue-' + hueNumber; + + if (!(parts[0] in themeColors)) { + throw new Error($mdUtil.supplant('mdColors: \'hue-x\' can only be used with [{availableThemes}], but was used with \'{usedTheme}\'', { + availableThemes: Object.keys(themeColors).join(', '), + usedTheme: parts[0] + })); + } + + return themeColors[parts[0]].hues[parts[1]]; + } + + return parts[1] || themeColors[parts[0] in themeColors ? parts[0] : 'primary'].hues['default']; + } + } + + /** + * @ngdoc directive + * @name mdColors + * @module material.components.colors + * + * @restrict A + * + * @description + * `mdColors` directive will apply the theme-based color expression as RGBA CSS style values. + * + * The format will be similar to our color defining in the scss files: + * + * ## `[?theme]-[palette]-[?hue]-[?opacity]` + * - [theme] - default value is the default theme + * - [palette] - can be either palette name or primary/accent/warn/background + * - [hue] - default is 500 (hue-x can be used with primary/accent/warn/background) + * - [opacity] - default is 1 + * + * > `?` indicates optional parameter + * + * @usage + * + *
+ *
+ * Color demo + *
+ *
+ *
+ * + * `mdColors` directive will automatically watch for changes in the expression if it recognizes an interpolation + * expression or a function. For performance options, you can use `::` prefix to the `md-colors` expression + * to indicate a one-time data binding. + * + * + * + * + * + */ + function MdColorsDirective($mdColors, $mdUtil, $log, $parse) { + return { + restrict: 'A', + require: ['^?mdTheme'], + compile: function (tElem, tAttrs) { + var shouldWatch = shouldColorsWatch(); + + return function (scope, element, attrs, ctrl) { + var mdThemeController = ctrl[0]; + + var lastColors = {}; + + var parseColors = function (theme) { + if (typeof theme !== 'string') { + theme = ''; + } + + if (!attrs.mdColors) { + attrs.mdColors = '{}'; + } + + /** + * Json.parse() does not work because the keys are not quoted; + * use $parse to convert to a hash map + */ + var colors = $parse(attrs.mdColors)(scope); + + /** + * If mdTheme is defined up the DOM tree + * we add mdTheme theme to colors who doesn't specified a theme + * + * # example + * + *
+ *
+ * Color demo + *
+ *
+ *
+ * + * 'primary-600' will be 'myTheme-primary-600', + * but 'mySecondTheme-accent-200' will stay the same cause it has a theme prefix + */ + if (mdThemeController) { + Object.keys(colors).forEach(function (prop) { + var color = colors[prop]; + if (!$mdColors.hasTheme(color)) { + colors[prop] = (theme || mdThemeController.$mdTheme) + '-' + color; + } + }); + } + + cleanElement(colors); + + return colors; + }; + + var cleanElement = function (colors) { + if (!angular.equals(colors, lastColors)) { + var keys = Object.keys(lastColors); + + if (lastColors.background && !keys.color) { + keys.push('color'); + } + + keys.forEach(function (key) { + element.css(key, ''); + }); + } + + lastColors = colors; + }; + + /** + * Registering for mgTheme changes and asking mdTheme controller run our callback whenever a theme changes + */ + var unregisterChanges = angular.noop; + + if (mdThemeController) { + unregisterChanges = mdThemeController.registerChanges(function (theme) { + $mdColors.applyThemeColors(element, parseColors(theme)); + }); + } + + scope.$on('$destroy', function () { + unregisterChanges(); + }); + + try { + if (shouldWatch) { + scope.$watch(parseColors, angular.bind(this, + $mdColors.applyThemeColors, element + ), true); + } + else { + $mdColors.applyThemeColors(element, parseColors()); + } + + } + catch (e) { + $log.error(e.message); + } + + }; + + function shouldColorsWatch() { + // Simulate 1x binding and mark mdColorsWatch == false + var rawColorExpression = tAttrs.mdColors; + var bindOnce = rawColorExpression.indexOf('::') > -1; + var isStatic = bindOnce ? true : STATIC_COLOR_EXPRESSION.test(tAttrs.mdColors); + + // Remove it for the postLink... + tAttrs.mdColors = rawColorExpression.replace('::', ''); + + var hasWatchAttr = angular.isDefined(tAttrs.mdColorsWatch); + + return (bindOnce || isStatic) ? false : + hasWatchAttr ? $mdUtil.parseAttributeBoolean(tAttrs.mdColorsWatch) : true; + } + } + }; + + } + + +})(); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.content + * + * @description + * Scrollable content + */ +mdContentDirective.$inject = ["$mdTheming"]; +angular.module('material.components.content', [ + 'material.core' +]) + .directive('mdContent', mdContentDirective); + +/** + * @ngdoc directive + * @name mdContent + * @module material.components.content + * + * @restrict E + * + * @description + * + * The `` directive is a container element useful for scrollable content. It achieves + * this by setting the CSS `overflow` property to `auto` so that content can properly scroll. + * + * In general, `` components are not designed to be nested inside one another. If + * possible, it is better to make them siblings. This often results in a better user experience as + * having nested scrollbars may confuse the user. + * + * ## Troubleshooting + * + * In some cases, you may wish to apply the `md-no-momentum` class to ensure that Safari's + * momentum scrolling is disabled. Momentum scrolling can cause flickering issues while scrolling + * SVG icons and some other components. + * + * Additionally, we now also offer the `md-no-flicker` class which can be applied to any element + * and uses a Webkit-specific filter of `blur(0px)` that forces GPU rendering of all elements + * inside (which eliminates the flicker on iOS devices). + * + * _Note: Forcing an element to render on the GPU can have unintended side-effects, especially + * related to the z-index of elements. Please use with caution and only on the elements needed._ + * + * @usage + * + * Add the `[layout-padding]` attribute to make the content padded. + * + * + * + * Lorem ipsum dolor sit amet, ne quod novum mei. + * + * + */ + +function mdContentDirective($mdTheming) { + return { + restrict: 'E', + controller: ['$scope', '$element', ContentController], + link: function(scope, element) { + element.addClass('_md'); // private md component indicator for styling + + $mdTheming(element); + scope.$broadcast('$mdContentLoaded', element); + + iosScrollFix(element[0]); + } + }; + + function ContentController($scope, $element) { + this.$scope = $scope; + this.$element = $element; + } +} + +function iosScrollFix(node) { + // IOS FIX: + // If we scroll where there is no more room for the webview to scroll, + // by default the webview itself will scroll up and down, this looks really + // bad. So if we are scrolling to the very top or bottom, add/subtract one + angular.element(node).on('$md.pressdown', function(ev) { + // Only touch events + if (ev.pointer.type !== 't') return; + // Don't let a child content's touchstart ruin it for us. + if (ev.$materialScrollFixed) return; + ev.$materialScrollFixed = true; + + if (node.scrollTop === 0) { + node.scrollTop = 1; + } else if (node.scrollHeight === node.scrollTop + node.offsetHeight) { + node.scrollTop -= 1; + } + }); +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.datepicker + * @description Module for the datepicker component. + */ + +angular.module('material.components.datepicker', [ + 'material.core', + 'material.components.icon', + 'material.components.virtualRepeat' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.dialog + */ +MdDialogDirective.$inject = ["$$rAF", "$mdTheming", "$mdDialog"]; +MdDialogProvider.$inject = ["$$interimElementProvider"]; +angular + .module('material.components.dialog', [ + 'material.core', + 'material.components.backdrop' + ]) + .directive('mdDialog', MdDialogDirective) + .provider('$mdDialog', MdDialogProvider); + +/** + * @ngdoc directive + * @name mdDialog + * @module material.components.dialog + * + * @restrict E + * + * @description + * `` - The dialog's template must be inside this element. + * + * Inside, use an `` element for the dialog's content, and use + * an `` element for the dialog's actions. + * + * ## CSS + * - `.md-dialog-content` - class that sets the padding on the content as the spec file + * + * ## Notes + * - If you specify an `id` for the ``, the `` will have the same `id` + * prefixed with `dialogContent_`. + * + * @usage + * ### Dialog template + * + * + * + * + * + *

Number {{item}}

+ *
+ *
+ *
+ * + * Close Dialog + * + *
+ *
+ */ +function MdDialogDirective($$rAF, $mdTheming, $mdDialog) { + return { + restrict: 'E', + link: function(scope, element) { + element.addClass('_md'); // private md component indicator for styling + + $mdTheming(element); + $$rAF(function() { + var images; + var content = element[0].querySelector('md-dialog-content'); + + if (content) { + images = content.getElementsByTagName('img'); + addOverflowClass(); + //-- delayed image loading may impact scroll height, check after images are loaded + angular.element(images).on('load', addOverflowClass); + } + + scope.$on('$destroy', function() { + $mdDialog.destroy(element); + }); + + /** + * + */ + function addOverflowClass() { + element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight); + } + + + }); + } + }; +} + +/** + * @ngdoc service + * @name $mdDialog + * @module material.components.dialog + * + * @description + * `$mdDialog` opens a dialog over the app to inform users about critical information or require + * them to make decisions. There are two approaches for setup: a simple promise API + * and regular object syntax. + * + * ## Restrictions + * + * - The dialog is always given an isolate scope. + * - The dialog's template must have an outer `` element. + * Inside, use an `` element for the dialog's content, and use + * an `` element for the dialog's actions. + * - Dialogs must cover the entire application to keep interactions inside of them. + * Use the `parent` option to change where dialogs are appended. + * + * ## Sizing + * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`. + * - Default max-width is 80% of the `rootElement` or `parent`. + * + * ## CSS + * - `.md-dialog-content` - class that sets the padding on the content as the spec file + * + * @usage + * + *
+ *
+ * + * Employee Alert! + * + *
+ *
+ * + * Custom Dialog + * + *
+ *
+ * + * Close Alert + * + *
+ *
+ * + * Greet Employee + * + *
+ *
+ *
+ * + * ### JavaScript: object syntax + * + * (function(angular, undefined){ + * "use strict"; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .controller('AppCtrl', AppController); + * + * function AppController($scope, $mdDialog) { + * var alert; + * $scope.showAlert = showAlert; + * $scope.showDialog = showDialog; + * $scope.items = [1, 2, 3]; + * + * // Internal method + * function showAlert() { + * alert = $mdDialog.alert({ + * title: 'Attention', + * textContent: 'This is an example of how easy dialogs can be!', + * ok: 'Close' + * }); + * + * $mdDialog + * .show( alert ) + * .finally(function() { + * alert = undefined; + * }); + * } + * + * function showDialog($event) { + * var parentEl = angular.element(document.body); + * $mdDialog.show({ + * parent: parentEl, + * targetEvent: $event, + * template: + * '' + + * ' '+ + * ' '+ + * ' '+ + * '

Number {{item}}

' + + * ' '+ + * '
'+ + * '
' + + * ' ' + + * ' ' + + * ' Close Dialog' + + * ' ' + + * ' ' + + * '
', + * locals: { + * items: $scope.items + * }, + * controller: DialogController + * }); + * function DialogController($scope, $mdDialog, items) { + * $scope.items = items; + * $scope.closeDialog = function() { + * $mdDialog.hide(); + * } + * } + * } + * } + * })(angular); + *
+ * + * ### Pre-Rendered Dialogs + * By using the `contentElement` option, it is possible to use an already existing element in the DOM. + * + * + * $scope.showPrerenderedDialog = function() { + * $mdDialog.show({ + * contentElement: '#myStaticDialog', + * parent: angular.element(document.body) + * }); + * }; + * + * + * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS selector. + * + * + *
+ *
+ * + * This is a pre-rendered dialog. + * + *
+ *
+ *
+ * + * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise the dialog + * will not show up. + * + * It also possible to use a DOM element for the `contentElement` option. + * - `contentElement: document.querySelector('#myStaticDialog')` + * - `contentElement: angular.element(TEMPLATE)` + * + * When using a `template` as content element, it will be not compiled upon open. + * This allows you to compile the element yourself and use it each time the dialog opens. + * + * ### Custom Presets + * Developers are also able to create their own preset, which can be easily used without repeating + * their options each time. + * + * + * $mdDialogProvider.addPreset('testPreset', { + * options: function() { + * return { + * template: + * '' + + * 'This is a custom preset' + + * '', + * controllerAs: 'dialog', + * bindToController: true, + * clickOutsideToClose: true, + * escapeToClose: true + * }; + * } + * }); + * + * + * After you created your preset at config phase, you can easily access it. + * + * + * $mdDialog.show( + * $mdDialog.testPreset() + * ); + * + * + * ### JavaScript: promise API syntax, custom dialog template + * + * (function(angular, undefined){ + * "use strict"; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .controller('EmployeeController', EmployeeEditor) + * .controller('GreetingController', GreetingController); + * + * // Fictitious Employee Editor to show how to use simple and complex dialogs. + * + * function EmployeeEditor($scope, $mdDialog) { + * var alert; + * + * $scope.showAlert = showAlert; + * $scope.closeAlert = closeAlert; + * $scope.showGreeting = showCustomGreeting; + * + * $scope.hasAlert = function() { return !!alert }; + * $scope.userName = $scope.userName || 'Bobby'; + * + * // Dialog #1 - Show simple alert dialog and cache + * // reference to dialog instance + * + * function showAlert() { + * alert = $mdDialog.alert() + * .title('Attention, ' + $scope.userName) + * .textContent('This is an example of how easy dialogs can be!') + * .ok('Close'); + * + * $mdDialog + * .show( alert ) + * .finally(function() { + * alert = undefined; + * }); + * } + * + * // Close the specified dialog instance and resolve with 'finished' flag + * // Normally this is not needed, just use '$mdDialog.hide()' to close + * // the most recent dialog popup. + * + * function closeAlert() { + * $mdDialog.hide( alert, "finished" ); + * alert = undefined; + * } + * + * // Dialog #2 - Demonstrate more complex dialogs construction and popup. + * + * function showCustomGreeting($event) { + * $mdDialog.show({ + * targetEvent: $event, + * template: + * '' + + * + * ' Hello {{ employee }}!' + + * + * ' ' + + * ' ' + + * ' Close Greeting' + + * ' ' + + * ' ' + + * '', + * controller: 'GreetingController', + * onComplete: afterShowAnimation, + * locals: { employee: $scope.userName } + * }); + * + * // When the 'enter' animation finishes... + * + * function afterShowAnimation(scope, element, options) { + * // post-show code here: DOM element focus, etc. + * } + * } + * + * // Dialog #3 - Demonstrate use of ControllerAs and passing $scope to dialog + * // Here we used ng-controller="GreetingController as vm" and + * // $scope.vm === + * + * function showCustomGreeting() { + * + * $mdDialog.show({ + * clickOutsideToClose: true, + * + * scope: $scope, // use parent scope in template + * preserveScope: true, // do not forget this if use parent scope + + * // Since GreetingController is instantiated with ControllerAs syntax + * // AND we are passing the parent '$scope' to the dialog, we MUST + * // use 'vm.' in the template markup + * + * template: '' + + * ' ' + + * ' Hi There {{vm.employee}}' + + * ' ' + + * '', + * + * controller: function DialogController($scope, $mdDialog) { + * $scope.closeDialog = function() { + * $mdDialog.hide(); + * } + * } + * }); + * } + * + * } + * + * // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog + * + * function GreetingController($scope, $mdDialog, employee) { + * // Assigned from construction locals options... + * $scope.employee = employee; + * + * $scope.closeDialog = function() { + * // Easily hides most recent dialog shown... + * // no specific instance reference is needed. + * $mdDialog.hide(); + * }; + * } + * + * })(angular); + * + */ + +/** + * @ngdoc method + * @name $mdDialog#alert + * + * @description + * Builds a preconfigured dialog with the specified message. + * + * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods: + * + * - $mdDialogPreset#title(string) - Sets the alert title. + * - $mdDialogPreset#textContent(string) - Sets the alert message. + * - $mdDialogPreset#htmlContent(string) - Sets the alert message as HTML. Requires ngSanitize + * module to be loaded. HTML is not run through Angular's compiler. + * - $mdDialogPreset#ok(string) - Sets the alert "Okay" button text. + * - $mdDialogPreset#theme(string) - Sets the theme of the alert dialog. + * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * + */ + +/** + * @ngdoc method + * @name $mdDialog#confirm + * + * @description + * Builds a preconfigured dialog with the specified message. You can call show and the promise returned + * will be resolved only if the user clicks the confirm action on the dialog. + * + * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods: + * + * Additionally, it supports the following methods: + * + * - $mdDialogPreset#title(string) - Sets the confirm title. + * - $mdDialogPreset#textContent(string) - Sets the confirm message. + * - $mdDialogPreset#htmlContent(string) - Sets the confirm message as HTML. Requires ngSanitize + * module to be loaded. HTML is not run through Angular's compiler. + * - $mdDialogPreset#ok(string) - Sets the confirm "Okay" button text. + * - $mdDialogPreset#cancel(string) - Sets the confirm "Cancel" button text. + * - $mdDialogPreset#theme(string) - Sets the theme of the confirm dialog. + * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * + */ + +/** + * @ngdoc method + * @name $mdDialog#prompt + * + * @description + * Builds a preconfigured dialog with the specified message and input box. You can call show and the promise returned + * will be resolved only if the user clicks the prompt action on the dialog, passing the input value as the first argument. + * + * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods: + * + * Additionally, it supports the following methods: + * + * - $mdDialogPreset#title(string) - Sets the prompt title. + * - $mdDialogPreset#textContent(string) - Sets the prompt message. + * - $mdDialogPreset#htmlContent(string) - Sets the prompt message as HTML. Requires ngSanitize + * module to be loaded. HTML is not run through Angular's compiler. + * - $mdDialogPreset#placeholder(string) - Sets the placeholder text for the input. + * - $mdDialogPreset#initialValue(string) - Sets the initial value for the prompt input. + * - $mdDialogPreset#ok(string) - Sets the prompt "Okay" button text. + * - $mdDialogPreset#cancel(string) - Sets the prompt "Cancel" button text. + * - $mdDialogPreset#theme(string) - Sets the theme of the prompt dialog. + * - $mdDialogPreset#targetEvent(DOMClickEvent=) - A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * + */ + +/** + * @ngdoc method + * @name $mdDialog#show + * + * @description + * Show a dialog with the specified options. + * + * @param {object} optionsOrPreset Either provide an `$mdDialogPreset` returned from `alert()`, and + * `confirm()`, or an options object with the following properties: + * - `templateUrl` - `{string=}`: The url of a template that will be used as the content + * of the dialog. + * - `template` - `{string=}`: HTML template to show in the dialog. This **must** be trusted HTML + * with respect to Angular's [$sce service](https://docs.angularjs.org/api/ng/service/$sce). + * This template should **never** be constructed with any kind of user input or user data. + * - `contentElement` - `{string|Element}`: Instead of using a template, which will be compiled each time a + * dialog opens, you can also use a DOM element.
+ * * When specifying an element, which is present on the DOM, `$mdDialog` will temporary fetch the element into + * the dialog and restores it at the old DOM position upon close. + * * When specifying a string, the string be used as a CSS selector, to lookup for the element in the DOM. + * - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template with a + * `` tag if one is not provided. Defaults to true. Can be disabled if you provide a + * custom dialog directive. + * - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option, + * the location of the click will be used as the starting point for the opening animation + * of the the dialog. + * - `openFrom` - `{string|Element|object}`: The query selector, DOM element or the Rect object + * that is used to determine the bounds (top, left, height, width) from which the Dialog will + * originate. + * - `closeTo` - `{string|Element|object}`: The query selector, DOM element or the Rect object + * that is used to determine the bounds (top, left, height, width) to which the Dialog will + * target. + * - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, + * it will create a new isolate scope. + * This scope will be destroyed when the dialog is removed unless `preserveScope` is set to true. + * - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false + * - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open. + * Default true. + * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog. + * Default true. + * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to + * close it. Default false. + * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog. + * Default true. + * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if + * focusing some other way, as focus management is required for dialogs to be accessible. + * Defaults to true. + * - `controller` - `{function|string=}`: The controller to associate with the dialog. The controller + * will be injected with the local `$mdDialog`, which passes along a scope for the dialog. + * - `locals` - `{object=}`: An object containing key/value pairs. The keys will be used as names + * of values to inject into the controller. For example, `locals: {three: 3}` would inject + * `three` into the controller, with the value 3. If `bindToController` is true, they will be + * copied to the controller instead. + * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in. + * - `resolve` - `{object=}`: Similar to locals, except it takes promises as values, and the + * dialog will not open until all of the promises resolve. + * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope. + * - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending + * to the root element of the application. + * - `onShowing` - `function(scope, element)`: Callback function used to announce the show() action is + * starting. + * - `onComplete` - `function(scope, element)`: Callback function used to announce when the show() action is + * finished. + * - `onRemoving` - `function(element, removePromise)`: Callback function used to announce the + * close/hide() action is starting. This allows developers to run custom animations + * in parallel the close animations. + * - `fullscreen` `{boolean=}`: An option to toggle whether the dialog should show in fullscreen + * or not. Defaults to `false`. + * @returns {promise} A promise that can be resolved with `$mdDialog.hide()` or + * rejected with `$mdDialog.cancel()`. + */ + +/** + * @ngdoc method + * @name $mdDialog#hide + * + * @description + * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`. + * + * @param {*=} response An argument for the resolved promise. + * + * @returns {promise} A promise that is resolved when the dialog has been closed. + */ + +/** + * @ngdoc method + * @name $mdDialog#cancel + * + * @description + * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`. + * + * @param {*=} response An argument for the rejected promise. + * + * @returns {promise} A promise that is resolved when the dialog has been closed. + */ + +function MdDialogProvider($$interimElementProvider) { + // Elements to capture and redirect focus when the user presses tab at the dialog boundary. + advancedDialogOptions.$inject = ["$mdDialog", "$mdConstant"]; + dialogDefaultOptions.$inject = ["$mdDialog", "$mdAria", "$mdUtil", "$mdConstant", "$animate", "$document", "$window", "$rootElement", "$log", "$injector", "$mdTheming"]; + var topFocusTrap, bottomFocusTrap; + + return $$interimElementProvider('$mdDialog') + .setDefaults({ + methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose', + 'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'contentElement'], + options: dialogDefaultOptions + }) + .addPreset('alert', { + methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'theme', + 'css'], + options: advancedDialogOptions + }) + .addPreset('confirm', { + methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'cancel', + 'theme', 'css'], + options: advancedDialogOptions + }) + .addPreset('prompt', { + methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'content', 'placeholder', 'ariaLabel', + 'ok', 'cancel', 'theme', 'css'], + options: advancedDialogOptions + }); + + /* @ngInject */ + function advancedDialogOptions($mdDialog, $mdConstant) { + return { + template: [ + '', + ' ', + '

{{ dialog.title }}

', + '
', + '
', + '

{{::dialog.mdTextContent}}

', + '
', + ' ', + ' ', + ' ', + '
', + ' ', + ' ', + ' {{ dialog.cancel }}', + ' ', + ' ', + ' {{ dialog.ok }}', + ' ', + ' ', + '
' + ].join('').replace(/\s\s+/g, ''), + controller: function mdDialogCtrl() { + var isPrompt = this.$type == 'prompt'; + + if (isPrompt && this.initialValue) { + this.result = this.initialValue; + } + + this.hide = function() { + $mdDialog.hide(isPrompt ? this.result : true); + }; + this.abort = function() { + $mdDialog.cancel(); + }; + this.keypress = function($event) { + if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) { + $mdDialog.hide(this.result); + } + }; + }, + controllerAs: 'dialog', + bindToController: true, + }; + } + + /* @ngInject */ + function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement, + $log, $injector, $mdTheming) { + + return { + hasBackdrop: true, + isolateScope: true, + onCompiling: beforeCompile, + onShow: onShow, + onShowing: beforeShow, + onRemove: onRemove, + clickOutsideToClose: false, + escapeToClose: true, + targetEvent: null, + contentElement: null, + closeTo: null, + openFrom: null, + focusOnOpen: true, + disableParentScroll: true, + autoWrap: true, + fullscreen: false, + transformTemplate: function(template, options) { + // Make the dialog container focusable, because otherwise the focus will be always redirected to + // an element outside of the container, and the focus trap won't work probably.. + // Also the tabindex is needed for the `escapeToClose` functionality, because + // the keyDown event can't be triggered when the focus is outside of the container. + return '
' + validatedTemplate(template) + '
'; + + /** + * The specified template should contain a wrapper element.... + */ + function validatedTemplate(template) { + if (options.autoWrap && !/<\/md-dialog>/g.test(template)) { + return '' + (template || '') + ''; + } else { + return template || ''; + } + } + } + }; + + function beforeCompile(options) { + // Automatically apply the theme, if the user didn't specify a theme explicitly. + // Those option changes need to be done, before the compilation has started, because otherwise + // the option changes will be not available in the $mdCompilers locales. + detectTheming(options); + + if (options.contentElement) { + options.restoreContentElement = installContentElement(options); + } + } + + function beforeShow(scope, element, options, controller) { + + if (controller) { + controller.mdHtmlContent = controller.htmlContent || options.htmlContent || ''; + controller.mdTextContent = controller.textContent || options.textContent || + controller.content || options.content || ''; + + if (controller.mdHtmlContent && !$injector.has('$sanitize')) { + throw Error('The ngSanitize module must be loaded in order to use htmlContent.'); + } + + if (controller.mdHtmlContent && controller.mdTextContent) { + throw Error('md-dialog cannot have both `htmlContent` and `textContent`'); + } + } + } + + /** Show method for dialogs */ + function onShow(scope, element, options, controller) { + angular.element($document[0].body).addClass('md-dialog-is-showing'); + + var dialogElement = element.find('md-dialog'); + + // Once a dialog has `ng-cloak` applied on his template the dialog animation will not work properly. + // This is a very common problem, so we have to notify the developer about this. + if (dialogElement.hasClass('ng-cloak')) { + var message = '$mdDialog: using `` will affect the dialog opening animations.'; + $log.warn( message, element[0] ); + } + + captureParentAndFromToElements(options); + configureAria(dialogElement, options); + showBackdrop(scope, element, options); + activateListeners(element, options); + + return dialogPopIn(element, options) + .then(function() { + lockScreenReader(element, options); + warnDeprecatedActions(); + focusOnOpen(); + }); + + /** + * Check to see if they used the deprecated .md-actions class and log a warning + */ + function warnDeprecatedActions() { + if (element[0].querySelector('.md-actions')) { + $log.warn('Using a class of md-actions is deprecated, please use .'); + } + } + + /** + * For alerts, focus on content... otherwise focus on + * the close button (or equivalent) + */ + function focusOnOpen() { + if (options.focusOnOpen) { + var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement; + target.focus(); + } + + /** + * If no element with class dialog-close, try to find the last + * button child in md-actions and assume it is a close button. + * + * If we find no actions at all, log a warning to the console. + */ + function findCloseButton() { + var closeButton = element[0].querySelector('.dialog-close'); + + if (!closeButton) { + var actionButtons = element[0].querySelectorAll('.md-actions button, md-dialog-actions button'); + closeButton = actionButtons[actionButtons.length - 1]; + } + + return closeButton; + } + } + } + + /** + * Remove function for all dialogs + */ + function onRemove(scope, element, options) { + options.deactivateListeners(); + options.unlockScreenReader(); + options.hideBackdrop(options.$destroy); + + // Remove the focus traps that we added earlier for keeping focus within the dialog. + if (topFocusTrap && topFocusTrap.parentNode) { + topFocusTrap.parentNode.removeChild(topFocusTrap); + } + + if (bottomFocusTrap && bottomFocusTrap.parentNode) { + bottomFocusTrap.parentNode.removeChild(bottomFocusTrap); + } + + // For navigation $destroy events, do a quick, non-animated removal, + // but for normal closes (from clicks, etc) animate the removal + return !!options.$destroy ? detachAndClean() : animateRemoval().then( detachAndClean ); + + /** + * For normal closes, animate the removal. + * For forced closes (like $destroy events), skip the animations + */ + function animateRemoval() { + return dialogPopOut(element, options); + } + + /** + * Detach the element + */ + function detachAndClean() { + angular.element($document[0].body).removeClass('md-dialog-is-showing'); + // Only remove the element, if it's not provided through the contentElement option. + if (!options.contentElement) { + element.remove(); + } else { + options.reverseContainerStretch(); + options.restoreContentElement(); + } + + if (!options.$destroy) options.origin.focus(); + } + } + + function detectTheming(options) { + // Only detect the theming, if the developer didn't specify the theme specifically. + if (options.theme) return; + + options.theme = $mdTheming.defaultTheme(); + + if (options.targetEvent && options.targetEvent.target) { + var targetEl = angular.element(options.targetEvent.target); + + // Once the user specifies a targetEvent, we will automatically try to find the correct + // nested theme. + options.theme = (targetEl.controller('mdTheme') || {}).$mdTheme || options.theme; + } + + } + + /** + * Installs a content element to the current $$interimElement provider options. + * @returns {Function} Function to restore the content element at its old DOM location. + */ + function installContentElement(options) { + var contentEl = options.contentElement; + var restoreFn = null; + + if (angular.isString(contentEl)) { + contentEl = document.querySelector(contentEl); + restoreFn = createRestoreFn(contentEl); + } else { + contentEl = contentEl[0] || contentEl; + + // When the element is visible in the DOM, then we restore it at close of the dialog. + // Otherwise it will be removed from the DOM after close. + if (document.contains(contentEl)) { + restoreFn = createRestoreFn(contentEl); + } else { + restoreFn = function() { + contentEl.parentNode.removeChild(contentEl); + } + } + } + + // Overwrite the options to use the content element. + options.element = angular.element(contentEl); + options.skipCompile = true; + + return restoreFn; + + function createRestoreFn(element) { + var parent = element.parentNode; + var nextSibling = element.nextElementSibling; + + return function() { + if (!nextSibling) { + // When the element didn't had any sibling, then it can be simply appended to the + // parent, because it plays no role, which index it had before. + parent.appendChild(element); + } else { + // When the element had a sibling, which marks the previous position of the element + // in the DOM, we insert it correctly before the sibling, to have the same index as + // before. + parent.insertBefore(element, nextSibling); + } + } + } + } + + /** + * Capture originator/trigger/from/to element information (if available) + * and the parent container for the dialog; defaults to the $rootElement + * unless overridden in the options.parent + */ + function captureParentAndFromToElements(options) { + options.origin = angular.extend({ + element: null, + bounds: null, + focus: angular.noop + }, options.origin || {}); + + options.parent = getDomElement(options.parent, $rootElement); + options.closeTo = getBoundingClientRect(getDomElement(options.closeTo)); + options.openFrom = getBoundingClientRect(getDomElement(options.openFrom)); + + if ( options.targetEvent ) { + options.origin = getBoundingClientRect(options.targetEvent.target, options.origin); + } + + + /** + * Identify the bounding RECT for the target element + * + */ + function getBoundingClientRect (element, orig) { + var source = angular.element((element || {})); + if (source && source.length) { + // Compute and save the target element's bounding rect, so that if the + // element is hidden when the dialog closes, we can shrink the dialog + // back to the same position it expanded from. + // + // Checking if the source is a rect object or a DOM element + var bounds = {top:0,left:0,height:0,width:0}; + var hasFn = angular.isFunction(source[0].getBoundingClientRect); + + return angular.extend(orig || {}, { + element : hasFn ? source : undefined, + bounds : hasFn ? source[0].getBoundingClientRect() : angular.extend({}, bounds, source[0]), + focus : angular.bind(source, source.focus), + }); + } + } + + /** + * If the specifier is a simple string selector, then query for + * the DOM element. + */ + function getDomElement(element, defaultElement) { + if (angular.isString(element)) { + element = $document[0].querySelector(element); + } + + // If we have a reference to a raw dom element, always wrap it in jqLite + return angular.element(element || defaultElement); + } + + } + + /** + * Listen for escape keys and outside clicks to auto close + */ + function activateListeners(element, options) { + var window = angular.element($window); + var onWindowResize = $mdUtil.debounce(function() { + stretchDialogContainerToViewport(element, options); + }, 60); + + var removeListeners = []; + var smartClose = function() { + // Only 'confirm' dialogs have a cancel button... escape/clickOutside will + // cancel or fallback to hide. + var closeFn = ( options.$type == 'alert' ) ? $mdDialog.hide : $mdDialog.cancel; + $mdUtil.nextTick(closeFn, true); + }; + + if (options.escapeToClose) { + var parentTarget = options.parent; + var keyHandlerFn = function(ev) { + if (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + + smartClose(); + } + }; + + // Add keydown listeners + element.on('keydown', keyHandlerFn); + parentTarget.on('keydown', keyHandlerFn); + + // Queue remove listeners function + removeListeners.push(function() { + + element.off('keydown', keyHandlerFn); + parentTarget.off('keydown', keyHandlerFn); + + }); + } + + // Register listener to update dialog on window resize + window.on('resize', onWindowResize); + + removeListeners.push(function() { + window.off('resize', onWindowResize); + }); + + if (options.clickOutsideToClose) { + var target = element; + var sourceElem; + + // Keep track of the element on which the mouse originally went down + // so that we can only close the backdrop when the 'click' started on it. + // A simple 'click' handler does not work, + // it sets the target object as the element the mouse went down on. + var mousedownHandler = function(ev) { + sourceElem = ev.target; + }; + + // We check if our original element and the target is the backdrop + // because if the original was the backdrop and the target was inside the dialog + // we don't want to dialog to close. + var mouseupHandler = function(ev) { + if (sourceElem === target[0] && ev.target === target[0]) { + ev.stopPropagation(); + ev.preventDefault(); + + smartClose(); + } + }; + + // Add listeners + target.on('mousedown', mousedownHandler); + target.on('mouseup', mouseupHandler); + + // Queue remove listeners function + removeListeners.push(function() { + target.off('mousedown', mousedownHandler); + target.off('mouseup', mouseupHandler); + }); + } + + // Attach specific `remove` listener handler + options.deactivateListeners = function() { + removeListeners.forEach(function(removeFn) { + removeFn(); + }); + options.deactivateListeners = null; + }; + } + + /** + * Show modal backdrop element... + */ + function showBackdrop(scope, element, options) { + + if (options.disableParentScroll) { + // !! DO this before creating the backdrop; since disableScrollAround() + // configures the scroll offset; which is used by mdBackDrop postLink() + options.restoreScroll = $mdUtil.disableScrollAround(element, options.parent); + } + + if (options.hasBackdrop) { + options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque"); + $animate.enter(options.backdrop, options.parent); + } + + /** + * Hide modal backdrop element... + */ + options.hideBackdrop = function hideBackdrop($destroy) { + if (options.backdrop) { + if ( !!$destroy ) options.backdrop.remove(); + else $animate.leave(options.backdrop); + } + + + if (options.disableParentScroll) { + options.restoreScroll(); + delete options.restoreScroll; + } + + options.hideBackdrop = null; + }; + } + + /** + * Inject ARIA-specific attributes appropriate for Dialogs + */ + function configureAria(element, options) { + + var role = (options.$type === 'alert') ? 'alertdialog' : 'dialog'; + var dialogContent = element.find('md-dialog-content'); + var existingDialogId = element.attr('id'); + var dialogContentId = 'dialogContent_' + (existingDialogId || $mdUtil.nextUid()); + + element.attr({ + 'role': role, + 'tabIndex': '-1' + }); + + if (dialogContent.length === 0) { + dialogContent = element; + // If the dialog element already had an ID, don't clobber it. + if (existingDialogId) { + dialogContentId = existingDialogId; + } + } + + dialogContent.attr('id', dialogContentId); + element.attr('aria-describedby', dialogContentId); + + if (options.ariaLabel) { + $mdAria.expect(element, 'aria-label', options.ariaLabel); + } + else { + $mdAria.expectAsync(element, 'aria-label', function() { + var words = dialogContent.text().split(/\s+/); + if (words.length > 3) words = words.slice(0, 3).concat('...'); + return words.join(' '); + }); + } + + // Set up elements before and after the dialog content to capture focus and + // redirect back into the dialog. + topFocusTrap = document.createElement('div'); + topFocusTrap.classList.add('md-dialog-focus-trap'); + topFocusTrap.tabIndex = 0; + + bottomFocusTrap = topFocusTrap.cloneNode(false); + + // When focus is about to move out of the dialog, we want to intercept it and redirect it + // back to the dialog element. + var focusHandler = function() { + element.focus(); + }; + topFocusTrap.addEventListener('focus', focusHandler); + bottomFocusTrap.addEventListener('focus', focusHandler); + + // The top focus trap inserted immeidately before the md-dialog element (as a sibling). + // The bottom focus trap is inserted at the very end of the md-dialog element (as a child). + element[0].parentNode.insertBefore(topFocusTrap, element[0]); + element.after(bottomFocusTrap); + } + + /** + * Prevents screen reader interaction behind modal window + * on swipe interfaces + */ + function lockScreenReader(element, options) { + var isHidden = true; + + // get raw DOM node + walkDOM(element[0]); + + options.unlockScreenReader = function() { + isHidden = false; + walkDOM(element[0]); + + options.unlockScreenReader = null; + }; + + /** + * Walk DOM to apply or remove aria-hidden on sibling nodes + * and parent sibling nodes + * + */ + function walkDOM(element) { + while (element.parentNode) { + if (element === document.body) { + return; + } + var children = element.parentNode.children; + for (var i = 0; i < children.length; i++) { + // skip over child if it is an ascendant of the dialog + // or a script or style tag + if (element !== children[i] && !isNodeOneOf(children[i], ['SCRIPT', 'STYLE'])) { + children[i].setAttribute('aria-hidden', isHidden); + } + } + + walkDOM(element = element.parentNode); + } + } + } + + /** + * Ensure the dialog container fill-stretches to the viewport + */ + function stretchDialogContainerToViewport(container, options) { + var isFixed = $window.getComputedStyle($document[0].body).position == 'fixed'; + var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null; + var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0; + + var previousStyles = { + top: container.css('top'), + height: container.css('height') + }; + + container.css({ + top: (isFixed ? $mdUtil.scrollTop(options.parent) : 0) + 'px', + height: height ? height + 'px' : '100%' + }); + + return function() { + // Reverts the modified styles back to the previous values. + // This is needed for contentElements, which should have the same styles after close + // as before. + container.css(previousStyles); + }; + } + + /** + * Dialog open and pop-in animation + */ + function dialogPopIn(container, options) { + // Add the `md-dialog-container` to the DOM + options.parent.append(container); + options.reverseContainerStretch = stretchDialogContainerToViewport(container, options); + + var dialogEl = container.find('md-dialog'); + var animator = $mdUtil.dom.animator; + var buildTranslateToOrigin = animator.calculateZoomToOrigin; + var translateOptions = {transitionInClass: 'md-transition-in', transitionOutClass: 'md-transition-out'}; + var from = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.openFrom || options.origin)); + var to = animator.toTransformCss(""); // defaults to center display (or parent or $rootElement) + + dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen); + + return animator + .translate3d(dialogEl, from, to, translateOptions) + .then(function(animateReversal) { + + // Build a reversal translate function synced to this translation... + options.reverseAnimate = function() { + delete options.reverseAnimate; + + if (options.closeTo) { + // Using the opposite classes to create a close animation to the closeTo element + translateOptions = {transitionInClass: 'md-transition-out', transitionOutClass: 'md-transition-in'}; + from = to; + to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo)); + + return animator + .translate3d(dialogEl, from, to,translateOptions); + } + + return animateReversal( + to = animator.toTransformCss( + // in case the origin element has moved or is hidden, + // let's recalculate the translateCSS + buildTranslateToOrigin(dialogEl, options.origin) + ) + ); + + }; + + // Function to revert the generated animation styles on the dialog element. + // Useful when using a contentElement instead of a template. + options.clearAnimate = function() { + delete options.clearAnimate; + + // Remove the transition classes, added from $animateCSS, since those can't be removed + // by reversely running the animator. + dialogEl.removeClass([ + translateOptions.transitionOutClass, + translateOptions.transitionInClass + ].join(' ')); + + // Run the animation reversely to remove the previous added animation styles. + return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {}); + }; + + return true; + }); + } + + /** + * Dialog close and pop-out animation + */ + function dialogPopOut(container, options) { + return options.reverseAnimate().then(function() { + if (options.contentElement) { + // When we use a contentElement, we want the element to be the same as before. + // That means, that we have to clear all the animation properties, like transform. + options.clearAnimate(); + } + }); + } + + /** + * Utility function to filter out raw DOM nodes + */ + function isNodeOneOf(elem, nodeTypeArray) { + if (nodeTypeArray.indexOf(elem.nodeName) !== -1) { + return true; + } + } + + } +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.divider + * @description Divider module! + */ +MdDividerDirective.$inject = ["$mdTheming"]; +angular.module('material.components.divider', [ + 'material.core' +]) + .directive('mdDivider', MdDividerDirective); + +/** + * @ngdoc directive + * @name mdDivider + * @module material.components.divider + * @restrict E + * + * @description + * Dividers group and separate content within lists and page layouts using strong visual and spatial distinctions. This divider is a thin rule, lightweight enough to not distract the user from content. + * + * @param {boolean=} md-inset Add this attribute to activate the inset divider style. + * @usage + * + * + * + * + * + * + */ +function MdDividerDirective($mdTheming) { + return { + restrict: 'E', + link: $mdTheming + }; +} + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc module + * @name material.components.fabActions + */ + MdFabActionsDirective.$inject = ["$mdUtil"]; + angular + .module('material.components.fabActions', ['material.core']) + .directive('mdFabActions', MdFabActionsDirective); + + /** + * @ngdoc directive + * @name mdFabActions + * @module material.components.fabActions + * + * @restrict E + * + * @description + * The `` directive is used inside of a `` or + * `` directive to mark an element (or elements) as the actions and setup the + * proper event listeners. + * + * @usage + * See the `` or `` directives for example usage. + */ + function MdFabActionsDirective($mdUtil) { + return { + restrict: 'E', + + require: ['^?mdFabSpeedDial', '^?mdFabToolbar'], + + compile: function(element, attributes) { + var children = element.children(); + + var hasNgRepeat = $mdUtil.prefixer().hasAttribute(children, 'ng-repeat'); + + // Support both ng-repeat and static content + if (hasNgRepeat) { + children.addClass('md-fab-action-item'); + } else { + // Wrap every child in a new div and add a class that we can scale/fling independently + children.wrap('
'); + } + } + } + } + +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + MdFabController.$inject = ["$scope", "$element", "$animate", "$mdUtil", "$mdConstant", "$timeout"]; + angular.module('material.components.fabShared', ['material.core']) + .controller('MdFabController', MdFabController); + + function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) { + var vm = this; + + // NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops + + vm.open = function() { + $scope.$evalAsync("vm.isOpen = true"); + }; + + vm.close = function() { + // Async eval to avoid conflicts with existing digest loops + $scope.$evalAsync("vm.isOpen = false"); + + // Focus the trigger when the element closes so users can still tab to the next item + $element.find('md-fab-trigger')[0].focus(); + }; + + // Toggle the open/close state when the trigger is clicked + vm.toggle = function() { + $scope.$evalAsync("vm.isOpen = !vm.isOpen"); + }; + + setupDefaults(); + setupListeners(); + setupWatchers(); + + var initialAnimationAttempts = 0; + fireInitialAnimations(); + + function setupDefaults() { + // Set the default direction to 'down' if none is specified + vm.direction = vm.direction || 'down'; + + // Set the default to be closed + vm.isOpen = vm.isOpen || false; + + // Start the keyboard interaction at the first action + resetActionIndex(); + + // Add an animations waiting class so we know not to run + $element.addClass('md-animations-waiting'); + } + + function setupListeners() { + var eventTypes = [ + 'click', 'focusin', 'focusout' + ]; + + // Add our listeners + angular.forEach(eventTypes, function(eventType) { + $element.on(eventType, parseEvents); + }); + + // Remove our listeners when destroyed + $scope.$on('$destroy', function() { + angular.forEach(eventTypes, function(eventType) { + $element.off(eventType, parseEvents); + }); + + // remove any attached keyboard handlers in case element is removed while + // speed dial is open + disableKeyboard(); + }); + } + + var closeTimeout; + function parseEvents(event) { + // If the event is a click, just handle it + if (event.type == 'click') { + handleItemClick(event); + } + + // If we focusout, set a timeout to close the element + if (event.type == 'focusout' && !closeTimeout) { + closeTimeout = $timeout(function() { + vm.close(); + }, 100, false); + } + + // If we see a focusin and there is a timeout about to run, cancel it so we stay open + if (event.type == 'focusin' && closeTimeout) { + $timeout.cancel(closeTimeout); + closeTimeout = null; + } + } + + function resetActionIndex() { + vm.currentActionIndex = -1; + } + + function setupWatchers() { + // Watch for changes to the direction and update classes/attributes + $scope.$watch('vm.direction', function(newDir, oldDir) { + // Add the appropriate classes so we can target the direction in the CSS + $animate.removeClass($element, 'md-' + oldDir); + $animate.addClass($element, 'md-' + newDir); + + // Reset the action index since it may have changed + resetActionIndex(); + }); + + var trigger, actions; + + // Watch for changes to md-open + $scope.$watch('vm.isOpen', function(isOpen) { + // Reset the action index since it may have changed + resetActionIndex(); + + // We can't get the trigger/actions outside of the watch because the component hasn't been + // linked yet, so we wait until the first watch fires to cache them. + if (!trigger || !actions) { + trigger = getTriggerElement(); + actions = getActionsElement(); + } + + if (isOpen) { + enableKeyboard(); + } else { + disableKeyboard(); + } + + var toAdd = isOpen ? 'md-is-open' : ''; + var toRemove = isOpen ? '' : 'md-is-open'; + + // Set the proper ARIA attributes + trigger.attr('aria-haspopup', true); + trigger.attr('aria-expanded', isOpen); + actions.attr('aria-hidden', !isOpen); + + // Animate the CSS classes + $animate.setClass($element, toAdd, toRemove); + }); + } + + function fireInitialAnimations() { + // If the element is actually visible on the screen + if ($element[0].scrollHeight > 0) { + // Fire our animation + $animate.addClass($element, '_md-animations-ready').then(function() { + // Remove the waiting class + $element.removeClass('md-animations-waiting'); + }); + } + + // Otherwise, try for up to 1 second before giving up + else if (initialAnimationAttempts < 10) { + $timeout(fireInitialAnimations, 100); + + // Increment our counter + initialAnimationAttempts = initialAnimationAttempts + 1; + } + } + + function enableKeyboard() { + $element.on('keydown', keyPressed); + + // On the next tick, setup a check for outside clicks; we do this on the next tick to avoid + // clicks/touches that result in the isOpen attribute changing (e.g. a bound radio button) + $mdUtil.nextTick(function() { + angular.element(document).on('click touchend', checkForOutsideClick); + }); + + // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but + // this breaks accessibility, especially on mobile, since you have no arrow keys to press + //resetActionTabIndexes(); + } + + function disableKeyboard() { + $element.off('keydown', keyPressed); + angular.element(document).off('click touchend', checkForOutsideClick); + } + + function checkForOutsideClick(event) { + if (event.target) { + var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger'); + var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions'); + + if (!closestTrigger && !closestActions) { + vm.close(); + } + } + } + + function keyPressed(event) { + switch (event.which) { + case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false; + case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false; + case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false; + case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false; + case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false; + } + } + + function doActionPrev(event) { + focusAction(event, -1); + } + + function doActionNext(event) { + focusAction(event, 1); + } + + function focusAction(event, direction) { + var actions = resetActionTabIndexes(); + + // Increment/decrement the counter with restrictions + vm.currentActionIndex = vm.currentActionIndex + direction; + vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex); + vm.currentActionIndex = Math.max(0, vm.currentActionIndex); + + // Focus the element + var focusElement = angular.element(actions[vm.currentActionIndex]).children()[0]; + angular.element(focusElement).attr('tabindex', 0); + focusElement.focus(); + + // Make sure the event doesn't bubble and cause something else + event.preventDefault(); + event.stopImmediatePropagation(); + } + + function resetActionTabIndexes() { + // Grab all of the actions + var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item'); + + // Disable all other actions for tabbing + angular.forEach(actions, function(action) { + angular.element(angular.element(action).children()[0]).attr('tabindex', -1); + }); + + return actions; + } + + function doKeyLeft(event) { + if (vm.direction === 'left') { + doActionNext(event); + } else { + doActionPrev(event); + } + } + + function doKeyUp(event) { + if (vm.direction === 'down') { + doActionPrev(event); + } else { + doActionNext(event); + } + } + + function doKeyRight(event) { + if (vm.direction === 'left') { + doActionPrev(event); + } else { + doActionNext(event); + } + } + + function doKeyDown(event) { + if (vm.direction === 'up') { + doActionPrev(event); + } else { + doActionNext(event); + } + } + + function isTrigger(element) { + return $mdUtil.getClosest(element, 'md-fab-trigger'); + } + + function isAction(element) { + return $mdUtil.getClosest(element, 'md-fab-actions'); + } + + function handleItemClick(event) { + if (isTrigger(event.target)) { + vm.toggle(); + } + + if (isAction(event.target)) { + vm.close(); + } + } + + function getTriggerElement() { + return $element.find('md-fab-trigger'); + } + + function getActionsElement() { + return $element.find('md-fab-actions'); + } + } +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * The duration of the CSS animation in milliseconds. + * + * @type {number} + */ + MdFabSpeedDialFlingAnimation.$inject = ["$timeout"]; + MdFabSpeedDialScaleAnimation.$inject = ["$timeout"]; + var cssAnimationDuration = 300; + + /** + * @ngdoc module + * @name material.components.fabSpeedDial + */ + angular + // Declare our module + .module('material.components.fabSpeedDial', [ + 'material.core', + 'material.components.fabShared', + 'material.components.fabActions' + ]) + + // Register our directive + .directive('mdFabSpeedDial', MdFabSpeedDialDirective) + + // Register our custom animations + .animation('.md-fling', MdFabSpeedDialFlingAnimation) + .animation('.md-scale', MdFabSpeedDialScaleAnimation) + + // Register a service for each animation so that we can easily inject them into unit tests + .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation) + .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation); + + /** + * @ngdoc directive + * @name mdFabSpeedDial + * @module material.components.fabSpeedDial + * + * @restrict E + * + * @description + * The `` directive is used to present a series of popup elements (usually + * ``s) for quick access to common actions. + * + * There are currently two animations available by applying one of the following classes to + * the component: + * + * - `md-fling` - The speed dial items appear from underneath the trigger and move into their + * appropriate positions. + * - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%. + * + * You may also easily position the trigger by applying one one of the following classes to the + * `` element: + * - `md-fab-top-left` + * - `md-fab-top-right` + * - `md-fab-bottom-left` + * - `md-fab-bottom-right` + * + * These CSS classes use `position: absolute`, so you need to ensure that the container element + * also uses `position: absolute` or `position: relative` in order for them to work. + * + * Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to + * open or close the speed dial. However, if you wish to allow users to hover over the empty + * space where the actions will appear, you must also add the `md-hover-full` class to the speed + * dial element. Without this, the hover effect will only occur on top of the trigger. + * + * See the demos for more information. + * + * ## Troubleshooting + * + * If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on + * the parent container to ensure that it is only visible once ready. We have plans to remove this + * necessity in the future. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param {string} md-direction From which direction you would like the speed dial to appear + * relative to the trigger element. + * @param {expression=} md-open Programmatically control whether or not the speed-dial is visible. + */ + function MdFabSpeedDialDirective() { + return { + restrict: 'E', + + scope: { + direction: '@?mdDirection', + isOpen: '=?mdOpen' + }, + + bindToController: true, + controller: 'MdFabController', + controllerAs: 'vm', + + link: FabSpeedDialLink + }; + + function FabSpeedDialLink(scope, element) { + // Prepend an element to hold our CSS variables so we can use them in the animations below + element.prepend('
'); + } + } + + function MdFabSpeedDialFlingAnimation($timeout) { + function delayDone(done) { $timeout(done, cssAnimationDuration, false); } + + function runAnimation(element) { + // Don't run if we are still waiting and we are not ready + if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) { + return; + } + + var el = element[0]; + var ctrl = element.controller('mdFabSpeedDial'); + var items = el.querySelectorAll('.md-fab-action-item'); + + // Grab our trigger element + var triggerElement = el.querySelector('md-fab-trigger'); + + // Grab our element which stores CSS variables + var variablesElement = el.querySelector('._md-css-variables'); + + // Setup JS variables based on our CSS variables + var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex); + + // Always reset the items to their natural position/state + angular.forEach(items, function(item, index) { + var styles = item.style; + + styles.transform = styles.webkitTransform = ''; + styles.transitionDelay = ''; + styles.opacity = 1; + + // Make the items closest to the trigger have the highest z-index + styles.zIndex = (items.length - index) + startZIndex; + }); + + // Set the trigger to be above all of the actions so they disappear behind it. + triggerElement.style.zIndex = startZIndex + items.length + 1; + + // If the control is closed, hide the items behind the trigger + if (!ctrl.isOpen) { + angular.forEach(items, function(item, index) { + var newPosition, axis; + var styles = item.style; + + // Make sure to account for differences in the dimensions of the trigger verses the items + // so that we can properly center everything; this helps hide the item's shadows behind + // the trigger. + var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2; + var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2; + + switch (ctrl.direction) { + case 'up': + newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset); + axis = 'Y'; + break; + case 'down': + newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset); + axis = 'Y'; + break; + case 'left': + newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset); + axis = 'X'; + break; + case 'right': + newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset); + axis = 'X'; + break; + } + + var newTranslate = 'translate' + axis + '(' + newPosition + 'px)'; + + styles.transform = styles.webkitTransform = newTranslate; + }); + } + } + + return { + addClass: function(element, className, done) { + if (element.hasClass('md-fling')) { + runAnimation(element); + delayDone(done); + } else { + done(); + } + }, + removeClass: function(element, className, done) { + runAnimation(element); + delayDone(done); + } + } + } + + function MdFabSpeedDialScaleAnimation($timeout) { + function delayDone(done) { $timeout(done, cssAnimationDuration, false); } + + var delay = 65; + + function runAnimation(element) { + var el = element[0]; + var ctrl = element.controller('mdFabSpeedDial'); + var items = el.querySelectorAll('.md-fab-action-item'); + + // Grab our element which stores CSS variables + var variablesElement = el.querySelector('._md-css-variables'); + + // Setup JS variables based on our CSS variables + var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex); + + // Always reset the items to their natural position/state + angular.forEach(items, function(item, index) { + var styles = item.style, + offsetDelay = index * delay; + + styles.opacity = ctrl.isOpen ? 1 : 0; + styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)'; + styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms'; + + // Make the items closest to the trigger have the highest z-index + styles.zIndex = (items.length - index) + startZIndex; + }); + } + + return { + addClass: function(element, className, done) { + runAnimation(element); + delayDone(done); + }, + + removeClass: function(element, className, done) { + runAnimation(element); + delayDone(done); + } + } + } +})(); + +})(); +(function(){ +"use strict"; + +(function() { + 'use strict'; + + /** + * @ngdoc module + * @name material.components.fabToolbar + */ + angular + // Declare our module + .module('material.components.fabToolbar', [ + 'material.core', + 'material.components.fabShared', + 'material.components.fabActions' + ]) + + // Register our directive + .directive('mdFabToolbar', MdFabToolbarDirective) + + // Register our custom animations + .animation('.md-fab-toolbar', MdFabToolbarAnimation) + + // Register a service for the animation so that we can easily inject it into unit tests + .service('mdFabToolbarAnimation', MdFabToolbarAnimation); + + /** + * @ngdoc directive + * @name mdFabToolbar + * @module material.components.fabToolbar + * + * @restrict E + * + * @description + * + * The `` directive is used present a toolbar of elements (usually ``s) + * for quick access to common actions when a floating action button is activated (via click or + * keyboard navigation). + * + * You may also easily position the trigger by applying one one of the following classes to the + * `` element: + * - `md-fab-top-left` + * - `md-fab-top-right` + * - `md-fab-bottom-left` + * - `md-fab-bottom-right` + * + * These CSS classes use `position: absolute`, so you need to ensure that the container element + * also uses `position: absolute` or `position: relative` in order for them to work. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param {string} md-direction From which direction you would like the toolbar items to appear + * relative to the trigger element. Supports `left` and `right` directions. + * @param {expression=} md-open Programmatically control whether or not the toolbar is visible. + */ + function MdFabToolbarDirective() { + return { + restrict: 'E', + transclude: true, + template: '
' + + '
' + + '
', + + scope: { + direction: '@?mdDirection', + isOpen: '=?mdOpen' + }, + + bindToController: true, + controller: 'MdFabController', + controllerAs: 'vm', + + link: link + }; + + function link(scope, element, attributes) { + // Add the base class for animations + element.addClass('md-fab-toolbar'); + + // Prepend the background element to the trigger's button + element.find('md-fab-trigger').find('button') + .prepend('
'); + } + } + + function MdFabToolbarAnimation() { + + function runAnimation(element, className, done) { + // If no className was specified, don't do anything + if (!className) { + return; + } + + var el = element[0]; + var ctrl = element.controller('mdFabToolbar'); + + // Grab the relevant child elements + var backgroundElement = el.querySelector('.md-fab-toolbar-background'); + var triggerElement = el.querySelector('md-fab-trigger button'); + var toolbarElement = el.querySelector('md-toolbar'); + var iconElement = el.querySelector('md-fab-trigger button md-icon'); + var actions = element.find('md-fab-actions').children(); + + // If we have both elements, use them to position the new background + if (triggerElement && backgroundElement) { + // Get our variables + var color = window.getComputedStyle(triggerElement).getPropertyValue('background-color'); + var width = el.offsetWidth; + var height = el.offsetHeight; + + // Make it twice as big as it should be since we scale from the center + var scale = 2 * (width / triggerElement.offsetWidth); + + // Set some basic styles no matter what animation we're doing + backgroundElement.style.backgroundColor = color; + backgroundElement.style.borderRadius = width + 'px'; + + // If we're open + if (ctrl.isOpen) { + // Turn on toolbar pointer events when closed + toolbarElement.style.pointerEvents = 'inherit'; + + backgroundElement.style.width = triggerElement.offsetWidth + 'px'; + backgroundElement.style.height = triggerElement.offsetHeight + 'px'; + backgroundElement.style.transform = 'scale(' + scale + ')'; + + // Set the next close animation to have the proper delays + backgroundElement.style.transitionDelay = '0ms'; + iconElement && (iconElement.style.transitionDelay = '.3s'); + + // Apply a transition delay to actions + angular.forEach(actions, function(action, index) { + action.style.transitionDelay = (actions.length - index) * 25 + 'ms'; + }); + } else { + // Turn off toolbar pointer events when closed + toolbarElement.style.pointerEvents = 'none'; + + // Scale it back down to the trigger's size + backgroundElement.style.transform = 'scale(1)'; + + // Reset the position + backgroundElement.style.top = '0'; + + if (element.hasClass('md-right')) { + backgroundElement.style.left = '0'; + backgroundElement.style.right = null; + } + + if (element.hasClass('md-left')) { + backgroundElement.style.right = '0'; + backgroundElement.style.left = null; + } + + // Set the next open animation to have the proper delays + backgroundElement.style.transitionDelay = '200ms'; + iconElement && (iconElement.style.transitionDelay = '0ms'); + + // Apply a transition delay to actions + angular.forEach(actions, function(action, index) { + action.style.transitionDelay = 200 + (index * 25) + 'ms'; + }); + } + } + } + + return { + addClass: function(element, className, done) { + runAnimation(element, className, done); + done(); + }, + + removeClass: function(element, className, done) { + runAnimation(element, className, done); + done(); + } + } + } +})(); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.gridList + */ +GridListController.$inject = ["$mdUtil"]; +GridLayoutFactory.$inject = ["$mdUtil"]; +GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"]; +GridTileDirective.$inject = ["$mdMedia"]; +angular.module('material.components.gridList', ['material.core']) + .directive('mdGridList', GridListDirective) + .directive('mdGridTile', GridTileDirective) + .directive('mdGridTileFooter', GridTileCaptionDirective) + .directive('mdGridTileHeader', GridTileCaptionDirective) + .factory('$mdGridLayout', GridLayoutFactory); + +/** + * @ngdoc directive + * @name mdGridList + * @module material.components.gridList + * @restrict E + * @description + * Grid lists are an alternative to standard list views. Grid lists are distinct + * from grids used for layouts and other visual presentations. + * + * A grid list is best suited to presenting a homogenous data type, typically + * images, and is optimized for visual comprehension and differentiating between + * like data types. + * + * A grid list is a continuous element consisting of tessellated, regular + * subdivisions called cells that contain tiles (`md-grid-tile`). + * + * Concept of grid explained visually + * Grid concepts legend + * + * Cells are arrayed vertically and horizontally within the grid. + * + * Tiles hold content and can span one or more cells vertically or horizontally. + * + * ### Responsive Attributes + * + * The `md-grid-list` directive supports "responsive" attributes, which allow + * different `md-cols`, `md-gutter` and `md-row-height` values depending on the + * currently matching media query. + * + * In order to set a responsive attribute, first define the fallback value with + * the standard attribute name, then add additional attributes with the + * following convention: `{base-attribute-name}-{media-query-name}="{value}"` + * (ie. `md-cols-lg="8"`) + * + * @param {number} md-cols Number of columns in the grid. + * @param {string} md-row-height One of + *
    + *
  • CSS length - Fixed height rows (eg. `8px` or `1rem`)
  • + *
  • `{width}:{height}` - Ratio of width to height (eg. + * `md-row-height="16:9"`)
  • + *
  • `"fit"` - Height will be determined by subdividing the available + * height by the number of rows
  • + *
+ * @param {string=} md-gutter The amount of space between tiles in CSS units + * (default 1px) + * @param {expression=} md-on-layout Expression to evaluate after layout. Event + * object is available as `$event`, and contains performance information. + * + * @usage + * Basic: + * + * + * + * + * + * + * Fixed-height rows: + * + * + * + * + * + * + * Fit rows: + * + * + * + * + * + * + * Using responsive attributes: + * + * + * + * + * + */ +function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) { + return { + restrict: 'E', + controller: GridListController, + scope: { + mdOnLayout: '&' + }, + link: postLink + }; + + function postLink(scope, element, attrs, ctrl) { + element.addClass('_md'); // private md component indicator for styling + + // Apply semantics + element.attr('role', 'list'); + + // Provide the controller with a way to trigger layouts. + ctrl.layoutDelegate = layoutDelegate; + + var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout), + unwatchAttrs = watchMedia(); + scope.$on('$destroy', unwatchMedia); + + /** + * Watches for changes in media, invalidating layout as necessary. + */ + function watchMedia() { + for (var mediaName in $mdConstant.MEDIA) { + $mdMedia(mediaName); // initialize + $mdMedia.getQuery($mdConstant.MEDIA[mediaName]) + .addListener(invalidateLayout); + } + return $mdMedia.watchResponsiveAttributes( + ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch); + } + + function unwatchMedia() { + ctrl.layoutDelegate = angular.noop; + + unwatchAttrs(); + for (var mediaName in $mdConstant.MEDIA) { + $mdMedia.getQuery($mdConstant.MEDIA[mediaName]) + .removeListener(invalidateLayout); + } + } + + /** + * Performs grid layout if the provided mediaName matches the currently + * active media type. + */ + function layoutIfMediaMatch(mediaName) { + if (mediaName == null) { + // TODO(shyndman): It would be nice to only layout if we have + // instances of attributes using this media type + ctrl.invalidateLayout(); + } else if ($mdMedia(mediaName)) { + ctrl.invalidateLayout(); + } + } + + var lastLayoutProps; + + /** + * Invokes the layout engine, and uses its results to lay out our + * tile elements. + * + * @param {boolean} tilesInvalidated Whether tiles have been + * added/removed/moved since the last layout. This is to avoid situations + * where tiles are replaced with properties identical to their removed + * counterparts. + */ + function layoutDelegate(tilesInvalidated) { + var tiles = getTileElements(); + var props = { + tileSpans: getTileSpans(tiles), + colCount: getColumnCount(), + rowMode: getRowMode(), + rowHeight: getRowHeight(), + gutter: getGutter() + }; + + if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) { + return; + } + + var performance = + $mdGridLayout(props.colCount, props.tileSpans, tiles) + .map(function(tilePositions, rowCount) { + return { + grid: { + element: element, + style: getGridStyle(props.colCount, rowCount, + props.gutter, props.rowMode, props.rowHeight) + }, + tiles: tilePositions.map(function(ps, i) { + return { + element: angular.element(tiles[i]), + style: getTileStyle(ps.position, ps.spans, + props.colCount, rowCount, + props.gutter, props.rowMode, props.rowHeight) + } + }) + } + }) + .reflow() + .performance(); + + // Report layout + scope.mdOnLayout({ + $event: { + performance: performance + } + }); + + lastLayoutProps = props; + } + + // Use $interpolate to do some simple string interpolation as a convenience. + + var startSymbol = $interpolate.startSymbol(); + var endSymbol = $interpolate.endSymbol(); + + // Returns an expression wrapped in the interpolator's start and end symbols. + function expr(exprStr) { + return startSymbol + exprStr + endSymbol; + } + + // The amount of space a single 1x1 tile would take up (either width or height), used as + // a basis for other calculations. This consists of taking the base size percent (as would be + // if evenly dividing the size between cells), and then subtracting the size of one gutter. + // However, since there are no gutters on the edges, each tile only uses a fration + // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per + // tile, and then breaking up the extra gutter on the edge evenly among the cells). + var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')'); + + // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value. + // The position comes the size of a 1x1 tile plus gutter for each previous tile in the + // row/column (offset). + var POSITION = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')'); + + // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account. + // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back + // in the space that the gutter would normally have used (which was already accounted for in + // the base unit calculation). + var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')'); + + /** + * Gets the styles applied to a tile element described by the given parameters. + * @param {{row: number, col: number}} position The row and column indices of the tile. + * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile. + * @param {number} colCount The number of columns. + * @param {number} rowCount The number of rows. + * @param {string} gutter The amount of space between tiles. This will be something like + * '5px' or '2em'. + * @param {string} rowMode The row height mode. Can be one of: + * 'fixed': all rows have a fixed size, given by rowHeight, + * 'ratio': row height defined as a ratio to width, or + * 'fit': fit to the grid-list element height, divinding evenly among rows. + * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and + * for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75). + * @returns {Object} Map of CSS properties to be applied to the style element. Will define + * values for top, left, width, height, marginTop, and paddingTop. + */ + function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) { + // TODO(shyndman): There are style caching opportunities here. + + // Percent of the available horizontal space that one column takes up. + var hShare = (1 / colCount) * 100; + + // Fraction of the gutter size that each column takes up. + var hGutterShare = (colCount - 1) / colCount; + + // Base horizontal size of a column. + var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter}); + + // The width and horizontal position of each tile is always calculated the same way, but the + // height and vertical position depends on the rowMode. + var style = { + left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }), + width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }), + // resets + paddingTop: '', + marginTop: '', + top: '', + height: '' + }; + + switch (rowMode) { + case 'fixed': + // In fixed mode, simply use the given rowHeight. + style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter }); + style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter }); + break; + + case 'ratio': + // Percent of the available vertical space that one row takes up. Here, rowHeight holds + // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333. + var vShare = hShare / rowHeight; + + // Base veritcal size of a row. + var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter }); + + // padidngTop and marginTop are used to maintain the given aspect ratio, as + // a percentage-based value for these properties is applied to the *width* of the + // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties + style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter}); + style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter }); + break; + + case 'fit': + // Fraction of the gutter size that each column takes up. + var vGutterShare = (rowCount - 1) / rowCount; + + // Percent of the available vertical space that one row takes up. + var vShare = (1 / rowCount) * 100; + + // Base vertical size of a row. + var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter}); + + style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter}); + style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter}); + break; + } + + return style; + } + + function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) { + var style = {}; + + switch(rowMode) { + case 'fixed': + style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter }); + style.paddingBottom = ''; + break; + + case 'ratio': + // rowHeight is width / height + var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount, + hShare = (1 / colCount) * 100, + vShare = hShare * (1 / rowHeight), + vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter }); + + style.height = ''; + style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter}); + break; + + case 'fit': + // noop, as the height is user set + break; + } + + return style; + } + + function getTileElements() { + return [].filter.call(element.children(), function(ele) { + return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed; + }); + } + + /** + * Gets an array of objects containing the rowspan and colspan for each tile. + * @returns {Array<{row: number, col: number}>} + */ + function getTileSpans(tileElements) { + return [].map.call(tileElements, function(ele) { + var ctrl = angular.element(ele).controller('mdGridTile'); + return { + row: parseInt( + $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1, + col: parseInt( + $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1 + }; + }); + } + + function getColumnCount() { + var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10); + if (isNaN(colCount)) { + throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value'; + } + return colCount; + } + + function getGutter() { + return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1); + } + + function getRowHeight() { + var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height'); + if (!rowHeight) { + throw 'md-grid-list: md-row-height attribute was not found'; + } + + switch (getRowMode()) { + case 'fixed': + return applyDefaultUnit(rowHeight); + case 'ratio': + var whRatio = rowHeight.split(':'); + return parseFloat(whRatio[0]) / parseFloat(whRatio[1]); + case 'fit': + return 0; // N/A + } + } + + function getRowMode() { + var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height'); + if (!rowHeight) { + throw 'md-grid-list: md-row-height attribute was not found'; + } + + if (rowHeight == 'fit') { + return 'fit'; + } else if (rowHeight.indexOf(':') !== -1) { + return 'ratio'; + } else { + return 'fixed'; + } + } + + function applyDefaultUnit(val) { + return /\D$/.test(val) ? val : val + 'px'; + } + } +} + +/* @ngInject */ +function GridListController($mdUtil) { + this.layoutInvalidated = false; + this.tilesInvalidated = false; + this.$timeout_ = $mdUtil.nextTick; + this.layoutDelegate = angular.noop; +} + +GridListController.prototype = { + invalidateTiles: function() { + this.tilesInvalidated = true; + this.invalidateLayout(); + }, + + invalidateLayout: function() { + if (this.layoutInvalidated) { + return; + } + this.layoutInvalidated = true; + this.$timeout_(angular.bind(this, this.layout)); + }, + + layout: function() { + try { + this.layoutDelegate(this.tilesInvalidated); + } finally { + this.layoutInvalidated = false; + this.tilesInvalidated = false; + } + } +}; + + +/* @ngInject */ +function GridLayoutFactory($mdUtil) { + var defaultAnimator = GridTileAnimator; + + /** + * Set the reflow animator callback + */ + GridLayout.animateWith = function(customAnimator) { + defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator; + }; + + return GridLayout; + + /** + * Publish layout function + */ + function GridLayout(colCount, tileSpans) { + var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime; + + layoutTime = $mdUtil.time(function() { + layoutInfo = calculateGridFor(colCount, tileSpans); + }); + + return self = { + + /** + * An array of objects describing each tile's position in the grid. + */ + layoutInfo: function() { + return layoutInfo; + }, + + /** + * Maps grid positioning to an element and a set of styles using the + * provided updateFn. + */ + map: function(updateFn) { + mapTime = $mdUtil.time(function() { + var info = self.layoutInfo(); + gridStyles = updateFn(info.positioning, info.rowCount); + }); + return self; + }, + + /** + * Default animator simply sets the element.css( ). An alternate + * animator can be provided as an argument. The function has the following + * signature: + * + * function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>) + */ + reflow: function(animatorFn) { + reflowTime = $mdUtil.time(function() { + var animator = animatorFn || defaultAnimator; + animator(gridStyles.grid, gridStyles.tiles); + }); + return self; + }, + + /** + * Timing for the most recent layout run. + */ + performance: function() { + return { + tileCount: tileSpans.length, + layoutTime: layoutTime, + mapTime: mapTime, + reflowTime: reflowTime, + totalTime: layoutTime + mapTime + reflowTime + }; + } + }; + } + + /** + * Default Gridlist animator simple sets the css for each element; + * NOTE: any transitions effects must be manually set in the CSS. + * e.g. + * + * md-grid-tile { + * transition: all 700ms ease-out 50ms; + * } + * + */ + function GridTileAnimator(grid, tiles) { + grid.element.css(grid.style); + tiles.forEach(function(t) { + t.element.css(t.style); + }) + } + + /** + * Calculates the positions of tiles. + * + * The algorithm works as follows: + * An Array with length colCount (spaceTracker) keeps track of + * available tiling positions, where elements of value 0 represents an + * empty position. Space for a tile is reserved by finding a sequence of + * 0s with length <= than the tile's colspan. When such a space has been + * found, the occupied tile positions are incremented by the tile's + * rowspan value, as these positions have become unavailable for that + * many rows. + * + * If the end of a row has been reached without finding space for the + * tile, spaceTracker's elements are each decremented by 1 to a minimum + * of 0. Rows are searched in this fashion until space is found. + */ + function calculateGridFor(colCount, tileSpans) { + var curCol = 0, + curRow = 0, + spaceTracker = newSpaceTracker(); + + return { + positioning: tileSpans.map(function(spans, i) { + return { + spans: spans, + position: reserveSpace(spans, i) + }; + }), + rowCount: curRow + Math.max.apply(Math, spaceTracker) + }; + + function reserveSpace(spans, i) { + if (spans.col > colCount) { + throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' + + '(' + spans.col + ') that exceeds the column count ' + + '(' + colCount + ')'; + } + + var start = 0, + end = 0; + + // TODO(shyndman): This loop isn't strictly necessary if you can + // determine the minimum number of rows before a space opens up. To do + // this, recognize that you've iterated across an entire row looking for + // space, and if so fast-forward by the minimum rowSpan count. Repeat + // until the required space opens up. + while (end - start < spans.col) { + if (curCol >= colCount) { + nextRow(); + continue; + } + + start = spaceTracker.indexOf(0, curCol); + if (start === -1 || (end = findEnd(start + 1)) === -1) { + start = end = 0; + nextRow(); + continue; + } + + curCol = end + 1; + } + + adjustRow(start, spans.col, spans.row); + curCol = start + spans.col; + + return { + col: start, + row: curRow + }; + } + + function nextRow() { + curCol = 0; + curRow++; + adjustRow(0, colCount, -1); // Decrement row spans by one + } + + function adjustRow(from, cols, by) { + for (var i = from; i < from + cols; i++) { + spaceTracker[i] = Math.max(spaceTracker[i] + by, 0); + } + } + + function findEnd(start) { + var i; + for (i = start; i < spaceTracker.length; i++) { + if (spaceTracker[i] !== 0) { + return i; + } + } + + if (i === spaceTracker.length) { + return i; + } + } + + function newSpaceTracker() { + var tracker = []; + for (var i = 0; i < colCount; i++) { + tracker.push(0); + } + return tracker; + } + } +} + +/** + * @ngdoc directive + * @name mdGridTile + * @module material.components.gridList + * @restrict E + * @description + * Tiles contain the content of an `md-grid-list`. They span one or more grid + * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to + * display secondary content. + * + * ### Responsive Attributes + * + * The `md-grid-tile` directive supports "responsive" attributes, which allow + * different `md-rowspan` and `md-colspan` values depending on the currently + * matching media query. + * + * In order to set a responsive attribute, first define the fallback value with + * the standard attribute name, then add additional attributes with the + * following convention: `{base-attribute-name}-{media-query-name}="{value}"` + * (ie. `md-colspan-sm="4"`) + * + * @param {number=} md-colspan The number of columns to span (default 1). Cannot + * exceed the number of columns in the grid. Supports interpolation. + * @param {number=} md-rowspan The number of rows to span (default 1). Supports + * interpolation. + * + * @usage + * With header: + * + * + * + *

This is a header

+ *
+ *
+ *
+ * + * With footer: + * + * + * + *

This is a footer

+ *
+ *
+ *
+ * + * Spanning multiple rows/columns: + * + * + * + * + * + * Responsive attributes: + * + * + * + * + */ +function GridTileDirective($mdMedia) { + return { + restrict: 'E', + require: '^mdGridList', + template: '
', + transclude: true, + scope: {}, + // Simple controller that exposes attributes to the grid directive + controller: ["$attrs", function($attrs) { + this.$attrs = $attrs; + }], + link: postLink + }; + + function postLink(scope, element, attrs, gridCtrl) { + // Apply semantics + element.attr('role', 'listitem'); + + // If our colspan or rowspan changes, trigger a layout + var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'], + attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout)); + + // Tile registration/deregistration + gridCtrl.invalidateTiles(); + scope.$on('$destroy', function() { + // Mark the tile as destroyed so it is no longer considered in layout, + // even if the DOM element sticks around (like during a leave animation) + element[0].$$mdDestroyed = true; + unwatchAttrs(); + gridCtrl.invalidateLayout(); + }); + + if (angular.isDefined(scope.$parent.$index)) { + scope.$watch(function() { return scope.$parent.$index; }, + function indexChanged(newIdx, oldIdx) { + if (newIdx === oldIdx) { + return; + } + gridCtrl.invalidateTiles(); + }); + } + } +} + + +function GridTileCaptionDirective() { + return { + template: '
', + transclude: true + }; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.icon + * @description + * Icon + */ +angular.module('material.components.icon', ['material.core']); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.input + */ +mdInputContainerDirective.$inject = ["$mdTheming", "$parse"]; +inputTextareaDirective.$inject = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"]; +mdMaxlengthDirective.$inject = ["$animate", "$mdUtil"]; +placeholderDirective.$inject = ["$compile"]; +ngMessageDirective.$inject = ["$mdUtil"]; +mdSelectOnFocusDirective.$inject = ["$timeout"]; +mdInputInvalidMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"]; +ngMessagesAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"]; +ngMessageAnimation.$inject = ["$$AnimateRunner", "$animateCss", "$mdUtil"]; +angular.module('material.components.input', [ + 'material.core' + ]) + .directive('mdInputContainer', mdInputContainerDirective) + .directive('label', labelDirective) + .directive('input', inputTextareaDirective) + .directive('textarea', inputTextareaDirective) + .directive('mdMaxlength', mdMaxlengthDirective) + .directive('placeholder', placeholderDirective) + .directive('ngMessages', ngMessagesDirective) + .directive('ngMessage', ngMessageDirective) + .directive('ngMessageExp', ngMessageDirective) + .directive('mdSelectOnFocus', mdSelectOnFocusDirective) + + .animation('.md-input-invalid', mdInputInvalidMessagesAnimation) + .animation('.md-input-messages-animation', ngMessagesAnimation) + .animation('.md-input-message-animation', ngMessageAnimation) + + // Register a service for each animation so that we can easily inject them into unit tests + .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation) + .service('mdInputMessagesAnimation', ngMessagesAnimation) + .service('mdInputMessageAnimation', ngMessageAnimation); + +/** + * @ngdoc directive + * @name mdInputContainer + * @module material.components.input + * + * @restrict E + * + * @description + * `` is the parent of any input or textarea element. + * + * Input and textarea elements will not behave properly unless the md-input-container + * parent is provided. + * + * A single `` should contain only one `` element, otherwise it will throw an error. + * + * Exception: Hidden inputs (``) are ignored and will not throw an error, so + * you may combine these with other inputs. + * + * Note: When using `ngMessages` with your input element, make sure the message and container elements + * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and + * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element. + * + * @param md-is-error {expression=} When the given expression evaluates to true, the input container + * will go into error state. Defaults to erroring if the input has been touched and is invalid. + * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating + * labels. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *

When disabling floating labels

+ * + * + * + * + * + * + * + */ +function mdInputContainerDirective($mdTheming, $parse) { + + ContainerCtrl.$inject = ["$scope", "$element", "$attrs", "$animate"]; + var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT']; + + var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) { + return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]); + }, []).join(","); + + var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) { + return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']); + }, []).join(","); + + return { + restrict: 'E', + compile: compile, + controller: ContainerCtrl + }; + + function compile(tElement) { + // Check for both a left & right icon + var leftIcon = tElement[0].querySelector(LEFT_SELECTORS); + var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS); + + if (leftIcon) { tElement.addClass('md-icon-left'); } + if (rightIcon) { tElement.addClass('md-icon-right'); } + + return function postLink(scope, element) { + $mdTheming(element); + }; + } + + function ContainerCtrl($scope, $element, $attrs, $animate) { + var self = this; + + self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError); + + self.delegateClick = function() { + self.input.focus(); + }; + self.element = $element; + self.setFocused = function(isFocused) { + $element.toggleClass('md-input-focused', !!isFocused); + }; + self.setHasValue = function(hasValue) { + $element.toggleClass('md-input-has-value', !!hasValue); + }; + self.setHasPlaceholder = function(hasPlaceholder) { + $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder); + }; + self.setInvalid = function(isInvalid) { + if (isInvalid) { + $animate.addClass($element, 'md-input-invalid'); + } else { + $animate.removeClass($element, 'md-input-invalid'); + } + }; + $scope.$watch(function() { + return self.label && self.input; + }, function(hasLabelAndInput) { + if (hasLabelAndInput && !self.label.attr('for')) { + self.label.attr('for', self.input.attr('id')); + } + }); + } +} + +function labelDirective() { + return { + restrict: 'E', + require: '^?mdInputContainer', + link: function(scope, element, attr, containerCtrl) { + if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return; + + containerCtrl.label = element; + scope.$on('$destroy', function() { + containerCtrl.label = null; + }); + } + }; +} + +/** + * @ngdoc directive + * @name mdInput + * @restrict E + * @module material.components.input + * + * @description + * You can use any `` or ` + *
+ *
This is required!
+ *
That's too long!
+ *
+ *
+ * + * + * + * + * + * + * + * + * + *

Notes

+ * + * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages). + * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input). + * + * The `md-input` and `md-input-container` directives use very specific positioning to achieve the + * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the + * `` tags. Instead, use relative or absolute positioning. + * + * + *

Textarea directive

+ * The `textarea` element within a `md-input-container` has the following specific behavior: + * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow` + * attribute. + * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will + * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text + * high initially. If no rows are specified, the directive defaults to 1. + * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations + * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In + * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope. + * - If you wan't a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute. + * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically. + * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a + * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute. + */ + +function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) { + return { + restrict: 'E', + require: ['^?mdInputContainer', '?ngModel', '?^form'], + link: postLink + }; + + function postLink(scope, element, attr, ctrls) { + + var containerCtrl = ctrls[0]; + var hasNgModel = !!ctrls[1]; + var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel(); + var parentForm = ctrls[2]; + var isReadonly = angular.isDefined(attr.readonly); + var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk); + var tagName = element[0].tagName.toLowerCase(); + + + if (!containerCtrl) return; + if (attr.type === 'hidden') { + element.attr('aria-hidden', 'true'); + return; + } else if (containerCtrl.input) { + if (containerCtrl.input[0].contains(element[0])) { + return; + } else { + throw new Error(" can only have *one* , + * + * + * + */ +function mdSelectOnFocusDirective($timeout) { + + return { + restrict: 'A', + link: postLink + }; + + function postLink(scope, element, attr) { + if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return; + + var preventMouseUp = false; + + element + .on('focus', onFocus) + .on('mouseup', onMouseUp); + + scope.$on('$destroy', function() { + element + .off('focus', onFocus) + .off('mouseup', onMouseUp); + }); + + function onFocus() { + preventMouseUp = true; + + $timeout(function() { + // Use HTMLInputElement#select to fix firefox select issues. + // The debounce is here for Edge's sake, otherwise the selection doesn't work. + element[0].select(); + + // This should be reset from inside the `focus`, because the event might + // have originated from something different than a click, e.g. a keyboard event. + preventMouseUp = false; + }, 1, false); + } + + // Prevents the default action of the first `mouseup` after a focus. + // This is necessary, because browsers fire a `mouseup` right after the element + // has been focused. In some browsers (Firefox in particular) this can clear the + // selection. There are examples of the problem in issue #7487. + function onMouseUp(event) { + if (preventMouseUp) { + event.preventDefault(); + } + } + } +} + +var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault']; +function ngMessagesDirective() { + return { + restrict: 'EA', + link: postLink, + + // This is optional because we don't want target *all* ngMessage instances, just those inside of + // mdInputContainer. + require: '^^?mdInputContainer' + }; + + function postLink(scope, element, attrs, inputContainer) { + // If we are not a child of an input container, don't do anything + if (!inputContainer) return; + + // Add our animation class + element.toggleClass('md-input-messages-animation', true); + + // Add our md-auto-hide class to automatically hide/show messages when container is invalid + element.toggleClass('md-auto-hide', true); + + // If we see some known visibility directives, remove the md-auto-hide class + if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) { + element.toggleClass('md-auto-hide', false); + } + } + + function hasVisibiltyDirective(attrs) { + return visibilityDirectives.some(function(attr) { + return attrs[attr]; + }); + } +} + +function ngMessageDirective($mdUtil) { + return { + restrict: 'EA', + compile: compile, + priority: 100 + }; + + function compile(tElement) { + if (!isInsideInputContainer(tElement)) { + + // When the current element is inside of a document fragment, then we need to check for an input-container + // in the postLink, because the element will be later added to the DOM and is currently just in a temporary + // fragment, which causes the input-container check to fail. + if (isInsideFragment()) { + return function (scope, element) { + if (isInsideInputContainer(element)) { + // Inside of the postLink function, a ngMessage directive will be a comment element, because it's + // currently hidden. To access the shown element, we need to use the element from the compile function. + initMessageElement(tElement); + } + }; + } + } else { + initMessageElement(tElement); + } + + function isInsideFragment() { + var nextNode = tElement[0]; + while (nextNode = nextNode.parentNode) { + if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return true; + } + } + return false; + } + + function isInsideInputContainer(element) { + return !!$mdUtil.getClosest(element, "md-input-container"); + } + + function initMessageElement(element) { + // Add our animation class + element.toggleClass('md-input-message-animation', true); + } + } +} + +var $$AnimateRunner, $animateCss, $mdUtil; + +function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil) { + saveSharedServices($$AnimateRunner, $animateCss, $mdUtil); + + return { + addClass: function(element, className, done) { + showInputMessages(element, done); + } + + // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire + }; +} + +function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil) { + saveSharedServices($$AnimateRunner, $animateCss, $mdUtil); + + return { + enter: function(element, done) { + showInputMessages(element, done); + }, + + leave: function(element, done) { + hideInputMessages(element, done); + }, + + addClass: function(element, className, done) { + if (className == "ng-hide") { + hideInputMessages(element, done); + } else { + done(); + } + }, + + removeClass: function(element, className, done) { + if (className == "ng-hide") { + showInputMessages(element, done); + } else { + done(); + } + } + } +} + +function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil) { + saveSharedServices($$AnimateRunner, $animateCss, $mdUtil); + + return { + enter: function(element, done) { + var animator = showMessage(element); + + animator.start().done(done); + }, + + leave: function(element, done) { + var animator = hideMessage(element); + + animator.start().done(done); + } + } +} + +function showInputMessages(element, done) { + var animators = [], animator; + var messages = getMessagesElement(element); + + angular.forEach(messages.children(), function(child) { + animator = showMessage(angular.element(child)); + + animators.push(animator.start()); + }); + + $$AnimateRunner.all(animators, done); +} + +function hideInputMessages(element, done) { + var animators = [], animator; + var messages = getMessagesElement(element); + + angular.forEach(messages.children(), function(child) { + animator = hideMessage(angular.element(child)); + + animators.push(animator.start()); + }); + + $$AnimateRunner.all(animators, done); +} + +function showMessage(element) { + var height = parseInt(window.getComputedStyle(element[0]).height); + var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop); + + var messages = getMessagesElement(element); + var container = getInputElement(element); + + // Check to see if the message is already visible so we can skip + var alreadyVisible = (topMargin > -height); + + // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip + if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) { + return $animateCss(element, {}); + } + + return $animateCss(element, { + event: 'enter', + structural: true, + from: {"opacity": 0, "margin-top": -height + "px"}, + to: {"opacity": 1, "margin-top": "0"}, + duration: 0.3 + }); +} + +function hideMessage(element) { + var height = element[0].offsetHeight; + var styles = window.getComputedStyle(element[0]); + + // If we are already hidden, just return an empty animation + if (styles.opacity == 0) { + return $animateCss(element, {}); + } + + // Otherwise, animate + return $animateCss(element, { + event: 'leave', + structural: true, + from: {"opacity": 1, "margin-top": 0}, + to: {"opacity": 0, "margin-top": -height + "px"}, + duration: 0.3 + }); +} + +function getInputElement(element) { + var inputContainer = element.controller('mdInputContainer'); + + return inputContainer.element; +} + +function getMessagesElement(element) { + // If we are a ng-message element, we need to traverse up the DOM tree + if (element.hasClass('md-input-message-animation')) { + return angular.element($mdUtil.getClosest(element, function(node) { + return node.classList.contains('md-input-messages-animation'); + })); + } + + // Otherwise, we can traverse down + return angular.element(element[0].querySelector('.md-input-messages-animation')); +} + +function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_) { + $$AnimateRunner = _$$AnimateRunner_; + $animateCss = _$animateCss_; + $mdUtil = _$mdUtil_; +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.list + * @description + * List module + */ +MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"]; +mdListDirective.$inject = ["$mdTheming"]; +mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"]; +angular.module('material.components.list', [ + 'material.core' +]) + .controller('MdListController', MdListController) + .directive('mdList', mdListDirective) + .directive('mdListItem', mdListItemDirective); + +/** + * @ngdoc directive + * @name mdList + * @module material.components.list + * + * @restrict E + * + * @description + * The `` directive is a list container for 1..n `` tags. + * + * @usage + * + * + * + * + *
+ *

{{item.title}}

+ *

{{item.description}}

+ *
+ *
+ *
+ *
+ */ + +function mdListDirective($mdTheming) { + return { + restrict: 'E', + compile: function(tEl) { + tEl[0].setAttribute('role', 'list'); + return $mdTheming; + } + }; +} +/** + * @ngdoc directive + * @name mdListItem + * @module material.components.list + * + * @restrict E + * + * @description + * A `md-list-item` element can be used to represent some information in a row.
+ * + * @usage + * ### Single Row Item + * + * + * Single Row Item + * + * + * + * ### Multiple Lines + * By using the following markup, you will be able to have two lines inside of one `md-list-item`. + * + * + * + *
+ *

First Line

+ *

Second Line

+ *
+ *
+ *
+ * + * It is also possible to have three lines inside of one list item. + * + * + * + *
+ *

First Line

+ *

Second Line

+ *

Third Line

+ *
+ *
+ *
+ * + * ### Secondary Items + * Secondary items are elements which will be aligned at the end of the `md-list-item`. + * + * + * + * Single Row Item + * + * Secondary Button + * + * + * + * + * It also possible to have multiple secondary items inside of one `md-list-item`. + * + * + * + * Single Row Item + * First Button + * Second Button + * + * + * + * ### Proxy Item + * Proxies are elements, which will execute their specific action on click
+ * Currently supported proxy items are + * - `md-checkbox` (Toggle) + * - `md-switch` (Toggle) + * - `md-menu` (Open) + * + * This means, when using a supported proxy item inside of `md-list-item`, the list item will + * become clickable and executes the associated action of the proxy element on click. + * + * + * + * First Line + * + * + * + * + * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click. + * + * + * + * First Line + * + * + * + * + * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`. + * + * It is also possible to have a `md-menu` inside of a `md-list-item`. + * + * + *

Click anywhere to fire the secondary action

+ * + * + * + * + * + * + * + * Redial + * + * + * + * + * Check voicemail + * + * + * + * + * + * Notifications + * + * + * + * + *
+ *
+ * + * The menu will automatically open, when the users clicks on the `md-list-item`.
+ * + * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the + * position mode and applies it to the `md-menu`. + * + * ### Avatars + * Sometimes you may want to have some avatars inside of the `md-list-item `.
+ * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `` element. + * + * + * + * + * Alan Turing + * + * + * When using `` for an avater, you have to use the `.md-avatar-icon` class. + * + * + * + * Timothy Kopra + * + * + * + * In cases, you have a `md-list-item`, which doesn't have any avatar, + * but you want to align it with the other avatar items, you have to use the `.md-offset` class. + * + * + * + * Jon Doe + * + * + * + * ### DOM modification + * The `md-list-item` component automatically detects if the list item should be clickable. + * + * --- + * If the `md-list-item` is clickable, we wrap all content inside of a `
` and create + * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`) + * + * We create an overlaying button, instead of wrapping all content inside of the button, + * because otherwise some elements may not be clickable inside of the button. + * + * --- + * When using a secondary item inside of your list item, the `md-list-item` component will automatically create + * a secondary container at the end of the `md-list-item`, which contains all secondary items. + * + * The secondary item container is not static, because otherwise the overflow will not work properly on the + * list item. + * + */ +function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { + var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu']; + return { + restrict: 'E', + controller: 'MdListController', + compile: function(tEl, tAttrs) { + + // Check for proxy controls (no ng-click on parent, and a control inside) + var secondaryItems = tEl[0].querySelectorAll('.md-secondary'); + var hasProxiedElement; + var proxyElement; + var itemContainer = tEl; + + tEl[0].setAttribute('role', 'listitem'); + + if (tAttrs.ngClick || tAttrs.ngDblclick || tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) { + wrapIn('button'); + } else { + for (var i = 0, type; type = proxiedTypes[i]; ++i) { + if (proxyElement = tEl[0].querySelector(type)) { + hasProxiedElement = true; + break; + } + } + if (hasProxiedElement) { + wrapIn('div'); + } else if (!tEl[0].querySelector('md-button:not(.md-secondary):not(.md-exclude)')) { + tEl.addClass('md-no-proxy'); + } + } + + wrapSecondaryItems(); + setupToggleAria(); + + if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") { + setupProxiedMenu(); + } + + function setupToggleAria() { + var toggleTypes = ['md-switch', 'md-checkbox']; + var toggle; + + for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) { + if (toggle = tEl.find(toggleType)[0]) { + if (!toggle.hasAttribute('aria-label')) { + var p = tEl.find('p')[0]; + if (!p) return; + toggle.setAttribute('aria-label', 'Toggle ' + p.textContent); + } + } + } + } + + function setupProxiedMenu() { + var menuEl = angular.element(proxyElement); + + var isEndAligned = menuEl.parent().hasClass('md-secondary-container') || + proxyElement.parentNode.firstElementChild !== proxyElement; + + var xAxisPosition = 'left'; + + if (isEndAligned) { + // When the proxy item is aligned at the end of the list, we have to set the origin to the end. + xAxisPosition = 'right'; + } + + // Set the position mode / origin of the proxied menu. + if (!menuEl.attr('md-position-mode')) { + menuEl.attr('md-position-mode', xAxisPosition + ' target'); + } + + // Apply menu open binding to menu button + var menuOpenButton = menuEl.children().eq(0); + if (!hasClickEvent(menuOpenButton[0])) { + menuOpenButton.attr('ng-click', '$mdOpenMenu($event)'); + } + + if (!menuOpenButton.attr('aria-label')) { + menuOpenButton.attr('aria-label', 'Open List Menu'); + } + } + + function wrapIn(type) { + if (type == 'div') { + itemContainer = angular.element('
'); + itemContainer.append(tEl.contents()); + tEl.addClass('md-proxy-focus'); + } else { + // Element which holds the default list-item content. + itemContainer = angular.element( + '
'+ + '
'+ + '
' + ); + + // Button which shows ripple and executes primary action. + var buttonWrap = angular.element( + '' + ); + + buttonWrap[0].setAttribute('aria-label', tEl[0].textContent); + + copyAttributes(tEl[0], buttonWrap[0]); + + // We allow developers to specify the `md-no-focus` class, to disable the focus style + // on the button executor. Once more classes should be forwarded, we should probably make the + // class forward more generic. + if (tEl.hasClass('md-no-focus')) { + buttonWrap.addClass('md-no-focus'); + } + + // Append the button wrap before our list-item content, because it will overlay in relative. + itemContainer.prepend(buttonWrap); + itemContainer.children().eq(1).append(tEl.contents()); + + tEl.addClass('_md-button-wrap'); + } + + tEl[0].setAttribute('tabindex', '-1'); + tEl.append(itemContainer); + } + + function wrapSecondaryItems() { + var secondaryItemsWrapper = angular.element('
'); + + angular.forEach(secondaryItems, function(secondaryItem) { + wrapSecondaryItem(secondaryItem, secondaryItemsWrapper); + }); + + itemContainer.append(secondaryItemsWrapper); + } + + function wrapSecondaryItem(secondaryItem, container) { + // If the current secondary item is not a button, but contains a ng-click attribute, + // the secondary item will be automatically wrapped inside of a button. + if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) { + + $mdAria.expect(secondaryItem, 'aria-label'); + var buttonWrapper = angular.element(''); + + // Copy the attributes from the secondary item to the generated button. + // We also support some additional attributes from the secondary item, + // because some developers may use a ngIf, ngHide, ngShow on their item. + copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']); + + secondaryItem.setAttribute('tabindex', '-1'); + buttonWrapper.append(secondaryItem); + + secondaryItem = buttonWrapper[0]; + } + + if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) { + // In this case we remove the secondary class, so we can identify it later, when we searching for the + // proxy items. + angular.element(secondaryItem).removeClass('md-secondary'); + } + + tEl.addClass('md-with-secondary'); + container.append(secondaryItem); + } + + /** + * Copies attributes from a source element to the destination element + * By default the function will copy the most necessary attributes, supported + * by the button executor for clickable list items. + * @param source Element with the specified attributes + * @param destination Element which will retrieve the attributes + * @param extraAttrs Additional attributes, which will be copied over. + */ + function copyAttributes(source, destination, extraAttrs) { + var copiedAttrs = $mdUtil.prefixer([ + 'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref', + 'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts' + ]); + + if (extraAttrs) { + copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs)); + } + + angular.forEach(copiedAttrs, function(attr) { + if (source.hasAttribute(attr)) { + destination.setAttribute(attr, source.getAttribute(attr)); + source.removeAttribute(attr); + } + }); + } + + function isProxiedElement(el) { + return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1; + } + + function isButton(el) { + var nodeName = el.nodeName.toUpperCase(); + + return nodeName == "MD-BUTTON" || nodeName == "BUTTON"; + } + + function hasClickEvent (element) { + var attr = element.attributes; + for (var i = 0; i < attr.length; i++) { + if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true; + } + return false; + } + + return postLink; + + function postLink($scope, $element, $attr, ctrl) { + $element.addClass('_md'); // private md component indicator for styling + + var proxies = [], + firstElement = $element[0].firstElementChild, + isButtonWrap = $element.hasClass('_md-button-wrap'), + clickChild = isButtonWrap ? firstElement.firstElementChild : firstElement, + hasClick = clickChild && hasClickEvent(clickChild); + + computeProxies(); + computeClickable(); + + if ($element.hasClass('md-proxy-focus') && proxies.length) { + angular.forEach(proxies, function(proxy) { + proxy = angular.element(proxy); + + $scope.mouseActive = false; + proxy.on('mousedown', function() { + $scope.mouseActive = true; + $timeout(function(){ + $scope.mouseActive = false; + }, 100); + }) + .on('focus', function() { + if ($scope.mouseActive === false) { $element.addClass('md-focused'); } + proxy.on('blur', function proxyOnBlur() { + $element.removeClass('md-focused'); + proxy.off('blur', proxyOnBlur); + }); + }); + }); + } + + + function computeProxies() { + if (firstElement && firstElement.children && !hasClick) { + + angular.forEach(proxiedTypes, function(type) { + + // All elements which are not capable for being used a proxy have the .md-secondary class + // applied. These items had been sorted out in the secondary wrap function. + angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) { + proxies.push(child); + }); + }); + + } + } + + function computeClickable() { + if (proxies.length == 1 || hasClick) { + $element.addClass('md-clickable'); + + if (!hasClick) { + ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style'))); + } + } + } + + function isEventFromControl(event) { + var forbiddenControls = ['md-slider']; + + // If there is no path property in the event, then we can assume that the event was not bubbled. + if (!event.path) { + return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1; + } + + // We iterate the event path up and check for a possible component. + // Our maximum index to search, is the list item root. + var maxPath = event.path.indexOf($element.children()[0]); + + for (var i = 0; i < maxPath; i++) { + if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) { + return true; + } + } + } + + var clickChildKeypressListener = function(e) { + if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) { + var keyCode = e.which || e.keyCode; + if (keyCode == $mdConstant.KEY_CODE.SPACE) { + if (clickChild) { + clickChild.click(); + e.preventDefault(); + e.stopPropagation(); + } + } + } + }; + + if (!hasClick && !proxies.length) { + clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener); + } + + $element.off('click'); + $element.off('keypress'); + + if (proxies.length == 1 && clickChild) { + $element.children().eq(0).on('click', function(e) { + // When the event is coming from an control and it should not trigger the proxied element + // then we are skipping. + if (isEventFromControl(e)) return; + + var parentButton = $mdUtil.getClosest(e.target, 'BUTTON'); + if (!parentButton && clickChild.contains(e.target)) { + angular.forEach(proxies, function(proxy) { + if (e.target !== proxy && !proxy.contains(e.target)) { + if (proxy.nodeName === 'MD-MENU') { + proxy = proxy.children[0]; + } + angular.element(proxy).triggerHandler('click'); + } + }); + } + }); + } + + $scope.$on('$destroy', function () { + clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener); + }); + } + } + }; +} + +/* + * @private + * @ngdoc controller + * @name MdListController + * @module material.components.list + * + */ +function MdListController($scope, $element, $mdListInkRipple) { + var ctrl = this; + ctrl.attachRipple = attachRipple; + + function attachRipple (scope, element) { + var options = {}; + $mdListInkRipple.attach(scope, element, options); + } +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.menu + */ + +angular.module('material.components.menu', [ + 'material.core', + 'material.components.backdrop' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.menu-bar + */ + +angular.module('material.components.menuBar', [ + 'material.core', + 'material.components.icon', + 'material.components.menu' +]); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.navBar + */ + + +MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"]; +MdNavItem.$inject = ["$$rAF"]; +MdNavItemController.$inject = ["$element"]; +MdNavBar.$inject = ["$mdAria", "$mdTheming"]; +angular.module('material.components.navBar', ['material.core']) + .controller('MdNavBarController', MdNavBarController) + .directive('mdNavBar', MdNavBar) + .controller('MdNavItemController', MdNavItemController) + .directive('mdNavItem', MdNavItem); + + +/***************************************************************************** + * PUBLIC DOCUMENTATION * + *****************************************************************************/ +/** + * @ngdoc directive + * @name mdNavBar + * @module material.components.navBar + * + * @restrict E + * + * @description + * The `` directive renders a list of material tabs that can be used + * for top-level page navigation. Unlike ``, it has no concept of a tab + * body and no bar pagination. + * + * Because it deals with page navigation, certain routing concepts are built-in. + * Route changes via via ng-href, ui-sref, or ng-click events are supported. + * Alternatively, the user could simply watch currentNavItem for changes. + * + * Accessibility functionality is implemented as a site navigator with a + * listbox, according to + * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style + * + * @param {string=} mdSelectedNavItem The name of the current tab; this must + * match the name attribute of `` + * @param {string=} navBarAriaLabel An aria-label for the nav-bar + * + * @usage + * + * + * Page One + * Page Two + * Page Three + * + * + * + * (function() { + * ‘use strict’; + * + * $rootScope.$on('$routeChangeSuccess', function(event, current) { + * $scope.currentLink = getCurrentLinkFromRoute(current); + * }); + * }); + * + */ + +/***************************************************************************** + * mdNavItem + *****************************************************************************/ +/** + * @ngdoc directive + * @name mdNavItem + * @module material.components.navBar + * + * @restrict E + * + * @description + * `` describes a page navigation link within the `` + * component. It renders an md-button as the actual link. + * + * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required to be + * specified. + * + * @param {Function=} mdNavClick Function which will be called when the + * link is clicked to change the page. Renders as an `ng-click`. + * @param {string=} mdNavHref url to transition to when this link is clicked. + * Renders as an `ng-href`. + * @param {string=} mdNavSref Ui-router state to transition to when this link is + * clicked. Renders as a `ui-sref`. + * @param {string=} name The name of this link. Used by the nav bar to know + * which link is currently selected. + * + * @usage + * See `` for usage. + */ + + +/***************************************************************************** + * IMPLEMENTATION * + *****************************************************************************/ + +function MdNavBar($mdAria, $mdTheming) { + return { + restrict: 'E', + transclude: true, + controller: MdNavBarController, + controllerAs: 'ctrl', + bindToController: true, + scope: { + 'mdSelectedNavItem': '=?', + 'navBarAriaLabel': '@?', + }, + template: + '
' + + '' + + '' + + '
', + link: function(scope, element, attrs, ctrl) { + $mdTheming(element); + if (!ctrl.navBarAriaLabel) { + $mdAria.expectAsync(element, 'aria-label', angular.noop); + } + }, + }; +} + +/** + * Controller for the nav-bar component. + * + * Accessibility functionality is implemented as a site navigator with a + * listbox, according to + * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style + * @param {!angular.JQLite} $element + * @param {!angular.Scope} $scope + * @param {!angular.Timeout} $timeout + * @param {!Object} $mdConstant + * @constructor + * @final + * @ngInject + */ +function MdNavBarController($element, $scope, $timeout, $mdConstant) { + // Injected variables + /** @private @const {!angular.Timeout} */ + this._$timeout = $timeout; + + /** @private @const {!angular.Scope} */ + this._$scope = $scope; + + /** @private @const {!Object} */ + this._$mdConstant = $mdConstant; + + // Data-bound variables. + /** @type {string} */ + this.mdSelectedNavItem; + + /** @type {string} */ + this.navBarAriaLabel; + + // State variables. + + /** @type {?angular.JQLite} */ + this._navBarEl = $element[0]; + + /** @type {?angular.JQLite} */ + this._inkbar; + + var self = this; + // need to wait for transcluded content to be available + var deregisterTabWatch = this._$scope.$watch(function() { + return self._navBarEl.querySelectorAll('._md-nav-button').length; + }, + function(newLength) { + if (newLength > 0) { + self._initTabs(); + deregisterTabWatch(); + } + }); +} + + + +/** + * Initializes the tab components once they exist. + * @private + */ +MdNavBarController.prototype._initTabs = function() { + this._inkbar = angular.element(this._navBarEl.getElementsByTagName('md-nav-ink-bar')[0]); + + var self = this; + this._$timeout(function() { + self._updateTabs(self.mdSelectedNavItem, undefined); + }); + + this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) { + // Wait a digest before update tabs for products doing + // anything dynamic in the template. + self._$timeout(function() { + self._updateTabs(newValue, oldValue); + }); + }); +}; + +/** + * Set the current tab to be selected. + * @param {string|undefined} newValue New current tab name. + * @param {string|undefined} oldValue Previous tab name. + * @private + */ +MdNavBarController.prototype._updateTabs = function(newValue, oldValue) { + var self = this; + var tabs = this._getTabs(); + var oldIndex = -1; + var newIndex = -1; + var newTab = this._getTabByName(newValue); + var oldTab = this._getTabByName(oldValue); + + if (oldTab) { + oldTab.setSelected(false); + oldIndex = tabs.indexOf(oldTab); + } + + if (newTab) { + newTab.setSelected(true); + newIndex = tabs.indexOf(newTab); + } + + this._$timeout(function() { + self._updateInkBarStyles(newTab, newIndex, oldIndex); + }); +}; + +/** + * Repositions the ink bar to the selected tab. + * @private + */ +MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) { + this._inkbar.toggleClass('_md-left', newIndex < oldIndex) + .toggleClass('_md-right', newIndex > oldIndex); + + this._inkbar.css({display: newIndex < 0 ? 'none' : ''}); + + if(tab){ + var tabEl = tab.getButtonEl(); + var left = tabEl.offsetLeft; + + this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'}); + } +}; + +/** + * Returns an array of the current tabs. + * @return {!Array} + * @private + */ +MdNavBarController.prototype._getTabs = function() { + var linkArray = Array.prototype.slice.call( + this._navBarEl.querySelectorAll('.md-nav-item')); + return linkArray.map(function(el) { + return angular.element(el).controller('mdNavItem') + }); +}; + +/** + * Returns the tab with the specified name. + * @param {string} name The name of the tab, found in its name attribute. + * @return {!NavItemController|undefined} + * @private + */ +MdNavBarController.prototype._getTabByName = function(name) { + return this._findTab(function(tab) { + return tab.getName() == name; + }); +}; + +/** + * Returns the selected tab. + * @return {!NavItemController|undefined} + * @private + */ +MdNavBarController.prototype._getSelectedTab = function() { + return this._findTab(function(tab) { + return tab.isSelected() + }); +}; + +/** + * Returns the focused tab. + * @return {!NavItemController|undefined} + */ +MdNavBarController.prototype.getFocusedTab = function() { + return this._findTab(function(tab) { + return tab.hasFocus() + }); +}; + +/** + * Find a tab that matches the specified function. + * @private + */ +MdNavBarController.prototype._findTab = function(fn) { + var tabs = this._getTabs(); + for (var i = 0; i < tabs.length; i++) { + if (fn(tabs[i])) { + return tabs[i]; + } + } + + return null; +}; + +/** + * Direct focus to the selected tab when focus enters the nav bar. + */ +MdNavBarController.prototype.onFocus = function() { + var tab = this._getSelectedTab(); + if (tab) { + tab.setFocused(true); + } +}; + +/** + * Clear tab focus when focus leaves the nav bar. + */ +MdNavBarController.prototype.onBlur = function() { + var tab = this.getFocusedTab(); + if (tab) { + tab.setFocused(false); + } +}; + +/** + * Move focus from oldTab to newTab. + * @param {!NavItemController} oldTab + * @param {!NavItemController} newTab + * @private + */ +MdNavBarController.prototype._moveFocus = function(oldTab, newTab) { + oldTab.setFocused(false); + newTab.setFocused(true); +}; + +/** + * Responds to keypress events. + * @param {!Event} e + */ +MdNavBarController.prototype.onKeydown = function(e) { + var keyCodes = this._$mdConstant.KEY_CODE; + var tabs = this._getTabs(); + var focusedTab = this.getFocusedTab(); + if (!focusedTab) return; + + var focusedTabIndex = tabs.indexOf(focusedTab); + + // use arrow keys to navigate between tabs + switch (e.keyCode) { + case keyCodes.UP_ARROW: + case keyCodes.LEFT_ARROW: + if (focusedTabIndex > 0) { + this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]); + } + break; + case keyCodes.DOWN_ARROW: + case keyCodes.RIGHT_ARROW: + if (focusedTabIndex < tabs.length - 1) { + this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]); + } + break; + case keyCodes.SPACE: + case keyCodes.ENTER: + // timeout to avoid a "digest already in progress" console error + this._$timeout(function() { + focusedTab.getButtonEl().click(); + }); + break; + } +}; + +/** + * @ngInject + */ +function MdNavItem($$rAF) { + return { + restrict: 'E', + require: ['mdNavItem', '^mdNavBar'], + controller: MdNavItemController, + bindToController: true, + controllerAs: 'ctrl', + replace: true, + transclude: true, + template: + '
  • ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
  • ', + scope: { + 'mdNavClick': '&?', + 'mdNavHref': '@?', + 'mdNavSref': '@?', + 'name': '@', + }, + link: function(scope, element, attrs, controllers) { + var mdNavItem = controllers[0]; + var mdNavBar = controllers[1]; + + // When accessing the element's contents synchronously, they + // may not be defined yet because of transclusion. There is a higher chance + // that it will be accessible if we wait one frame. + $$rAF(function() { + if (!mdNavItem.name) { + mdNavItem.name = angular.element(element[0].querySelector('._md-nav-button-text')) + .text().trim(); + } + + var navButton = angular.element(element[0].querySelector('._md-nav-button')); + navButton.on('click', function() { + mdNavBar.mdSelectedNavItem = mdNavItem.name; + scope.$apply(); + }); + }); + } + }; +} + +/** + * Controller for the nav-item component. + * @param {!angular.JQLite} $element + * @constructor + * @final + * @ngInject + */ +function MdNavItemController($element) { + + /** @private @const {!angular.JQLite} */ + this._$element = $element; + + // Data-bound variables + /** @const {?Function} */ + this.mdNavClick; + /** @const {?string} */ + this.mdNavHref; + /** @const {?string} */ + this.name; + + // State variables + /** @private {boolean} */ + this._selected = false; + + /** @private {boolean} */ + this._focused = false; + + var hasNavClick = !!($element.attr('md-nav-click')); + var hasNavHref = !!($element.attr('md-nav-href')); + var hasNavSref = !!($element.attr('md-nav-sref')); + + // Cannot specify more than one nav attribute + if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) { + throw Error( + 'Must specify exactly one of md-nav-click, md-nav-href, ' + + 'md-nav-sref for nav-item directive'); + } +} + +/** + * Returns a map of class names and values for use by ng-class. + * @return {!Object} + */ +MdNavItemController.prototype.getNgClassMap = function() { + return { + 'md-active': this._selected, + 'md-primary': this._selected, + 'md-unselected': !this._selected, + 'md-focused': this._focused, + }; +}; + +/** + * Get the name attribute of the tab. + * @return {string} + */ +MdNavItemController.prototype.getName = function() { + return this.name; +}; + +/** + * Get the button element associated with the tab. + * @return {!Element} + */ +MdNavItemController.prototype.getButtonEl = function() { + return this._$element[0].querySelector('._md-nav-button'); +}; + +/** + * Set the selected state of the tab. + * @param {boolean} isSelected + */ +MdNavItemController.prototype.setSelected = function(isSelected) { + this._selected = isSelected; +}; + +/** + * @return {boolean} + */ +MdNavItemController.prototype.isSelected = function() { + return this._selected; +}; + +/** + * Set the focused state of the tab. + * @param {boolean} isFocused + */ +MdNavItemController.prototype.setFocused = function(isFocused) { + this._focused = isFocused; +}; + +/** + * @return {boolean} + */ +MdNavItemController.prototype.hasFocus = function() { + return this._focused; +}; + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.panel + */ +MdPanelService.$inject = ["$rootElement", "$rootScope", "$injector", "$window"]; +angular + .module('material.components.panel', [ + 'material.core', + 'material.components.backdrop' + ]) + .service('$mdPanel', MdPanelService); + + +/***************************************************************************** + * PUBLIC DOCUMENTATION * + *****************************************************************************/ + +/** + * @ngdoc service + * @name $mdPanel + * @module material.components.panel + * + * @description + * `$mdPanel` is a robust, low-level service for creating floating panels on + * the screen. It can be used to implement tooltips, dialogs, pop-ups, etc. + * + * @usage + * + * (function(angular, undefined) { + * ‘use strict’; + * + * angular + * .module('demoApp', ['ngMaterial']) + * .controller('DemoDialogController', DialogController); + * + * var panelRef; + * + * function showPanel($event) { + * var panelPosition = $mdPanel.newPanelPosition() + * .absolute() + * .top('50%') + * .left('50%'); + * + * var panelAnimation = $mdPanel.newPanelAnimation() + * .targetEvent($event) + * .defaultAnimation('md-panel-animate-fly') + * .closeTo('.show-button'); + * + * var config = { + * attachTo: angular.element(document.body), + * controller: DialogController, + * controllerAs: 'ctrl', + * position: panelPosition, + * animation: panelAnimation, + * targetEvent: $event, + * templateUrl: 'dialog-template.html', + * clickOutsideToClose: true, + * escapeToClose: true, + * focusOnOpen: true + * } + * + * $mdPanel.open(config) + * .then(function(result) { + * panelRef = result; + * }); + * } + * + * function DialogController(MdPanelRef, toppings) { + * var toppings; + * + * function closeDialog() { + * MdPanelRef && MdPanelRef.close(); + * } + * } + * })(angular); + * + */ + +/** + * @ngdoc method + * @name $mdPanel#create + * @description + * Creates a panel with the specified options. + * + * @param config {!Object=} Specific configuration object that may contain the + * following properties: + * + * - `id` - `{string=}`: An ID to track the panel by. When an ID is provided, + * the created panel is added to a tracked panels object. Any subsequent + * requests made to create a panel with that ID are ignored. This is useful + * in having the panel service not open multiple panels from the same user + * interaction when there is no backdrop and events are propagated. Defaults + * to an arbitrary string that is not tracked. + * - `template` - `{string=}`: HTML template to show in the panel. This + * **must** be trusted HTML with respect to Angular’s + * [$sce service](https://docs.angularjs.org/api/ng/service/$sce). + * - `templateUrl` - `{string=}`: The URL that will be used as the content of + * the panel. + * - `controller` - `{(function|string)=}`: The controller to associate with + * the panel. The controller can inject a reference to the returned + * panelRef, which allows the panel to be closed, hidden, and shown. Any + * fields passed in through locals or resolve will be bound to the + * controller. + * - `controllerAs` - `{string=}`: An alias to assign the controller to on + * the scope. + * - `bindToController` - `{boolean=}`: Binds locals to the controller + * instead of passing them in. Defaults to true, as this is a best + * practice. + * - `locals` - `{Object=}`: An object containing key/value pairs. The keys + * will be used as names of values to inject into the controller. For + * example, `locals: {three: 3}` would inject `three` into the controller, + * with the value 3. + * - `resolve` - `{Object=}`: Similar to locals, except it takes promises as + * values. The panel will not open until all of the promises resolve. + * - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to + * attach the panel to. Defaults to appending to the root element of the + * application. + * - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch + * events should be allowed to propagate 'go through' the container, aka the + * wrapper, of the panel. Defaults to false. + * - `panelClass` - `{string=}`: A css class to apply to the panel element. + * This class should define any borders, box-shadow, etc. for the panel. + * - `zIndex` - `{number=}`: The z-index to place the panel at. + * Defaults to 80. + * - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that + * specifies the alignment of the panel. For more information, see + * `MdPanelPosition`. + * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click + * outside the panel to close it. Defaults to false. + * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to + * close the panel. Defaults to false. + * - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the + * panel. If `trapFocus` is true, the user will not be able to interact + * with the rest of the page until the panel is dismissed. Defaults to + * false. + * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on + * open. Only disable if focusing some other way, as focus management is + * required for panels to be accessible. Defaults to true. + * - `fullscreen` - `{boolean=}`: Whether the panel should be full screen. + * Applies the class `._md-panel-fullscreen` to the panel on open. Defaults + * to false. + * - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that + * specifies the animation of the panel. For more information, see + * `MdPanelAnimation`. + * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop + * behind the panel. Defaults to false. + * - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the + * page behind the panel. Defaults to false. + * - `onDomAdded` - `{function=}`: Callback function used to announce when + * the panel is added to the DOM. + * - `onOpenComplete` - `{function=}`: Callback function used to announce + * when the open() action is finished. + * - `onRemoving` - `{function=}`: Callback function used to announce the + * close/hide() action is starting. + * - `onDomRemoved` - `{function=}`: Callback function used to announce when + * the panel is removed from the DOM. + * - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus + * on when the panel closes. This is commonly the element which triggered + * the opening of the panel. If you do not use `origin`, you need to control + * the focus manually. + * + * @returns {!MdPanelRef} panelRef + */ + + +/** + * @ngdoc method + * @name $mdPanel#open + * @description + * Calls the create method above, then opens the panel. This is a shortcut for + * creating and then calling open manually. If custom methods need to be + * called when the panel is added to the DOM or opened, do not use this method. + * Instead create the panel, chain promises on the domAdded and openComplete + * methods, and call open from the returned panelRef. + * + * @param {!Object=} config Specific configuration object that may contain + * the properties defined in `$mdPanel.create`. + * @returns {!angular.$q.Promise} panelRef A promise that resolves + * to an instance of the panel. + */ + + +/** + * @ngdoc method + * @name $mdPanel#newPanelPosition + * @description + * Returns a new instance of the MdPanelPosition object. Use this to create + * the position config object. + * + * @returns {!MdPanelPosition} panelPosition + */ + + +/** + * @ngdoc method + * @name $mdPanel#newPanelAnimation + * @description + * Returns a new instance of the MdPanelAnimation object. Use this to create + * the animation config object. + * + * @returns {!MdPanelAnimation} panelAnimation + */ + + +/***************************************************************************** + * MdPanelRef * + *****************************************************************************/ + + +/** + * @ngdoc type + * @name MdPanelRef + * @module material.components.panel + * @description + * A reference to a created panel. This reference contains a unique id for the + * panel, along with the following properties: + * + * - `id` - `{string}`: The unique id for the panel. This id is used to track + * when a panel was interacted with. + * - `config` - `{!Object=}`: The entire config object that was used in + * create. + * - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM. + * Visibility to the user does not factor into isAttached. + * - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the + * panel. This property is added in order to have access to the `addClass`, + * `removeClass`, `toggleClass`, etc methods. + * - `panelEl` - `{angular.JQLite}`: The panel element. This property is added + * in order to have access to the `addClass`, `removeClass`, `toggleClass`, + * etc methods. + */ + +/** + * @ngdoc method + * @name MdPanelRef#open + * @description + * Attaches and shows the panel. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * opened. + */ + +/** + * @ngdoc method + * @name MdPanelRef#close + * @description + * Hides and detaches the panel. Note that this will **not** destroy the panel. + * If you don't intend on using the panel again, call the {@link #destroy + * destroy} method afterwards. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * closed. + */ + +/** + * @ngdoc method + * @name MdPanelRef#attach + * @description + * Create the panel elements and attach them to the DOM. The panel will be + * hidden by default. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * attached. + */ + +/** + * @ngdoc method + * @name MdPanelRef#detach + * @description + * Removes the panel from the DOM. This will NOT hide the panel before removing + * it. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * detached. + */ + +/** + * @ngdoc method + * @name MdPanelRef#show + * @description + * Shows the panel. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * shown and animations are completed. + */ + +/** + * @ngdoc method + * @name MdPanelRef#hide + * @description + * Hides the panel. + * + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * hidden and animations are completed. + */ + +/** + * @ngdoc method + * @name MdPanelRef#destroy + * @description + * Destroys the panel. The panel cannot be opened again after this is called. + */ + +/** + * @ngdoc method + * @name MdPanelRef#addClass + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * @description + * Adds a class to the panel. DO NOT use this hide/show the panel. + * + * @param {string} newClass class to be added. + * @param {boolean} toElement Whether or not to add the class to the panel + * element instead of the container. + */ + +/** + * @ngdoc method + * @name MdPanelRef#removeClass + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * @description + * Removes a class from the panel. DO NOT use this to hide/show the panel. + * + * @param {string} oldClass Class to be removed. + * @param {boolean} fromElement Whether or not to remove the class from the + * panel element instead of the container. + */ + +/** + * @ngdoc method + * @name MdPanelRef#toggleClass + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * @description + * Toggles a class on the panel. DO NOT use this to hide/show the panel. + * + * @param {string} toggleClass Class to be toggled. + * @param {boolean} onElement Whether or not to remove the class from the panel + * element instead of the container. + */ + +/** + * @ngdoc method + * @name MdPanelRef#updatePosition + * @description + * Updates the position configuration of a panel. Use this to update the + * position of a panel that is open, without having to close and re-open the + * panel. + * + * @param {!MdPanelPosition} position + */ + + +/***************************************************************************** + * MdPanelPosition * + *****************************************************************************/ + + +/** + * @ngdoc type + * @name MdPanelPosition + * @module material.components.panel + * @description + * + * Object for configuring the position of the panel. + * + * @usage + * + * #### Centering the panel + * + * + * new MdPanelPosition().absolute().center(); + * + * + * #### Overlapping the panel with an element + * + * + * new MdPanelPosition() + * .relativeTo(someElement) + * .addPanelPosition( + * $mdPanel.xPosition.ALIGN_START, + * $mdPanel.yPosition.ALIGN_TOPS + * ); + * + * + * #### Aligning the panel with the bottom of an element + * + * + * new MdPanelPosition() + * .relativeTo(someElement) + * .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW); + * + */ + +/** + * @ngdoc method + * @name MdPanelPosition#absolute + * @description + * Positions the panel absolutely relative to the parent element. If the parent + * is document.body, this is equivalent to positioning the panel absolutely + * within the viewport. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#relativeTo + * @description + * Positions the panel relative to a specific element. + * + * @param {string|!Element|!angular.JQLite} element Query selector, DOM element, + * or angular element to position the panel with respect to. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#top + * @description + * Sets the value of `top` for the panel. Clears any previously set vertical + * position. + * + * @param {string=} top Value of `top`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#bottom + * @description + * Sets the value of `bottom` for the panel. Clears any previously set vertical + * position. + * + * @param {string=} bottom Value of `bottom`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#start + * @description + * Sets the panel to the start of the page - `left` if `ltr` or `right` for + * `rtl`. Clears any previously set horizontal position. + * + * @param {string=} start Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#end + * @description + * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`. + * Clears any previously set horizontal position. + * + * @param {string=} end Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#left + * @description + * Sets the value of `left` for the panel. Clears any previously set + * horizontal position. + * + * @param {string=} left Value of `left`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#right + * @description + * Sets the value of `right` for the panel. Clears any previously set + * horizontal position. + * + * @param {string=} right Value of `right`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#centerHorizontally + * @description + * Centers the panel horizontally in the viewport. Clears any previously set + * horizontal position. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#centerVertically + * @description + * Centers the panel vertically in the viewport. Clears any previously set + * vertical position. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#center + * @description + * Centers the panel horizontally and vertically in the viewport. This is + * equivalent to calling both `centerHorizontally` and `centerVertically`. + * Clears any previously set horizontal and vertical positions. + * + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#addPanelPosition + * @description + * Sets the x and y position for the panel relative to another element. Can be + * called multiple times to specify an ordered list of panel positions. The + * first position which allows the panel to be completely on-screen will be + * chosen; the last position will be chose whether it is on-screen or not. + * + * xPosition must be one of the following values available on + * $mdPanel.xPosition: + * + * CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END + * + * ************* + * * * + * * PANEL * + * * * + * ************* + * A B C D E + * + * A: OFFSET_START (for LTR displays) + * B: ALIGN_START (for LTR displays) + * C: CENTER + * D: ALIGN_END (for LTR displays) + * E: OFFSET_END (for LTR displays) + * + * yPosition must be one of the following values available on + * $mdPanel.yPosition: + * + * CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW + * + * F + * G ************* + * * * + * H * PANEL * + * * * + * I ************* + * J + * + * F: BELOW + * G: ALIGN_TOPS + * H: CENTER + * I: ALIGN_BOTTOMS + * J: ABOVE + * + * @param {string} xPosition + * @param {string} yPosition + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#withOffsetX + * @description + * Sets the value of the offset in the x-direction. + * + * @param {string} offsetX + * @returns {!MdPanelPosition} + */ + +/** + * @ngdoc method + * @name MdPanelPosition#withOffsetY + * @description + * Sets the value of the offset in the y-direction. + * + * @param {string} offsetY + * @returns {!MdPanelPosition} + */ + + +/***************************************************************************** + * MdPanelAnimation * + *****************************************************************************/ + + +/** + * @ngdoc object + * @name MdPanelAnimation + * @description + * Animation configuration object. To use, create an MdPanelAnimation with the + * desired properties, then pass the object as part of $mdPanel creation. + * + * Example: + * + * var panelAnimation = new MdPanelAnimation() + * .openFrom(myButtonEl) + * .closeTo('.my-button') + * .withAnimation($mdPanel.animation.SCALE); + * + * $mdPanel.create({ + * animation: panelAnimation + * }); + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#openFrom + * @description + * Specifies where to start the open animation. `openFrom` accepts a + * click event object, query selector, DOM element, or a Rect object that + * is used to determine the bounds. When passed a click event, the location + * of the click will be used as the position to start the animation. + * + * @param {string|!Element|!Event|{top: number, left: number}} + * @returns {!MdPanelAnimation} + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#closeTo + * @description + * Specifies where to animate the panel close. `closeTo` accepts a + * query selector, DOM element, or a Rect object that is used to determine + * the bounds. + * + * @param {string|!Element|{top: number, left: number}} + * @returns {!MdPanelAnimation} + */ + +/** + * @ngdoc method + * @name MdPanelAnimation#withAnimation + * @description + * Specifies the animation class. + * + * There are several default animations that can be used: + * ($mdPanel.animation) + * SLIDE: The panel slides in and out from the specified + * elements. It will not fade in or out. + * SCALE: The panel scales in and out. Slide and fade are + * included in this animation. + * FADE: The panel fades in and out. + * + * Custom classes will by default fade in and out unless + * "transition: opacity 1ms" is added to the to custom class. + * + * @param {string|{open: string, close: string}} cssClass + * @returns {!MdPanelAnimation} + */ + + +/***************************************************************************** + * IMPLEMENTATION * + *****************************************************************************/ + + +// Default z-index for the panel. +var defaultZIndex = 80; +var MD_PANEL_HIDDEN = '_md-panel-hidden'; + +var FOCUS_TRAP_TEMPLATE = angular.element( + '
    '); + + +/** + * A service that is used for controlling/displaying panels on the screen. + * @param {!angular.JQLite} $rootElement + * @param {!angular.Scope} $rootScope + * @param {!angular.$injector} $injector + * @param {!angular.$window} $window + * @final @constructor @ngInject + */ +function MdPanelService($rootElement, $rootScope, $injector, $window) { + /** + * Default config options for the panel. + * Anything angular related needs to be done later. Therefore + * scope: $rootScope.$new(true), + * attachTo: $rootElement, + * are added later. + * @private {!Object} + */ + this._defaultConfigOptions = { + bindToController: true, + clickOutsideToClose: false, + disableParentScroll: false, + escapeToClose: false, + focusOnOpen: true, + fullscreen: false, + hasBackdrop: false, + propagateContainerEvents: false, + transformTemplate: angular.bind(this, this._wrapTemplate), + trapFocus: false, + zIndex: defaultZIndex + }; + + /** @private {!Object} */ + this._config = {}; + + /** @private @const */ + this._$rootElement = $rootElement; + + /** @private @const */ + this._$rootScope = $rootScope; + + /** @private @const */ + this._$injector = $injector; + + /** @private @const */ + this._$window = $window; + + /** @private {!Object} */ + this._trackedPanels = {}; + + /** + * Default animations that can be used within the panel. + * @type {enum} + */ + this.animation = MdPanelAnimation.animation; + + /** + * Possible values of xPosition for positioning the panel relative to + * another element. + * @type {enum} + */ + this.xPosition = MdPanelPosition.xPosition; + + /** + * Possible values of yPosition for positioning the panel relative to + * another element. + * @type {enum} + */ + this.yPosition = MdPanelPosition.yPosition; +} + + +/** + * Creates a panel with the specified options. + * @param {!Object=} config Configuration object for the panel. + * @returns {!MdPanelRef} + */ +MdPanelService.prototype.create = function(config) { + config = config || {}; + + // If the passed-in config contains an ID and the ID is within _trackedPanels, + // return the tracked panel. + if (angular.isDefined(config.id) && this._trackedPanels[config.id]) { + return this._trackedPanels[config.id]; + } + + // If no ID is set within the passed-in config, then create an arbitrary ID. + this._config = { + id: config.id || 'panel_' + this._$injector.get('$mdUtil').nextUid(), + scope: this._$rootScope.$new(true), + attachTo: this._$rootElement + }; + angular.extend(this._config, this._defaultConfigOptions, config); + + var panelRef = new MdPanelRef(this._config, this._$injector); + this._trackedPanels[config.id] = panelRef; + + return panelRef; +}; + + +/** + * Creates and opens a panel with the specified options. + * @param {!Object=} config Configuration object for the panel. + * @returns {!angular.$q.Promise} The panel created from create. + */ +MdPanelService.prototype.open = function(config) { + var panelRef = this.create(config); + return panelRef.open().then(function() { + return panelRef; + }); +}; + + +/** + * Returns a new instance of the MdPanelPosition. Use this to create the + * positioning object. + * @returns {!MdPanelPosition} + */ +MdPanelService.prototype.newPanelPosition = function() { + return new MdPanelPosition(this._$injector); +}; + + +/** + * Returns a new instance of the MdPanelAnimation. Use this to create the + * animation object. + * @returns {!MdPanelAnimation} + */ +MdPanelService.prototype.newPanelAnimation = function() { + return new MdPanelAnimation(this._$injector); +}; + + +/** + * Wraps the users template in two elements, md-panel-outer-wrapper, which + * covers the entire attachTo element, and md-panel, which contains only the + * template. This allows the panel control over positioning, animations, + * and similar properties. + * @param {string} origTemplate The original template. + * @returns {string} The wrapped template. + * @private + */ +MdPanelService.prototype._wrapTemplate = function(origTemplate) { + var template = origTemplate || ''; + + // The panel should be initially rendered offscreen so we can calculate + // height and width for positioning. + return '' + + '
    ' + + '
    ' + template + '
    ' + + '
    '; +}; + + +/***************************************************************************** + * MdPanelRef * + *****************************************************************************/ + + +/** + * A reference to a created panel. This reference contains a unique id for the + * panel, along with properties/functions used to control the panel. + * @param {!Object} config + * @param {!angular.$injector} $injector + * @final @constructor + */ +function MdPanelRef(config, $injector) { + // Injected variables. + /** @private @const {!angular.$q} */ + this._$q = $injector.get('$q'); + + /** @private @const {!angular.$mdCompiler} */ + this._$mdCompiler = $injector.get('$mdCompiler'); + + /** @private @const {!angular.$mdConstant} */ + this._$mdConstant = $injector.get('$mdConstant'); + + /** @private @const {!angular.$mdUtil} */ + this._$mdUtil = $injector.get('$mdUtil'); + + /** @private @const {!angular.Scope} */ + this._$rootScope = $injector.get('$rootScope'); + + /** @private @const {!angular.$animate} */ + this._$animate = $injector.get('$animate'); + + /** @private @const {!MdPanelRef} */ + this._$mdPanel = $injector.get('$mdPanel'); + + /** @private @const {!angular.$log} */ + this._$log = $injector.get('$log'); + + /** @private @const {!angular.$window} */ + this._$window = $injector.get('$window'); + + /** @private @const {!Function} */ + this._$$rAF = $injector.get('$$rAF'); + + // Public variables. + /** + * Unique id for the panelRef. + * @type {string} + */ + this.id = config.id; + + /** @type {!Object} */ + this.config = config; + + /** @type {!angular.JQLite|undefined} */ + this.panelContainer; + + /** @type {!angular.JQLite|undefined} */ + this.panelEl; + + /** + * Whether the panel is attached. This is synchronous. When attach is called, + * isAttached is set to true. When detach is called, isAttached is set to + * false. + * @type {boolean} + */ + this.isAttached = false; + + // Private variables. + /** @private {Array} */ + this._removeListeners = []; + + /** @private {!angular.JQLite|undefined} */ + this._topFocusTrap; + + /** @private {!angular.JQLite|undefined} */ + this._bottomFocusTrap; + + /** @private {!$mdPanel|undefined} */ + this._backdropRef; + + /** @private {Function?} */ + this._restoreScroll = null; +} + + +/** + * Opens an already created and configured panel. If the panel is already + * visible, does nothing. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is opened and animations finish. + */ +MdPanelRef.prototype.open = function() { + var self = this; + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var show = self._simpleBind(self.show, self); + + self.attach() + .then(show) + .then(done) + .catch(reject); + }); +}; + + +/** + * Closes the panel. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is closed and animations finish. + */ +MdPanelRef.prototype.close = function() { + var self = this; + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var detach = self._simpleBind(self.detach, self); + + self.hide() + .then(detach) + .then(done) + .catch(reject); + }); +}; + + +/** + * Attaches the panel. The panel will be hidden afterwards. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is attached. + */ +MdPanelRef.prototype.attach = function() { + if (this.isAttached && this.panelEl) { + return this._$q.when(this); + } + + var self = this; + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var onDomAdded = self.config['onDomAdded'] || angular.noop; + var addListeners = function(response) { + self.isAttached = true; + self._addEventListeners(); + return response; + }; + + self._$q.all([ + self._createBackdrop(), + self._createPanel() + .then(addListeners) + .catch(reject) + ]).then(onDomAdded) + .then(done) + .catch(reject); + }); +}; + + +/** + * Only detaches the panel. Will NOT hide the panel first. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel is detached. + */ +MdPanelRef.prototype.detach = function() { + if (!this.isAttached) { + return this._$q.when(this); + } + + var self = this; + var onDomRemoved = self.config['onDomRemoved'] || angular.noop; + + var detachFn = function() { + self._removeEventListeners(); + + // Remove the focus traps that we added earlier for keeping focus within + // the panel. + if (self._topFocusTrap && self._topFocusTrap.parentNode) { + self._topFocusTrap.parentNode.removeChild(self._topFocusTrap); + } + + if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) { + self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap); + } + + self.panelContainer.remove(); + self.isAttached = false; + return self._$q.when(self); + }; + + if (this._restoreScroll) { + this._restoreScroll(); + this._restoreScroll = null; + } + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + + self._$q.all([ + detachFn(), + self._backdropRef ? self._backdropRef.detach() : true + ]).then(onDomRemoved) + .then(done) + .catch(reject); + }); +}; + + +/** + * Destroys the panel. The Panel cannot be opened again after this. + */ +MdPanelRef.prototype.destroy = function() { + this.config.scope.$destroy(); + this.config.locals = null; +}; + + +/** + * Shows the panel. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel has shown and animations finish. + */ +MdPanelRef.prototype.show = function() { + if (!this.panelContainer) { + return this._$q(function(resolve, reject) { + reject('Panel does not exist yet. Call open() or attach().'); + }); + } + + if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) { + return this._$q.when(this); + } + + var self = this; + var animatePromise = function() { + self.panelContainer.removeClass(MD_PANEL_HIDDEN); + return self._animateOpen(); + }; + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var onOpenComplete = self.config['onOpenComplete'] || angular.noop; + + self._$q.all([ + self._backdropRef ? self._backdropRef.show() : self, + animatePromise().then(function() { self._focusOnOpen(); }, reject) + ]).then(onOpenComplete) + .then(done) + .catch(reject); + }); +}; + + +/** + * Hides the panel. + * @returns {!angular.$q.Promise} A promise that is resolved when + * the panel has hidden and animations finish. + */ +MdPanelRef.prototype.hide = function() { + if (!this.panelContainer) { + return this._$q(function(resolve, reject) { + reject('Panel does not exist yet. Call open() or attach().'); + }); + } + + if (this.panelContainer.hasClass(MD_PANEL_HIDDEN)) { + return this._$q.when(this); + } + + var self = this; + + return this._$q(function(resolve, reject) { + var done = self._done(resolve, self); + var onRemoving = self.config['onRemoving'] || angular.noop; + + var focusOnOrigin = function() { + var origin = self.config['origin']; + if (origin) { + getElement(origin).focus(); + } + }; + + var hidePanel = function() { + self.panelContainer.addClass(MD_PANEL_HIDDEN); + }; + + self._$q.all([ + self._backdropRef ? self._backdropRef.hide() : self, + self._animateClose() + .then(onRemoving) + .then(hidePanel) + .then(focusOnOrigin) + .catch(reject) + ]).then(done, reject); + }); +}; + + +/** + * Add a class to the panel. DO NOT use this to hide/show the panel. + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * + * @param {string} newClass Class to be added. + * @param {boolean} toElement Whether or not to add the class to the panel + * element instead of the container. + */ +MdPanelRef.prototype.addClass = function(newClass, toElement) { + this._$log.warn( + 'The addClass method is in the process of being deprecated. ' + + 'Full deprecation is scheduled for the Angular Material 1.2 release. ' + + 'To achieve the same results, use the panelContainer or panelEl ' + + 'JQLite elements that are referenced in MdPanelRef.'); + + if (!this.panelContainer) { + throw new Error('Panel does not exist yet. Call open() or attach().'); + } + + if (!toElement && !this.panelContainer.hasClass(newClass)) { + this.panelContainer.addClass(newClass); + } else if (toElement && !this.panelEl.hasClass(newClass)) { + this.panelEl.addClass(newClass); + } +}; + + +/** + * Remove a class from the panel. DO NOT use this to hide/show the panel. + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * + * @param {string} oldClass Class to be removed. + * @param {boolean} fromElement Whether or not to remove the class from the + * panel element instead of the container. + */ +MdPanelRef.prototype.removeClass = function(oldClass, fromElement) { + this._$log.warn( + 'The removeClass method is in the process of being deprecated. ' + + 'Full deprecation is scheduled for the Angular Material 1.2 release. ' + + 'To achieve the same results, use the panelContainer or panelEl ' + + 'JQLite elements that are referenced in MdPanelRef.'); + + if (!this.panelContainer) { + throw new Error('Panel does not exist yet. Call open() or attach().'); + } + + if (!fromElement && this.panelContainer.hasClass(oldClass)) { + this.panelContainer.removeClass(oldClass); + } else if (fromElement && this.panelEl.hasClass(oldClass)) { + this.panelEl.removeClass(oldClass); + } +}; + + +/** + * Toggle a class on the panel. DO NOT use this to hide/show the panel. + * @deprecated + * This method is in the process of being deprecated in favor of using the panel + * and container JQLite elements that are referenced in the MdPanelRef object. + * Full deprecation is scheduled for material 1.2. + * + * @param {string} toggleClass The class to toggle. + * @param {boolean} onElement Whether or not to toggle the class on the panel + * element instead of the container. + */ +MdPanelRef.prototype.toggleClass = function(toggleClass, onElement) { + this._$log.warn( + 'The toggleClass method is in the process of being deprecated. ' + + 'Full deprecation is scheduled for the Angular Material 1.2 release. ' + + 'To achieve the same results, use the panelContainer or panelEl ' + + 'JQLite elements that are referenced in MdPanelRef.'); + + if (!this.panelContainer) { + throw new Error('Panel does not exist yet. Call open() or attach().'); + } + + if (!onElement) { + this.panelContainer.toggleClass(toggleClass); + } else { + this.panelEl.toggleClass(toggleClass); + } +}; + + +/** + * Creates a panel and adds it to the dom. + * @returns {!angular.$q.Promise} A promise that is resolved when the panel is + * created. + * @private + */ +MdPanelRef.prototype._createPanel = function() { + var self = this; + + return this._$q(function(resolve, reject) { + if (!self.config.locals) { + self.config.locals = {}; + } + + self.config.locals.mdPanelRef = self; + self._$mdCompiler.compile(self.config) + .then(function(compileData) { + self.panelContainer = compileData.link(self.config['scope']); + getElement(self.config['attachTo']).append(self.panelContainer); + + if (self.config['disableParentScroll']) { + self._restoreScroll = self._$mdUtil.disableScrollAround( + null, + self.panelContainer, + { disableScrollMask: true } + ); + } + + self.panelEl = angular.element( + self.panelContainer[0].querySelector('.md-panel')); + + // Add a custom CSS class to the panel element. + if (self.config['panelClass']) { + self.panelEl.addClass(self.config['panelClass']); + } + + // Handle click and touch events for the panel container. + if (self.config['propagateContainerEvents']) { + self.panelContainer.css('pointer-events', 'none'); + } + + // Panel may be outside the $rootElement, tell ngAnimate to animate + // regardless. + if (self._$animate.pin) { + self._$animate.pin(self.panelContainer, + getElement(self.config['attachTo'])); + } + + self._configureTrapFocus(); + self._addStyles().then(function() { + resolve(self); + }, reject); + }, reject); + }); +}; + + +/** + * Adds the styles for the panel, such as positioning and z-index. + * @returns {!angular.$q.Promise} + * @private + */ +MdPanelRef.prototype._addStyles = function() { + var self = this; + return this._$q(function(resolve) { + self.panelContainer.css('z-index', self.config['zIndex']); + self.panelEl.css('z-index', self.config['zIndex'] + 1); + + var hideAndResolve = function() { + // Remove left: -9999px and add hidden class. + self.panelEl.css('left', ''); + self.panelContainer.addClass(MD_PANEL_HIDDEN); + resolve(self); + }; + + if (self.config['fullscreen']) { + self.panelEl.addClass('_md-panel-fullscreen'); + hideAndResolve(); + return; // Don't setup positioning. + } + + var positionConfig = self.config['position']; + if (!positionConfig) { + hideAndResolve(); + return; // Don't setup positioning. + } + + // Wait for angular to finish processing the template, then position it + // correctly. This is necessary so that the panel will have a defined height + // and width. + self._$rootScope['$$postDigest'](function() { + self._updatePosition(true); + resolve(self); + }); + }); +}; + + +/** + * Updates the position configuration of a panel + * @param {!MdPanelPosition} position + */ +MdPanelRef.prototype.updatePosition = function(position) { + if (!this.panelContainer) { + throw new Error('Panel does not exist yet. Call open() or attach().'); + } + + this.config['position'] = position; + this._updatePosition(); +}; + + +/** + * Calculates and updates the position of the panel. + * @param {boolean=} init + * @private + */ +MdPanelRef.prototype._updatePosition = function(init) { + var positionConfig = this.config['position']; + + if (positionConfig) { + positionConfig._setPanelPosition(this.panelEl); + + // Hide the panel now that position is known. + if (init) { + this.panelContainer.addClass(MD_PANEL_HIDDEN); + } + + this.panelEl.css( + MdPanelPosition.absPosition.TOP, + positionConfig.getTop() + ); + this.panelEl.css( + MdPanelPosition.absPosition.BOTTOM, + positionConfig.getBottom() + ); + this.panelEl.css( + MdPanelPosition.absPosition.LEFT, + positionConfig.getLeft() + ); + this.panelEl.css( + MdPanelPosition.absPosition.RIGHT, + positionConfig.getRight() + ); + + // Use the vendor prefixed version of transform. + var prefixedTransform = this._$mdConstant.CSS.TRANSFORM; + this.panelEl.css(prefixedTransform, positionConfig.getTransform()); + } +}; + + +/** + * Focuses on the panel or the first focus target. + * @private + */ +MdPanelRef.prototype._focusOnOpen = function() { + if (this.config['focusOnOpen']) { + // Wait for the template to finish rendering to guarantee md-autofocus has + // finished adding the class md-autofocus, otherwise the focusable element + // isn't available to focus. + var self = this; + this._$rootScope['$$postDigest'](function() { + var target = self._$mdUtil.findFocusTarget(self.panelEl) || + self.panelEl; + target.focus(); + }); + } +}; + + +/** + * Shows the backdrop. + * @returns {!angular.$q.Promise} A promise that is resolved when the backdrop + * is created and attached. + * @private + */ +MdPanelRef.prototype._createBackdrop = function() { + if (this.config.hasBackdrop) { + if (!this._backdropRef) { + var backdropAnimation = this._$mdPanel.newPanelAnimation() + .openFrom(this.config.attachTo) + .withAnimation({ + open: '_md-opaque-enter', + close: '_md-opaque-leave' + }); + var backdropConfig = { + animation: backdropAnimation, + attachTo: this.config.attachTo, + focusOnOpen: false, + panelClass: '_md-panel-backdrop', + zIndex: this.config.zIndex - 1 + }; + this._backdropRef = this._$mdPanel.create(backdropConfig); + } + if (!this._backdropRef.isAttached) { + return this._backdropRef.attach(); + } + } +}; + + +/** + * Listen for escape keys and outside clicks to auto close. + * @private + */ +MdPanelRef.prototype._addEventListeners = function() { + this._configureEscapeToClose(); + this._configureClickOutsideToClose(); + this._configureScrollListener(); +}; + + +/** + * Remove event listeners added in _addEventListeners. + * @private + */ +MdPanelRef.prototype._removeEventListeners = function() { + this._removeListeners && this._removeListeners.forEach(function(removeFn) { + removeFn(); + }); + this._removeListeners = []; +}; + + +/** + * Setup the escapeToClose event listeners. + * @private + */ +MdPanelRef.prototype._configureEscapeToClose = function() { + if (this.config['escapeToClose']) { + var parentTarget = getElement(this.config['attachTo']); + var self = this; + + var keyHandlerFn = function(ev) { + if (ev.keyCode === self._$mdConstant.KEY_CODE.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + + self.close(); + } + }; + + // Add keydown listeners + this.panelContainer.on('keydown', keyHandlerFn); + parentTarget.on('keydown', keyHandlerFn); + + // Queue remove listeners function + this._removeListeners.push(function() { + self.panelContainer.off('keydown', keyHandlerFn); + parentTarget.off('keydown', keyHandlerFn); + }); + } +}; + + +/** + * Setup the clickOutsideToClose event listeners. + * @private + */ +MdPanelRef.prototype._configureClickOutsideToClose = function() { + if (this.config['clickOutsideToClose']) { + var target = this.panelContainer; + var sourceElem; + + // Keep track of the element on which the mouse originally went down + // so that we can only close the backdrop when the 'click' started on it. + // A simple 'click' handler does not work, + // it sets the target object as the element the mouse went down on. + var mousedownHandler = function(ev) { + sourceElem = ev.target; + }; + + // We check if our original element and the target is the backdrop + // because if the original was the backdrop and the target was inside the + // panel we don't want to panel to close. + var self = this; + var mouseupHandler = function(ev) { + if (sourceElem === target[0] && ev.target === target[0]) { + ev.stopPropagation(); + ev.preventDefault(); + + self.close(); + } + }; + + // Add listeners + target.on('mousedown', mousedownHandler); + target.on('mouseup', mouseupHandler); + + // Queue remove listeners function + this._removeListeners.push(function() { + target.off('mousedown', mousedownHandler); + target.off('mouseup', mouseupHandler); + }); + } +}; + + +/** + * Configures the listeners for updating the panel position on scroll. + * @private +*/ +MdPanelRef.prototype._configureScrollListener = function() { + var updatePosition = angular.bind(this, this._updatePosition); + var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition); + var self = this; + + var onScroll = function() { + if (!self.config['disableParentScroll']) { + debouncedUpdatePosition(); + } + }; + + // Add listeners. + this._$window.addEventListener('scroll', onScroll, true); + + // Queue remove listeners function. + this._removeListeners.push(function() { + self._$window.removeEventListener('scroll', onScroll, true); + }); +}; + + +/** + * Setup the focus traps. These traps will wrap focus when tabbing past the + * panel. When shift-tabbing, the focus will stick in place. + * @private + */ +MdPanelRef.prototype._configureTrapFocus = function() { + // Focus doesn't remain instead of the panel without this. + this.panelEl.attr('tabIndex', '-1'); + if (this.config['trapFocus']) { + var element = this.panelEl; + // Set up elements before and after the panel to capture focus and + // redirect back into the panel. + this._topFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0]; + this._bottomFocusTrap = FOCUS_TRAP_TEMPLATE.clone()[0]; + + // When focus is about to move out of the panel, we want to intercept it + // and redirect it back to the panel element. + var focusHandler = function() { + element.focus(); + }; + this._topFocusTrap.addEventListener('focus', focusHandler); + this._bottomFocusTrap.addEventListener('focus', focusHandler); + + // Queue remove listeners function + this._removeListeners.push(this._simpleBind(function() { + this._topFocusTrap.removeEventListener('focus', focusHandler); + this._bottomFocusTrap.removeEventListener('focus', focusHandler); + }, this)); + + // The top focus trap inserted immediately before the md-panel element (as + // a sibling). The bottom focus trap inserted immediately after the + // md-panel element (as a sibling). + element[0].parentNode.insertBefore(this._topFocusTrap, element[0]); + element.after(this._bottomFocusTrap); + } +}; + + +/** + * Animate the panel opening. + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * animated open. + * @private + */ +MdPanelRef.prototype._animateOpen = function() { + this.panelContainer.addClass('md-panel-is-showing'); + var animationConfig = this.config['animation']; + if (!animationConfig) { + // Promise is in progress, return it. + this.panelContainer.addClass('_md-panel-shown'); + return this._$q.when(this); + } + + var self = this; + return this._$q(function(resolve) { + var done = self._done(resolve, self); + var warnAndOpen = function() { + self._$log.warn( + 'MdPanel Animations failed. Showing panel without animating.'); + done(); + }; + + animationConfig.animateOpen(self.panelEl) + .then(done, warnAndOpen); + }); +}; + + +/** + * Animate the panel closing. + * @returns {!angular.$q.Promise} A promise that is resolved when the panel has + * animated closed. + * @private + */ +MdPanelRef.prototype._animateClose = function() { + var animationConfig = this.config['animation']; + if (!animationConfig) { + this.panelContainer.removeClass('md-panel-is-showing'); + this.panelContainer.removeClass('_md-panel-shown'); + return this._$q.when(this); + } + + var self = this; + return this._$q(function(resolve) { + var done = function() { + self.panelContainer.removeClass('md-panel-is-showing'); + resolve(self); + }; + var warnAndClose = function() { + self._$log.warn( + 'MdPanel Animations failed. Hiding panel without animating.'); + done(); + }; + + animationConfig.animateClose(self.panelEl) + .then(done, warnAndClose); + }); +}; + + +/** + * Faster, more basic than angular.bind + * http://jsperf.com/angular-bind-vs-custom-vs-native + * @param {function} callback + * @param {!Object} self + * @return {function} Callback function with a bound self. + */ +MdPanelRef.prototype._simpleBind = function(callback, self) { + return function(value) { + return callback.apply(self, value); + }; +}; + + +/** + * @param {function} callback + * @param {!Object} self + * @return {function} Callback function with a self param. + */ +MdPanelRef.prototype._done = function(callback, self) { + return function() { + callback(self); + }; +}; + + +/***************************************************************************** + * MdPanelPosition * + *****************************************************************************/ + + +/** + * Position configuration object. To use, create an MdPanelPosition with the + * desired properties, then pass the object as part of $mdPanel creation. + * + * Example: + * + * var panelPosition = new MdPanelPosition() + * .relativeTo(myButtonEl) + * .addPanelPosition( + * $mdPanel.xPosition.CENTER, + * $mdPanel.yPosition.ALIGN_TOPS + * ); + * + * $mdPanel.create({ + * position: panelPosition + * }); + * + * @param {!angular.$injector} $injector + * @final @constructor + */ +function MdPanelPosition($injector) { + /** @private @const {!angular.$window} */ + this._$window = $injector.get('$window'); + + /** @private {boolean} */ + this._isRTL = $injector.get('$mdUtil').bidi() === 'rtl'; + + /** @private {boolean} */ + this._absolute = false; + + /** @private {!angular.JQLite} */ + this._relativeToEl; + + /** @private {string} */ + this._top = ''; + + /** @private {string} */ + this._bottom = ''; + + /** @private {string} */ + this._left = ''; + + /** @private {string} */ + this._right = ''; + + /** @private {!Array} */ + this._translateX = []; + + /** @private {!Array} */ + this._translateY = []; + + /** @private {!Array<{x:string, y:string}>} */ + this._positions = []; + + /** @private {?{x:string, y:string}} */ + this._actualPosition; +} + + +/** + * Possible values of xPosition. + * @enum {string} + */ +MdPanelPosition.xPosition = { + CENTER: 'center', + ALIGN_START: 'align-start', + ALIGN_END: 'align-end', + OFFSET_START: 'offset-start', + OFFSET_END: 'offset-end' +}; + + +/** + * Possible values of yPosition. + * @enum {string} + */ +MdPanelPosition.yPosition = { + CENTER: 'center', + ALIGN_TOPS: 'align-tops', + ALIGN_BOTTOMS: 'align-bottoms', + ABOVE: 'above', + BELOW: 'below' +}; + + +/** + * Possible values of absolute position. + * @enum {string} + */ +MdPanelPosition.absPosition = { + TOP: 'top', + RIGHT: 'right', + BOTTOM: 'bottom', + LEFT: 'left' +}; + + +/** + * Sets absolute positioning for the panel. + * @return {!MdPanelPosition} + */ +MdPanelPosition.prototype.absolute = function() { + this._absolute = true; + return this; +}; + +/** + * Sets the value of a position for the panel. Clears any previously set + * position. + * @param {string} position Position to set + * @param {string=} value Value of the position. Defaults to '0'. + * @returns {!MdPanelPosition} + * @private + */ +MdPanelPosition.prototype._setPosition = function(position, value) { + if (position === MdPanelPosition.absPosition.RIGHT || + position === MdPanelPosition.absPosition.LEFT) { + this._left = this._right = ''; + } else if ( + position === MdPanelPosition.absPosition.BOTTOM || + position === MdPanelPosition.absPosition.TOP) { + this._top = this._bottom = ''; + } else { + var positions = Object.keys(MdPanelPosition.absPosition).join() + .toLowerCase(); + + throw new Error('Position must be one of ' + positions + '.'); + } + + this['_' + position] = angular.isString(value) ? value : '0'; + + return this; +}; + + +/** + * Sets the value of `top` for the panel. Clears any previously set vertical + * position. + * @param {string=} top Value of `top`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.top = function(top) { + return this._setPosition(MdPanelPosition.absPosition.TOP, top); +}; + + +/** + * Sets the value of `bottom` for the panel. Clears any previously set vertical + * position. + * @param {string=} bottom Value of `bottom`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.bottom = function(bottom) { + return this._setPosition(MdPanelPosition.absPosition.BOTTOM, bottom); +}; + + +/** + * Sets the panel to the start of the page - `left` if `ltr` or `right` for + * `rtl`. Clears any previously set horizontal position. + * @param {string=} start Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.start = function(start) { + var position = this._isRTL ? MdPanelPosition.absPosition.RIGHT : MdPanelPosition.absPosition.LEFT; + return this._setPosition(position, start); +}; + + +/** + * Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`. + * Clears any previously set horizontal position. + * @param {string=} end Value of position. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.end = function(end) { + var position = this._isRTL ? MdPanelPosition.absPosition.LEFT : MdPanelPosition.absPosition.RIGHT; + return this._setPosition(position, end); +}; + + +/** + * Sets the value of `left` for the panel. Clears any previously set + * horizontal position. + * @param {string=} left Value of `left`. Defaults to '0'. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.left = function(left) { + return this._setPosition(MdPanelPosition.absPosition.LEFT, left); +}; + + +/** + * Sets the value of `right` for the panel. Clears any previously set + * horizontal position. + * @param {string=} right Value of `right`. Defaults to '0'. + * @returns {!MdPanelPosition} +*/ +MdPanelPosition.prototype.right = function(right) { + return this._setPosition(MdPanelPosition.absPosition.RIGHT, right); +}; + + +/** + * Centers the panel horizontally in the viewport. Clears any previously set + * horizontal position. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.centerHorizontally = function() { + this._left = '50%'; + this._right = ''; + this._translateX = ['-50%']; + return this; +}; + + +/** + * Centers the panel vertically in the viewport. Clears any previously set + * vertical position. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.centerVertically = function() { + this._top = '50%'; + this._bottom = ''; + this._translateY = ['-50%']; + return this; +}; + + +/** + * Centers the panel horizontally and vertically in the viewport. This is + * equivalent to calling both `centerHorizontally` and `centerVertically`. + * Clears any previously set horizontal and vertical positions. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.center = function() { + return this.centerHorizontally().centerVertically(); +}; + + +/** + * Sets element for relative positioning. + * @param {string|!Element|!angular.JQLite} element Query selector, DOM element, + * or angular element to set the panel relative to. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.relativeTo = function(element) { + this._absolute = false; + this._relativeToEl = getElement(element); + return this; +}; + + +/** + * Sets the x and y positions for the panel relative to another element. + * @param {string} xPosition must be one of the MdPanelPosition.xPosition + * values. + * @param {string} yPosition must be one of the MdPanelPosition.yPosition + * values. + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) { + if (!this._relativeToEl) { + throw new Error('addPanelPosition can only be used with relative ' + + 'positioning. Set relativeTo first.'); + } + + this._validateXPosition(xPosition); + this._validateYPosition(yPosition); + + this._positions.push({ + x: xPosition, + y: yPosition, + }); + return this; +}; + + +/** + * Ensures that yPosition is a valid position name. Throw an exception if not. + * @param {string} yPosition + */ +MdPanelPosition.prototype._validateYPosition = function(yPosition) { + // empty is ok + if (yPosition == null) { + return; + } + + var positionKeys = Object.keys(MdPanelPosition.yPosition); + var positionValues = []; + for (var key, i = 0; key = positionKeys[i]; i++) { + var position = MdPanelPosition.yPosition[key]; + positionValues.push(position); + + if (position === yPosition) { + return; + } + } + + throw new Error('Panel y position only accepts the following values:\n' + + positionValues.join(' | ')); +}; + + +/** + * Ensures that xPosition is a valid position name. Throw an exception if not. + * @param {string} xPosition + */ +MdPanelPosition.prototype._validateXPosition = function(xPosition) { + // empty is ok + if (xPosition == null) { + return; + } + + var positionKeys = Object.keys(MdPanelPosition.xPosition); + var positionValues = []; + for (var key, i = 0; key = positionKeys[i]; i++) { + var position = MdPanelPosition.xPosition[key]; + positionValues.push(position); + if (position === xPosition) { + return; + } + } + + throw new Error('Panel x Position only accepts the following values:\n' + + positionValues.join(' | ')); +}; + + +/** + * Sets the value of the offset in the x-direction. This will add to any + * previously set offsets. + * @param {string} offsetX + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.withOffsetX = function(offsetX) { + this._translateX.push(offsetX); + return this; +}; + + +/** + * Sets the value of the offset in the y-direction. This will add to any + * previously set offsets. + * @param {string} offsetY + * @returns {!MdPanelPosition} + */ +MdPanelPosition.prototype.withOffsetY = function(offsetY) { + this._translateY.push(offsetY); + return this; +}; + + +/** + * Gets the value of `top` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getTop = function() { + return this._top; +}; + + +/** + * Gets the value of `bottom` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getBottom = function() { + return this._bottom; +}; + + +/** + * Gets the value of `left` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getLeft = function() { + return this._left; +}; + + +/** + * Gets the value of `right` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getRight = function() { + return this._right; +}; + + +/** + * Gets the value of `transform` for the panel. + * @returns {string} + */ +MdPanelPosition.prototype.getTransform = function() { + var translateX = this._reduceTranslateValues('translateX', this._translateX); + var translateY = this._reduceTranslateValues('translateY', this._translateY); + + // It's important to trim the result, because the browser will ignore the set + // operation if the string contains only whitespace. + return (translateX + ' ' + translateY).trim(); +}; + +/** + * True if the panel is completely on-screen with this positioning; false + * otherwise. + * @param {!angular.JQLite} panelEl + * @return {boolean} + */ +MdPanelPosition.prototype._isOnscreen = function(panelEl) { + // this works because we always use fixed positioning for the panel, + // which is relative to the viewport. + // TODO(gmoothart): take into account _translateX and _translateY to the + // extent feasible. + + var left = parseInt(this.getLeft()); + var top = parseInt(this.getTop()); + var right = left + panelEl[0].offsetWidth; + var bottom = top + panelEl[0].offsetHeight; + + return (left >= 0) && + (top >= 0) && + (bottom <= this._$window.innerHeight) && + (right <= this._$window.innerWidth); +}; + + +/** + * Gets the first x/y position that can fit on-screen. + * @returns {{x: string, y: string}} + */ +MdPanelPosition.prototype.getActualPosition = function() { + return this._actualPosition; +}; + + +/** + * Reduces a list of translate values to a string that can be used within + * transform. + * @param {string} translateFn + * @param {!Array} values + * @returns {string} + * @private + */ +MdPanelPosition.prototype._reduceTranslateValues = + function(translateFn, values) { + return values.map(function(translation) { + return translateFn + '(' + translation + ')'; + }).join(' '); + }; + + +/** + * Sets the panel position based on the created panel element and best x/y + * positioning. + * @param {!angular.JQLite} panelEl + * @private + */ +MdPanelPosition.prototype._setPanelPosition = function(panelEl) { + // Only calculate the position if necessary. + if (this._absolute) { + return; + } + + if (this._actualPosition) { + this._calculatePanelPosition(panelEl, this._actualPosition); + return; + } + + for (var i = 0; i < this._positions.length; i++) { + this._actualPosition = this._positions[i]; + this._calculatePanelPosition(panelEl, this._actualPosition); + if (this._isOnscreen(panelEl)) { + break; + } + } +}; + + +/** + * Switches between 'start' and 'end'. + * @param {string} position Horizontal position of the panel + * @returns {string} Reversed position + * @private + */ +MdPanelPosition.prototype._reverseXPosition = function(position) { + if (position === MdPanelPosition.xPosition.CENTER) { + return; + } + + var start = 'start'; + var end = 'end'; + + return position.indexOf(start) > -1 ? position.replace(start, end) : position.replace(end, start); +}; + + +/** + * Handles horizontal positioning in rtl or ltr environments. + * @param {string} position Horizontal position of the panel + * @returns {string} The correct position according the page direction + * @private + */ +MdPanelPosition.prototype._bidi = function(position) { + return this._isRTL ? this._reverseXPosition(position) : position; +}; + + +/** + * Calculates the panel position based on the created panel element and the + * provided positioning. + * @param {!angular.JQLite} panelEl + * @param {!{x:string, y:string}} position + * @private + */ +MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) { + + var panelBounds = panelEl[0].getBoundingClientRect(); + var panelWidth = panelBounds.width; + var panelHeight = panelBounds.height; + + var targetBounds = this._relativeToEl[0].getBoundingClientRect(); + + var targetLeft = targetBounds.left; + var targetRight = targetBounds.right; + var targetWidth = targetBounds.width; + + switch (this._bidi(position.x)) { + case MdPanelPosition.xPosition.OFFSET_START: + this._left = targetLeft - panelWidth + 'px'; + break; + case MdPanelPosition.xPosition.ALIGN_END: + this._left = targetRight - panelWidth + 'px'; + break; + case MdPanelPosition.xPosition.CENTER: + var left = targetLeft + (0.5 * targetWidth) - (0.5 * panelWidth); + this._left = left + 'px'; + break; + case MdPanelPosition.xPosition.ALIGN_START: + this._left = targetLeft + 'px'; + break; + case MdPanelPosition.xPosition.OFFSET_END: + this._left = targetRight + 'px'; + break; + } + + var targetTop = targetBounds.top; + var targetBottom = targetBounds.bottom; + var targetHeight = targetBounds.height; + + switch (position.y) { + case MdPanelPosition.yPosition.ABOVE: + this._top = targetTop - panelHeight + 'px'; + break; + case MdPanelPosition.yPosition.ALIGN_BOTTOMS: + this._top = targetBottom - panelHeight + 'px'; + break; + case MdPanelPosition.yPosition.CENTER: + var top = targetTop + (0.5 * targetHeight) - (0.5 * panelHeight); + this._top = top + 'px'; + break; + case MdPanelPosition.yPosition.ALIGN_TOPS: + this._top = targetTop + 'px'; + break; + case MdPanelPosition.yPosition.BELOW: + this._top = targetBottom + 'px'; + break; + } +}; + + +/***************************************************************************** + * MdPanelAnimation * + *****************************************************************************/ + + +/** + * Animation configuration object. To use, create an MdPanelAnimation with the + * desired properties, then pass the object as part of $mdPanel creation. + * + * Example: + * + * var panelAnimation = new MdPanelAnimation() + * .openFrom(myButtonEl) + * .closeTo('.my-button') + * .withAnimation($mdPanel.animation.SCALE); + * + * $mdPanel.create({ + * animation: panelAnimation + * }); + * + * @param {!angular.$injector} $injector + * @final @constructor + */ +function MdPanelAnimation($injector) { + /** @private @const {!angular.$mdUtil} */ + this._$mdUtil = $injector.get('$mdUtil'); + + /** + * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}| + * undefined} + */ + this._openFrom; + + /** + * @private {{element: !angular.JQLite|undefined, bounds: !DOMRect}| + * undefined} + */ + this._closeTo; + + /** @private {string|{open: string, close: string}} */ + this._animationClass = ''; +} + + +/** + * Possible default animations. + * @enum {string} + */ +MdPanelAnimation.animation = { + SLIDE: 'md-panel-animate-slide', + SCALE: 'md-panel-animate-scale', + FADE: 'md-panel-animate-fade' +}; + + +/** + * Specifies where to start the open animation. `openFrom` accepts a + * click event object, query selector, DOM element, or a Rect object that + * is used to determine the bounds. When passed a click event, the location + * of the click will be used as the position to start the animation. + * @param {string|!Element|!Event|{top: number, left: number}} openFrom + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.openFrom = function(openFrom) { + // Check if 'openFrom' is an Event. + openFrom = openFrom.target ? openFrom.target : openFrom; + + this._openFrom = this._getPanelAnimationTarget(openFrom); + + if (!this._closeTo) { + this._closeTo = this._openFrom; + } + return this; +}; + + +/** + * Specifies where to animate the panel close. `closeTo` accepts a + * query selector, DOM element, or a Rect object that is used to determine + * the bounds. + * @param {string|!Element|{top: number, left: number}} closeTo + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.closeTo = function(closeTo) { + this._closeTo = this._getPanelAnimationTarget(closeTo); + return this; +}; + + +/** + * Returns the element and bounds for the animation target. + * @param {string|!Element|{top: number, left: number}} location + * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}} + * @private + */ +MdPanelAnimation.prototype._getPanelAnimationTarget = function(location) { + if (angular.isDefined(location.top) || angular.isDefined(location.left)) { + return { + element: undefined, + bounds: { + top: location.top || 0, + left: location.left || 0 + } + }; + } else { + return this._getBoundingClientRect(getElement(location)); + } +}; + + +/** + * Specifies the animation class. + * + * There are several default animations that can be used: + * (MdPanelAnimation.animation) + * SLIDE: The panel slides in and out from the specified + * elements. + * SCALE: The panel scales in and out. + * FADE: The panel fades in and out. + * + * @param {string|{open: string, close: string}} cssClass + * @returns {!MdPanelAnimation} + */ +MdPanelAnimation.prototype.withAnimation = function(cssClass) { + this._animationClass = cssClass; + return this; +}; + + +/** + * Animate the panel open. + * @param {!angular.JQLite} panelEl + * @returns {!angular.$q.Promise} A promise that is resolved when the open + * animation is complete. + */ +MdPanelAnimation.prototype.animateOpen = function(panelEl) { + var animator = this._$mdUtil.dom.animator; + + this._fixBounds(panelEl); + var animationOptions = {}; + + // Include the panel transformations when calculating the animations. + var panelTransform = panelEl[0].style.transform || ''; + + var openFrom = animator.toTransformCss(panelTransform); + var openTo = animator.toTransformCss(panelTransform); + + switch (this._animationClass) { + case MdPanelAnimation.animation.SLIDE: + // Slide should start with opacity: 1. + panelEl.css('opacity', '1'); + + animationOptions = { + transitionInClass: '_md-panel-animate-enter' + }; + + var openSlide = animator.calculateSlideToOrigin( + panelEl, this._openFrom) || ''; + openFrom = animator.toTransformCss(openSlide + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.SCALE: + animationOptions = { + transitionInClass: '_md-panel-animate-enter' + }; + + var openScale = animator.calculateZoomToOrigin( + panelEl, this._openFrom) || ''; + openFrom = animator.toTransformCss(openScale + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.FADE: + animationOptions = { + transitionInClass: '_md-panel-animate-enter' + }; + break; + + default: + if (angular.isString(this._animationClass)) { + animationOptions = { + transitionInClass: this._animationClass + }; + } else { + animationOptions = { + transitionInClass: this._animationClass['open'], + transitionOutClass: this._animationClass['close'], + }; + } + } + + return animator + .translate3d(panelEl, openFrom, openTo, animationOptions); +}; + + +/** + * Animate the panel close. + * @param {!angular.JQLite} panelEl + * @returns {!angular.$q.Promise} A promise that resolves when the close + * animation is complete. + */ +MdPanelAnimation.prototype.animateClose = function(panelEl) { + var animator = this._$mdUtil.dom.animator; + var reverseAnimationOptions = {}; + + // Include the panel transformations when calculating the animations. + var panelTransform = panelEl[0].style.transform || ''; + + var closeFrom = animator.toTransformCss(panelTransform); + var closeTo = animator.toTransformCss(panelTransform); + + switch (this._animationClass) { + case MdPanelAnimation.animation.SLIDE: + // Slide should start with opacity: 1. + panelEl.css('opacity', '1'); + reverseAnimationOptions = { + transitionInClass: '_md-panel-animate-leave' + }; + + var closeSlide = animator.calculateSlideToOrigin( + panelEl, this._closeTo) || ''; + closeTo = animator.toTransformCss(closeSlide + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.SCALE: + reverseAnimationOptions = { + transitionInClass: '_md-panel-animate-scale-out _md-panel-animate-leave' + }; + + var closeScale = animator.calculateZoomToOrigin( + panelEl, this._closeTo) || ''; + closeTo = animator.toTransformCss(closeScale + ' ' + panelTransform); + break; + + case MdPanelAnimation.animation.FADE: + reverseAnimationOptions = { + transitionInClass: '_md-panel-animate-fade-out _md-panel-animate-leave' + }; + break; + + default: + if (angular.isString(this._animationClass)) { + reverseAnimationOptions = { + transitionOutClass: this._animationClass + }; + } else { + reverseAnimationOptions = { + transitionInClass: this._animationClass['close'], + transitionOutClass: this._animationClass['open'] + }; + } + } + + return animator + .translate3d(panelEl, closeFrom, closeTo, reverseAnimationOptions); +}; + + +/** + * Set the height and width to match the panel if not provided. + * @param {!angular.JQLite} panelEl + * @private + */ +MdPanelAnimation.prototype._fixBounds = function(panelEl) { + var panelWidth = panelEl[0].offsetWidth; + var panelHeight = panelEl[0].offsetHeight; + + if (this._openFrom && this._openFrom.bounds.height == null) { + this._openFrom.bounds.height = panelHeight; + } + if (this._openFrom && this._openFrom.bounds.width == null) { + this._openFrom.bounds.width = panelWidth; + } + if (this._closeTo && this._closeTo.bounds.height == null) { + this._closeTo.bounds.height = panelHeight; + } + if (this._closeTo && this._closeTo.bounds.width == null) { + this._closeTo.bounds.width = panelWidth; + } +}; + + +/** + * Identify the bounding RECT for the target element. + * @param {!angular.JQLite} element + * @returns {{element: !angular.JQLite|undefined, bounds: !DOMRect}} + * @private + */ +MdPanelAnimation.prototype._getBoundingClientRect = function(element) { + if (element instanceof angular.element) { + return { + element: element, + bounds: element[0].getBoundingClientRect() + }; + } +}; + + +/***************************************************************************** + * Util Methods * + *****************************************************************************/ + +/** + * Returns the angular element associated with a css selector or element. + * @param el {string|!angular.JQLite|!Element} + * @returns {!angular.JQLite} + */ +function getElement(el) { + var queryResult = angular.isString(el) ? + document.querySelector(el) : el; + return angular.element(queryResult); +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.progressCircular + * @description Module for a circular progressbar + */ + +angular.module('material.components.progressCircular', ['material.core']); + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.progressLinear + * @description Linear Progress module! + */ +MdProgressLinearDirective.$inject = ["$mdTheming", "$mdUtil", "$log"]; +angular.module('material.components.progressLinear', [ + 'material.core' +]) + .directive('mdProgressLinear', MdProgressLinearDirective); + +/** + * @ngdoc directive + * @name mdProgressLinear + * @module material.components.progressLinear + * @restrict E + * + * @description + * The linear progress directive is used to make loading content + * in your app as delightful and painless as possible by minimizing + * the amount of visual change a user sees before they can view + * and interact with content. + * + * Each operation should only be represented by one activity indicator + * For example: one refresh operation should not display both a + * refresh bar and an activity circle. + * + * For operations where the percentage of the operation completed + * can be determined, use a determinate indicator. They give users + * a quick sense of how long an operation will take. + * + * For operations where the user is asked to wait a moment while + * something finishes up, and it’s not necessary to expose what's + * happening behind the scenes and how long it will take, use an + * indeterminate indicator. + * + * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query. + * + * Note: if the `md-mode` value is set as undefined or specified as 1 of the four (4) valid modes, then `indeterminate` + * will be auto-applied as the mode. + * + * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute. If `value=""` is also specified, however, + * then `md-mode="determinate"` would be auto-injected instead. + * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0 + * @param {number=} md-buffer-value In the buffer mode, this number represents the percentage of the secondary progress bar. Default: 0 + * @param {boolean=} ng-disabled Determines whether to disable the progress element. + * + * @usage + * + * + * + * + * + * + * + * + * + * + * + */ +function MdProgressLinearDirective($mdTheming, $mdUtil, $log) { + var MODE_DETERMINATE = "determinate"; + var MODE_INDETERMINATE = "indeterminate"; + var MODE_BUFFER = "buffer"; + var MODE_QUERY = "query"; + var DISABLED_CLASS = "_md-progress-linear-disabled"; + + return { + restrict: 'E', + template: '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + compile: compile + }; + + function compile(tElement, tAttrs, transclude) { + tElement.attr('aria-valuemin', 0); + tElement.attr('aria-valuemax', 100); + tElement.attr('role', 'progressbar'); + + return postLink; + } + function postLink(scope, element, attr) { + $mdTheming(element); + + var lastMode; + var isDisabled = attr.hasOwnProperty('disabled'); + var toVendorCSS = $mdUtil.dom.animator.toCss; + var bar1 = angular.element(element[0].querySelector('.md-bar1')); + var bar2 = angular.element(element[0].querySelector('.md-bar2')); + var container = angular.element(element[0].querySelector('.md-container')); + + element + .attr('md-mode', mode()) + .toggleClass(DISABLED_CLASS, isDisabled); + + validateMode(); + watchAttributes(); + + /** + * Watch the value, md-buffer-value, and md-mode attributes + */ + function watchAttributes() { + attr.$observe('value', function(value) { + var percentValue = clamp(value); + element.attr('aria-valuenow', percentValue); + + if (mode() != MODE_QUERY) animateIndicator(bar2, percentValue); + }); + + attr.$observe('mdBufferValue', function(value) { + animateIndicator(bar1, clamp(value)); + }); + + attr.$observe('disabled', function(value) { + if (value === true || value === false) { + isDisabled = !!value; + } else { + isDisabled = angular.isDefined(value); + } + + element.toggleClass(DISABLED_CLASS, isDisabled); + container.toggleClass(lastMode, !isDisabled); + }); + + attr.$observe('mdMode', function(mode) { + if (lastMode) container.removeClass( lastMode ); + + switch( mode ) { + case MODE_QUERY: + case MODE_BUFFER: + case MODE_DETERMINATE: + case MODE_INDETERMINATE: + container.addClass( lastMode = "md-mode-" + mode ); + break; + default: + container.addClass( lastMode = "md-mode-" + MODE_INDETERMINATE ); + break; + } + }); + } + + /** + * Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified + */ + function validateMode() { + if ( angular.isUndefined(attr.mdMode) ) { + var hasValue = angular.isDefined(attr.value); + var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE; + var info = "Auto-adding the missing md-mode='{0}' to the ProgressLinear element"; + + //$log.debug( $mdUtil.supplant(info, [mode]) ); + + element.attr("md-mode", mode); + attr.mdMode = mode; + } + } + + /** + * Is the md-mode a valid option? + */ + function mode() { + var value = (attr.mdMode || "").trim(); + if ( value ) { + switch(value) { + case MODE_DETERMINATE: + case MODE_INDETERMINATE: + case MODE_BUFFER: + case MODE_QUERY: + break; + default: + value = MODE_INDETERMINATE; + break; + } + } + return value; + } + + /** + * Manually set CSS to animate the Determinate indicator based on the specified + * percentage value (0-100). + */ + function animateIndicator(target, value) { + if ( isDisabled || !mode() ) return; + + var to = $mdUtil.supplant("translateX({0}%) scale({1},1)", [ (value-100)/2, value/100 ]); + var styles = toVendorCSS({ transform : to }); + angular.element(target).css( styles ); + } + } + + /** + * Clamps the value to be between 0 and 100. + * @param {number} value The value to clamp. + * @returns {number} + */ + function clamp(value) { + return Math.max(0, Math.min(value || 0, 100)); + } +} + + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.radioButton + * @description radioButton module! + */ +mdRadioGroupDirective.$inject = ["$mdUtil", "$mdConstant", "$mdTheming", "$timeout"]; +mdRadioButtonDirective.$inject = ["$mdAria", "$mdUtil", "$mdTheming"]; +angular.module('material.components.radioButton', [ + 'material.core' +]) + .directive('mdRadioGroup', mdRadioGroupDirective) + .directive('mdRadioButton', mdRadioButtonDirective); + +/** + * @ngdoc directive + * @module material.components.radioButton + * @name mdRadioGroup + * + * @restrict E + * + * @description + * The `` directive identifies a grouping + * container for the 1..n grouped radio buttons; specified using nested + * `` tags. + * + * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application) + * the radio button is in the accent color by default. The primary color palette may be used with + * the `md-primary` class. + * + * Note: `` and `` handle tabindex differently + * than the native `` controls. Whereas the native controls + * force the user to tab through all the radio buttons, `` + * is focusable, and by default the ``s are not. + * + * @param {string} ng-model Assignable angular expression to data-bind to. + * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects. + * + * @usage + * + * + * + * + * + * {{ d.label }} + * + * + * + * + * + * + */ +function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) { + RadioGroupController.prototype = createRadioGroupControllerProto(); + + return { + restrict: 'E', + controller: ['$element', RadioGroupController], + require: ['mdRadioGroup', '?ngModel'], + link: { pre: linkRadioGroup } + }; + + function linkRadioGroup(scope, element, attr, ctrls) { + element.addClass('_md'); // private md component indicator for styling + $mdTheming(element); + + var rgCtrl = ctrls[0]; + var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel(); + + rgCtrl.init(ngModelCtrl); + + scope.mouseActive = false; + + element + .attr({ + 'role': 'radiogroup', + 'tabIndex': element.attr('tabindex') || '0' + }) + .on('keydown', keydownListener) + .on('mousedown', function(event) { + scope.mouseActive = true; + $timeout(function() { + scope.mouseActive = false; + }, 100); + }) + .on('focus', function() { + if(scope.mouseActive === false) { + rgCtrl.$element.addClass('md-focused'); + } + }) + .on('blur', function() { + rgCtrl.$element.removeClass('md-focused'); + }); + + /** + * + */ + function setFocus() { + if (!element.hasClass('md-focused')) { element.addClass('md-focused'); } + } + + /** + * + */ + function keydownListener(ev) { + var keyCode = ev.which || ev.keyCode; + + // Only listen to events that we originated ourselves + // so that we don't trigger on things like arrow keys in + // inputs. + + if (keyCode != $mdConstant.KEY_CODE.ENTER && + ev.currentTarget != ev.target) { + return; + } + + switch (keyCode) { + case $mdConstant.KEY_CODE.LEFT_ARROW: + case $mdConstant.KEY_CODE.UP_ARROW: + ev.preventDefault(); + rgCtrl.selectPrevious(); + setFocus(); + break; + + case $mdConstant.KEY_CODE.RIGHT_ARROW: + case $mdConstant.KEY_CODE.DOWN_ARROW: + ev.preventDefault(); + rgCtrl.selectNext(); + setFocus(); + break; + + case $mdConstant.KEY_CODE.ENTER: + var form = angular.element($mdUtil.getClosest(element[0], 'form')); + if (form.length > 0) { + form.triggerHandler('submit'); + } + break; + } + + } + } + + function RadioGroupController($element) { + this._radioButtonRenderFns = []; + this.$element = $element; + } + + function createRadioGroupControllerProto() { + return { + init: function(ngModelCtrl) { + this._ngModelCtrl = ngModelCtrl; + this._ngModelCtrl.$render = angular.bind(this, this.render); + }, + add: function(rbRender) { + this._radioButtonRenderFns.push(rbRender); + }, + remove: function(rbRender) { + var index = this._radioButtonRenderFns.indexOf(rbRender); + if (index !== -1) { + this._radioButtonRenderFns.splice(index, 1); + } + }, + render: function() { + this._radioButtonRenderFns.forEach(function(rbRender) { + rbRender(); + }); + }, + setViewValue: function(value, eventType) { + this._ngModelCtrl.$setViewValue(value, eventType); + // update the other radio buttons as well + this.render(); + }, + getViewValue: function() { + return this._ngModelCtrl.$viewValue; + }, + selectNext: function() { + return changeSelectedButton(this.$element, 1); + }, + selectPrevious: function() { + return changeSelectedButton(this.$element, -1); + }, + setActiveDescendant: function (radioId) { + this.$element.attr('aria-activedescendant', radioId); + }, + isDisabled: function() { + return this.$element[0].hasAttribute('disabled'); + } + }; + } + /** + * Change the radio group's selected button by a given increment. + * If no button is selected, select the first button. + */ + function changeSelectedButton(parent, increment) { + // Coerce all child radio buttons into an array, then wrap then in an iterator + var buttons = $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true); + + if (buttons.count()) { + var validate = function (button) { + // If disabled, then NOT valid + return !angular.element(button).attr("disabled"); + }; + + var selected = parent[0].querySelector('md-radio-button.md-checked'); + var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first(); + + // Activate radioButton's click listener (triggerHandler won't create a real click event) + angular.element(target).triggerHandler('click'); + + + } + } + +} + +/** + * @ngdoc directive + * @module material.components.radioButton + * @name mdRadioButton + * + * @restrict E + * + * @description + * The ``directive is the child directive required to be used within `` elements. + * + * While similar to the `` directive, + * the `` directive provides ink effects, ARIA support, and + * supports use within named radio groups. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {string} ngValue Angular expression which sets the value to which the expression should + * be set when selected. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} aria-label Adds label to radio button for accessibility. + * Defaults to radio button's text. If no text content is available, a warning will be logged. + * + * @usage + * + * + * + * Label 1 + * + * + * + * Green + * + * + * + * + */ +function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) { + + var CHECKED_CSS = 'md-checked'; + + return { + restrict: 'E', + require: '^mdRadioGroup', + transclude: true, + template: '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + link: link + }; + + function link(scope, element, attr, rgCtrl) { + var lastChecked; + + $mdTheming(element); + configureAria(element, scope); + + initialize(); + + /** + * + */ + function initialize() { + if (!rgCtrl) { + throw 'RadioButton: No RadioGroupController could be found.'; + } + + rgCtrl.add(render); + attr.$observe('value', render); + + element + .on('click', listener) + .on('$destroy', function() { + rgCtrl.remove(render); + }); + } + + /** + * + */ + function listener(ev) { + if (element[0].hasAttribute('disabled') || rgCtrl.isDisabled()) return; + + scope.$apply(function() { + rgCtrl.setViewValue(attr.value, ev && ev.type); + }); + } + + /** + * Add or remove the `.md-checked` class from the RadioButton (and conditionally its parent). + * Update the `aria-activedescendant` attribute. + */ + function render() { + var checked = (rgCtrl.getViewValue() == attr.value); + if (checked === lastChecked) { + return; + } + + lastChecked = checked; + element.attr('aria-checked', checked); + + if (checked) { + markParentAsChecked(true); + element.addClass(CHECKED_CSS); + + rgCtrl.setActiveDescendant(element.attr('id')); + + } else { + markParentAsChecked(false); + element.removeClass(CHECKED_CSS); + } + + /** + * If the radioButton is inside a div, then add class so highlighting will work... + */ + function markParentAsChecked(addClass ) { + if ( element.parent()[0].nodeName != "MD-RADIO-GROUP") { + element.parent()[ !!addClass ? 'addClass' : 'removeClass'](CHECKED_CSS); + } + + } + } + + /** + * Inject ARIA-specific attributes appropriate for each radio button + */ + function configureAria( element, scope ){ + scope.ariaId = buildAriaID(); + + element.attr({ + 'id' : scope.ariaId, + 'role' : 'radio', + 'aria-checked' : 'false' + }); + + $mdAria.expectWithText(element, 'aria-label'); + + /** + * Build a unique ID for each radio button that will be used with aria-activedescendant. + * Preserve existing ID if already specified. + * @returns {*|string} + */ + function buildAriaID() { + return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() ); + } + } + } +} + +})(); +(function(){ +"use strict"; + +/** + * @ngdoc module + * @name material.components.select + */ + +/*************************************************** + + ### TODO - POST RC1 ### + - [ ] Abstract placement logic in $mdSelect service to $mdMenu service + + ***************************************************/ + +SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdConstant", "$mdTheming", "$mdAria", "$compile", "$parse"]; +SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdConstant", "$mdTheming"]; +OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil"]; +SelectProvider.$inject = ["$$interimElementProvider"]; +var SELECT_EDGE_MARGIN = 8; +var selectNextId = 0; +var CHECKBOX_SELECTION_INDICATOR = + angular.element('
    '); + +angular.module('material.components.select', [ + 'material.core', + 'material.components.backdrop' + ]) + .directive('mdSelect', SelectDirective) + .directive('mdSelectMenu', SelectMenuDirective) + .directive('mdOption', OptionDirective) + .directive('mdOptgroup', OptgroupDirective) + .directive('mdSelectHeader', SelectHeaderDirective) + .provider('$mdSelect', SelectProvider); + +/** + * @ngdoc directive + * @name mdSelect + * @restrict E + * @module material.components.select + * + * @description Displays a select box, bound to an ng-model. + * + * When the select is required and uses a floating label, then the label will automatically contain + * an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute. + * + * By default, the select will display with an underline to match other form elements. This can be + * disabled by applying the `md-no-underline` CSS class. + * + * ### Option Params + * + * When applied, `md-option-empty` will mark the option as "empty" allowing the option to clear the + * select and put it back in it's default state. You may supply this attribute on any option you + * wish, however, it is automatically applied to an option whose `value` or `ng-value` are not + * defined. + * + * **Automatically Applied** + * + * - `` + * - `` + * - `` + * - `` + * - `` + * + * **NOT Automatically Applied** + * + * - `` + * - `` + * - `` + * - `` (this evaluates to the string `"undefined"`) + * - <md-option ng-value="{{someValueThatMightBeUndefined}}"> + * + * **Note:** A value of `undefined` ***is considered a valid value*** (and does not auto-apply this + * attribute) since you may wish this to be your "Not Available" or "None" option. + * + * **Note:** Using the `value` attribute (as opposed to `ng-value`) always evaluates to a string, so + * `value="null"` will require the test `ng-if="myValue != 'null'"` rather than `ng-if="!myValue"`. + * + * @param {expression} ng-model The model! + * @param {boolean=} multiple Whether it's multiple. + * @param {expression=} md-on-close Expression to be evaluated when the select is closed. + * @param {expression=} md-on-open Expression to be evaluated when opening the select. + * Will hide the select options and show a spinner until the evaluated promise resolves. + * @param {expression=} md-selected-text Expression to be evaluated that will return a string + * to be displayed as a placeholder in the select input box when it is closed. + * @param {string=} placeholder Placeholder hint text. + * @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the + * floating label. **Note:** This attribute is only evaluated once; it is not watched. + * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or + * explicit label is present. + * @param {string=} md-container-class Class list to get applied to the `.md-select-menu-container` + * element (for custom styling). + * + * @usage + * With a placeholder (label and aria-label are added dynamically) + * + * + * + * {{ opt }} + * + * + * + * + * With an explicit label + * + * + * + * + * {{ opt }} + * + * + * + * + * With a select-header + * + * When a developer needs to put more than just a text label in the + * md-select-menu, they should use the md-select-header. + * The user can put custom HTML inside of the header and style it to their liking. + * One common use case of this would be a sticky search bar. + * + * When using the md-select-header the labels that would previously be added to the + * OptGroupDirective are ignored. + * + * + * + * + * + * Neighborhoods - + * + * {{ opt }} + * + * + * + * + * ## Selects and object equality + * When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles + * equality. Consider the following example: + * + * angular.controller('MyCtrl', function($scope) { + * $scope.users = [ + * { id: 1, name: 'Bob' }, + * { id: 2, name: 'Alice' }, + * { id: 3, name: 'Steve' } + * ]; + * $scope.selectedUser = { id: 1, name: 'Bob' }; + * }); + * + * + *
    + * + * {{ user.name }} + * + *
    + *
    + * + * At first one might expect that the select should be populated with "Bob" as the selected user. However, + * this is not true. To determine whether something is selected, + * `ngModelController` is looking at whether `$scope.selectedUser == (any user in $scope.users);`; + * + * Javascript's `==` operator does not check for deep equality (ie. that all properties + * on the object are the same), but instead whether the objects are *the same object in memory*. + * In this case, we have two instances of identical objects, but they exist in memory as unique + * entities. Because of this, the select will have no value populated for a selected user. + * + * To get around this, `ngModelController` provides a `track by` option that allows us to specify a different + * expression which will be used for the equality operator. As such, we can update our `html` to + * make use of this by specifying the `ng-model-options="{trackBy: '$value.id'}"` on the `md-select` + * element. This converts our equality expression to be + * `$scope.selectedUser.id == (any id in $scope.users.map(function(u) { return u.id; }));` + * which results in Bob being selected as desired. + * + * Working HTML: + * + *
    + * + * {{ user.name }} + * + *
    + *
    + */ +function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $compile, $parse) { + var keyCodes = $mdConstant.KEY_CODE; + var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW]; + + return { + restrict: 'E', + require: ['^?mdInputContainer', 'mdSelect', 'ngModel', '?^form'], + compile: compile, + controller: function() { + } // empty placeholder controller to be initialized in link + }; + + function compile(element, attr) { + // add the select value that will hold our placeholder or selected option value + var valueEl = angular.element(''); + valueEl.append(''); + valueEl.addClass('md-select-value'); + if (!valueEl[0].hasAttribute('id')) { + valueEl.attr('id', 'select_value_label_' + $mdUtil.nextUid()); + } + + // There's got to be an md-content inside. If there's not one, let's add it. + if (!element.find('md-content').length) { + element.append(angular.element('').append(element.contents())); + } + + + // Add progress spinner for md-options-loading + if (attr.mdOnOpen) { + + // Show progress indicator while loading async + // Use ng-hide for `display:none` so the indicator does not interfere with the options list + element + .find('md-content') + .prepend(angular.element( + '
    ' + + ' ' + + '
    ' + )); + + // Hide list [of item options] while loading async + element + .find('md-option') + .attr('ng-show', '$$loadingAsyncDone'); + } + + if (attr.name) { + var autofillClone = angular.element(',
    +
    +
    + + + it('should auto compile', function() { + var textarea = $('textarea'); + var output = $('div[compile]'); + // The initial state reads 'Hello Angular'. + expect(output.getText()).toBe('Hello Angular'); + textarea.clear(); + textarea.sendKeys('{{name}}!'); + expect(output.getText()).toBe('Angular!'); + }); + + + + * + * + * @param {string|DOMElement} element Element or HTML string to compile into a template function. + * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. + * + *
    + * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it + * e.g. will not use the right outer scope. Please pass the transclude function as a + * `parentBoundTranscludeFn` to the link function instead. + *
    + * + * @param {number} maxPriority only apply directives lower than given priority (Only effects the + * root element(s), not their children) + * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template + * (a DOM element/tree) to a scope. Where: + * + * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. + * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the + * `template` and call the `cloneAttachFn` function allowing the caller to attach the + * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is + * called as:
    `cloneAttachFn(clonedElement, scope)` where: + * + * * `clonedElement` - is a clone of the original `element` passed into the compiler. + * * `scope` - is the current scope with which the linking function is working with. + * + * * `options` - An optional object hash with linking options. If `options` is provided, then the following + * keys may be used to control linking behavior: + * + * * `parentBoundTranscludeFn` - the transclude function made available to + * directives; if given, it will be passed through to the link functions of + * directives found in `element` during compilation. + * * `transcludeControllers` - an object hash with keys that map controller names + * to a hash with the key `instance`, which maps to the controller instance; + * if given, it will make the controllers available to directives on the compileNode: + * ``` + * { + * parent: { + * instance: parentControllerInstance + * } + * } + * ``` + * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add + * the cloned elements; only needed for transcludes that are allowed to contain non html + * elements (e.g. SVG elements). See also the directive.controller property. + * + * Calling the linking function returns the element of the template. It is either the original + * element passed in, or the clone of the element if the `cloneAttachFn` is provided. + * + * After linking the view is not updated until after a call to $digest which typically is done by + * Angular automatically. + * + * If you need access to the bound view, there are two ways to do it: + * + * - If you are not asking the linking function to clone the template, create the DOM element(s) + * before you send them to the compiler and keep this reference around. + * ```js + * var element = $compile('

    {{total}}

    ')(scope); + * ``` + * + * - if on the other hand, you need the element to be cloned, the view reference from the original + * example would not point to the clone, but rather to the original template that was cloned. In + * this case, you can access the clone via the cloneAttachFn: + * ```js + * var templateElement = angular.element('

    {{total}}

    '), + * scope = ....; + * + * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { + * //attach the clone to DOM document at the right place + * }); + * + * //now we have reference to the cloned DOM via `clonedElement` + * ``` + * + * + * For information on how the compiler works, see the + * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. + * + * @knownIssue + * + * ### Double Compilation + * + Double compilation occurs when an already compiled part of the DOM gets + compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, + and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it + section on double compilation} for an in-depth explanation and ways to avoid it. + * + */ + +var $compileMinErr = minErr('$compile'); + +function UNINITIALIZED_VALUE() {} +var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); + +/** + * @ngdoc provider + * @name $compileProvider + * + * @description + */ +$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; +/** @this */ +function $CompileProvider($provide, $$sanitizeUriProvider) { + var hasDirectives = {}, + Suffix = 'Directive', + COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, + ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), + REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; + + // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes + // The assumption is that future DOM event attribute names will begin with + // 'on' and be composed of only English letters. + var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + var bindingCache = createMap(); + + function parseIsolateBindings(scope, directiveName, isController) { + var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/; + + var bindings = createMap(); + + forEach(scope, function(definition, scopeName) { + if (definition in bindingCache) { + bindings[scopeName] = bindingCache[definition]; + return; + } + var match = definition.match(LOCAL_REGEXP); + + if (!match) { + throw $compileMinErr('iscp', + 'Invalid {3} for directive \'{0}\'.' + + ' Definition: {... {1}: \'{2}\' ...}', + directiveName, scopeName, definition, + (isController ? 'controller bindings definition' : + 'isolate scope definition')); + } + + bindings[scopeName] = { + mode: match[1][0], + collection: match[2] === '*', + optional: match[3] === '?', + attrName: match[4] || scopeName + }; + if (match[4]) { + bindingCache[definition] = bindings[scopeName]; + } + }); + + return bindings; + } + + function parseDirectiveBindings(directive, directiveName) { + var bindings = { + isolateScope: null, + bindToController: null + }; + if (isObject(directive.scope)) { + if (directive.bindToController === true) { + bindings.bindToController = parseIsolateBindings(directive.scope, + directiveName, true); + bindings.isolateScope = {}; + } else { + bindings.isolateScope = parseIsolateBindings(directive.scope, + directiveName, false); + } + } + if (isObject(directive.bindToController)) { + bindings.bindToController = + parseIsolateBindings(directive.bindToController, directiveName, true); + } + if (bindings.bindToController && !directive.controller) { + // There is no controller + throw $compileMinErr('noctrl', + 'Cannot bind to controller without directive \'{0}\'s controller.', + directiveName); + } + return bindings; + } + + function assertValidDirectiveName(name) { + var letter = name.charAt(0); + if (!letter || letter !== lowercase(letter)) { + throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name); + } + if (name !== name.trim()) { + throw $compileMinErr('baddir', + 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces', + name); + } + } + + function getDirectiveRequire(directive) { + var require = directive.require || (directive.controller && directive.name); + + if (!isArray(require) && isObject(require)) { + forEach(require, function(value, key) { + var match = value.match(REQUIRE_PREFIX_REGEXP); + var name = value.substring(match[0].length); + if (!name) require[key] = match[0] + key; + }); + } + + return require; + } + + function getDirectiveRestrict(restrict, name) { + if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) { + throw $compileMinErr('badrestrict', + 'Restrict property \'{0}\' of directive \'{1}\' is invalid', + restrict, + name); + } + + return restrict || 'EA'; + } + + /** + * @ngdoc method + * @name $compileProvider#directive + * @kind function + * + * @description + * Register a new directive with the compiler. + * + * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which + * will match as ng-bind), or an object map of directives where the keys are the + * names and the values are the factories. + * @param {Function|Array} directiveFactory An injectable directive factory function. See the + * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. + * @returns {ng.$compileProvider} Self for chaining. + */ + this.directive = function registerDirective(name, directiveFactory) { + assertArg(name, 'name'); + assertNotHasOwnProperty(name, 'directive'); + if (isString(name)) { + assertValidDirectiveName(name); + assertArg(directiveFactory, 'directiveFactory'); + if (!hasDirectives.hasOwnProperty(name)) { + hasDirectives[name] = []; + $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', + function($injector, $exceptionHandler) { + var directives = []; + forEach(hasDirectives[name], function(directiveFactory, index) { + try { + var directive = $injector.invoke(directiveFactory); + if (isFunction(directive)) { + directive = { compile: valueFn(directive) }; + } else if (!directive.compile && directive.link) { + directive.compile = valueFn(directive.link); + } + directive.priority = directive.priority || 0; + directive.index = index; + directive.name = directive.name || name; + directive.require = getDirectiveRequire(directive); + directive.restrict = getDirectiveRestrict(directive.restrict, name); + directive.$$moduleName = directiveFactory.$$moduleName; + directives.push(directive); + } catch (e) { + $exceptionHandler(e); + } + }); + return directives; + }]); + } + hasDirectives[name].push(directiveFactory); + } else { + forEach(name, reverseParams(registerDirective)); + } + return this; + }; + + /** + * @ngdoc method + * @name $compileProvider#component + * @module ng + * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match ``) + * @param {Object} options Component definition object (a simplified + * {@link ng.$compile#directive-definition-object directive definition object}), + * with the following properties (all optional): + * + * - `controller` – `{(string|function()=}` – controller constructor function that should be + * associated with newly created scope or the name of a {@link ng.$compile#-controller- + * registered controller} if passed as a string. An empty `noop` function by default. + * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope. + * If present, the controller will be published to scope under the `controllerAs` name. + * If not present, this will default to be `$ctrl`. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used as the contents of this component. + * Empty string by default. + * + * If `template` is a function, then it is {@link auto.$injector#invoke injected} with + * the following locals: + * + * - `$element` - Current element + * - `$attrs` - Current attributes object for the element + * + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html + * template that should be used as the contents of this component. + * + * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with + * the following locals: + * + * - `$element` - Current element + * - `$attrs` - Current attributes object for the element + * + * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties. + * Component properties are always bound to the component controller and not to the scope. + * See {@link ng.$compile#-bindtocontroller- `bindToController`}. + * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled. + * Disabled by default. + * - `require` - `{Object=}` - requires the controllers of other directives and binds them to + * this component's controller. The object keys specify the property names under which the required + * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. + * - `$...` – additional properties to attach to the directive factory function and the controller + * constructor function. (This is used by the component router to annotate) + * + * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. + * @description + * Register a **component definition** with the compiler. This is a shorthand for registering a special + * type of directive, which represents a self-contained UI component in your application. Such components + * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). + * + * Component definitions are very simple and do not require as much configuration as defining general + * directives. Component definitions usually consist only of a template and a controller backing it. + * + * In order to make the definition easier, components enforce best practices like use of `controllerAs`, + * `bindToController`. They always have **isolate scope** and are restricted to elements. + * + * Here are a few examples of how you would usually define components: + * + * ```js + * var myMod = angular.module(...); + * myMod.component('myComp', { + * template: '
    My name is {{$ctrl.name}}
    ', + * controller: function() { + * this.name = 'shahar'; + * } + * }); + * + * myMod.component('myComp', { + * template: '
    My name is {{$ctrl.name}}
    ', + * bindings: {name: '@'} + * }); + * + * myMod.component('myComp', { + * templateUrl: 'views/my-comp.html', + * controller: 'MyCtrl', + * controllerAs: 'ctrl', + * bindings: {name: '@'} + * }); + * + * ``` + * For more examples, and an in-depth guide, see the {@link guide/component component guide}. + * + *
    + * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. + */ + this.component = function registerComponent(name, options) { + var controller = options.controller || function() {}; + + function factory($injector) { + function makeInjectable(fn) { + if (isFunction(fn) || isArray(fn)) { + return /** @this */ function(tElement, tAttrs) { + return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); + }; + } else { + return fn; + } + } + + var template = (!options.template && !options.templateUrl ? '' : options.template); + var ddo = { + controller: controller, + controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', + template: makeInjectable(template), + templateUrl: makeInjectable(options.templateUrl), + transclude: options.transclude, + scope: {}, + bindToController: options.bindings || {}, + restrict: 'E', + require: options.require + }; + + // Copy annotations (starting with $) over to the DDO + forEach(options, function(val, key) { + if (key.charAt(0) === '$') ddo[key] = val; + }); + + return ddo; + } + + // TODO(pete) remove the following `forEach` before we release 1.6.0 + // The component-router@0.2.0 looks for the annotations on the controller constructor + // Nothing in Angular looks for annotations on the factory function but we can't remove + // it from 1.5.x yet. + + // Copy any annotation properties (starting with $) over to the factory and controller constructor functions + // These could be used by libraries such as the new component router + forEach(options, function(val, key) { + if (key.charAt(0) === '$') { + factory[key] = val; + // Don't try to copy over annotations to named controller + if (isFunction(controller)) controller[key] = val; + } + }); + + factory.$inject = ['$injector']; + + return this.directive(name, factory); + }; + + + /** + * @ngdoc method + * @name $compileProvider#aHrefSanitizationWhitelist + * @kind function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at preventing XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.aHrefSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); + return this; + } else { + return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); + } + }; + + + /** + * @ngdoc method + * @name $compileProvider#imgSrcSanitizationWhitelist + * @kind function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during img[src] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to img[src] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.imgSrcSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); + return this; + } else { + return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); + } + }; + + /** + * @ngdoc method + * @name $compileProvider#debugInfoEnabled + * + * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the + * current debugInfoEnabled state + * @returns {*} current value if used as getter or itself (chaining) if used as setter + * + * @kind function + * + * @description + * Call this method to enable/disable various debug runtime information in the compiler such as adding + * binding information and a reference to the current scope on to DOM elements. + * If enabled, the compiler will add the following to DOM elements that have been bound to the scope + * * `ng-binding` CSS class + * * `$binding` data property containing an array of the binding expressions + * + * You may want to disable this in production for a significant performance boost. See + * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. + * + * The default value is true. + */ + var debugInfoEnabled = true; + this.debugInfoEnabled = function(enabled) { + if (isDefined(enabled)) { + debugInfoEnabled = enabled; + return this; + } + return debugInfoEnabled; + }; + + /** + * @ngdoc method + * @name $compileProvider#preAssignBindingsEnabled + * + * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the + * current preAssignBindingsEnabled state + * @returns {*} current value if used as getter or itself (chaining) if used as setter + * + * @kind function + * + * @description + * Call this method to enable/disable whether directive controllers are assigned bindings before + * calling the controller's constructor. + * If enabled (true), the compiler assigns the value of each of the bindings to the + * properties of the controller object before the constructor of this object is called. + * + * If disabled (false), the compiler calls the constructor first before assigning bindings. + * + * The default value is true in Angular 1.5.x but will switch to false in Angular 1.6.x. + */ + var preAssignBindingsEnabled = false; + this.preAssignBindingsEnabled = function(enabled) { + if (isDefined(enabled)) { + preAssignBindingsEnabled = enabled; + return this; + } + return preAssignBindingsEnabled; + }; + + + var TTL = 10; + /** + * @ngdoc method + * @name $compileProvider#onChangesTtl + * @description + * + * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and + * assuming that the model is unstable. + * + * The current default is 10 iterations. + * + * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result + * in several iterations of calls to these hooks. However if an application needs more than the default 10 + * iterations to stabilize then you should investigate what is causing the model to continuously change during + * the `$onChanges` hook execution. + * + * Increasing the TTL could have performance implications, so you should not change it without proper justification. + * + * @param {number} limit The number of `$onChanges` hook iterations. + * @returns {number|object} the current limit (or `this` if called as a setter for chaining) + */ + this.onChangesTtl = function(value) { + if (arguments.length) { + TTL = value; + return this; + } + return TTL; + }; + + var commentDirectivesEnabledConfig = true; + /** + * @ngdoc method + * @name $compileProvider#commentDirectivesEnabled + * @description + * + * It indicates to the compiler + * whether or not directives on comments should be compiled. + * Defaults to `true`. + * + * Calling this function with false disables the compilation of directives + * on comments for the whole application. + * This results in a compilation performance gain, + * as the compiler doesn't have to check comments when looking for directives. + * This should however only be used if you are sure that no comment directives are used in + * the application (including any 3rd party directives). + * + * @param {boolean} enabled `false` if the compiler may ignore directives on comments + * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) + */ + this.commentDirectivesEnabled = function(value) { + if (arguments.length) { + commentDirectivesEnabledConfig = value; + return this; + } + return commentDirectivesEnabledConfig; + }; + + + var cssClassDirectivesEnabledConfig = true; + /** + * @ngdoc method + * @name $compileProvider#cssClassDirectivesEnabled + * @description + * + * It indicates to the compiler + * whether or not directives on element classes should be compiled. + * Defaults to `true`. + * + * Calling this function with false disables the compilation of directives + * on element classes for the whole application. + * This results in a compilation performance gain, + * as the compiler doesn't have to check element classes when looking for directives. + * This should however only be used if you are sure that no class directives are used in + * the application (including any 3rd party directives). + * + * @param {boolean} enabled `false` if the compiler may ignore directives on element classes + * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) + */ + this.cssClassDirectivesEnabled = function(value) { + if (arguments.length) { + cssClassDirectivesEnabledConfig = value; + return this; + } + return cssClassDirectivesEnabledConfig; + }; + + this.$get = [ + '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', + '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', + function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, + $controller, $rootScope, $sce, $animate, $$sanitizeUri) { + + var SIMPLE_ATTR_NAME = /^\w/; + var specialAttrHolder = window.document.createElement('div'); + + + var commentDirectivesEnabled = commentDirectivesEnabledConfig; + var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig; + + + var onChangesTtl = TTL; + // The onChanges hooks should all be run together in a single digest + // When changes occur, the call to trigger their hooks will be added to this queue + var onChangesQueue; + + // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest + function flushOnChangesQueue() { + try { + if (!(--onChangesTtl)) { + // We have hit the TTL limit so reset everything + onChangesQueue = undefined; + throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); + } + // We must run this hook in an apply since the $$postDigest runs outside apply + $rootScope.$apply(function() { + var errors = []; + for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { + try { + onChangesQueue[i](); + } catch (e) { + errors.push(e); + } + } + // Reset the queue to trigger a new schedule next time there is a change + onChangesQueue = undefined; + if (errors.length) { + throw errors; + } + }); + } finally { + onChangesTtl++; + } + } + + + function Attributes(element, attributesToCopy) { + if (attributesToCopy) { + var keys = Object.keys(attributesToCopy); + var i, l, key; + + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i]; + this[key] = attributesToCopy[key]; + } + } else { + this.$attr = {}; + } + + this.$$element = element; + } + + Attributes.prototype = { + /** + * @ngdoc method + * @name $compile.directive.Attributes#$normalize + * @kind function + * + * @description + * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or + * `data-`) to its normalized, camelCase form. + * + * Also there is special case for Moz prefix starting with upper case letter. + * + * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} + * + * @param {string} name Name to normalize + */ + $normalize: directiveNormalize, + + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$addClass + * @kind function + * + * @description + * Adds the CSS class value specified by the classVal parameter to the element. If animations + * are enabled then an animation will be triggered for the class addition. + * + * @param {string} classVal The className value that will be added to the element + */ + $addClass: function(classVal) { + if (classVal && classVal.length > 0) { + $animate.addClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$removeClass + * @kind function + * + * @description + * Removes the CSS class value specified by the classVal parameter from the element. If + * animations are enabled then an animation will be triggered for the class removal. + * + * @param {string} classVal The className value that will be removed from the element + */ + $removeClass: function(classVal) { + if (classVal && classVal.length > 0) { + $animate.removeClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$updateClass + * @kind function + * + * @description + * Adds and removes the appropriate CSS class values to the element based on the difference + * between the new and old CSS class values (specified as newClasses and oldClasses). + * + * @param {string} newClasses The current CSS className value + * @param {string} oldClasses The former CSS className value + */ + $updateClass: function(newClasses, oldClasses) { + var toAdd = tokenDifference(newClasses, oldClasses); + if (toAdd && toAdd.length) { + $animate.addClass(this.$$element, toAdd); + } + + var toRemove = tokenDifference(oldClasses, newClasses); + if (toRemove && toRemove.length) { + $animate.removeClass(this.$$element, toRemove); + } + }, + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + // TODO: decide whether or not to throw an error if "class" + //is set through this function since it may cause $updateClass to + //become unstable. + + var node = this.$$element[0], + booleanKey = getBooleanAttrName(node, key), + aliasedKey = getAliasedAttrName(key), + observer = key, + nodeName; + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } else if (aliasedKey) { + this[aliasedKey] = value; + observer = aliasedKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + nodeName = nodeName_(this.$$element); + + if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || + (nodeName === 'img' && key === 'src')) { + // sanitize a[href] and img[src] values + this[key] = value = $$sanitizeUri(value, key === 'src'); + } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) { + // sanitize img[srcset] values + var result = ''; + + // first check if there are spaces because it's not the same pattern + var trimmedSrcset = trim(value); + // ( 999x ,| 999w ,| ,|, ) + var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; + var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; + + // split srcset into tuple of uri and descriptor except for the last item + var rawUris = trimmedSrcset.split(pattern); + + // for each tuples + var nbrUrisWith2parts = Math.floor(rawUris.length / 2); + for (var i = 0; i < nbrUrisWith2parts; i++) { + var innerIdx = i * 2; + // sanitize the uri + result += $$sanitizeUri(trim(rawUris[innerIdx]), true); + // add the descriptor + result += (' ' + trim(rawUris[innerIdx + 1])); + } + + // split the last item into uri and descriptor + var lastTuple = trim(rawUris[i * 2]).split(/\s/); + + // sanitize the last uri + result += $$sanitizeUri(trim(lastTuple[0]), true); + + // and add the last descriptor if any + if (lastTuple.length === 2) { + result += (' ' + trim(lastTuple[1])); + } + this[key] = value = result; + } + + if (writeAttr !== false) { + if (value === null || isUndefined(value)) { + this.$$element.removeAttr(attrName); + } else { + if (SIMPLE_ATTR_NAME.test(attrName)) { + this.$$element.attr(attrName, value); + } else { + setSpecialAttr(this.$$element[0], attrName, value); + } + } + } + + // fire observers + var $$observers = this.$$observers; + if ($$observers) { + forEach($$observers[observer], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + } + }, + + + /** + * @ngdoc method + * @name $compile.directive.Attributes#$observe + * @kind function + * + * @description + * Observes an interpolated attribute. + * + * The observer function will be invoked once during the next `$digest` following + * compilation. The observer is then invoked whenever the interpolated value + * changes. + * + * @param {string} key Normalized key. (ie ngAttribute) . + * @param {function(interpolatedValue)} fn Function that will be called whenever + the interpolated value of the attribute changes. + * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation + * guide} for more info. + * @returns {function()} Returns a deregistration function for this observer. + */ + $observe: function(key, fn) { + var attrs = this, + $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), + listeners = ($$observers[key] || ($$observers[key] = [])); + + listeners.push(fn); + $rootScope.$evalAsync(function() { + if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { + // no one registered attribute interpolation function, so lets call it manually + fn(attrs[key]); + } + }); + + return function() { + arrayRemove(listeners, fn); + }; + } + }; + + function setSpecialAttr(element, attrName, value) { + // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` + // so we have to jump through some hoops to get such an attribute + // https://github.com/angular/angular.js/pull/13318 + specialAttrHolder.innerHTML = ''; + var attributes = specialAttrHolder.firstChild.attributes; + var attribute = attributes[0]; + // We have to remove the attribute from its container element before we can add it to the destination element + attributes.removeNamedItem(attribute.name); + attribute.value = value; + element.attributes.setNamedItem(attribute); + } + + function safeAddClass($element, className) { + try { + $element.addClass(className); + } catch (e) { + // ignore, since it means that we are trying to set class on + // SVG element, where class name is read-only. + } + } + + + var startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}') + ? identity + : function denormalizeTemplate(template) { + return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + }, + NG_ATTR_BINDING = /^ngAttr[A-Z]/; + var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; + + compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { + var bindings = $element.data('$binding') || []; + + if (isArray(binding)) { + bindings = bindings.concat(binding); + } else { + bindings.push(binding); + } + + $element.data('$binding', bindings); + } : noop; + + compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { + safeAddClass($element, 'ng-binding'); + } : noop; + + compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { + var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; + $element.data(dataName, scope); + } : noop; + + compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { + safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); + } : noop; + + compile.$$createComment = function(directiveName, comment) { + var content = ''; + if (debugInfoEnabled) { + content = ' ' + (directiveName || '') + ': '; + if (comment) content += comment + ' '; + } + return window.document.createComment(content); + }; + + return compile; + + //================================ + + function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, + previousCompileContext) { + if (!($compileNodes instanceof jqLite)) { + // jquery always rewraps, whereas we need to preserve the original selector so that we can + // modify it. + $compileNodes = jqLite($compileNodes); + } + var compositeLinkFn = + compileNodes($compileNodes, transcludeFn, $compileNodes, + maxPriority, ignoreDirective, previousCompileContext); + compile.$$addScopeClass($compileNodes); + var namespace = null; + return function publicLinkFn(scope, cloneConnectFn, options) { + if (!$compileNodes) { + throw $compileMinErr('multilink', 'This element has already been linked.'); + } + assertArg(scope, 'scope'); + + if (previousCompileContext && previousCompileContext.needsNewScope) { + // A parent directive did a replace and a directive on this element asked + // for transclusion, which caused us to lose a layer of element on which + // we could hold the new transclusion scope, so we will create it manually + // here. + scope = scope.$parent.$new(); + } + + options = options || {}; + var parentBoundTranscludeFn = options.parentBoundTranscludeFn, + transcludeControllers = options.transcludeControllers, + futureParentElement = options.futureParentElement; + + // When `parentBoundTranscludeFn` is passed, it is a + // `controllersBoundTransclude` function (it was previously passed + // as `transclude` to directive.link) so we must unwrap it to get + // its `boundTranscludeFn` + if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { + parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; + } + + if (!namespace) { + namespace = detectNamespaceForChildElements(futureParentElement); + } + var $linkNode; + if (namespace !== 'html') { + // When using a directive with replace:true and templateUrl the $compileNodes + // (or a child element inside of them) + // might change, so we need to recreate the namespace adapted compileNodes + // for call to the link function. + // Note: This will already clone the nodes... + $linkNode = jqLite( + wrapTemplate(namespace, jqLite('
    ').append($compileNodes).html()) + ); + } else if (cloneConnectFn) { + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart + // and sometimes changes the structure of the DOM. + $linkNode = JQLitePrototype.clone.call($compileNodes); + } else { + $linkNode = $compileNodes; + } + + if (transcludeControllers) { + for (var controllerName in transcludeControllers) { + $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); + } + } + + compile.$$addScopeInfo($linkNode, scope); + + if (cloneConnectFn) cloneConnectFn($linkNode, scope); + if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); + + if (!cloneConnectFn) { + $compileNodes = compositeLinkFn = null; + } + return $linkNode; + }; + } + + function detectNamespaceForChildElements(parentElement) { + // TODO: Make this detect MathML as well... + var node = parentElement && parentElement[0]; + if (!node) { + return 'html'; + } else { + return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; + } + } + + /** + * Compile function matches each node in nodeList against the directives. Once all directives + * for a particular node are collected their compile functions are executed. The compile + * functions return values - the linking functions - are combined into a composite linking + * function, which is the a linking function for the node. + * + * @param {NodeList} nodeList an array of nodes or NodeList to compile + * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the + * scope argument is auto-generated to the new child of the transcluded parent scope. + * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then + * the rootElement must be set the jqLite collection of the compile root. This is + * needed so that the jqLite collection items can be replaced with widgets. + * @param {number=} maxPriority Max directive priority. + * @returns {Function} A composite linking function of all of the matched directives or null. + */ + function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, + previousCompileContext) { + var linkFns = [], + // `nodeList` can be either an element's `.childNodes` (live NodeList) + // or a jqLite/jQuery collection or an array + notLiveList = isArray(nodeList) || (nodeList instanceof jqLite), + attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; + + + for (var i = 0; i < nodeList.length; i++) { + attrs = new Attributes(); + + // Support: IE 11 only + // Workaround for #11781 and #14924 + if (msie === 11) { + mergeConsecutiveTextNodes(nodeList, i, notLiveList); + } + + // We must always refer to `nodeList[i]` hereafter, + // since the nodes can be replaced underneath us. + directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, + ignoreDirective); + + nodeLinkFn = (directives.length) + ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, + null, [], [], previousCompileContext) + : null; + + if (nodeLinkFn && nodeLinkFn.scope) { + compile.$$addScopeClass(attrs.$$element); + } + + childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || + !(childNodes = nodeList[i].childNodes) || + !childNodes.length) + ? null + : compileNodes(childNodes, + nodeLinkFn ? ( + (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) + && nodeLinkFn.transclude) : transcludeFn); + + if (nodeLinkFn || childLinkFn) { + linkFns.push(i, nodeLinkFn, childLinkFn); + linkFnFound = true; + nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; + } + + //use the previous context only for the first element in the virtual group + previousCompileContext = null; + } + + // return a linking function if we have found anything, null otherwise + return linkFnFound ? compositeLinkFn : null; + + function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { + var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; + var stableNodeList; + + + if (nodeLinkFnFound) { + // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our + // offsets don't get screwed up + var nodeListLength = nodeList.length; + stableNodeList = new Array(nodeListLength); + + // create a sparse array by only copying the elements which have a linkFn + for (i = 0; i < linkFns.length; i += 3) { + idx = linkFns[i]; + stableNodeList[idx] = nodeList[idx]; + } + } else { + stableNodeList = nodeList; + } + + for (i = 0, ii = linkFns.length; i < ii;) { + node = stableNodeList[linkFns[i++]]; + nodeLinkFn = linkFns[i++]; + childLinkFn = linkFns[i++]; + + if (nodeLinkFn) { + if (nodeLinkFn.scope) { + childScope = scope.$new(); + compile.$$addScopeInfo(jqLite(node), childScope); + } else { + childScope = scope; + } + + if (nodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn( + scope, nodeLinkFn.transclude, parentBoundTranscludeFn); + + } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { + childBoundTranscludeFn = parentBoundTranscludeFn; + + } else if (!parentBoundTranscludeFn && transcludeFn) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); + + } else { + childBoundTranscludeFn = null; + } + + nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); + + } else if (childLinkFn) { + childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); + } + } + } + } + + function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) { + var node = nodeList[idx]; + var parent = node.parentNode; + var sibling; + + if (node.nodeType !== NODE_TYPE_TEXT) { + return; + } + + while (true) { + sibling = parent ? node.nextSibling : nodeList[idx + 1]; + if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) { + break; + } + + node.nodeValue = node.nodeValue + sibling.nodeValue; + + if (sibling.parentNode) { + sibling.parentNode.removeChild(sibling); + } + if (notLiveList && sibling === nodeList[idx + 1]) { + nodeList.splice(idx + 1, 1); + } + } + } + + function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { + function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { + + if (!transcludedScope) { + transcludedScope = scope.$new(false, containingScope); + transcludedScope.$$transcluded = true; + } + + return transcludeFn(transcludedScope, cloneFn, { + parentBoundTranscludeFn: previousBoundTranscludeFn, + transcludeControllers: controllers, + futureParentElement: futureParentElement + }); + } + + // We need to attach the transclusion slots onto the `boundTranscludeFn` + // so that they are available inside the `controllersBoundTransclude` function + var boundSlots = boundTranscludeFn.$$slots = createMap(); + for (var slotName in transcludeFn.$$slots) { + if (transcludeFn.$$slots[slotName]) { + boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); + } else { + boundSlots[slotName] = null; + } + } + + return boundTranscludeFn; + } + + /** + * Looks for directives on the given node and adds them to the directive collection which is + * sorted. + * + * @param node Node to search. + * @param directives An array to which the directives are added to. This array is sorted before + * the function returns. + * @param attrs The shared attrs object which is used to populate the normalized attributes. + * @param {number=} maxPriority Max directive priority. + */ + function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { + var nodeType = node.nodeType, + attrsMap = attrs.$attr, + match, + nodeName, + className; + + switch (nodeType) { + case NODE_TYPE_ELEMENT: /* Element */ + + nodeName = nodeName_(node); + + // use the node name: + addDirective(directives, + directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); + + // iterate over the attributes + for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, + j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { + var attrStartName = false; + var attrEndName = false; + + attr = nAttrs[j]; + name = attr.name; + value = attr.value; + + // support ngAttr attribute binding + ngAttrName = directiveNormalize(name); + isNgAttr = NG_ATTR_BINDING.test(ngAttrName); + if (isNgAttr) { + name = name.replace(PREFIX_REGEXP, '') + .substr(8).replace(/_(.)/g, function(match, letter) { + return letter.toUpperCase(); + }); + } + + var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); + if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { + attrStartName = name; + attrEndName = name.substr(0, name.length - 5) + 'end'; + name = name.substr(0, name.length - 6); + } + + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + if (isNgAttr || !attrs.hasOwnProperty(nName)) { + attrs[nName] = value; + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } + } + addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); + addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, + attrEndName); + } + + if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { + // Hidden input elements can have strange behaviour when navigating back to the page + // This tells the browser not to try to cache and reinstate previous values + node.setAttribute('autocomplete', 'off'); + } + + // use class as directive + if (!cssClassDirectivesEnabled) break; + className = node.className; + if (isObject(className)) { + // Maybe SVGAnimatedString + className = className.animVal; + } + if (isString(className) && className !== '') { + while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) { + nName = directiveNormalize(match[2]); + if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[3]); + } + className = className.substr(match.index + match[0].length); + } + } + break; + case NODE_TYPE_TEXT: /* Text Node */ + addTextInterpolateDirective(directives, node.nodeValue); + break; + case NODE_TYPE_COMMENT: /* Comment */ + if (!commentDirectivesEnabled) break; + collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective); + break; + } + + directives.sort(byPriority); + return directives; + } + + function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) { + // function created because of performance, try/catch disables + // the optimization of the whole function #14848 + try { + var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); + if (match) { + var nName = directiveNormalize(match[1]); + if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[2]); + } + } + } catch (e) { + // turns out that under some circumstances IE9 throws errors when one attempts to read + // comment's node value. + // Just ignore it and continue. (Can't seem to reproduce in test case.) + } + } + + /** + * Given a node with a directive-start it collects all of the siblings until it finds + * directive-end. + * @param node + * @param attrStart + * @param attrEnd + * @returns {*} + */ + function groupScan(node, attrStart, attrEnd) { + var nodes = []; + var depth = 0; + if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { + do { + if (!node) { + throw $compileMinErr('uterdir', + 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.', + attrStart, attrEnd); + } + if (node.nodeType === NODE_TYPE_ELEMENT) { + if (node.hasAttribute(attrStart)) depth++; + if (node.hasAttribute(attrEnd)) depth--; + } + nodes.push(node); + node = node.nextSibling; + } while (depth > 0); + } else { + nodes.push(node); + } + + return jqLite(nodes); + } + + /** + * Wrapper for linking function which converts normal linking function into a grouped + * linking function. + * @param linkFn + * @param attrStart + * @param attrEnd + * @returns {Function} + */ + function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { + return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { + element = groupScan(element[0], attrStart, attrEnd); + return linkFn(scope, element, attrs, controllers, transcludeFn); + }; + } + + /** + * A function generator that is used to support both eager and lazy compilation + * linking function. + * @param eager + * @param $compileNodes + * @param transcludeFn + * @param maxPriority + * @param ignoreDirective + * @param previousCompileContext + * @returns {Function} + */ + function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { + var compiled; + + if (eager) { + return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); + } + return /** @this */ function lazyCompilation() { + if (!compiled) { + compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); + + // Null out all of these references in order to make them eligible for garbage collection + // since this is a potentially long lived closure + $compileNodes = transcludeFn = previousCompileContext = null; + } + return compiled.apply(this, arguments); + }; + } + + /** + * Once the directives have been collected, their compile functions are executed. This method + * is responsible for inlining directive templates as well as terminating the application + * of the directives if the terminal directive has been reached. + * + * @param {Array} directives Array of collected directives to execute their compile function. + * this needs to be pre-sorted by priority order. + * @param {Node} compileNode The raw DOM node to apply the compile functions to + * @param {Object} templateAttrs The shared attribute function + * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the + * scope argument is auto-generated to the new + * child of the transcluded parent scope. + * @param {JQLite} jqCollection If we are working on the root of the compile tree then this + * argument has the root jqLite array so that we can replace nodes + * on it. + * @param {Object=} originalReplaceDirective An optional directive that will be ignored when + * compiling the transclusion. + * @param {Array.} preLinkFns + * @param {Array.} postLinkFns + * @param {Object} previousCompileContext Context used for previous compilation of the current + * node + * @returns {Function} linkFn + */ + function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, + jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, + previousCompileContext) { + previousCompileContext = previousCompileContext || {}; + + var terminalPriority = -Number.MAX_VALUE, + newScopeDirective = previousCompileContext.newScopeDirective, + controllerDirectives = previousCompileContext.controllerDirectives, + newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, + templateDirective = previousCompileContext.templateDirective, + nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, + hasTranscludeDirective = false, + hasTemplate = false, + hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, + $compileNode = templateAttrs.$$element = jqLite(compileNode), + directive, + directiveName, + $template, + replaceDirective = originalReplaceDirective, + childTranscludeFn = transcludeFn, + linkFn, + didScanForMultipleTransclusion = false, + mightHaveMultipleTransclusionError = false, + directiveValue; + + // executes all directives on the current element + for (var i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + var attrStart = directive.$$start; + var attrEnd = directive.$$end; + + // collect multiblock sections + if (attrStart) { + $compileNode = groupScan(compileNode, attrStart, attrEnd); + } + $template = undefined; + + if (terminalPriority > directive.priority) { + break; // prevent further processing of directives + } + + directiveValue = directive.scope; + + if (directiveValue) { + + // skip the check for directives with async templates, we'll check the derived sync + // directive when the template arrives + if (!directive.templateUrl) { + if (isObject(directiveValue)) { + // This directive is trying to add an isolated scope. + // Check that there is no scope of any kind already + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, + directive, $compileNode); + newIsolateScopeDirective = directive; + } else { + // This directive is trying to add a child scope. + // Check that there is no isolated scope already + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, + $compileNode); + } + } + + newScopeDirective = newScopeDirective || directive; + } + + directiveName = directive.name; + + // If we encounter a condition that can result in transclusion on the directive, + // then scan ahead in the remaining directives for others that may cause a multiple + // transclusion error to be thrown during the compilation process. If a matching directive + // is found, then we know that when we encounter a transcluded directive, we need to eagerly + // compile the `transclude` function rather than doing it lazily in order to throw + // exceptions at the correct time + if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) + || (directive.transclude && !directive.$$tlb))) { + var candidateDirective; + + for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) { + if ((candidateDirective.transclude && !candidateDirective.$$tlb) + || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { + mightHaveMultipleTransclusionError = true; + break; + } + } + + didScanForMultipleTransclusion = true; + } + + if (!directive.templateUrl && directive.controller) { + controllerDirectives = controllerDirectives || createMap(); + assertNoDuplicate('\'' + directiveName + '\' controller', + controllerDirectives[directiveName], directive, $compileNode); + controllerDirectives[directiveName] = directive; + } + + directiveValue = directive.transclude; + + if (directiveValue) { + hasTranscludeDirective = true; + + // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. + // This option should only be used by directives that know how to safely handle element transclusion, + // where the transcluded nodes are added or replaced after linking. + if (!directive.$$tlb) { + assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); + nonTlbTranscludeDirective = directive; + } + + if (directiveValue === 'element') { + hasElementTranscludeDirective = true; + terminalPriority = directive.priority; + $template = $compileNode; + $compileNode = templateAttrs.$$element = + jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); + compileNode = $compileNode[0]; + replaceWith(jqCollection, sliceArgs($template), compileNode); + + // Support: Chrome < 50 + // https://github.com/angular/angular.js/issues/14041 + + // In the versions of V8 prior to Chrome 50, the document fragment that is created + // in the `replaceWith` function is improperly garbage collected despite still + // being referenced by the `parentNode` property of all of the child nodes. By adding + // a reference to the fragment via a different property, we can avoid that incorrect + // behavior. + // TODO: remove this line after Chrome 50 has been released + $template[0].$$parentNode = $template[0].parentNode; + + childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, + replaceDirective && replaceDirective.name, { + // Don't pass in: + // - controllerDirectives - otherwise we'll create duplicates controllers + // - newIsolateScopeDirective or templateDirective - combining templates with + // element transclusion doesn't make sense. + // + // We need only nonTlbTranscludeDirective so that we prevent putting transclusion + // on the same element more than once. + nonTlbTranscludeDirective: nonTlbTranscludeDirective + }); + } else { + + var slots = createMap(); + + if (!isObject(directiveValue)) { + $template = jqLite(jqLiteClone(compileNode)).contents(); + } else { + + // We have transclusion slots, + // collect them up, compile them and store their transclusion functions + $template = []; + + var slotMap = createMap(); + var filledSlots = createMap(); + + // Parse the element selectors + forEach(directiveValue, function(elementSelector, slotName) { + // If an element selector starts with a ? then it is optional + var optional = (elementSelector.charAt(0) === '?'); + elementSelector = optional ? elementSelector.substring(1) : elementSelector; + + slotMap[elementSelector] = slotName; + + // We explicitly assign `null` since this implies that a slot was defined but not filled. + // Later when calling boundTransclusion functions with a slot name we only error if the + // slot is `undefined` + slots[slotName] = null; + + // filledSlots contains `true` for all slots that are either optional or have been + // filled. This is used to check that we have not missed any required slots + filledSlots[slotName] = optional; + }); + + // Add the matching elements into their slot + forEach($compileNode.contents(), function(node) { + var slotName = slotMap[directiveNormalize(nodeName_(node))]; + if (slotName) { + filledSlots[slotName] = true; + slots[slotName] = slots[slotName] || []; + slots[slotName].push(node); + } else { + $template.push(node); + } + }); + + // Check for required slots that were not filled + forEach(filledSlots, function(filled, slotName) { + if (!filled) { + throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); + } + }); + + for (var slotName in slots) { + if (slots[slotName]) { + // Only define a transclusion function if the slot was filled + slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); + } + } + } + + $compileNode.empty(); // clear contents + childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, + undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); + childTranscludeFn.$$slots = slots; + } + } + + if (directive.template) { + hasTemplate = true; + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + directiveValue = (isFunction(directive.template)) + ? directive.template($compileNode, templateAttrs) + : directive.template; + + directiveValue = denormalizeTemplate(directiveValue); + + if (directive.replace) { + replaceDirective = directive; + if (jqLiteIsTextNode(directiveValue)) { + $template = []; + } else { + $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); + } + compileNode = $template[0]; + + if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { + throw $compileMinErr('tplrt', + 'Template for directive \'{0}\' must have exactly one root element. {1}', + directiveName, ''); + } + + replaceWith(jqCollection, $compileNode, compileNode); + + var newTemplateAttrs = {$attr: {}}; + + // combine directives from the original node and from the template: + // - take the array of directives for this element + // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) + // - collect directives from the template and sort them by priority + // - combine directives as: processed + template + unprocessed + var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); + var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); + + if (newIsolateScopeDirective || newScopeDirective) { + // The original directive caused the current element to be replaced but this element + // also needs to have a new scope, so we need to tell the template directives + // that they would need to get their scope from further up, if they require transclusion + markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); + } + directives = directives.concat(templateDirectives).concat(unprocessedDirectives); + mergeTemplateAttributes(templateAttrs, newTemplateAttrs); + + ii = directives.length; + } else { + $compileNode.html(directiveValue); + } + } + + if (directive.templateUrl) { + hasTemplate = true; + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + if (directive.replace) { + replaceDirective = directive; + } + + // eslint-disable-next-line no-func-assign + nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, + templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { + controllerDirectives: controllerDirectives, + newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, + newIsolateScopeDirective: newIsolateScopeDirective, + templateDirective: templateDirective, + nonTlbTranscludeDirective: nonTlbTranscludeDirective + }); + ii = directives.length; + } else if (directive.compile) { + try { + linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); + var context = directive.$$originalDirective || directive; + if (isFunction(linkFn)) { + addLinkFns(null, bind(context, linkFn), attrStart, attrEnd); + } else if (linkFn) { + addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd); + } + } catch (e) { + $exceptionHandler(e, startingTag($compileNode)); + } + } + + if (directive.terminal) { + nodeLinkFn.terminal = true; + terminalPriority = Math.max(terminalPriority, directive.priority); + } + + } + + nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; + nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; + nodeLinkFn.templateOnThisElement = hasTemplate; + nodeLinkFn.transclude = childTranscludeFn; + + previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; + + // might be normal or delayed nodeLinkFn depending on if templateUrl is present + return nodeLinkFn; + + //////////////////// + + function addLinkFns(pre, post, attrStart, attrEnd) { + if (pre) { + if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); + pre.require = directive.require; + pre.directiveName = directiveName; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + pre = cloneAndAnnotateFn(pre, {isolateScope: true}); + } + preLinkFns.push(pre); + } + if (post) { + if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); + post.require = directive.require; + post.directiveName = directiveName; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + post = cloneAndAnnotateFn(post, {isolateScope: true}); + } + postLinkFns.push(post); + } + } + + function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { + var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, + attrs, scopeBindingInfo; + + if (compileNode === linkNode) { + attrs = templateAttrs; + $element = templateAttrs.$$element; + } else { + $element = jqLite(linkNode); + attrs = new Attributes($element, templateAttrs); + } + + controllerScope = scope; + if (newIsolateScopeDirective) { + isolateScope = scope.$new(true); + } else if (newScopeDirective) { + controllerScope = scope.$parent; + } + + if (boundTranscludeFn) { + // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` + // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` + transcludeFn = controllersBoundTransclude; + transcludeFn.$$boundTransclude = boundTranscludeFn; + // expose the slots on the `$transclude` function + transcludeFn.isSlotFilled = function(slotName) { + return !!boundTranscludeFn.$$slots[slotName]; + }; + } + + if (controllerDirectives) { + elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); + } + + if (newIsolateScopeDirective) { + // Initialize isolate scope bindings for new isolate scope directive. + compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || + templateDirective === newIsolateScopeDirective.$$originalDirective))); + compile.$$addScopeClass($element, true); + isolateScope.$$isolateBindings = + newIsolateScopeDirective.$$isolateBindings; + scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, + isolateScope.$$isolateBindings, + newIsolateScopeDirective); + if (scopeBindingInfo.removeWatches) { + isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); + } + } + + // Initialize bindToController bindings + for (var name in elementControllers) { + var controllerDirective = controllerDirectives[name]; + var controller = elementControllers[name]; + var bindings = controllerDirective.$$bindings.bindToController; + + if (preAssignBindingsEnabled) { + if (bindings) { + controller.bindingInfo = + initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); + } else { + controller.bindingInfo = {}; + } + + var controllerResult = controller(); + if (controllerResult !== controller.instance) { + // If the controller constructor has a return value, overwrite the instance + // from setupControllers + controller.instance = controllerResult; + $element.data('$' + controllerDirective.name + 'Controller', controllerResult); + if (controller.bindingInfo.removeWatches) { + controller.bindingInfo.removeWatches(); + } + controller.bindingInfo = + initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); + } + } else { + controller.instance = controller(); + $element.data('$' + controllerDirective.name + 'Controller', controller.instance); + controller.bindingInfo = + initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); + } + } + + // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy + forEach(controllerDirectives, function(controllerDirective, name) { + var require = controllerDirective.require; + if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { + extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); + } + }); + + // Handle the init and destroy lifecycle hooks on all controllers that have them + forEach(elementControllers, function(controller) { + var controllerInstance = controller.instance; + if (isFunction(controllerInstance.$onChanges)) { + try { + controllerInstance.$onChanges(controller.bindingInfo.initialChanges); + } catch (e) { + $exceptionHandler(e); + } + } + if (isFunction(controllerInstance.$onInit)) { + try { + controllerInstance.$onInit(); + } catch (e) { + $exceptionHandler(e); + } + } + if (isFunction(controllerInstance.$doCheck)) { + controllerScope.$watch(function() { controllerInstance.$doCheck(); }); + controllerInstance.$doCheck(); + } + if (isFunction(controllerInstance.$onDestroy)) { + controllerScope.$on('$destroy', function callOnDestroyHook() { + controllerInstance.$onDestroy(); + }); + } + }); + + // PRELINKING + for (i = 0, ii = preLinkFns.length; i < ii; i++) { + linkFn = preLinkFns[i]; + invokeLinkFn(linkFn, + linkFn.isolateScope ? isolateScope : scope, + $element, + attrs, + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), + transcludeFn + ); + } + + // RECURSION + // We only pass the isolate scope, if the isolate directive has a template, + // otherwise the child elements do not belong to the isolate directive. + var scopeToChild = scope; + if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { + scopeToChild = isolateScope; + } + if (childLinkFn) { + childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); + } + + // POSTLINKING + for (i = postLinkFns.length - 1; i >= 0; i--) { + linkFn = postLinkFns[i]; + invokeLinkFn(linkFn, + linkFn.isolateScope ? isolateScope : scope, + $element, + attrs, + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), + transcludeFn + ); + } + + // Trigger $postLink lifecycle hooks + forEach(elementControllers, function(controller) { + var controllerInstance = controller.instance; + if (isFunction(controllerInstance.$postLink)) { + controllerInstance.$postLink(); + } + }); + + // This is the function that is injected as `$transclude`. + // Note: all arguments are optional! + function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { + var transcludeControllers; + // No scope passed in: + if (!isScope(scope)) { + slotName = futureParentElement; + futureParentElement = cloneAttachFn; + cloneAttachFn = scope; + scope = undefined; + } + + if (hasElementTranscludeDirective) { + transcludeControllers = elementControllers; + } + if (!futureParentElement) { + futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; + } + if (slotName) { + // slotTranscludeFn can be one of three things: + // * a transclude function - a filled slot + // * `null` - an optional slot that was not filled + // * `undefined` - a slot that was not declared (i.e. invalid) + var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; + if (slotTranscludeFn) { + return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); + } else if (isUndefined(slotTranscludeFn)) { + throw $compileMinErr('noslot', + 'No parent directive that requires a transclusion with slot name "{0}". ' + + 'Element: {1}', + slotName, startingTag($element)); + } + } else { + return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); + } + } + } + } + + function getControllers(directiveName, require, $element, elementControllers) { + var value; + + if (isString(require)) { + var match = require.match(REQUIRE_PREFIX_REGEXP); + var name = require.substring(match[0].length); + var inheritType = match[1] || match[3]; + var optional = match[2] === '?'; + + //If only parents then start at the parent element + if (inheritType === '^^') { + $element = $element.parent(); + //Otherwise attempt getting the controller from elementControllers in case + //the element is transcluded (and has no data) and to avoid .data if possible + } else { + value = elementControllers && elementControllers[name]; + value = value && value.instance; + } + + if (!value) { + var dataName = '$' + name + 'Controller'; + value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); + } + + if (!value && !optional) { + throw $compileMinErr('ctreq', + 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!', + name, directiveName); + } + } else if (isArray(require)) { + value = []; + for (var i = 0, ii = require.length; i < ii; i++) { + value[i] = getControllers(directiveName, require[i], $element, elementControllers); + } + } else if (isObject(require)) { + value = {}; + forEach(require, function(controller, property) { + value[property] = getControllers(directiveName, controller, $element, elementControllers); + }); + } + + return value || null; + } + + function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { + var elementControllers = createMap(); + for (var controllerKey in controllerDirectives) { + var directive = controllerDirectives[controllerKey]; + var locals = { + $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, + $element: $element, + $attrs: attrs, + $transclude: transcludeFn + }; + + var controller = directive.controller; + if (controller === '@') { + controller = attrs[directive.name]; + } + + var controllerInstance = $controller(controller, locals, true, directive.controllerAs); + + // For directives with element transclusion the element is a comment. + // In this case .data will not attach any data. + // Instead, we save the controllers for the element in a local hash and attach to .data + // later, once we have the actual element. + elementControllers[directive.name] = controllerInstance; + $element.data('$' + directive.name + 'Controller', controllerInstance.instance); + } + return elementControllers; + } + + // Depending upon the context in which a directive finds itself it might need to have a new isolated + // or child scope created. For instance: + // * if the directive has been pulled into a template because another directive with a higher priority + // asked for element transclusion + // * if the directive itself asks for transclusion but it is at the root of a template and the original + // element was replaced. See https://github.com/angular/angular.js/issues/12936 + function markDirectiveScope(directives, isolateScope, newScope) { + for (var j = 0, jj = directives.length; j < jj; j++) { + directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); + } + } + + /** + * looks up the directive and decorates it with exception handling and proper parameters. We + * call this the boundDirective. + * + * @param {string} name name of the directive to look up. + * @param {string} location The directive must be found in specific format. + * String containing any of theses characters: + * + * * `E`: element name + * * `A': attribute + * * `C`: class + * * `M`: comment + * @returns {boolean} true if directive was added. + */ + function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, + endAttrName) { + if (name === ignoreDirective) return null; + var match = null; + if (hasDirectives.hasOwnProperty(name)) { + for (var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + if ((isUndefined(maxPriority) || maxPriority > directive.priority) && + directive.restrict.indexOf(location) !== -1) { + if (startAttrName) { + directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); + } + if (!directive.$$bindings) { + var bindings = directive.$$bindings = + parseDirectiveBindings(directive, directive.name); + if (isObject(bindings.isolateScope)) { + directive.$$isolateBindings = bindings.isolateScope; + } + } + tDirectives.push(directive); + match = directive; + } + } + } + return match; + } + + + /** + * looks up the directive and returns true if it is a multi-element directive, + * and therefore requires DOM nodes between -start and -end markers to be grouped + * together. + * + * @param {string} name name of the directive to look up. + * @returns true if directive was registered as multi-element. + */ + function directiveIsMultiElement(name) { + if (hasDirectives.hasOwnProperty(name)) { + for (var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + if (directive.multiElement) { + return true; + } + } + } + return false; + } + + /** + * When the element is replaced with HTML template then the new attributes + * on the template need to be merged with the existing attributes in the DOM. + * The desired effect is to have both of the attributes present. + * + * @param {object} dst destination attributes (original DOM) + * @param {object} src source attributes (from the directive template) + */ + function mergeTemplateAttributes(dst, src) { + var srcAttr = src.$attr, + dstAttr = dst.$attr; + + // reapply the old attributes to the new element + forEach(dst, function(value, key) { + if (key.charAt(0) !== '$') { + if (src[key] && src[key] !== value) { + if (value.length) { + value += (key === 'style' ? ';' : ' ') + src[key]; + } else { + value = src[key]; + } + } + dst.$set(key, value, true, srcAttr[key]); + } + }); + + // copy the new attributes on the old attrs object + forEach(src, function(value, key) { + // Check if we already set this attribute in the loop above. + // `dst` will never contain hasOwnProperty as DOM parser won't let it. + // You will get an "InvalidCharacterError: DOM Exception 5" error if you + // have an attribute like "has-own-property" or "data-has-own-property", etc. + if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') { + dst[key] = value; + + if (key !== 'class' && key !== 'style') { + dstAttr[key] = srcAttr[key]; + } + } + }); + } + + + function compileTemplateUrl(directives, $compileNode, tAttrs, + $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { + var linkQueue = [], + afterTemplateNodeLinkFn, + afterTemplateChildLinkFn, + beforeTemplateCompileNode = $compileNode[0], + origAsyncDirective = directives.shift(), + derivedSyncDirective = inherit(origAsyncDirective, { + templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective + }), + templateUrl = (isFunction(origAsyncDirective.templateUrl)) + ? origAsyncDirective.templateUrl($compileNode, tAttrs) + : origAsyncDirective.templateUrl, + templateNamespace = origAsyncDirective.templateNamespace; + + $compileNode.empty(); + + $templateRequest(templateUrl) + .then(function(content) { + var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; + + content = denormalizeTemplate(content); + + if (origAsyncDirective.replace) { + if (jqLiteIsTextNode(content)) { + $template = []; + } else { + $template = removeComments(wrapTemplate(templateNamespace, trim(content))); + } + compileNode = $template[0]; + + if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { + throw $compileMinErr('tplrt', + 'Template for directive \'{0}\' must have exactly one root element. {1}', + origAsyncDirective.name, templateUrl); + } + + tempTemplateAttrs = {$attr: {}}; + replaceWith($rootElement, $compileNode, compileNode); + var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); + + if (isObject(origAsyncDirective.scope)) { + // the original directive that caused the template to be loaded async required + // an isolate scope + markDirectiveScope(templateDirectives, true); + } + directives = templateDirectives.concat(directives); + mergeTemplateAttributes(tAttrs, tempTemplateAttrs); + } else { + compileNode = beforeTemplateCompileNode; + $compileNode.html(content); + } + + directives.unshift(derivedSyncDirective); + + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, + childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, + previousCompileContext); + forEach($rootElement, function(node, i) { + if (node === compileNode) { + $rootElement[i] = $compileNode[0]; + } + }); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); + + while (linkQueue.length) { + var scope = linkQueue.shift(), + beforeTemplateLinkNode = linkQueue.shift(), + linkRootElement = linkQueue.shift(), + boundTranscludeFn = linkQueue.shift(), + linkNode = $compileNode[0]; + + if (scope.$$destroyed) continue; + + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { + var oldClasses = beforeTemplateLinkNode.className; + + if (!(previousCompileContext.hasElementTranscludeDirective && + origAsyncDirective.replace)) { + // it was cloned therefore we have to clone as well. + linkNode = jqLiteClone(compileNode); + } + replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); + + // Copy in CSS classes from original node + safeAddClass(jqLite(linkNode), oldClasses); + } + if (afterTemplateNodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); + } else { + childBoundTranscludeFn = boundTranscludeFn; + } + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, + childBoundTranscludeFn); + } + linkQueue = null; + }).catch(function(error) { + if (error instanceof Error) { + $exceptionHandler(error); + } + }).catch(noop); + + return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { + var childBoundTranscludeFn = boundTranscludeFn; + if (scope.$$destroyed) return; + if (linkQueue) { + linkQueue.push(scope, + node, + rootElement, + childBoundTranscludeFn); + } else { + if (afterTemplateNodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); + } + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); + } + }; + } + + + /** + * Sorting function for bound directives. + */ + function byPriority(a, b) { + var diff = b.priority - a.priority; + if (diff !== 0) return diff; + if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; + return a.index - b.index; + } + + function assertNoDuplicate(what, previousDirective, directive, element) { + + function wrapModuleNameIfDefined(moduleName) { + return moduleName ? + (' (module: ' + moduleName + ')') : + ''; + } + + if (previousDirective) { + throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', + previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), + directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); + } + } + + + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: function textInterpolateCompileFn(templateNode) { + var templateNodeParent = templateNode.parent(), + hasCompileParent = !!templateNodeParent.length; + + // When transcluding a template that has bindings in the root + // we don't have a parent and thus need to add the class during linking fn. + if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); + + return function textInterpolateLinkFn(scope, node) { + var parent = node.parent(); + if (!hasCompileParent) compile.$$addBindingClass(parent); + compile.$$addBindingInfo(parent, interpolateFn.expressions); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }; + } + }); + } + } + + + function wrapTemplate(type, template) { + type = lowercase(type || 'html'); + switch (type) { + case 'svg': + case 'math': + var wrapper = window.document.createElement('div'); + wrapper.innerHTML = '<' + type + '>' + template + ''; + return wrapper.childNodes[0].childNodes; + default: + return template; + } + } + + + function getTrustedContext(node, attrNormalizedName) { + if (attrNormalizedName === 'srcdoc') { + return $sce.HTML; + } + var tag = nodeName_(node); + // All tags with src attributes require a RESOURCE_URL value, except for + // img and various html5 media tags. + if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { + if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) { + return $sce.RESOURCE_URL; + } + // maction[xlink:href] can source SVG. It's not limited to . + } else if (attrNormalizedName === 'xlinkHref' || + (tag === 'form' && attrNormalizedName === 'action') || + // links can be stylesheets or imports, which can run script in the current origin + (tag === 'link' && attrNormalizedName === 'href') + ) { + return $sce.RESOURCE_URL; + } + } + + + function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { + var trustedContext = getTrustedContext(node, name); + var mustHaveExpression = !isNgAttr; + var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; + + var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing); + + // no interpolation found -> ignore + if (!interpolateFn) return; + + if (name === 'multiple' && nodeName_(node) === 'select') { + throw $compileMinErr('selmulti', + 'Binding to the \'multiple\' attribute is not supported. Element: {0}', + startingTag(node)); + } + + if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { + throw $compileMinErr('nodomevents', + 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' + + 'ng- versions (such as ng-click instead of onclick) instead.'); + } + + directives.push({ + priority: 100, + compile: function() { + return { + pre: function attrInterpolatePreLinkFn(scope, element, attr) { + var $$observers = (attr.$$observers || (attr.$$observers = createMap())); + + // If the attribute has changed since last $interpolate()ed + var newValue = attr[name]; + if (newValue !== value) { + // we need to interpolate again since the attribute value has been updated + // (e.g. by another directive's compile function) + // ensure unset/empty values make interpolateFn falsy + interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); + value = newValue; + } + + // if attribute was updated so that there is no interpolation going on we don't want to + // register any observers + if (!interpolateFn) return; + + // initialize attr object so that it's ready in case we need the value for isolate + // scope initialization, otherwise the value would not be available from isolate + // directive's linking fn during linking phase + attr[name] = interpolateFn(scope); + + ($$observers[name] || ($$observers[name] = [])).$$inter = true; + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { + //special case for class attribute addition + removal + //so that class changes can tap into the animation + //hooks provided by the $animate service. Be sure to + //skip animations when the first digest occurs (when + //both the new and the old values are the same) since + //the CSS classes are the non-interpolated values + if (name === 'class' && newValue !== oldValue) { + attr.$updateClass(newValue, oldValue); + } else { + attr.$set(name, newValue); + } + }); + } + }; + } + }); + } + + + /** + * This is a special jqLite.replaceWith, which can replace items which + * have no parents, provided that the containing jqLite collection is provided. + * + * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes + * in the root of the tree. + * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep + * the shell, but replace its DOM node reference. + * @param {Node} newNode The new DOM node. + */ + function replaceWith($rootElement, elementsToRemove, newNode) { + var firstElementToRemove = elementsToRemove[0], + removeCount = elementsToRemove.length, + parent = firstElementToRemove.parentNode, + i, ii; + + if ($rootElement) { + for (i = 0, ii = $rootElement.length; i < ii; i++) { + if ($rootElement[i] === firstElementToRemove) { + $rootElement[i++] = newNode; + for (var j = i, j2 = j + removeCount - 1, + jj = $rootElement.length; + j < jj; j++, j2++) { + if (j2 < jj) { + $rootElement[j] = $rootElement[j2]; + } else { + delete $rootElement[j]; + } + } + $rootElement.length -= removeCount - 1; + + // If the replaced element is also the jQuery .context then replace it + // .context is a deprecated jQuery api, so we should set it only when jQuery set it + // http://api.jquery.com/context/ + if ($rootElement.context === firstElementToRemove) { + $rootElement.context = newNode; + } + break; + } + } + } + + if (parent) { + parent.replaceChild(newNode, firstElementToRemove); + } + + // Append all the `elementsToRemove` to a fragment. This will... + // - remove them from the DOM + // - allow them to still be traversed with .nextSibling + // - allow a single fragment.qSA to fetch all elements being removed + var fragment = window.document.createDocumentFragment(); + for (i = 0; i < removeCount; i++) { + fragment.appendChild(elementsToRemove[i]); + } + + if (jqLite.hasData(firstElementToRemove)) { + // Copy over user data (that includes Angular's $scope etc.). Don't copy private + // data here because there's no public interface in jQuery to do that and copying over + // event listeners (which is the main use of private data) wouldn't work anyway. + jqLite.data(newNode, jqLite.data(firstElementToRemove)); + + // Remove $destroy event listeners from `firstElementToRemove` + jqLite(firstElementToRemove).off('$destroy'); + } + + // Cleanup any data/listeners on the elements and children. + // This includes invoking the $destroy event on any elements with listeners. + jqLite.cleanData(fragment.querySelectorAll('*')); + + // Update the jqLite collection to only contain the `newNode` + for (i = 1; i < removeCount; i++) { + delete elementsToRemove[i]; + } + elementsToRemove[0] = newNode; + elementsToRemove.length = 1; + } + + + function cloneAndAnnotateFn(fn, annotation) { + return extend(function() { return fn.apply(null, arguments); }, fn, annotation); + } + + + function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { + try { + linkFn(scope, $element, attrs, controllers, transcludeFn); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + + // Set up $watches for isolate scope and controller bindings. + function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { + var removeWatchCollection = []; + var initialChanges = {}; + var changes; + forEach(bindings, function initializeBinding(definition, scopeName) { + var attrName = definition.attrName, + optional = definition.optional, + mode = definition.mode, // @, =, <, or & + lastValue, + parentGet, parentSet, compare, removeWatch; + + switch (mode) { + + case '@': + if (!optional && !hasOwnProperty.call(attrs, attrName)) { + destination[scopeName] = attrs[attrName] = undefined; + } + removeWatch = attrs.$observe(attrName, function(value) { + if (isString(value) || isBoolean(value)) { + var oldValue = destination[scopeName]; + recordChanges(scopeName, value, oldValue); + destination[scopeName] = value; + } + }); + attrs.$$observers[attrName].$$scope = scope; + lastValue = attrs[attrName]; + if (isString(lastValue)) { + // If the attribute has been provided then we trigger an interpolation to ensure + // the value is there for use in the link fn + destination[scopeName] = $interpolate(lastValue)(scope); + } else if (isBoolean(lastValue)) { + // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted + // the value to boolean rather than a string, so we special case this situation + destination[scopeName] = lastValue; + } + initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); + removeWatchCollection.push(removeWatch); + break; + + case '=': + if (!hasOwnProperty.call(attrs, attrName)) { + if (optional) break; + attrs[attrName] = undefined; + } + if (optional && !attrs[attrName]) break; + + parentGet = $parse(attrs[attrName]); + if (parentGet.literal) { + compare = equals; + } else { + // eslint-disable-next-line no-self-compare + compare = function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }; + } + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = destination[scopeName] = parentGet(scope); + throw $compileMinErr('nonassign', + 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!', + attrs[attrName], attrName, directive.name); + }; + lastValue = destination[scopeName] = parentGet(scope); + var parentValueWatch = function parentValueWatch(parentValue) { + if (!compare(parentValue, destination[scopeName])) { + // we are out of sync and need to copy + if (!compare(parentValue, lastValue)) { + // parent changed and it has precedence + destination[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(scope, parentValue = destination[scopeName]); + } + } + lastValue = parentValue; + return lastValue; + }; + parentValueWatch.$stateful = true; + if (definition.collection) { + removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); + } else { + removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); + } + removeWatchCollection.push(removeWatch); + break; + + case '<': + if (!hasOwnProperty.call(attrs, attrName)) { + if (optional) break; + attrs[attrName] = undefined; + } + if (optional && !attrs[attrName]) break; + + parentGet = $parse(attrs[attrName]); + var deepWatch = parentGet.literal; + + var initialValue = destination[scopeName] = parentGet(scope); + initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); + + removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) { + if (oldValue === newValue) { + if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) { + return; + } + oldValue = initialValue; + } + recordChanges(scopeName, newValue, oldValue); + destination[scopeName] = newValue; + }, deepWatch); + + removeWatchCollection.push(removeWatch); + break; + + case '&': + // Don't assign Object.prototype method to scope + parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; + + // Don't assign noop to destination if expression is not valid + if (parentGet === noop && optional) break; + + destination[scopeName] = function(locals) { + return parentGet(scope, locals); + }; + break; + } + }); + + function recordChanges(key, currentValue, previousValue) { + if (isFunction(destination.$onChanges) && currentValue !== previousValue && + // eslint-disable-next-line no-self-compare + (currentValue === currentValue || previousValue === previousValue)) { + // If we have not already scheduled the top level onChangesQueue handler then do so now + if (!onChangesQueue) { + scope.$$postDigest(flushOnChangesQueue); + onChangesQueue = []; + } + // If we have not already queued a trigger of onChanges for this controller then do so now + if (!changes) { + changes = {}; + onChangesQueue.push(triggerOnChangesHook); + } + // If the has been a change on this property already then we need to reuse the previous value + if (changes[key]) { + previousValue = changes[key].previousValue; + } + // Store this change + changes[key] = new SimpleChange(previousValue, currentValue); + } + } + + function triggerOnChangesHook() { + destination.$onChanges(changes); + // Now clear the changes so that we schedule onChanges when more changes arrive + changes = undefined; + } + + return { + initialChanges: initialChanges, + removeWatches: removeWatchCollection.length && function removeWatches() { + for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { + removeWatchCollection[i](); + } + } + }; + } + }]; +} + +function SimpleChange(previous, current) { + this.previousValue = previous; + this.currentValue = current; +} +SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; + + +var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; +var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; + +/** + * Converts all accepted directives format into proper directive name. + * @param name Name to normalize + */ +function directiveNormalize(name) { + return name + .replace(PREFIX_REGEXP, '') + .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace); +} + +/** + * @ngdoc type + * @name $compile.directive.Attributes + * + * @description + * A shared object between directive compile / linking functions which contains normalized DOM + * element attributes. The values reflect current binding state `{{ }}`. The normalization is + * needed since all of these are treated as equivalent in Angular: + * + * ``` + * + * ``` + */ + +/** + * @ngdoc property + * @name $compile.directive.Attributes#$attr + * + * @description + * A map of DOM element attribute names to the normalized name. This is + * needed to do reverse lookup from normalized name back to actual name. + */ + + +/** + * @ngdoc method + * @name $compile.directive.Attributes#$set + * @kind function + * + * @description + * Set DOM element attribute value. + * + * + * @param {string} name Normalized element attribute name of the property to modify. The name is + * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} + * property to the original name. + * @param {string} value Value to set the attribute to. The value can be an interpolated string. + */ + + + +/** + * Closure compiler type information + */ + +function nodesetLinkingFn( + /* angular.Scope */ scope, + /* NodeList */ nodeList, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +) {} + +function directiveLinkingFn( + /* nodesetLinkingFn */ nodesetLinkingFn, + /* angular.Scope */ scope, + /* Node */ node, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +) {} + +function tokenDifference(str1, str2) { + var values = '', + tokens1 = str1.split(/\s+/), + tokens2 = str2.split(/\s+/); + + outer: + for (var i = 0; i < tokens1.length; i++) { + var token = tokens1[i]; + for (var j = 0; j < tokens2.length; j++) { + if (token === tokens2[j]) continue outer; + } + values += (values.length > 0 ? ' ' : '') + token; + } + return values; +} + +function removeComments(jqNodes) { + jqNodes = jqLite(jqNodes); + var i = jqNodes.length; + + if (i <= 1) { + return jqNodes; + } + + while (i--) { + var node = jqNodes[i]; + if (node.nodeType === NODE_TYPE_COMMENT || + (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) { + splice.call(jqNodes, i, 1); + } + } + return jqNodes; +} + +var $controllerMinErr = minErr('$controller'); + + +var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; +function identifierForController(controller, ident) { + if (ident && isString(ident)) return ident; + if (isString(controller)) { + var match = CNTRL_REG.exec(controller); + if (match) return match[3]; + } +} + + +/** + * @ngdoc provider + * @name $controllerProvider + * @this + * + * @description + * The {@link ng.$controller $controller service} is used by Angular to create new + * controllers. + * + * This provider allows controller registration via the + * {@link ng.$controllerProvider#register register} method. + */ +function $ControllerProvider() { + var controllers = {}, + globals = false; + + /** + * @ngdoc method + * @name $controllerProvider#has + * @param {string} name Controller name to check. + */ + this.has = function(name) { + return controllers.hasOwnProperty(name); + }; + + /** + * @ngdoc method + * @name $controllerProvider#register + * @param {string|Object} name Controller name, or an object map of controllers where the keys are + * the names and the values are the constructors. + * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI + * annotations in the array notation). + */ + this.register = function(name, constructor) { + assertNotHasOwnProperty(name, 'controller'); + if (isObject(name)) { + extend(controllers, name); + } else { + controllers[name] = constructor; + } + }; + + /** + * @ngdoc method + * @name $controllerProvider#allowGlobals + * @description If called, allows `$controller` to find controller constructors on `window` + * + * @deprecated + * sinceVersion="v1.3.0" + * removeVersion="v1.7.0" + * This method of finding controllers has been deprecated. + */ + this.allowGlobals = function() { + globals = true; + }; + + + this.$get = ['$injector', '$window', function($injector, $window) { + + /** + * @ngdoc service + * @name $controller + * @requires $injector + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (deprecated, not recommended) + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * + * @param {Object} locals Injection locals for Controller. + * @return {Object} Instance of given controller. + * + * @description + * `$controller` service is responsible for instantiating controllers. + * + * It's just a simple call to {@link auto.$injector $injector}, but extracted into + * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). + */ + return function $controller(expression, locals, later, ident) { + // PRIVATE API: + // param `later` --- indicates that the controller's constructor is invoked at a later time. + // If true, $controller will allocate the object with the correct + // prototype chain, but will not invoke the controller until a returned + // callback is invoked. + // param `ident` --- An optional label which overrides the label parsed from the controller + // expression, if any. + var instance, match, constructor, identifier; + later = later === true; + if (ident && isString(ident)) { + identifier = ident; + } + + if (isString(expression)) { + match = expression.match(CNTRL_REG); + if (!match) { + throw $controllerMinErr('ctrlfmt', + 'Badly formed controller string \'{0}\'. ' + + 'Must match `__name__ as __id__` or `__name__`.', expression); + } + constructor = match[1]; + identifier = identifier || match[3]; + expression = controllers.hasOwnProperty(constructor) + ? controllers[constructor] + : getter(locals.$scope, constructor, true) || + (globals ? getter($window, constructor, true) : undefined); + + if (!expression) { + throw $controllerMinErr('ctrlreg', + 'The controller with the name \'{0}\' is not registered.', constructor); + } + + assertArgFn(expression, constructor, true); + } + + if (later) { + // Instantiate controller later: + // This machinery is used to create an instance of the object before calling the + // controller's constructor itself. + // + // This allows properties to be added to the controller before the constructor is + // invoked. Primarily, this is used for isolate scope bindings in $compile. + // + // This feature is not intended for use by applications, and is thus not documented + // publicly. + // Object creation: http://jsperf.com/create-constructor/2 + var controllerPrototype = (isArray(expression) ? + expression[expression.length - 1] : expression).prototype; + instance = Object.create(controllerPrototype || null); + + if (identifier) { + addIdentifier(locals, identifier, instance, constructor || expression.name); + } + + return extend(function $controllerInit() { + var result = $injector.invoke(expression, instance, locals, constructor); + if (result !== instance && (isObject(result) || isFunction(result))) { + instance = result; + if (identifier) { + // If result changed, re-assign controllerAs value to scope. + addIdentifier(locals, identifier, instance, constructor || expression.name); + } + } + return instance; + }, { + instance: instance, + identifier: identifier + }); + } + + instance = $injector.instantiate(expression, locals, constructor); + + if (identifier) { + addIdentifier(locals, identifier, instance, constructor || expression.name); + } + + return instance; + }; + + function addIdentifier(locals, identifier, instance, name) { + if (!(locals && isObject(locals.$scope))) { + throw minErr('$controller')('noscp', + 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.', + name, identifier); + } + + locals.$scope[identifier] = instance; + } + }]; +} + +/** + * @ngdoc service + * @name $document + * @requires $window + * @this + * + * @description + * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. + * + * @example + + +
    +

    $document title:

    +

    window.document title:

    +
    +
    + + angular.module('documentExample', []) + .controller('ExampleController', ['$scope', '$document', function($scope, $document) { + $scope.title = $document[0].title; + $scope.windowTitle = angular.element(window.document)[0].title; + }]); + +
    + */ +function $DocumentProvider() { + this.$get = ['$window', function(window) { + return jqLite(window.document); + }]; +} + + +/** + * @private + * @this + * Listens for document visibility change and makes the current status accessible. + */ +function $$IsDocumentHiddenProvider() { + this.$get = ['$document', '$rootScope', function($document, $rootScope) { + var doc = $document[0]; + var hidden = doc && doc.hidden; + + $document.on('visibilitychange', changeListener); + + $rootScope.$on('$destroy', function() { + $document.off('visibilitychange', changeListener); + }); + + function changeListener() { + hidden = doc.hidden; + } + + return function() { + return hidden; + }; + }]; +} + +/** + * @ngdoc service + * @name $exceptionHandler + * @requires ng.$log + * @this + * + * @description + * Any uncaught exception in angular expressions is delegated to this service. + * The default implementation simply delegates to `$log.error` which logs it into + * the browser console. + * + * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by + * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. + * + * ## Example: + * + * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught + * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead + * of `$log.error()`. + * + * ```js + * angular. + * module('exceptionOverwrite', []). + * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) { + * return function myExceptionHandler(exception, cause) { + * logErrorsToBackend(exception, cause); + * $log.warn(exception, cause); + * }; + * }]); + * ``` + * + *
    + * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` + * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} + * (unless executed during a digest). + * + * If you wish, you can manually delegate exceptions, e.g. + * `try { ... } catch(e) { $exceptionHandler(e); }` + * + * @param {Error} exception Exception associated with the error. + * @param {string=} cause Optional information about the context in which + * the error was thrown. + * + */ +function $ExceptionHandlerProvider() { + this.$get = ['$log', function($log) { + return function(exception, cause) { + $log.error.apply($log, arguments); + }; + }]; +} + +var $$ForceReflowProvider = /** @this */ function() { + this.$get = ['$document', function($document) { + return function(domNode) { + //the line below will force the browser to perform a repaint so + //that all the animated elements within the animation frame will + //be properly updated and drawn on screen. This is required to + //ensure that the preparation animation is properly flushed so that + //the active state picks up from there. DO NOT REMOVE THIS LINE. + //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH + //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND + //WILL TAKE YEARS AWAY FROM YOUR LIFE. + if (domNode) { + if (!domNode.nodeType && domNode instanceof jqLite) { + domNode = domNode[0]; + } + } else { + domNode = $document[0].body; + } + return domNode.offsetWidth + 1; + }; + }]; +}; + +var APPLICATION_JSON = 'application/json'; +var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; +var JSON_START = /^\[|^\{(?!\{)/; +var JSON_ENDS = { + '[': /]$/, + '{': /}$/ +}; +var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/; +var $httpMinErr = minErr('$http'); + +function serializeValue(v) { + if (isObject(v)) { + return isDate(v) ? v.toISOString() : toJson(v); + } + return v; +} + + +/** @this */ +function $HttpParamSerializerProvider() { + /** + * @ngdoc service + * @name $httpParamSerializer + * @description + * + * Default {@link $http `$http`} params serializer that converts objects to strings + * according to the following rules: + * + * * `{'foo': 'bar'}` results in `foo=bar` + * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) + * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) + * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object) + * + * Note that serializer will sort the request parameters alphabetically. + * */ + + this.$get = function() { + return function ngParamSerializer(params) { + if (!params) return ''; + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || isUndefined(value)) return; + if (isArray(value)) { + forEach(value, function(v) { + parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); + }); + } else { + parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); + } + }); + + return parts.join('&'); + }; + }; +} + +/** @this */ +function $HttpParamSerializerJQLikeProvider() { + /** + * @ngdoc service + * @name $httpParamSerializerJQLike + * + * @description + * + * Alternative {@link $http `$http`} params serializer that follows + * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. + * The serializer will also sort the params alphabetically. + * + * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: + * + * ```js + * $http({ + * url: myUrl, + * method: 'GET', + * params: myParams, + * paramSerializer: '$httpParamSerializerJQLike' + * }); + * ``` + * + * It is also possible to set it as the default `paramSerializer` in the + * {@link $httpProvider#defaults `$httpProvider`}. + * + * Additionally, you can inject the serializer and use it explicitly, for example to serialize + * form data for submission: + * + * ```js + * .controller(function($http, $httpParamSerializerJQLike) { + * //... + * + * $http({ + * url: myUrl, + * method: 'POST', + * data: $httpParamSerializerJQLike(myData), + * headers: { + * 'Content-Type': 'application/x-www-form-urlencoded' + * } + * }); + * + * }); + * ``` + * + * */ + this.$get = function() { + return function jQueryLikeParamSerializer(params) { + if (!params) return ''; + var parts = []; + serialize(params, '', true); + return parts.join('&'); + + function serialize(toSerialize, prefix, topLevel) { + if (toSerialize === null || isUndefined(toSerialize)) return; + if (isArray(toSerialize)) { + forEach(toSerialize, function(value, index) { + serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); + }); + } else if (isObject(toSerialize) && !isDate(toSerialize)) { + forEachSorted(toSerialize, function(value, key) { + serialize(value, prefix + + (topLevel ? '' : '[') + + key + + (topLevel ? '' : ']')); + }); + } else { + parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); + } + } + }; + }; +} + +function defaultHttpResponseTransform(data, headers) { + if (isString(data)) { + // Strip json vulnerability protection prefix and trim whitespace + var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); + + if (tempData) { + var contentType = headers('Content-Type'); + if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { + data = fromJson(tempData); + } + } + } + + return data; +} + +function isJsonLike(str) { + var jsonStart = str.match(JSON_START); + return jsonStart && JSON_ENDS[jsonStart[0]].test(str); +} + +/** + * Parse headers into key value object + * + * @param {string} headers Raw headers as a string + * @returns {Object} Parsed headers as key value object + */ +function parseHeaders(headers) { + var parsed = createMap(), i; + + function fillInParsed(key, val) { + if (key) { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + } + + if (isString(headers)) { + forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); + }); + } else if (isObject(headers)) { + forEach(headers, function(headerVal, headerKey) { + fillInParsed(lowercase(headerKey), trim(headerVal)); + }); + } + + return parsed; +} + + +/** + * Returns a function that provides access to parsed headers. + * + * Headers are lazy parsed when first requested. + * @see parseHeaders + * + * @param {(string|Object)} headers Headers to provide access to. + * @returns {function(string=)} Returns a getter function which if called with: + * + * - if called with an argument returns a single header value or null + * - if called with no arguments returns an object containing all headers. + */ +function headersGetter(headers) { + var headersObj; + + return function(name) { + if (!headersObj) headersObj = parseHeaders(headers); + + if (name) { + var value = headersObj[lowercase(name)]; + if (value === undefined) { + value = null; + } + return value; + } + + return headersObj; + }; +} + + +/** + * Chain all given functions + * + * This function is used for both request and response transforming + * + * @param {*} data Data to transform. + * @param {function(string=)} headers HTTP headers getter fn. + * @param {number} status HTTP status code of the response. + * @param {(Function|Array.)} fns Function or an array of functions. + * @returns {*} Transformed data. + */ +function transformData(data, headers, status, fns) { + if (isFunction(fns)) { + return fns(data, headers, status); + } + + forEach(fns, function(fn) { + data = fn(data, headers, status); + }); + + return data; +} + + +function isSuccess(status) { + return 200 <= status && status < 300; +} + + +/** + * @ngdoc provider + * @name $httpProvider + * @this + * + * @description + * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. + * */ +function $HttpProvider() { + /** + * @ngdoc property + * @name $httpProvider#defaults + * @description + * + * Object containing default values for all {@link ng.$http $http} requests. + * + * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with + * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses + * by default. See {@link $http#caching $http Caching} for more information. + * + * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. + * Defaults value is `'XSRF-TOKEN'`. + * + * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the + * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. + * + * - **`defaults.headers`** - {Object} - Default headers for all $http requests. + * Refer to {@link ng.$http#setting-http-headers $http} for documentation on + * setting default headers. + * - **`defaults.headers.common`** + * - **`defaults.headers.post`** + * - **`defaults.headers.put`** + * - **`defaults.headers.patch`** + * + * + * - **`defaults.paramSerializer`** - `{string|function(Object):string}` - A function + * used to the prepare string representation of request parameters (specified as an object). + * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. + * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. + * + * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the + * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the + * {@link $jsonpCallbacks} service. Defaults to `'callback'`. + * + **/ + var defaults = this.defaults = { + // transform incoming response data + transformResponse: [defaultHttpResponseTransform], + + // transform outgoing request data + transformRequest: [function(d) { + return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; + }], + + // default headers + headers: { + common: { + 'Accept': 'application/json, text/plain, */*' + }, + post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), + put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), + patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) + }, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', + + paramSerializer: '$httpParamSerializer', + + jsonpCallbackParam: 'callback' + }; + + var useApplyAsync = false; + /** + * @ngdoc method + * @name $httpProvider#useApplyAsync + * @description + * + * Configure $http service to combine processing of multiple http responses received at around + * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in + * significant performance improvement for bigger applications that make many HTTP requests + * concurrently (common during application bootstrap). + * + * Defaults to false. If no value is specified, returns the current configured value. + * + * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred + * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window + * to load and share the same digest cycle. + * + * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. + * otherwise, returns the current configured value. + **/ + this.useApplyAsync = function(value) { + if (isDefined(value)) { + useApplyAsync = !!value; + return this; + } + return useApplyAsync; + }; + + /** + * @ngdoc property + * @name $httpProvider#interceptors + * @description + * + * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} + * pre-processing of request or postprocessing of responses. + * + * These service factories are ordered by request, i.e. they are applied in the same order as the + * array, on request, but reverse order, on response. + * + * {@link ng.$http#interceptors Interceptors detailed info} + **/ + var interceptorFactories = this.interceptors = []; + + this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce', + function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) { + + var defaultCache = $cacheFactory('$http'); + + /** + * Make sure that default param serializer is exposed as a function + */ + defaults.paramSerializer = isString(defaults.paramSerializer) ? + $injector.get(defaults.paramSerializer) : defaults.paramSerializer; + + /** + * Interceptors stored in reverse order. Inner interceptors before outer interceptors. + * The reversal is needed so that we can build up the interception chain around the + * server request. + */ + var reversedInterceptors = []; + + forEach(interceptorFactories, function(interceptorFactory) { + reversedInterceptors.unshift(isString(interceptorFactory) + ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); + }); + + /** + * @ngdoc service + * @kind function + * @name $http + * @requires ng.$httpBackend + * @requires $cacheFactory + * @requires $rootScope + * @requires $q + * @requires $injector + * + * @description + * The `$http` service is a core Angular service that facilitates communication with the remote + * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) + * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). + * + * For unit testing applications that use `$http` service, see + * {@link ngMock.$httpBackend $httpBackend mock}. + * + * For a higher level of abstraction, please check out the {@link ngResource.$resource + * $resource} service. + * + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by + * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage + * it is important to familiarize yourself with these APIs and the guarantees they provide. + * + * + * ## General usage + * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} — + * that is used to generate an HTTP request and returns a {@link ng.$q promise}. + * + * ```js + * // Simple GET request example: + * $http({ + * method: 'GET', + * url: '/someUrl' + * }).then(function successCallback(response) { + * // this callback will be called asynchronously + * // when the response is available + * }, function errorCallback(response) { + * // called asynchronously if an error occurs + * // or server returns response with an error status. + * }); + * ``` + * + * The response object has these properties: + * + * - **data** – `{string|Object}` – The response body transformed with the transform + * functions. + * - **status** – `{number}` – HTTP status code of the response. + * - **headers** – `{function([headerName])}` – Header getter function. + * - **config** – `{Object}` – The configuration object that was used to generate the request. + * - **statusText** – `{string}` – HTTP status text of the response. + * + * A response status code between 200 and 299 is considered a success status and will result in + * the success callback being called. Any response status code outside of that range is + * considered an error status and will result in the error callback being called. + * Also, status codes less than -1 are normalized to zero. -1 usually means the request was + * aborted, e.g. using a `config.timeout`. + * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning + * that the outcome (success or error) will be determined by the final response status code. + * + * + * ## Shortcut methods + * + * Shortcut methods are also available. All shortcut methods require passing in the URL, and + * request data must be passed in for POST/PUT requests. An optional config can be passed as the + * last argument. + * + * ```js + * $http.get('/someUrl', config).then(successCallback, errorCallback); + * $http.post('/someUrl', data, config).then(successCallback, errorCallback); + * ``` + * + * Complete list of shortcut methods: + * + * - {@link ng.$http#get $http.get} + * - {@link ng.$http#head $http.head} + * - {@link ng.$http#post $http.post} + * - {@link ng.$http#put $http.put} + * - {@link ng.$http#delete $http.delete} + * - {@link ng.$http#jsonp $http.jsonp} + * - {@link ng.$http#patch $http.patch} + * + * + * ## Writing Unit Tests that use $http + * When unit testing (using {@link ngMock ngMock}), it is necessary to call + * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending + * request using trained responses. + * + * ``` + * $httpBackend.expectGET(...); + * $http.get(...); + * $httpBackend.flush(); + * ``` + * + * ## Setting HTTP Headers + * + * The $http service will automatically add certain HTTP headers to all requests. These defaults + * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration + * object, which currently contains this default configuration: + * + * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): + * - Accept: application/json, text/plain, \*/\* + * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) + * - `Content-Type: application/json` + * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) + * - `Content-Type: application/json` + * + * To add or overwrite these defaults, simply add or remove a property from these configuration + * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object + * with the lowercased HTTP method name as the key, e.g. + * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. + * + * The defaults can also be set at runtime via the `$http.defaults` object in the same + * fashion. For example: + * + * ``` + * module.run(function($http) { + * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; + * }); + * ``` + * + * In addition, you can supply a `headers` property in the config object passed when + * calling `$http(config)`, which overrides the defaults without changing them globally. + * + * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, + * Use the `headers` property, setting the desired header to `undefined`. For example: + * + * ```js + * var req = { + * method: 'POST', + * url: 'http://example.com', + * headers: { + * 'Content-Type': undefined + * }, + * data: { test: 'test' } + * } + * + * $http(req).then(function(){...}, function(){...}); + * ``` + * + * ## Transforming Requests and Responses + * + * Both requests and responses can be transformed using transformation functions: `transformRequest` + * and `transformResponse`. These properties can be a single function that returns + * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, + * which allows you to `push` or `unshift` a new transformation function into the transformation chain. + * + *
    + * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. + * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). + * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest + * function will be reflected on the scope and in any templates where the object is data-bound. + * To prevent this, transform functions should have no side-effects. + * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. + *
    + * + * ### Default Transformations + * + * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and + * `defaults.transformResponse` properties. If a request does not provide its own transformations + * then these will be applied. + * + * You can augment or replace the default transformations by modifying these properties by adding to or + * replacing the array. + * + * Angular provides the following default transformations: + * + * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): + * + * - If the `data` property of the request configuration object contains an object, serialize it + * into JSON format. + * + * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): + * + * - If XSRF prefix is detected, strip it (see Security Considerations section below). + * - If JSON response is detected, deserialize it using a JSON parser. + * + * + * ### Overriding the Default Transformations Per Request + * + * If you wish to override the request/response transformations only for a single request then provide + * `transformRequest` and/or `transformResponse` properties on the configuration object passed + * into `$http`. + * + * Note that if you provide these properties on the config object the default transformations will be + * overwritten. If you wish to augment the default transformations then you must include them in your + * local transformation array. + * + * The following code demonstrates adding a new response transformation to be run after the default response + * transformations have been run. + * + * ```js + * function appendTransform(defaults, transform) { + * + * // We can't guarantee that the default transformation is an array + * defaults = angular.isArray(defaults) ? defaults : [defaults]; + * + * // Append the new transformation to the defaults + * return defaults.concat(transform); + * } + * + * $http({ + * url: '...', + * method: 'GET', + * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { + * return doTransform(value); + * }) + * }); + * ``` + * + * + * ## Caching + * + * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must + * set the config.cache value or the default cache value to TRUE or to a cache object (created + * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes + * precedence over the default cache value. + * + * In order to: + * * cache all responses - set the default cache value to TRUE or to a cache object + * * cache a specific response - set config.cache value to TRUE or to a cache object + * + * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, + * then the default `$cacheFactory("$http")` object is used. + * + * The default cache value can be set by updating the + * {@link ng.$http#defaults `$http.defaults.cache`} property or the + * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. + * + * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using + * the relevant cache object. The next time the same request is made, the response is returned + * from the cache without sending a request to the server. + * + * Take note that: + * + * * Only GET and JSONP requests are cached. + * * The cache key is the request URL including search parameters; headers are not considered. + * * Cached responses are returned asynchronously, in the same way as responses from the server. + * * If multiple identical requests are made using the same cache, which is not yet populated, + * one request will be made to the server and remaining requests will return the same response. + * * A cache-control header on the response does not affect if or how responses are cached. + * + * + * ## Interceptors + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication, or any kind of synchronous or + * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be + * able to intercept requests before they are handed to the server and + * responses before they are handed over to the application code that + * initiated these requests. The interceptors leverage the {@link ng.$q + * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. + * + * The interceptors are service factories that are registered with the `$httpProvider` by + * adding them to the `$httpProvider.interceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor. + * + * There are two kinds of interceptors (and two kinds of rejection interceptors): + * + * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to + * modify the `config` object or create a new one. The function needs to return the `config` + * object directly, or a promise containing the `config` or a new `config` object. + * * `requestError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * * `response`: interceptors get called with http `response` object. The function is free to + * modify the `response` object or create a new one. The function needs to return the `response` + * object directly, or as a promise containing the `response` or a new `response` object. + * * `responseError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * + * + * ```js + * // register the interceptor as a service + * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { + * return { + * // optional method + * 'request': function(config) { + * // do something on success + * return config; + * }, + * + * // optional method + * 'requestError': function(rejection) { + * // do something on error + * if (canRecover(rejection)) { + * return responseOrNewPromise + * } + * return $q.reject(rejection); + * }, + * + * + * + * // optional method + * 'response': function(response) { + * // do something on success + * return response; + * }, + * + * // optional method + * 'responseError': function(rejection) { + * // do something on error + * if (canRecover(rejection)) { + * return responseOrNewPromise + * } + * return $q.reject(rejection); + * } + * }; + * }); + * + * $httpProvider.interceptors.push('myHttpInterceptor'); + * + * + * // alternatively, register the interceptor via an anonymous factory + * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { + * return { + * 'request': function(config) { + * // same as above + * }, + * + * 'response': function(response) { + * // same as above + * } + * }; + * }); + * ``` + * + * ## Security Considerations + * + * When designing web applications, consider security threats from: + * + * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) + * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) + * + * Both server and the client must cooperate in order to eliminate these threats. Angular comes + * pre-configured with strategies that address these issues, but for this to work backend server + * cooperation is required. + * + * ### JSON Vulnerability Protection + * + * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) + * allows third party website to turn your JSON resource URL into + * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To + * counter this your server can prefix all JSON requests with following string `")]}',\n"`. + * Angular will automatically strip the prefix before processing it as JSON. + * + * For example if your server needs to return: + * ```js + * ['one','two'] + * ``` + * + * which is vulnerable to attack, your server can return: + * ```js + * )]}', + * ['one','two'] + * ``` + * + * Angular will strip the prefix, before processing the JSON. + * + * + * ### Cross Site Request Forgery (XSRF) Protection + * + * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by + * which the attacker can trick an authenticated user into unknowingly executing actions on your + * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the + * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP + * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the + * cookie, your server can be assured that the XHR came from JavaScript running on your domain. + * The header will not be set for cross-domain requests. + * + * To take advantage of this, your server needs to set a token in a JavaScript readable session + * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the + * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure + * that only JavaScript running on your domain could have sent the request. The token must be + * unique for each user and must be verifiable by the server (to prevent the JavaScript from + * making up its own tokens). We recommend that the token is a digest of your site's + * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) + * for added security. + * + * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName + * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, + * or the per-request config object. + * + * In order to prevent collisions in environments where multiple Angular apps share the + * same domain or subdomain, we recommend that each application uses unique cookie name. + * + * @param {object} config Object describing the request to be made and how it should be + * processed. The object has following properties: + * + * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) + * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. + * - **params** – `{Object.}` – Map of strings or objects which will be serialized + * with the `paramSerializer` and appended as GET parameters. + * - **data** – `{string|Object}` – Data to be sent as the request message data. + * - **headers** – `{Object}` – Map of strings or functions which return strings representing + * HTTP headers to send to the server. If the return value of a function is null, the + * header will not be sent. Functions accept a config object as an argument. + * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. + * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. + * The handler will be called in the context of a `$apply` block. + * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload + * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. + * The handler will be called in the context of a `$apply` block. + * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. + * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. + * - **transformRequest** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} + * - **transformResponse** – + * `{function(data, headersGetter, status)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body, headers and status and returns its transformed (typically deserialized) version. + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} + * - **paramSerializer** - `{string|function(Object):string}` - A function used to + * prepare the string representation of request parameters (specified as an object). + * If specified as string, it is interpreted as function registered with the + * {@link $injector $injector}, which means you can create your own serializer + * by registering it as a {@link auto.$provide#service service}. + * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; + * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} + * - **cache** – `{boolean|Object}` – A boolean value or object created with + * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. + * See {@link $http#caching $http Caching} for more information. + * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} + * that should abort the request when resolved. + * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) + * for more information. + * - **responseType** - `{string}` - see + * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). + * + * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object + * when the request succeeds or fails. + * + * + * @property {Array.} pendingRequests Array of config objects for currently pending + * requests. This is primarily meant to be used for debugging purposes. + * + * + * @example + + +
    + + +
    + + + +
    http status code: {{status}}
    +
    http response data: {{data}}
    +
    +
    + + angular.module('httpExample', []) + .config(['$sceDelegateProvider', function($sceDelegateProvider) { + // We must whitelist the JSONP endpoint that we are using to show that we trust it + $sceDelegateProvider.resourceUrlWhitelist([ + 'self', + 'https://angularjs.org/**' + ]); + }]) + .controller('FetchController', ['$scope', '$http', '$templateCache', + function($scope, $http, $templateCache) { + $scope.method = 'GET'; + $scope.url = 'http-hello.html'; + + $scope.fetch = function() { + $scope.code = null; + $scope.response = null; + + $http({method: $scope.method, url: $scope.url, cache: $templateCache}). + then(function(response) { + $scope.status = response.status; + $scope.data = response.data; + }, function(response) { + $scope.data = response.data || 'Request failed'; + $scope.status = response.status; + }); + }; + + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + }]); + + + Hello, $http! + + + var status = element(by.binding('status')); + var data = element(by.binding('data')); + var fetchBtn = element(by.id('fetchbtn')); + var sampleGetBtn = element(by.id('samplegetbtn')); + var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); + + it('should make an xhr GET request', function() { + sampleGetBtn.click(); + fetchBtn.click(); + expect(status.getText()).toMatch('200'); + expect(data.getText()).toMatch(/Hello, \$http!/); + }); + +// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 +// it('should make a JSONP request to angularjs.org', function() { +// var sampleJsonpBtn = element(by.id('samplejsonpbtn')); +// sampleJsonpBtn.click(); +// fetchBtn.click(); +// expect(status.getText()).toMatch('200'); +// expect(data.getText()).toMatch(/Super Hero!/); +// }); + + it('should make JSONP request to invalid URL and invoke the error handler', + function() { + invalidJsonpBtn.click(); + fetchBtn.click(); + expect(status.getText()).toMatch('0'); + expect(data.getText()).toMatch('Request failed'); + }); + +
    + */ + function $http(requestConfig) { + + if (!isObject(requestConfig)) { + throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); + } + + if (!isString($sce.valueOf(requestConfig.url))) { + throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url); + } + + var config = extend({ + method: 'get', + transformRequest: defaults.transformRequest, + transformResponse: defaults.transformResponse, + paramSerializer: defaults.paramSerializer, + jsonpCallbackParam: defaults.jsonpCallbackParam + }, requestConfig); + + config.headers = mergeHeaders(requestConfig); + config.method = uppercase(config.method); + config.paramSerializer = isString(config.paramSerializer) ? + $injector.get(config.paramSerializer) : config.paramSerializer; + + $browser.$$incOutstandingRequestCount(); + + var requestInterceptors = []; + var responseInterceptors = []; + var promise = $q.resolve(config); + + // apply interceptors + forEach(reversedInterceptors, function(interceptor) { + if (interceptor.request || interceptor.requestError) { + requestInterceptors.unshift(interceptor.request, interceptor.requestError); + } + if (interceptor.response || interceptor.responseError) { + responseInterceptors.push(interceptor.response, interceptor.responseError); + } + }); + + promise = chainInterceptors(promise, requestInterceptors); + promise = promise.then(serverRequest); + promise = chainInterceptors(promise, responseInterceptors); + promise = promise.finally(completeOutstandingRequest); + + return promise; + + + function chainInterceptors(promise, interceptors) { + for (var i = 0, ii = interceptors.length; i < ii;) { + var thenFn = interceptors[i++]; + var rejectFn = interceptors[i++]; + + promise = promise.then(thenFn, rejectFn); + } + + interceptors.length = 0; + + return promise; + } + + function completeOutstandingRequest() { + $browser.$$completeOutstandingRequest(noop); + } + + function executeHeaderFns(headers, config) { + var headerContent, processedHeaders = {}; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(config); + if (headerContent != null) { + processedHeaders[header] = headerContent; + } + } else { + processedHeaders[header] = headerFn; + } + }); + + return processedHeaders; + } + + function mergeHeaders(config) { + var defHeaders = defaults.headers, + reqHeaders = extend({}, config.headers), + defHeaderName, lowercaseDefHeaderName, reqHeaderName; + + defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); + + // using for-in instead of forEach to avoid unnecessary iteration after header has been found + defaultHeadersIteration: + for (defHeaderName in defHeaders) { + lowercaseDefHeaderName = lowercase(defHeaderName); + + for (reqHeaderName in reqHeaders) { + if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { + continue defaultHeadersIteration; + } + } + + reqHeaders[defHeaderName] = defHeaders[defHeaderName]; + } + + // execute if header value is a function for merged headers + return executeHeaderFns(reqHeaders, shallowCopy(config)); + } + + function serverRequest(config) { + var headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); + + // strip content-type if data is undefined + if (isUndefined(reqData)) { + forEach(headers, function(value, header) { + if (lowercase(header) === 'content-type') { + delete headers[header]; + } + }); + } + + if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { + config.withCredentials = defaults.withCredentials; + } + + // send request + return sendReq(config, reqData).then(transformResponse, transformResponse); + } + + function transformResponse(response) { + // make a copy since the response must be cacheable + var resp = extend({}, response); + resp.data = transformData(response.data, response.headers, response.status, + config.transformResponse); + return (isSuccess(response.status)) + ? resp + : $q.reject(resp); + } + } + + $http.pendingRequests = []; + + /** + * @ngdoc method + * @name $http#get + * + * @description + * Shortcut method to perform `GET` request. + * + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#delete + * + * @description + * Shortcut method to perform `DELETE` request. + * + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#head + * + * @description + * Shortcut method to perform `HEAD` request. + * + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#jsonp + * + * @description + * Shortcut method to perform `JSONP` request. + * + * Note that, since JSONP requests are sensitive because the response is given full access to the browser, + * the url must be declared, via {@link $sce} as a trusted resource URL. + * You can trust a URL by adding it to the whitelist via + * {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or + * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}. + * + * JSONP requests must specify a callback to be used in the response from the server. This callback + * is passed as a query parameter in the request. You must specify the name of this parameter by + * setting the `jsonpCallbackParam` property on the request config object. + * + * ``` + * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'}) + * ``` + * + * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`. + * Initially this is set to `'callback'`. + * + *
    + * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback + * parameter value should go. + *
    + * + * If you would like to customise where and how the callbacks are stored then try overriding + * or decorating the {@link $jsonpCallbacks} service. + * + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethods('get', 'delete', 'head', 'jsonp'); + + /** + * @ngdoc method + * @name $http#post + * + * @description + * Shortcut method to perform `POST` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#put + * + * @description + * Shortcut method to perform `PUT` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name $http#patch + * + * @description + * Shortcut method to perform `PATCH` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethodsWithData('post', 'put', 'patch'); + + /** + * @ngdoc property + * @name $http#defaults + * + * @description + * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of + * default headers, withCredentials as well as request and response transformations. + * + * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. + */ + $http.defaults = defaults; + + + return $http; + + + function createShortMethods(names) { + forEach(arguments, function(name) { + $http[name] = function(url, config) { + return $http(extend({}, config || {}, { + method: name, + url: url + })); + }; + }); + } + + + function createShortMethodsWithData(name) { + forEach(arguments, function(name) { + $http[name] = function(url, data, config) { + return $http(extend({}, config || {}, { + method: name, + url: url, + data: data + })); + }; + }); + } + + + /** + * Makes the request. + * + * !!! ACCESSES CLOSURE VARS: + * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests + */ + function sendReq(config, reqData) { + var deferred = $q.defer(), + promise = deferred.promise, + cache, + cachedResp, + reqHeaders = config.headers, + isJsonp = lowercase(config.method) === 'jsonp', + url = config.url; + + if (isJsonp) { + // JSONP is a pretty sensitive operation where we're allowing a script to have full access to + // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL. + url = $sce.getTrustedResourceUrl(url); + } else if (!isString(url)) { + // If it is not a string then the URL must be a $sce trusted object + url = $sce.valueOf(url); + } + + url = buildUrl(url, config.paramSerializer(config.params)); + + if (isJsonp) { + // Check the url and add the JSONP callback placeholder + url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam); + } + + $http.pendingRequests.push(config); + promise.then(removePendingReq, removePendingReq); + + if ((config.cache || defaults.cache) && config.cache !== false && + (config.method === 'GET' || config.method === 'JSONP')) { + cache = isObject(config.cache) ? config.cache + : isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + if (cache) { + cachedResp = cache.get(url); + if (isDefined(cachedResp)) { + if (isPromiseLike(cachedResp)) { + // cached request has already been sent, but there is no response yet + cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); + } else { + // serving from cache + if (isArray(cachedResp)) { + resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); + } else { + resolvePromise(cachedResp, 200, {}, 'OK'); + } + } + } else { + // put the promise for the non-transformed response into cache as a placeholder + cache.put(url, promise); + } + } + + + // if we won't have the response in cache, set the xsrf headers and + // send the request to the backend + if (isUndefined(cachedResp)) { + var xsrfValue = urlIsSameOrigin(config.url) + ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] + : undefined; + if (xsrfValue) { + reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; + } + + $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, + config.withCredentials, config.responseType, + createApplyHandlers(config.eventHandlers), + createApplyHandlers(config.uploadEventHandlers)); + } + + return promise; + + function createApplyHandlers(eventHandlers) { + if (eventHandlers) { + var applyHandlers = {}; + forEach(eventHandlers, function(eventHandler, key) { + applyHandlers[key] = function(event) { + if (useApplyAsync) { + $rootScope.$applyAsync(callEventHandler); + } else if ($rootScope.$$phase) { + callEventHandler(); + } else { + $rootScope.$apply(callEventHandler); + } + + function callEventHandler() { + eventHandler(event); + } + }; + }); + return applyHandlers; + } + } + + + /** + * Callback registered to $httpBackend(): + * - caches the response if desired + * - resolves the raw $http promise + * - calls $apply + */ + function done(status, response, headersString, statusText) { + if (cache) { + if (isSuccess(status)) { + cache.put(url, [status, response, parseHeaders(headersString), statusText]); + } else { + // remove promise from the cache + cache.remove(url); + } + } + + function resolveHttpPromise() { + resolvePromise(response, status, headersString, statusText); + } + + if (useApplyAsync) { + $rootScope.$applyAsync(resolveHttpPromise); + } else { + resolveHttpPromise(); + if (!$rootScope.$$phase) $rootScope.$apply(); + } + } + + + /** + * Resolves the raw $http promise. + */ + function resolvePromise(response, status, headers, statusText) { + //status: HTTP response status code, 0, -1 (aborted by timeout / promise) + status = status >= -1 ? status : 0; + + (isSuccess(status) ? deferred.resolve : deferred.reject)({ + data: response, + status: status, + headers: headersGetter(headers), + config: config, + statusText: statusText + }); + } + + function resolvePromiseWithResult(result) { + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); + } + + function removePendingReq() { + var idx = $http.pendingRequests.indexOf(config); + if (idx !== -1) $http.pendingRequests.splice(idx, 1); + } + } + + + function buildUrl(url, serializedParams) { + if (serializedParams.length > 0) { + url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams; + } + return url; + } + + function sanitizeJsonpCallbackParam(url, key) { + if (/[&?][^=]+=JSON_CALLBACK/.test(url)) { + // Throw if the url already contains a reference to JSON_CALLBACK + throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url); + } + + var callbackParamRegex = new RegExp('[&?]' + key + '='); + if (callbackParamRegex.test(url)) { + // Throw if the callback param was already provided + throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', key, url); + } + + // Add in the JSON_CALLBACK callback param value + url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK'; + + return url; + } + }]; +} + +/** + * @ngdoc service + * @name $xhrFactory + * @this + * + * @description + * Factory function used to create XMLHttpRequest objects. + * + * Replace or decorate this service to create your own custom XMLHttpRequest objects. + * + * ``` + * angular.module('myApp', []) + * .factory('$xhrFactory', function() { + * return function createXhr(method, url) { + * return new window.XMLHttpRequest({mozSystem: true}); + * }; + * }); + * ``` + * + * @param {string} method HTTP method of the request (GET, POST, PUT, ..) + * @param {string} url URL of the request. + */ +function $xhrFactoryProvider() { + this.$get = function() { + return function createXhr() { + return new window.XMLHttpRequest(); + }; + }; +} + +/** + * @ngdoc service + * @name $httpBackend + * @requires $jsonpCallbacks + * @requires $document + * @requires $xhrFactory + * @this + * + * @description + * HTTP backend used by the {@link ng.$http service} that delegates to + * XMLHttpRequest object or JSONP and deals with browser incompatibilities. + * + * You should never need to use this service directly, instead use the higher-level abstractions: + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. + * + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock + * $httpBackend} which can be trained with responses. + */ +function $HttpBackendProvider() { + this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) { + return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]); + }]; +} + +function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { + // TODO(vojta): fix the signature + return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { + url = url || $browser.url(); + + if (lowercase(method) === 'jsonp') { + var callbackPath = callbacks.createCallback(url); + var jsonpDone = jsonpReq(url, callbackPath, function(status, text) { + // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING) + var response = (status === 200) && callbacks.getResponse(callbackPath); + completeRequest(callback, status, response, '', text); + callbacks.removeCallback(callbackPath); + }); + } else { + + var xhr = createXhr(method, url); + + xhr.open(method, url, true); + forEach(headers, function(value, key) { + if (isDefined(value)) { + xhr.setRequestHeader(key, value); + } + }); + + xhr.onload = function requestLoaded() { + var statusText = xhr.statusText || ''; + + // responseText is the old-school way of retrieving response (supported by IE9) + // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) + var response = ('response' in xhr) ? xhr.response : xhr.responseText; + + // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) + var status = xhr.status === 1223 ? 204 : xhr.status; + + // fix status code when it is 0 (0 status is undocumented). + // Occurs when accessing file resources or on Android 4.1 stock browser + // while retrieving files from application cache. + if (status === 0) { + status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0; + } + + completeRequest(callback, + status, + response, + xhr.getAllResponseHeaders(), + statusText); + }; + + var requestError = function() { + // The response is always empty + // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error + completeRequest(callback, -1, null, null, ''); + }; + + xhr.onerror = requestError; + xhr.onabort = requestError; + xhr.ontimeout = requestError; + + forEach(eventHandlers, function(value, key) { + xhr.addEventListener(key, value); + }); + + forEach(uploadEventHandlers, function(value, key) { + xhr.upload.addEventListener(key, value); + }); + + if (withCredentials) { + xhr.withCredentials = true; + } + + if (responseType) { + try { + xhr.responseType = responseType; + } catch (e) { + // WebKit added support for the json responseType value on 09/03/2013 + // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are + // known to throw when setting the value "json" as the response type. Other older + // browsers implementing the responseType + // + // The json response type can be ignored if not supported, because JSON payloads are + // parsed on the client-side regardless. + if (responseType !== 'json') { + throw e; + } + } + } + + xhr.send(isUndefined(post) ? null : post); + } + + if (timeout > 0) { + var timeoutId = $browserDefer(timeoutRequest, timeout); + } else if (isPromiseLike(timeout)) { + timeout.then(timeoutRequest); + } + + + function timeoutRequest() { + if (jsonpDone) { + jsonpDone(); + } + if (xhr) { + xhr.abort(); + } + } + + function completeRequest(callback, status, response, headersString, statusText) { + // cancel timeout and subsequent timeout promise resolution + if (isDefined(timeoutId)) { + $browserDefer.cancel(timeoutId); + } + jsonpDone = xhr = null; + + callback(status, response, headersString, statusText); + } + }; + + function jsonpReq(url, callbackPath, done) { + url = url.replace('JSON_CALLBACK', callbackPath); + // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: + // - fetches local scripts via XHR and evals them + // - adds and immediately removes script elements from the document + var script = rawDocument.createElement('script'), callback = null; + script.type = 'text/javascript'; + script.src = url; + script.async = true; + + callback = function(event) { + script.removeEventListener('load', callback); + script.removeEventListener('error', callback); + rawDocument.body.removeChild(script); + script = null; + var status = -1; + var text = 'unknown'; + + if (event) { + if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) { + event = { type: 'error' }; + } + text = event.type; + status = event.type === 'error' ? 404 : 200; + } + + if (done) { + done(status, text); + } + }; + + script.addEventListener('load', callback); + script.addEventListener('error', callback); + rawDocument.body.appendChild(script); + return callback; + } +} + +var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); +$interpolateMinErr.throwNoconcat = function(text) { + throw $interpolateMinErr('noconcat', + 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' + + 'interpolations that concatenate multiple expressions when a trusted value is ' + + 'required. See http://docs.angularjs.org/api/ng.$sce', text); +}; + +$interpolateMinErr.interr = function(text, err) { + return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString()); +}; + +/** + * @ngdoc provider + * @name $interpolateProvider + * @this + * + * @description + * + * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. + * + *
    + * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular + * template within a Python Jinja template (or any other template language). Mixing templating + * languages is **very dangerous**. The embedding template language will not safely escape Angular + * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) + * security bugs! + *
    + * + * @example + + + +
    + //demo.label// +
    +
    + + it('should interpolate binding with custom symbols', function() { + expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); + }); + +
    + */ +function $InterpolateProvider() { + var startSymbol = '{{'; + var endSymbol = '}}'; + + /** + * @ngdoc method + * @name $interpolateProvider#startSymbol + * @description + * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. + * + * @param {string=} value new value to set the starting symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.startSymbol = function(value) { + if (value) { + startSymbol = value; + return this; + } else { + return startSymbol; + } + }; + + /** + * @ngdoc method + * @name $interpolateProvider#endSymbol + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * @param {string=} value new value to set the ending symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.endSymbol = function(value) { + if (value) { + endSymbol = value; + return this; + } else { + return endSymbol; + } + }; + + + this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { + var startSymbolLength = startSymbol.length, + endSymbolLength = endSymbol.length, + escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), + escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); + + function escape(ch) { + return '\\\\\\' + ch; + } + + function unescapeText(text) { + return text.replace(escapedStartRegexp, startSymbol). + replace(escapedEndRegexp, endSymbol); + } + + // TODO: this is the same as the constantWatchDelegate in parse.js + function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { + var unwatch = scope.$watch(function constantInterpolateWatch(scope) { + unwatch(); + return constantInterp(scope); + }, listener, objectEquality); + return unwatch; + } + + /** + * @ngdoc service + * @name $interpolate + * @kind function + * + * @requires $parse + * @requires $sce + * + * @description + * + * Compiles a string with markup into an interpolation function. This service is used by the + * HTML {@link ng.$compile $compile} service for data binding. See + * {@link ng.$interpolateProvider $interpolateProvider} for configuring the + * interpolation markup. + * + * + * ```js + * var $interpolate = ...; // injected + * var exp = $interpolate('Hello {{name | uppercase}}!'); + * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); + * ``` + * + * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is + * `true`, the interpolation function will return `undefined` unless all embedded expressions + * evaluate to a value other than `undefined`. + * + * ```js + * var $interpolate = ...; // injected + * var context = {greeting: 'Hello', name: undefined }; + * + * // default "forgiving" mode + * var exp = $interpolate('{{greeting}} {{name}}!'); + * expect(exp(context)).toEqual('Hello !'); + * + * // "allOrNothing" mode + * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); + * expect(exp(context)).toBeUndefined(); + * context.name = 'Angular'; + * expect(exp(context)).toEqual('Hello Angular!'); + * ``` + * + * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. + * + * #### Escaped Interpolation + * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers + * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). + * It will be rendered as a regular start/end marker, and will not be interpreted as an expression + * or binding. + * + * This enables web-servers to prevent script injection attacks and defacing attacks, to some + * degree, while also enabling code examples to work without relying on the + * {@link ng.directive:ngNonBindable ngNonBindable} directive. + * + * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, + * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all + * interpolation start/end markers with their escaped counterparts.** + * + * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered + * output when the $interpolate service processes the text. So, for HTML elements interpolated + * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter + * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, + * this is typically useful only when user-data is used in rendering a template from the server, or + * when otherwise untrusted data is used by a directive. + * + * + * + *
    + *

    {{apptitle}}: \{\{ username = "defaced value"; \}\} + *

    + *

    {{username}} attempts to inject code which will deface the + * application, but fails to accomplish their task, because the server has correctly + * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) + * characters.

    + *

    Instead, the result of the attempted script injection is visible, and can be removed + * from the database by an administrator.

    + *
    + *
    + *
    + * + * @knownIssue + * It is currently not possible for an interpolated expression to contain the interpolation end + * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e. + * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string. + * + * @knownIssue + * All directives and components must use the standard `{{` `}}` interpolation symbols + * in their templates. If you change the application interpolation symbols the {@link $compile} + * service will attempt to denormalize the standard symbols to the custom symbols. + * The denormalization process is not clever enough to know not to replace instances of the standard + * symbols where they would not normally be treated as interpolation symbols. For example in the following + * code snippet the closing braces of the literal object will get incorrectly denormalized: + * + * ``` + *
    + * ``` + * + * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information. + * + * @param {string} text The text with markup to interpolate. + * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have + * embedded expression in order to return an interpolation function. Strings with no + * embedded expression will return null for the interpolation function. + * @param {string=} trustedContext when provided, the returned function passes the interpolated + * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, + * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that + * provides Strict Contextual Escaping for details. + * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined + * unless all embedded expressions evaluate to a value other than `undefined`. + * @returns {function(context)} an interpolation function which is used to compute the + * interpolated string. The function has these parameters: + * + * - `context`: evaluation context for all expressions embedded in the interpolated text + */ + function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { + // Provide a quick exit and simplified result function for text with no interpolation + if (!text.length || text.indexOf(startSymbol) === -1) { + var constantInterp; + if (!mustHaveExpression) { + var unescapedText = unescapeText(text); + constantInterp = valueFn(unescapedText); + constantInterp.exp = text; + constantInterp.expressions = []; + constantInterp.$$watchDelegate = constantWatchDelegate; + } + return constantInterp; + } + + allOrNothing = !!allOrNothing; + var startIndex, + endIndex, + index = 0, + expressions = [], + parseFns = [], + textLength = text.length, + exp, + concat = [], + expressionPositions = []; + + while (index < textLength) { + if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) { + if (index !== startIndex) { + concat.push(unescapeText(text.substring(index, startIndex))); + } + exp = text.substring(startIndex + startSymbolLength, endIndex); + expressions.push(exp); + parseFns.push($parse(exp, parseStringifyInterceptor)); + index = endIndex + endSymbolLength; + expressionPositions.push(concat.length); + concat.push(''); + } else { + // we did not find an interpolation, so we have to add the remainder to the separators array + if (index !== textLength) { + concat.push(unescapeText(text.substring(index))); + } + break; + } + } + + // Concatenating expressions makes it hard to reason about whether some combination of + // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a + // single expression be used for iframe[src], object[src], etc., we ensure that the value + // that's used is assigned or constructed by some JS code somewhere that is more testable or + // make it obvious that you bound the value to some user controlled value. This helps reduce + // the load when auditing for XSS issues. + if (trustedContext && concat.length > 1) { + $interpolateMinErr.throwNoconcat(text); + } + + if (!mustHaveExpression || expressions.length) { + var compute = function(values) { + for (var i = 0, ii = expressions.length; i < ii; i++) { + if (allOrNothing && isUndefined(values[i])) return; + concat[expressionPositions[i]] = values[i]; + } + return concat.join(''); + }; + + var getValue = function(value) { + return trustedContext ? + $sce.getTrusted(trustedContext, value) : + $sce.valueOf(value); + }; + + return extend(function interpolationFn(context) { + var i = 0; + var ii = expressions.length; + var values = new Array(ii); + + try { + for (; i < ii; i++) { + values[i] = parseFns[i](context); + } + + return compute(values); + } catch (err) { + $exceptionHandler($interpolateMinErr.interr(text, err)); + } + + }, { + // all of these properties are undocumented for now + exp: text, //just for compatibility with regular watchers created via $watch + expressions: expressions, + $$watchDelegate: function(scope, listener) { + var lastValue; + return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) { + var currValue = compute(values); + if (isFunction(listener)) { + listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); + } + lastValue = currValue; + }); + } + }); + } + + function parseStringifyInterceptor(value) { + try { + value = getValue(value); + return allOrNothing && !isDefined(value) ? value : stringify(value); + } catch (err) { + $exceptionHandler($interpolateMinErr.interr(text, err)); + } + } + } + + + /** + * @ngdoc method + * @name $interpolate#startSymbol + * @description + * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. + * + * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change + * the symbol. + * + * @returns {string} start symbol. + */ + $interpolate.startSymbol = function() { + return startSymbol; + }; + + + /** + * @ngdoc method + * @name $interpolate#endSymbol + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change + * the symbol. + * + * @returns {string} end symbol. + */ + $interpolate.endSymbol = function() { + return endSymbol; + }; + + return $interpolate; + }]; +} + +/** @this */ +function $IntervalProvider() { + this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', + function($rootScope, $window, $q, $$q, $browser) { + var intervals = {}; + + + /** + * @ngdoc service + * @name $interval + * + * @description + * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` + * milliseconds. + * + * The return value of registering an interval function is a promise. This promise will be + * notified upon each tick of the interval, and will be resolved after `count` iterations, or + * run indefinitely if `count` is not defined. The value of the notification will be the + * number of iterations that have run. + * To cancel an interval, call `$interval.cancel(promise)`. + * + * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + *
    + * **Note**: Intervals created by this service must be explicitly destroyed when you are finished + * with them. In particular they are not automatically destroyed when a controller's scope or a + * directive's element are destroyed. + * You should take this into consideration and make sure to always cancel the interval at the + * appropriate moment. See the example below for more details on how and when to do this. + *
    + * + * @param {function()} fn A function that should be called repeatedly. If no additional arguments + * are passed (see below), the function is called with the current iteration count. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {promise} A promise which will be notified on each iteration. + * + * @example + * + * + * + * + *
    + *
    + *
    + * Current time is: + *
    + * Blood 1 : {{blood_1}} + * Blood 2 : {{blood_2}} + * + * + * + *
    + *
    + * + *
    + *
    + */ + function interval(fn, delay, count, invokeApply) { + var hasParams = arguments.length > 4, + args = hasParams ? sliceArgs(arguments, 4) : [], + setInterval = $window.setInterval, + clearInterval = $window.clearInterval, + iteration = 0, + skipApply = (isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; + + count = isDefined(count) ? count : 0; + + promise.$$intervalId = setInterval(function tick() { + if (skipApply) { + $browser.defer(callback); + } else { + $rootScope.$evalAsync(callback); + } + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + deferred.resolve(iteration); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + } + + if (!skipApply) $rootScope.$apply(); + + }, delay); + + intervals[promise.$$intervalId] = deferred; + + return promise; + + function callback() { + if (!hasParams) { + fn(iteration); + } else { + fn.apply(null, args); + } + } + } + + + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {Promise=} promise returned by the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled. + */ + interval.cancel = function(promise) { + if (promise && promise.$$intervalId in intervals) { + // Interval cancels should not report as unhandled promise. + intervals[promise.$$intervalId].promise.catch(noop); + intervals[promise.$$intervalId].reject('canceled'); + $window.clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + return true; + } + return false; + }; + + return interval; + }]; +} + +/** + * @ngdoc service + * @name $jsonpCallbacks + * @requires $window + * @description + * This service handles the lifecycle of callbacks to handle JSONP requests. + * Override this service if you wish to customise where the callbacks are stored and + * how they vary compared to the requested url. + */ +var $jsonpCallbacksProvider = /** @this */ function() { + this.$get = ['$window', function($window) { + var callbacks = $window.angular.callbacks; + var callbackMap = {}; + + function createCallback(callbackId) { + var callback = function(data) { + callback.data = data; + callback.called = true; + }; + callback.id = callbackId; + return callback; + } + + return { + /** + * @ngdoc method + * @name $jsonpCallbacks#createCallback + * @param {string} url the url of the JSONP request + * @returns {string} the callback path to send to the server as part of the JSONP request + * @description + * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback + * to pass to the server, which will be used to call the callback with its payload in the JSONP response. + */ + createCallback: function(url) { + var callbackId = '_' + (callbacks.$$counter++).toString(36); + var callbackPath = 'angular.callbacks.' + callbackId; + var callback = createCallback(callbackId); + callbackMap[callbackPath] = callbacks[callbackId] = callback; + return callbackPath; + }, + /** + * @ngdoc method + * @name $jsonpCallbacks#wasCalled + * @param {string} callbackPath the path to the callback that was sent in the JSONP request + * @returns {boolean} whether the callback has been called, as a result of the JSONP response + * @description + * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the + * callback that was passed in the request. + */ + wasCalled: function(callbackPath) { + return callbackMap[callbackPath].called; + }, + /** + * @ngdoc method + * @name $jsonpCallbacks#getResponse + * @param {string} callbackPath the path to the callback that was sent in the JSONP request + * @returns {*} the data received from the response via the registered callback + * @description + * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback + * in the JSONP response. + */ + getResponse: function(callbackPath) { + return callbackMap[callbackPath].data; + }, + /** + * @ngdoc method + * @name $jsonpCallbacks#removeCallback + * @param {string} callbackPath the path to the callback that was sent in the JSONP request + * @description + * {@link $httpBackend} calls this method to remove the callback after the JSONP request has + * completed or timed-out. + */ + removeCallback: function(callbackPath) { + var callback = callbackMap[callbackPath]; + delete callbacks[callback.id]; + delete callbackMap[callbackPath]; + } + }; + }]; +}; + +/** + * @ngdoc service + * @name $locale + * + * @description + * $locale service provides localization rules for various Angular components. As of right now the + * only public api is: + * + * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) + */ + +var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, + DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; +var $locationMinErr = minErr('$location'); + + +/** + * Encode path using encodeUriSegment, ignoring forward slashes + * + * @param {string} path Path to encode + * @returns {string} + */ +function encodePath(path) { + var segments = path.split('/'), + i = segments.length; + + while (i--) { + segments[i] = encodeUriSegment(segments[i]); + } + + return segments.join('/'); +} + +function parseAbsoluteUrl(absoluteUrl, locationObj) { + var parsedUrl = urlResolve(absoluteUrl); + + locationObj.$$protocol = parsedUrl.protocol; + locationObj.$$host = parsedUrl.hostname; + locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; +} + +var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/; +function parseAppUrl(url, locationObj) { + + if (DOUBLE_SLASH_REGEX.test(url)) { + throw $locationMinErr('badpath', 'Invalid url "{0}".', url); + } + + var prefixed = (url.charAt(0) !== '/'); + if (prefixed) { + url = '/' + url; + } + var match = urlResolve(url); + locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? + match.pathname.substring(1) : match.pathname); + locationObj.$$search = parseKeyValue(match.search); + locationObj.$$hash = decodeURIComponent(match.hash); + + // make sure path starts with '/'; + if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') { + locationObj.$$path = '/' + locationObj.$$path; + } +} + +function startsWith(str, search) { + return str.slice(0, search.length) === search; +} + +/** + * + * @param {string} base + * @param {string} url + * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with + * the expected string. + */ +function stripBaseUrl(base, url) { + if (startsWith(url, base)) { + return url.substr(base.length); + } +} + + +function stripHash(url) { + var index = url.indexOf('#'); + return index === -1 ? url : url.substr(0, index); +} + +function trimEmptyHash(url) { + return url.replace(/(#.+)|#$/, '$1'); +} + + +function stripFile(url) { + return url.substr(0, stripHash(url).lastIndexOf('/') + 1); +} + +/* return the server only (scheme://host:port) */ +function serverBase(url) { + return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); +} + + +/** + * LocationHtml5Url represents a URL + * This object is exposed as $location service when HTML5 mode is enabled and supported + * + * @constructor + * @param {string} appBase application base URL + * @param {string} appBaseNoFile application base URL stripped of any filename + * @param {string} basePrefix URL path prefix + */ +function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { + this.$$html5 = true; + basePrefix = basePrefix || ''; + parseAbsoluteUrl(appBase, this); + + + /** + * Parse given HTML5 (regular) URL string into properties + * @param {string} url HTML5 URL + * @private + */ + this.$$parse = function(url) { + var pathUrl = stripBaseUrl(appBaseNoFile, url); + if (!isString(pathUrl)) { + throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, + appBaseNoFile); + } + + parseAppUrl(pathUrl, this); + + if (!this.$$path) { + this.$$path = '/'; + } + + this.$$compose(); + }; + + /** + * Compose url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' + }; + + this.$$parseLinkUrl = function(url, relHref) { + if (relHref && relHref[0] === '#') { + // special case for links to hash fragments: + // keep the old url and only replace the hash fragment + this.hash(relHref.slice(1)); + return true; + } + var appUrl, prevAppUrl; + var rewrittenUrl; + + + if (isDefined(appUrl = stripBaseUrl(appBase, url))) { + prevAppUrl = appUrl; + if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) { + rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl); + } else { + rewrittenUrl = appBase + prevAppUrl; + } + } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) { + rewrittenUrl = appBaseNoFile + appUrl; + } else if (appBaseNoFile === url + '/') { + rewrittenUrl = appBaseNoFile; + } + if (rewrittenUrl) { + this.$$parse(rewrittenUrl); + } + return !!rewrittenUrl; + }; +} + + +/** + * LocationHashbangUrl represents URL + * This object is exposed as $location service when developer doesn't opt into html5 mode. + * It also serves as the base class for html5 mode fallback on legacy browsers. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} appBaseNoFile application base URL stripped of any filename + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { + + parseAbsoluteUrl(appBase, this); + + + /** + * Parse given hashbang URL into properties + * @param {string} url Hashbang URL + * @private + */ + this.$$parse = function(url) { + var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url); + var withoutHashUrl; + + if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { + + // The rest of the URL starts with a hash so we have + // got either a hashbang path or a plain hash fragment + withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl); + if (isUndefined(withoutHashUrl)) { + // There was no hashbang prefix so we just have a hash fragment + withoutHashUrl = withoutBaseUrl; + } + + } else { + // There was no hashbang path nor hash fragment: + // If we are in HTML5 mode we use what is left as the path; + // Otherwise we ignore what is left + if (this.$$html5) { + withoutHashUrl = withoutBaseUrl; + } else { + withoutHashUrl = ''; + if (isUndefined(withoutBaseUrl)) { + appBase = url; + this.replace(); + } + } + } + + parseAppUrl(withoutHashUrl, this); + + this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); + + this.$$compose(); + + /* + * In Windows, on an anchor node on documents loaded from + * the filesystem, the browser will return a pathname + * prefixed with the drive name ('/C:/path') when a + * pathname without a drive is set: + * * a.setAttribute('href', '/foo') + * * a.pathname === '/C:/foo' //true + * + * Inside of Angular, we're always using pathnames that + * do not include drive names for routing. + */ + function removeWindowsDriveName(path, url, base) { + /* + Matches paths for file protocol on windows, + such as /C:/foo/bar, and captures only /foo/bar. + */ + var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; + + var firstPathSegmentMatch; + + //Get the relative path from the input URL. + if (startsWith(url, base)) { + url = url.replace(base, ''); + } + + // The input URL intentionally contains a first path segment that ends with a colon. + if (windowsFilePathExp.exec(url)) { + return path; + } + + firstPathSegmentMatch = windowsFilePathExp.exec(path); + return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; + } + }; + + /** + * Compose hashbang URL and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); + }; + + this.$$parseLinkUrl = function(url, relHref) { + if (stripHash(appBase) === stripHash(url)) { + this.$$parse(url); + return true; + } + return false; + }; +} + + +/** + * LocationHashbangUrl represents URL + * This object is exposed as $location service when html5 history api is enabled but the browser + * does not support it. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} appBaseNoFile application base URL stripped of any filename + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { + this.$$html5 = true; + LocationHashbangUrl.apply(this, arguments); + + this.$$parseLinkUrl = function(url, relHref) { + if (relHref && relHref[0] === '#') { + // special case for links to hash fragments: + // keep the old url and only replace the hash fragment + this.hash(relHref.slice(1)); + return true; + } + + var rewrittenUrl; + var appUrl; + + if (appBase === stripHash(url)) { + rewrittenUrl = url; + } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) { + rewrittenUrl = appBase + hashPrefix + appUrl; + } else if (appBaseNoFile === url + '/') { + rewrittenUrl = appBaseNoFile; + } + if (rewrittenUrl) { + this.$$parse(rewrittenUrl); + } + return !!rewrittenUrl; + }; + + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' + this.$$absUrl = appBase + hashPrefix + this.$$url; + }; + +} + + +var locationPrototype = { + + /** + * Ensure absolute URL is initialized. + * @private + */ + $$absUrl:'', + + /** + * Are we in html5 mode? + * @private + */ + $$html5: false, + + /** + * Has any change been replacing? + * @private + */ + $$replace: false, + + /** + * @ngdoc method + * @name $location#absUrl + * + * @description + * This method is getter only. + * + * Return full URL representation with all segments encoded according to rules specified in + * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var absUrl = $location.absUrl(); + * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" + * ``` + * + * @return {string} full URL + */ + absUrl: locationGetter('$$absUrl'), + + /** + * @ngdoc method + * @name $location#url + * + * @description + * This method is getter / setter. + * + * Return URL (e.g. `/path?a=b#hash`) when called without any parameter. + * + * Change path, search and hash, when called with parameter and return `$location`. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var url = $location.url(); + * // => "/some/path?foo=bar&baz=xoxo" + * ``` + * + * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`) + * @return {string} url + */ + url: function(url) { + if (isUndefined(url)) { + return this.$$url; + } + + var match = PATH_MATCH.exec(url); + if (match[1] || url === '') this.path(decodeURIComponent(match[1])); + if (match[2] || match[1] || url === '') this.search(match[3] || ''); + this.hash(match[5] || ''); + + return this; + }, + + /** + * @ngdoc method + * @name $location#protocol + * + * @description + * This method is getter only. + * + * Return protocol of current URL. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var protocol = $location.protocol(); + * // => "http" + * ``` + * + * @return {string} protocol of current URL + */ + protocol: locationGetter('$$protocol'), + + /** + * @ngdoc method + * @name $location#host + * + * @description + * This method is getter only. + * + * Return host of current URL. + * + * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var host = $location.host(); + * // => "example.com" + * + * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo + * host = $location.host(); + * // => "example.com" + * host = location.host; + * // => "example.com:8080" + * ``` + * + * @return {string} host of current URL. + */ + host: locationGetter('$$host'), + + /** + * @ngdoc method + * @name $location#port + * + * @description + * This method is getter only. + * + * Return port of current URL. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var port = $location.port(); + * // => 80 + * ``` + * + * @return {Number} port + */ + port: locationGetter('$$port'), + + /** + * @ngdoc method + * @name $location#path + * + * @description + * This method is getter / setter. + * + * Return path of current URL when called without any parameter. + * + * Change path when called with parameter and return `$location`. + * + * Note: Path should always begin with forward slash (/), this method will add the forward slash + * if it is missing. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var path = $location.path(); + * // => "/some/path" + * ``` + * + * @param {(string|number)=} path New path + * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter + */ + path: locationGetterSetter('$$path', function(path) { + path = path !== null ? path.toString() : ''; + return path.charAt(0) === '/' ? path : '/' + path; + }), + + /** + * @ngdoc method + * @name $location#search + * + * @description + * This method is getter / setter. + * + * Return search part (as object) of current URL when called without any parameter. + * + * Change search part when called with parameter and return `$location`. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo + * var searchObject = $location.search(); + * // => {foo: 'bar', baz: 'xoxo'} + * + * // set foo to 'yipee' + * $location.search('foo', 'yipee'); + * // $location.search() => {foo: 'yipee', baz: 'xoxo'} + * ``` + * + * @param {string|Object.|Object.>} search New search params - string or + * hash object. + * + * When called with a single argument the method acts as a setter, setting the `search` component + * of `$location` to the specified value. + * + * If the argument is a hash object containing an array of values, these values will be encoded + * as duplicate search parameters in the URL. + * + * @param {(string|Number|Array|boolean)=} paramValue If `search` is a string or number, then `paramValue` + * will override only a single search property. + * + * If `paramValue` is an array, it will override the property of the `search` component of + * `$location` specified via the first argument. + * + * If `paramValue` is `null`, the property specified via the first argument will be deleted. + * + * If `paramValue` is `true`, the property specified via the first argument will be added with no + * value nor trailing equal sign. + * + * @return {Object} If called with no arguments returns the parsed `search` object. If called with + * one or more arguments returns `$location` object itself. + */ + search: function(search, paramValue) { + switch (arguments.length) { + case 0: + return this.$$search; + case 1: + if (isString(search) || isNumber(search)) { + search = search.toString(); + this.$$search = parseKeyValue(search); + } else if (isObject(search)) { + search = copy(search, {}); + // remove object undefined or null properties + forEach(search, function(value, key) { + if (value == null) delete search[key]; + }); + + this.$$search = search; + } else { + throw $locationMinErr('isrcharg', + 'The first argument of the `$location#search()` call must be a string or an object.'); + } + break; + default: + if (isUndefined(paramValue) || paramValue === null) { + delete this.$$search[search]; + } else { + this.$$search[search] = paramValue; + } + } + + this.$$compose(); + return this; + }, + + /** + * @ngdoc method + * @name $location#hash + * + * @description + * This method is getter / setter. + * + * Returns the hash fragment when called without any parameters. + * + * Changes the hash fragment when called with a parameter and returns `$location`. + * + * + * ```js + * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue + * var hash = $location.hash(); + * // => "hashValue" + * ``` + * + * @param {(string|number)=} hash New hash fragment + * @return {string} hash + */ + hash: locationGetterSetter('$$hash', function(hash) { + return hash !== null ? hash.toString() : ''; + }), + + /** + * @ngdoc method + * @name $location#replace + * + * @description + * If called, all changes to $location during the current `$digest` will replace the current history + * record, instead of adding a new one. + */ + replace: function() { + this.$$replace = true; + return this; + } +}; + +forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { + Location.prototype = Object.create(locationPrototype); + + /** + * @ngdoc method + * @name $location#state + * + * @description + * This method is getter / setter. + * + * Return the history state object when called without any parameter. + * + * Change the history state object when called with one parameter and return `$location`. + * The state object is later passed to `pushState` or `replaceState`. + * + * NOTE: This method is supported only in HTML5 mode and only in browsers supporting + * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support + * older browsers (like IE9 or Android < 4.0), don't use this method. + * + * @param {object=} state State object for pushState or replaceState + * @return {object} state + */ + Location.prototype.state = function(state) { + if (!arguments.length) { + return this.$$state; + } + + if (Location !== LocationHtml5Url || !this.$$html5) { + throw $locationMinErr('nostate', 'History API state support is available only ' + + 'in HTML5 mode and only in browsers supporting HTML5 History API'); + } + // The user might modify `stateObject` after invoking `$location.state(stateObject)` + // but we're changing the $$state reference to $browser.state() during the $digest + // so the modification window is narrow. + this.$$state = isUndefined(state) ? null : state; + + return this; + }; +}); + + +function locationGetter(property) { + return /** @this */ function() { + return this[property]; + }; +} + + +function locationGetterSetter(property, preprocess) { + return /** @this */ function(value) { + if (isUndefined(value)) { + return this[property]; + } + + this[property] = preprocess(value); + this.$$compose(); + + return this; + }; +} + + +/** + * @ngdoc service + * @name $location + * + * @requires $rootElement + * + * @description + * The $location service parses the URL in the browser address bar (based on the + * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL + * available to your application. Changes to the URL in the address bar are reflected into + * $location service and changes to $location are reflected into the browser address bar. + * + * **The $location service:** + * + * - Exposes the current URL in the browser address bar, so you can + * - Watch and observe the URL. + * - Change the URL. + * - Synchronizes the URL with the browser when the user + * - Changes the address bar. + * - Clicks the back or forward button (or clicks a History link). + * - Clicks on a link. + * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). + * + * For more information see {@link guide/$location Developer Guide: Using $location} + */ + +/** + * @ngdoc provider + * @name $locationProvider + * @this + * + * @description + * Use the `$locationProvider` to configure how the application deep linking paths are stored. + */ +function $LocationProvider() { + var hashPrefix = '!', + html5Mode = { + enabled: false, + requireBase: true, + rewriteLinks: true + }; + + /** + * @ngdoc method + * @name $locationProvider#hashPrefix + * @description + * The default value for the prefix is `'!'`. + * @param {string=} prefix Prefix for hash part (containing path and search) + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.hashPrefix = function(prefix) { + if (isDefined(prefix)) { + hashPrefix = prefix; + return this; + } else { + return hashPrefix; + } + }; + + /** + * @ngdoc method + * @name $locationProvider#html5Mode + * @description + * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. + * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported + * properties: + * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to + * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not + * support `pushState`. + * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies + * whether or not a tag is required to be present. If `enabled` and `requireBase` are + * true, and a base tag is not present, an error will be thrown when `$location` is injected. + * See the {@link guide/$location $location guide for more information} + * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled, + * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will + * only happen on links with an attribute that matches the given string. For example, if set + * to `'internal-link'`, then the URL will only be rewritten for `` links. + * Note that [attribute name normalization](guide/directive#normalization) does not apply + * here, so `'internalLink'` will **not** match `'internal-link'`. + * + * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter + */ + this.html5Mode = function(mode) { + if (isBoolean(mode)) { + html5Mode.enabled = mode; + return this; + } else if (isObject(mode)) { + + if (isBoolean(mode.enabled)) { + html5Mode.enabled = mode.enabled; + } + + if (isBoolean(mode.requireBase)) { + html5Mode.requireBase = mode.requireBase; + } + + if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) { + html5Mode.rewriteLinks = mode.rewriteLinks; + } + + return this; + } else { + return html5Mode; + } + }; + + /** + * @ngdoc event + * @name $location#$locationChangeStart + * @eventType broadcast on root scope + * @description + * Broadcasted before a URL will change. + * + * This change can be prevented by calling + * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more + * details about event object. Upon successful change + * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. + * + * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when + * the browser supports the HTML5 History API. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + * @param {string=} newState New history state object + * @param {string=} oldState History state object that was before it was changed. + */ + + /** + * @ngdoc event + * @name $location#$locationChangeSuccess + * @eventType broadcast on root scope + * @description + * Broadcasted after a URL was changed. + * + * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when + * the browser supports the HTML5 History API. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + * @param {string=} newState New history state object + * @param {string=} oldState History state object that was before it was changed. + */ + + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', + function($rootScope, $browser, $sniffer, $rootElement, $window) { + var $location, + LocationMode, + baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' + initialUrl = $browser.url(), + appBase; + + if (html5Mode.enabled) { + if (!baseHref && html5Mode.requireBase) { + throw $locationMinErr('nobase', + '$location in HTML5 mode requires a tag to be present!'); + } + appBase = serverBase(initialUrl) + (baseHref || '/'); + LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; + } else { + appBase = stripHash(initialUrl); + LocationMode = LocationHashbangUrl; + } + var appBaseNoFile = stripFile(appBase); + + $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); + $location.$$parseLinkUrl(initialUrl, initialUrl); + + $location.$$state = $browser.state(); + + var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; + + function setBrowserUrlWithFallback(url, replace, state) { + var oldUrl = $location.url(); + var oldState = $location.$$state; + try { + $browser.url(url, replace, state); + + // Make sure $location.state() returns referentially identical (not just deeply equal) + // state object; this makes possible quick checking if the state changed in the digest + // loop. Checking deep equality would be too expensive. + $location.$$state = $browser.state(); + } catch (e) { + // Restore old values if pushState fails + $location.url(oldUrl); + $location.$$state = oldState; + + throw e; + } + } + + $rootElement.on('click', function(event) { + var rewriteLinks = html5Mode.rewriteLinks; + // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) + // currently we open nice url link and redirect then + + if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return; + + var elm = jqLite(event.target); + + // traverse the DOM up to find first A tag + while (nodeName_(elm[0]) !== 'a') { + // ignore rewriting if no A tag (reached root element, or no parent - removed from document) + if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; + } + + if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return; + + var absHref = elm.prop('href'); + // get the actual href attribute - see + // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx + var relHref = elm.attr('href') || elm.attr('xlink:href'); + + if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { + // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during + // an animation. + absHref = urlResolve(absHref.animVal).href; + } + + // Ignore when url is started with javascript: or mailto: + if (IGNORE_URI_REGEXP.test(absHref)) return; + + if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { + if ($location.$$parseLinkUrl(absHref, relHref)) { + // We do a preventDefault for all urls that are part of the angular application, + // in html5mode and also without, so that we are able to abort navigation without + // getting double entries in the location history. + event.preventDefault(); + // update location manually + if ($location.absUrl() !== $browser.url()) { + $rootScope.$apply(); + // hack to work around FF6 bug 684208 when scenario runner clicks on links + $window.angular['ff-684208-preventDefault'] = true; + } + } + } + }); + + + // rewrite hashbang url <> html5 url + if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) { + $browser.url($location.absUrl(), true); + } + + var initializing = true; + + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl, newState) { + + if (!startsWith(newUrl, appBaseNoFile)) { + // If we are navigating outside of the app then force a reload + $window.location.href = newUrl; + return; + } + + $rootScope.$evalAsync(function() { + var oldUrl = $location.absUrl(); + var oldState = $location.$$state; + var defaultPrevented; + newUrl = trimEmptyHash(newUrl); + $location.$$parse(newUrl); + $location.$$state = newState; + + defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + newState, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { + $location.$$parse(oldUrl); + $location.$$state = oldState; + setBrowserUrlWithFallback(oldUrl, false, oldState); + } else { + initializing = false; + afterLocationChange(oldUrl, oldState); + } + }); + if (!$rootScope.$$phase) $rootScope.$digest(); + }); + + // update browser + $rootScope.$watch(function $locationWatch() { + var oldUrl = trimEmptyHash($browser.url()); + var newUrl = trimEmptyHash($location.absUrl()); + var oldState = $browser.state(); + var currentReplace = $location.$$replace; + var urlOrStateChanged = oldUrl !== newUrl || + ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); + + if (initializing || urlOrStateChanged) { + initializing = false; + + $rootScope.$evalAsync(function() { + var newUrl = $location.absUrl(); + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + $location.$$state, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { + $location.$$parse(oldUrl); + $location.$$state = oldState; + } else { + if (urlOrStateChanged) { + setBrowserUrlWithFallback(newUrl, currentReplace, + oldState === $location.$$state ? null : $location.$$state); + } + afterLocationChange(oldUrl, oldState); + } + }); + } + + $location.$$replace = false; + + // we don't need to return anything because $evalAsync will make the digest loop dirty when + // there is a change + }); + + return $location; + + function afterLocationChange(oldUrl, oldState) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, + $location.$$state, oldState); + } +}]; +} + +/** + * @ngdoc service + * @name $log + * @requires $window + * + * @description + * Simple service for logging. Default implementation safely writes the message + * into the browser's console (if present). + * + * The main purpose of this service is to simplify debugging and troubleshooting. + * + * The default is to log `debug` messages. You can use + * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. + * + * @example + + + angular.module('logExample', []) + .controller('LogController', ['$scope', '$log', function($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + }]); + + +
    +

    Reload this page with open console, enter text and hit the log button...

    + + + + + + +
    +
    +
    + */ + +/** + * @ngdoc provider + * @name $logProvider + * @this + * + * @description + * Use the `$logProvider` to configure how the application logs messages + */ +function $LogProvider() { + var debug = true, + self = this; + + /** + * @ngdoc method + * @name $logProvider#debugEnabled + * @description + * @param {boolean=} flag enable or disable debug level messages + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.debugEnabled = function(flag) { + if (isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; + + this.$get = ['$window', function($window) { + return { + /** + * @ngdoc method + * @name $log#log + * + * @description + * Write a log message + */ + log: consoleLog('log'), + + /** + * @ngdoc method + * @name $log#info + * + * @description + * Write an information message + */ + info: consoleLog('info'), + + /** + * @ngdoc method + * @name $log#warn + * + * @description + * Write a warning message + */ + warn: consoleLog('warn'), + + /** + * @ngdoc method + * @name $log#error + * + * @description + * Write an error message + */ + error: consoleLog('error'), + + /** + * @ngdoc method + * @name $log#debug + * + * @description + * Write a debug message + */ + debug: (function() { + var fn = consoleLog('debug'); + + return function() { + if (debug) { + fn.apply(self, arguments); + } + }; + })() + }; + + function formatError(arg) { + if (arg instanceof Error) { + if (arg.stack) { + arg = (arg.message && arg.stack.indexOf(arg.message) === -1) + ? 'Error: ' + arg.message + '\n' + arg.stack + : arg.stack; + } else if (arg.sourceURL) { + arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; + } + } + return arg; + } + + function consoleLog(type) { + var console = $window.console || {}, + logFn = console[type] || console.log || noop, + hasApply = false; + + // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. + // The reason behind this is that console.log has type "object" in IE8... + try { + hasApply = !!logFn.apply; + } catch (e) { /* empty */ } + + if (hasApply) { + return function() { + var args = []; + forEach(arguments, function(arg) { + args.push(formatError(arg)); + }); + return logFn.apply(console, args); + }; + } + + // we are IE which either doesn't have window.console => this is noop and we do nothing, + // or we are IE where console.log doesn't have apply so we log at least first 2 args + return function(arg1, arg2) { + logFn(arg1, arg2 == null ? '' : arg2); + }; + } + }]; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +var $parseMinErr = minErr('$parse'); + +var objectValueOf = {}.constructor.prototype.valueOf; + +// Sandboxing Angular Expressions +// ------------------------------ +// Angular expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by +// various means such as obtaining a reference to native JS functions like the Function constructor. +// +// As an example, consider the following Angular expression: +// +// {}.toString.constructor('alert("evil JS code")') +// +// It is important to realize that if you create an expression from a string that contains user provided +// content then it is possible that your application contains a security vulnerability to an XSS style attack. +// +// See https://docs.angularjs.org/guide/security + + +function getStringValue(name) { + // Property names must be strings. This means that non-string objects cannot be used + // as keys in an object. Any non-string object, including a number, is typecasted + // into a string via the toString method. + // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names + // + // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it + // to a string. It's not always possible. If `name` is an object and its `toString` method is + // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: + // + // TypeError: Cannot convert object to primitive value + // + // For performance reasons, we don't catch this error here and allow it to propagate up the call + // stack. Note that you'll get the same error in JavaScript if you try to access a property using + // such a 'broken' object as a key. + return name + ''; +} + + +var OPERATORS = createMap(); +forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); +var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'}; + + +///////////////////////////////////////// + + +/** + * @constructor + */ +var Lexer = function Lexer(options) { + this.options = options; +}; + +Lexer.prototype = { + constructor: Lexer, + + lex: function(text) { + this.text = text; + this.index = 0; + this.tokens = []; + + while (this.index < this.text.length) { + var ch = this.text.charAt(this.index); + if (ch === '"' || ch === '\'') { + this.readString(ch); + } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { + this.readNumber(); + } else if (this.isIdentifierStart(this.peekMultichar())) { + this.readIdent(); + } else if (this.is(ch, '(){}[].,;:?')) { + this.tokens.push({index: this.index, text: ch}); + this.index++; + } else if (this.isWhitespace(ch)) { + this.index++; + } else { + var ch2 = ch + this.peek(); + var ch3 = ch2 + this.peek(2); + var op1 = OPERATORS[ch]; + var op2 = OPERATORS[ch2]; + var op3 = OPERATORS[ch3]; + if (op1 || op2 || op3) { + var token = op3 ? ch3 : (op2 ? ch2 : ch); + this.tokens.push({index: this.index, text: token, operator: true}); + this.index += token.length; + } else { + this.throwError('Unexpected next character ', this.index, this.index + 1); + } + } + } + return this.tokens; + }, + + is: function(ch, chars) { + return chars.indexOf(ch) !== -1; + }, + + peek: function(i) { + var num = i || 1; + return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; + }, + + isNumber: function(ch) { + return ('0' <= ch && ch <= '9') && typeof ch === 'string'; + }, + + isWhitespace: function(ch) { + // IE treats non-breaking space as \u00A0 + return (ch === ' ' || ch === '\r' || ch === '\t' || + ch === '\n' || ch === '\v' || ch === '\u00A0'); + }, + + isIdentifierStart: function(ch) { + return this.options.isIdentifierStart ? + this.options.isIdentifierStart(ch, this.codePointAt(ch)) : + this.isValidIdentifierStart(ch); + }, + + isValidIdentifierStart: function(ch) { + return ('a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + '_' === ch || ch === '$'); + }, + + isIdentifierContinue: function(ch) { + return this.options.isIdentifierContinue ? + this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : + this.isValidIdentifierContinue(ch); + }, + + isValidIdentifierContinue: function(ch, cp) { + return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); + }, + + codePointAt: function(ch) { + if (ch.length === 1) return ch.charCodeAt(0); + // eslint-disable-next-line no-bitwise + return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; + }, + + peekMultichar: function() { + var ch = this.text.charAt(this.index); + var peek = this.peek(); + if (!peek) { + return ch; + } + var cp1 = ch.charCodeAt(0); + var cp2 = peek.charCodeAt(0); + if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { + return ch + peek; + } + return ch; + }, + + isExpOperator: function(ch) { + return (ch === '-' || ch === '+' || this.isNumber(ch)); + }, + + throwError: function(error, start, end) { + end = end || this.index; + var colStr = (isDefined(start) + ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' + : ' ' + end); + throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', + error, colStr, this.text); + }, + + readNumber: function() { + var number = ''; + var start = this.index; + while (this.index < this.text.length) { + var ch = lowercase(this.text.charAt(this.index)); + if (ch === '.' || this.isNumber(ch)) { + number += ch; + } else { + var peekCh = this.peek(); + if (ch === 'e' && this.isExpOperator(peekCh)) { + number += ch; + } else if (this.isExpOperator(ch) && + peekCh && this.isNumber(peekCh) && + number.charAt(number.length - 1) === 'e') { + number += ch; + } else if (this.isExpOperator(ch) && + (!peekCh || !this.isNumber(peekCh)) && + number.charAt(number.length - 1) === 'e') { + this.throwError('Invalid exponent'); + } else { + break; + } + } + this.index++; + } + this.tokens.push({ + index: start, + text: number, + constant: true, + value: Number(number) + }); + }, + + readIdent: function() { + var start = this.index; + this.index += this.peekMultichar().length; + while (this.index < this.text.length) { + var ch = this.peekMultichar(); + if (!this.isIdentifierContinue(ch)) { + break; + } + this.index += ch.length; + } + this.tokens.push({ + index: start, + text: this.text.slice(start, this.index), + identifier: true + }); + }, + + readString: function(quote) { + var start = this.index; + this.index++; + var string = ''; + var rawString = quote; + var escape = false; + while (this.index < this.text.length) { + var ch = this.text.charAt(this.index); + rawString += ch; + if (escape) { + if (ch === 'u') { + var hex = this.text.substring(this.index + 1, this.index + 5); + if (!hex.match(/[\da-f]{4}/i)) { + this.throwError('Invalid unicode escape [\\u' + hex + ']'); + } + this.index += 4; + string += String.fromCharCode(parseInt(hex, 16)); + } else { + var rep = ESCAPE[ch]; + string = string + (rep || ch); + } + escape = false; + } else if (ch === '\\') { + escape = true; + } else if (ch === quote) { + this.index++; + this.tokens.push({ + index: start, + text: rawString, + constant: true, + value: string + }); + return; + } else { + string += ch; + } + this.index++; + } + this.throwError('Unterminated quote', start); + } +}; + +var AST = function AST(lexer, options) { + this.lexer = lexer; + this.options = options; +}; + +AST.Program = 'Program'; +AST.ExpressionStatement = 'ExpressionStatement'; +AST.AssignmentExpression = 'AssignmentExpression'; +AST.ConditionalExpression = 'ConditionalExpression'; +AST.LogicalExpression = 'LogicalExpression'; +AST.BinaryExpression = 'BinaryExpression'; +AST.UnaryExpression = 'UnaryExpression'; +AST.CallExpression = 'CallExpression'; +AST.MemberExpression = 'MemberExpression'; +AST.Identifier = 'Identifier'; +AST.Literal = 'Literal'; +AST.ArrayExpression = 'ArrayExpression'; +AST.Property = 'Property'; +AST.ObjectExpression = 'ObjectExpression'; +AST.ThisExpression = 'ThisExpression'; +AST.LocalsExpression = 'LocalsExpression'; + +// Internal use only +AST.NGValueParameter = 'NGValueParameter'; + +AST.prototype = { + ast: function(text) { + this.text = text; + this.tokens = this.lexer.lex(text); + + var value = this.program(); + + if (this.tokens.length !== 0) { + this.throwError('is an unexpected token', this.tokens[0]); + } + + return value; + }, + + program: function() { + var body = []; + while (true) { + if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) + body.push(this.expressionStatement()); + if (!this.expect(';')) { + return { type: AST.Program, body: body}; + } + } + }, + + expressionStatement: function() { + return { type: AST.ExpressionStatement, expression: this.filterChain() }; + }, + + filterChain: function() { + var left = this.expression(); + while (this.expect('|')) { + left = this.filter(left); + } + return left; + }, + + expression: function() { + return this.assignment(); + }, + + assignment: function() { + var result = this.ternary(); + if (this.expect('=')) { + if (!isAssignable(result)) { + throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); + } + + result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; + } + return result; + }, + + ternary: function() { + var test = this.logicalOR(); + var alternate; + var consequent; + if (this.expect('?')) { + alternate = this.expression(); + if (this.consume(':')) { + consequent = this.expression(); + return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; + } + } + return test; + }, + + logicalOR: function() { + var left = this.logicalAND(); + while (this.expect('||')) { + left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; + } + return left; + }, + + logicalAND: function() { + var left = this.equality(); + while (this.expect('&&')) { + left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; + } + return left; + }, + + equality: function() { + var left = this.relational(); + var token; + while ((token = this.expect('==','!=','===','!=='))) { + left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; + } + return left; + }, + + relational: function() { + var left = this.additive(); + var token; + while ((token = this.expect('<', '>', '<=', '>='))) { + left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; + } + return left; + }, + + additive: function() { + var left = this.multiplicative(); + var token; + while ((token = this.expect('+','-'))) { + left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; + } + return left; + }, + + multiplicative: function() { + var left = this.unary(); + var token; + while ((token = this.expect('*','/','%'))) { + left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; + } + return left; + }, + + unary: function() { + var token; + if ((token = this.expect('+', '-', '!'))) { + return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; + } else { + return this.primary(); + } + }, + + primary: function() { + var primary; + if (this.expect('(')) { + primary = this.filterChain(); + this.consume(')'); + } else if (this.expect('[')) { + primary = this.arrayDeclaration(); + } else if (this.expect('{')) { + primary = this.object(); + } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { + primary = copy(this.selfReferential[this.consume().text]); + } else if (this.options.literals.hasOwnProperty(this.peek().text)) { + primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; + } else if (this.peek().identifier) { + primary = this.identifier(); + } else if (this.peek().constant) { + primary = this.constant(); + } else { + this.throwError('not a primary expression', this.peek()); + } + + var next; + while ((next = this.expect('(', '[', '.'))) { + if (next.text === '(') { + primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; + this.consume(')'); + } else if (next.text === '[') { + primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; + this.consume(']'); + } else if (next.text === '.') { + primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; + } else { + this.throwError('IMPOSSIBLE'); + } + } + return primary; + }, + + filter: function(baseExpression) { + var args = [baseExpression]; + var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; + + while (this.expect(':')) { + args.push(this.expression()); + } + + return result; + }, + + parseArguments: function() { + var args = []; + if (this.peekToken().text !== ')') { + do { + args.push(this.filterChain()); + } while (this.expect(',')); + } + return args; + }, + + identifier: function() { + var token = this.consume(); + if (!token.identifier) { + this.throwError('is not a valid identifier', token); + } + return { type: AST.Identifier, name: token.text }; + }, + + constant: function() { + // TODO check that it is a constant + return { type: AST.Literal, value: this.consume().value }; + }, + + arrayDeclaration: function() { + var elements = []; + if (this.peekToken().text !== ']') { + do { + if (this.peek(']')) { + // Support trailing commas per ES5.1. + break; + } + elements.push(this.expression()); + } while (this.expect(',')); + } + this.consume(']'); + + return { type: AST.ArrayExpression, elements: elements }; + }, + + object: function() { + var properties = [], property; + if (this.peekToken().text !== '}') { + do { + if (this.peek('}')) { + // Support trailing commas per ES5.1. + break; + } + property = {type: AST.Property, kind: 'init'}; + if (this.peek().constant) { + property.key = this.constant(); + property.computed = false; + this.consume(':'); + property.value = this.expression(); + } else if (this.peek().identifier) { + property.key = this.identifier(); + property.computed = false; + if (this.peek(':')) { + this.consume(':'); + property.value = this.expression(); + } else { + property.value = property.key; + } + } else if (this.peek('[')) { + this.consume('['); + property.key = this.expression(); + this.consume(']'); + property.computed = true; + this.consume(':'); + property.value = this.expression(); + } else { + this.throwError('invalid key', this.peek()); + } + properties.push(property); + } while (this.expect(',')); + } + this.consume('}'); + + return {type: AST.ObjectExpression, properties: properties }; + }, + + throwError: function(msg, token) { + throw $parseMinErr('syntax', + 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', + token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); + }, + + consume: function(e1) { + if (this.tokens.length === 0) { + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + } + + var token = this.expect(e1); + if (!token) { + this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); + } + return token; + }, + + peekToken: function() { + if (this.tokens.length === 0) { + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + } + return this.tokens[0]; + }, + + peek: function(e1, e2, e3, e4) { + return this.peekAhead(0, e1, e2, e3, e4); + }, + + peekAhead: function(i, e1, e2, e3, e4) { + if (this.tokens.length > i) { + var token = this.tokens[i]; + var t = token.text; + if (t === e1 || t === e2 || t === e3 || t === e4 || + (!e1 && !e2 && !e3 && !e4)) { + return token; + } + } + return false; + }, + + expect: function(e1, e2, e3, e4) { + var token = this.peek(e1, e2, e3, e4); + if (token) { + this.tokens.shift(); + return token; + } + return false; + }, + + selfReferential: { + 'this': {type: AST.ThisExpression }, + '$locals': {type: AST.LocalsExpression } + } +}; + +function ifDefined(v, d) { + return typeof v !== 'undefined' ? v : d; +} + +function plusFn(l, r) { + if (typeof l === 'undefined') return r; + if (typeof r === 'undefined') return l; + return l + r; +} + +function isStateless($filter, filterName) { + var fn = $filter(filterName); + return !fn.$stateful; +} + +function findConstantAndWatchExpressions(ast, $filter) { + var allConstants; + var argsToWatch; + var isStatelessFilter; + switch (ast.type) { + case AST.Program: + allConstants = true; + forEach(ast.body, function(expr) { + findConstantAndWatchExpressions(expr.expression, $filter); + allConstants = allConstants && expr.expression.constant; + }); + ast.constant = allConstants; + break; + case AST.Literal: + ast.constant = true; + ast.toWatch = []; + break; + case AST.UnaryExpression: + findConstantAndWatchExpressions(ast.argument, $filter); + ast.constant = ast.argument.constant; + ast.toWatch = ast.argument.toWatch; + break; + case AST.BinaryExpression: + findConstantAndWatchExpressions(ast.left, $filter); + findConstantAndWatchExpressions(ast.right, $filter); + ast.constant = ast.left.constant && ast.right.constant; + ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); + break; + case AST.LogicalExpression: + findConstantAndWatchExpressions(ast.left, $filter); + findConstantAndWatchExpressions(ast.right, $filter); + ast.constant = ast.left.constant && ast.right.constant; + ast.toWatch = ast.constant ? [] : [ast]; + break; + case AST.ConditionalExpression: + findConstantAndWatchExpressions(ast.test, $filter); + findConstantAndWatchExpressions(ast.alternate, $filter); + findConstantAndWatchExpressions(ast.consequent, $filter); + ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; + ast.toWatch = ast.constant ? [] : [ast]; + break; + case AST.Identifier: + ast.constant = false; + ast.toWatch = [ast]; + break; + case AST.MemberExpression: + findConstantAndWatchExpressions(ast.object, $filter); + if (ast.computed) { + findConstantAndWatchExpressions(ast.property, $filter); + } + ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); + ast.toWatch = [ast]; + break; + case AST.CallExpression: + isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false; + allConstants = isStatelessFilter; + argsToWatch = []; + forEach(ast.arguments, function(expr) { + findConstantAndWatchExpressions(expr, $filter); + allConstants = allConstants && expr.constant; + if (!expr.constant) { + argsToWatch.push.apply(argsToWatch, expr.toWatch); + } + }); + ast.constant = allConstants; + ast.toWatch = isStatelessFilter ? argsToWatch : [ast]; + break; + case AST.AssignmentExpression: + findConstantAndWatchExpressions(ast.left, $filter); + findConstantAndWatchExpressions(ast.right, $filter); + ast.constant = ast.left.constant && ast.right.constant; + ast.toWatch = [ast]; + break; + case AST.ArrayExpression: + allConstants = true; + argsToWatch = []; + forEach(ast.elements, function(expr) { + findConstantAndWatchExpressions(expr, $filter); + allConstants = allConstants && expr.constant; + if (!expr.constant) { + argsToWatch.push.apply(argsToWatch, expr.toWatch); + } + }); + ast.constant = allConstants; + ast.toWatch = argsToWatch; + break; + case AST.ObjectExpression: + allConstants = true; + argsToWatch = []; + forEach(ast.properties, function(property) { + findConstantAndWatchExpressions(property.value, $filter); + allConstants = allConstants && property.value.constant && !property.computed; + if (!property.value.constant) { + argsToWatch.push.apply(argsToWatch, property.value.toWatch); + } + }); + ast.constant = allConstants; + ast.toWatch = argsToWatch; + break; + case AST.ThisExpression: + ast.constant = false; + ast.toWatch = []; + break; + case AST.LocalsExpression: + ast.constant = false; + ast.toWatch = []; + break; + } +} + +function getInputs(body) { + if (body.length !== 1) return; + var lastExpression = body[0].expression; + var candidate = lastExpression.toWatch; + if (candidate.length !== 1) return candidate; + return candidate[0] !== lastExpression ? candidate : undefined; +} + +function isAssignable(ast) { + return ast.type === AST.Identifier || ast.type === AST.MemberExpression; +} + +function assignableAST(ast) { + if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { + return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; + } +} + +function isLiteral(ast) { + return ast.body.length === 0 || + ast.body.length === 1 && ( + ast.body[0].expression.type === AST.Literal || + ast.body[0].expression.type === AST.ArrayExpression || + ast.body[0].expression.type === AST.ObjectExpression); +} + +function isConstant(ast) { + return ast.constant; +} + +function ASTCompiler(astBuilder, $filter) { + this.astBuilder = astBuilder; + this.$filter = $filter; +} + +ASTCompiler.prototype = { + compile: function(expression) { + var self = this; + var ast = this.astBuilder.ast(expression); + this.state = { + nextId: 0, + filters: {}, + fn: {vars: [], body: [], own: {}}, + assign: {vars: [], body: [], own: {}}, + inputs: [] + }; + findConstantAndWatchExpressions(ast, self.$filter); + var extra = ''; + var assignable; + this.stage = 'assign'; + if ((assignable = assignableAST(ast))) { + this.state.computing = 'assign'; + var result = this.nextId(); + this.recurse(assignable, result); + this.return_(result); + extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); + } + var toWatch = getInputs(ast.body); + self.stage = 'inputs'; + forEach(toWatch, function(watch, key) { + var fnKey = 'fn' + key; + self.state[fnKey] = {vars: [], body: [], own: {}}; + self.state.computing = fnKey; + var intoId = self.nextId(); + self.recurse(watch, intoId); + self.return_(intoId); + self.state.inputs.push(fnKey); + watch.watchId = key; + }); + this.state.computing = 'fn'; + this.stage = 'main'; + this.recurse(ast); + var fnString = + // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. + // This is a workaround for this until we do a better job at only removing the prefix only when we should. + '"' + this.USE + ' ' + this.STRICT + '";\n' + + this.filterPrefix() + + 'var fn=' + this.generateFunction('fn', 's,l,a,i') + + extra + + this.watchFns() + + 'return fn;'; + + // eslint-disable-next-line no-new-func + var fn = (new Function('$filter', + 'getStringValue', + 'ifDefined', + 'plus', + fnString))( + this.$filter, + getStringValue, + ifDefined, + plusFn); + this.state = this.stage = undefined; + fn.literal = isLiteral(ast); + fn.constant = isConstant(ast); + return fn; + }, + + USE: 'use', + + STRICT: 'strict', + + watchFns: function() { + var result = []; + var fns = this.state.inputs; + var self = this; + forEach(fns, function(name) { + result.push('var ' + name + '=' + self.generateFunction(name, 's')); + }); + if (fns.length) { + result.push('fn.inputs=[' + fns.join(',') + '];'); + } + return result.join(''); + }, + + generateFunction: function(name, params) { + return 'function(' + params + '){' + + this.varsPrefix(name) + + this.body(name) + + '};'; + }, + + filterPrefix: function() { + var parts = []; + var self = this; + forEach(this.state.filters, function(id, filter) { + parts.push(id + '=$filter(' + self.escape(filter) + ')'); + }); + if (parts.length) return 'var ' + parts.join(',') + ';'; + return ''; + }, + + varsPrefix: function(section) { + return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; + }, + + body: function(section) { + return this.state[section].body.join(''); + }, + + recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { + var left, right, self = this, args, expression, computed; + recursionFn = recursionFn || noop; + if (!skipWatchIdCheck && isDefined(ast.watchId)) { + intoId = intoId || this.nextId(); + this.if_('i', + this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), + this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) + ); + return; + } + switch (ast.type) { + case AST.Program: + forEach(ast.body, function(expression, pos) { + self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); + if (pos !== ast.body.length - 1) { + self.current().body.push(right, ';'); + } else { + self.return_(right); + } + }); + break; + case AST.Literal: + expression = this.escape(ast.value); + this.assign(intoId, expression); + recursionFn(intoId || expression); + break; + case AST.UnaryExpression: + this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); + expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; + this.assign(intoId, expression); + recursionFn(expression); + break; + case AST.BinaryExpression: + this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); + this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); + if (ast.operator === '+') { + expression = this.plus(left, right); + } else if (ast.operator === '-') { + expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); + } else { + expression = '(' + left + ')' + ast.operator + '(' + right + ')'; + } + this.assign(intoId, expression); + recursionFn(expression); + break; + case AST.LogicalExpression: + intoId = intoId || this.nextId(); + self.recurse(ast.left, intoId); + self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); + recursionFn(intoId); + break; + case AST.ConditionalExpression: + intoId = intoId || this.nextId(); + self.recurse(ast.test, intoId); + self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); + recursionFn(intoId); + break; + case AST.Identifier: + intoId = intoId || this.nextId(); + if (nameId) { + nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); + nameId.computed = false; + nameId.name = ast.name; + } + self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), + function() { + self.if_(self.stage === 'inputs' || 's', function() { + if (create && create !== 1) { + self.if_( + self.isNull(self.nonComputedMember('s', ast.name)), + self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); + } + self.assign(intoId, self.nonComputedMember('s', ast.name)); + }); + }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) + ); + recursionFn(intoId); + break; + case AST.MemberExpression: + left = nameId && (nameId.context = this.nextId()) || this.nextId(); + intoId = intoId || this.nextId(); + self.recurse(ast.object, left, undefined, function() { + self.if_(self.notNull(left), function() { + if (ast.computed) { + right = self.nextId(); + self.recurse(ast.property, right); + self.getStringValue(right); + if (create && create !== 1) { + self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); + } + expression = self.computedMember(left, right); + self.assign(intoId, expression); + if (nameId) { + nameId.computed = true; + nameId.name = right; + } + } else { + if (create && create !== 1) { + self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); + } + expression = self.nonComputedMember(left, ast.property.name); + self.assign(intoId, expression); + if (nameId) { + nameId.computed = false; + nameId.name = ast.property.name; + } + } + }, function() { + self.assign(intoId, 'undefined'); + }); + recursionFn(intoId); + }, !!create); + break; + case AST.CallExpression: + intoId = intoId || this.nextId(); + if (ast.filter) { + right = self.filter(ast.callee.name); + args = []; + forEach(ast.arguments, function(expr) { + var argument = self.nextId(); + self.recurse(expr, argument); + args.push(argument); + }); + expression = right + '(' + args.join(',') + ')'; + self.assign(intoId, expression); + recursionFn(intoId); + } else { + right = self.nextId(); + left = {}; + args = []; + self.recurse(ast.callee, right, left, function() { + self.if_(self.notNull(right), function() { + forEach(ast.arguments, function(expr) { + self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { + args.push(argument); + }); + }); + if (left.name) { + expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; + } else { + expression = right + '(' + args.join(',') + ')'; + } + self.assign(intoId, expression); + }, function() { + self.assign(intoId, 'undefined'); + }); + recursionFn(intoId); + }); + } + break; + case AST.AssignmentExpression: + right = this.nextId(); + left = {}; + this.recurse(ast.left, undefined, left, function() { + self.if_(self.notNull(left.context), function() { + self.recurse(ast.right, right); + expression = self.member(left.context, left.name, left.computed) + ast.operator + right; + self.assign(intoId, expression); + recursionFn(intoId || expression); + }); + }, 1); + break; + case AST.ArrayExpression: + args = []; + forEach(ast.elements, function(expr) { + self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { + args.push(argument); + }); + }); + expression = '[' + args.join(',') + ']'; + this.assign(intoId, expression); + recursionFn(intoId || expression); + break; + case AST.ObjectExpression: + args = []; + computed = false; + forEach(ast.properties, function(property) { + if (property.computed) { + computed = true; + } + }); + if (computed) { + intoId = intoId || this.nextId(); + this.assign(intoId, '{}'); + forEach(ast.properties, function(property) { + if (property.computed) { + left = self.nextId(); + self.recurse(property.key, left); + } else { + left = property.key.type === AST.Identifier ? + property.key.name : + ('' + property.key.value); + } + right = self.nextId(); + self.recurse(property.value, right); + self.assign(self.member(intoId, left, property.computed), right); + }); + } else { + forEach(ast.properties, function(property) { + self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) { + args.push(self.escape( + property.key.type === AST.Identifier ? property.key.name : + ('' + property.key.value)) + + ':' + expr); + }); + }); + expression = '{' + args.join(',') + '}'; + this.assign(intoId, expression); + } + recursionFn(intoId || expression); + break; + case AST.ThisExpression: + this.assign(intoId, 's'); + recursionFn(intoId || 's'); + break; + case AST.LocalsExpression: + this.assign(intoId, 'l'); + recursionFn(intoId || 'l'); + break; + case AST.NGValueParameter: + this.assign(intoId, 'v'); + recursionFn(intoId || 'v'); + break; + } + }, + + getHasOwnProperty: function(element, property) { + var key = element + '.' + property; + var own = this.current().own; + if (!own.hasOwnProperty(key)) { + own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); + } + return own[key]; + }, + + assign: function(id, value) { + if (!id) return; + this.current().body.push(id, '=', value, ';'); + return id; + }, + + filter: function(filterName) { + if (!this.state.filters.hasOwnProperty(filterName)) { + this.state.filters[filterName] = this.nextId(true); + } + return this.state.filters[filterName]; + }, + + ifDefined: function(id, defaultValue) { + return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; + }, + + plus: function(left, right) { + return 'plus(' + left + ',' + right + ')'; + }, + + return_: function(id) { + this.current().body.push('return ', id, ';'); + }, + + if_: function(test, alternate, consequent) { + if (test === true) { + alternate(); + } else { + var body = this.current().body; + body.push('if(', test, '){'); + alternate(); + body.push('}'); + if (consequent) { + body.push('else{'); + consequent(); + body.push('}'); + } + } + }, + + not: function(expression) { + return '!(' + expression + ')'; + }, + + isNull: function(expression) { + return expression + '==null'; + }, + + notNull: function(expression) { + return expression + '!=null'; + }, + + nonComputedMember: function(left, right) { + var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/; + var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; + if (SAFE_IDENTIFIER.test(right)) { + return left + '.' + right; + } else { + return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; + } + }, + + computedMember: function(left, right) { + return left + '[' + right + ']'; + }, + + member: function(left, right, computed) { + if (computed) return this.computedMember(left, right); + return this.nonComputedMember(left, right); + }, + + getStringValue: function(item) { + this.assign(item, 'getStringValue(' + item + ')'); + }, + + lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { + var self = this; + return function() { + self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); + }; + }, + + lazyAssign: function(id, value) { + var self = this; + return function() { + self.assign(id, value); + }; + }, + + stringEscapeRegex: /[^ a-zA-Z0-9]/g, + + stringEscapeFn: function(c) { + return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); + }, + + escape: function(value) { + if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\''; + if (isNumber(value)) return value.toString(); + if (value === true) return 'true'; + if (value === false) return 'false'; + if (value === null) return 'null'; + if (typeof value === 'undefined') return 'undefined'; + + throw $parseMinErr('esc', 'IMPOSSIBLE'); + }, + + nextId: function(skip, init) { + var id = 'v' + (this.state.nextId++); + if (!skip) { + this.current().vars.push(id + (init ? '=' + init : '')); + } + return id; + }, + + current: function() { + return this.state[this.state.computing]; + } +}; + + +function ASTInterpreter(astBuilder, $filter) { + this.astBuilder = astBuilder; + this.$filter = $filter; +} + +ASTInterpreter.prototype = { + compile: function(expression) { + var self = this; + var ast = this.astBuilder.ast(expression); + findConstantAndWatchExpressions(ast, self.$filter); + var assignable; + var assign; + if ((assignable = assignableAST(ast))) { + assign = this.recurse(assignable); + } + var toWatch = getInputs(ast.body); + var inputs; + if (toWatch) { + inputs = []; + forEach(toWatch, function(watch, key) { + var input = self.recurse(watch); + watch.input = input; + inputs.push(input); + watch.watchId = key; + }); + } + var expressions = []; + forEach(ast.body, function(expression) { + expressions.push(self.recurse(expression.expression)); + }); + var fn = ast.body.length === 0 ? noop : + ast.body.length === 1 ? expressions[0] : + function(scope, locals) { + var lastValue; + forEach(expressions, function(exp) { + lastValue = exp(scope, locals); + }); + return lastValue; + }; + if (assign) { + fn.assign = function(scope, value, locals) { + return assign(scope, locals, value); + }; + } + if (inputs) { + fn.inputs = inputs; + } + fn.literal = isLiteral(ast); + fn.constant = isConstant(ast); + return fn; + }, + + recurse: function(ast, context, create) { + var left, right, self = this, args; + if (ast.input) { + return this.inputs(ast.input, ast.watchId); + } + switch (ast.type) { + case AST.Literal: + return this.value(ast.value, context); + case AST.UnaryExpression: + right = this.recurse(ast.argument); + return this['unary' + ast.operator](right, context); + case AST.BinaryExpression: + left = this.recurse(ast.left); + right = this.recurse(ast.right); + return this['binary' + ast.operator](left, right, context); + case AST.LogicalExpression: + left = this.recurse(ast.left); + right = this.recurse(ast.right); + return this['binary' + ast.operator](left, right, context); + case AST.ConditionalExpression: + return this['ternary?:']( + this.recurse(ast.test), + this.recurse(ast.alternate), + this.recurse(ast.consequent), + context + ); + case AST.Identifier: + return self.identifier(ast.name, context, create); + case AST.MemberExpression: + left = this.recurse(ast.object, false, !!create); + if (!ast.computed) { + right = ast.property.name; + } + if (ast.computed) right = this.recurse(ast.property); + return ast.computed ? + this.computedMember(left, right, context, create) : + this.nonComputedMember(left, right, context, create); + case AST.CallExpression: + args = []; + forEach(ast.arguments, function(expr) { + args.push(self.recurse(expr)); + }); + if (ast.filter) right = this.$filter(ast.callee.name); + if (!ast.filter) right = this.recurse(ast.callee, true); + return ast.filter ? + function(scope, locals, assign, inputs) { + var values = []; + for (var i = 0; i < args.length; ++i) { + values.push(args[i](scope, locals, assign, inputs)); + } + var value = right.apply(undefined, values, inputs); + return context ? {context: undefined, name: undefined, value: value} : value; + } : + function(scope, locals, assign, inputs) { + var rhs = right(scope, locals, assign, inputs); + var value; + if (rhs.value != null) { + var values = []; + for (var i = 0; i < args.length; ++i) { + values.push(args[i](scope, locals, assign, inputs)); + } + value = rhs.value.apply(rhs.context, values); + } + return context ? {value: value} : value; + }; + case AST.AssignmentExpression: + left = this.recurse(ast.left, true, 1); + right = this.recurse(ast.right); + return function(scope, locals, assign, inputs) { + var lhs = left(scope, locals, assign, inputs); + var rhs = right(scope, locals, assign, inputs); + lhs.context[lhs.name] = rhs; + return context ? {value: rhs} : rhs; + }; + case AST.ArrayExpression: + args = []; + forEach(ast.elements, function(expr) { + args.push(self.recurse(expr)); + }); + return function(scope, locals, assign, inputs) { + var value = []; + for (var i = 0; i < args.length; ++i) { + value.push(args[i](scope, locals, assign, inputs)); + } + return context ? {value: value} : value; + }; + case AST.ObjectExpression: + args = []; + forEach(ast.properties, function(property) { + if (property.computed) { + args.push({key: self.recurse(property.key), + computed: true, + value: self.recurse(property.value) + }); + } else { + args.push({key: property.key.type === AST.Identifier ? + property.key.name : + ('' + property.key.value), + computed: false, + value: self.recurse(property.value) + }); + } + }); + return function(scope, locals, assign, inputs) { + var value = {}; + for (var i = 0; i < args.length; ++i) { + if (args[i].computed) { + value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs); + } else { + value[args[i].key] = args[i].value(scope, locals, assign, inputs); + } + } + return context ? {value: value} : value; + }; + case AST.ThisExpression: + return function(scope) { + return context ? {value: scope} : scope; + }; + case AST.LocalsExpression: + return function(scope, locals) { + return context ? {value: locals} : locals; + }; + case AST.NGValueParameter: + return function(scope, locals, assign) { + return context ? {value: assign} : assign; + }; + } + }, + + 'unary+': function(argument, context) { + return function(scope, locals, assign, inputs) { + var arg = argument(scope, locals, assign, inputs); + if (isDefined(arg)) { + arg = +arg; + } else { + arg = 0; + } + return context ? {value: arg} : arg; + }; + }, + 'unary-': function(argument, context) { + return function(scope, locals, assign, inputs) { + var arg = argument(scope, locals, assign, inputs); + if (isDefined(arg)) { + arg = -arg; + } else { + arg = -0; + } + return context ? {value: arg} : arg; + }; + }, + 'unary!': function(argument, context) { + return function(scope, locals, assign, inputs) { + var arg = !argument(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary+': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var lhs = left(scope, locals, assign, inputs); + var rhs = right(scope, locals, assign, inputs); + var arg = plusFn(lhs, rhs); + return context ? {value: arg} : arg; + }; + }, + 'binary-': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var lhs = left(scope, locals, assign, inputs); + var rhs = right(scope, locals, assign, inputs); + var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); + return context ? {value: arg} : arg; + }; + }, + 'binary*': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary/': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary%': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary===': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary!==': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary==': function(left, right, context) { + return function(scope, locals, assign, inputs) { + // eslint-disable-next-line eqeqeq + var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary!=': function(left, right, context) { + return function(scope, locals, assign, inputs) { + // eslint-disable-next-line eqeqeq + var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary<': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary>': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary<=': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary>=': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary&&': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'binary||': function(left, right, context) { + return function(scope, locals, assign, inputs) { + var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + 'ternary?:': function(test, alternate, consequent, context) { + return function(scope, locals, assign, inputs) { + var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); + return context ? {value: arg} : arg; + }; + }, + value: function(value, context) { + return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; + }, + identifier: function(name, context, create) { + return function(scope, locals, assign, inputs) { + var base = locals && (name in locals) ? locals : scope; + if (create && create !== 1 && base && base[name] == null) { + base[name] = {}; + } + var value = base ? base[name] : undefined; + if (context) { + return {context: base, name: name, value: value}; + } else { + return value; + } + }; + }, + computedMember: function(left, right, context, create) { + return function(scope, locals, assign, inputs) { + var lhs = left(scope, locals, assign, inputs); + var rhs; + var value; + if (lhs != null) { + rhs = right(scope, locals, assign, inputs); + rhs = getStringValue(rhs); + if (create && create !== 1) { + if (lhs && !(lhs[rhs])) { + lhs[rhs] = {}; + } + } + value = lhs[rhs]; + } + if (context) { + return {context: lhs, name: rhs, value: value}; + } else { + return value; + } + }; + }, + nonComputedMember: function(left, right, context, create) { + return function(scope, locals, assign, inputs) { + var lhs = left(scope, locals, assign, inputs); + if (create && create !== 1) { + if (lhs && lhs[right] == null) { + lhs[right] = {}; + } + } + var value = lhs != null ? lhs[right] : undefined; + if (context) { + return {context: lhs, name: right, value: value}; + } else { + return value; + } + }; + }, + inputs: function(input, watchId) { + return function(scope, value, locals, inputs) { + if (inputs) return inputs[watchId]; + return input(scope, value, locals); + }; + } +}; + +/** + * @constructor + */ +var Parser = function Parser(lexer, $filter, options) { + this.lexer = lexer; + this.$filter = $filter; + this.options = options; + this.ast = new AST(lexer, options); + this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : + new ASTCompiler(this.ast, $filter); +}; + +Parser.prototype = { + constructor: Parser, + + parse: function(text) { + return this.astCompiler.compile(text); + } +}; + +function getValueOf(value) { + return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); +} + +/////////////////////////////////// + +/** + * @ngdoc service + * @name $parse + * @kind function + * + * @description + * + * Converts Angular {@link guide/expression expression} into a function. + * + * ```js + * var getter = $parse('user.name'); + * var setter = getter.assign; + * var context = {user:{name:'angular'}}; + * var locals = {user:{name:'local'}}; + * + * expect(getter(context)).toEqual('angular'); + * setter(context, 'newValue'); + * expect(context.user.name).toEqual('newValue'); + * expect(getter(context, locals)).toEqual('local'); + * ``` + * + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + * + * The returned function also has the following properties: + * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript + * literal. + * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript + * constant literals. + * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be + * set to a function to change its value on the given context. + * + */ + + +/** + * @ngdoc provider + * @name $parseProvider + * @this + * + * @description + * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} + * service. + */ +function $ParseProvider() { + var cache = createMap(); + var literals = { + 'true': true, + 'false': false, + 'null': null, + 'undefined': undefined + }; + var identStart, identContinue; + + /** + * @ngdoc method + * @name $parseProvider#addLiteral + * @description + * + * Configure $parse service to add literal values that will be present as literal at expressions. + * + * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. + * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. + * + **/ + this.addLiteral = function(literalName, literalValue) { + literals[literalName] = literalValue; + }; + + /** + * @ngdoc method + * @name $parseProvider#setIdentifierFns + * + * @description + * + * Allows defining the set of characters that are allowed in Angular expressions. The function + * `identifierStart` will get called to know if a given character is a valid character to be the + * first character for an identifier. The function `identifierContinue` will get called to know if + * a given character is a valid character to be a follow-up identifier character. The functions + * `identifierStart` and `identifierContinue` will receive as arguments the single character to be + * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in + * mind that the `string` parameter can be two characters long depending on the character + * representation. It is expected for the function to return `true` or `false`, whether that + * character is allowed or not. + * + * Since this function will be called extensively, keep the implementation of these functions fast, + * as the performance of these functions have a direct impact on the expressions parsing speed. + * + * @param {function=} identifierStart The function that will decide whether the given character is + * a valid identifier start character. + * @param {function=} identifierContinue The function that will decide whether the given character is + * a valid identifier continue character. + */ + this.setIdentifierFns = function(identifierStart, identifierContinue) { + identStart = identifierStart; + identContinue = identifierContinue; + return this; + }; + + this.$get = ['$filter', function($filter) { + var noUnsafeEval = csp().noUnsafeEval; + var $parseOptions = { + csp: noUnsafeEval, + literals: copy(literals), + isIdentifierStart: isFunction(identStart) && identStart, + isIdentifierContinue: isFunction(identContinue) && identContinue + }; + return $parse; + + function $parse(exp, interceptorFn) { + var parsedExpression, oneTime, cacheKey; + + switch (typeof exp) { + case 'string': + exp = exp.trim(); + cacheKey = exp; + + parsedExpression = cache[cacheKey]; + + if (!parsedExpression) { + if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { + oneTime = true; + exp = exp.substring(2); + } + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + parsedExpression = parser.parse(exp); + if (parsedExpression.constant) { + parsedExpression.$$watchDelegate = constantWatchDelegate; + } else if (oneTime) { + parsedExpression.$$watchDelegate = parsedExpression.literal ? + oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; + } else if (parsedExpression.inputs) { + parsedExpression.$$watchDelegate = inputsWatchDelegate; + } + cache[cacheKey] = parsedExpression; + } + return addInterceptor(parsedExpression, interceptorFn); + + case 'function': + return addInterceptor(exp, interceptorFn); + + default: + return addInterceptor(noop, interceptorFn); + } + } + + function expressionInputDirtyCheck(newValue, oldValueOfValue) { + + if (newValue == null || oldValueOfValue == null) { // null/undefined + return newValue === oldValueOfValue; + } + + if (typeof newValue === 'object') { + + // attempt to convert the value to a primitive type + // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can + // be cheaply dirty-checked + newValue = getValueOf(newValue); + + if (typeof newValue === 'object') { + // objects/arrays are not supported - deep-watching them would be too expensive + return false; + } + + // fall-through to the primitive equality check + } + + //Primitive or NaN + // eslint-disable-next-line no-self-compare + return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); + } + + function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { + var inputExpressions = parsedExpression.inputs; + var lastResult; + + if (inputExpressions.length === 1) { + var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails + inputExpressions = inputExpressions[0]; + return scope.$watch(function expressionInputWatch(scope) { + var newInputValue = inputExpressions(scope); + if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { + lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); + oldInputValueOf = newInputValue && getValueOf(newInputValue); + } + return lastResult; + }, listener, objectEquality, prettyPrintExpression); + } + + var oldInputValueOfValues = []; + var oldInputValues = []; + for (var i = 0, ii = inputExpressions.length; i < ii; i++) { + oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails + oldInputValues[i] = null; + } + + return scope.$watch(function expressionInputsWatch(scope) { + var changed = false; + + for (var i = 0, ii = inputExpressions.length; i < ii; i++) { + var newInputValue = inputExpressions[i](scope); + if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { + oldInputValues[i] = newInputValue; + oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); + } + } + + if (changed) { + lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); + } + + return lastResult; + }, listener, objectEquality, prettyPrintExpression); + } + + function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { + var unwatch, lastValue; + if (parsedExpression.inputs) { + unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression); + } else { + unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality); + } + return unwatch; + + function oneTimeWatch(scope) { + return parsedExpression(scope); + } + function oneTimeListener(value, old, scope) { + lastValue = value; + if (isFunction(listener)) { + listener(value, old, scope); + } + if (isDefined(value)) { + scope.$$postDigest(function() { + if (isDefined(lastValue)) { + unwatch(); + } + }); + } + } + } + + function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var unwatch, lastValue; + unwatch = scope.$watch(function oneTimeWatch(scope) { + return parsedExpression(scope); + }, function oneTimeListener(value, old, scope) { + lastValue = value; + if (isFunction(listener)) { + listener(value, old, scope); + } + if (isAllDefined(value)) { + scope.$$postDigest(function() { + if (isAllDefined(lastValue)) unwatch(); + }); + } + }, objectEquality); + + return unwatch; + + function isAllDefined(value) { + var allDefined = true; + forEach(value, function(val) { + if (!isDefined(val)) allDefined = false; + }); + return allDefined; + } + } + + function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var unwatch = scope.$watch(function constantWatch(scope) { + unwatch(); + return parsedExpression(scope); + }, listener, objectEquality); + return unwatch; + } + + function addInterceptor(parsedExpression, interceptorFn) { + if (!interceptorFn) return parsedExpression; + var watchDelegate = parsedExpression.$$watchDelegate; + var useInputs = false; + + var regularWatch = + watchDelegate !== oneTimeLiteralWatchDelegate && + watchDelegate !== oneTimeWatchDelegate; + + var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { + var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); + return interceptorFn(value, scope, locals); + } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { + var value = parsedExpression(scope, locals, assign, inputs); + var result = interceptorFn(value, scope, locals); + // we only return the interceptor's result if the + // initial value is defined (for bind-once) + return isDefined(value) ? result : value; + }; + + // Propagate $$watchDelegates other then inputsWatchDelegate + useInputs = !parsedExpression.inputs; + if (parsedExpression.$$watchDelegate && + parsedExpression.$$watchDelegate !== inputsWatchDelegate) { + fn.$$watchDelegate = parsedExpression.$$watchDelegate; + fn.inputs = parsedExpression.inputs; + } else if (!interceptorFn.$stateful) { + // If there is an interceptor, but no watchDelegate then treat the interceptor like + // we treat filters - it is assumed to be a pure function unless flagged with $stateful + fn.$$watchDelegate = inputsWatchDelegate; + fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; + } + + return fn; + } + }]; +} + +/** + * @ngdoc service + * @name $q + * @requires $rootScope + * + * @description + * A service that helps you run functions asynchronously, and use their return values (or exceptions) + * when they are done processing. + * + * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred + * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * + * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred + * implementations, and the other which resembles ES6 (ES2015) promises to some degree. + * + * # $q constructor + * + * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` + * function as the first argument. This is similar to the native Promise implementation from ES6, + * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). + * + * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are + * available yet. + * + * It can be used like so: + * + * ```js + * // for the purpose of this example let's assume that variables `$q` and `okToGreet` + * // are available in the current lexical scope (they could have been injected or passed in). + * + * function asyncGreet(name) { + * // perform some asynchronous operation, resolve or reject the promise when appropriate. + * return $q(function(resolve, reject) { + * setTimeout(function() { + * if (okToGreet(name)) { + * resolve('Hello, ' + name + '!'); + * } else { + * reject('Greeting ' + name + ' is not allowed.'); + * } + * }, 1000); + * }); + * } + * + * var promise = asyncGreet('Robin Hood'); + * promise.then(function(greeting) { + * alert('Success: ' + greeting); + * }, function(reason) { + * alert('Failed: ' + reason); + * }); + * ``` + * + * Note: progress/notify callbacks are not currently supported via the ES6-style interface. + * + * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. + * + * However, the more traditional CommonJS-style usage is still available, and documented below. + * + * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an + * interface for interacting with an object that represents the result of an action that is + * performed asynchronously, and may or may not be finished at any given point in time. + * + * From the perspective of dealing with error handling, deferred and promise APIs are to + * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. + * + * ```js + * // for the purpose of this example let's assume that variables `$q` and `okToGreet` + * // are available in the current lexical scope (they could have been injected or passed in). + * + * function asyncGreet(name) { + * var deferred = $q.defer(); + * + * setTimeout(function() { + * deferred.notify('About to greet ' + name + '.'); + * + * if (okToGreet(name)) { + * deferred.resolve('Hello, ' + name + '!'); + * } else { + * deferred.reject('Greeting ' + name + ' is not allowed.'); + * } + * }, 1000); + * + * return deferred.promise; + * } + * + * var promise = asyncGreet('Robin Hood'); + * promise.then(function(greeting) { + * alert('Success: ' + greeting); + * }, function(reason) { + * alert('Failed: ' + reason); + * }, function(update) { + * alert('Got notification: ' + update); + * }); + * ``` + * + * At first it might not be obvious why this extra complexity is worth the trouble. The payoff + * comes in the way of guarantees that promise and deferred APIs make, see + * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. + * + * Additionally the promise api allows for composition that is very hard to do with the + * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. + * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the + * section on serial or parallel joining of promises. + * + * # The Deferred API + * + * A new instance of deferred is constructed by calling `$q.defer()`. + * + * The purpose of the deferred object is to expose the associated Promise instance as well as APIs + * that can be used for signaling the successful or unsuccessful completion, as well as the status + * of the task. + * + * **Methods** + * + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + * constructed via `$q.reject`, the promise will be rejected instead. + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + * resolving it with a rejection constructed via `$q.reject`. + * - `notify(value)` - provides updates on the status of the promise's execution. This may be called + * multiple times before the promise is either resolved or rejected. + * + * **Properties** + * + * - promise – `{Promise}` – promise object associated with this deferred. + * + * + * # The Promise API + * + * A new promise instance is created when a deferred instance is created and can be retrieved by + * calling `deferred.promise`. + * + * The purpose of the promise object is to allow for interested parties to get access to the result + * of the deferred task when it completes. + * + * **Methods** + * + * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or + * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously + * as soon as the result is available. The callbacks are called with a single argument: the result + * or rejection reason. Additionally, the notify callback may be called zero or more times to + * provide a progress indication, before the promise is resolved or rejected. + * + * This method *returns a new promise* which is resolved or rejected via the return value of the + * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved + * with the value which is resolved in that promise using + * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). + * It also notifies via the return value of the `notifyCallback` method. The promise cannot be + * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback + * arguments are optional. + * + * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` + * + * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, + * but to do so without modifying the final value. This is useful to release resources or do some + * clean-up that needs to be done whether the promise was rejected or resolved. See the [full + * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for + * more information. + * + * # Chaining promises + * + * Because calling the `then` method of a promise returns a new derived promise, it is easily + * possible to create a chain of promises: + * + * ```js + * promiseB = promiseA.then(function(result) { + * return result + 1; + * }); + * + * // promiseB will be resolved immediately after promiseA is resolved and its value + * // will be the result of promiseA incremented by 1 + * ``` + * + * It is possible to create chains of any length and since a promise can be resolved with another + * promise (which will defer its resolution further), it is possible to pause/defer resolution of + * the promises at any point in the chain. This makes it possible to implement powerful APIs like + * $http's response interceptors. + * + * + * # Differences between Kris Kowal's Q and $q + * + * There are two main differences: + * + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation + * mechanism in angular, which means faster propagation of resolution or rejection into your + * models and avoiding unnecessary browser repaints, which would result in flickering UI. + * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains + * all the important functionality needed for common async tasks. + * + * # Testing + * + * ```js + * it('should simulate promise', inject(function($q, $rootScope) { + * var deferred = $q.defer(); + * var promise = deferred.promise; + * var resolvedValue; + * + * promise.then(function(value) { resolvedValue = value; }); + * expect(resolvedValue).toBeUndefined(); + * + * // Simulate resolving of promise + * deferred.resolve(123); + * // Note that the 'then' function does not get called synchronously. + * // This is because we want the promise API to always be async, whether or not + * // it got called synchronously or asynchronously. + * expect(resolvedValue).toBeUndefined(); + * + * // Propagate promise resolution to 'then' functions using $apply(). + * $rootScope.$apply(); + * expect(resolvedValue).toEqual(123); + * })); + * ``` + * + * @param {function(function, function)} resolver Function which is responsible for resolving or + * rejecting the newly created promise. The first parameter is a function which resolves the + * promise, the second parameter is a function which rejects the promise. + * + * @returns {Promise} The newly created promise. + */ +/** + * @ngdoc provider + * @name $qProvider + * @this + * + * @description + */ +function $QProvider() { + var errorOnUnhandledRejections = true; + this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { + return qFactory(function(callback) { + $rootScope.$evalAsync(callback); + }, $exceptionHandler, errorOnUnhandledRejections); + }]; + + /** + * @ngdoc method + * @name $qProvider#errorOnUnhandledRejections + * @kind function + * + * @description + * Retrieves or overrides whether to generate an error when a rejected promise is not handled. + * This feature is enabled by default. + * + * @param {boolean=} value Whether to generate an error when a rejected promise is not handled. + * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for + * chaining otherwise. + */ + this.errorOnUnhandledRejections = function(value) { + if (isDefined(value)) { + errorOnUnhandledRejections = value; + return this; + } else { + return errorOnUnhandledRejections; + } + }; +} + +/** @this */ +function $$QProvider() { + var errorOnUnhandledRejections = true; + this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { + return qFactory(function(callback) { + $browser.defer(callback); + }, $exceptionHandler, errorOnUnhandledRejections); + }]; + + this.errorOnUnhandledRejections = function(value) { + if (isDefined(value)) { + errorOnUnhandledRejections = value; + return this; + } else { + return errorOnUnhandledRejections; + } + }; +} + +/** + * Constructs a promise manager. + * + * @param {function(function)} nextTick Function for executing functions in the next turn. + * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for + * debugging purposes. + @ param {=boolean} errorOnUnhandledRejections Whether an error should be generated on unhandled + * promises rejections. + * @returns {object} Promise manager. + */ +function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { + var $qMinErr = minErr('$q', TypeError); + var queueSize = 0; + var checkQueue = []; + + /** + * @ngdoc method + * @name ng.$q#defer + * @kind function + * + * @description + * Creates a `Deferred` object which represents a task which will finish in the future. + * + * @returns {Deferred} Returns a new instance of deferred. + */ + function defer() { + return new Deferred(); + } + + function Deferred() { + var promise = this.promise = new Promise(); + //Non prototype methods necessary to support unbound execution :/ + this.resolve = function(val) { resolvePromise(promise, val); }; + this.reject = function(reason) { rejectPromise(promise, reason); }; + this.notify = function(progress) { notifyPromise(promise, progress); }; + } + + + function Promise() { + this.$$state = { status: 0 }; + } + + extend(Promise.prototype, { + then: function(onFulfilled, onRejected, progressBack) { + if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { + return this; + } + var result = new Promise(); + + this.$$state.pending = this.$$state.pending || []; + this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); + if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); + + return result; + }, + + 'catch': function(callback) { + return this.then(null, callback); + }, + + 'finally': function(callback, progressBack) { + return this.then(function(value) { + return handleCallback(value, resolve, callback); + }, function(error) { + return handleCallback(error, reject, callback); + }, progressBack); + } + }); + + function processQueue(state) { + var fn, promise, pending; + + pending = state.pending; + state.processScheduled = false; + state.pending = undefined; + try { + for (var i = 0, ii = pending.length; i < ii; ++i) { + state.pur = true; + promise = pending[i][0]; + fn = pending[i][state.status]; + try { + if (isFunction(fn)) { + resolvePromise(promise, fn(state.value)); + } else if (state.status === 1) { + resolvePromise(promise, state.value); + } else { + rejectPromise(promise, state.value); + } + } catch (e) { + rejectPromise(promise, e); + } + } + } finally { + --queueSize; + if (errorOnUnhandledRejections && queueSize === 0) { + nextTick(processChecks); + } + } + } + + function processChecks() { + // eslint-disable-next-line no-unmodified-loop-condition + while (!queueSize && checkQueue.length) { + var toCheck = checkQueue.shift(); + if (!toCheck.pur) { + toCheck.pur = true; + var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value); + if (toCheck.value instanceof Error) { + exceptionHandler(toCheck.value, errorMessage); + } else { + exceptionHandler(errorMessage); + } + } + } + } + + function scheduleProcessQueue(state) { + if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !state.pur) { + if (queueSize === 0 && checkQueue.length === 0) { + nextTick(processChecks); + } + checkQueue.push(state); + } + if (state.processScheduled || !state.pending) return; + state.processScheduled = true; + ++queueSize; + nextTick(function() { processQueue(state); }); + } + + function resolvePromise(promise, val) { + if (promise.$$state.status) return; + if (val === promise) { + $$reject(promise, $qMinErr( + 'qcycle', + 'Expected promise to be resolved with value other than itself \'{0}\'', + val)); + } else { + $$resolve(promise, val); + } + + } + + function $$resolve(promise, val) { + var then; + var done = false; + try { + if (isObject(val) || isFunction(val)) then = val.then; + if (isFunction(then)) { + promise.$$state.status = -1; + then.call(val, doResolve, doReject, doNotify); + } else { + promise.$$state.value = val; + promise.$$state.status = 1; + scheduleProcessQueue(promise.$$state); + } + } catch (e) { + doReject(e); + } + + function doResolve(val) { + if (done) return; + done = true; + $$resolve(promise, val); + } + function doReject(val) { + if (done) return; + done = true; + $$reject(promise, val); + } + function doNotify(progress) { + notifyPromise(promise, progress); + } + } + + function rejectPromise(promise, reason) { + if (promise.$$state.status) return; + $$reject(promise, reason); + } + + function $$reject(promise, reason) { + promise.$$state.value = reason; + promise.$$state.status = 2; + scheduleProcessQueue(promise.$$state); + } + + function notifyPromise(promise, progress) { + var callbacks = promise.$$state.pending; + + if ((promise.$$state.status <= 0) && callbacks && callbacks.length) { + nextTick(function() { + var callback, result; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + result = callbacks[i][0]; + callback = callbacks[i][3]; + try { + notifyPromise(result, isFunction(callback) ? callback(progress) : progress); + } catch (e) { + exceptionHandler(e); + } + } + }); + } + } + + /** + * @ngdoc method + * @name $q#reject + * @kind function + * + * @description + * Creates a promise that is resolved as rejected with the specified `reason`. This api should be + * used to forward rejection in a chain of promises. If you are dealing with the last promise in + * a promise chain, you don't need to worry about it. + * + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of + * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via + * a promise error callback and you want to forward the error to the promise derived from the + * current promise, you have to "rethrow" the error by returning a rejection constructed via + * `reject`. + * + * ```js + * promiseB = promiseA.then(function(result) { + * // success: do something and resolve promiseB + * // with the old or a new result + * return result; + * }, function(reason) { + * // error: handle the error if possible and + * // resolve promiseB with newPromiseOrValue, + * // otherwise forward the rejection to promiseB + * if (canHandle(reason)) { + * // handle the error and recover + * return newPromiseOrValue; + * } + * return $q.reject(reason); + * }); + * ``` + * + * @param {*} reason Constant, message, exception or an object representing the rejection reason. + * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. + */ + function reject(reason) { + var result = new Promise(); + rejectPromise(result, reason); + return result; + } + + function handleCallback(value, resolver, callback) { + var callbackOutput = null; + try { + if (isFunction(callback)) callbackOutput = callback(); + } catch (e) { + return reject(e); + } + if (isPromiseLike(callbackOutput)) { + return callbackOutput.then(function() { + return resolver(value); + }, reject); + } else { + return resolver(value); + } + } + + /** + * @ngdoc method + * @name $q#when + * @kind function + * + * @description + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. + * This is useful when you are dealing with an object that might or might not be a promise, or if + * the promise comes from a source that can't be trusted. + * + * @param {*} value Value or a promise + * @param {Function=} successCallback + * @param {Function=} errorCallback + * @param {Function=} progressCallback + * @returns {Promise} Returns a promise of the passed value or promise + */ + + + function when(value, callback, errback, progressBack) { + var result = new Promise(); + resolvePromise(result, value); + return result.then(callback, errback, progressBack); + } + + /** + * @ngdoc method + * @name $q#resolve + * @kind function + * + * @description + * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. + * + * @param {*} value Value or a promise + * @param {Function=} successCallback + * @param {Function=} errorCallback + * @param {Function=} progressCallback + * @returns {Promise} Returns a promise of the passed value or promise + */ + var resolve = when; + + /** + * @ngdoc method + * @name $q#all + * @kind function + * + * @description + * Combines multiple promises into a single promise that is resolved when all of the input + * promises are resolved. + * + * @param {Array.|Object.} promises An array or hash of promises. + * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, + * each value corresponding to the promise at the same index/key in the `promises` array/hash. + * If any of the promises is resolved with a rejection, this resulting promise will be rejected + * with the same rejection value. + */ + + function all(promises) { + var result = new Promise(), + counter = 0, + results = isArray(promises) ? [] : {}; + + forEach(promises, function(promise, key) { + counter++; + when(promise).then(function(value) { + results[key] = value; + if (!(--counter)) resolvePromise(result, results); + }, function(reason) { + rejectPromise(result, reason); + }); + }); + + if (counter === 0) { + resolvePromise(result, results); + } + + return result; + } + + /** + * @ngdoc method + * @name $q#race + * @kind function + * + * @description + * Returns a promise that resolves or rejects as soon as one of those promises + * resolves or rejects, with the value or reason from that promise. + * + * @param {Array.|Object.} promises An array or hash of promises. + * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises` + * resolves or rejects, with the value or reason from that promise. + */ + + function race(promises) { + var deferred = defer(); + + forEach(promises, function(promise) { + when(promise).then(deferred.resolve, deferred.reject); + }); + + return deferred.promise; + } + + function $Q(resolver) { + if (!isFunction(resolver)) { + throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); + } + + var promise = new Promise(); + + function resolveFn(value) { + resolvePromise(promise, value); + } + + function rejectFn(reason) { + rejectPromise(promise, reason); + } + + resolver(resolveFn, rejectFn); + + return promise; + } + + // Let's make the instanceof operator work for promises, so that + // `new $q(fn) instanceof $q` would evaluate to true. + $Q.prototype = Promise.prototype; + + $Q.defer = defer; + $Q.reject = reject; + $Q.when = when; + $Q.resolve = resolve; + $Q.all = all; + $Q.race = race; + + return $Q; +} + +/** @this */ +function $$RAFProvider() { //rAF + this.$get = ['$window', '$timeout', function($window, $timeout) { + var requestAnimationFrame = $window.requestAnimationFrame || + $window.webkitRequestAnimationFrame; + + var cancelAnimationFrame = $window.cancelAnimationFrame || + $window.webkitCancelAnimationFrame || + $window.webkitCancelRequestAnimationFrame; + + var rafSupported = !!requestAnimationFrame; + var raf = rafSupported + ? function(fn) { + var id = requestAnimationFrame(fn); + return function() { + cancelAnimationFrame(id); + }; + } + : function(fn) { + var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 + return function() { + $timeout.cancel(timer); + }; + }; + + raf.supported = rafSupported; + + return raf; + }]; +} + +/** + * DESIGN NOTES + * + * The design decisions behind the scope are heavily favored for speed and memory consumption. + * + * The typical use of scope is to watch the expressions, which most of the time return the same + * value as last time so we optimize the operation. + * + * Closures construction is expensive in terms of speed as well as memory: + * - No closures, instead use prototypical inheritance for API + * - Internal state needs to be stored on scope directly, which means that private state is + * exposed as $$____ properties + * + * Loop operations are optimized by using while(count--) { ... } + * - This means that in order to keep the same order of execution as addition we have to add + * items to the array at the beginning (unshift) instead of at the end (push) + * + * Child scopes are created and removed often + * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists + * + * There are fewer watches than observers. This is why you don't want the observer to be implemented + * in the same way as watch. Watch requires return of the initialization function which is expensive + * to construct. + */ + + +/** + * @ngdoc provider + * @name $rootScopeProvider + * @description + * + * Provider for the $rootScope service. + */ + +/** + * @ngdoc method + * @name $rootScopeProvider#digestTtl + * @description + * + * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and + * assuming that the model is unstable. + * + * The current default is 10 iterations. + * + * In complex applications it's possible that the dependencies between `$watch`s will result in + * several digest iterations. However if an application needs more than the default 10 digest + * iterations for its model to stabilize then you should investigate what is causing the model to + * continuously change during the digest. + * + * Increasing the TTL could have performance implications, so you should not change it without + * proper justification. + * + * @param {number} limit The number of digest iterations. + */ + + +/** + * @ngdoc service + * @name $rootScope + * @this + * + * @description + * + * Every application has a single root {@link ng.$rootScope.Scope scope}. + * All other scopes are descendant scopes of the root scope. Scopes provide separation + * between the model and the view, via a mechanism for watching the model for changes. + * They also provide event emission/broadcast and subscription facility. See the + * {@link guide/scope developer guide on scopes}. + */ +function $RootScopeProvider() { + var TTL = 10; + var $rootScopeMinErr = minErr('$rootScope'); + var lastDirtyWatch = null; + var applyAsyncId = null; + + this.digestTtl = function(value) { + if (arguments.length) { + TTL = value; + } + return TTL; + }; + + function createChildScopeClass(parent) { + function ChildScope() { + this.$$watchers = this.$$nextSibling = + this.$$childHead = this.$$childTail = null; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$$watchersCount = 0; + this.$id = nextUid(); + this.$$ChildScope = null; + } + ChildScope.prototype = parent; + return ChildScope; + } + + this.$get = ['$exceptionHandler', '$parse', '$browser', + function($exceptionHandler, $parse, $browser) { + + function destroyChildScope($event) { + $event.currentScope.$$destroyed = true; + } + + function cleanUpScope($scope) { + + // Support: IE 9 only + if (msie === 9) { + // There is a memory leak in IE9 if all child scopes are not disconnected + // completely when a scope is destroyed. So this code will recurse up through + // all this scopes children + // + // See issue https://github.com/angular/angular.js/issues/10706 + if ($scope.$$childHead) { + cleanUpScope($scope.$$childHead); + } + if ($scope.$$nextSibling) { + cleanUpScope($scope.$$nextSibling); + } + } + + // The code below works around IE9 and V8's memory leaks + // + // See: + // - https://code.google.com/p/v8/issues/detail?id=2073#c26 + // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 + // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + + $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = + $scope.$$childTail = $scope.$root = $scope.$$watchers = null; + } + + /** + * @ngdoc type + * @name $rootScope.Scope + * + * @description + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link auto.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when + * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for + * an in-depth introduction and usage examples. + * + * + * # Inheritance + * A scope can inherit from a parent scope, as in this example: + * ```js + var parent = $rootScope; + var child = parent.$new(); + + parent.salutation = "Hello"; + expect(child.salutation).toEqual('Hello'); + + child.salutation = "Welcome"; + expect(child.salutation).toEqual('Welcome'); + expect(parent.salutation).toEqual('Hello'); + * ``` + * + * When interacting with `Scope` in tests, additional helper methods are available on the + * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional + * details. + * + * + * @param {Object.=} providers Map of service factory which need to be + * provided for the current scope. Defaults to {@link ng}. + * @param {Object.=} instanceCache Provides pre-instantiated services which should + * append/override services provided by `providers`. This is handy + * when unit-testing and having the need to override a default + * service. + * @returns {Object} Newly created scope. + * + */ + function Scope() { + this.$id = nextUid(); + this.$$phase = this.$parent = this.$$watchers = + this.$$nextSibling = this.$$prevSibling = + this.$$childHead = this.$$childTail = null; + this.$root = this; + this.$$destroyed = false; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$$watchersCount = 0; + this.$$isolateBindings = null; + } + + /** + * @ngdoc property + * @name $rootScope.Scope#$id + * + * @description + * Unique scope ID (monotonically increasing) useful for debugging. + */ + + /** + * @ngdoc property + * @name $rootScope.Scope#$parent + * + * @description + * Reference to the parent scope. + */ + + /** + * @ngdoc property + * @name $rootScope.Scope#$root + * + * @description + * Reference to the root scope. + */ + + Scope.prototype = { + constructor: Scope, + /** + * @ngdoc method + * @name $rootScope.Scope#$new + * @kind function + * + * @description + * Creates a new child {@link ng.$rootScope.Scope scope}. + * + * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. + * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. + * + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is + * desired for the scope and its child scopes to be permanently detached from the parent and + * thus stop participating in model change detection and listener notification by invoking. + * + * @param {boolean} isolate If true, then the scope does not prototypically inherit from the + * parent scope. The scope is isolated, as it can not see parent scope properties. + * When creating widgets, it is useful for the widget to not accidentally read parent + * state. + * + * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` + * of the newly created scope. Defaults to `this` scope if not provided. + * This is used when creating a transclude scope to correctly place it + * in the scope hierarchy while maintaining the correct prototypical + * inheritance. + * + * @returns {Object} The newly created child scope. + * + */ + $new: function(isolate, parent) { + var child; + + parent = parent || this; + + if (isolate) { + child = new Scope(); + child.$root = this.$root; + } else { + // Only create a child scope class if somebody asks for one, + // but cache it to allow the VM to optimize lookups. + if (!this.$$ChildScope) { + this.$$ChildScope = createChildScopeClass(this); + } + child = new this.$$ChildScope(); + } + child.$parent = parent; + child.$$prevSibling = parent.$$childTail; + if (parent.$$childHead) { + parent.$$childTail.$$nextSibling = child; + parent.$$childTail = child; + } else { + parent.$$childHead = parent.$$childTail = child; + } + + // When the new scope is not isolated or we inherit from `this`, and + // the parent scope is destroyed, the property `$$destroyed` is inherited + // prototypically. In all other cases, this property needs to be set + // when the parent scope is destroyed. + // The listener needs to be added after the parent is set + if (isolate || parent !== this) child.$on('$destroy', destroyChildScope); + + return child; + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$watch + * @kind function + * + * @description + * Registers a `listener` callback to be executed whenever the `watchExpression` changes. + * + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest + * $digest()} and should return the value that will be watched. (`watchExpression` should not change + * its value when executed multiple times with the same input because it may be executed multiple + * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be + * [idempotent](http://en.wikipedia.org/wiki/Idempotence).) + * - The `listener` is called only when the value from the current `watchExpression` and the + * previous call to `watchExpression` are not equal (with the exception of the initial run, + * see below). Inequality is determined according to reference inequality, + * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) + * via the `!==` Javascript operator, unless `objectEquality == true` + * (see next point) + * - When `objectEquality == true`, inequality of the `watchExpression` is determined + * according to the {@link angular.equals} function. To save the value of the object for + * later comparison, the {@link angular.copy} function is used. This therefore means that + * watching complex objects will have adverse memory and performance implications. + * - This should not be used to watch for changes in objects that are + * or contain [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}. + * - The watch `listener` may change the model, which may trigger other `listener`s to fire. + * This is achieved by rerunning the watchers until no changes are detected. The rerun + * iteration limit is 10 to prevent an infinite loop deadlock. + * + * + * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Be prepared for + * multiple calls to your `watchExpression` because it will execute multiple times in a + * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) + * + * After a watcher is registered with the scope, the `listener` fn is called asynchronously + * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the + * watcher. In rare cases, this is undesirable because the listener is called when the result + * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you + * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the + * listener was called due to initialization. + * + * + * + * # Example + * ```js + // let's assume that scope was dependency injected as the $rootScope + var scope = $rootScope; + scope.name = 'misko'; + scope.counter = 0; + + expect(scope.counter).toEqual(0); + scope.$watch('name', function(newValue, oldValue) { + scope.counter = scope.counter + 1; + }); + expect(scope.counter).toEqual(0); + + scope.$digest(); + // the listener is always called during the first $digest loop after it was registered + expect(scope.counter).toEqual(1); + + scope.$digest(); + // but now it will not be called unless the value changes + expect(scope.counter).toEqual(1); + + scope.name = 'adam'; + scope.$digest(); + expect(scope.counter).toEqual(2); + + + + // Using a function as a watchExpression + var food; + scope.foodCounter = 0; + expect(scope.foodCounter).toEqual(0); + scope.$watch( + // This function returns the value being watched. It is called for each turn of the $digest loop + function() { return food; }, + // This is the change listener, called when the value returned from the above function changes + function(newValue, oldValue) { + if ( newValue !== oldValue ) { + // Only increment the counter if the value changed + scope.foodCounter = scope.foodCounter + 1; + } + } + ); + // No digest has been run so the counter will be zero + expect(scope.foodCounter).toEqual(0); + + // Run the digest but since food has not changed count will still be zero + scope.$digest(); + expect(scope.foodCounter).toEqual(0); + + // Update food and run digest. Now the counter will increment + food = 'cheeseburger'; + scope.$digest(); + expect(scope.foodCounter).toEqual(1); + + * ``` + * + * + * + * @param {(function()|string)} watchExpression Expression that is evaluated on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers + * a call to the `listener`. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(scope)`: called with current `scope` as a parameter. + * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value + * of `watchExpression` changes. + * + * - `newVal` contains the current value of the `watchExpression` + * - `oldVal` contains the previous value of the `watchExpression` + * - `scope` refers to the current scope + * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of + * comparing for reference equality. + * @returns {function()} Returns a deregistration function for this listener. + */ + $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { + var get = $parse(watchExp); + + if (get.$$watchDelegate) { + return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); + } + var scope = this, + array = scope.$$watchers, + watcher = { + fn: listener, + last: initWatchVal, + get: get, + exp: prettyPrintExpression || watchExp, + eq: !!objectEquality + }; + + lastDirtyWatch = null; + + if (!isFunction(listener)) { + watcher.fn = noop; + } + + if (!array) { + array = scope.$$watchers = []; + array.$$digestWatchIndex = -1; + } + // we use unshift since we use a while loop in $digest for speed. + // the while loop reads in reverse order. + array.unshift(watcher); + array.$$digestWatchIndex++; + incrementWatchersCount(this, 1); + + return function deregisterWatch() { + var index = arrayRemove(array, watcher); + if (index >= 0) { + incrementWatchersCount(scope, -1); + if (index < array.$$digestWatchIndex) { + array.$$digestWatchIndex--; + } + } + lastDirtyWatch = null; + }; + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$watchGroup + * @kind function + * + * @description + * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. + * If any one expression in the collection changes the `listener` is executed. + * + * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return + * values are examined for changes on every call to `$digest`. + * - The `listener` is called whenever any expression in the `watchExpressions` array changes. + * + * @param {Array.} watchExpressions Array of expressions that will be individually + * watched using {@link ng.$rootScope.Scope#$watch $watch()} + * + * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any + * expression in `watchExpressions` changes + * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * The `scope` refers to the current scope. + * @returns {function()} Returns a de-registration function for all listeners. + */ + $watchGroup: function(watchExpressions, listener) { + var oldValues = new Array(watchExpressions.length); + var newValues = new Array(watchExpressions.length); + var deregisterFns = []; + var self = this; + var changeReactionScheduled = false; + var firstRun = true; + + if (!watchExpressions.length) { + // No expressions means we call the listener ASAP + var shouldCall = true; + self.$evalAsync(function() { + if (shouldCall) listener(newValues, newValues, self); + }); + return function deregisterWatchGroup() { + shouldCall = false; + }; + } + + if (watchExpressions.length === 1) { + // Special case size of one + return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { + newValues[0] = value; + oldValues[0] = oldValue; + listener(newValues, (value === oldValue) ? newValues : oldValues, scope); + }); + } + + forEach(watchExpressions, function(expr, i) { + var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { + newValues[i] = value; + oldValues[i] = oldValue; + if (!changeReactionScheduled) { + changeReactionScheduled = true; + self.$evalAsync(watchGroupAction); + } + }); + deregisterFns.push(unwatchFn); + }); + + function watchGroupAction() { + changeReactionScheduled = false; + + if (firstRun) { + firstRun = false; + listener(newValues, newValues, self); + } else { + listener(newValues, oldValues, self); + } + } + + return function deregisterWatchGroup() { + while (deregisterFns.length) { + deregisterFns.shift()(); + } + }; + }, + + + /** + * @ngdoc method + * @name $rootScope.Scope#$watchCollection + * @kind function + * + * @description + * Shallow watches the properties of an object and fires whenever any of the properties change + * (for arrays, this implies watching the array items; for object maps, this implies watching + * the properties). If a change is detected, the `listener` callback is fired. + * + * - The `obj` collection is observed via standard $watch operation and is examined on every + * call to $digest() to see if any items have been added, removed, or moved. + * - The `listener` is called whenever anything within the `obj` has changed. Examples include + * adding, removing, and moving items belonging to an object or array. + * + * + * # Example + * ```js + $scope.names = ['igor', 'matias', 'misko', 'james']; + $scope.dataCount = 4; + + $scope.$watchCollection('names', function(newNames, oldNames) { + $scope.dataCount = newNames.length; + }); + + expect($scope.dataCount).toEqual(4); + $scope.$digest(); + + //still at 4 ... no changes + expect($scope.dataCount).toEqual(4); + + $scope.names.pop(); + $scope.$digest(); + + //now there's been a change + expect($scope.dataCount).toEqual(3); + * ``` + * + * + * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The + * expression value should evaluate to an object or an array which is observed on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the + * collection will trigger a call to the `listener`. + * + * @param {function(newCollection, oldCollection, scope)} listener a callback function called + * when a change is detected. + * - The `newCollection` object is the newly modified data obtained from the `obj` expression + * - The `oldCollection` object is a copy of the former collection data. + * Due to performance considerations, the`oldCollection` value is computed only if the + * `listener` function declares two or more arguments. + * - The `scope` argument refers to the current scope. + * + * @returns {function()} Returns a de-registration function for this listener. When the + * de-registration function is executed, the internal watch operation is terminated. + */ + $watchCollection: function(obj, listener) { + $watchCollectionInterceptor.$stateful = true; + + var self = this; + // the current value, updated on each dirty-check run + var newValue; + // a shallow copy of the newValue from the last dirty-check run, + // updated to match newValue during dirty-check run + var oldValue; + // a shallow copy of the newValue from when the last change happened + var veryOldValue; + // only track veryOldValue if the listener is asking for it + var trackVeryOldValue = (listener.length > 1); + var changeDetected = 0; + var changeDetector = $parse(obj, $watchCollectionInterceptor); + var internalArray = []; + var internalObject = {}; + var initRun = true; + var oldLength = 0; + + function $watchCollectionInterceptor(_value) { + newValue = _value; + var newLength, key, bothNaN, newItem, oldItem; + + // If the new value is undefined, then return undefined as the watch may be a one-time watch + if (isUndefined(newValue)) return; + + if (!isObject(newValue)) { // if primitive + if (oldValue !== newValue) { + oldValue = newValue; + changeDetected++; + } + } else if (isArrayLike(newValue)) { + if (oldValue !== internalArray) { + // we are transitioning from something which was not an array into array. + oldValue = internalArray; + oldLength = oldValue.length = 0; + changeDetected++; + } + + newLength = newValue.length; + + if (oldLength !== newLength) { + // if lengths do not match we need to trigger change notification + changeDetected++; + oldValue.length = oldLength = newLength; + } + // copy the items to oldValue and look for changes. + for (var i = 0; i < newLength; i++) { + oldItem = oldValue[i]; + newItem = newValue[i]; + + // eslint-disable-next-line no-self-compare + bothNaN = (oldItem !== oldItem) && (newItem !== newItem); + if (!bothNaN && (oldItem !== newItem)) { + changeDetected++; + oldValue[i] = newItem; + } + } + } else { + if (oldValue !== internalObject) { + // we are transitioning from something which was not an object into object. + oldValue = internalObject = {}; + oldLength = 0; + changeDetected++; + } + // copy the items to oldValue and look for changes. + newLength = 0; + for (key in newValue) { + if (hasOwnProperty.call(newValue, key)) { + newLength++; + newItem = newValue[key]; + oldItem = oldValue[key]; + + if (key in oldValue) { + // eslint-disable-next-line no-self-compare + bothNaN = (oldItem !== oldItem) && (newItem !== newItem); + if (!bothNaN && (oldItem !== newItem)) { + changeDetected++; + oldValue[key] = newItem; + } + } else { + oldLength++; + oldValue[key] = newItem; + changeDetected++; + } + } + } + if (oldLength > newLength) { + // we used to have more keys, need to find them and destroy them. + changeDetected++; + for (key in oldValue) { + if (!hasOwnProperty.call(newValue, key)) { + oldLength--; + delete oldValue[key]; + } + } + } + } + return changeDetected; + } + + function $watchCollectionAction() { + if (initRun) { + initRun = false; + listener(newValue, newValue, self); + } else { + listener(newValue, veryOldValue, self); + } + + // make a copy for the next time a collection is changed + if (trackVeryOldValue) { + if (!isObject(newValue)) { + //primitive + veryOldValue = newValue; + } else if (isArrayLike(newValue)) { + veryOldValue = new Array(newValue.length); + for (var i = 0; i < newValue.length; i++) { + veryOldValue[i] = newValue[i]; + } + } else { // if object + veryOldValue = {}; + for (var key in newValue) { + if (hasOwnProperty.call(newValue, key)) { + veryOldValue[key] = newValue[key]; + } + } + } + } + } + + return this.$watch(changeDetector, $watchCollectionAction); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$digest + * @kind function + * + * @description + * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and + * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change + * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} + * until no more listeners are firing. This means that it is possible to get into an infinite + * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of + * iterations exceeds 10. + * + * Usually, you don't call `$digest()` directly in + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#directive directives}. + * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within + * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. + * + * If you want to be notified whenever `$digest()` is called, + * you can register a `watchExpression` function with + * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. + * + * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. + * + * # Example + * ```js + var scope = ...; + scope.name = 'misko'; + scope.counter = 0; + + expect(scope.counter).toEqual(0); + scope.$watch('name', function(newValue, oldValue) { + scope.counter = scope.counter + 1; + }); + expect(scope.counter).toEqual(0); + + scope.$digest(); + // the listener is always called during the first $digest loop after it was registered + expect(scope.counter).toEqual(1); + + scope.$digest(); + // but now it will not be called unless the value changes + expect(scope.counter).toEqual(1); + + scope.name = 'adam'; + scope.$digest(); + expect(scope.counter).toEqual(2); + * ``` + * + */ + $digest: function() { + var watch, value, last, fn, get, + watchers, + dirty, ttl = TTL, + next, current, target = this, + watchLog = [], + logIdx, asyncTask; + + beginPhase('$digest'); + // Check for changes to browser url that happened in sync before the call to $digest + $browser.$$checkUrlChange(); + + if (this === $rootScope && applyAsyncId !== null) { + // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then + // cancel the scheduled $apply and flush the queue of expressions to be evaluated. + $browser.defer.cancel(applyAsyncId); + flushApplyAsync(); + } + + lastDirtyWatch = null; + + do { // "while dirty" loop + dirty = false; + current = target; + + // It's safe for asyncQueuePosition to be a local variable here because this loop can't + // be reentered recursively. Calling $digest from a function passed to $applyAsync would + // lead to a '$digest already in progress' error. + for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) { + try { + asyncTask = asyncQueue[asyncQueuePosition]; + asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); + } catch (e) { + $exceptionHandler(e); + } + lastDirtyWatch = null; + } + asyncQueue.length = 0; + + traverseScopesLoop: + do { // "traverse the scopes" loop + if ((watchers = current.$$watchers)) { + // process our watches + watchers.$$digestWatchIndex = watchers.length; + while (watchers.$$digestWatchIndex--) { + try { + watch = watchers[watchers.$$digestWatchIndex]; + // Most common watches are on primitives, in which case we can short + // circuit it with === operator, only when === fails do we use .equals + if (watch) { + get = watch.get; + if ((value = get(current)) !== (last = watch.last) && + !(watch.eq + ? equals(value, last) + : (isNumberNaN(value) && isNumberNaN(last)))) { + dirty = true; + lastDirtyWatch = watch; + watch.last = watch.eq ? copy(value, null) : value; + fn = watch.fn; + fn(value, ((last === initWatchVal) ? value : last), current); + if (ttl < 5) { + logIdx = 4 - ttl; + if (!watchLog[logIdx]) watchLog[logIdx] = []; + watchLog[logIdx].push({ + msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, + newVal: value, + oldVal: last + }); + } + } else if (watch === lastDirtyWatch) { + // If the most recently dirty watcher is now clean, short circuit since the remaining watchers + // have already been tested. + dirty = false; + break traverseScopesLoop; + } + } + } catch (e) { + $exceptionHandler(e); + } + } + } + + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $broadcast + if (!(next = ((current.$$watchersCount && current.$$childHead) || + (current !== target && current.$$nextSibling)))) { + while (current !== target && !(next = current.$$nextSibling)) { + current = current.$parent; + } + } + } while ((current = next)); + + // `break traverseScopesLoop;` takes us to here + + if ((dirty || asyncQueue.length) && !(ttl--)) { + clearPhase(); + throw $rootScopeMinErr('infdig', + '{0} $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: {1}', + TTL, watchLog); + } + + } while (dirty || asyncQueue.length); + + clearPhase(); + + // postDigestQueuePosition isn't local here because this loop can be reentered recursively. + while (postDigestQueuePosition < postDigestQueue.length) { + try { + postDigestQueue[postDigestQueuePosition++](); + } catch (e) { + $exceptionHandler(e); + } + } + postDigestQueue.length = postDigestQueuePosition = 0; + }, + + + /** + * @ngdoc event + * @name $rootScope.Scope#$destroy + * @eventType broadcast on scope being destroyed + * + * @description + * Broadcasted when a scope and its children are being destroyed. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + + /** + * @ngdoc method + * @name $rootScope.Scope#$destroy + * @kind function + * + * @description + * Removes the current scope (and all of its children) from the parent scope. Removal implies + * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer + * propagate to the current scope and its children. Removal also implies that the current + * scope is eligible for garbage collection. + * + * The `$destroy()` is usually used by directives such as + * {@link ng.directive:ngRepeat ngRepeat} for managing the + * unrolling of the loop. + * + * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. + * Application code can register a `$destroy` event handler that will give it a chance to + * perform any necessary cleanup. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + $destroy: function() { + // We can't destroy a scope that has been already destroyed. + if (this.$$destroyed) return; + var parent = this.$parent; + + this.$broadcast('$destroy'); + this.$$destroyed = true; + + if (this === $rootScope) { + //Remove handlers attached to window when $rootScope is removed + $browser.$$applicationDestroyed(); + } + + incrementWatchersCount(this, -this.$$watchersCount); + for (var eventName in this.$$listenerCount) { + decrementListenerCount(this, this.$$listenerCount[eventName], eventName); + } + + // sever all the references to parent scopes (after this cleanup, the current scope should + // not be retained by any of our references and should be eligible for garbage collection) + if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling; + if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling; + if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; + if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + + // Disable listeners, watchers and apply/digest methods + this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; + this.$on = this.$watch = this.$watchGroup = function() { return noop; }; + this.$$listeners = {}; + + // Disconnect the next sibling to prevent `cleanUpScope` destroying those too + this.$$nextSibling = null; + cleanUpScope(this); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$eval + * @kind function + * + * @description + * Executes the `expression` on the current scope and returns the result. Any exceptions in + * the expression are propagated (uncaught). This is useful when evaluating Angular + * expressions. + * + * # Example + * ```js + var scope = ng.$rootScope.Scope(); + scope.a = 1; + scope.b = 2; + + expect(scope.$eval('a+b')).toEqual(3); + expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); + * ``` + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. + * @returns {*} The result of evaluating the expression. + */ + $eval: function(expr, locals) { + return $parse(expr)(this, locals); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$evalAsync + * @kind function + * + * @description + * Executes the expression on the current scope at a later point in time. + * + * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only + * that: + * + * - it will execute after the function that scheduled the evaluation (preferably before DOM + * rendering). + * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after + * `expression` execution. + * + * Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle + * will be scheduled. However, it is encouraged to always call code that changes the model + * from within an `$apply` call. That includes code evaluated via `$evalAsync`. + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. + */ + $evalAsync: function(expr, locals) { + // if we are outside of an $digest loop and this is the first time we are scheduling async + // task also schedule async auto-flush + if (!$rootScope.$$phase && !asyncQueue.length) { + $browser.defer(function() { + if (asyncQueue.length) { + $rootScope.$digest(); + } + }); + } + + asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); + }, + + $$postDigest: function(fn) { + postDigestQueue.push(fn); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$apply + * @kind function + * + * @description + * `$apply()` is used to execute an expression in angular from outside of the angular + * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). + * Because we are calling into the angular framework we need to perform proper scope life + * cycle of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#$digest executing watches}. + * + * ## Life cycle + * + * # Pseudo-Code of `$apply()` + * ```js + function $apply(expr) { + try { + return $eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + $root.$digest(); + } + } + * ``` + * + * + * Scope's `$apply()` method transitions through the following stages: + * + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#$eval $eval()} method. + * 2. Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the + * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. + * + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $apply: function(expr) { + try { + beginPhase('$apply'); + try { + return this.$eval(expr); + } finally { + clearPhase(); + } + } catch (e) { + $exceptionHandler(e); + } finally { + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + // eslint-disable-next-line no-unsafe-finally + throw e; + } + } + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$applyAsync + * @kind function + * + * @description + * Schedule the invocation of $apply to occur at a later time. The actual time difference + * varies across browsers, but is typically around ~10 milliseconds. + * + * This can be used to queue up multiple expressions which need to be evaluated in the same + * digest. + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + */ + $applyAsync: function(expr) { + var scope = this; + if (expr) { + applyAsyncQueue.push($applyAsyncExpression); + } + expr = $parse(expr); + scheduleApplyAsync(); + + function $applyAsyncExpression() { + scope.$eval(expr); + } + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$on + * @kind function + * + * @description + * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for + * discussion of event life cycle. + * + * The event listener function format is: `function(event, args...)`. The `event` object + * passed into the listener has the following attributes: + * + * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or + * `$broadcast`-ed. + * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the + * event propagates through the scope hierarchy, this property is set to null. + * - `name` - `{string}`: name of the event. + * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel + * further event propagation (available only for events that were `$emit`-ed). + * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag + * to true. + * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. + * + * @param {string} name Event name to listen on. + * @param {function(event, ...args)} listener Function to call when the event is emitted. + * @returns {function()} Returns a deregistration function for this listener. + */ + $on: function(name, listener) { + var namedListeners = this.$$listeners[name]; + if (!namedListeners) { + this.$$listeners[name] = namedListeners = []; + } + namedListeners.push(listener); + + var current = this; + do { + if (!current.$$listenerCount[name]) { + current.$$listenerCount[name] = 0; + } + current.$$listenerCount[name]++; + } while ((current = current.$parent)); + + var self = this; + return function() { + var indexOfListener = namedListeners.indexOf(listener); + if (indexOfListener !== -1) { + namedListeners[indexOfListener] = null; + decrementListenerCount(self, 1, name); + } + }; + }, + + + /** + * @ngdoc method + * @name $rootScope.Scope#$emit + * @kind function + * + * @description + * Dispatches an event `name` upwards through the scope hierarchy notifying the + * registered {@link ng.$rootScope.Scope#$on} listeners. + * + * The event life cycle starts at the scope on which `$emit` was called. All + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get + * notified. Afterwards, the event traverses upwards toward the root scope and calls all + * registered listeners along the way. The event will stop propagating if one of the listeners + * cancels it. + * + * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. + * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). + */ + $emit: function(name, args) { + var empty = [], + namedListeners, + scope = this, + stopPropagation = false, + event = { + name: name, + targetScope: scope, + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false + }, + listenerArgs = concat([event], arguments, 1), + i, length; + + do { + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i = 0, length = namedListeners.length; i < length; i++) { + + // if listeners were deregistered, defragment the array + if (!namedListeners[i]) { + namedListeners.splice(i, 1); + i--; + length--; + continue; + } + try { + //allow all listeners attached to the current scope to run + namedListeners[i].apply(null, listenerArgs); + } catch (e) { + $exceptionHandler(e); + } + } + //if any listener on the current scope stops propagation, prevent bubbling + if (stopPropagation) { + event.currentScope = null; + return event; + } + //traverse upwards + scope = scope.$parent; + } while (scope); + + event.currentScope = null; + + return event; + }, + + + /** + * @ngdoc method + * @name $rootScope.Scope#$broadcast + * @kind function + * + * @description + * Dispatches an event `name` downwards to all child scopes (and their children) notifying the + * registered {@link ng.$rootScope.Scope#$on} listeners. + * + * The event life cycle starts at the scope on which `$broadcast` was called. All + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get + * notified. Afterwards, the event propagates to all direct and indirect scopes of the current + * scope and calls all registered listeners along the way. The event cannot be canceled. + * + * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to broadcast. + * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. + * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} + */ + $broadcast: function(name, args) { + var target = this, + current = target, + next = target, + event = { + name: name, + targetScope: target, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false + }; + + if (!target.$$listenerCount[name]) return event; + + var listenerArgs = concat([event], arguments, 1), + listeners, i, length; + + //down while you can, then up and next sibling or up and next sibling until back at root + while ((current = next)) { + event.currentScope = current; + listeners = current.$$listeners[name] || []; + for (i = 0, length = listeners.length; i < length; i++) { + // if listeners were deregistered, defragment the array + if (!listeners[i]) { + listeners.splice(i, 1); + i--; + length--; + continue; + } + + try { + listeners[i].apply(null, listenerArgs); + } catch (e) { + $exceptionHandler(e); + } + } + + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $digest + // (though it differs due to having the extra check for $$listenerCount) + if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || + (current !== target && current.$$nextSibling)))) { + while (current !== target && !(next = current.$$nextSibling)) { + current = current.$parent; + } + } + } + + event.currentScope = null; + return event; + } + }; + + var $rootScope = new Scope(); + + //The internal queues. Expose them on the $rootScope for debugging/testing purposes. + var asyncQueue = $rootScope.$$asyncQueue = []; + var postDigestQueue = $rootScope.$$postDigestQueue = []; + var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; + + var postDigestQueuePosition = 0; + + return $rootScope; + + + function beginPhase(phase) { + if ($rootScope.$$phase) { + throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); + } + + $rootScope.$$phase = phase; + } + + function clearPhase() { + $rootScope.$$phase = null; + } + + function incrementWatchersCount(current, count) { + do { + current.$$watchersCount += count; + } while ((current = current.$parent)); + } + + function decrementListenerCount(current, count, name) { + do { + current.$$listenerCount[name] -= count; + + if (current.$$listenerCount[name] === 0) { + delete current.$$listenerCount[name]; + } + } while ((current = current.$parent)); + } + + /** + * function used as an initial value for watchers. + * because it's unique we can easily tell it apart from other values + */ + function initWatchVal() {} + + function flushApplyAsync() { + while (applyAsyncQueue.length) { + try { + applyAsyncQueue.shift()(); + } catch (e) { + $exceptionHandler(e); + } + } + applyAsyncId = null; + } + + function scheduleApplyAsync() { + if (applyAsyncId === null) { + applyAsyncId = $browser.defer(function() { + $rootScope.$apply(flushApplyAsync); + }); + } + } + }]; +} + +/** + * @ngdoc service + * @name $rootElement + * + * @description + * The root element of Angular application. This is either the element where {@link + * ng.directive:ngApp ngApp} was declared or the element passed into + * {@link angular.bootstrap}. The element represents the root element of application. It is also the + * location where the application's {@link auto.$injector $injector} service gets + * published, and can be retrieved using `$rootElement.injector()`. + */ + + +// the implementation is in angular.bootstrap + +/** + * @this + * @description + * Private service to sanitize uris for links and images. Used by $compile and $sanitize. + */ +function $$SanitizeUriProvider() { + var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, + imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; + + /** + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.aHrefSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + aHrefSanitizationWhitelist = regexp; + return this; + } + return aHrefSanitizationWhitelist; + }; + + + /** + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during img[src] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to img[src] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.imgSrcSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + imgSrcSanitizationWhitelist = regexp; + return this; + } + return imgSrcSanitizationWhitelist; + }; + + this.$get = function() { + return function sanitizeUri(uri, isImage) { + var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; + var normalizedVal; + normalizedVal = urlResolve(uri).href; + if (normalizedVal !== '' && !normalizedVal.match(regex)) { + return 'unsafe:' + normalizedVal; + } + return uri; + }; + }; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* exported $SceProvider, $SceDelegateProvider */ + +var $sceMinErr = minErr('$sce'); + +var SCE_CONTEXTS = { + HTML: 'html', + CSS: 'css', + URL: 'url', + // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a + // url. (e.g. ng-include, script src, templateUrl) + RESOURCE_URL: 'resourceUrl', + JS: 'js' +}; + +// Helper functions follow. + +var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g; + +function snakeToCamel(name) { + return name + .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace); +} + +function adjustMatcher(matcher) { + if (matcher === 'self') { + return matcher; + } else if (isString(matcher)) { + // Strings match exactly except for 2 wildcards - '*' and '**'. + // '*' matches any character except those from the set ':/.?&'. + // '**' matches any character (like .* in a RegExp). + // More than 2 *'s raises an error as it's ill defined. + if (matcher.indexOf('***') > -1) { + throw $sceMinErr('iwcard', + 'Illegal sequence *** in string matcher. String: {0}', matcher); + } + matcher = escapeForRegexp(matcher). + replace(/\\\*\\\*/g, '.*'). + replace(/\\\*/g, '[^:/.?&;]*'); + return new RegExp('^' + matcher + '$'); + } else if (isRegExp(matcher)) { + // The only other type of matcher allowed is a Regexp. + // Match entire URL / disallow partial matches. + // Flags are reset (i.e. no global, ignoreCase or multiline) + return new RegExp('^' + matcher.source + '$'); + } else { + throw $sceMinErr('imatcher', + 'Matchers may only be "self", string patterns or RegExp objects'); + } +} + + +function adjustMatchers(matchers) { + var adjustedMatchers = []; + if (isDefined(matchers)) { + forEach(matchers, function(matcher) { + adjustedMatchers.push(adjustMatcher(matcher)); + }); + } + return adjustedMatchers; +} + + +/** + * @ngdoc service + * @name $sceDelegate + * @kind function + * + * @description + * + * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict + * Contextual Escaping (SCE)} services to AngularJS. + * + * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of + * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is + * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to + * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things + * work because `$sce` delegates to `$sceDelegate` for these operations. + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. + * + * The default instance of `$sceDelegate` should work out of the box with little pain. While you + * can override it completely to change the behavior of `$sce`, the common case would + * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting + * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as + * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist + * $sceDelegateProvider.resourceUrlWhitelist} and {@link + * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + */ + +/** + * @ngdoc provider + * @name $sceDelegateProvider + * @this + * + * @description + * + * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate + * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure + * that the URLs used for sourcing Angular templates are safe. Refer {@link + * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and + * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * + * For the general details about this service in Angular, read the main page for {@link ng.$sce + * Strict Contextual Escaping (SCE)}. + * + * **Example**: Consider the following case.
    + * + * - your app is hosted at url `http://myapp.example.com/` + * - but some of your templates are hosted on other domains you control such as + * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. + * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. + * + * Here is what a secure configuration for this scenario might look like: + * + * ``` + * angular.module('myApp', []).config(function($sceDelegateProvider) { + * $sceDelegateProvider.resourceUrlWhitelist([ + * // Allow same origin resource loads. + * 'self', + * // Allow loading from our assets domain. Notice the difference between * and **. + * 'http://srv*.assets.example.com/**' + * ]); + * + * // The blacklist overrides the whitelist so the open redirect here is blocked. + * $sceDelegateProvider.resourceUrlBlacklist([ + * 'http://myapp.example.com/clickThru**' + * ]); + * }); + * ``` + */ + +function $SceDelegateProvider() { + this.SCE_CONTEXTS = SCE_CONTEXTS; + + // Resource URLs can also be trusted by policy. + var resourceUrlWhitelist = ['self'], + resourceUrlBlacklist = []; + + /** + * @ngdoc method + * @name $sceDelegateProvider#resourceUrlWhitelist + * @kind function + * + * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + *
    + * **Note:** an empty whitelist array will block all URLs! + *
    + * + * @return {Array} the currently set whitelist array. + * + * The **default value** when no whitelist has been explicitly set is `['self']` allowing only + * same origin resource requests. + * + * @description + * Sets/Gets the whitelist of trusted resource URLs. + */ + this.resourceUrlWhitelist = function(value) { + if (arguments.length) { + resourceUrlWhitelist = adjustMatchers(value); + } + return resourceUrlWhitelist; + }; + + /** + * @ngdoc method + * @name $sceDelegateProvider#resourceUrlBlacklist + * @kind function + * + * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * The typical usage for the blacklist is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. + * + * Finally, **the blacklist overrides the whitelist** and has the final say. + * + * @return {Array} the currently set blacklist array. + * + * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there + * is no blacklist.) + * + * @description + * Sets/Gets the blacklist of trusted resource URLs. + */ + + this.resourceUrlBlacklist = function(value) { + if (arguments.length) { + resourceUrlBlacklist = adjustMatchers(value); + } + return resourceUrlBlacklist; + }; + + this.$get = ['$injector', function($injector) { + + var htmlSanitizer = function htmlSanitizer(html) { + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + }; + + if ($injector.has('$sanitize')) { + htmlSanitizer = $injector.get('$sanitize'); + } + + + function matchUrl(matcher, parsedUrl) { + if (matcher === 'self') { + return urlIsSameOrigin(parsedUrl); + } else { + // definitely a regex. See adjustMatchers() + return !!matcher.exec(parsedUrl.href); + } + } + + function isResourceUrlAllowedByPolicy(url) { + var parsedUrl = urlResolve(url.toString()); + var i, n, allowed = false; + // Ensure that at least one item from the whitelist allows this url. + for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { + if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { + allowed = true; + break; + } + } + if (allowed) { + // Ensure that no item from the blacklist blocked this url. + for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { + if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { + allowed = false; + break; + } + } + } + return allowed; + } + + function generateHolderType(Base) { + var holderType = function TrustedValueHolderType(trustedValue) { + this.$$unwrapTrustedValue = function() { + return trustedValue; + }; + }; + if (Base) { + holderType.prototype = new Base(); + } + holderType.prototype.valueOf = function sceValueOf() { + return this.$$unwrapTrustedValue(); + }; + holderType.prototype.toString = function sceToString() { + return this.$$unwrapTrustedValue().toString(); + }; + return holderType; + } + + var trustedValueHolderBase = generateHolderType(), + byType = {}; + + byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); + + /** + * @ngdoc method + * @name $sceDelegate#trustAs + * + * @description + * Returns an object that is trusted by angular for use in specified strict + * contextual escaping contexts (such as ng-bind-html, ng-include, any src + * attribute interpolation, any dom event binding attribute interpolation + * such as for onclick, etc.) that uses the provided value. + * See {@link ng.$sce $sce} for enabling strict contextual escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resourceUrl, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + function trustAs(type, trustedValue) { + var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (!Constructor) { + throw $sceMinErr('icontext', + 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', + type, trustedValue); + } + if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { + return trustedValue; + } + // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting + // mutable objects, we ensure here that the value passed in is actually a string. + if (typeof trustedValue !== 'string') { + throw $sceMinErr('itype', + 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', + type); + } + return new Constructor(trustedValue); + } + + /** + * @ngdoc method + * @name $sceDelegate#valueOf + * + * @description + * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. + * + * If the passed parameter is not a value that had been returned by {@link + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. + * + * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} + * call or anything else. + * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns + * `value` unchanged. + */ + function valueOf(maybeTrusted) { + if (maybeTrusted instanceof trustedValueHolderBase) { + return maybeTrusted.$$unwrapTrustedValue(); + } else { + return maybeTrusted; + } + } + + /** + * @ngdoc method + * @name $sceDelegate#getTrusted + * + * @description + * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and + * returns the originally supplied value if the queried context type is a supertype of the + * created type. If this condition isn't satisfied, throws an exception. + * + *
    + * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting + * (XSS) vulnerability in your application. + *
    + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. + */ + function getTrusted(type, maybeTrusted) { + if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { + return maybeTrusted; + } + var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (constructor && maybeTrusted instanceof constructor) { + return maybeTrusted.$$unwrapTrustedValue(); + } + // If we get here, then we may only take one of two actions. + // 1. sanitize the value for the requested type, or + // 2. throw an exception. + if (type === SCE_CONTEXTS.RESOURCE_URL) { + if (isResourceUrlAllowedByPolicy(maybeTrusted)) { + return maybeTrusted; + } else { + throw $sceMinErr('insecurl', + 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', + maybeTrusted.toString()); + } + } else if (type === SCE_CONTEXTS.HTML) { + return htmlSanitizer(maybeTrusted); + } + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + } + + return { trustAs: trustAs, + getTrusted: getTrusted, + valueOf: valueOf }; + }]; +} + + +/** + * @ngdoc provider + * @name $sceProvider + * @this + * + * @description + * + * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. + * - enable/disable Strict Contextual Escaping (SCE) in a module + * - override the default implementation with a custom delegate + * + * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. + */ + +/** + * @ngdoc service + * @name $sce + * @kind function + * + * @description + * + * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. + * + * # Strict Contextual Escaping + * + * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain + * contexts to result in a value that is marked as safe to use for that context. One example of + * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer + * to these contexts as privileged or SCE contexts. + * + * As of version 1.2, Angular ships with SCE enabled by default. + * + * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow + * one to execute arbitrary javascript by the use of the expression() syntax. Refer + * to learn more about them. + * You can ensure your document is in standards mode and not quirks mode by adding `` + * to the top of your HTML document. + * + * SCE assists in writing code in a way that (a) is secure by default and (b) makes auditing for + * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. + * + * Here's an example of a binding in a privileged context: + * + * ``` + * + *
    + * ``` + * + * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE + * disabled, this application allows the user to render arbitrary HTML into the DIV. + * In a more realistic example, one may be rendering user comments, blog articles, etc. via + * bindings. (HTML is just one example of a context where rendering user controlled input creates + * security vulnerabilities.) + * + * For the case of HTML, you might use a library, either on the client side, or on the server side, + * to sanitize unsafe HTML before binding to the value and rendering it in the document. + * + * How would you ensure that every place that used these types of bindings was bound to a value that + * was sanitized by your library (or returned as safe for rendering by your server?) How can you + * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some + * properties/fields and forgot to update the binding to the sanitized value? + * + * To be secure by default, you want to ensure that any such bindings are disallowed unless you can + * determine that something explicitly says it's safe to use a value for binding in that + * context. You can then audit your code (a simple grep would do) to ensure that this is only done + * for those values that you can easily tell are safe - because they were received from your server, + * sanitized by your library, etc. You can organize your codebase to help with this - perhaps + * allowing only the files in a specific directory to do this. Ensuring that the internal API + * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. + * + * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} + * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to + * obtain values that will be accepted by SCE / privileged contexts. + * + * + * ## How does it work? + * + * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted + * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link + * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * + * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link + * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly + * simplified): + * + * ``` + * var ngBindHtmlDirective = ['$sce', function($sce) { + * return function(scope, element, attr) { + * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { + * element.html(value || ''); + * }); + * }; + * }]; + * ``` + * + * ## Impact on loading templates + * + * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as + * `templateUrl`'s specified by {@link guide/directive directives}. + * + * By default, Angular only loads templates from the same domain and protocol as the application + * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or + * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist + * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. + * + * *Please note*: + * The browser's + * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) + * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) + * policy apply in addition to this and may further restrict whether the template is successfully + * loaded. This means that without the right CORS policy, loading templates from a different domain + * won't work on all browsers. Also, loading templates from `file://` URL does not work on some + * browsers. + * + * ## This feels like too much overhead + * + * It's important to remember that SCE only applies to interpolation expressions. + * + * If your expressions are constant literals, they're automatically trusted and you don't need to + * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. + * `
    `) just works. + * + * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them + * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. + * + * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load + * templates in `ng-include` from your application's domain without having to even know about SCE. + * It blocks loading templates from other domains or loading templates over http from an https + * served document. You can change these by setting your own custom {@link + * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link + * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. + * + * This significantly reduces the overhead. It is far easier to pay the small overhead and have an + * application that's secure and can be audited to verify that with much more ease than bolting + * security onto an application later. + * + * + * ## What trusted context types are supported? + * + * | Context | Notes | + * |---------------------|----------------| + * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | + * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | + * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
    Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | + * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | + * + * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
    + * + * Each element in these arrays must be one of the following: + * + * - **'self'** + * - The special **string**, `'self'`, can be used to match against all URLs of the **same + * domain** as the application document using the **same protocol**. + * - **String** (except the special value `'self'`) + * - The string is matched against the full *normalized / absolute URL* of the resource + * being tested (substring matches are not good enough.) + * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters + * match themselves. + * - `*`: matches zero or more occurrences of any character other than one of the following 6 + * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use + * in a whitelist. + * - `**`: matches zero or more occurrences of *any* character. As such, it's not + * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. + * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might + * not have been the intention.) Its usage at the very end of the path is ok. (e.g. + * http://foo.example.com/templates/**). + * - **RegExp** (*see caveat below*) + * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax + * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to + * accidentally introduce a bug when one updates a complex expression (imho, all regexes should + * have good test coverage). For instance, the use of `.` in the regex is correct only in a + * small number of cases. A `.` character in the regex used when matching the scheme or a + * subdomain could be matched against a `:` or literal `.` that was likely not intended. It + * is highly recommended to use the string patterns and only fall back to regular expressions + * as a last resort. + * - The regular expression must be an instance of RegExp (i.e. not a string.) It is + * matched against the **entire** *normalized / absolute URL* of the resource being tested + * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags + * present on the RegExp (such as multiline, global, ignoreCase) are ignored. + * - If you are generating your JavaScript from some other templating engine (not + * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), + * remember to escape your regular expression (and be aware that you might need more than + * one level of escaping depending on your templating engine and the way you interpolated + * the value.) Do make use of your platform's escaping mechanism as it might be good + * enough before coding your own. E.g. Ruby has + * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) + * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). + * Javascript lacks a similar built in function for escaping. Take a look at Google + * Closure library's [goog.string.regExpEscape(s)]( + * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. + * + * ## Show me an example using SCE. + * + * + * + *
    + *

    + * User comments
    + * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + * $sanitize is available. If $sanitize isn't available, this results in an error instead of an + * exploit. + *
    + *
    + * {{userComment.name}}: + * + *
    + *
    + *
    + *
    + *
    + * + * + * angular.module('mySceApp', ['ngSanitize']) + * .controller('AppController', ['$http', '$templateCache', '$sce', + * function AppController($http, $templateCache, $sce) { + * var self = this; + * $http.get('test_data.json', {cache: $templateCache}).then(function(response) { + * self.userComments = response.data; + * }); + * self.explicitlyTrustedHtml = $sce.trustAsHtml( + * 'Hover over this text.'); + * }]); + * + * + * + * [ + * { "name": "Alice", + * "htmlComment": + * "Is anyone reading this?" + * }, + * { "name": "Bob", + * "htmlComment": "Yes! Am I the only other one?" + * } + * ] + * + * + * + * describe('SCE doc demo', function() { + * it('should sanitize untrusted values', function() { + * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML')) + * .toBe('Is anyone reading this?'); + * }); + * + * it('should NOT sanitize explicitly trusted values', function() { + * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe( + * 'Hover over this text.'); + * }); + * }); + * + *
    + * + * + * + * ## Can I disable SCE completely? + * + * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits + * for little coding overhead. It will be much harder to take an SCE disabled application and + * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE + * for cases where you have a lot of existing code that was written before SCE was introduced and + * you're migrating them a module at a time. + * + * That said, here's how you can completely disable SCE: + * + * ``` + * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { + * // Completely disable SCE. For demonstration purposes only! + * // Do not use in new projects. + * $sceProvider.enabled(false); + * }); + * ``` + * + */ + +function $SceProvider() { + var enabled = true; + + /** + * @ngdoc method + * @name $sceProvider#enabled + * @kind function + * + * @param {boolean=} value If provided, then enables/disables SCE. + * @return {boolean} true if SCE is enabled, false otherwise. + * + * @description + * Enables/disables SCE and returns the current value. + */ + this.enabled = function(value) { + if (arguments.length) { + enabled = !!value; + } + return enabled; + }; + + + /* Design notes on the default implementation for SCE. + * + * The API contract for the SCE delegate + * ------------------------------------- + * The SCE delegate object must provide the following 3 methods: + * + * - trustAs(contextEnum, value) + * This method is used to tell the SCE service that the provided value is OK to use in the + * contexts specified by contextEnum. It must return an object that will be accepted by + * getTrusted() for a compatible contextEnum and return this value. + * + * - valueOf(value) + * For values that were not produced by trustAs(), return them as is. For values that were + * produced by trustAs(), return the corresponding input value to trustAs. Basically, if + * trustAs is wrapping the given values into some type, this operation unwraps it when given + * such a value. + * + * - getTrusted(contextEnum, value) + * This function should return the a value that is safe to use in the context specified by + * contextEnum or throw and exception otherwise. + * + * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be + * opaque or wrapped in some holder object. That happens to be an implementation detail. For + * instance, an implementation could maintain a registry of all trusted objects by context. In + * such a case, trustAs() would return the same object that was passed in. getTrusted() would + * return the same object passed in if it was found in the registry under a compatible context or + * throw an exception otherwise. An implementation might only wrap values some of the time based + * on some criteria. getTrusted() might return a value and not throw an exception for special + * constants or objects even if not wrapped. All such implementations fulfill this contract. + * + * + * A note on the inheritance model for SCE contexts + * ------------------------------------------------ + * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This + * is purely an implementation details. + * + * The contract is simply this: + * + * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) + * will also succeed. + * + * Inheritance happens to capture this in a natural way. In some future, we + * may not use inheritance anymore. That is OK because no code outside of + * sce.js and sceSpecs.js would need to be aware of this detail. + */ + + this.$get = ['$parse', '$sceDelegate', function( + $parse, $sceDelegate) { + // Support: IE 9-11 only + // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow + // the "expression(javascript expression)" syntax which is insecure. + if (enabled && msie < 8) { + throw $sceMinErr('iequirks', + 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + + 'mode. You can fix this by adding the text to the top of your HTML ' + + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); + } + + var sce = shallowCopy(SCE_CONTEXTS); + + /** + * @ngdoc method + * @name $sce#isEnabled + * @kind function + * + * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you + * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. + * + * @description + * Returns a boolean indicating if SCE is enabled. + */ + sce.isEnabled = function() { + return enabled; + }; + sce.trustAs = $sceDelegate.trustAs; + sce.getTrusted = $sceDelegate.getTrusted; + sce.valueOf = $sceDelegate.valueOf; + + if (!enabled) { + sce.trustAs = sce.getTrusted = function(type, value) { return value; }; + sce.valueOf = identity; + } + + /** + * @ngdoc method + * @name $sce#parseAs + * + * @description + * Converts Angular {@link guide/expression expression} into a function. This is like {@link + * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it + * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, + * *result*)} + * + * @param {string} type The kind of SCE context in which this result will be used. + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + sce.parseAs = function sceParseAs(type, expr) { + var parsed = $parse(expr); + if (parsed.literal && parsed.constant) { + return parsed; + } else { + return $parse(expr, function(value) { + return sce.getTrusted(type, value); + }); + } + }; + + /** + * @ngdoc method + * @name $sce#trustAs + * + * @description + * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, + * returns an object that is trusted by angular for use in specified strict contextual + * escaping contexts (such as ng-bind-html, ng-include, any src attribute + * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) + * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual + * escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resourceUrl, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + + /** + * @ngdoc method + * @name $sce#trustAsHtml + * + * @description + * Shorthand method. `$sce.trustAsHtml(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml + * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#trustAsUrl + * + * @description + * Shorthand method. `$sce.trustAsUrl(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl + * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#trustAsResourceUrl + * + * @description + * Shorthand method. `$sce.trustAsResourceUrl(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the return + * value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#trustAsJs + * + * @description + * Shorthand method. `$sce.trustAsJs(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs + * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name $sce#getTrusted + * + * @description + * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, + * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the + * originally supplied value if the queried context type is a supertype of the created type. + * If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} + * call. + * @returns {*} The value the was originally provided to + * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. + * Otherwise, throws an exception. + */ + + /** + * @ngdoc method + * @name $sce#getTrustedHtml + * + * @description + * Shorthand method. `$sce.getTrustedHtml(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedCss + * + * @description + * Shorthand method. `$sce.getTrustedCss(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedUrl + * + * @description + * Shorthand method. `$sce.getTrustedUrl(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedResourceUrl + * + * @description + * Shorthand method. `$sce.getTrustedResourceUrl(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to pass to `$sceDelegate.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` + */ + + /** + * @ngdoc method + * @name $sce#getTrustedJs + * + * @description + * Shorthand method. `$sce.getTrustedJs(value)` → + * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` + */ + + /** + * @ngdoc method + * @name $sce#parseAsHtml + * + * @description + * Shorthand method. `$sce.parseAsHtml(expression string)` → + * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsCss + * + * @description + * Shorthand method. `$sce.parseAsCss(value)` → + * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsUrl + * + * @description + * Shorthand method. `$sce.parseAsUrl(value)` → + * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsResourceUrl + * + * @description + * Shorthand method. `$sce.parseAsResourceUrl(value)` → + * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name $sce#parseAsJs + * + * @description + * Shorthand method. `$sce.parseAsJs(value)` → + * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + // Shorthand delegations. + var parse = sce.parseAs, + getTrusted = sce.getTrusted, + trustAs = sce.trustAs; + + forEach(SCE_CONTEXTS, function(enumValue, name) { + var lName = lowercase(name); + sce[snakeToCamel('parse_as_' + lName)] = function(expr) { + return parse(enumValue, expr); + }; + sce[snakeToCamel('get_trusted_' + lName)] = function(value) { + return getTrusted(enumValue, value); + }; + sce[snakeToCamel('trust_as_' + lName)] = function(value) { + return trustAs(enumValue, value); + }; + }); + + return sce; + }]; +} + +/* exported $SnifferProvider */ + +/** + * !!! This is an undocumented "private" service !!! + * + * @name $sniffer + * @requires $window + * @requires $document + * @this + * + * @property {boolean} history Does the browser support html5 history api ? + * @property {boolean} transitions Does the browser support CSS transition events ? + * @property {boolean} animations Does the browser support CSS animation events ? + * + * @description + * This is very simple implementation of testing browser's features. + */ +function $SnifferProvider() { + this.$get = ['$window', '$document', function($window, $document) { + var eventSupport = {}, + // Chrome Packaged Apps are not allowed to access `history.pushState`. + // If not sandboxed, they can be detected by the presence of `chrome.app.runtime` + // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by + // the presence of an extension runtime ID and the absence of other Chrome runtime APIs + // (see https://developer.chrome.com/apps/manifest/sandbox). + isChromePackagedApp = + $window.chrome && + ($window.chrome.app && $window.chrome.app.runtime || + !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id), + hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, + android = + toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), + boxee = /Boxee/i.test(($window.navigator || {}).userAgent), + document = $document[0] || {}, + bodyStyle = document.body && document.body.style, + transitions = false, + animations = false; + + if (bodyStyle) { + // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x + // Mentioned browsers need a -webkit- prefix for transitions & animations. + transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle); + animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle); + } + + + return { + // Android has history.pushState, but it does not update location correctly + // so let's not use the history API at all. + // http://code.google.com/p/android/issues/detail?id=17471 + // https://github.com/angular/angular.js/issues/904 + + // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has + // so let's not use the history API also + // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined + history: !!(hasHistoryPushState && !(android < 4) && !boxee), + hasEvent: function(event) { + // Support: IE 9-11 only + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + // IE10+ implements 'input' event but it erroneously fires under various situations, + // e.g. when placeholder changes, or a form is focused. + if (event === 'input' && msie) return false; + + if (isUndefined(eventSupport[event])) { + var divElm = document.createElement('div'); + eventSupport[event] = 'on' + event in divElm; + } + + return eventSupport[event]; + }, + csp: csp(), + transitions: transitions, + animations: animations, + android: android + }; + }]; +} + +var $templateRequestMinErr = minErr('$compile'); + +/** + * @ngdoc provider + * @name $templateRequestProvider + * @this + * + * @description + * Used to configure the options passed to the {@link $http} service when making a template request. + * + * For example, it can be used for specifying the "Accept" header that is sent to the server, when + * requesting a template. + */ +function $TemplateRequestProvider() { + + var httpOptions; + + /** + * @ngdoc method + * @name $templateRequestProvider#httpOptions + * @description + * The options to be passed to the {@link $http} service when making the request. + * You can use this to override options such as the "Accept" header for template requests. + * + * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the + * options if not overridden here. + * + * @param {string=} value new value for the {@link $http} options. + * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. + */ + this.httpOptions = function(val) { + if (val) { + httpOptions = val; + return this; + } + return httpOptions; + }; + + /** + * @ngdoc service + * @name $templateRequest + * + * @description + * The `$templateRequest` service runs security checks then downloads the provided template using + * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request + * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the + * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the + * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted + * when `tpl` is of type string and `$templateCache` has the matching entry. + * + * If you want to pass custom options to the `$http` service, such as setting the Accept header you + * can configure this via {@link $templateRequestProvider#httpOptions}. + * + * @param {string|TrustedResourceUrl} tpl The HTTP request template URL + * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty + * + * @return {Promise} a promise for the HTTP response data of the given URL. + * + * @property {number} totalPendingRequests total amount of pending template requests being downloaded. + */ + this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce', + function($exceptionHandler, $templateCache, $http, $q, $sce) { + + function handleRequestFn(tpl, ignoreRequestError) { + handleRequestFn.totalPendingRequests++; + + // We consider the template cache holds only trusted templates, so + // there's no need to go through whitelisting again for keys that already + // are included in there. This also makes Angular accept any script + // directive, no matter its name. However, we still need to unwrap trusted + // types. + if (!isString(tpl) || isUndefined($templateCache.get(tpl))) { + tpl = $sce.getTrustedResourceUrl(tpl); + } + + var transformResponse = $http.defaults && $http.defaults.transformResponse; + + if (isArray(transformResponse)) { + transformResponse = transformResponse.filter(function(transformer) { + return transformer !== defaultHttpResponseTransform; + }); + } else if (transformResponse === defaultHttpResponseTransform) { + transformResponse = null; + } + + return $http.get(tpl, extend({ + cache: $templateCache, + transformResponse: transformResponse + }, httpOptions)) + .finally(function() { + handleRequestFn.totalPendingRequests--; + }) + .then(function(response) { + $templateCache.put(tpl, response.data); + return response.data; + }, handleError); + + function handleError(resp) { + if (!ignoreRequestError) { + resp = $templateRequestMinErr('tpload', + 'Failed to load template: {0} (HTTP status: {1} {2})', + tpl, resp.status, resp.statusText); + + $exceptionHandler(resp); + } + + return $q.reject(resp); + } + } + + handleRequestFn.totalPendingRequests = 0; + + return handleRequestFn; + } + ]; +} + +/** @this */ +function $$TestabilityProvider() { + this.$get = ['$rootScope', '$browser', '$location', + function($rootScope, $browser, $location) { + + /** + * @name $testability + * + * @description + * The private $$testability service provides a collection of methods for use when debugging + * or by automated test and debugging tools. + */ + var testability = {}; + + /** + * @name $$testability#findBindings + * + * @description + * Returns an array of elements that are bound (via ng-bind or {{}}) + * to expressions matching the input. + * + * @param {Element} element The element root to search from. + * @param {string} expression The binding expression to match. + * @param {boolean} opt_exactMatch If true, only returns exact matches + * for the expression. Filters and whitespace are ignored. + */ + testability.findBindings = function(element, expression, opt_exactMatch) { + var bindings = element.getElementsByClassName('ng-binding'); + var matches = []; + forEach(bindings, function(binding) { + var dataBinding = angular.element(binding).data('$binding'); + if (dataBinding) { + forEach(dataBinding, function(bindingName) { + if (opt_exactMatch) { + var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); + if (matcher.test(bindingName)) { + matches.push(binding); + } + } else { + if (bindingName.indexOf(expression) !== -1) { + matches.push(binding); + } + } + }); + } + }); + return matches; + }; + + /** + * @name $$testability#findModels + * + * @description + * Returns an array of elements that are two-way found via ng-model to + * expressions matching the input. + * + * @param {Element} element The element root to search from. + * @param {string} expression The model expression to match. + * @param {boolean} opt_exactMatch If true, only returns exact matches + * for the expression. + */ + testability.findModels = function(element, expression, opt_exactMatch) { + var prefixes = ['ng-', 'data-ng-', 'ng\\:']; + for (var p = 0; p < prefixes.length; ++p) { + var attributeEquals = opt_exactMatch ? '=' : '*='; + var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; + var elements = element.querySelectorAll(selector); + if (elements.length) { + return elements; + } + } + }; + + /** + * @name $$testability#getLocation + * + * @description + * Shortcut for getting the location in a browser agnostic way. Returns + * the path, search, and hash. (e.g. /path?a=b#hash) + */ + testability.getLocation = function() { + return $location.url(); + }; + + /** + * @name $$testability#setLocation + * + * @description + * Shortcut for navigating to a location without doing a full page reload. + * + * @param {string} url The location url (path, search and hash, + * e.g. /path?a=b#hash) to go to. + */ + testability.setLocation = function(url) { + if (url !== $location.url()) { + $location.url(url); + $rootScope.$digest(); + } + }; + + /** + * @name $$testability#whenStable + * + * @description + * Calls the callback when $timeout and $http requests are completed. + * + * @param {function} callback + */ + testability.whenStable = function(callback) { + $browser.notifyWhenNoOutstandingRequests(callback); + }; + + return testability; + }]; +} + +/** @this */ +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', + function($rootScope, $browser, $q, $$q, $exceptionHandler) { + + var deferreds = {}; + + + /** + * @ngdoc service + * @name $timeout + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of calling `$timeout` is a promise, which will be resolved when + * the delay has passed and the timeout function, if provided, is executed. + * + * To cancel a timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * If you only want a promise that will be resolved after some specified delay + * then you can call `$timeout` without the `fn` function. + * + * @param {function()=} fn A function, whose execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise + * will be resolved with the return value of the `fn` function. + * + */ + function timeout(fn, delay, invokeApply) { + if (!isFunction(fn)) { + invokeApply = delay; + delay = fn; + fn = noop; + } + + var args = sliceArgs(arguments, 3), + skipApply = (isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise, + timeoutId; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn.apply(null, args)); + } catch (e) { + deferred.reject(e); + $exceptionHandler(e); + } finally { + delete deferreds[promise.$$timeoutId]; + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + + return promise; + } + + + /** + * @ngdoc method + * @name $timeout#cancel + * + * @description + * Cancels a task associated with the `promise`. As a result of this, the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + // Timeout cancels should not report an unhandled promise. + deferreds[promise.$$timeoutId].promise.catch(noop); + deferreds[promise.$$timeoutId].reject('canceled'); + delete deferreds[promise.$$timeoutId]; + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + +// NOTE: The usage of window and document instead of $window and $document here is +// deliberate. This service depends on the specific behavior of anchor nodes created by the +// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and +// cause us to break tests. In addition, when the browser resolves a URL for XHR, it +// doesn't know about mocked locations and resolves URLs to the real document - which is +// exactly the behavior needed here. There is little value is mocking these out for this +// service. +var urlParsingNode = window.document.createElement('a'); +var originUrl = urlResolve(window.location.href); + + +/** + * + * Implementation Notes for non-IE browsers + * ---------------------------------------- + * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, + * results both in the normalizing and parsing of the URL. Normalizing means that a relative + * URL will be resolved into an absolute URL in the context of the application document. + * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related + * properties are all populated to reflect the normalized URL. This approach has wide + * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * + * Implementation Notes for IE + * --------------------------- + * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other + * browsers. However, the parsed components will not be set if the URL assigned did not specify + * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We + * work around that by performing the parsing in a 2nd step by taking a previously normalized + * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the + * properties such as protocol, hostname, port, etc. + * + * References: + * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * http://url.spec.whatwg.org/#urlutils + * https://github.com/angular/angular.js/pull/2902 + * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ + * + * @kind function + * @param {string} url The URL to be parsed. + * @description Normalizes and parses a URL. + * @returns {object} Returns the normalized URL as a dictionary. + * + * | member name | Description | + * |---------------|----------------| + * | href | A normalized version of the provided URL if it was not an absolute URL | + * | protocol | The protocol including the trailing colon | + * | host | The host and port (if the port is non-default) of the normalizedUrl | + * | search | The search params, minus the question mark | + * | hash | The hash string, minus the hash symbol + * | hostname | The hostname + * | port | The port, without ":" + * | pathname | The pathname, beginning with "/" + * + */ +function urlResolve(url) { + var href = url; + + // Support: IE 9-11 only + if (msie) { + // Normalize before parse. Refer Implementation Notes on why this is + // done in two steps on IE. + urlParsingNode.setAttribute('href', href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') + ? urlParsingNode.pathname + : '/' + urlParsingNode.pathname + }; +} + +/** + * Parse a request URL and determine whether this is a same-origin request as the application document. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the request is for the same origin as the application document. + */ +function urlIsSameOrigin(requestUrl) { + var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); +} + +/** + * @ngdoc service + * @name $window + * @this + * + * @description + * A reference to the browser's `window` object. While `window` + * is globally available in JavaScript, it causes testability problems, because + * it is a global variable. In angular we always refer to it through the + * `$window` service, so it may be overridden, removed or mocked for testing. + * + * Expressions, like the one defined for the `ngClick` directive in the example + * below, are evaluated with respect to the current scope. Therefore, there is + * no risk of inadvertently coding in a dependency on a global value in such an + * expression. + * + * @example + + + +
    + + +
    +
    + + it('should display the greeting in the input box', function() { + element(by.model('greeting')).sendKeys('Hello, E2E Tests'); + // If we click the button it will block the test runner + // element(':button').click(); + }); + +
    + */ +function $WindowProvider() { + this.$get = valueFn(window); +} + +/** + * @name $$cookieReader + * @requires $document + * + * @description + * This is a private service for reading cookies used by $http and ngCookies + * + * @return {Object} a key/value map of the current cookies + */ +function $$CookieReader($document) { + var rawDocument = $document[0] || {}; + var lastCookies = {}; + var lastCookieString = ''; + + function safeGetCookie(rawDocument) { + try { + return rawDocument.cookie || ''; + } catch (e) { + return ''; + } + } + + function safeDecodeURIComponent(str) { + try { + return decodeURIComponent(str); + } catch (e) { + return str; + } + } + + return function() { + var cookieArray, cookie, i, index, name; + var currentCookieString = safeGetCookie(rawDocument); + + if (currentCookieString !== lastCookieString) { + lastCookieString = currentCookieString; + cookieArray = lastCookieString.split('; '); + lastCookies = {}; + + for (i = 0; i < cookieArray.length; i++) { + cookie = cookieArray[i]; + index = cookie.indexOf('='); + if (index > 0) { //ignore nameless cookies + name = safeDecodeURIComponent(cookie.substring(0, index)); + // the first value that is seen for a cookie is the most + // specific one. values for the same cookie name that + // follow are for less specific paths. + if (isUndefined(lastCookies[name])) { + lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); + } + } + } + } + return lastCookies; + }; +} + +$$CookieReader.$inject = ['$document']; + +/** @this */ +function $$CookieReaderProvider() { + this.$get = $$CookieReader; +} + +/* global currencyFilter: true, + dateFilter: true, + filterFilter: true, + jsonFilter: true, + limitToFilter: true, + lowercaseFilter: true, + numberFilter: true, + orderByFilter: true, + uppercaseFilter: true, + */ + +/** + * @ngdoc provider + * @name $filterProvider + * @description + * + * Filters are just functions which transform input to an output. However filters need to be + * Dependency Injected. To achieve this a filter definition consists of a factory function which is + * annotated with dependencies and is responsible for creating a filter function. + * + *
    + * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. + * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace + * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores + * (`myapp_subsection_filterx`). + *
    + * + * ```js + * // Filter registration + * function MyModule($provide, $filterProvider) { + * // create a service to demonstrate injection (not always needed) + * $provide.value('greet', function(name){ + * return 'Hello ' + name + '!'; + * }); + * + * // register a filter factory which uses the + * // greet service to demonstrate DI. + * $filterProvider.register('greet', function(greet){ + * // return the filter function which uses the greet service + * // to generate salutation + * return function(text) { + * // filters need to be forgiving so check input validity + * return text && greet(text) || text; + * }; + * }); + * } + * ``` + * + * The filter function is registered with the `$injector` under the filter name suffix with + * `Filter`. + * + * ```js + * it('should be the same instance', inject( + * function($filterProvider) { + * $filterProvider.register('reverse', function(){ + * return ...; + * }); + * }, + * function($filter, reverseFilter) { + * expect($filter('reverse')).toBe(reverseFilter); + * }); + * ``` + * + * + * For more information about how angular filters work, and how to create your own filters, see + * {@link guide/filter Filters} in the Angular Developer Guide. + */ + +/** + * @ngdoc service + * @name $filter + * @kind function + * @description + * Filters are used for formatting data displayed to the user. + * + * They can be used in view templates, controllers or services.Angular comes + * with a collection of [built-in filters](api/ng/filter), but it is easy to + * define your own as well. + * + * The general syntax in templates is as follows: + * + * ```html + * {{ expression [| filter_name[:parameter_value] ... ] }} + * ``` + * + * @param {String} name Name of the filter function to retrieve + * @return {Function} the filter function + * @example + + +
    +

    {{ originalText }}

    +

    {{ filteredText }}

    +
    +
    + + + angular.module('filterExample', []) + .controller('MainCtrl', function($scope, $filter) { + $scope.originalText = 'hello'; + $scope.filteredText = $filter('uppercase')($scope.originalText); + }); + +
    + */ +$FilterProvider.$inject = ['$provide']; +/** @this */ +function $FilterProvider($provide) { + var suffix = 'Filter'; + + /** + * @ngdoc method + * @name $filterProvider#register + * @param {string|Object} name Name of the filter function, or an object map of filters where + * the keys are the filter names and the values are the filter factories. + * + *
    + * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. + * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace + * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores + * (`myapp_subsection_filterx`). + *
    + * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. + * @returns {Object} Registered filter instance, or if a map of filters was provided then a map + * of the registered filter instances. + */ + function register(name, factory) { + if (isObject(name)) { + var filters = {}; + forEach(name, function(filter, key) { + filters[key] = register(key, filter); + }); + return filters; + } else { + return $provide.factory(name + suffix, factory); + } + } + this.register = register; + + this.$get = ['$injector', function($injector) { + return function(name) { + return $injector.get(name + suffix); + }; + }]; + + //////////////////////////////////////// + + /* global + currencyFilter: false, + dateFilter: false, + filterFilter: false, + jsonFilter: false, + limitToFilter: false, + lowercaseFilter: false, + numberFilter: false, + orderByFilter: false, + uppercaseFilter: false + */ + + register('currency', currencyFilter); + register('date', dateFilter); + register('filter', filterFilter); + register('json', jsonFilter); + register('limitTo', limitToFilter); + register('lowercase', lowercaseFilter); + register('number', numberFilter); + register('orderBy', orderByFilter); + register('uppercase', uppercaseFilter); +} + +/** + * @ngdoc filter + * @name filter + * @kind function + * + * @description + * Selects a subset of items from `array` and returns it as a new array. + * + * @param {Array} array The source array. + * @param {string|Object|function()} expression The predicate to be used for selecting items from + * `array`. + * + * Can be one of: + * + * - `string`: The string is used for matching against the contents of the `array`. All strings or + * objects with string properties in `array` that match this string will be returned. This also + * applies to nested object properties. + * The predicate can be negated by prefixing the string with `!`. + * + * - `Object`: A pattern object can be used to filter specific properties on objects contained + * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items + * which have property `name` containing "M" and property `phone` containing "1". A special + * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match + * against any property of the object or its nested object properties. That's equivalent to the + * simple substring match with a `string` as described above. The special property name can be + * overwritten, using the `anyPropertyKey` parameter. + * The predicate can be negated by prefixing the string with `!`. + * For example `{name: "!M"}` predicate will return an array of items which have property `name` + * not containing "M". + * + * Note that a named property will match properties on the same level only, while the special + * `$` property will match properties on the same level or deeper. E.g. an array item like + * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but + * **will** be matched by `{$: 'John'}`. + * + * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. + * The function is called for each element of the array, with the element, its index, and + * the entire array itself as arguments. + * + * The final result is an array of those elements that the predicate returned true for. + * + * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in + * determining if the expected value (from the filter expression) and actual value (from + * the object in the array) should be considered a match. + * + * Can be one of: + * + * - `function(actual, expected)`: + * The function will be given the object value and the predicate value to compare and + * should return true if both values should be considered equal. + * + * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. + * This is essentially strict comparison of expected and actual. + * + * - `false`: A short hand for a function which will look for a substring match in a case + * insensitive way. Primitive values are converted to strings. Objects are not compared against + * primitives, unless they have a custom `toString` method (e.g. `Date` objects). + * + * + * Defaults to `false`. + * + * @param {string} [anyPropertyKey] The special property name that matches against any property. + * By default `$`. + * + * @example + + +
    + + + + + + + + +
    NamePhone
    {{friend.name}}{{friend.phone}}
    +
    +
    +
    +
    +
    + + + + + + +
    NamePhone
    {{friendObj.name}}{{friendObj.phone}}
    +
    + + var expectFriendNames = function(expectedNames, key) { + element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { + arr.forEach(function(wd, i) { + expect(wd.getText()).toMatch(expectedNames[i]); + }); + }); + }; + + it('should search across all fields when filtering with a string', function() { + var searchText = element(by.model('searchText')); + searchText.clear(); + searchText.sendKeys('m'); + expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); + + searchText.clear(); + searchText.sendKeys('76'); + expectFriendNames(['John', 'Julie'], 'friend'); + }); + + it('should search in specific fields when filtering with a predicate object', function() { + var searchAny = element(by.model('search.$')); + searchAny.clear(); + searchAny.sendKeys('i'); + expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); + }); + it('should use a equal comparison when comparator is true', function() { + var searchName = element(by.model('search.name')); + var strict = element(by.model('strict')); + searchName.clear(); + searchName.sendKeys('Julie'); + strict.click(); + expectFriendNames(['Julie'], 'friendObj'); + }); + +
    + */ + +function filterFilter() { + return function(array, expression, comparator, anyPropertyKey) { + if (!isArrayLike(array)) { + if (array == null) { + return array; + } else { + throw minErr('filter')('notarray', 'Expected array but received: {0}', array); + } + } + + anyPropertyKey = anyPropertyKey || '$'; + var expressionType = getTypeForFilter(expression); + var predicateFn; + var matchAgainstAnyProp; + + switch (expressionType) { + case 'function': + predicateFn = expression; + break; + case 'boolean': + case 'null': + case 'number': + case 'string': + matchAgainstAnyProp = true; + // falls through + case 'object': + predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp); + break; + default: + return array; + } + + return Array.prototype.filter.call(array, predicateFn); + }; +} + +// Helper functions for `filterFilter` +function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) { + var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression); + var predicateFn; + + if (comparator === true) { + comparator = equals; + } else if (!isFunction(comparator)) { + comparator = function(actual, expected) { + if (isUndefined(actual)) { + // No substring matching against `undefined` + return false; + } + if ((actual === null) || (expected === null)) { + // No substring matching against `null`; only match against `null` + return actual === expected; + } + if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { + // Should not compare primitives against objects, unless they have custom `toString` method + return false; + } + + actual = lowercase('' + actual); + expected = lowercase('' + expected); + return actual.indexOf(expected) !== -1; + }; + } + + predicateFn = function(item) { + if (shouldMatchPrimitives && !isObject(item)) { + return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false); + } + return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp); + }; + + return predicateFn; +} + +function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) { + var actualType = getTypeForFilter(actual); + var expectedType = getTypeForFilter(expected); + + if ((expectedType === 'string') && (expected.charAt(0) === '!')) { + return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp); + } else if (isArray(actual)) { + // In case `actual` is an array, consider it a match + // if ANY of it's items matches `expected` + return actual.some(function(item) { + return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp); + }); + } + + switch (actualType) { + case 'object': + var key; + if (matchAgainstAnyProp) { + for (key in actual) { + if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { + return true; + } + } + return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false); + } else if (expectedType === 'object') { + for (key in expected) { + var expectedVal = expected[key]; + if (isFunction(expectedVal) || isUndefined(expectedVal)) { + continue; + } + + var matchAnyProperty = key === anyPropertyKey; + var actualVal = matchAnyProperty ? actual : actual[key]; + if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) { + return false; + } + } + return true; + } else { + return comparator(actual, expected); + } + case 'function': + return false; + default: + return comparator(actual, expected); + } +} + +// Used for easily differentiating between `null` and actual `object` +function getTypeForFilter(val) { + return (val === null) ? 'null' : typeof val; +} + +var MAX_DIGITS = 22; +var DECIMAL_SEP = '.'; +var ZERO_CHAR = '0'; + +/** + * @ngdoc filter + * @name currency + * @kind function + * + * @description + * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default + * symbol for current locale is used. + * + * @param {number} amount Input to filter. + * @param {string=} symbol Currency symbol or identifier to be displayed. + * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale + * @returns {string} Formatted number. + * + * + * @example + + + +
    +
    + default currency symbol ($): {{amount | currency}}
    + custom currency identifier (USD$): {{amount | currency:"USD$"}}
    + no fractions (0): {{amount | currency:"USD$":0}} +
    +
    + + it('should init with 1234.56', function() { + expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); + expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); + expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); + }); + it('should update', function() { + if (browser.params.browser === 'safari') { + // Safari does not understand the minus key. See + // https://github.com/angular/protractor/issues/481 + return; + } + element(by.model('amount')).clear(); + element(by.model('amount')).sendKeys('-1234'); + expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); + expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); + expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); + }); + +
    + */ +currencyFilter.$inject = ['$locale']; +function currencyFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(amount, currencySymbol, fractionSize) { + if (isUndefined(currencySymbol)) { + currencySymbol = formats.CURRENCY_SYM; + } + + if (isUndefined(fractionSize)) { + fractionSize = formats.PATTERNS[1].maxFrac; + } + + // if null or undefined pass it through + return (amount == null) + ? amount + : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). + replace(/\u00A4/g, currencySymbol); + }; +} + +/** + * @ngdoc filter + * @name number + * @kind function + * + * @description + * Formats a number as text. + * + * If the input is null or undefined, it will just be returned. + * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively. + * If the input is not a number an empty string is returned. + * + * + * @param {number|string} number Number to format. + * @param {(number|string)=} fractionSize Number of decimal places to round the number to. + * If this is not provided then the fraction size is computed from the current locale's number + * formatting pattern. In the case of the default locale, it will be 3. + * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current + * locale (e.g., in the en_US locale it will have "." as the decimal separator and + * include "," group separators after each third digit). + * + * @example + + + +
    +
    + Default formatting: {{val | number}}
    + No fractions: {{val | number:0}}
    + Negative number: {{-val | number:4}} +
    +
    + + it('should format numbers', function() { + expect(element(by.id('number-default')).getText()).toBe('1,234.568'); + expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); + expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); + }); + + it('should update', function() { + element(by.model('val')).clear(); + element(by.model('val')).sendKeys('3374.333'); + expect(element(by.id('number-default')).getText()).toBe('3,374.333'); + expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); + expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); + }); + +
    + */ +numberFilter.$inject = ['$locale']; +function numberFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(number, fractionSize) { + + // if null or undefined pass it through + return (number == null) + ? number + : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, + fractionSize); + }; +} + +/** + * Parse a number (as a string) into three components that can be used + * for formatting the number. + * + * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) + * + * @param {string} numStr The number to parse + * @return {object} An object describing this number, containing the following keys: + * - d : an array of digits containing leading zeros as necessary + * - i : the number of the digits in `d` that are to the left of the decimal point + * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` + * + */ +function parse(numStr) { + var exponent = 0, digits, numberOfIntegerDigits; + var i, j, zeros; + + // Decimal point? + if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { + numStr = numStr.replace(DECIMAL_SEP, ''); + } + + // Exponential form? + if ((i = numStr.search(/e/i)) > 0) { + // Work out the exponent. + if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; + numberOfIntegerDigits += +numStr.slice(i + 1); + numStr = numStr.substring(0, i); + } else if (numberOfIntegerDigits < 0) { + // There was no decimal point or exponent so it is an integer. + numberOfIntegerDigits = numStr.length; + } + + // Count the number of leading zeros. + for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ } + + if (i === (zeros = numStr.length)) { + // The digits are all zero. + digits = [0]; + numberOfIntegerDigits = 1; + } else { + // Count the number of trailing zeros + zeros--; + while (numStr.charAt(zeros) === ZERO_CHAR) zeros--; + + // Trailing zeros are insignificant so ignore them + numberOfIntegerDigits -= i; + digits = []; + // Convert string to array of digits without leading/trailing zeros. + for (j = 0; i <= zeros; i++, j++) { + digits[j] = +numStr.charAt(i); + } + } + + // If the number overflows the maximum allowed digits then use an exponent. + if (numberOfIntegerDigits > MAX_DIGITS) { + digits = digits.splice(0, MAX_DIGITS - 1); + exponent = numberOfIntegerDigits - 1; + numberOfIntegerDigits = 1; + } + + return { d: digits, e: exponent, i: numberOfIntegerDigits }; +} + +/** + * Round the parsed number to the specified number of decimal places + * This function changed the parsedNumber in-place + */ +function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { + var digits = parsedNumber.d; + var fractionLen = digits.length - parsedNumber.i; + + // determine fractionSize if it is not specified; `+fractionSize` converts it to a number + fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; + + // The index of the digit to where rounding is to occur + var roundAt = fractionSize + parsedNumber.i; + var digit = digits[roundAt]; + + if (roundAt > 0) { + // Drop fractional digits beyond `roundAt` + digits.splice(Math.max(parsedNumber.i, roundAt)); + + // Set non-fractional digits beyond `roundAt` to 0 + for (var j = roundAt; j < digits.length; j++) { + digits[j] = 0; + } + } else { + // We rounded to zero so reset the parsedNumber + fractionLen = Math.max(0, fractionLen); + parsedNumber.i = 1; + digits.length = Math.max(1, roundAt = fractionSize + 1); + digits[0] = 0; + for (var i = 1; i < roundAt; i++) digits[i] = 0; + } + + if (digit >= 5) { + if (roundAt - 1 < 0) { + for (var k = 0; k > roundAt; k--) { + digits.unshift(0); + parsedNumber.i++; + } + digits.unshift(1); + parsedNumber.i++; + } else { + digits[roundAt - 1]++; + } + } + + // Pad out with zeros to get the required fraction length + for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); + + + // Do any carrying, e.g. a digit was rounded up to 10 + var carry = digits.reduceRight(function(carry, d, i, digits) { + d = d + carry; + digits[i] = d % 10; + return Math.floor(d / 10); + }, 0); + if (carry) { + digits.unshift(carry); + parsedNumber.i++; + } +} + +/** + * Format a number into a string + * @param {number} number The number to format + * @param {{ + * minFrac, // the minimum number of digits required in the fraction part of the number + * maxFrac, // the maximum number of digits required in the fraction part of the number + * gSize, // number of digits in each group of separated digits + * lgSize, // number of digits in the last group of digits before the decimal separator + * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) + * posPre, // the string to go in front of a positive number + * negSuf, // the string to go after a negative number (e.g. `)`) + * posSuf // the string to go after a positive number + * }} pattern + * @param {string} groupSep The string to separate groups of number (e.g. `,`) + * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) + * @param {[type]} fractionSize The size of the fractional part of the number + * @return {string} The number formatted as a string + */ +function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { + + if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; + + var isInfinity = !isFinite(number); + var isZero = false; + var numStr = Math.abs(number) + '', + formattedText = '', + parsedNumber; + + if (isInfinity) { + formattedText = '\u221e'; + } else { + parsedNumber = parse(numStr); + + roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); + + var digits = parsedNumber.d; + var integerLen = parsedNumber.i; + var exponent = parsedNumber.e; + var decimals = []; + isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); + + // pad zeros for small numbers + while (integerLen < 0) { + digits.unshift(0); + integerLen++; + } + + // extract decimals digits + if (integerLen > 0) { + decimals = digits.splice(integerLen, digits.length); + } else { + decimals = digits; + digits = [0]; + } + + // format the integer digits with grouping separators + var groups = []; + if (digits.length >= pattern.lgSize) { + groups.unshift(digits.splice(-pattern.lgSize, digits.length).join('')); + } + while (digits.length > pattern.gSize) { + groups.unshift(digits.splice(-pattern.gSize, digits.length).join('')); + } + if (digits.length) { + groups.unshift(digits.join('')); + } + formattedText = groups.join(groupSep); + + // append the decimal digits + if (decimals.length) { + formattedText += decimalSep + decimals.join(''); + } + + if (exponent) { + formattedText += 'e+' + exponent; + } + } + if (number < 0 && !isZero) { + return pattern.negPre + formattedText + pattern.negSuf; + } else { + return pattern.posPre + formattedText + pattern.posSuf; + } +} + +function padNumber(num, digits, trim, negWrap) { + var neg = ''; + if (num < 0 || (negWrap && num <= 0)) { + if (negWrap) { + num = -num + 1; + } else { + num = -num; + neg = '-'; + } + } + num = '' + num; + while (num.length < digits) num = ZERO_CHAR + num; + if (trim) { + num = num.substr(num.length - digits); + } + return neg + num; +} + + +function dateGetter(name, size, offset, trim, negWrap) { + offset = offset || 0; + return function(date) { + var value = date['get' + name](); + if (offset > 0 || value > -offset) { + value += offset; + } + if (value === 0 && offset === -12) value = 12; + return padNumber(value, size, trim, negWrap); + }; +} + +function dateStrGetter(name, shortForm, standAlone) { + return function(date, formats) { + var value = date['get' + name](); + var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); + var get = uppercase(propPrefix + name); + + return formats[get][value]; + }; +} + +function timeZoneGetter(date, formats, offset) { + var zone = -1 * offset; + var paddedZone = (zone >= 0) ? '+' : ''; + + paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + + padNumber(Math.abs(zone % 60), 2); + + return paddedZone; +} + +function getFirstThursdayOfYear(year) { + // 0 = index of January + var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); + // 4 = index of Thursday (+1 to account for 1st = 5) + // 11 = index of *next* Thursday (+1 account for 1st = 12) + return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); +} + +function getThursdayThisWeek(datetime) { + return new Date(datetime.getFullYear(), datetime.getMonth(), + // 4 = index of Thursday + datetime.getDate() + (4 - datetime.getDay())); +} + +function weekGetter(size) { + return function(date) { + var firstThurs = getFirstThursdayOfYear(date.getFullYear()), + thisThurs = getThursdayThisWeek(date); + + var diff = +thisThurs - +firstThurs, + result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week + + return padNumber(result, size); + }; +} + +function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; +} + +function eraGetter(date, formats) { + return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; +} + +function longEraGetter(date, formats) { + return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; +} + +var DATE_FORMATS = { + yyyy: dateGetter('FullYear', 4, 0, false, true), + yy: dateGetter('FullYear', 2, 0, true, true), + y: dateGetter('FullYear', 1, 0, false, true), + MMMM: dateStrGetter('Month'), + MMM: dateStrGetter('Month', true), + MM: dateGetter('Month', 2, 1), + M: dateGetter('Month', 1, 1), + LLLL: dateStrGetter('Month', false, true), + dd: dateGetter('Date', 2), + d: dateGetter('Date', 1), + HH: dateGetter('Hours', 2), + H: dateGetter('Hours', 1), + hh: dateGetter('Hours', 2, -12), + h: dateGetter('Hours', 1, -12), + mm: dateGetter('Minutes', 2), + m: dateGetter('Minutes', 1), + ss: dateGetter('Seconds', 2), + s: dateGetter('Seconds', 1), + // while ISO 8601 requires fractions to be prefixed with `.` or `,` + // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions + sss: dateGetter('Milliseconds', 3), + EEEE: dateStrGetter('Day'), + EEE: dateStrGetter('Day', true), + a: ampmGetter, + Z: timeZoneGetter, + ww: weekGetter(2), + w: weekGetter(1), + G: eraGetter, + GG: eraGetter, + GGG: eraGetter, + GGGG: longEraGetter +}; + +var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, + NUMBER_STRING = /^-?\d+$/; + +/** + * @ngdoc filter + * @name date + * @kind function + * + * @description + * Formats `date` to a string based on the requested `format`. + * + * `format` string can be composed of the following elements: + * + * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) + * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) + * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) + * * `'MMMM'`: Month in year (January-December) + * * `'MMM'`: Month in year (Jan-Dec) + * * `'MM'`: Month in year, padded (01-12) + * * `'M'`: Month in year (1-12) + * * `'LLLL'`: Stand-alone month in year (January-December) + * * `'dd'`: Day in month, padded (01-31) + * * `'d'`: Day in month (1-31) + * * `'EEEE'`: Day in Week,(Sunday-Saturday) + * * `'EEE'`: Day in Week, (Sun-Sat) + * * `'HH'`: Hour in day, padded (00-23) + * * `'H'`: Hour in day (0-23) + * * `'hh'`: Hour in AM/PM, padded (01-12) + * * `'h'`: Hour in AM/PM, (1-12) + * * `'mm'`: Minute in hour, padded (00-59) + * * `'m'`: Minute in hour (0-59) + * * `'ss'`: Second in minute, padded (00-59) + * * `'s'`: Second in minute (0-59) + * * `'sss'`: Millisecond in second, padded (000-999) + * * `'a'`: AM/PM marker + * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) + * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year + * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year + * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') + * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') + * + * `format` string can also be one of the following predefined + * {@link guide/i18n localizable formats}: + * + * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale + * (e.g. Sep 3, 2010 12:05:08 PM) + * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) + * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale + * (e.g. Friday, September 3, 2010) + * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) + * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) + * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) + * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) + * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) + * + * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. + * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence + * (e.g. `"h 'o''clock'"`). + * + * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its + * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is + * specified in the string input, the time is considered to be in the local timezone. + * @param {string=} format Formatting rules (see Description). If not specified, + * `mediumDate` is used. + * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the + * continental US time zone abbreviations, but for general use, use a time zone offset, for + * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) + * If not specified, the timezone of the browser will be used. + * @returns {string} Formatted string or the input if input is not recognized as date/millis. + * + * @example + + + {{1288323623006 | date:'medium'}}: + {{1288323623006 | date:'medium'}}
    + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
    + {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: + {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
    + {{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}: + {{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}
    +
    + + it('should format date', function() { + expect(element(by.binding("1288323623006 | date:'medium'")).getText()). + toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); + expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). + toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/); + expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). + toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); + expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). + toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); + }); + +
    + */ +dateFilter.$inject = ['$locale']; +function dateFilter($locale) { + + + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; + // 1 2 3 4 5 6 7 8 9 10 11 + function jsonStringToDate(string) { + var match; + if ((match = string.match(R_ISO8601_STR))) { + var date = new Date(0), + tzHour = 0, + tzMin = 0, + dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, + timeSetter = match[8] ? date.setUTCHours : date.setHours; + + if (match[9]) { + tzHour = toInt(match[9] + match[10]); + tzMin = toInt(match[9] + match[11]); + } + dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); + var h = toInt(match[4] || 0) - tzHour; + var m = toInt(match[5] || 0) - tzMin; + var s = toInt(match[6] || 0); + var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); + timeSetter.call(date, h, m, s, ms); + return date; + } + return string; + } + + + return function(date, format, timezone) { + var text = '', + parts = [], + fn, match; + + format = format || 'mediumDate'; + format = $locale.DATETIME_FORMATS[format] || format; + if (isString(date)) { + date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); + } + + if (isNumber(date)) { + date = new Date(date); + } + + if (!isDate(date) || !isFinite(date.getTime())) { + return date; + } + + while (format) { + match = DATE_FORMATS_SPLIT.exec(format); + if (match) { + parts = concat(parts, match, 1); + format = parts.pop(); + } else { + parts.push(format); + format = null; + } + } + + var dateTimezoneOffset = date.getTimezoneOffset(); + if (timezone) { + dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); + date = convertTimezoneToLocal(date, timezone, true); + } + forEach(parts, function(value) { + fn = DATE_FORMATS[value]; + text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) + : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\''); + }); + + return text; + }; +} + + +/** + * @ngdoc filter + * @name json + * @kind function + * + * @description + * Allows you to convert a JavaScript object into JSON string. + * + * This filter is mostly useful for debugging. When using the double curly {{value}} notation + * the binding is automatically converted to JSON. + * + * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. + * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. + * @returns {string} JSON string. + * + * + * @example + + +
    {{ {'name':'value'} | json }}
    +
    {{ {'name':'value'} | json:4 }}
    +
    + + it('should jsonify filtered objects', function() { + expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/); + expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/); + }); + +
    + * + */ +function jsonFilter() { + return function(object, spacing) { + if (isUndefined(spacing)) { + spacing = 2; + } + return toJson(object, spacing); + }; +} + + +/** + * @ngdoc filter + * @name lowercase + * @kind function + * @description + * Converts string to lowercase. + * @see angular.lowercase + */ +var lowercaseFilter = valueFn(lowercase); + + +/** + * @ngdoc filter + * @name uppercase + * @kind function + * @description + * Converts string to uppercase. + * @see angular.uppercase + */ +var uppercaseFilter = valueFn(uppercase); + +/** + * @ngdoc filter + * @name limitTo + * @kind function + * + * @description + * Creates a new array or string containing only a specified number of elements. The elements are + * taken from either the beginning or the end of the source array, string or number, as specified by + * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported + * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input, + * it is converted to a string. + * + * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited. + * @param {string|number} limit - The length of the returned array or string. If the `limit` number + * is positive, `limit` number of items from the beginning of the source array/string are copied. + * If the number is negative, `limit` number of items from the end of the source array/string + * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, + * the input will be returned unchanged. + * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index, + * `begin` indicates an offset from the end of `input`. Defaults to `0`. + * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had + * less than `limit` elements. + * + * @example + + + +
    + +

    Output numbers: {{ numbers | limitTo:numLimit }}

    + +

    Output letters: {{ letters | limitTo:letterLimit }}

    + +

    Output long number: {{ longNumber | limitTo:longNumberLimit }}

    +
    +
    + + var numLimitInput = element(by.model('numLimit')); + var letterLimitInput = element(by.model('letterLimit')); + var longNumberLimitInput = element(by.model('longNumberLimit')); + var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); + var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); + var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); + + it('should limit the number array to first three items', function() { + expect(numLimitInput.getAttribute('value')).toBe('3'); + expect(letterLimitInput.getAttribute('value')).toBe('3'); + expect(longNumberLimitInput.getAttribute('value')).toBe('3'); + expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); + expect(limitedLetters.getText()).toEqual('Output letters: abc'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); + }); + + // There is a bug in safari and protractor that doesn't like the minus key + // it('should update the output when -3 is entered', function() { + // numLimitInput.clear(); + // numLimitInput.sendKeys('-3'); + // letterLimitInput.clear(); + // letterLimitInput.sendKeys('-3'); + // longNumberLimitInput.clear(); + // longNumberLimitInput.sendKeys('-3'); + // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); + // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); + // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); + // }); + + it('should not exceed the maximum size of input array', function() { + numLimitInput.clear(); + numLimitInput.sendKeys('100'); + letterLimitInput.clear(); + letterLimitInput.sendKeys('100'); + longNumberLimitInput.clear(); + longNumberLimitInput.sendKeys('100'); + expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); + expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); + }); + +
    +*/ +function limitToFilter() { + return function(input, limit, begin) { + if (Math.abs(Number(limit)) === Infinity) { + limit = Number(limit); + } else { + limit = toInt(limit); + } + if (isNumberNaN(limit)) return input; + + if (isNumber(input)) input = input.toString(); + if (!isArrayLike(input)) return input; + + begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); + begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; + + if (limit >= 0) { + return sliceFn(input, begin, begin + limit); + } else { + if (begin === 0) { + return sliceFn(input, limit, input.length); + } else { + return sliceFn(input, Math.max(0, begin + limit), begin); + } + } + }; +} + +function sliceFn(input, begin, end) { + if (isString(input)) return input.slice(begin, end); + + return slice.call(input, begin, end); +} + +/** + * @ngdoc filter + * @name orderBy + * @kind function + * + * @description + * Returns an array containing the items from the specified `collection`, ordered by a `comparator` + * function based on the values computed using the `expression` predicate. + * + * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in + * `[{id: 'bar'}, {id: 'foo'}]`. + * + * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray, + * String, etc). + * + * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker + * for the preceding one. The `expression` is evaluated against each item and the output is used + * for comparing with other items. + * + * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in + * ascending order. + * + * The comparison is done using the `comparator` function. If none is specified, a default, built-in + * comparator is used (see below for details - in a nutshell, it compares numbers numerically and + * strings alphabetically). + * + * ### Under the hood + * + * Ordering the specified `collection` happens in two phases: + * + * 1. All items are passed through the predicate (or predicates), and the returned values are saved + * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed + * through a predicate that extracts the value of the `label` property, would be transformed to: + * ``` + * { + * value: 'foo', + * type: 'string', + * index: ... + * } + * ``` + * 2. The comparator function is used to sort the items, based on the derived values, types and + * indices. + * + * If you use a custom comparator, it will be called with pairs of objects of the form + * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal + * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the + * second, or `1` otherwise. + * + * In order to ensure that the sorting will be deterministic across platforms, if none of the + * specified predicates can distinguish between two items, `orderBy` will automatically introduce a + * dummy predicate that returns the item's index as `value`. + * (If you are using a custom comparator, make sure it can handle this predicate as well.) + * + * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted + * value for an item, `orderBy` will try to convert that object to a primitive value, before passing + * it to the comparator. The following rules govern the conversion: + * + * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be + * used instead.
    + * (If the object has a `valueOf()` method that returns another object, then the returned object + * will be used in subsequent steps.) + * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that + * returns a primitive, its return value will be used instead.
    + * (If the object has a `toString()` method that returns another object, then the returned object + * will be used in subsequent steps.) + * 3. No conversion; the object itself is used. + * + * ### The default comparator + * + * The default, built-in comparator should be sufficient for most usecases. In short, it compares + * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to + * using their index in the original collection, and sorts values of different types by type. + * + * More specifically, it follows these steps to determine the relative order of items: + * + * 1. If the compared values are of different types, compare the types themselves alphabetically. + * 2. If both values are of type `string`, compare them alphabetically in a case- and + * locale-insensitive way. + * 3. If both values are objects, compare their indices instead. + * 4. Otherwise, return: + * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`). + * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator). + * - `1`, otherwise. + * + * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being + * saved as numbers and not strings. + * **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e. + * `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to + * other values. + * + * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort. + * @param {(Function|string|Array.)=} expression - A predicate (or list of + * predicates) to be used by the comparator to determine the order of elements. + * + * Can be one of: + * + * - `Function`: A getter function. This function will be called with each item as argument and + * the return value will be used for sorting. + * - `string`: An Angular expression. This expression will be evaluated against each item and the + * result will be used for sorting. For example, use `'label'` to sort by a property called + * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label` + * property.
    + * (The result of a constant expression is interpreted as a property name to be used for + * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a + * property called `special name`.)
    + * An expression can be optionally prefixed with `+` or `-` to control the sorting direction, + * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided, + * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons. + * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the + * relative order of two items, the next predicate is used as a tie-breaker. + * + * **Note:** If the predicate is missing or empty then it defaults to `'+'`. + * + * @param {boolean=} reverse - If `true`, reverse the sorting order. + * @param {(Function)=} comparator - The comparator function used to determine the relative order of + * value pairs. If omitted, the built-in comparator will be used. + * + * @returns {Array} - The sorted array. + * + * + * @example + * ### Ordering a table with `ngRepeat` + * + * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by + * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means + * it defaults to the built-in comparator. + * + + +
    + + + + + + + + + + + +
    NamePhone NumberAge
    {{friend.name}}{{friend.phone}}{{friend.age}}
    +
    +
    + + angular.module('orderByExample1', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.friends = [ + {name: 'John', phone: '555-1212', age: 10}, + {name: 'Mary', phone: '555-9876', age: 19}, + {name: 'Mike', phone: '555-4321', age: 21}, + {name: 'Adam', phone: '555-5678', age: 35}, + {name: 'Julie', phone: '555-8765', age: 29} + ]; + }]); + + + .friends { + border-collapse: collapse; + } + + .friends th { + border-bottom: 1px solid; + } + .friends td, .friends th { + border-left: 1px solid; + padding: 5px 10px; + } + .friends td:first-child, .friends th:first-child { + border-left: none; + } + + + // Element locators + var names = element.all(by.repeater('friends').column('friend.name')); + + it('should sort friends by age in reverse order', function() { + expect(names.get(0).getText()).toBe('Adam'); + expect(names.get(1).getText()).toBe('Julie'); + expect(names.get(2).getText()).toBe('Mike'); + expect(names.get(3).getText()).toBe('Mary'); + expect(names.get(4).getText()).toBe('John'); + }); + +
    + *
    + * + * @example + * ### Changing parameters dynamically + * + * All parameters can be changed dynamically. The next example shows how you can make the columns of + * a table sortable, by binding the `expression` and `reverse` parameters to scope properties. + * + + +
    +
    Sort by = {{propertyName}}; reverse = {{reverse}}
    +
    + +
    + + + + + + + + + + + +
    + + + + + + + + +
    {{friend.name}}{{friend.phone}}{{friend.age}}
    +
    +
    + + angular.module('orderByExample2', []) + .controller('ExampleController', ['$scope', function($scope) { + var friends = [ + {name: 'John', phone: '555-1212', age: 10}, + {name: 'Mary', phone: '555-9876', age: 19}, + {name: 'Mike', phone: '555-4321', age: 21}, + {name: 'Adam', phone: '555-5678', age: 35}, + {name: 'Julie', phone: '555-8765', age: 29} + ]; + + $scope.propertyName = 'age'; + $scope.reverse = true; + $scope.friends = friends; + + $scope.sortBy = function(propertyName) { + $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; + $scope.propertyName = propertyName; + }; + }]); + + + .friends { + border-collapse: collapse; + } + + .friends th { + border-bottom: 1px solid; + } + .friends td, .friends th { + border-left: 1px solid; + padding: 5px 10px; + } + .friends td:first-child, .friends th:first-child { + border-left: none; + } + + .sortorder:after { + content: '\25b2'; // BLACK UP-POINTING TRIANGLE + } + .sortorder.reverse:after { + content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE + } + + + // Element locators + var unsortButton = element(by.partialButtonText('unsorted')); + var nameHeader = element(by.partialButtonText('Name')); + var phoneHeader = element(by.partialButtonText('Phone')); + var ageHeader = element(by.partialButtonText('Age')); + var firstName = element(by.repeater('friends').column('friend.name').row(0)); + var lastName = element(by.repeater('friends').column('friend.name').row(4)); + + it('should sort friends by some property, when clicking on the column header', function() { + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + + phoneHeader.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Mary'); + + nameHeader.click(); + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('Mike'); + + ageHeader.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Adam'); + }); + + it('should sort friends in reverse order, when clicking on the same column', function() { + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + + ageHeader.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Adam'); + + ageHeader.click(); + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + }); + + it('should restore the original order, when clicking "Set to unsorted"', function() { + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + + unsortButton.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Julie'); + }); + +
    + *
    + * + * @example + * ### Using `orderBy` inside a controller + * + * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and + * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory + * and retrieve the `orderBy` filter with `$filter('orderBy')`.) + * + + +
    +
    Sort by = {{propertyName}}; reverse = {{reverse}}
    +
    + +
    + + + + + + + + + + + +
    + + + + + + + + +
    {{friend.name}}{{friend.phone}}{{friend.age}}
    +
    +
    + + angular.module('orderByExample3', []) + .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) { + var friends = [ + {name: 'John', phone: '555-1212', age: 10}, + {name: 'Mary', phone: '555-9876', age: 19}, + {name: 'Mike', phone: '555-4321', age: 21}, + {name: 'Adam', phone: '555-5678', age: 35}, + {name: 'Julie', phone: '555-8765', age: 29} + ]; + + $scope.propertyName = 'age'; + $scope.reverse = true; + $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); + + $scope.sortBy = function(propertyName) { + $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName) + ? !$scope.reverse : false; + $scope.propertyName = propertyName; + $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); + }; + }]); + + + .friends { + border-collapse: collapse; + } + + .friends th { + border-bottom: 1px solid; + } + .friends td, .friends th { + border-left: 1px solid; + padding: 5px 10px; + } + .friends td:first-child, .friends th:first-child { + border-left: none; + } + + .sortorder:after { + content: '\25b2'; // BLACK UP-POINTING TRIANGLE + } + .sortorder.reverse:after { + content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE + } + + + // Element locators + var unsortButton = element(by.partialButtonText('unsorted')); + var nameHeader = element(by.partialButtonText('Name')); + var phoneHeader = element(by.partialButtonText('Phone')); + var ageHeader = element(by.partialButtonText('Age')); + var firstName = element(by.repeater('friends').column('friend.name').row(0)); + var lastName = element(by.repeater('friends').column('friend.name').row(4)); + + it('should sort friends by some property, when clicking on the column header', function() { + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + + phoneHeader.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Mary'); + + nameHeader.click(); + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('Mike'); + + ageHeader.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Adam'); + }); + + it('should sort friends in reverse order, when clicking on the same column', function() { + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + + ageHeader.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Adam'); + + ageHeader.click(); + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + }); + + it('should restore the original order, when clicking "Set to unsorted"', function() { + expect(firstName.getText()).toBe('Adam'); + expect(lastName.getText()).toBe('John'); + + unsortButton.click(); + expect(firstName.getText()).toBe('John'); + expect(lastName.getText()).toBe('Julie'); + }); + +
    + *
    + * + * @example + * ### Using a custom comparator + * + * If you have very specific requirements about the way items are sorted, you can pass your own + * comparator function. For example, you might need to compare some strings in a locale-sensitive + * way. (When specifying a custom comparator, you also need to pass a value for the `reverse` + * argument - passing `false` retains the default sorting order, i.e. ascending.) + * + + +
    +
    +

    Locale-sensitive Comparator

    + + + + + + + + + +
    NameFavorite Letter
    {{friend.name}}{{friend.favoriteLetter}}
    +
    +
    +

    Default Comparator

    + + + + + + + + + +
    NameFavorite Letter
    {{friend.name}}{{friend.favoriteLetter}}
    +
    +
    +
    + + angular.module('orderByExample4', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.friends = [ + {name: 'John', favoriteLetter: 'Ä'}, + {name: 'Mary', favoriteLetter: 'Ü'}, + {name: 'Mike', favoriteLetter: 'Ö'}, + {name: 'Adam', favoriteLetter: 'H'}, + {name: 'Julie', favoriteLetter: 'Z'} + ]; + + $scope.localeSensitiveComparator = function(v1, v2) { + // If we don't get strings, just compare by index + if (v1.type !== 'string' || v2.type !== 'string') { + return (v1.index < v2.index) ? -1 : 1; + } + + // Compare strings alphabetically, taking locale into account + return v1.value.localeCompare(v2.value); + }; + }]); + + + .friends-container { + display: inline-block; + margin: 0 30px; + } + + .friends { + border-collapse: collapse; + } + + .friends th { + border-bottom: 1px solid; + } + .friends td, .friends th { + border-left: 1px solid; + padding: 5px 10px; + } + .friends td:first-child, .friends th:first-child { + border-left: none; + } + + + // Element locators + var container = element(by.css('.custom-comparator')); + var names = container.all(by.repeater('friends').column('friend.name')); + + it('should sort friends by favorite letter (in correct alphabetical order)', function() { + expect(names.get(0).getText()).toBe('John'); + expect(names.get(1).getText()).toBe('Adam'); + expect(names.get(2).getText()).toBe('Mike'); + expect(names.get(3).getText()).toBe('Mary'); + expect(names.get(4).getText()).toBe('Julie'); + }); + +
    + * + */ +orderByFilter.$inject = ['$parse']; +function orderByFilter($parse) { + return function(array, sortPredicate, reverseOrder, compareFn) { + + if (array == null) return array; + if (!isArrayLike(array)) { + throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); + } + + if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } + if (sortPredicate.length === 0) { sortPredicate = ['+']; } + + var predicates = processPredicates(sortPredicate); + + var descending = reverseOrder ? -1 : 1; + + // Define the `compare()` function. Use a default comparator if none is specified. + var compare = isFunction(compareFn) ? compareFn : defaultCompare; + + // The next three lines are a version of a Swartzian Transform idiom from Perl + // (sometimes called the Decorate-Sort-Undecorate idiom) + // See https://en.wikipedia.org/wiki/Schwartzian_transform + var compareValues = Array.prototype.map.call(array, getComparisonObject); + compareValues.sort(doComparison); + array = compareValues.map(function(item) { return item.value; }); + + return array; + + function getComparisonObject(value, index) { + // NOTE: We are adding an extra `tieBreaker` value based on the element's index. + // This will be used to keep the sort stable when none of the input predicates can + // distinguish between two elements. + return { + value: value, + tieBreaker: {value: index, type: 'number', index: index}, + predicateValues: predicates.map(function(predicate) { + return getPredicateValue(predicate.get(value), index); + }) + }; + } + + function doComparison(v1, v2) { + for (var i = 0, ii = predicates.length; i < ii; i++) { + var result = compare(v1.predicateValues[i], v2.predicateValues[i]); + if (result) { + return result * predicates[i].descending * descending; + } + } + + return compare(v1.tieBreaker, v2.tieBreaker) * descending; + } + }; + + function processPredicates(sortPredicates) { + return sortPredicates.map(function(predicate) { + var descending = 1, get = identity; + + if (isFunction(predicate)) { + get = predicate; + } else if (isString(predicate)) { + if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) { + descending = predicate.charAt(0) === '-' ? -1 : 1; + predicate = predicate.substring(1); + } + if (predicate !== '') { + get = $parse(predicate); + if (get.constant) { + var key = get(); + get = function(value) { return value[key]; }; + } + } + } + return {get: get, descending: descending}; + }); + } + + function isPrimitive(value) { + switch (typeof value) { + case 'number': /* falls through */ + case 'boolean': /* falls through */ + case 'string': + return true; + default: + return false; + } + } + + function objectValue(value) { + // If `valueOf` is a valid function use that + if (isFunction(value.valueOf)) { + value = value.valueOf(); + if (isPrimitive(value)) return value; + } + // If `toString` is a valid function and not the one from `Object.prototype` use that + if (hasCustomToString(value)) { + value = value.toString(); + if (isPrimitive(value)) return value; + } + + return value; + } + + function getPredicateValue(value, index) { + var type = typeof value; + if (value === null) { + type = 'string'; + value = 'null'; + } else if (type === 'object') { + value = objectValue(value); + } + return {value: value, type: type, index: index}; + } + + function defaultCompare(v1, v2) { + var result = 0; + var type1 = v1.type; + var type2 = v2.type; + + if (type1 === type2) { + var value1 = v1.value; + var value2 = v2.value; + + if (type1 === 'string') { + // Compare strings case-insensitively + value1 = value1.toLowerCase(); + value2 = value2.toLowerCase(); + } else if (type1 === 'object') { + // For basic objects, use the position of the object + // in the collection instead of the value + if (isObject(value1)) value1 = v1.index; + if (isObject(value2)) value2 = v2.index; + } + + if (value1 !== value2) { + result = value1 < value2 ? -1 : 1; + } + } else { + result = type1 < type2 ? -1 : 1; + } + + return result; + } +} + +function ngDirective(directive) { + if (isFunction(directive)) { + directive = { + link: directive + }; + } + directive.restrict = directive.restrict || 'AC'; + return valueFn(directive); +} + +/** + * @ngdoc directive + * @name a + * @restrict E + * + * @description + * Modifies the default behavior of the html a tag so that the default action is prevented when + * the href attribute is empty. + * + * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive. + */ +var htmlAnchorDirective = valueFn({ + restrict: 'E', + compile: function(element, attr) { + if (!attr.href && !attr.xlinkHref) { + return function(scope, element) { + // If the linked element is not an anchor tag anymore, do nothing + if (element[0].nodeName.toLowerCase() !== 'a') return; + + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? + 'xlink:href' : 'href'; + element.on('click', function(event) { + // if we have no href url, then don't navigate anywhere. + if (!element.attr(href)) { + event.preventDefault(); + } + }); + }; + } + } +}); + +/** + * @ngdoc directive + * @name ngHref + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in an href attribute will + * make the link go to the wrong URL if the user clicks it before + * Angular has a chance to replace the `{{hash}}` markup with its + * value. Until Angular replaces the markup the link will be broken + * and will most likely return a 404 error. The `ngHref` directive + * solves this problem. + * + * The wrong way to write it: + * ```html + * link1 + * ``` + * + * The correct way to write it: + * ```html + * link1 + * ``` + * + * @element A + * @param {template} ngHref any string which can contain `{{}}` markup. + * + * @example + * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes + * in links and their different behaviors: + + +
    + link 1 (link, don't reload)
    + link 2 (link, don't reload)
    + link 3 (link, reload!)
    + anchor (link, don't reload)
    + anchor (no link)
    + link (link, change location) +
    + + it('should execute ng-click but not reload when href without value', function() { + element(by.id('link-1')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('1'); + expect(element(by.id('link-1')).getAttribute('href')).toBe(''); + }); + + it('should execute ng-click but not reload when href empty string', function() { + element(by.id('link-2')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('2'); + expect(element(by.id('link-2')).getAttribute('href')).toBe(''); + }); + + it('should execute ng-click and change url when ng-href specified', function() { + expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); + + element(by.id('link-3')).click(); + + // At this point, we navigate away from an Angular page, so we need + // to use browser.driver to get the base webdriver. + + browser.wait(function() { + return browser.driver.getCurrentUrl().then(function(url) { + return url.match(/\/123$/); + }); + }, 5000, 'page should navigate to /123'); + }); + + it('should execute ng-click but not reload when href empty string and name specified', function() { + element(by.id('link-4')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('4'); + expect(element(by.id('link-4')).getAttribute('href')).toBe(''); + }); + + it('should execute ng-click but not reload when no href but name specified', function() { + element(by.id('link-5')).click(); + expect(element(by.model('value')).getAttribute('value')).toEqual('5'); + expect(element(by.id('link-5')).getAttribute('href')).toBe(null); + }); + + it('should only change url when only ng-href', function() { + element(by.model('value')).clear(); + element(by.model('value')).sendKeys('6'); + expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); + + element(by.id('link-6')).click(); + + // At this point, we navigate away from an Angular page, so we need + // to use browser.driver to get the base webdriver. + browser.wait(function() { + return browser.driver.getCurrentUrl().then(function(url) { + return url.match(/\/6$/); + }); + }, 5000, 'page should navigate to /6'); + }); + +
    + */ + +/** + * @ngdoc directive + * @name ngSrc + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in a `src` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrc` directive solves this problem. + * + * The buggy way to write it: + * ```html + * Description + * ``` + * + * The correct way to write it: + * ```html + * Description + * ``` + * + * @element IMG + * @param {template} ngSrc any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ngSrcset + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrcset` directive solves this problem. + * + * The buggy way to write it: + * ```html + * Description + * ``` + * + * The correct way to write it: + * ```html + * Description + * ``` + * + * @element IMG + * @param {template} ngSrcset any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ngDisabled + * @restrict A + * @priority 100 + * + * @description + * + * This directive sets the `disabled` attribute on the element if the + * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. + * + * A special directive is necessary because we cannot use interpolation inside the `disabled` + * attribute. See the {@link guide/interpolation interpolation guide} for more info. + * + * @example + + +
    + +
    + + it('should toggle button', function() { + expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); + element(by.model('checked')).click(); + expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); + }); + +
    + * + * @element INPUT + * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, + * then the `disabled` attribute will be set on the element + */ + + +/** + * @ngdoc directive + * @name ngChecked + * @restrict A + * @priority 100 + * + * @description + * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. + * + * Note that this directive should not be used together with {@link ngModel `ngModel`}, + * as this can lead to unexpected behavior. + * + * A special directive is necessary because we cannot use interpolation inside the `checked` + * attribute. See the {@link guide/interpolation interpolation guide} for more info. + * + * @example + + +
    + +
    + + it('should check both checkBoxes', function() { + expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); + element(by.model('master')).click(); + expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); + }); + +
    + * + * @element INPUT + * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, + * then the `checked` attribute will be set on the element + */ + + +/** + * @ngdoc directive + * @name ngReadonly + * @restrict A + * @priority 100 + * + * @description + * + * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy. + * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on + * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information. + * + * A special directive is necessary because we cannot use interpolation inside the `readonly` + * attribute. See the {@link guide/interpolation interpolation guide} for more info. + * + * @example + + +
    + +
    + + it('should toggle readonly attr', function() { + expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); + element(by.model('checked')).click(); + expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); + }); + +
    + * + * @element INPUT + * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, + * then special attribute "readonly" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ngSelected + * @restrict A + * @priority 100 + * + * @description + * + * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. + * + * A special directive is necessary because we cannot use interpolation inside the `selected` + * attribute. See the {@link guide/interpolation interpolation guide} for more info. + * + *
    + * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only + * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you + * should not use `ngSelected` on the options, as `ngModel` will set the select value and + * selected options. + *
    + * + * @example + + +
    + +
    + + it('should select Greetings!', function() { + expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); + element(by.model('selected')).click(); + expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); + }); + +
    + * + * @element OPTION + * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, + * then special attribute "selected" will be set on the element + */ + +/** + * @ngdoc directive + * @name ngOpen + * @restrict A + * @priority 100 + * + * @description + * + * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. + * + * A special directive is necessary because we cannot use interpolation inside the `open` + * attribute. See the {@link guide/interpolation interpolation guide} for more info. + * + * ## A note about browser compatibility + * + * Edge, Firefox, and Internet Explorer do not support the `details` element, it is + * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. + * + * @example + + +
    +
    + Show/Hide me +
    +
    + + it('should toggle open', function() { + expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); + element(by.model('open')).click(); + expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); + }); + +
    + * + * @element DETAILS + * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, + * then special attribute "open" will be set on the element + */ + +var ngAttributeAliasDirectives = {}; + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + // binding to multiple is not supported + if (propName === 'multiple') return; + + function defaultLinkFn(scope, element, attr) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { + attr.$set(attrName, !!value); + }); + } + + var normalized = directiveNormalize('ng-' + attrName); + var linkFn = defaultLinkFn; + + if (propName === 'checked') { + linkFn = function(scope, element, attr) { + // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input + if (attr.ngModel !== attr[normalized]) { + defaultLinkFn(scope, element, attr); + } + }; + } + + ngAttributeAliasDirectives[normalized] = function() { + return { + restrict: 'A', + priority: 100, + link: linkFn + }; + }; +}); + +// aliased input attrs are evaluated +forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { + ngAttributeAliasDirectives[ngAttr] = function() { + return { + priority: 100, + link: function(scope, element, attr) { + //special case ngPattern when a literal regular expression value + //is used as the expression (this way we don't have to watch anything). + if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') { + var match = attr.ngPattern.match(REGEX_STRING_REGEXP); + if (match) { + attr.$set('ngPattern', new RegExp(match[1], match[2])); + return; + } + } + + scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { + attr.$set(ngAttr, value); + }); + } + }; + }; +}); + +// ng-src, ng-srcset, ng-href are interpolated +forEach(['src', 'srcset', 'href'], function(attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 99, // it needs to run after the attributes are interpolated + link: function(scope, element, attr) { + var propName = attrName, + name = attrName; + + if (attrName === 'href' && + toString.call(element.prop('href')) === '[object SVGAnimatedString]') { + name = 'xlinkHref'; + attr.$attr[name] = 'xlink:href'; + propName = null; + } + + attr.$observe(normalized, function(value) { + if (!value) { + if (attrName === 'href') { + attr.$set(name, null); + } + return; + } + + attr.$set(name, value); + + // Support: IE 9-11 only + // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist + // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need + // to set the property as well to achieve the desired effect. + // We use attr[attrName] value since $set can sanitize the url. + if (msie && propName) element.prop(propName, attr[name]); + }); + } + }; + }; +}); + +/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS + */ +var nullFormCtrl = { + $addControl: noop, + $$renameControl: nullFormRenameControl, + $removeControl: noop, + $setValidity: noop, + $setDirty: noop, + $setPristine: noop, + $setSubmitted: noop +}, +PENDING_CLASS = 'ng-pending', +SUBMITTED_CLASS = 'ng-submitted'; + +function nullFormRenameControl(control, name) { + control.$name = name; +} + +/** + * @ngdoc type + * @name form.FormController + * + * @property {boolean} $pristine True if user has not interacted with the form yet. + * @property {boolean} $dirty True if user has already interacted with the form. + * @property {boolean} $valid True if all of the containing forms and controls are valid. + * @property {boolean} $invalid True if at least one containing control or form is invalid. + * @property {boolean} $pending True if at least one containing control or form is pending. + * @property {boolean} $submitted True if user has submitted the form even if its invalid. + * + * @property {Object} $error Is an object hash, containing references to controls or + * forms with failing validators, where: + * + * - keys are validation tokens (error names), + * - values are arrays of controls or forms that have a failing validator for given error name. + * + * Built-in validation tokens: + * + * - `email` + * - `max` + * - `maxlength` + * - `min` + * - `minlength` + * - `number` + * - `pattern` + * - `required` + * - `url` + * - `date` + * - `datetimelocal` + * - `time` + * - `week` + * - `month` + * + * @description + * `FormController` keeps track of all its controls and nested forms as well as the state of them, + * such as being valid/invalid or dirty/pristine. + * + * Each {@link ng.directive:form form} directive creates an instance + * of `FormController`. + * + */ +//asks for $scope to fool the BC controller module +FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; +function FormController($element, $attrs, $scope, $animate, $interpolate) { + this.$$controls = []; + + // init state + this.$error = {}; + this.$$success = {}; + this.$pending = undefined; + this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope); + this.$dirty = false; + this.$pristine = true; + this.$valid = true; + this.$invalid = false; + this.$submitted = false; + this.$$parentForm = nullFormCtrl; + + this.$$element = $element; + this.$$animate = $animate; + + setupValidity(this); +} + +FormController.prototype = { + /** + * @ngdoc method + * @name form.FormController#$rollbackViewValue + * + * @description + * Rollback all form controls pending updates to the `$modelValue`. + * + * Updates may be pending by a debounced event or because the input is waiting for a some future + * event defined in `ng-model-options`. This method is typically needed by the reset button of + * a form that uses `ng-model-options` to pend updates. + */ + $rollbackViewValue: function() { + forEach(this.$$controls, function(control) { + control.$rollbackViewValue(); + }); + }, + + /** + * @ngdoc method + * @name form.FormController#$commitViewValue + * + * @description + * Commit all form controls pending updates to the `$modelValue`. + * + * Updates may be pending by a debounced event or because the input is waiting for a some future + * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` + * usually handles calling this in response to input events. + */ + $commitViewValue: function() { + forEach(this.$$controls, function(control) { + control.$commitViewValue(); + }); + }, + + /** + * @ngdoc method + * @name form.FormController#$addControl + * @param {object} control control object, either a {@link form.FormController} or an + * {@link ngModel.NgModelController} + * + * @description + * Register a control with the form. Input elements using ngModelController do this automatically + * when they are linked. + * + * Note that the current state of the control will not be reflected on the new parent form. This + * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` + * state. + * + * However, if the method is used programmatically, for example by adding dynamically created controls, + * or controls that have been previously removed without destroying their corresponding DOM element, + * it's the developers responsibility to make sure the current state propagates to the parent form. + * + * For example, if an input control is added that is already `$dirty` and has `$error` properties, + * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. + */ + $addControl: function(control) { + // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored + // and not added to the scope. Now we throw an error. + assertNotHasOwnProperty(control.$name, 'input'); + this.$$controls.push(control); + + if (control.$name) { + this[control.$name] = control; + } + + control.$$parentForm = this; + }, + + // Private API: rename a form control + $$renameControl: function(control, newName) { + var oldName = control.$name; + + if (this[oldName] === control) { + delete this[oldName]; + } + this[newName] = control; + control.$name = newName; + }, + + /** + * @ngdoc method + * @name form.FormController#$removeControl + * @param {object} control control object, either a {@link form.FormController} or an + * {@link ngModel.NgModelController} + * + * @description + * Deregister a control from the form. + * + * Input elements using ngModelController do this automatically when they are destroyed. + * + * Note that only the removed control's validation state (`$errors`etc.) will be removed from the + * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be + * different from case to case. For example, removing the only `$dirty` control from a form may or + * may not mean that the form is still `$dirty`. + */ + $removeControl: function(control) { + if (control.$name && this[control.$name] === control) { + delete this[control.$name]; + } + forEach(this.$pending, function(value, name) { + // eslint-disable-next-line no-invalid-this + this.$setValidity(name, null, control); + }, this); + forEach(this.$error, function(value, name) { + // eslint-disable-next-line no-invalid-this + this.$setValidity(name, null, control); + }, this); + forEach(this.$$success, function(value, name) { + // eslint-disable-next-line no-invalid-this + this.$setValidity(name, null, control); + }, this); + + arrayRemove(this.$$controls, control); + control.$$parentForm = nullFormCtrl; + }, + + /** + * @ngdoc method + * @name form.FormController#$setDirty + * + * @description + * Sets the form to a dirty state. + * + * This method can be called to add the 'ng-dirty' class and set the form to a dirty + * state (ng-dirty class). This method will also propagate to parent forms. + */ + $setDirty: function() { + this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); + this.$$animate.addClass(this.$$element, DIRTY_CLASS); + this.$dirty = true; + this.$pristine = false; + this.$$parentForm.$setDirty(); + }, + + /** + * @ngdoc method + * @name form.FormController#$setPristine + * + * @description + * Sets the form to its pristine state. + * + * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes + * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted` + * state to false. + * + * This method will also propagate to all the controls contained in this form. + * + * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after + * saving or resetting it. + */ + $setPristine: function() { + this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); + this.$dirty = false; + this.$pristine = true; + this.$submitted = false; + forEach(this.$$controls, function(control) { + control.$setPristine(); + }); + }, + + /** + * @ngdoc method + * @name form.FormController#$setUntouched + * + * @description + * Sets the form to its untouched state. + * + * This method can be called to remove the 'ng-touched' class and set the form controls to their + * untouched state (ng-untouched class). + * + * Setting a form controls back to their untouched state is often useful when setting the form + * back to its pristine state. + */ + $setUntouched: function() { + forEach(this.$$controls, function(control) { + control.$setUntouched(); + }); + }, + + /** + * @ngdoc method + * @name form.FormController#$setSubmitted + * + * @description + * Sets the form to its submitted state. + */ + $setSubmitted: function() { + this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); + this.$submitted = true; + this.$$parentForm.$setSubmitted(); + } +}; + +/** + * @ngdoc method + * @name form.FormController#$setValidity + * + * @description + * Sets the validity of a form control. + * + * This method will also propagate to parent forms. + */ +addSetValidityMethod({ + clazz: FormController, + set: function(object, property, controller) { + var list = object[property]; + if (!list) { + object[property] = [controller]; + } else { + var index = list.indexOf(controller); + if (index === -1) { + list.push(controller); + } + } + }, + unset: function(object, property, controller) { + var list = object[property]; + if (!list) { + return; + } + arrayRemove(list, controller); + if (list.length === 0) { + delete object[property]; + } + } +}); + +/** + * @ngdoc directive + * @name ngForm + * @restrict EAC + * + * @description + * Nestable alias of {@link ng.directive:form `form`} directive. HTML + * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a + * sub-group of controls needs to be determined. + * + * Note: the purpose of `ngForm` is to group controls, + * but not to be a replacement for the `
    ` tag with all of its capabilities + * (e.g. posting to the server, ...). + * + * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + */ + + /** + * @ngdoc directive + * @name form + * @restrict E + * + * @description + * Directive that instantiates + * {@link form.FormController FormController}. + * + * If the `name` attribute is specified, the form controller is published onto the current scope under + * this name. + * + * # Alias: {@link ng.directive:ngForm `ngForm`} + * + * In Angular, forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However, browsers do not allow nesting of `` elements, so + * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to + * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group + * of controls needs to be determined. + * + * # CSS classes + * - `ng-valid` is set if the form is valid. + * - `ng-invalid` is set if the form is invalid. + * - `ng-pending` is set if the form is pending. + * - `ng-pristine` is set if the form is pristine. + * - `ng-dirty` is set if the form is dirty. + * - `ng-submitted` is set if the form was submitted. + * + * Keep in mind that ngAnimate can detect each of these classes when added and removed. + * + * + * # Submitting a form and preventing the default action + * + * Since the role of forms in client-side Angular applications is different than in classical + * roundtrip apps, it is desirable for the browser not to translate the form submission into a full + * page reload that sends the data to the server. Instead some javascript logic should be triggered + * to handle the form submission in an application-specific way. + * + * For this reason, Angular prevents the default action (form submission to the server) unless the + * `` element has an `action` attribute specified. + * + * You can use one of the following two ways to specify what javascript method should be called when + * a form is submitted: + * + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first + * button or input field of type submit (input[type=submit]) + * + * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} + * or {@link ng.directive:ngClick ngClick} directives. + * This is because of the following form submission rules in the HTML specification: + * + * - If a form has only one input field then hitting enter in this field triggers form submit + * (`ngSubmit`) + * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter + * doesn't trigger submit + * - if a form has one or more input fields and one or more buttons or input[type=submit] then + * hitting enter in any of the input fields will trigger the click handler on the *first* button or + * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) + * + * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is + * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` + * to have access to the updated model. + * + * ## Animation Hooks + * + * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. + * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any + * other validations that are performed within the form. Animations in ngForm are similar to how + * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well + * as JS animations. + * + * The following example shows a simple way to utilize CSS transitions to style a form element + * that has been rendered as invalid after it has been validated: + * + *
    + * //be sure to include ngAnimate as a module to hook into more
    + * //advanced animations
    + * .my-form {
    + *   transition:0.5s linear all;
    + *   background: white;
    + * }
    + * .my-form.ng-invalid {
    + *   background: red;
    + *   color:white;
    + * }
    + * 
    + * + * @example + + + + + + userType: + Required!
    + userType = {{userType}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    + +
    + + it('should initialize to model', function() { + var userType = element(by.binding('userType')); + var valid = element(by.binding('myForm.input.$valid')); + + expect(userType.getText()).toContain('guest'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + var userType = element(by.binding('userType')); + var valid = element(by.binding('myForm.input.$valid')); + var userInput = element(by.model('userType')); + + userInput.clear(); + userInput.sendKeys(''); + + expect(userType.getText()).toEqual('userType ='); + expect(valid.getText()).toContain('false'); + }); + +
    + * + * @param {string=} name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + */ +var formDirectiveFactory = function(isNgForm) { + return ['$timeout', '$parse', function($timeout, $parse) { + var formDirective = { + name: 'form', + restrict: isNgForm ? 'EAC' : 'E', + require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form + controller: FormController, + compile: function ngFormCompile(formElement, attr) { + // Setup initial state of the control + formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); + + var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); + + return { + pre: function ngFormPreLink(scope, formElement, attr, ctrls) { + var controller = ctrls[0]; + + // if `action` attr is not present on the form, prevent the default action (submission) + if (!('action' in attr)) { + // we can't use jq events because if a form is destroyed during submission the default + // action is not prevented. see #1238 + // + // IE 9 is not affected because it doesn't fire a submit event and try to do a full + // page reload if the form was destroyed by submission of the form via a click handler + // on a button in the form. Looks like an IE9 specific bug. + var handleFormSubmission = function(event) { + scope.$apply(function() { + controller.$commitViewValue(); + controller.$setSubmitted(); + }); + + event.preventDefault(); + }; + + formElement[0].addEventListener('submit', handleFormSubmission); + + // unregister the preventDefault listener so that we don't not leak memory but in a + // way that will achieve the prevention of the default action. + formElement.on('$destroy', function() { + $timeout(function() { + formElement[0].removeEventListener('submit', handleFormSubmission); + }, 0, false); + }); + } + + var parentFormCtrl = ctrls[1] || controller.$$parentForm; + parentFormCtrl.$addControl(controller); + + var setter = nameAttr ? getSetter(controller.$name) : noop; + + if (nameAttr) { + setter(scope, controller); + attr.$observe(nameAttr, function(newValue) { + if (controller.$name === newValue) return; + setter(scope, undefined); + controller.$$parentForm.$$renameControl(controller, newValue); + setter = getSetter(controller.$name); + setter(scope, controller); + }); + } + formElement.on('$destroy', function() { + controller.$$parentForm.$removeControl(controller); + setter(scope, undefined); + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); + } + }; + } + }; + + return formDirective; + + function getSetter(expression) { + if (expression === '') { + //create an assignable expression, so forms with an empty name can be renamed later + return $parse('this[""]').assign; + } + return $parse(expression).assign || noop; + } + }]; +}; + +var formDirective = formDirectiveFactory(); +var ngFormDirective = formDirectiveFactory(true); + + + +// helper methods +function setupValidity(instance) { + instance.$$classCache = {}; + instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS)); +} +function addSetValidityMethod(context) { + var clazz = context.clazz, + set = context.set, + unset = context.unset; + + clazz.prototype.$setValidity = function(validationErrorKey, state, controller) { + if (isUndefined(state)) { + createAndSet(this, '$pending', validationErrorKey, controller); + } else { + unsetAndCleanup(this, '$pending', validationErrorKey, controller); + } + if (!isBoolean(state)) { + unset(this.$error, validationErrorKey, controller); + unset(this.$$success, validationErrorKey, controller); + } else { + if (state) { + unset(this.$error, validationErrorKey, controller); + set(this.$$success, validationErrorKey, controller); + } else { + set(this.$error, validationErrorKey, controller); + unset(this.$$success, validationErrorKey, controller); + } + } + if (this.$pending) { + cachedToggleClass(this, PENDING_CLASS, true); + this.$valid = this.$invalid = undefined; + toggleValidationCss(this, '', null); + } else { + cachedToggleClass(this, PENDING_CLASS, false); + this.$valid = isObjectEmpty(this.$error); + this.$invalid = !this.$valid; + toggleValidationCss(this, '', this.$valid); + } + + // re-read the state as the set/unset methods could have + // combined state in this.$error[validationError] (used for forms), + // where setting/unsetting only increments/decrements the value, + // and does not replace it. + var combinedState; + if (this.$pending && this.$pending[validationErrorKey]) { + combinedState = undefined; + } else if (this.$error[validationErrorKey]) { + combinedState = false; + } else if (this.$$success[validationErrorKey]) { + combinedState = true; + } else { + combinedState = null; + } + + toggleValidationCss(this, validationErrorKey, combinedState); + this.$$parentForm.$setValidity(validationErrorKey, combinedState, this); + }; + + function createAndSet(ctrl, name, value, controller) { + if (!ctrl[name]) { + ctrl[name] = {}; + } + set(ctrl[name], value, controller); + } + + function unsetAndCleanup(ctrl, name, value, controller) { + if (ctrl[name]) { + unset(ctrl[name], value, controller); + } + if (isObjectEmpty(ctrl[name])) { + ctrl[name] = undefined; + } + } + + function cachedToggleClass(ctrl, className, switchValue) { + if (switchValue && !ctrl.$$classCache[className]) { + ctrl.$$animate.addClass(ctrl.$$element, className); + ctrl.$$classCache[className] = true; + } else if (!switchValue && ctrl.$$classCache[className]) { + ctrl.$$animate.removeClass(ctrl.$$element, className); + ctrl.$$classCache[className] = false; + } + } + + function toggleValidationCss(ctrl, validationErrorKey, isValid) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + + cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true); + cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false); + } +} + +function isObjectEmpty(obj) { + if (obj) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + return false; + } + } + } + return true; +} + +/* global + VALID_CLASS: false, + INVALID_CLASS: false, + PRISTINE_CLASS: false, + DIRTY_CLASS: false, + ngModelMinErr: false +*/ + +// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 +var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/; +// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) +// Note: We are being more lenient, because browsers are too. +// 1. Scheme +// 2. Slashes +// 3. Username +// 4. Password +// 5. Hostname +// 6. Port +// 7. Path +// 8. Query +// 9. Fragment +// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999 +var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; +// eslint-disable-next-line max-len +var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/; +var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; +var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; +var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; +var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; +var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; +var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; + +var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; +var PARTIAL_VALIDATION_TYPES = createMap(); +forEach('date,datetime-local,month,time,week'.split(','), function(type) { + PARTIAL_VALIDATION_TYPES[type] = true; +}); + +var inputType = { + + /** + * @ngdoc input + * @name input[text] + * + * @description + * Standard HTML text input with angular data binding, inherited by most of the `input` elements. + * + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object, then this is used directly. + * If the expression evaluates to a string, then it will be converted to a RegExp + * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to + * `new RegExp('^abc$')`.
    + * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to + * start at the index of the last search's match, thus not taking the whole input value into + * account. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * This parameter is ignored for input[type=password] controls, which will never trim the + * input. + * + * @example + + + +
    + +
    + + Required! + + Single word only! +
    + text = {{example.text}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var text = element(by.binding('example.text')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.text')); + + it('should initialize to model', function() { + expect(text.getText()).toContain('guest'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + + expect(text.getText()).toEqual('text ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if multi word', function() { + input.clear(); + input.sendKeys('hello world'); + + expect(valid.getText()).toContain('false'); + }); + +
    + */ + 'text': textInputType, + + /** + * @ngdoc input + * @name input[date] + * + * @description + * Input with date validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 + * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many + * modern browsers do not yet support this input type, it is important to provide cues to users on the + * expected input format via a placeholder or label. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a + * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute + * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 + * constraint validation. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute + * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 + * constraint validation. + * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string + * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. + * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string + * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + + +
    + + Required! + + Not a valid date! +
    + value = {{example.value | date: "yyyy-MM-dd"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); + var valid = element(by.binding('myForm.input.$valid')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (see https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-10-22'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01-01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'date': createDateInputType('date', DATE_REGEXP, + createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), + 'yyyy-MM-dd'), + + /** + * @ngdoc input + * @name input[datetime-local] + * + * @description + * Input with datetime validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation + * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). + * Note that `min` will also add native HTML5 constraint validation. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation + * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). + * Note that `max` will also add native HTML5 constraint validation. + * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string + * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. + * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string + * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + + +
    + + Required! + + Not a valid date! +
    + value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); + var valid = element(by.binding('myForm.input.$valid')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2010-12-28T14:57:00'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01-01T23:59:00'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, + createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), + 'yyyy-MM-ddTHH:mm:ss.sss'), + + /** + * @ngdoc input + * @name input[time] + * + * @description + * Input with time validation and transformation. In browsers that do not yet support + * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a + * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this + * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add + * native HTML5 constraint validation. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this + * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add + * native HTML5 constraint validation. + * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the + * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. + * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the + * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + + +
    + + Required! + + Not a valid date! +
    + value = {{example.value | date: "HH:mm:ss"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "HH:mm:ss"')); + var valid = element(by.binding('myForm.input.$valid')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('14:57:00'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('23:59:00'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'time': createDateInputType('time', TIME_REGEXP, + createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), + 'HH:mm:ss.sss'), + + /** + * @ngdoc input + * @name input[week] + * + * @description + * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support + * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * week format (yyyy-W##), for example: `2013-W02`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this + * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add + * native HTML5 constraint validation. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this + * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add + * native HTML5 constraint validation. + * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string + * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. + * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string + * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + +
    + + Required! + + Not a valid date! +
    + value = {{example.value | date: "yyyy-Www"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-Www"')); + var valid = element(by.binding('myForm.input.$valid')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-W01'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-W01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), + + /** + * @ngdoc input + * @name input[month] + * + * @description + * Input with month validation and transformation. In browsers that do not yet support + * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * month format (yyyy-MM), for example: `2009-01`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * If the model is not set to the first of the month, the next view to model update will set it + * to the first of the month. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this + * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add + * native HTML5 constraint validation. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this + * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add + * native HTML5 constraint validation. + * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string + * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. + * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string + * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. + + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + + +
    + + Required! + + Not a valid month! +
    + value = {{example.value | date: "yyyy-MM"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-MM"')); + var valid = element(by.binding('myForm.input.$valid')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-10'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'month': createDateInputType('month', MONTH_REGEXP, + createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), + 'yyyy-MM'), + + /** + * @ngdoc input + * @name input[number] + * + * @description + * Text input with number validation and transformation. Sets the `number` validation + * error if not a valid number. + * + *
    + * The model must always be of type `number` otherwise Angular will throw an error. + * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} + * error docs for more information and an example of how to convert your model if necessary. + *
    + * + * ## Issues with HTML5 constraint validation + * + * In browsers that follow the + * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), + * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. + * If a non-number is entered in the input, the browser will report the value as an empty string, + * which means the view / model values in `ngModel` and subsequently the scope value + * will also be an empty string. + * + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * Can be interpolated. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * Can be interpolated. + * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`, + * but does not trigger HTML5 native validation. Takes an expression. + * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`, + * but does not trigger HTML5 native validation. Takes an expression. + * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint. + * Can be interpolated. + * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint, + * but does not trigger HTML5 native validation. Takes an expression. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object, then this is used directly. + * If the expression evaluates to a string, then it will be converted to a RegExp + * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to + * `new RegExp('^abc$')`.
    + * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to + * start at the index of the last search's match, thus not taking the whole input value into + * account. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + +
    + + Required! + + Not valid number! +
    + value = {{example.value}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.value')); + + it('should initialize to model', function() { + expect(value.getText()).toContain('12'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if over max', function() { + input.clear(); + input.sendKeys('123'); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('false'); + }); + +
    + */ + 'number': numberInputType, + + + /** + * @ngdoc input + * @name input[url] + * + * @description + * Text input with URL validation. Sets the `url` validation error key if the content is not a + * valid URL. + * + *
    + * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex + * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify + * the built-in validators (see the {@link guide/forms Forms guide}) + *
    + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object, then this is used directly. + * If the expression evaluates to a string, then it will be converted to a RegExp + * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to + * `new RegExp('^abc$')`.
    + * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to + * start at the index of the last search's match, thus not taking the whole input value into + * account. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    +
    + + var text = element(by.binding('url.text')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('url.text')); + + it('should initialize to model', function() { + expect(text.getText()).toContain('http://google.com'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + + expect(text.getText()).toEqual('text ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if not url', function() { + input.clear(); + input.sendKeys('box'); + + expect(valid.getText()).toContain('false'); + }); + +
    + */ + 'url': urlInputType, + + + /** + * @ngdoc input + * @name input[email] + * + * @description + * Text input with email validation. Sets the `email` validation error key if not a valid email + * address. + * + *
    + * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex + * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can + * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) + *
    + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object, then this is used directly. + * If the expression evaluates to a string, then it will be converted to a RegExp + * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to + * `new RegExp('^abc$')`.
    + * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to + * start at the index of the last search's match, thus not taking the whole input value into + * account. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + +
    + + Required! + + Not valid email! +
    + text = {{email.text}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    + myForm.$error.email = {{!!myForm.$error.email}}
    +
    +
    + + var text = element(by.binding('email.text')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('email.text')); + + it('should initialize to model', function() { + expect(text.getText()).toContain('me@example.com'); + expect(valid.getText()).toContain('true'); + }); + + it('should be invalid if empty', function() { + input.clear(); + input.sendKeys(''); + expect(text.getText()).toEqual('text ='); + expect(valid.getText()).toContain('false'); + }); + + it('should be invalid if not email', function() { + input.clear(); + input.sendKeys('xxx'); + + expect(valid.getText()).toContain('false'); + }); + +
    + */ + 'email': emailInputType, + + + /** + * @ngdoc input + * @name input[radio] + * + * @description + * HTML radio button. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} value The value to which the `ngModel` expression should be set when selected. + * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, + * too. Use `ngValue` if you need complex models (`number`, `object`, ...). + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio + * is selected. Should be used instead of the `value` attribute if you need + * a non-string `ngModel` (`boolean`, `array`, ...). + * + * @example + + + +
    +
    +
    +
    + color = {{color.name | json}}
    +
    + Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. +
    + + it('should change state', function() { + var inputs = element.all(by.model('color.name')); + var color = element(by.binding('color.name')); + + expect(color.getText()).toContain('blue'); + + inputs.get(0).click(); + expect(color.getText()).toContain('red'); + + inputs.get(1).click(); + expect(color.getText()).toContain('green'); + }); + +
    + */ + 'radio': radioInputType, + + /** + * @ngdoc input + * @name input[range] + * + * @description + * Native range input with validation and transformation. + * + * The model for the range input must always be a `Number`. + * + * IE9 and other browsers that do not support the `range` type fall back + * to a text input without any default values for `min`, `max` and `step`. Model binding, + * validation and number parsing are nevertheless supported. + * + * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` + * in a way that never allows the input to hold an invalid value. That means: + * - any non-numerical value is set to `(max + min) / 2`. + * - any numerical value that is less than the current min val, or greater than the current max val + * is set to the min / max val respectively. + * - additionally, the current `step` is respected, so the nearest value that satisfies a step + * is used. + * + * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) + * for more info. + * + * This has the following consequences for Angular: + * + * Since the element value should always reflect the current model value, a range input + * will set the bound ngModel expression to the value that the browser has set for the + * input element. For example, in the following input ``, + * if the application sets `model.value = null`, the browser will set the input to `'50'`. + * Angular will then set the model to `50`, to prevent input and model value being out of sync. + * + * That means the model for range will immediately be set to `50` after `ngModel` has been + * initialized. It also means a range input can never have the required error. + * + * This does not only affect changes to the model value, but also to the values of the `min`, + * `max`, and `step` attributes. When these change in a way that will cause the browser to modify + * the input value, Angular will also update the model value. + * + * Automatic value adjustment also means that a range input element can never have the `required`, + * `min`, or `max` errors. + * + * However, `step` is currently only fully implemented by Firefox. Other browsers have problems + * when the step value changes dynamically - they do not adjust the element value correctly, but + * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step` + * error on the input, and set the model to `undefined`. + * + * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do + * not set the `min` and `max` attributes, which means that the browser won't automatically adjust + * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation to ensure that the value entered is greater + * than `min`. Can be interpolated. + * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`. + * Can be interpolated. + * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` + * Can be interpolated. + * @param {string=} ngChange Angular expression to be executed when the ngModel value changes due + * to user interaction with the input element. + * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the + * element. **Note** : `ngChecked` should not be used alongside `ngModel`. + * Checkout {@link ng.directive:ngChecked ngChecked} for usage. + * + * @example + + + +
    + + Model as range: +
    + Model as number:
    + Min:
    + Max:
    + value = {{value}}
    + myForm.range.$valid = {{myForm.range.$valid}}
    + myForm.range.$error = {{myForm.range.$error}} +
    +
    +
    + + * ## Range Input with ngMin & ngMax attributes + + * @example + + + +
    + Model as range: +
    + Model as number:
    + Min:
    + Max:
    + value = {{value}}
    + myForm.range.$valid = {{myForm.range.$valid}}
    + myForm.range.$error = {{myForm.range.$error}} +
    +
    +
    + + */ + 'range': rangeInputType, + + /** + * @ngdoc input + * @name input[checkbox] + * + * @description + * HTML checkbox. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {expression=} ngTrueValue The value to which the expression should be set when selected. + * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    +
    +
    + value1 = {{checkboxModel.value1}}
    + value2 = {{checkboxModel.value2}}
    +
    +
    + + it('should change state', function() { + var value1 = element(by.binding('checkboxModel.value1')); + var value2 = element(by.binding('checkboxModel.value2')); + + expect(value1.getText()).toContain('true'); + expect(value2.getText()).toContain('YES'); + + element(by.model('checkboxModel.value1')).click(); + element(by.model('checkboxModel.value2')).click(); + + expect(value1.getText()).toContain('false'); + expect(value2.getText()).toContain('NO'); + }); + +
    + */ + 'checkbox': checkboxInputType, + + 'hidden': noop, + 'button': noop, + 'submit': noop, + 'reset': noop, + 'file': noop +}; + +function stringBasedInputType(ctrl) { + ctrl.$formatters.push(function(value) { + return ctrl.$isEmpty(value) ? value : value.toString(); + }); +} + +function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + stringBasedInputType(ctrl); +} + +function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { + var type = lowercase(element[0].type); + + // In composition mode, users are still inputting intermediate text buffer, + // hold the listener until composition is done. + // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent + if (!$sniffer.android) { + var composing = false; + + element.on('compositionstart', function() { + composing = true; + }); + + element.on('compositionend', function() { + composing = false; + listener(); + }); + } + + var timeout; + + var listener = function(ev) { + if (timeout) { + $browser.defer.cancel(timeout); + timeout = null; + } + if (composing) return; + var value = element.val(), + event = ev && ev.type; + + // By default we will trim the value + // If the attribute ng-trim exists we will avoid trimming + // If input type is 'password', the value is never trimmed + if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { + value = trim(value); + } + + // If a control is suffering from bad input (due to native validators), browsers discard its + // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the + // control's value is the same empty value twice in a row. + if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { + ctrl.$setViewValue(value, event); + } + }; + + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut + if ($sniffer.hasEvent('input')) { + element.on('input', listener); + } else { + var deferListener = function(ev, input, origValue) { + if (!timeout) { + timeout = $browser.defer(function() { + timeout = null; + if (!input || input.value !== origValue) { + listener(ev); + } + }); + } + }; + + element.on('keydown', /** @this */ function(event) { + var key = event.keyCode; + + // ignore + // command modifiers arrows + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; + + deferListener(event, this, this.value); + }); + + // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + if ($sniffer.hasEvent('paste')) { + element.on('paste cut', deferListener); + } + } + + // if user paste into input using mouse on older browser + // or form autocomplete on newer browser, we need "change" event to catch it + element.on('change', listener); + + // Some native input types (date-family) have the ability to change validity without + // firing any input/change events. + // For these event types, when native validators are present and the browser supports the type, + // check for validity changes on various DOM events. + if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { + element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { + if (!timeout) { + var validity = this[VALIDITY_STATE_PROPERTY]; + var origBadInput = validity.badInput; + var origTypeMismatch = validity.typeMismatch; + timeout = $browser.defer(function() { + timeout = null; + if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { + listener(ev); + } + }); + } + }); + } + + ctrl.$render = function() { + // Workaround for Firefox validation #12102. + var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; + if (element.val() !== value) { + element.val(value); + } + }; +} + +function weekParser(isoWeek, existingDate) { + if (isDate(isoWeek)) { + return isoWeek; + } + + if (isString(isoWeek)) { + WEEK_REGEXP.lastIndex = 0; + var parts = WEEK_REGEXP.exec(isoWeek); + if (parts) { + var year = +parts[1], + week = +parts[2], + hours = 0, + minutes = 0, + seconds = 0, + milliseconds = 0, + firstThurs = getFirstThursdayOfYear(year), + addDays = (week - 1) * 7; + + if (existingDate) { + hours = existingDate.getHours(); + minutes = existingDate.getMinutes(); + seconds = existingDate.getSeconds(); + milliseconds = existingDate.getMilliseconds(); + } + + return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); + } + } + + return NaN; +} + +function createDateParser(regexp, mapping) { + return function(iso, date) { + var parts, map; + + if (isDate(iso)) { + return iso; + } + + if (isString(iso)) { + // When a date is JSON'ified to wraps itself inside of an extra + // set of double quotes. This makes the date parsing code unable + // to match the date string and parse it as a date. + if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { + iso = iso.substring(1, iso.length - 1); + } + if (ISO_DATE_REGEXP.test(iso)) { + return new Date(iso); + } + regexp.lastIndex = 0; + parts = regexp.exec(iso); + + if (parts) { + parts.shift(); + if (date) { + map = { + yyyy: date.getFullYear(), + MM: date.getMonth() + 1, + dd: date.getDate(), + HH: date.getHours(), + mm: date.getMinutes(), + ss: date.getSeconds(), + sss: date.getMilliseconds() / 1000 + }; + } else { + map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; + } + + forEach(parts, function(part, index) { + if (index < mapping.length) { + map[mapping[index]] = +part; + } + }); + return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); + } + } + + return NaN; + }; +} + +function createDateInputType(type, regexp, parseDate, format) { + return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { + badInputChecker(scope, element, attr, ctrl); + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + var timezone = ctrl && ctrl.$options.getOption('timezone'); + var previousDate; + + ctrl.$$parserName = type; + ctrl.$parsers.push(function(value) { + if (ctrl.$isEmpty(value)) return null; + if (regexp.test(value)) { + // Note: We cannot read ctrl.$modelValue, as there might be a different + // parser/formatter in the processing chain so that the model + // contains some different data format! + var parsedDate = parseDate(value, previousDate); + if (timezone) { + parsedDate = convertTimezoneToLocal(parsedDate, timezone); + } + return parsedDate; + } + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if (value && !isDate(value)) { + throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); + } + if (isValidDate(value)) { + previousDate = value; + if (previousDate && timezone) { + previousDate = convertTimezoneToLocal(previousDate, timezone, true); + } + return $filter('date')(value, format, timezone); + } else { + previousDate = null; + return ''; + } + }); + + if (isDefined(attr.min) || attr.ngMin) { + var minVal; + ctrl.$validators.min = function(value) { + return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; + }; + attr.$observe('min', function(val) { + minVal = parseObservedDateValue(val); + ctrl.$validate(); + }); + } + + if (isDefined(attr.max) || attr.ngMax) { + var maxVal; + ctrl.$validators.max = function(value) { + return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; + }; + attr.$observe('max', function(val) { + maxVal = parseObservedDateValue(val); + ctrl.$validate(); + }); + } + + function isValidDate(value) { + // Invalid Date: getTime() returns NaN + return value && !(value.getTime && value.getTime() !== value.getTime()); + } + + function parseObservedDateValue(val) { + return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; + } + }; +} + +function badInputChecker(scope, element, attr, ctrl) { + var node = element[0]; + var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); + if (nativeValidation) { + ctrl.$parsers.push(function(value) { + var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; + return validity.badInput || validity.typeMismatch ? undefined : value; + }); + } +} + +function numberFormatterParser(ctrl) { + ctrl.$$parserName = 'number'; + ctrl.$parsers.push(function(value) { + if (ctrl.$isEmpty(value)) return null; + if (NUMBER_REGEXP.test(value)) return parseFloat(value); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if (!ctrl.$isEmpty(value)) { + if (!isNumber(value)) { + throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); + } + value = value.toString(); + } + return value; + }); +} + +function parseNumberAttrVal(val) { + if (isDefined(val) && !isNumber(val)) { + val = parseFloat(val); + } + return !isNumberNaN(val) ? val : undefined; +} + +function isNumberInteger(num) { + // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066 + // (minus the assumption that `num` is a number) + + // eslint-disable-next-line no-bitwise + return (num | 0) === num; +} + +function countDecimals(num) { + var numString = num.toString(); + var decimalSymbolIndex = numString.indexOf('.'); + + if (decimalSymbolIndex === -1) { + if (-1 < num && num < 1) { + // It may be in the exponential notation format (`1e-X`) + var match = /e-(\d+)$/.exec(numString); + + if (match) { + return Number(match[1]); + } + } + + return 0; + } + + return numString.length - decimalSymbolIndex - 1; +} + +function isValidForStep(viewValue, stepBase, step) { + // At this point `stepBase` and `step` are expected to be non-NaN values + // and `viewValue` is expected to be a valid stringified number. + var value = Number(viewValue); + + // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or + // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. + if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) { + var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step)); + var multiplier = Math.pow(10, decimalCount); + + value = value * multiplier; + stepBase = stepBase * multiplier; + step = step * multiplier; + } + + return (value - stepBase) % step === 0; +} + +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { + badInputChecker(scope, element, attr, ctrl); + numberFormatterParser(ctrl); + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var minVal; + var maxVal; + + if (isDefined(attr.min) || attr.ngMin) { + ctrl.$validators.min = function(value) { + return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; + }; + + attr.$observe('min', function(val) { + minVal = parseNumberAttrVal(val); + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + }); + } + + if (isDefined(attr.max) || attr.ngMax) { + ctrl.$validators.max = function(value) { + return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; + }; + + attr.$observe('max', function(val) { + maxVal = parseNumberAttrVal(val); + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + }); + } + + if (isDefined(attr.step) || attr.ngStep) { + var stepVal; + ctrl.$validators.step = function(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || + isValidForStep(viewValue, minVal || 0, stepVal); + }; + + attr.$observe('step', function(val) { + stepVal = parseNumberAttrVal(val); + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + }); + } +} + +function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { + badInputChecker(scope, element, attr, ctrl); + numberFormatterParser(ctrl); + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', + minVal = supportsRange ? 0 : undefined, + maxVal = supportsRange ? 100 : undefined, + stepVal = supportsRange ? 1 : undefined, + validity = element[0].validity, + hasMinAttr = isDefined(attr.min), + hasMaxAttr = isDefined(attr.max), + hasStepAttr = isDefined(attr.step); + + var originalRender = ctrl.$render; + + ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? + //Browsers that implement range will set these values automatically, but reading the adjusted values after + //$render would cause the min / max validators to be applied with the wrong value + function rangeRender() { + originalRender(); + ctrl.$setViewValue(element.val()); + } : + originalRender; + + if (hasMinAttr) { + ctrl.$validators.min = supportsRange ? + // Since all browsers set the input to a valid value, we don't need to check validity + function noopMinValidator() { return true; } : + // non-support browsers validate the min val + function minValidator(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; + }; + + setInitialValueAndObserver('min', minChange); + } + + if (hasMaxAttr) { + ctrl.$validators.max = supportsRange ? + // Since all browsers set the input to a valid value, we don't need to check validity + function noopMaxValidator() { return true; } : + // non-support browsers validate the max val + function maxValidator(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; + }; + + setInitialValueAndObserver('max', maxChange); + } + + if (hasStepAttr) { + ctrl.$validators.step = supportsRange ? + function nativeStepValidator() { + // Currently, only FF implements the spec on step change correctly (i.e. adjusting the + // input element value to a valid value). It's possible that other browsers set the stepMismatch + // validity error instead, so we can at least report an error in that case. + return !validity.stepMismatch; + } : + // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would + function stepValidator(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || + isValidForStep(viewValue, minVal || 0, stepVal); + }; + + setInitialValueAndObserver('step', stepChange); + } + + function setInitialValueAndObserver(htmlAttrName, changeFn) { + // interpolated attributes set the attribute value only after a digest, but we need the + // attribute value when the input is first rendered, so that the browser can adjust the + // input value based on the min/max value + element.attr(htmlAttrName, attr[htmlAttrName]); + attr.$observe(htmlAttrName, changeFn); + } + + function minChange(val) { + minVal = parseNumberAttrVal(val); + // ignore changes before model is initialized + if (isNumberNaN(ctrl.$modelValue)) { + return; + } + + if (supportsRange) { + var elVal = element.val(); + // IE11 doesn't set the el val correctly if the minVal is greater than the element value + if (minVal > elVal) { + elVal = minVal; + element.val(elVal); + } + ctrl.$setViewValue(elVal); + } else { + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } + } + + function maxChange(val) { + maxVal = parseNumberAttrVal(val); + // ignore changes before model is initialized + if (isNumberNaN(ctrl.$modelValue)) { + return; + } + + if (supportsRange) { + var elVal = element.val(); + // IE11 doesn't set the el val correctly if the maxVal is less than the element value + if (maxVal < elVal) { + element.val(maxVal); + // IE11 and Chrome don't set the value to the minVal when max < min + elVal = maxVal < minVal ? minVal : maxVal; + } + ctrl.$setViewValue(elVal); + } else { + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } + } + + function stepChange(val) { + stepVal = parseNumberAttrVal(val); + // ignore changes before model is initialized + if (isNumberNaN(ctrl.$modelValue)) { + return; + } + + // Some browsers don't adjust the input value correctly, but set the stepMismatch error + if (supportsRange && ctrl.$viewValue !== element.val()) { + ctrl.$setViewValue(element.val()); + } else { + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } + } +} + +function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { + // Note: no badInputChecker here by purpose as `url` is only a validation + // in browsers, i.e. we can always read out input.value even if it is not valid! + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + stringBasedInputType(ctrl); + + ctrl.$$parserName = 'url'; + ctrl.$validators.url = function(modelValue, viewValue) { + var value = modelValue || viewValue; + return ctrl.$isEmpty(value) || URL_REGEXP.test(value); + }; +} + +function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { + // Note: no badInputChecker here by purpose as `url` is only a validation + // in browsers, i.e. we can always read out input.value even if it is not valid! + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + stringBasedInputType(ctrl); + + ctrl.$$parserName = 'email'; + ctrl.$validators.email = function(modelValue, viewValue) { + var value = modelValue || viewValue; + return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); + }; +} + +function radioInputType(scope, element, attr, ctrl) { + var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false'; + // make the name unique, if not defined + if (isUndefined(attr.name)) { + element.attr('name', nextUid()); + } + + var listener = function(ev) { + var value; + if (element[0].checked) { + value = attr.value; + if (doTrim) { + value = trim(value); + } + ctrl.$setViewValue(value, ev && ev.type); + } + }; + + element.on('click', listener); + + ctrl.$render = function() { + var value = attr.value; + if (doTrim) { + value = trim(value); + } + element[0].checked = (value === ctrl.$viewValue); + }; + + attr.$observe('value', ctrl.$render); +} + +function parseConstantExpr($parse, context, name, expression, fallback) { + var parseFn; + if (isDefined(expression)) { + parseFn = $parse(expression); + if (!parseFn.constant) { + throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + + '`{1}`.', name, expression); + } + return parseFn(context); + } + return fallback; +} + +function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { + var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); + var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); + + var listener = function(ev) { + ctrl.$setViewValue(element[0].checked, ev && ev.type); + }; + + element.on('click', listener); + + ctrl.$render = function() { + element[0].checked = ctrl.$viewValue; + }; + + // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` + // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert + // it to a boolean. + ctrl.$isEmpty = function(value) { + return value === false; + }; + + ctrl.$formatters.push(function(value) { + return equals(value, trueValue); + }); + + ctrl.$parsers.push(function(value) { + return value ? trueValue : falseValue; + }); +} + + +/** + * @ngdoc directive + * @name textarea + * @restrict E + * + * @description + * HTML textarea element control with angular data-binding. The data-binding and validation + * properties of this element are exactly the same as those of the + * {@link ng.directive:input input element}. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object, then this is used directly. + * If the expression evaluates to a string, then it will be converted to a RegExp + * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to + * `new RegExp('^abc$')`.
    + * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to + * start at the index of the last search's match, thus not taking the whole input value into + * account. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * + * @knownIssue + * + * When specifying the `placeholder` attribute of ` + *
    {{ list | json }}
    + * + * + * it("should split the text by newlines", function() { + * var listInput = element(by.model('list')); + * var output = element(by.binding('list | json')); + * listInput.sendKeys('abc\ndef\nghi'); + * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); + * }); + * + * + * + * @element input + * @param {string=} ngList optional delimiter that should be used to split the value. + */ +var ngListDirective = function() { + return { + restrict: 'A', + priority: 100, + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + var ngList = attr.ngList || ', '; + var trimValues = attr.ngTrim !== 'false'; + var separator = trimValues ? trim(ngList) : ngList; + + var parse = function(viewValue) { + // If the viewValue is invalid (say required but empty) it will be `undefined` + if (isUndefined(viewValue)) return; + + var list = []; + + if (viewValue) { + forEach(viewValue.split(separator), function(value) { + if (value) list.push(trimValues ? trim(value) : value); + }); + } + + return list; + }; + + ctrl.$parsers.push(parse); + ctrl.$formatters.push(function(value) { + if (isArray(value)) { + return value.join(ngList); + } + + return undefined; + }); + + // Override the standard $isEmpty because an empty array means the input is empty. + ctrl.$isEmpty = function(value) { + return !value || !value.length; + }; + } + }; +}; + +/* global VALID_CLASS: true, + INVALID_CLASS: true, + PRISTINE_CLASS: true, + DIRTY_CLASS: true, + UNTOUCHED_CLASS: true, + TOUCHED_CLASS: true, + PENDING_CLASS: true, + addSetValidityMethod: true, + setupValidity: true, + defaultModelOptions: false +*/ + + +var VALID_CLASS = 'ng-valid', + INVALID_CLASS = 'ng-invalid', + PRISTINE_CLASS = 'ng-pristine', + DIRTY_CLASS = 'ng-dirty', + UNTOUCHED_CLASS = 'ng-untouched', + TOUCHED_CLASS = 'ng-touched', + EMPTY_CLASS = 'ng-empty', + NOT_EMPTY_CLASS = 'ng-not-empty'; + +var ngModelMinErr = minErr('ngModel'); + +/** + * @ngdoc type + * @name ngModel.NgModelController + * + * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a + * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue + * is set. + * @property {*} $modelValue The value in the model that the control is bound to. + * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever + the control reads value from the DOM. The functions are called in array order, each passing + its return value through to the next. The last return value is forwarded to the + {@link ngModel.NgModelController#$validators `$validators`} collection. + +Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue +`$viewValue`}. + +Returning `undefined` from a parser means a parse error occurred. In that case, +no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` +will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} +is set to `true`. The parse error is stored in `ngModel.$error.parse`. + + * + * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever + the model value changes. The functions are called in reverse array order, each passing the value through to the + next. The last return value is used as the actual DOM value. + Used to format / convert values for display in the control. + * ```js + * function formatter(value) { + * if (value) { + * return value.toUpperCase(); + * } + * } + * ngModel.$formatters.push(formatter); + * ``` + * + * @property {Object.} $validators A collection of validators that are applied + * whenever the model value changes. The key value within the object refers to the name of the + * validator while the function refers to the validation operation. The validation operation is + * provided with the model value as an argument and must return a true or false value depending + * on the response of that validation. + * + * ```js + * ngModel.$validators.validCharacters = function(modelValue, viewValue) { + * var value = modelValue || viewValue; + * return /[0-9]+/.test(value) && + * /[a-z]+/.test(value) && + * /[A-Z]+/.test(value) && + * /\W+/.test(value); + * }; + * ``` + * + * @property {Object.} $asyncValidators A collection of validations that are expected to + * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided + * is expected to return a promise when it is run during the model validation process. Once the promise + * is delivered then the validation status will be set to true when fulfilled and false when rejected. + * When the asynchronous validators are triggered, each of the validators will run in parallel and the model + * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator + * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators + * will only run once all synchronous validators have passed. + * + * Please note that if $http is used then it is important that the server returns a success HTTP response code + * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. + * + * ```js + * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { + * var value = modelValue || viewValue; + * + * // Lookup user by username + * return $http.get('/api/users/' + value). + * then(function resolved() { + * //username exists, this means validation fails + * return $q.reject('exists'); + * }, function rejected() { + * //username does not exist, therefore this validation passes + * return true; + * }); + * }; + * ``` + * + * @property {Array.} $viewChangeListeners Array of functions to execute whenever the + * view value has changed. It is called with no arguments, and its return value is ignored. + * This can be used in place of additional $watches against the model value. + * + * @property {Object} $error An object hash with all failing validator ids as keys. + * @property {Object} $pending An object hash with all pending validator ids as keys. + * + * @property {boolean} $untouched True if control has not lost focus yet. + * @property {boolean} $touched True if control has lost focus. + * @property {boolean} $pristine True if user has not interacted with the control yet. + * @property {boolean} $dirty True if user has already interacted with the control. + * @property {boolean} $valid True if there is no error. + * @property {boolean} $invalid True if at least one error on the control. + * @property {string} $name The name attribute of the control. + * + * @description + * + * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. + * The controller contains services for data-binding, validation, CSS updates, and value formatting + * and parsing. It purposefully does not contain any logic which deals with DOM rendering or + * listening to DOM events. + * Such DOM related logic should be provided by other directives which make use of + * `NgModelController` for data-binding to control elements. + * Angular provides this DOM logic for most {@link input `input`} elements. + * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example + * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. + * + * @example + * ### Custom Control Example + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * `contenteditable` is an HTML5 attribute, which tells the browser to let the element + * contents be edited in place by the user. + * + * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} + * module to automatically remove "bad" content like inline event listener (e.g. ``). + * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks + * that content using the `$sce` service. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', ['ngSanitize']). + directive('contenteditable', ['$sce', function($sce) { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if (!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); + }; + + // Listen for change events to enable binding + element.on('blur keyup change', function() { + scope.$evalAsync(read); + }); + read(); // initialize + + // Write data to the model + function read() { + var html = element.html(); + // When we clear the content editable the browser leaves a
    behind + // If strip-br attribute is provided then we strip this out + if (attrs.stripBr && html === '
    ') { + html = ''; + } + ngModel.$setViewValue(html); + } + } + }; + }]); +
    + +
    +
    Change me!
    + Required! +
    + +
    +
    + + it('should data-bind and become invalid', function() { + if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') { + // SafariDriver can't handle contenteditable + // and Firefox driver can't clear contenteditables very well + return; + } + var contentEditable = element(by.css('[contenteditable]')); + var content = 'Change me!'; + + expect(contentEditable.getText()).toEqual(content); + + contentEditable.clear(); + contentEditable.sendKeys(protractor.Key.BACK_SPACE); + expect(contentEditable.getText()).toEqual(''); + expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); + }); + + *
    + * + * + */ +NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate']; +function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) { + this.$viewValue = Number.NaN; + this.$modelValue = Number.NaN; + this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. + this.$validators = {}; + this.$asyncValidators = {}; + this.$parsers = []; + this.$formatters = []; + this.$viewChangeListeners = []; + this.$untouched = true; + this.$touched = false; + this.$pristine = true; + this.$dirty = false; + this.$valid = true; + this.$invalid = false; + this.$error = {}; // keep invalid keys here + this.$$success = {}; // keep valid keys here + this.$pending = undefined; // keep pending keys here + this.$name = $interpolate($attr.name || '', false)($scope); + this.$$parentForm = nullFormCtrl; + this.$options = defaultModelOptions; + + this.$$parsedNgModel = $parse($attr.ngModel); + this.$$parsedNgModelAssign = this.$$parsedNgModel.assign; + this.$$ngModelGet = this.$$parsedNgModel; + this.$$ngModelSet = this.$$parsedNgModelAssign; + this.$$pendingDebounce = null; + this.$$parserValid = undefined; + + this.$$currentValidationRunId = 0; + + this.$$scope = $scope; + this.$$attr = $attr; + this.$$element = $element; + this.$$animate = $animate; + this.$$timeout = $timeout; + this.$$parse = $parse; + this.$$q = $q; + this.$$exceptionHandler = $exceptionHandler; + + setupValidity(this); + setupModelWatcher(this); +} + +NgModelController.prototype = { + $$initGetterSetters: function() { + if (this.$options.getOption('getterSetter')) { + var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'), + invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)'); + + this.$$ngModelGet = function($scope) { + var modelValue = this.$$parsedNgModel($scope); + if (isFunction(modelValue)) { + modelValue = invokeModelGetter($scope); + } + return modelValue; + }; + this.$$ngModelSet = function($scope, newValue) { + if (isFunction(this.$$parsedNgModel($scope))) { + invokeModelSetter($scope, {$$$p: newValue}); + } else { + this.$$parsedNgModelAssign($scope, newValue); + } + }; + } else if (!this.$$parsedNgModel.assign) { + throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}', + this.$$attr.ngModel, startingTag(this.$$element)); + } + }, + + + /** + * @ngdoc method + * @name ngModel.NgModelController#$render + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + * + * The `$render()` method is invoked in the following situations: + * + * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last + * committed value then `$render()` is called to update the input control. + * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and + * the `$viewValue` are different from last time. + * + * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of + * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` + * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be + * invoked if you only change a property on the objects. + */ + $render: noop, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$isEmpty + * + * @description + * This is called when we need to determine if the value of an input is empty. + * + * For instance, the required directive does this to work out if the input has data or not. + * + * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. + * + * You can override this for input directives whose concept of being empty is different from the + * default. The `checkboxInputType` directive does this because in its case a value of `false` + * implies empty. + * + * @param {*} value The value of the input to check for emptiness. + * @returns {boolean} True if `value` is "empty". + */ + $isEmpty: function(value) { + // eslint-disable-next-line no-self-compare + return isUndefined(value) || value === '' || value === null || value !== value; + }, + + $$updateEmptyClasses: function(value) { + if (this.$isEmpty(value)) { + this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS); + this.$$animate.addClass(this.$$element, EMPTY_CLASS); + } else { + this.$$animate.removeClass(this.$$element, EMPTY_CLASS); + this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS); + } + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setPristine + * + * @description + * Sets the control to its pristine state. + * + * This method can be called to remove the `ng-dirty` class and set the control to its pristine + * state (`ng-pristine` class). A model is considered to be pristine when the control + * has not been changed from when first compiled. + */ + $setPristine: function() { + this.$dirty = false; + this.$pristine = true; + this.$$animate.removeClass(this.$$element, DIRTY_CLASS); + this.$$animate.addClass(this.$$element, PRISTINE_CLASS); + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setDirty + * + * @description + * Sets the control to its dirty state. + * + * This method can be called to remove the `ng-pristine` class and set the control to its dirty + * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed + * from when first compiled. + */ + $setDirty: function() { + this.$dirty = true; + this.$pristine = false; + this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); + this.$$animate.addClass(this.$$element, DIRTY_CLASS); + this.$$parentForm.$setDirty(); + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setUntouched + * + * @description + * Sets the control to its untouched state. + * + * This method can be called to remove the `ng-touched` class and set the control to its + * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched + * by default, however this function can be used to restore that state if the model has + * already been touched by the user. + */ + $setUntouched: function() { + this.$touched = false; + this.$untouched = true; + this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS); + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setTouched + * + * @description + * Sets the control to its touched state. + * + * This method can be called to remove the `ng-untouched` class and set the control to its + * touched state (`ng-touched` class). A model is considered to be touched when the user has + * first focused the control element and then shifted focus away from the control (blur event). + */ + $setTouched: function() { + this.$touched = true; + this.$untouched = false; + this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS); + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$rollbackViewValue + * + * @description + * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, + * which may be caused by a pending debounced event or because the input is waiting for some + * future event. + * + * If you have an input that uses `ng-model-options` to set up debounced updates or updates that + * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of + * sync with the ngModel's `$modelValue`. + * + * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update + * and reset the input to the last committed view value. + * + * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` + * programmatically before these debounced/future events have resolved/occurred, because Angular's + * dirty checking mechanism is not able to tell whether the model has actually changed or not. + * + * The `$rollbackViewValue()` method should be called before programmatically changing the model of an + * input which may have such events pending. This is important in order to make sure that the + * input field will be updated with the new model value and any pending operations are cancelled. + * + * + * + * angular.module('cancel-update-example', []) + * + * .controller('CancelUpdateController', ['$scope', function($scope) { + * $scope.model = {value1: '', value2: ''}; + * + * $scope.setEmpty = function(e, value, rollback) { + * if (e.keyCode === 27) { + * e.preventDefault(); + * if (rollback) { + * $scope.myForm[value].$rollbackViewValue(); + * } + * $scope.model[value] = ''; + * } + * }; + * }]); + * + * + *
    + *

    Both of these inputs are only updated if they are blurred. Hitting escape should + * empty them. Follow these steps and observe the difference:

    + *
      + *
    1. Type something in the input. You will see that the model is not yet updated
    2. + *
    3. Press the Escape key. + *
        + *
      1. In the first example, nothing happens, because the model is already '', and no + * update is detected. If you blur the input, the model will be set to the current view. + *
      2. + *
      3. In the second example, the pending update is cancelled, and the input is set back + * to the last committed view value (''). Blurring the input does nothing. + *
      4. + *
      + *
    4. + *
    + * + *
    + *
    + *

    Without $rollbackViewValue():

    + * + * value1: "{{ model.value1 }}" + *
    + * + *
    + *

    With $rollbackViewValue():

    + * + * value2: "{{ model.value2 }}" + *
    + *
    + *
    + *
    + + div { + display: table-cell; + } + div:nth-child(1) { + padding-right: 30px; + } + + + *
    + */ + $rollbackViewValue: function() { + this.$$timeout.cancel(this.$$pendingDebounce); + this.$viewValue = this.$$lastCommittedViewValue; + this.$render(); + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$validate + * + * @description + * Runs each of the registered validators (first synchronous validators and then + * asynchronous validators). + * If the validity changes to invalid, the model will be set to `undefined`, + * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. + * If the validity changes to valid, it will set the model to the last available valid + * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. + */ + $validate: function() { + // ignore $validate before model is initialized + if (isNumberNaN(this.$modelValue)) { + return; + } + + var viewValue = this.$$lastCommittedViewValue; + // Note: we use the $$rawModelValue as $modelValue might have been + // set to undefined during a view -> model update that found validation + // errors. We can't parse the view here, since that could change + // the model although neither viewValue nor the model on the scope changed + var modelValue = this.$$rawModelValue; + + var prevValid = this.$valid; + var prevModelValue = this.$modelValue; + + var allowInvalid = this.$options.getOption('allowInvalid'); + + var that = this; + this.$$runValidators(modelValue, viewValue, function(allValid) { + // If there was no change in validity, don't update the model + // This prevents changing an invalid modelValue to undefined + if (!allowInvalid && prevValid !== allValid) { + // Note: Don't check this.$valid here, as we could have + // external validators (e.g. calculated on the server), + // that just call $setValidity and need the model value + // to calculate their validity. + that.$modelValue = allValid ? modelValue : undefined; + + if (that.$modelValue !== prevModelValue) { + that.$$writeModelToScope(); + } + } + }); + }, + + $$runValidators: function(modelValue, viewValue, doneCallback) { + this.$$currentValidationRunId++; + var localValidationRunId = this.$$currentValidationRunId; + var that = this; + + // check parser error + if (!processParseErrors()) { + validationDone(false); + return; + } + if (!processSyncValidators()) { + validationDone(false); + return; + } + processAsyncValidators(); + + function processParseErrors() { + var errorKey = that.$$parserName || 'parse'; + if (isUndefined(that.$$parserValid)) { + setValidity(errorKey, null); + } else { + if (!that.$$parserValid) { + forEach(that.$validators, function(v, name) { + setValidity(name, null); + }); + forEach(that.$asyncValidators, function(v, name) { + setValidity(name, null); + }); + } + // Set the parse error last, to prevent unsetting it, should a $validators key == parserName + setValidity(errorKey, that.$$parserValid); + return that.$$parserValid; + } + return true; + } + + function processSyncValidators() { + var syncValidatorsValid = true; + forEach(that.$validators, function(validator, name) { + var result = Boolean(validator(modelValue, viewValue)); + syncValidatorsValid = syncValidatorsValid && result; + setValidity(name, result); + }); + if (!syncValidatorsValid) { + forEach(that.$asyncValidators, function(v, name) { + setValidity(name, null); + }); + return false; + } + return true; + } + + function processAsyncValidators() { + var validatorPromises = []; + var allValid = true; + forEach(that.$asyncValidators, function(validator, name) { + var promise = validator(modelValue, viewValue); + if (!isPromiseLike(promise)) { + throw ngModelMinErr('nopromise', + 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise); + } + setValidity(name, undefined); + validatorPromises.push(promise.then(function() { + setValidity(name, true); + }, function() { + allValid = false; + setValidity(name, false); + })); + }); + if (!validatorPromises.length) { + validationDone(true); + } else { + that.$$q.all(validatorPromises).then(function() { + validationDone(allValid); + }, noop); + } + } + + function setValidity(name, isValid) { + if (localValidationRunId === that.$$currentValidationRunId) { + that.$setValidity(name, isValid); + } + } + + function validationDone(allValid) { + if (localValidationRunId === that.$$currentValidationRunId) { + + doneCallback(allValid); + } + } + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$commitViewValue + * + * @description + * Commit a pending update to the `$modelValue`. + * + * Updates may be pending by a debounced event or because the input is waiting for a some future + * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` + * usually handles calling this in response to input events. + */ + $commitViewValue: function() { + var viewValue = this.$viewValue; + + this.$$timeout.cancel(this.$$pendingDebounce); + + // If the view value has not changed then we should just exit, except in the case where there is + // a native validator on the element. In this case the validation state may have changed even though + // the viewValue has stayed empty. + if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) { + return; + } + this.$$updateEmptyClasses(viewValue); + this.$$lastCommittedViewValue = viewValue; + + // change to dirty + if (this.$pristine) { + this.$setDirty(); + } + this.$$parseAndValidate(); + }, + + $$parseAndValidate: function() { + var viewValue = this.$$lastCommittedViewValue; + var modelValue = viewValue; + var that = this; + + this.$$parserValid = isUndefined(modelValue) ? undefined : true; + + if (this.$$parserValid) { + for (var i = 0; i < this.$parsers.length; i++) { + modelValue = this.$parsers[i](modelValue); + if (isUndefined(modelValue)) { + this.$$parserValid = false; + break; + } + } + } + if (isNumberNaN(this.$modelValue)) { + // this.$modelValue has not been touched yet... + this.$modelValue = this.$$ngModelGet(this.$$scope); + } + var prevModelValue = this.$modelValue; + var allowInvalid = this.$options.getOption('allowInvalid'); + this.$$rawModelValue = modelValue; + + if (allowInvalid) { + this.$modelValue = modelValue; + writeToModelIfNeeded(); + } + + // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. + // This can happen if e.g. $setViewValue is called from inside a parser + this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) { + if (!allowInvalid) { + // Note: Don't check this.$valid here, as we could have + // external validators (e.g. calculated on the server), + // that just call $setValidity and need the model value + // to calculate their validity. + that.$modelValue = allValid ? modelValue : undefined; + writeToModelIfNeeded(); + } + }); + + function writeToModelIfNeeded() { + if (that.$modelValue !== prevModelValue) { + that.$$writeModelToScope(); + } + } + }, + + $$writeModelToScope: function() { + this.$$ngModelSet(this.$$scope, this.$modelValue); + forEach(this.$viewChangeListeners, function(listener) { + try { + listener(); + } catch (e) { + // eslint-disable-next-line no-invalid-this + this.$$exceptionHandler(e); + } + }, this); + }, + + /** + * @ngdoc method + * @name ngModel.NgModelController#$setViewValue + * + * @description + * Update the view value. + * + * This method should be called when a control wants to change the view value; typically, + * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} + * directive calls it when the value of the input changes and {@link ng.directive:select select} + * calls it when an option is selected. + * + * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` + * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged + * value sent directly for processing, finally to be applied to `$modelValue` and then the + * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, + * in the `$viewChangeListeners` list, are called. + * + * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` + * and the `default` trigger is not listed, all those actions will remain pending until one of the + * `updateOn` events is triggered on the DOM element. + * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} + * directive is used with a custom debounce for this particular event. + * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` + * is specified, once the timer runs out. + * + * When used with standard inputs, the view value will always be a string (which is in some cases + * parsed into another type, such as a `Date` object for `input[date]`.) + * However, custom controls might also pass objects to this method. In this case, we should make + * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not + * perform a deep watch of objects, it only looks for a change of identity. If you only change + * the property of the object then ngModel will not realize that the object has changed and + * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should + * not change properties of the copy once it has been passed to `$setViewValue`. + * Otherwise you may cause the model value on the scope to change incorrectly. + * + *
    + * In any case, the value passed to the method should always reflect the current value + * of the control. For example, if you are calling `$setViewValue` for an input element, + * you should pass the input DOM value. Otherwise, the control and the scope model become + * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change + * the control's DOM value in any way. If we want to change the control's DOM value + * programmatically, we should update the `ngModel` scope expression. Its new value will be + * picked up by the model controller, which will run it through the `$formatters`, `$render` it + * to update the DOM, and finally call `$validate` on it. + *
    + * + * @param {*} value value from the view. + * @param {string} trigger Event that triggered the update. + */ + $setViewValue: function(value, trigger) { + this.$viewValue = value; + if (this.$options.getOption('updateOnDefault')) { + this.$$debounceViewValueCommit(trigger); + } + }, + + $$debounceViewValueCommit: function(trigger) { + var debounceDelay = this.$options.getOption('debounce'); + + if (isNumber(debounceDelay[trigger])) { + debounceDelay = debounceDelay[trigger]; + } else if (isNumber(debounceDelay['default'])) { + debounceDelay = debounceDelay['default']; + } + + this.$$timeout.cancel(this.$$pendingDebounce); + var that = this; + if (debounceDelay > 0) { // this fails if debounceDelay is an object + this.$$pendingDebounce = this.$$timeout(function() { + that.$commitViewValue(); + }, debounceDelay); + } else if (this.$$scope.$root.$$phase) { + this.$commitViewValue(); + } else { + this.$$scope.$apply(function() { + that.$commitViewValue(); + }); + } + } +}; + +function setupModelWatcher(ctrl) { + // model -> value + // Note: we cannot use a normal scope.$watch as we want to detect the following: + // 1. scope value is 'a' + // 2. user enters 'b' + // 3. ng-change kicks in and reverts scope value to 'a' + // -> scope value did not change since the last digest as + // ng-change executes in apply phase + // 4. view should be changed back to 'a' + ctrl.$$scope.$watch(function ngModelWatch() { + var modelValue = ctrl.$$ngModelGet(ctrl.$$scope); + + // if scope model value and ngModel value are out of sync + // TODO(perf): why not move this to the action fn? + if (modelValue !== ctrl.$modelValue && + // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator + // eslint-disable-next-line no-self-compare + (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) + ) { + ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; + ctrl.$$parserValid = undefined; + + var formatters = ctrl.$formatters, + idx = formatters.length; + + var viewValue = modelValue; + while (idx--) { + viewValue = formatters[idx](viewValue); + } + if (ctrl.$viewValue !== viewValue) { + ctrl.$$updateEmptyClasses(viewValue); + ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; + ctrl.$render(); + + // It is possible that model and view value have been updated during render + ctrl.$$runValidators(ctrl.$modelValue, ctrl.$viewValue, noop); + } + } + + return modelValue; + }); +} + +/** + * @ngdoc method + * @name ngModel.NgModelController#$setValidity + * + * @description + * Change the validity state, and notify the form. + * + * This method can be called within $parsers/$formatters or a custom validation implementation. + * However, in most cases it should be sufficient to use the `ngModel.$validators` and + * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. + * + * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned + * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` + * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. + * The `validationErrorKey` should be in camelCase and will get converted into dash-case + * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` + * class and can be bound to as `{{someForm.someControl.$error.myError}}` . + * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), + * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. + * Skipped is used by Angular when validators do not run because of parse errors and + * when `$asyncValidators` do not run because any of the `$validators` failed. + */ +addSetValidityMethod({ + clazz: NgModelController, + set: function(object, property) { + object[property] = true; + }, + unset: function(object, property) { + delete object[property]; + } +}); + + +/** + * @ngdoc directive + * @name ngModel + * + * @element input + * @priority 1 + * + * @description + * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a + * property on the scope using {@link ngModel.NgModelController NgModelController}, + * which is created and exposed by this directive. + * + * `ngModel` is responsible for: + * + * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` + * require. + * - Providing validation behavior (i.e. required, number, email, url). + * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). + * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, + * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. + * - Registering the control with its parent {@link ng.directive:form form}. + * + * Note: `ngModel` will try to bind to the property given by evaluating the expression on the + * current scope. If the property doesn't already exist on this scope, it will be created + * implicitly and added to the scope. + * + * For best practices on using `ngModel`, see: + * + * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) + * + * For basic examples, how to use `ngModel`, see: + * + * - {@link ng.directive:input input} + * - {@link input[text] text} + * - {@link input[checkbox] checkbox} + * - {@link input[radio] radio} + * - {@link input[number] number} + * - {@link input[email] email} + * - {@link input[url] url} + * - {@link input[date] date} + * - {@link input[datetime-local] datetime-local} + * - {@link input[time] time} + * - {@link input[month] month} + * - {@link input[week] week} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} + * + * # Complex Models (objects or collections) + * + * By default, `ngModel` watches the model by reference, not value. This is important to know when + * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the + * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. + * + * The model must be assigned an entirely new object or collection before a re-rendering will occur. + * + * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression + * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or + * if the select is given the `multiple` attribute. + * + * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the + * first level of the object (or only changing the properties of an item in the collection if it's an array) will still + * not trigger a re-rendering of the model. + * + * # CSS classes + * The following CSS classes are added and removed on the associated input/select/textarea element + * depending on the validity of the model. + * + * - `ng-valid`: the model is valid + * - `ng-invalid`: the model is invalid + * - `ng-valid-[key]`: for each valid key added by `$setValidity` + * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` + * - `ng-pristine`: the control hasn't been interacted with yet + * - `ng-dirty`: the control has been interacted with + * - `ng-touched`: the control has been blurred + * - `ng-untouched`: the control hasn't been blurred + * - `ng-pending`: any `$asyncValidators` are unfulfilled + * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined + * by the {@link ngModel.NgModelController#$isEmpty} method + * - `ng-not-empty`: the view contains a non-empty value + * + * Keep in mind that ngAnimate can detect each of these classes when added and removed. + * + * ## Animation Hooks + * + * Animations within models are triggered when any of the associated CSS classes are added and removed + * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, + * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. + * The animations that are triggered within ngModel are similar to how they work in ngClass and + * animations can be hooked into using CSS transitions, keyframes as well as JS animations. + * + * The following example shows a simple way to utilize CSS transitions to style an input element + * that has been rendered as invalid after it has been validated: + * + *
    + * //be sure to include ngAnimate as a module to hook into more
    + * //advanced animations
    + * .my-input {
    + *   transition:0.5s linear all;
    + *   background: white;
    + * }
    + * .my-input.ng-invalid {
    + *   background: red;
    + *   color:white;
    + * }
    + * 
    + * + * @example + * + + + +

    + Update input to see transitions when valid/invalid. + Integer is a valid value. +

    +
    + +
    +
    + *
    + * + * ## Binding to a getter/setter + * + * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a + * function that returns a representation of the model when called with zero arguments, and sets + * the internal state of a model when called with an argument. It's sometimes useful to use this + * for models that have an internal representation that's different from what the model exposes + * to the view. + * + *
    + * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more + * frequently than other parts of your code. + *
    + * + * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that + * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to + * a `
    `, which will enable this behavior for all ``s within it. See + * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. + * + * The following example shows how to use `ngModel` with a getter/setter: + * + * @example + * + +
    + + + +
    user.name = 
    +
    +
    + + angular.module('getterSetterExample', []) + .controller('ExampleController', ['$scope', function($scope) { + var _name = 'Brian'; + $scope.user = { + name: function(newName) { + // Note that newName can be undefined for two reasons: + // 1. Because it is called as a getter and thus called with no arguments + // 2. Because the property should actually be set to undefined. This happens e.g. if the + // input is invalid + return arguments.length ? (_name = newName) : _name; + } + }; + }]); + + *
    + */ +var ngModelDirective = ['$rootScope', function($rootScope) { + return { + restrict: 'A', + require: ['ngModel', '^?form', '^?ngModelOptions'], + controller: NgModelController, + // Prelink needs to run before any input directive + // so that we can set the NgModelOptions in NgModelController + // before anyone else uses it. + priority: 1, + compile: function ngModelCompile(element) { + // Setup initial state of the control + element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); + + return { + pre: function ngModelPreLink(scope, element, attr, ctrls) { + var modelCtrl = ctrls[0], + formCtrl = ctrls[1] || modelCtrl.$$parentForm, + optionsCtrl = ctrls[2]; + + if (optionsCtrl) { + modelCtrl.$options = optionsCtrl.$options; + } + + modelCtrl.$$initGetterSetters(); + + // notify others, especially parent forms + formCtrl.$addControl(modelCtrl); + + attr.$observe('name', function(newValue) { + if (modelCtrl.$name !== newValue) { + modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); + } + }); + + scope.$on('$destroy', function() { + modelCtrl.$$parentForm.$removeControl(modelCtrl); + }); + }, + post: function ngModelPostLink(scope, element, attr, ctrls) { + var modelCtrl = ctrls[0]; + if (modelCtrl.$options.getOption('updateOn')) { + element.on(modelCtrl.$options.getOption('updateOn'), function(ev) { + modelCtrl.$$debounceViewValueCommit(ev && ev.type); + }); + } + + function setTouched() { + modelCtrl.$setTouched(); + } + + element.on('blur', function() { + if (modelCtrl.$touched) return; + + if ($rootScope.$$phase) { + scope.$evalAsync(setTouched); + } else { + scope.$apply(setTouched); + } + }); + } + }; + } + }; +}]; + +/* exported defaultModelOptions */ +var defaultModelOptions; +var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; + +/** + * @ngdoc type + * @name ModelOptions + * @description + * A container for the options set by the {@link ngModelOptions} directive + */ +function ModelOptions(options) { + this.$$options = options; +} + +ModelOptions.prototype = { + + /** + * @ngdoc method + * @name ModelOptions#getOption + * @param {string} name the name of the option to retrieve + * @returns {*} the value of the option + * @description + * Returns the value of the given option + */ + getOption: function(name) { + return this.$$options[name]; + }, + + /** + * @ngdoc method + * @name ModelOptions#createChild + * @param {Object} options a hash of options for the new child that will override the parent's options + * @return {ModelOptions} a new `ModelOptions` object initialized with the given options. + */ + createChild: function(options) { + var inheritAll = false; + + // make a shallow copy + options = extend({}, options); + + // Inherit options from the parent if specified by the value `"$inherit"` + forEach(options, /* @this */ function(option, key) { + if (option === '$inherit') { + if (key === '*') { + inheritAll = true; + } else { + options[key] = this.$$options[key]; + // `updateOn` is special so we must also inherit the `updateOnDefault` option + if (key === 'updateOn') { + options.updateOnDefault = this.$$options.updateOnDefault; + } + } + } else { + if (key === 'updateOn') { + // If the `updateOn` property contains the `default` event then we have to remove + // it from the event list and set the `updateOnDefault` flag. + options.updateOnDefault = false; + options[key] = trim(option.replace(DEFAULT_REGEXP, function() { + options.updateOnDefault = true; + return ' '; + })); + } + } + }, this); + + if (inheritAll) { + // We have a property of the form: `"*": "$inherit"` + delete options['*']; + defaults(options, this.$$options); + } + + // Finally add in any missing defaults + defaults(options, defaultModelOptions.$$options); + + return new ModelOptions(options); + } +}; + + +defaultModelOptions = new ModelOptions({ + updateOn: '', + updateOnDefault: true, + debounce: 0, + getterSetter: false, + allowInvalid: false, + timezone: null +}); + + +/** + * @ngdoc directive + * @name ngModelOptions + * + * @description + * This directive allows you to modify the behaviour of {@link ngModel} directives within your + * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel} + * directives will use the options of their nearest `ngModelOptions` ancestor. + * + * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as + * an Angular expression. This expression should evaluate to an object, whose properties contain + * the settings. For example: `
    + *
    + * + *
    + *
    + * ``` + * + * the `input` element will have the following settings + * + * ```js + * { allowInvalid: true, updateOn: 'default', debounce: 0 } + * ``` + * + * Notice that the `debounce` setting was not inherited and used the default value instead. + * + * You can specify that all undefined settings are automatically inherited from an ancestor by + * including a property with key of `"*"` and value of `"$inherit"`. + * + * For example given the following fragment of HTML + * + * + * ```html + *
    + *
    + * + *
    + *
    + * ``` + * + * the `input` element will have the following settings + * + * ```js + * { allowInvalid: true, updateOn: 'default', debounce: 200 } + * ``` + * + * Notice that the `debounce` setting now inherits the value from the outer `
    ` element. + * + * If you are creating a reusable component then you should be careful when using `"*": "$inherit"` + * since you may inadvertently inherit a setting in the future that changes the behavior of your component. + * + * + * ## Triggering and debouncing model updates + * + * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will + * trigger a model update and/or a debouncing delay so that the actual update only takes place when + * a timer expires; this timer will be reset after another change takes place. + * + * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might + * be different from the value in the actual model. This means that if you update the model you + * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in + * order to make sure it is synchronized with the model and that any debounced action is canceled. + * + * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue} + * method is by making sure the input is placed inside a form that has a `name` attribute. This is + * important because `form` controllers are published to the related scope under the name in their + * `name` attribute. + * + * Any pending changes will take place immediately when an enclosing form is submitted via the + * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` + * to have access to the updated model. + * + * The following example shows how to override immediate updates. Changes on the inputs within the + * form will update the model only when the control loses focus (blur event). If `escape` key is + * pressed while the input field is focused, the value is reset to the value in the current model. + * + * + * + *
    + *
    + *
    + *
    + *
    + *
    user.name = 
    + *
    + *
    + * + * angular.module('optionsExample', []) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.user = { name: 'say', data: '' }; + * + * $scope.cancel = function(e) { + * if (e.keyCode === 27) { + * $scope.userForm.userName.$rollbackViewValue(); + * } + * }; + * }]); + * + * + * var model = element(by.binding('user.name')); + * var input = element(by.model('user.name')); + * var other = element(by.model('user.data')); + * + * it('should allow custom events', function() { + * input.sendKeys(' hello'); + * input.click(); + * expect(model.getText()).toEqual('say'); + * other.click(); + * expect(model.getText()).toEqual('say hello'); + * }); + * + * it('should $rollbackViewValue when model changes', function() { + * input.sendKeys(' hello'); + * expect(input.getAttribute('value')).toEqual('say hello'); + * input.sendKeys(protractor.Key.ESCAPE); + * expect(input.getAttribute('value')).toEqual('say'); + * other.click(); + * expect(model.getText()).toEqual('say'); + * }); + * + *
    + * + * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change. + * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. + * + * + * + *
    + *
    + * Name: + * + *
    + *
    + *
    user.name = 
    + *
    + *
    + * + * angular.module('optionsExample', []) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.user = { name: 'say' }; + * }]); + * + *
    + * + * ## Model updates and validation + * + * The default behaviour in `ngModel` is that the model value is set to `undefined` when the + * validation determines that the value is invalid. By setting the `allowInvalid` property to true, + * the model will still be updated even if the value is invalid. + * + * + * ## Connecting to the scope + * + * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression + * on the scope refers to a "getter/setter" function rather than the value itself. + * + * The following example shows how to bind to getter/setters: + * + * + * + *
    + *
    + * + *
    + *
    user.name = 
    + *
    + *
    + * + * angular.module('getterSetterExample', []) + * .controller('ExampleController', ['$scope', function($scope) { + * var _name = 'Brian'; + * $scope.user = { + * name: function(newName) { + * return angular.isDefined(newName) ? (_name = newName) : _name; + * } + * }; + * }]); + * + *
    + * + * + * ## Specifying timezones + * + * You can specify the timezone that date/time input directives expect by providing its name in the + * `timezone` property. + * + * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and + * and its descendents. Valid keys are: + * - `updateOn`: string specifying which event should the input be bound to. You can set several + * events using an space delimited list. There is a special event called `default` that + * matches the default events belonging to the control. + * - `debounce`: integer value which contains the debounce model update value in milliseconds. A + * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a + * custom value for each event. For example: + * ``` + * ng-model-options="{ + * updateOn: 'default blur', + * debounce: { 'default': 500, 'blur': 0 } + * }" + * ``` + * - `allowInvalid`: boolean value which indicates that the model can be set with values that did + * not validate correctly instead of the default behavior of setting the model to undefined. + * - `getterSetter`: boolean value which determines whether or not to treat functions bound to + * `ngModel` as getters/setters. + * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for + * ``, ``, ... . It understands UTC/GMT and the + * continental US time zone abbreviations, but for general use, use a time zone offset, for + * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) + * If not specified, the timezone of the browser will be used. + * + */ +var ngModelOptionsDirective = function() { + NgModelOptionsController.$inject = ['$attrs', '$scope']; + function NgModelOptionsController($attrs, $scope) { + this.$$attrs = $attrs; + this.$$scope = $scope; + } + NgModelOptionsController.prototype = { + $onInit: function() { + var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions; + var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions); + + this.$options = parentOptions.createChild(modelOptionsDefinition); + } + }; + + return { + restrict: 'A', + // ngModelOptions needs to run before ngModel and input directives + priority: 10, + require: {parentCtrl: '?^^ngModelOptions'}, + bindToController: true, + controller: NgModelOptionsController + }; +}; + + +// shallow copy over values from `src` that are not already specified on `dst` +function defaults(dst, src) { + forEach(src, function(value, key) { + if (!isDefined(dst[key])) { + dst[key] = value; + } + }); +} + +/** + * @ngdoc directive + * @name ngNonBindable + * @restrict AC + * @priority 1000 + * + * @description + * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current + * DOM element. This is useful if the element contains what appears to be Angular directives and + * bindings but which should be ignored by Angular. This could be the case if you have a site that + * displays snippets of code, for instance. + * + * @element ANY + * + * @example + * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, + * but the one wrapped in `ngNonBindable` is left alone. + * + * @example + + +
    Normal: {{1 + 2}}
    +
    Ignored: {{1 + 2}}
    +
    + + it('should check ng-non-bindable', function() { + expect(element(by.binding('1 + 2')).getText()).toContain('3'); + expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); + }); + +
    + */ +var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); + +/* exported ngOptionsDirective */ + +/* global jqLiteRemove */ + +var ngOptionsMinErr = minErr('ngOptions'); + +/** + * @ngdoc directive + * @name ngOptions + * @restrict A + * + * @description + * + * The `ngOptions` attribute can be used to dynamically generate a list of `` + * DOM element. + * * `disable`: The result of this expression will be used to disable the rendered `