├── .gitignore ├── css ├── style.css └── style.css.map ├── gulpfile.js ├── index.html ├── js ├── app.js ├── vue-resource.js ├── vue-router.js └── vue.js ├── package.json ├── readme.md └── sass ├── _fonts.scss ├── _footer.scss ├── _globals.scss ├── _header.scss ├── _homepage.scss ├── _mixins.scss ├── _su.scss ├── _susy.scss ├── _variables.scss ├── style.scss └── susy ├── _su.scss ├── language ├── _susy.scss └── susy │ ├── _background.scss │ ├── _bleed.scss │ ├── _box-sizing.scss │ ├── _breakpoint-plugin.scss │ ├── _container.scss │ ├── _context.scss │ ├── _gallery.scss │ ├── _grids.scss │ ├── _gutters.scss │ ├── _isolate.scss │ ├── _margins.scss │ ├── _padding.scss │ ├── _rows.scss │ ├── _settings.scss │ ├── _span.scss │ └── _validation.scss ├── output ├── _float.scss ├── _shared.scss ├── _support.scss ├── float │ ├── _container.scss │ ├── _end.scss │ ├── _isolate.scss │ └── _span.scss ├── shared │ ├── _background.scss │ ├── _container.scss │ ├── _direction.scss │ ├── _inspect.scss │ ├── _margins.scss │ ├── _output.scss │ └── _padding.scss └── support │ ├── _background.scss │ ├── _box-sizing.scss │ ├── _clearfix.scss │ ├── _prefix.scss │ ├── _rem.scss │ └── _support.scss └── su ├── _grid.scss ├── _settings.scss ├── _utilities.scss └── _validation.scss /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Assets -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html{font-size:100%}body{-webkit-font-smoothing:antialiased;padding:60px 0}*{box-sizing:border-box}.movie-item{border-bottom:1px solid #ccc;margin-bottom:20px;padding:10px}.movie-item img{max-width:100%;height:auto}.movie-item h4{margin-top:0}.filter{padding:40px;background:#efefef;margin-bottom:40px}* html .clearfix{height:1%;overflow:visible}*+html .clearfix{min-height:1%}.clearfix:after{clear:both;content:".";display:block;height:0;visibility:hidden;font-size:0}.clr{clear:both}.loading-transition{transition:all .3s ease;padding:20px;color:#fff;background:#5CB85C;margin:20px 0;width:100%}.loading-enter,.loading-leave{width:0} 2 | 3 | /*# sourceMappingURL=style.css.map */ 4 | -------------------------------------------------------------------------------- /css/style.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["style.css"],"names":[],"mappings":"AAAA,KAAK,cAAc,CAAC,KAAK,mCAAmC,cAAc,CAAC,EAAE,qBAAqB,CAAC,YAAY,6BAA6B,mBAAmB,YAAY,CAAC,gBAAgB,eAAe,WAAW,CAAC,eAAe,YAAY,CAAC,QAAQ,aAAa,mBAAmB,kBAAkB,CAAC,iBAAiB,UAAU,gBAAgB,CAAC,iBAAiB,aAAa,CAAC,gBAAgB,WAAW,YAAY,cAAc,SAAS,kBAAkB,WAAW,CAAC,KAAK,UAAU,CAAC,oBAAoB,wBAAwB,aAAa,WAAW,mBAAmB,cAAc,UAAU,CAAC,8BAA8B,OAAO,CAAC","file":"style.css","sourcesContent":["html{font-size:100%}body{-webkit-font-smoothing:antialiased;padding:60px 0}*{box-sizing:border-box}.movie-item{border-bottom:1px solid #ccc;margin-bottom:20px;padding:10px}.movie-item img{max-width:100%;height:auto}.movie-item h4{margin-top:0}.filter{padding:40px;background:#efefef;margin-bottom:40px}* html .clearfix{height:1%;overflow:visible}*+html .clearfix{min-height:1%}.clearfix:after{clear:both;content:\".\";display:block;height:0;visibility:hidden;font-size:0}.clr{clear:both}.loading-transition{transition:all .3s ease;padding:20px;color:#fff;background:#5CB85C;margin:20px 0;width:100%}.loading-enter,.loading-leave{width:0}\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var livereload = require('gulp-livereload') 3 | var sass = require('gulp-sass'); 4 | var autoprefixer = require('gulp-autoprefixer'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | 7 | 8 | 9 | gulp.task('sass', function () { 10 | gulp.src('./sass/**/*.scss') 11 | .pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError)) 12 | .pipe(sourcemaps.init()) 13 | .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 7', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4')) 14 | .pipe(sourcemaps.write('./')) 15 | .pipe(gulp.dest('./css')); 16 | }); 17 | 18 | gulp.task('watch', function(){ 19 | livereload.listen(); 20 | 21 | gulp.watch('./sass/**/*.scss', ['sass']); 22 | gulp.watch(['./css/style.css', './js/*.js', '*.html'], function (files){ 23 | livereload.changed(files) 24 | }); 25 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Movies App 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 |
16 | 17 | 68 | 69 | 78 | 79 | 97 | 98 | 103 | 104 | 105 | 125 | 126 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | apiURL = "http://moviesapi.dev/api/movies" 2 | 3 | var App = Vue.extend({}); 4 | 5 | var register = Vue.extend({ 6 | template: '#register', 7 | 8 | data: function(){ 9 | return { 10 | name: '', 11 | email: '', 12 | password:'', 13 | message: '', 14 | success: '', 15 | error: '' 16 | } 17 | }, 18 | 19 | http:{ 20 | headers:{ 21 | 'Accept' : 'json', 22 | 'Content-Type' : 'application/hal+json', 23 | 'Authorization' : 'Basic aXZhbjpwYXNzd29yZA==' 24 | } 25 | }, 26 | 27 | methods: { 28 | registerUser: function(event){ 29 | event.preventDefault(); 30 | var data = { 31 | '_links':{ 32 | 'type' : { 33 | 'href' : 'http://moviesapi.dev/rest/type/user/user' 34 | } 35 | }, 36 | 'name':[ 37 | { 38 | 'value' : this.name 39 | } 40 | ], 41 | 'mail':[ 42 | { 43 | 'value' : this.email 44 | } 45 | ], 46 | 'pass':[ 47 | { 48 | 'value' : this.password 49 | } 50 | ], 51 | 'status':[ 52 | { 53 | 'value' : '1' 54 | } 55 | ], 56 | 'roles':[ 57 | { 58 | 'target_id' : 'administrator' 59 | } 60 | ] 61 | } 62 | 63 | this.$http.post('http://moviesapi.dev/entity/user', data, function(response){ 64 | this.$set('message', 'You have registered! Yaaaay! :)'); 65 | this.$set('success', true) 66 | }).error(function(response){ 67 | this.$set('message', 'Something went wrong! Please check your info and try again.'); 68 | this.$set('error', true); 69 | }); 70 | } 71 | } 72 | }) 73 | 74 | var editMovie = Vue.extend({ 75 | template: '#edit-movie', 76 | 77 | data: function(){ 78 | return { 79 | movie: '', 80 | title: '', 81 | body: '' 82 | } 83 | }, 84 | 85 | ready: function(){ 86 | this.getTheMovie(); 87 | }, 88 | 89 | methods: { 90 | getTheMovie: function(){ 91 | this.$http.get(apiURL + '/' + this.$route.params.movieID, function(movie){ 92 | this.$set('movie', movie); 93 | }) 94 | }, 95 | 96 | updateTheMovie: function(event){ 97 | event.preventDefault(); 98 | var data = { 99 | '_links':{ 100 | 'type' : { 101 | 'href' : 'http://moviesapi.dev/rest/type/node/movies' 102 | } 103 | }, 104 | 'title':[ 105 | { 106 | 'value' : this.title 107 | } 108 | ], 109 | 'body':[ 110 | { 111 | 'value' : this.body 112 | } 113 | ] 114 | } 115 | 116 | this.$http.patch('http://moviesapi.dev/node/' + this.$route.params.movieID, data, function(response){ 117 | this.$route.router.go('/'); 118 | },{ 119 | headers:{ 120 | 'Accept' : 'json', 121 | 'Content-Type' : 'application/hal+json', 122 | 'Authorization' : 'Basic aXZhbjpwYXNzd29yZA==' 123 | } 124 | }); 125 | } 126 | } 127 | }) 128 | 129 | var deleteMovie = Vue.extend({ 130 | template: '#delete-movie', 131 | http:{ 132 | headers:{ 133 | 'Accept' : 'json', 134 | 'Content-Type' : 'application/hal+json', 135 | 'Authorization' : 'Basic aXZhbjpwYXNzd29yZA==' 136 | } 137 | }, 138 | 139 | methods:{ 140 | deleteTheMovie: function(){ 141 | this.$http.delete('http://moviesapi.dev/node/' + this.$route.params.movieID, function(response){ 142 | this.$route.router.go('/'); 143 | }) 144 | } 145 | } 146 | }); 147 | 148 | var createMovie = Vue.extend({ 149 | template: '#create-movie', 150 | 151 | data: function(){ 152 | return { 153 | title: '', 154 | body: '', 155 | success:'' 156 | } 157 | }, 158 | 159 | http:{ 160 | headers:{ 161 | 'Accept' : 'json', 162 | 'Content-Type' : 'application/hal+json', 163 | 'Authorization' : 'Basic aXZhbjpwYXNzd29yZA==' 164 | } 165 | }, 166 | 167 | ready: function(){ 168 | //this.createTheMovie(); 169 | }, 170 | 171 | methods: { 172 | createTheMovie: function(event){ 173 | event.preventDefault(); 174 | var data = { 175 | '_links':{ 176 | 'type' : { 177 | 'href' : 'http://moviesapi.dev/rest/type/node/movies' 178 | } 179 | }, 180 | 'title':[ 181 | { 182 | 'value' : this.title 183 | } 184 | ], 185 | 'body':[ 186 | { 187 | 'value' : this.body 188 | } 189 | ] 190 | } 191 | 192 | this.$http.post('http://moviesapi.dev/entity/node', data, function(response){ 193 | this.$set('success', 'ok'); 194 | this.$set('title', ''); 195 | this.$set('body', ''); 196 | }); 197 | } 198 | } 199 | }) 200 | 201 | var movieList = Vue.extend({ 202 | template: '#movie-list-template', 203 | 204 | data: function() { 205 | return { 206 | movies: '', 207 | liveFilter: '', 208 | genreFilter: '', 209 | genres: '', 210 | movie:'', 211 | loading: false 212 | } 213 | }, 214 | 215 | ready: function(){ 216 | this.getMovies(); 217 | }, 218 | 219 | methods: { 220 | getMovies: function(){ 221 | this.$set('movie', ''); 222 | this.$set('loading', true); 223 | 224 | this.$http.get(apiURL, function(movies){ 225 | this.$set('loading', false); 226 | this.$set('movies', movies); 227 | 228 | genresArr=[]; 229 | 230 | jQuery.each(movies, function(index, movie){ 231 | jQuery.each(movie.field_genres, function(index, genre){ 232 | if(jQuery.inArray(genre.value, genresArr) === -1) { 233 | genresArr.push(genre.value); 234 | } 235 | }); 236 | }); 237 | 238 | this.$set('genres', genresArr); 239 | //console.log(JSON.stringify(genresArr)); 240 | 241 | }); 242 | } 243 | } 244 | }) 245 | 246 | var singleMovie = Vue.extend({ 247 | template: '#single-movie-template', 248 | 249 | data: function(){ 250 | return { 251 | movie:'', 252 | loading: false 253 | } 254 | }, 255 | 256 | ready: function(){ 257 | this.getTheMovie(); 258 | }, 259 | 260 | methods: { 261 | getTheMovie: function(){ 262 | this.$set('loading', true); 263 | this.$http.get(apiURL + '/' + this.$route.params.movieID, function(movie){ 264 | this.$set('loading', false); 265 | this.$set('movie', movie); 266 | console.log(JSON.stringify(movie)); 267 | }) 268 | } 269 | } 270 | }) 271 | 272 | 273 | var router = new VueRouter(); 274 | 275 | router.map({ 276 | '/':{ 277 | component: movieList 278 | }, 279 | 'create':{ 280 | component: createMovie 281 | }, 282 | 'movie/:movieID':{ 283 | name: 'movie', 284 | component: singleMovie 285 | }, 286 | 'delete/:movieID':{ 287 | name: 'delete', 288 | component: deleteMovie 289 | }, 290 | 'edit/:movieID':{ 291 | name: 'edit', 292 | component: editMovie 293 | }, 294 | 'register':{ 295 | component: register 296 | }, 297 | }); 298 | 299 | router.start(App, '#app'); 300 | 301 | 302 | 303 | /*new Vue({ 304 | el: '#app', 305 | 306 | data: { 307 | movies: '', 308 | liveFilter: '', 309 | genreFilter: '', 310 | genres: '', 311 | movie:'' 312 | }, 313 | 314 | ready: function(){ 315 | this.getMovies(); 316 | }, 317 | 318 | methods: { 319 | getMovies: function(){ 320 | this.$set('movie', ''); 321 | this.$http.get(apiURL, function(movies){ 322 | this.$set('movies', movies); 323 | 324 | genresArr=[]; 325 | 326 | jQuery.each(movies, function(index, movie){ 327 | jQuery.each(movie.field_genres, function(index, genre){ 328 | if(jQuery.inArray(genre.value, genresArr) === -1) { 329 | genresArr.push(genre.value); 330 | } 331 | }); 332 | }); 333 | 334 | this.$set('genres', genresArr); 335 | //console.log(JSON.stringify(genresArr)); 336 | 337 | }); 338 | }, 339 | 340 | getTheMovie: function(movieID){ 341 | this.$http.get(apiURL + '/' + movieID, function(movie){ 342 | this.$set('movie', movie); 343 | console.log(JSON.stringify(movie)); 344 | }) 345 | } 346 | } 347 | })*/ -------------------------------------------------------------------------------- /js/vue-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vue-resource v0.1.17 3 | * https://github.com/vuejs/vue-resource 4 | * Released under the MIT License. 5 | */ 6 | 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.VueResource=e():t.VueResource=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){function r(t){var e=n(1)(t);t.url=n(2)(e),t.http=n(3)(e),t.resource=n(7)(e),Object.defineProperties(t.prototype,{$url:{get:function(){return e.options(t.url,this,this.$options.url)}},$http:{get:function(){return e.options(t.http,this,this.$options.http)}},$resource:{get:function(){return t.resource.bind(this)}}})}window.Vue&&Vue.use(r),t.exports=r},function(t,e){t.exports=function(t){function e(t,r,o){for(var a in r)o&&(n.isPlainObject(r[a])||n.isArray(r[a]))?(n.isPlainObject(r[a])&&!n.isPlainObject(t[a])&&(t[a]={}),n.isArray(r[a])&&!n.isArray(t[a])&&(t[a]=[]),e(t[a],r[a],o)):void 0!==r[a]&&(t[a]=r[a])}var n=t.util.extend({},t.util);return n.isString=function(t){return"string"==typeof t},n.isFunction=function(t){return"function"==typeof t},n.options=function(t,e,r){return r=r||{},n.isFunction(r)&&(r=r.call(e)),n.extend(t.bind({vm:e,options:r}),t,{options:r})},n.each=function(t,e){var r,o;if("number"==typeof t.length)for(r=0;r=200&&a.status<300),(a.ok?n:r)(a)};a.onload=o,a.onabort=o,a.onerror=o,a.send(e.data)})}},function(t,e){function n(t){this.state=a,this.value=void 0,this.deferred=[];var e=this;try{t(function(t){e.resolve(t)},function(t){e.reject(t)})}catch(n){e.reject(n)}}var r=0,o=1,a=2;n.reject=function(t){return new n(function(e,n){n(t)})},n.resolve=function(t){return new n(function(e,n){e(t)})},n.all=function(t){return new n(function(e,n){function r(n){return function(r){a[n]=r,o+=1,o===t.length&&e(a)}}var o=0,a=[];0===t.length&&e(a);for(var i=0;i z`. For instance, "199" is smaller 232 | // then "200", even though "y" and "z" (which are both 9) are larger than "0" (the value 233 | // of (`b` and `c`). This is because the leading symbol, "2", is larger than the other 234 | // leading symbol, "1". 235 | // The rule is that symbols to the left carry more weight than symbols to the right 236 | // when a number is written out as a string. In the above strings, the leading digit 237 | // represents how many 100's are in the number, and it carries more weight than the middle 238 | // number which represents how many 10's are in the number. 239 | // This system of number magnitude works well for route specificity, too. A route written as 240 | // `a/b/c` will be more specific than `x/y/z` as long as `a` is more specific than 241 | // `x`, irrespective of the other parts. 242 | // Because of this similarity, we assign each type of segment a number value written as a 243 | // string. We can find the specificity of compound routes by concatenating these strings 244 | // together, from left to right. After we have looped through all of the segments, 245 | // we convert the string to a number. 246 | specificity.val = ''; 247 | 248 | for (var i = 0, l = segments.length; i < l; i++) { 249 | var segment = segments[i], 250 | match; 251 | 252 | if (match = segment.match(/^:([^\/]+)$/)) { 253 | results.push(new DynamicSegment(match[1])); 254 | names.push(match[1]); 255 | specificity.val += '3'; 256 | } else if (match = segment.match(/^\*([^\/]+)$/)) { 257 | results.push(new StarSegment(match[1])); 258 | specificity.val += '2'; 259 | names.push(match[1]); 260 | } else if (segment === "") { 261 | results.push(new EpsilonSegment()); 262 | specificity.val += '1'; 263 | } else { 264 | results.push(new StaticSegment(segment)); 265 | specificity.val += '4'; 266 | } 267 | } 268 | 269 | specificity.val = +specificity.val; 270 | 271 | return results; 272 | } 273 | 274 | // A State has a character specification and (`charSpec`) and a list of possible 275 | // subsequent states (`nextStates`). 276 | // 277 | // If a State is an accepting state, it will also have several additional 278 | // properties: 279 | // 280 | // * `regex`: A regular expression that is used to extract parameters from paths 281 | // that reached this accepting state. 282 | // * `handlers`: Information on how to convert the list of captures into calls 283 | // to registered handlers with the specified parameters 284 | // * `types`: How many static, dynamic or star segments in this route. Used to 285 | // decide which route to use if multiple registered routes match a path. 286 | // 287 | // Currently, State is implemented naively by looping over `nextStates` and 288 | // comparing a character specification against a character. A more efficient 289 | // implementation would use a hash of keys pointing at one or more next states. 290 | 291 | function State(charSpec) { 292 | this.charSpec = charSpec; 293 | this.nextStates = []; 294 | } 295 | 296 | State.prototype = { 297 | get: function get(charSpec) { 298 | var nextStates = this.nextStates; 299 | 300 | for (var i = 0, l = nextStates.length; i < l; i++) { 301 | var child = nextStates[i]; 302 | 303 | var isEqual = child.charSpec.validChars === charSpec.validChars; 304 | isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars; 305 | 306 | if (isEqual) { 307 | return child; 308 | } 309 | } 310 | }, 311 | 312 | put: function put(charSpec) { 313 | var state; 314 | 315 | // If the character specification already exists in a child of the current 316 | // state, just return that state. 317 | if (state = this.get(charSpec)) { 318 | return state; 319 | } 320 | 321 | // Make a new state for the character spec 322 | state = new State(charSpec); 323 | 324 | // Insert the new state as a child of the current state 325 | this.nextStates.push(state); 326 | 327 | // If this character specification repeats, insert the new state as a child 328 | // of itself. Note that this will not trigger an infinite loop because each 329 | // transition during recognition consumes a character. 330 | if (charSpec.repeat) { 331 | state.nextStates.push(state); 332 | } 333 | 334 | // Return the new state 335 | return state; 336 | }, 337 | 338 | // Find a list of child states matching the next character 339 | match: function match(ch) { 340 | // DEBUG "Processing `" + ch + "`:" 341 | var nextStates = this.nextStates, 342 | child, 343 | charSpec, 344 | chars; 345 | 346 | // DEBUG " " + debugState(this) 347 | var returned = []; 348 | 349 | for (var i = 0, l = nextStates.length; i < l; i++) { 350 | child = nextStates[i]; 351 | 352 | charSpec = child.charSpec; 353 | 354 | if (typeof (chars = charSpec.validChars) !== 'undefined') { 355 | if (chars.indexOf(ch) !== -1) { 356 | returned.push(child); 357 | } 358 | } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') { 359 | if (chars.indexOf(ch) === -1) { 360 | returned.push(child); 361 | } 362 | } 363 | } 364 | 365 | return returned; 366 | } 367 | 368 | /** IF DEBUG 369 | , debug: function() { 370 | var charSpec = this.charSpec, 371 | debug = "[", 372 | chars = charSpec.validChars || charSpec.invalidChars; 373 | if (charSpec.invalidChars) { debug += "^"; } 374 | debug += chars; 375 | debug += "]"; 376 | if (charSpec.repeat) { debug += "+"; } 377 | return debug; 378 | } 379 | END IF **/ 380 | }; 381 | 382 | /** IF DEBUG 383 | function debug(log) { 384 | console.log(log); 385 | } 386 | 387 | function debugState(state) { 388 | return state.nextStates.map(function(n) { 389 | if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; } 390 | return "( " + n.debug() + " " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )"; 391 | }).join(", ") 392 | } 393 | END IF **/ 394 | 395 | // Sort the routes by specificity 396 | function sortSolutions(states) { 397 | return states.sort(function (a, b) { 398 | return b.specificity.val - a.specificity.val; 399 | }); 400 | } 401 | 402 | function recognizeChar(states, ch) { 403 | var nextStates = []; 404 | 405 | for (var i = 0, l = states.length; i < l; i++) { 406 | var state = states[i]; 407 | 408 | nextStates = nextStates.concat(state.match(ch)); 409 | } 410 | 411 | return nextStates; 412 | } 413 | 414 | var oCreate = Object.create || function (proto) { 415 | function F() {} 416 | F.prototype = proto; 417 | return new F(); 418 | }; 419 | 420 | function RecognizeResults(queryParams) { 421 | this.queryParams = queryParams || {}; 422 | } 423 | RecognizeResults.prototype = oCreate({ 424 | splice: Array.prototype.splice, 425 | slice: Array.prototype.slice, 426 | push: Array.prototype.push, 427 | length: 0, 428 | queryParams: null 429 | }); 430 | 431 | function findHandler(state, path, queryParams) { 432 | var handlers = state.handlers, 433 | regex = state.regex; 434 | var captures = path.match(regex), 435 | currentCapture = 1; 436 | var result = new RecognizeResults(queryParams); 437 | 438 | for (var i = 0, l = handlers.length; i < l; i++) { 439 | var handler = handlers[i], 440 | names = handler.names, 441 | params = {}; 442 | 443 | for (var j = 0, m = names.length; j < m; j++) { 444 | params[names[j]] = captures[currentCapture++]; 445 | } 446 | 447 | result.push({ handler: handler.handler, params: params, isDynamic: !!names.length }); 448 | } 449 | 450 | return result; 451 | } 452 | 453 | function addSegment(currentState, segment) { 454 | segment.eachChar(function (ch) { 455 | var state; 456 | 457 | currentState = currentState.put(ch); 458 | }); 459 | 460 | return currentState; 461 | } 462 | 463 | function decodeQueryParamPart(part) { 464 | // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 465 | part = part.replace(/\+/gm, '%20'); 466 | return decodeURIComponent(part); 467 | } 468 | 469 | // The main interface 470 | 471 | var RouteRecognizer = function RouteRecognizer() { 472 | this.rootState = new State(); 473 | this.names = {}; 474 | }; 475 | 476 | RouteRecognizer.prototype = { 477 | add: function add(routes, options) { 478 | var currentState = this.rootState, 479 | regex = "^", 480 | specificity = {}, 481 | handlers = [], 482 | allSegments = [], 483 | name; 484 | 485 | var isEmpty = true; 486 | 487 | for (var i = 0, l = routes.length; i < l; i++) { 488 | var route = routes[i], 489 | names = []; 490 | 491 | var segments = parse(route.path, names, specificity); 492 | 493 | allSegments = allSegments.concat(segments); 494 | 495 | for (var j = 0, m = segments.length; j < m; j++) { 496 | var segment = segments[j]; 497 | 498 | if (segment instanceof EpsilonSegment) { 499 | continue; 500 | } 501 | 502 | isEmpty = false; 503 | 504 | // Add a "/" for the new segment 505 | currentState = currentState.put({ validChars: "/" }); 506 | regex += "/"; 507 | 508 | // Add a representation of the segment to the NFA and regex 509 | currentState = addSegment(currentState, segment); 510 | regex += segment.regex(); 511 | } 512 | 513 | var handler = { handler: route.handler, names: names }; 514 | handlers.push(handler); 515 | } 516 | 517 | if (isEmpty) { 518 | currentState = currentState.put({ validChars: "/" }); 519 | regex += "/"; 520 | } 521 | 522 | currentState.handlers = handlers; 523 | currentState.regex = new RegExp(regex + "$"); 524 | currentState.specificity = specificity; 525 | 526 | if (name = options && options.as) { 527 | this.names[name] = { 528 | segments: allSegments, 529 | handlers: handlers 530 | }; 531 | } 532 | }, 533 | 534 | handlersFor: function handlersFor(name) { 535 | var route = this.names[name], 536 | result = []; 537 | if (!route) { 538 | throw new Error("There is no route named " + name); 539 | } 540 | 541 | for (var i = 0, l = route.handlers.length; i < l; i++) { 542 | result.push(route.handlers[i]); 543 | } 544 | 545 | return result; 546 | }, 547 | 548 | hasRoute: function hasRoute(name) { 549 | return !!this.names[name]; 550 | }, 551 | 552 | generate: function generate(name, params) { 553 | var route = this.names[name], 554 | output = ""; 555 | if (!route) { 556 | throw new Error("There is no route named " + name); 557 | } 558 | 559 | var segments = route.segments; 560 | 561 | for (var i = 0, l = segments.length; i < l; i++) { 562 | var segment = segments[i]; 563 | 564 | if (segment instanceof EpsilonSegment) { 565 | continue; 566 | } 567 | 568 | output += "/"; 569 | output += segment.generate(params); 570 | } 571 | 572 | if (output.charAt(0) !== '/') { 573 | output = '/' + output; 574 | } 575 | 576 | if (params && params.queryParams) { 577 | output += this.generateQueryString(params.queryParams); 578 | } 579 | 580 | return output; 581 | }, 582 | 583 | generateQueryString: function generateQueryString(params) { 584 | var pairs = []; 585 | var keys = []; 586 | for (var key in params) { 587 | if (params.hasOwnProperty(key)) { 588 | keys.push(key); 589 | } 590 | } 591 | keys.sort(); 592 | for (var i = 0, len = keys.length; i < len; i++) { 593 | key = keys[i]; 594 | var value = params[key]; 595 | if (value == null) { 596 | continue; 597 | } 598 | var pair = encodeURIComponent(key); 599 | if (isArray(value)) { 600 | for (var j = 0, l = value.length; j < l; j++) { 601 | var arrayPair = key + '[]' + '=' + encodeURIComponent(value[j]); 602 | pairs.push(arrayPair); 603 | } 604 | } else { 605 | pair += "=" + encodeURIComponent(value); 606 | pairs.push(pair); 607 | } 608 | } 609 | 610 | if (pairs.length === 0) { 611 | return ''; 612 | } 613 | 614 | return "?" + pairs.join("&"); 615 | }, 616 | 617 | parseQueryString: function parseQueryString(queryString) { 618 | var pairs = queryString.split("&"), 619 | queryParams = {}; 620 | for (var i = 0; i < pairs.length; i++) { 621 | var pair = pairs[i].split('='), 622 | key = decodeQueryParamPart(pair[0]), 623 | keyLength = key.length, 624 | isArray = false, 625 | value; 626 | if (pair.length === 1) { 627 | value = 'true'; 628 | } else { 629 | //Handle arrays 630 | if (keyLength > 2 && key.slice(keyLength - 2) === '[]') { 631 | isArray = true; 632 | key = key.slice(0, keyLength - 2); 633 | if (!queryParams[key]) { 634 | queryParams[key] = []; 635 | } 636 | } 637 | value = pair[1] ? decodeQueryParamPart(pair[1]) : ''; 638 | } 639 | if (isArray) { 640 | queryParams[key].push(value); 641 | } else { 642 | queryParams[key] = value; 643 | } 644 | } 645 | return queryParams; 646 | }, 647 | 648 | recognize: function recognize(path) { 649 | var states = [this.rootState], 650 | pathLen, 651 | i, 652 | l, 653 | queryStart, 654 | queryParams = {}, 655 | isSlashDropped = false; 656 | 657 | queryStart = path.indexOf('?'); 658 | if (queryStart !== -1) { 659 | var queryString = path.substr(queryStart + 1, path.length); 660 | path = path.substr(0, queryStart); 661 | queryParams = this.parseQueryString(queryString); 662 | } 663 | 664 | path = decodeURI(path); 665 | 666 | // DEBUG GROUP path 667 | 668 | if (path.charAt(0) !== "/") { 669 | path = "/" + path; 670 | } 671 | 672 | pathLen = path.length; 673 | if (pathLen > 1 && path.charAt(pathLen - 1) === "/") { 674 | path = path.substr(0, pathLen - 1); 675 | isSlashDropped = true; 676 | } 677 | 678 | for (i = 0, l = path.length; i < l; i++) { 679 | states = recognizeChar(states, path.charAt(i)); 680 | if (!states.length) { 681 | break; 682 | } 683 | } 684 | 685 | // END DEBUG GROUP 686 | 687 | var solutions = []; 688 | for (i = 0, l = states.length; i < l; i++) { 689 | if (states[i].handlers) { 690 | solutions.push(states[i]); 691 | } 692 | } 693 | 694 | states = sortSolutions(solutions); 695 | 696 | var state = solutions[0]; 697 | 698 | if (state && state.handlers) { 699 | // if a trailing slash was dropped and a star segment is the last segment 700 | // specified, put the trailing slash back 701 | if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") { 702 | path = path + "/"; 703 | } 704 | return findHandler(state, path, queryParams); 705 | } 706 | } 707 | }; 708 | 709 | RouteRecognizer.prototype.map = map; 710 | 711 | RouteRecognizer.VERSION = '0.1.9'; 712 | 713 | var genQuery = RouteRecognizer.prototype.generateQueryString; 714 | 715 | // export default for holding the Vue reference 716 | var exports$1 = {}; 717 | /** 718 | * Warn stuff. 719 | * 720 | * @param {String} msg 721 | */ 722 | 723 | function warn(msg) { 724 | /* istanbul ignore next */ 725 | if (window.console) { 726 | console.warn('[vue-router] ' + msg); 727 | /* istanbul ignore if */ 728 | if (!exports$1.Vue || exports$1.Vue.config.debug) { 729 | console.warn(new Error('warning stack trace:').stack); 730 | } 731 | } 732 | } 733 | 734 | /** 735 | * Resolve a relative path. 736 | * 737 | * @param {String} base 738 | * @param {String} relative 739 | * @param {Boolean} append 740 | * @return {String} 741 | */ 742 | 743 | function resolvePath(base, relative, append) { 744 | var query = base.match(/(\?.*)$/); 745 | if (query) { 746 | query = query[1]; 747 | base = base.slice(0, -query.length); 748 | } 749 | // a query! 750 | if (relative.charAt(0) === '?') { 751 | return base + relative; 752 | } 753 | var stack = base.split('/'); 754 | // remove trailing segment if: 755 | // - not appending 756 | // - appending to trailing slash (last segment is empty) 757 | if (!append || !stack[stack.length - 1]) { 758 | stack.pop(); 759 | } 760 | // resolve relative path 761 | var segments = relative.replace(/^\//, '').split('/'); 762 | for (var i = 0; i < segments.length; i++) { 763 | var segment = segments[i]; 764 | if (segment === '.') { 765 | continue; 766 | } else if (segment === '..') { 767 | stack.pop(); 768 | } else { 769 | stack.push(segment); 770 | } 771 | } 772 | // ensure leading slash 773 | if (stack[0] !== '') { 774 | stack.unshift(''); 775 | } 776 | return stack.join('/'); 777 | } 778 | 779 | /** 780 | * Forgiving check for a promise 781 | * 782 | * @param {Object} p 783 | * @return {Boolean} 784 | */ 785 | 786 | function isPromise(p) { 787 | return p && typeof p.then === 'function'; 788 | } 789 | 790 | /** 791 | * Retrive a route config field from a component instance 792 | * OR a component contructor. 793 | * 794 | * @param {Function|Vue} component 795 | * @param {String} name 796 | * @return {*} 797 | */ 798 | 799 | function getRouteConfig(component, name) { 800 | var options = component && (component.$options || component.options); 801 | return options && options.route && options.route[name]; 802 | } 803 | 804 | /** 805 | * Resolve an async component factory. Have to do a dirty 806 | * mock here because of Vue core's internal API depends on 807 | * an ID check. 808 | * 809 | * @param {Object} handler 810 | * @param {Function} cb 811 | */ 812 | 813 | var resolver = undefined; 814 | 815 | function resolveAsyncComponent(handler, cb) { 816 | if (!resolver) { 817 | resolver = { 818 | resolve: exports$1.Vue.prototype._resolveComponent, 819 | $options: { 820 | components: { 821 | _: handler.component 822 | } 823 | } 824 | }; 825 | } else { 826 | resolver.$options.components._ = handler.component; 827 | } 828 | resolver.resolve('_', function (Component) { 829 | handler.component = Component; 830 | cb(Component); 831 | }); 832 | } 833 | 834 | /** 835 | * Map the dynamic segments in a path to params. 836 | * 837 | * @param {String} path 838 | * @param {Object} params 839 | * @param {Object} query 840 | */ 841 | 842 | function mapParams(path, params, query) { 843 | if (params === undefined) params = {}; 844 | 845 | path = path.replace(/:([^\/]+)/g, function (_, key) { 846 | var val = params[key]; 847 | if (!val) { 848 | warn('param "' + key + '" not found when generating ' + 'path for "' + path + '" with params ' + JSON.stringify(params)); 849 | } 850 | return val || ''; 851 | }); 852 | if (query) { 853 | path += genQuery(query); 854 | } 855 | return path; 856 | } 857 | 858 | var hashRE = /#.*$/; 859 | 860 | var HTML5History = (function () { 861 | function HTML5History(_ref) { 862 | var root = _ref.root; 863 | var onChange = _ref.onChange; 864 | babelHelpers.classCallCheck(this, HTML5History); 865 | 866 | if (root) { 867 | // make sure there's the starting slash 868 | if (root.charAt(0) !== '/') { 869 | root = '/' + root; 870 | } 871 | // remove trailing slash 872 | this.root = root.replace(/\/$/, ''); 873 | this.rootRE = new RegExp('^\\' + this.root); 874 | } else { 875 | this.root = null; 876 | } 877 | this.onChange = onChange; 878 | // check base tag 879 | var baseEl = document.querySelector('base'); 880 | this.base = baseEl && baseEl.getAttribute('href'); 881 | } 882 | 883 | HTML5History.prototype.start = function start() { 884 | var _this = this; 885 | 886 | this.listener = function (e) { 887 | var url = decodeURI(location.pathname + location.search); 888 | if (_this.root) { 889 | url = url.replace(_this.rootRE, ''); 890 | } 891 | _this.onChange(url, e && e.state, location.hash); 892 | }; 893 | window.addEventListener('popstate', this.listener); 894 | this.listener(); 895 | }; 896 | 897 | HTML5History.prototype.stop = function stop() { 898 | window.removeEventListener('popstate', this.listener); 899 | }; 900 | 901 | HTML5History.prototype.go = function go(path, replace, append) { 902 | var url = this.formatPath(path, append); 903 | if (replace) { 904 | history.replaceState({}, '', url); 905 | } else { 906 | // record scroll position by replacing current state 907 | history.replaceState({ 908 | pos: { 909 | x: window.pageXOffset, 910 | y: window.pageYOffset 911 | } 912 | }, ''); 913 | // then push new state 914 | history.pushState({}, '', url); 915 | } 916 | var hashMatch = path.match(hashRE); 917 | var hash = hashMatch && hashMatch[0]; 918 | path = url 919 | // strip hash so it doesn't mess up params 920 | .replace(hashRE, '') 921 | // remove root before matching 922 | .replace(this.rootRE, ''); 923 | this.onChange(path, null, hash); 924 | }; 925 | 926 | HTML5History.prototype.formatPath = function formatPath(path, append) { 927 | return path.charAt(0) === '/' 928 | // absolute path 929 | ? this.root ? this.root + '/' + path.replace(/^\//, '') : path : resolvePath(this.base || location.pathname, path, append); 930 | }; 931 | 932 | return HTML5History; 933 | })(); 934 | 935 | var HashHistory = (function () { 936 | function HashHistory(_ref) { 937 | var hashbang = _ref.hashbang; 938 | var onChange = _ref.onChange; 939 | babelHelpers.classCallCheck(this, HashHistory); 940 | 941 | this.hashbang = hashbang; 942 | this.onChange = onChange; 943 | } 944 | 945 | HashHistory.prototype.start = function start() { 946 | var self = this; 947 | this.listener = function () { 948 | var path = location.hash; 949 | var raw = path.replace(/^#!?/, ''); 950 | // always 951 | if (raw.charAt(0) !== '/') { 952 | raw = '/' + raw; 953 | } 954 | var formattedPath = self.formatPath(raw); 955 | if (formattedPath !== path) { 956 | location.replace(formattedPath); 957 | return; 958 | } 959 | // determine query 960 | // note it's possible to have queries in both the actual URL 961 | // and the hash fragment itself. 962 | var query = location.search && path.indexOf('?') > -1 ? '&' + location.search.slice(1) : location.search; 963 | self.onChange(decodeURI(path.replace(/^#!?/, '') + query)); 964 | }; 965 | window.addEventListener('hashchange', this.listener); 966 | this.listener(); 967 | }; 968 | 969 | HashHistory.prototype.stop = function stop() { 970 | window.removeEventListener('hashchange', this.listener); 971 | }; 972 | 973 | HashHistory.prototype.go = function go(path, replace, append) { 974 | path = this.formatPath(path, append); 975 | if (replace) { 976 | location.replace(path); 977 | } else { 978 | location.hash = path; 979 | } 980 | }; 981 | 982 | HashHistory.prototype.formatPath = function formatPath(path, append) { 983 | var isAbsoloute = path.charAt(0) === '/'; 984 | var prefix = '#' + (this.hashbang ? '!' : ''); 985 | return isAbsoloute ? prefix + path : prefix + resolvePath(location.hash.replace(/^#!?/, ''), path, append); 986 | }; 987 | 988 | return HashHistory; 989 | })(); 990 | 991 | var AbstractHistory = (function () { 992 | function AbstractHistory(_ref) { 993 | var onChange = _ref.onChange; 994 | babelHelpers.classCallCheck(this, AbstractHistory); 995 | 996 | this.onChange = onChange; 997 | this.currentPath = '/'; 998 | } 999 | 1000 | AbstractHistory.prototype.start = function start() { 1001 | this.onChange('/'); 1002 | }; 1003 | 1004 | AbstractHistory.prototype.stop = function stop() { 1005 | // noop 1006 | }; 1007 | 1008 | AbstractHistory.prototype.go = function go(path, replace, append) { 1009 | path = this.currentPath = this.formatPath(path, append); 1010 | this.onChange(path); 1011 | }; 1012 | 1013 | AbstractHistory.prototype.formatPath = function formatPath(path, append) { 1014 | return path.charAt(0) === '/' ? path : resolvePath(this.currentPath, path, append); 1015 | }; 1016 | 1017 | return AbstractHistory; 1018 | })(); 1019 | 1020 | /** 1021 | * Determine the reusability of an existing router view. 1022 | * 1023 | * @param {Directive} view 1024 | * @param {Object} handler 1025 | * @param {Transition} transition 1026 | */ 1027 | 1028 | function canReuse(view, handler, transition) { 1029 | var component = view.childVM; 1030 | if (!component || !handler) { 1031 | return false; 1032 | } 1033 | // important: check view.Component here because it may 1034 | // have been changed in activate hook 1035 | if (view.Component !== handler.component) { 1036 | return false; 1037 | } 1038 | var canReuseFn = getRouteConfig(component, 'canReuse'); 1039 | return typeof canReuseFn === 'boolean' ? canReuseFn : canReuseFn ? canReuseFn.call(component, { 1040 | to: transition.to, 1041 | from: transition.from 1042 | }) : true; // defaults to true 1043 | } 1044 | 1045 | /** 1046 | * Check if a component can deactivate. 1047 | * 1048 | * @param {Directive} view 1049 | * @param {Transition} transition 1050 | * @param {Function} next 1051 | */ 1052 | 1053 | function canDeactivate(view, transition, next) { 1054 | var fromComponent = view.childVM; 1055 | var hook = getRouteConfig(fromComponent, 'canDeactivate'); 1056 | if (!hook) { 1057 | next(); 1058 | } else { 1059 | transition.callHook(hook, fromComponent, next, { 1060 | expectBoolean: true 1061 | }); 1062 | } 1063 | } 1064 | 1065 | /** 1066 | * Check if a component can activate. 1067 | * 1068 | * @param {Object} handler 1069 | * @param {Transition} transition 1070 | * @param {Function} next 1071 | */ 1072 | 1073 | function canActivate(handler, transition, next) { 1074 | resolveAsyncComponent(handler, function (Component) { 1075 | // have to check due to async-ness 1076 | if (transition.aborted) { 1077 | return; 1078 | } 1079 | // determine if this component can be activated 1080 | var hook = getRouteConfig(Component, 'canActivate'); 1081 | if (!hook) { 1082 | next(); 1083 | } else { 1084 | transition.callHook(hook, null, next, { 1085 | expectBoolean: true 1086 | }); 1087 | } 1088 | }); 1089 | } 1090 | 1091 | /** 1092 | * Call deactivate hooks for existing router-views. 1093 | * 1094 | * @param {Directive} view 1095 | * @param {Transition} transition 1096 | * @param {Function} next 1097 | */ 1098 | 1099 | function deactivate(view, transition, next) { 1100 | var component = view.childVM; 1101 | var hook = getRouteConfig(component, 'deactivate'); 1102 | if (!hook) { 1103 | next(); 1104 | } else { 1105 | transition.callHooks(hook, component, next); 1106 | } 1107 | } 1108 | 1109 | /** 1110 | * Activate / switch component for a router-view. 1111 | * 1112 | * @param {Directive} view 1113 | * @param {Transition} transition 1114 | * @param {Number} depth 1115 | * @param {Function} [cb] 1116 | */ 1117 | 1118 | function activate(view, transition, depth, cb, reuse) { 1119 | var handler = transition.activateQueue[depth]; 1120 | if (!handler) { 1121 | // fix 1.0.0-alpha.3 compat 1122 | if (view._bound) { 1123 | view.setComponent(null); 1124 | } 1125 | cb && cb(); 1126 | return; 1127 | } 1128 | 1129 | var Component = view.Component = handler.component; 1130 | var activateHook = getRouteConfig(Component, 'activate'); 1131 | var dataHook = getRouteConfig(Component, 'data'); 1132 | var waitForData = getRouteConfig(Component, 'waitForData'); 1133 | 1134 | view.depth = depth; 1135 | view.activated = false; 1136 | 1137 | var component = undefined; 1138 | var loading = !!(dataHook && !waitForData); 1139 | 1140 | // "reuse" is a flag passed down when the parent view is 1141 | // either reused via keep-alive or as a child of a kept-alive view. 1142 | // of course we can only reuse if the current kept-alive instance 1143 | // is of the correct type. 1144 | reuse = reuse && view.childVM && view.childVM.constructor === Component; 1145 | 1146 | if (reuse) { 1147 | // just reuse 1148 | component = view.childVM; 1149 | component.$loadingRouteData = loading; 1150 | } else { 1151 | // unbuild current component. this step also destroys 1152 | // and removes all nested child views. 1153 | view.unbuild(true); 1154 | // handle keep-alive. 1155 | // if the view has keep-alive, the child vm is not actually 1156 | // destroyed - its nested views will still be in router's 1157 | // view list. We need to removed these child views and 1158 | // cache them on the child vm. 1159 | if (view.keepAlive) { 1160 | var views = transition.router._views; 1161 | var i = views.indexOf(view); 1162 | if (i > 0) { 1163 | transition.router._views = views.slice(i); 1164 | if (view.childVM) { 1165 | view.childVM._routerViews = views.slice(0, i); 1166 | } 1167 | } 1168 | } 1169 | 1170 | // build the new component. this will also create the 1171 | // direct child view of the current one. it will register 1172 | // itself as view.childView. 1173 | component = view.build({ 1174 | _meta: { 1175 | $loadingRouteData: loading 1176 | } 1177 | }); 1178 | // handle keep-alive. 1179 | // when a kept-alive child vm is restored, we need to 1180 | // add its cached child views into the router's view list, 1181 | // and also properly update current view's child view. 1182 | if (view.keepAlive) { 1183 | component.$loadingRouteData = loading; 1184 | var cachedViews = component._routerViews; 1185 | if (cachedViews) { 1186 | transition.router._views = cachedViews.concat(transition.router._views); 1187 | view.childView = cachedViews[cachedViews.length - 1]; 1188 | component._routerViews = null; 1189 | } 1190 | } 1191 | } 1192 | 1193 | // cleanup the component in case the transition is aborted 1194 | // before the component is ever inserted. 1195 | var cleanup = function cleanup() { 1196 | component.$destroy(); 1197 | }; 1198 | 1199 | // actually insert the component and trigger transition 1200 | var insert = function insert() { 1201 | if (reuse) { 1202 | cb && cb(); 1203 | return; 1204 | } 1205 | var router = transition.router; 1206 | if (router._rendered || router._transitionOnLoad) { 1207 | view.transition(component); 1208 | } else { 1209 | // no transition on first render, manual transition 1210 | /* istanbul ignore if */ 1211 | if (view.setCurrent) { 1212 | // 0.12 compat 1213 | view.setCurrent(component); 1214 | } else { 1215 | // 1.0 1216 | view.childVM = component; 1217 | } 1218 | component.$before(view.anchor, null, false); 1219 | } 1220 | cb && cb(); 1221 | }; 1222 | 1223 | // called after activation hook is resolved 1224 | var afterActivate = function afterActivate() { 1225 | view.activated = true; 1226 | // activate the child view 1227 | if (view.childView) { 1228 | activate(view.childView, transition, depth + 1, null, reuse || view.keepAlive); 1229 | } 1230 | if (dataHook && waitForData) { 1231 | // wait until data loaded to insert 1232 | loadData(component, transition, dataHook, insert, cleanup); 1233 | } else { 1234 | // load data and insert at the same time 1235 | if (dataHook) { 1236 | loadData(component, transition, dataHook); 1237 | } 1238 | insert(); 1239 | } 1240 | }; 1241 | 1242 | if (activateHook) { 1243 | transition.callHooks(activateHook, component, afterActivate, { 1244 | cleanup: cleanup 1245 | }); 1246 | } else { 1247 | afterActivate(); 1248 | } 1249 | } 1250 | 1251 | /** 1252 | * Reuse a view, just reload data if necessary. 1253 | * 1254 | * @param {Directive} view 1255 | * @param {Transition} transition 1256 | */ 1257 | 1258 | function reuse(view, transition) { 1259 | var component = view.childVM; 1260 | var dataHook = getRouteConfig(component, 'data'); 1261 | if (dataHook) { 1262 | loadData(component, transition, dataHook); 1263 | } 1264 | } 1265 | 1266 | /** 1267 | * Asynchronously load and apply data to component. 1268 | * 1269 | * @param {Vue} component 1270 | * @param {Transition} transition 1271 | * @param {Function} hook 1272 | * @param {Function} cb 1273 | * @param {Function} cleanup 1274 | */ 1275 | 1276 | function loadData(component, transition, hook, cb, cleanup) { 1277 | component.$loadingRouteData = true; 1278 | transition.callHooks(hook, component, function (data, onError) { 1279 | // merge data from multiple data hooks 1280 | if (Array.isArray(data) && data._needMerge) { 1281 | data = data.reduce(function (res, obj) { 1282 | if (isPlainObject(obj)) { 1283 | Object.keys(obj).forEach(function (key) { 1284 | res[key] = obj[key]; 1285 | }); 1286 | } 1287 | return res; 1288 | }, Object.create(null)); 1289 | } 1290 | // handle promise sugar syntax 1291 | var promises = []; 1292 | if (isPlainObject(data)) { 1293 | Object.keys(data).forEach(function (key) { 1294 | var val = data[key]; 1295 | if (isPromise(val)) { 1296 | promises.push(val.then(function (resolvedVal) { 1297 | component.$set(key, resolvedVal); 1298 | })); 1299 | } else { 1300 | component.$set(key, val); 1301 | } 1302 | }); 1303 | } 1304 | if (!promises.length) { 1305 | component.$loadingRouteData = false; 1306 | cb && cb(); 1307 | } else { 1308 | promises[0].constructor.all(promises).then(function (_) { 1309 | component.$loadingRouteData = false; 1310 | cb && cb(); 1311 | }, onError); 1312 | } 1313 | }, { 1314 | cleanup: cleanup, 1315 | expectData: true 1316 | }); 1317 | } 1318 | 1319 | function isPlainObject(obj) { 1320 | return Object.prototype.toString.call(obj) === '[object Object]'; 1321 | } 1322 | 1323 | /** 1324 | * A RouteTransition object manages the pipeline of a 1325 | * router-view switching process. This is also the object 1326 | * passed into user route hooks. 1327 | * 1328 | * @param {Router} router 1329 | * @param {Route} to 1330 | * @param {Route} from 1331 | */ 1332 | 1333 | var RouteTransition = (function () { 1334 | function RouteTransition(router, to, from) { 1335 | babelHelpers.classCallCheck(this, RouteTransition); 1336 | 1337 | this.router = router; 1338 | this.to = to; 1339 | this.from = from; 1340 | this.next = null; 1341 | this.aborted = false; 1342 | this.done = false; 1343 | 1344 | // start by determine the queues 1345 | 1346 | // the deactivate queue is an array of router-view 1347 | // directive instances that need to be deactivated, 1348 | // deepest first. 1349 | this.deactivateQueue = router._views; 1350 | 1351 | // check the default handler of the deepest match 1352 | var matched = to.matched ? Array.prototype.slice.call(to.matched) : []; 1353 | 1354 | // the activate queue is an array of route handlers 1355 | // that need to be activated 1356 | this.activateQueue = matched.map(function (match) { 1357 | return match.handler; 1358 | }); 1359 | } 1360 | 1361 | /** 1362 | * Abort current transition and return to previous location. 1363 | */ 1364 | 1365 | RouteTransition.prototype.abort = function abort() { 1366 | if (!this.aborted) { 1367 | this.aborted = true; 1368 | // if the root path throws an error during validation 1369 | // on initial load, it gets caught in an infinite loop. 1370 | var abortingOnLoad = !this.from.path && this.to.path === '/'; 1371 | if (!abortingOnLoad) { 1372 | this.router.replace(this.from.path || '/'); 1373 | } 1374 | } 1375 | }; 1376 | 1377 | /** 1378 | * Abort current transition and redirect to a new location. 1379 | * 1380 | * @param {String} path 1381 | */ 1382 | 1383 | RouteTransition.prototype.redirect = function redirect(path) { 1384 | if (!this.aborted) { 1385 | this.aborted = true; 1386 | if (typeof path === 'string') { 1387 | path = mapParams(path, this.to.params, this.to.query); 1388 | } else { 1389 | path.params = path.params || this.to.params; 1390 | path.query = path.query || this.to.query; 1391 | } 1392 | this.router.replace(path); 1393 | } 1394 | }; 1395 | 1396 | /** 1397 | * A router view transition's pipeline can be described as 1398 | * follows, assuming we are transitioning from an existing 1399 | * chain [Component A, Component B] to a new 1400 | * chain [Component A, Component C]: 1401 | * 1402 | * A A 1403 | * | => | 1404 | * B C 1405 | * 1406 | * 1. Reusablity phase: 1407 | * -> canReuse(A, A) 1408 | * -> canReuse(B, C) 1409 | * -> determine new queues: 1410 | * - deactivation: [B] 1411 | * - activation: [C] 1412 | * 1413 | * 2. Validation phase: 1414 | * -> canDeactivate(B) 1415 | * -> canActivate(C) 1416 | * 1417 | * 3. Activation phase: 1418 | * -> deactivate(B) 1419 | * -> activate(C) 1420 | * 1421 | * Each of these steps can be asynchronous, and any 1422 | * step can potentially abort the transition. 1423 | * 1424 | * @param {Function} cb 1425 | */ 1426 | 1427 | RouteTransition.prototype.start = function start(cb) { 1428 | var transition = this; 1429 | var daq = this.deactivateQueue; 1430 | var aq = this.activateQueue; 1431 | var rdaq = daq.slice().reverse(); 1432 | var reuseQueue = undefined; 1433 | 1434 | // 1. Reusability phase 1435 | var i = undefined; 1436 | for (i = 0; i < rdaq.length; i++) { 1437 | if (!canReuse(rdaq[i], aq[i], transition)) { 1438 | break; 1439 | } 1440 | } 1441 | if (i > 0) { 1442 | reuseQueue = rdaq.slice(0, i); 1443 | daq = rdaq.slice(i).reverse(); 1444 | aq = aq.slice(i); 1445 | } 1446 | 1447 | // 2. Validation phase 1448 | transition.runQueue(daq, canDeactivate, function () { 1449 | transition.runQueue(aq, canActivate, function () { 1450 | transition.runQueue(daq, deactivate, function () { 1451 | // 3. Activation phase 1452 | 1453 | // Update router current route 1454 | transition.router._onTransitionValidated(transition); 1455 | 1456 | // trigger reuse for all reused views 1457 | reuseQueue && reuseQueue.forEach(function (view) { 1458 | reuse(view, transition); 1459 | }); 1460 | 1461 | // the root of the chain that needs to be replaced 1462 | // is the top-most non-reusable view. 1463 | if (daq.length) { 1464 | var view = daq[daq.length - 1]; 1465 | var depth = reuseQueue ? reuseQueue.length : 0; 1466 | activate(view, transition, depth, cb); 1467 | } else { 1468 | cb(); 1469 | } 1470 | }); 1471 | }); 1472 | }); 1473 | }; 1474 | 1475 | /** 1476 | * Asynchronously and sequentially apply a function to a 1477 | * queue. 1478 | * 1479 | * @param {Array} queue 1480 | * @param {Function} fn 1481 | * @param {Function} cb 1482 | */ 1483 | 1484 | RouteTransition.prototype.runQueue = function runQueue(queue, fn, cb) { 1485 | var transition = this; 1486 | step(0); 1487 | function step(index) { 1488 | if (index >= queue.length) { 1489 | cb(); 1490 | } else { 1491 | fn(queue[index], transition, function () { 1492 | step(index + 1); 1493 | }); 1494 | } 1495 | } 1496 | }; 1497 | 1498 | /** 1499 | * Call a user provided route transition hook and handle 1500 | * the response (e.g. if the user returns a promise). 1501 | * 1502 | * If the user neither expects an argument nor returns a 1503 | * promise, the hook is assumed to be synchronous. 1504 | * 1505 | * @param {Function} hook 1506 | * @param {*} [context] 1507 | * @param {Function} [cb] 1508 | * @param {Object} [options] 1509 | * - {Boolean} expectBoolean 1510 | * - {Boolean} expectData 1511 | * - {Function} cleanup 1512 | */ 1513 | 1514 | RouteTransition.prototype.callHook = function callHook(hook, context, cb) { 1515 | var _ref = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; 1516 | 1517 | var _ref$expectBoolean = _ref.expectBoolean; 1518 | var expectBoolean = _ref$expectBoolean === undefined ? false : _ref$expectBoolean; 1519 | var _ref$expectData = _ref.expectData; 1520 | var expectData = _ref$expectData === undefined ? false : _ref$expectData; 1521 | var cleanup = _ref.cleanup; 1522 | 1523 | var transition = this; 1524 | var nextCalled = false; 1525 | 1526 | // abort the transition 1527 | var abort = function abort() { 1528 | cleanup && cleanup(); 1529 | transition.abort(); 1530 | }; 1531 | 1532 | // handle errors 1533 | var onError = function onError(err) { 1534 | // cleanup indicates an after-activation hook, 1535 | // so instead of aborting we just let the transition 1536 | // finish. 1537 | cleanup ? next() : abort(); 1538 | if (err && !transition.router._suppress) { 1539 | warn('Uncaught error during transition: '); 1540 | throw err instanceof Error ? err : new Error(err); 1541 | } 1542 | }; 1543 | 1544 | // advance the transition to the next step 1545 | var next = function next(data) { 1546 | if (nextCalled) { 1547 | warn('transition.next() should be called only once.'); 1548 | return; 1549 | } 1550 | nextCalled = true; 1551 | if (transition.aborted) { 1552 | cleanup && cleanup(); 1553 | return; 1554 | } 1555 | cb && cb(data, onError); 1556 | }; 1557 | 1558 | // expose a clone of the transition object, so that each 1559 | // hook gets a clean copy and prevent the user from 1560 | // messing with the internals. 1561 | var exposed = { 1562 | to: transition.to, 1563 | from: transition.from, 1564 | abort: abort, 1565 | next: next, 1566 | redirect: function redirect() { 1567 | transition.redirect.apply(transition, arguments); 1568 | } 1569 | }; 1570 | 1571 | // actually call the hook 1572 | var res = undefined; 1573 | try { 1574 | res = hook.call(context, exposed); 1575 | } catch (err) { 1576 | return onError(err); 1577 | } 1578 | 1579 | // handle boolean/promise return values 1580 | var resIsPromise = isPromise(res); 1581 | if (expectBoolean) { 1582 | if (typeof res === 'boolean') { 1583 | res ? next() : abort(); 1584 | } else if (resIsPromise) { 1585 | res.then(function (ok) { 1586 | ok ? next() : abort(); 1587 | }, onError); 1588 | } else if (!hook.length) { 1589 | next(res); 1590 | } 1591 | } else if (resIsPromise) { 1592 | res.then(next, onError); 1593 | } else if (expectData && isPlainOjbect(res) || !hook.length) { 1594 | next(res); 1595 | } 1596 | }; 1597 | 1598 | /** 1599 | * Call a single hook or an array of async hooks in series. 1600 | * 1601 | * @param {Array} hooks 1602 | * @param {*} context 1603 | * @param {Function} cb 1604 | * @param {Object} [options] 1605 | */ 1606 | 1607 | RouteTransition.prototype.callHooks = function callHooks(hooks, context, cb, options) { 1608 | var _this = this; 1609 | 1610 | if (Array.isArray(hooks)) { 1611 | (function () { 1612 | var res = []; 1613 | res._needMerge = true; 1614 | var onError = undefined; 1615 | _this.runQueue(hooks, function (hook, _, next) { 1616 | if (!_this.aborted) { 1617 | _this.callHook(hook, context, function (r, onError) { 1618 | if (r) res.push(r); 1619 | onError = onError; 1620 | next(); 1621 | }, options); 1622 | } 1623 | }, function () { 1624 | cb(res, onError); 1625 | }); 1626 | })(); 1627 | } else { 1628 | this.callHook(hooks, context, cb, options); 1629 | } 1630 | }; 1631 | 1632 | return RouteTransition; 1633 | })(); 1634 | 1635 | function isPlainOjbect(val) { 1636 | return Object.prototype.toString.call(val) === '[object Object]'; 1637 | } 1638 | 1639 | var internalKeysRE = /^(component|subRoutes)$/; 1640 | 1641 | /** 1642 | * Route Context Object 1643 | * 1644 | * @param {String} path 1645 | * @param {Router} router 1646 | */ 1647 | 1648 | var Route = function Route(path, router) { 1649 | var _this = this; 1650 | 1651 | babelHelpers.classCallCheck(this, Route); 1652 | 1653 | var matched = router._recognizer.recognize(path); 1654 | if (matched) { 1655 | // copy all custom fields from route configs 1656 | [].forEach.call(matched, function (match) { 1657 | for (var key in match.handler) { 1658 | if (!internalKeysRE.test(key)) { 1659 | _this[key] = match.handler[key]; 1660 | } 1661 | } 1662 | }); 1663 | // set query and params 1664 | this.query = matched.queryParams; 1665 | this.params = [].reduce.call(matched, function (prev, cur) { 1666 | if (cur.params) { 1667 | for (var key in cur.params) { 1668 | prev[key] = cur.params[key]; 1669 | } 1670 | } 1671 | return prev; 1672 | }, {}); 1673 | } 1674 | // expose path and router 1675 | this.path = path; 1676 | this.router = router; 1677 | // for internal use 1678 | this.matched = matched || router._notFoundHandler; 1679 | // Important: freeze self to prevent observation 1680 | Object.freeze(this); 1681 | }; 1682 | 1683 | function applyOverride (Vue) { 1684 | 1685 | var _ = Vue.util; 1686 | 1687 | // override Vue's init and destroy process to keep track of router instances 1688 | var init = Vue.prototype._init; 1689 | Vue.prototype._init = function (options) { 1690 | var root = options._parent || options.parent || this; 1691 | var route = root.$route; 1692 | if (route) { 1693 | route.router._children.push(this); 1694 | if (!this.$route) { 1695 | /* istanbul ignore if */ 1696 | if (this._defineMeta) { 1697 | // 0.12 1698 | this._defineMeta('$route', route); 1699 | } else { 1700 | // 1.0 1701 | _.defineReactive(this, '$route', route); 1702 | } 1703 | } 1704 | } 1705 | init.call(this, options); 1706 | }; 1707 | 1708 | var destroy = Vue.prototype._destroy; 1709 | Vue.prototype._destroy = function () { 1710 | if (!this._isBeingDestroyed) { 1711 | var route = this.$root.$route; 1712 | if (route) { 1713 | route.router._children.$remove(this); 1714 | } 1715 | destroy.apply(this, arguments); 1716 | } 1717 | }; 1718 | 1719 | // 1.0 only: enable route mixins 1720 | var strats = Vue.config.optionMergeStrategies; 1721 | var hooksToMergeRE = /^(data|activate|deactivate)$/; 1722 | 1723 | if (strats) { 1724 | strats.route = function (parentVal, childVal) { 1725 | if (!childVal) return parentVal; 1726 | if (!parentVal) return childVal; 1727 | var ret = {}; 1728 | _.extend(ret, parentVal); 1729 | for (var key in childVal) { 1730 | var a = ret[key]; 1731 | var b = childVal[key]; 1732 | // for data, activate and deactivate, we need to merge them into 1733 | // arrays similar to lifecycle hooks. 1734 | if (a && hooksToMergeRE.test(key)) { 1735 | ret[key] = (_.isArray(a) ? a : [a]).concat(b); 1736 | } else { 1737 | ret[key] = b; 1738 | } 1739 | } 1740 | return ret; 1741 | }; 1742 | } 1743 | } 1744 | 1745 | function View (Vue) { 1746 | 1747 | var _ = Vue.util; 1748 | var componentDef = 1749 | // 0.12 1750 | Vue.directive('_component') || 1751 | // 1.0 1752 | Vue.internalDirectives.component; 1753 | // extends the internal component directive 1754 | var viewDef = _.extend({}, componentDef); 1755 | 1756 | // with some overrides 1757 | _.extend(viewDef, { 1758 | 1759 | _isRouterView: true, 1760 | 1761 | bind: function bind() { 1762 | var route = this.vm.$route; 1763 | /* istanbul ignore if */ 1764 | if (!route) { 1765 | warn(' can only be used inside a ' + 'router-enabled app.'); 1766 | return; 1767 | } 1768 | // force dynamic directive so v-component doesn't 1769 | // attempt to build right now 1770 | this._isDynamicLiteral = true; 1771 | // finally, init by delegating to v-component 1772 | componentDef.bind.call(this); 1773 | 1774 | // all we need to do here is registering this view 1775 | // in the router. actual component switching will be 1776 | // managed by the pipeline. 1777 | var router = this.router = route.router; 1778 | router._views.unshift(this); 1779 | 1780 | // note the views are in reverse order. 1781 | var parentView = router._views[1]; 1782 | if (parentView) { 1783 | // register self as a child of the parent view, 1784 | // instead of activating now. This is so that the 1785 | // child's activate hook is called after the 1786 | // parent's has resolved. 1787 | parentView.childView = this; 1788 | } 1789 | 1790 | // handle late-rendered view 1791 | // two possibilities: 1792 | // 1. root view rendered after transition has been 1793 | // validated; 1794 | // 2. child view rendered after parent view has been 1795 | // activated. 1796 | var transition = route.router._currentTransition; 1797 | if (!parentView && transition.done || parentView && parentView.activated) { 1798 | var depth = parentView ? parentView.depth + 1 : 0; 1799 | activate(this, transition, depth); 1800 | } 1801 | }, 1802 | 1803 | unbind: function unbind() { 1804 | this.router._views.$remove(this); 1805 | componentDef.unbind.call(this); 1806 | } 1807 | }); 1808 | 1809 | Vue.elementDirective('router-view', viewDef); 1810 | } 1811 | 1812 | var trailingSlashRE = /\/$/; 1813 | var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g; 1814 | var queryStringRE = /\?.*$/; 1815 | 1816 | // install v-link, which provides navigation support for 1817 | // HTML5 history mode 1818 | function Link (Vue) { 1819 | 1820 | var _ = Vue.util; 1821 | 1822 | Vue.directive('link', { 1823 | 1824 | bind: function bind() { 1825 | var _this = this; 1826 | 1827 | var vm = this.vm; 1828 | /* istanbul ignore if */ 1829 | if (!vm.$route) { 1830 | warn('v-link can only be used inside a ' + 'router-enabled app.'); 1831 | return; 1832 | } 1833 | // no need to handle click if link expects to be opened 1834 | // in a new window/tab. 1835 | /* istanbul ignore if */ 1836 | if (this.el.tagName === 'A' && this.el.getAttribute('target') === '_blank') { 1837 | return; 1838 | } 1839 | // handle click 1840 | var router = vm.$route.router; 1841 | this.handler = function (e) { 1842 | // don't redirect with control keys 1843 | if (e.metaKey || e.ctrlKey || e.shiftKey) return; 1844 | // don't redirect when preventDefault called 1845 | if (e.defaultPrevented) return; 1846 | // don't redirect on right click 1847 | if (e.button !== 0) return; 1848 | 1849 | var target = _this.target; 1850 | var go = function go(target) { 1851 | e.preventDefault(); 1852 | if (target != null) { 1853 | router.go(target); 1854 | } 1855 | }; 1856 | 1857 | if (_this.el.tagName === 'A' || e.target === _this.el) { 1858 | // v-link on 1859 | go(target); 1860 | } else { 1861 | // v-link delegate on
1862 | var el = e.target; 1863 | while (el && el.tagName !== 'A' && el !== _this.el) { 1864 | el = el.parentNode; 1865 | } 1866 | if (!el) return; 1867 | if (el.tagName !== 'A' || !el.href) { 1868 | // allow not anchor 1869 | go(target); 1870 | } else if (sameOrigin(el)) { 1871 | go({ 1872 | path: el.pathname, 1873 | replace: target && target.replace, 1874 | append: target && target.append 1875 | }); 1876 | } 1877 | } 1878 | }; 1879 | this.el.addEventListener('click', this.handler); 1880 | // manage active link class 1881 | this.unwatch = vm.$watch('$route.path', _.bind(this.updateClasses, this)); 1882 | }, 1883 | 1884 | update: function update(path) { 1885 | var router = this.vm.$route.router; 1886 | var append = undefined; 1887 | this.target = path; 1888 | if (_.isObject(path)) { 1889 | append = path.append; 1890 | this.exact = path.exact; 1891 | this.prevActiveClass = this.activeClass; 1892 | this.activeClass = path.activeClass; 1893 | } 1894 | path = this.path = router._stringifyPath(path); 1895 | this.activeRE = path && !this.exact ? new RegExp('^' + path.replace(/\/$/, '').replace(regexEscapeRE, '\\$&') + '(\\/|$)') : null; 1896 | this.updateClasses(this.vm.$route.path); 1897 | var isAbsolute = path.charAt(0) === '/'; 1898 | // do not format non-hash relative paths 1899 | var href = path && (router.mode === 'hash' || isAbsolute) ? router.history.formatPath(path, append) : path; 1900 | if (this.el.tagName === 'A') { 1901 | if (href) { 1902 | this.el.href = href; 1903 | } else { 1904 | this.el.removeAttribute('href'); 1905 | } 1906 | } 1907 | }, 1908 | 1909 | updateClasses: function updateClasses(path) { 1910 | var el = this.el; 1911 | var router = this.vm.$route.router; 1912 | var activeClass = this.activeClass || router._linkActiveClass; 1913 | // clear old class 1914 | if (this.prevActiveClass !== activeClass) { 1915 | _.removeClass(el, this.prevActiveClass); 1916 | } 1917 | // remove query string before matching 1918 | var dest = this.path.replace(queryStringRE, ''); 1919 | path = path.replace(queryStringRE, ''); 1920 | // add new class 1921 | if (this.exact) { 1922 | if (dest === path || 1923 | // also allow additional trailing slash 1924 | dest.charAt(dest.length - 1) !== '/' && dest === path.replace(trailingSlashRE, '')) { 1925 | _.addClass(el, activeClass); 1926 | } else { 1927 | _.removeClass(el, activeClass); 1928 | } 1929 | } else { 1930 | if (this.activeRE && this.activeRE.test(path)) { 1931 | _.addClass(el, activeClass); 1932 | } else { 1933 | _.removeClass(el, activeClass); 1934 | } 1935 | } 1936 | }, 1937 | 1938 | unbind: function unbind() { 1939 | this.el.removeEventListener('click', this.handler); 1940 | this.unwatch && this.unwatch(); 1941 | } 1942 | }); 1943 | 1944 | function sameOrigin(link) { 1945 | return link.protocol === location.protocol && link.hostname === location.hostname && link.port === location.port; 1946 | } 1947 | } 1948 | 1949 | var historyBackends = { 1950 | abstract: AbstractHistory, 1951 | hash: HashHistory, 1952 | html5: HTML5History 1953 | }; 1954 | 1955 | // late bind during install 1956 | var Vue = undefined; 1957 | 1958 | /** 1959 | * Router constructor 1960 | * 1961 | * @param {Object} [options] 1962 | */ 1963 | 1964 | var Router = (function () { 1965 | function Router() { 1966 | var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 1967 | 1968 | var _ref$hashbang = _ref.hashbang; 1969 | var hashbang = _ref$hashbang === undefined ? true : _ref$hashbang; 1970 | var _ref$abstract = _ref.abstract; 1971 | var abstract = _ref$abstract === undefined ? false : _ref$abstract; 1972 | var _ref$history = _ref.history; 1973 | var history = _ref$history === undefined ? false : _ref$history; 1974 | var _ref$saveScrollPosition = _ref.saveScrollPosition; 1975 | var saveScrollPosition = _ref$saveScrollPosition === undefined ? false : _ref$saveScrollPosition; 1976 | var _ref$transitionOnLoad = _ref.transitionOnLoad; 1977 | var transitionOnLoad = _ref$transitionOnLoad === undefined ? false : _ref$transitionOnLoad; 1978 | var _ref$suppressTransitionError = _ref.suppressTransitionError; 1979 | var suppressTransitionError = _ref$suppressTransitionError === undefined ? false : _ref$suppressTransitionError; 1980 | var _ref$root = _ref.root; 1981 | var root = _ref$root === undefined ? null : _ref$root; 1982 | var _ref$linkActiveClass = _ref.linkActiveClass; 1983 | var linkActiveClass = _ref$linkActiveClass === undefined ? 'v-link-active' : _ref$linkActiveClass; 1984 | babelHelpers.classCallCheck(this, Router); 1985 | 1986 | /* istanbul ignore if */ 1987 | if (!Router.installed) { 1988 | throw new Error('Please install the Router with Vue.use() before ' + 'creating an instance.'); 1989 | } 1990 | 1991 | // Vue instances 1992 | this.app = null; 1993 | this._views = []; 1994 | this._children = []; 1995 | 1996 | // route recognizer 1997 | this._recognizer = new RouteRecognizer(); 1998 | this._guardRecognizer = new RouteRecognizer(); 1999 | 2000 | // state 2001 | this._started = false; 2002 | this._startCb = null; 2003 | this._currentRoute = {}; 2004 | this._currentTransition = null; 2005 | this._previousTransition = null; 2006 | this._notFoundHandler = null; 2007 | this._notFoundRedirect = null; 2008 | this._beforeEachHooks = []; 2009 | this._afterEachHooks = []; 2010 | 2011 | // feature detection 2012 | this._hasPushState = typeof window !== 'undefined' && window.history && window.history.pushState; 2013 | 2014 | // trigger transition on initial render? 2015 | this._rendered = false; 2016 | this._transitionOnLoad = transitionOnLoad; 2017 | 2018 | // history mode 2019 | this._abstract = abstract; 2020 | this._hashbang = hashbang; 2021 | this._history = this._hasPushState && history; 2022 | 2023 | // other options 2024 | this._saveScrollPosition = saveScrollPosition; 2025 | this._linkActiveClass = linkActiveClass; 2026 | this._suppress = suppressTransitionError; 2027 | 2028 | // create history object 2029 | var inBrowser = Vue.util.inBrowser; 2030 | this.mode = !inBrowser || this._abstract ? 'abstract' : this._history ? 'html5' : 'hash'; 2031 | 2032 | var History = historyBackends[this.mode]; 2033 | var self = this; 2034 | this.history = new History({ 2035 | root: root, 2036 | hashbang: this._hashbang, 2037 | onChange: function onChange(path, state, anchor) { 2038 | self._match(path, state, anchor); 2039 | } 2040 | }); 2041 | } 2042 | 2043 | /** 2044 | * Allow directly passing components to a route 2045 | * definition. 2046 | * 2047 | * @param {String} path 2048 | * @param {Object} handler 2049 | */ 2050 | 2051 | // API =================================================== 2052 | 2053 | /** 2054 | * Register a map of top-level paths. 2055 | * 2056 | * @param {Object} map 2057 | */ 2058 | 2059 | Router.prototype.map = function map(_map) { 2060 | for (var route in _map) { 2061 | this.on(route, _map[route]); 2062 | } 2063 | }; 2064 | 2065 | /** 2066 | * Register a single root-level path 2067 | * 2068 | * @param {String} rootPath 2069 | * @param {Object} handler 2070 | * - {String} component 2071 | * - {Object} [subRoutes] 2072 | * - {Boolean} [forceRefresh] 2073 | * - {Function} [before] 2074 | * - {Function} [after] 2075 | */ 2076 | 2077 | Router.prototype.on = function on(rootPath, handler) { 2078 | if (rootPath === '*') { 2079 | this._notFound(handler); 2080 | } else { 2081 | this._addRoute(rootPath, handler, []); 2082 | } 2083 | }; 2084 | 2085 | /** 2086 | * Set redirects. 2087 | * 2088 | * @param {Object} map 2089 | */ 2090 | 2091 | Router.prototype.redirect = function redirect(map) { 2092 | for (var path in map) { 2093 | this._addRedirect(path, map[path]); 2094 | } 2095 | }; 2096 | 2097 | /** 2098 | * Set aliases. 2099 | * 2100 | * @param {Object} map 2101 | */ 2102 | 2103 | Router.prototype.alias = function alias(map) { 2104 | for (var path in map) { 2105 | this._addAlias(path, map[path]); 2106 | } 2107 | }; 2108 | 2109 | /** 2110 | * Set global before hook. 2111 | * 2112 | * @param {Function} fn 2113 | */ 2114 | 2115 | Router.prototype.beforeEach = function beforeEach(fn) { 2116 | this._beforeEachHooks.push(fn); 2117 | }; 2118 | 2119 | /** 2120 | * Set global after hook. 2121 | * 2122 | * @param {Function} fn 2123 | */ 2124 | 2125 | Router.prototype.afterEach = function afterEach(fn) { 2126 | this._afterEachHooks.push(fn); 2127 | }; 2128 | 2129 | /** 2130 | * Navigate to a given path. 2131 | * The path can be an object describing a named path in 2132 | * the format of { name: '...', params: {}, query: {}} 2133 | * The path is assumed to be already decoded, and will 2134 | * be resolved against root (if provided) 2135 | * 2136 | * @param {String|Object} path 2137 | * @param {Boolean} [replace] 2138 | */ 2139 | 2140 | Router.prototype.go = function go(path) { 2141 | var replace = false; 2142 | var append = false; 2143 | if (Vue.util.isObject(path)) { 2144 | replace = path.replace; 2145 | append = path.append; 2146 | } 2147 | path = this._stringifyPath(path); 2148 | if (path) { 2149 | this.history.go(path, replace, append); 2150 | } 2151 | }; 2152 | 2153 | /** 2154 | * Short hand for replacing current path 2155 | * 2156 | * @param {String} path 2157 | */ 2158 | 2159 | Router.prototype.replace = function replace(path) { 2160 | if (typeof path === 'string') { 2161 | path = { path: path }; 2162 | } 2163 | path.replace = true; 2164 | this.go(path); 2165 | }; 2166 | 2167 | /** 2168 | * Start the router. 2169 | * 2170 | * @param {VueConstructor} App 2171 | * @param {String|Element} container 2172 | * @param {Function} [cb] 2173 | */ 2174 | 2175 | Router.prototype.start = function start(App, container, cb) { 2176 | /* istanbul ignore if */ 2177 | if (this._started) { 2178 | warn('already started.'); 2179 | return; 2180 | } 2181 | this._started = true; 2182 | this._startCb = cb; 2183 | if (!this.app) { 2184 | /* istanbul ignore if */ 2185 | if (!App || !container) { 2186 | throw new Error('Must start vue-router with a component and a ' + 'root container.'); 2187 | } 2188 | this._appContainer = container; 2189 | var Ctor = this._appConstructor = typeof App === 'function' ? App : Vue.extend(App); 2190 | // give it a name for better debugging 2191 | Ctor.options.name = Ctor.options.name || 'RouterApp'; 2192 | } 2193 | this.history.start(); 2194 | }; 2195 | 2196 | /** 2197 | * Stop listening to route changes. 2198 | */ 2199 | 2200 | Router.prototype.stop = function stop() { 2201 | this.history.stop(); 2202 | this._started = false; 2203 | }; 2204 | 2205 | // Internal methods ====================================== 2206 | 2207 | /** 2208 | * Add a route containing a list of segments to the internal 2209 | * route recognizer. Will be called recursively to add all 2210 | * possible sub-routes. 2211 | * 2212 | * @param {String} path 2213 | * @param {Object} handler 2214 | * @param {Array} segments 2215 | */ 2216 | 2217 | Router.prototype._addRoute = function _addRoute(path, handler, segments) { 2218 | guardComponent(path, handler); 2219 | handler.path = path; 2220 | handler.fullPath = (segments.reduce(function (path, segment) { 2221 | return path + segment.path; 2222 | }, '') + path).replace('//', '/'); 2223 | segments.push({ 2224 | path: path, 2225 | handler: handler 2226 | }); 2227 | this._recognizer.add(segments, { 2228 | as: handler.name 2229 | }); 2230 | // add sub routes 2231 | if (handler.subRoutes) { 2232 | for (var subPath in handler.subRoutes) { 2233 | // recursively walk all sub routes 2234 | this._addRoute(subPath, handler.subRoutes[subPath], 2235 | // pass a copy in recursion to avoid mutating 2236 | // across branches 2237 | segments.slice()); 2238 | } 2239 | } 2240 | }; 2241 | 2242 | /** 2243 | * Set the notFound route handler. 2244 | * 2245 | * @param {Object} handler 2246 | */ 2247 | 2248 | Router.prototype._notFound = function _notFound(handler) { 2249 | guardComponent('*', handler); 2250 | this._notFoundHandler = [{ handler: handler }]; 2251 | }; 2252 | 2253 | /** 2254 | * Add a redirect record. 2255 | * 2256 | * @param {String} path 2257 | * @param {String} redirectPath 2258 | */ 2259 | 2260 | Router.prototype._addRedirect = function _addRedirect(path, redirectPath) { 2261 | if (path === '*') { 2262 | this._notFoundRedirect = redirectPath; 2263 | } else { 2264 | this._addGuard(path, redirectPath, this.replace); 2265 | } 2266 | }; 2267 | 2268 | /** 2269 | * Add an alias record. 2270 | * 2271 | * @param {String} path 2272 | * @param {String} aliasPath 2273 | */ 2274 | 2275 | Router.prototype._addAlias = function _addAlias(path, aliasPath) { 2276 | this._addGuard(path, aliasPath, this._match); 2277 | }; 2278 | 2279 | /** 2280 | * Add a path guard. 2281 | * 2282 | * @param {String} path 2283 | * @param {String} mappedPath 2284 | * @param {Function} handler 2285 | */ 2286 | 2287 | Router.prototype._addGuard = function _addGuard(path, mappedPath, _handler) { 2288 | var _this = this; 2289 | 2290 | this._guardRecognizer.add([{ 2291 | path: path, 2292 | handler: function handler(match, query) { 2293 | var realPath = mapParams(mappedPath, match.params, query); 2294 | _handler.call(_this, realPath); 2295 | } 2296 | }]); 2297 | }; 2298 | 2299 | /** 2300 | * Check if a path matches any redirect records. 2301 | * 2302 | * @param {String} path 2303 | * @return {Boolean} - if true, will skip normal match. 2304 | */ 2305 | 2306 | Router.prototype._checkGuard = function _checkGuard(path) { 2307 | var matched = this._guardRecognizer.recognize(path); 2308 | if (matched) { 2309 | matched[0].handler(matched[0], matched.queryParams); 2310 | return true; 2311 | } else if (this._notFoundRedirect) { 2312 | matched = this._recognizer.recognize(path); 2313 | if (!matched) { 2314 | this.replace(this._notFoundRedirect); 2315 | return true; 2316 | } 2317 | } 2318 | }; 2319 | 2320 | /** 2321 | * Match a URL path and set the route context on vm, 2322 | * triggering view updates. 2323 | * 2324 | * @param {String} path 2325 | * @param {Object} [state] 2326 | * @param {String} [anchor] 2327 | */ 2328 | 2329 | Router.prototype._match = function _match(path, state, anchor) { 2330 | var _this2 = this; 2331 | 2332 | if (this._checkGuard(path)) { 2333 | return; 2334 | } 2335 | 2336 | var currentRoute = this._currentRoute; 2337 | var currentTransition = this._currentTransition; 2338 | 2339 | if (currentTransition) { 2340 | if (currentTransition.to.path === path) { 2341 | // do nothing if we have an active transition going to the same path 2342 | return; 2343 | } else if (currentRoute.path === path) { 2344 | // We are going to the same path, but we also have an ongoing but 2345 | // not-yet-validated transition. Abort that transition and reset to 2346 | // prev transition. 2347 | currentTransition.aborted = true; 2348 | this._currentTransition = this._prevTransition; 2349 | return; 2350 | } else { 2351 | // going to a totally different path. abort ongoing transition. 2352 | currentTransition.aborted = true; 2353 | } 2354 | } 2355 | 2356 | // construct new route and transition context 2357 | var route = new Route(path, this); 2358 | var transition = new RouteTransition(this, route, currentRoute); 2359 | 2360 | // current transition is updated right now. 2361 | // however, current route will only be updated after the transition has 2362 | // been validated. 2363 | this._prevTransition = currentTransition; 2364 | this._currentTransition = transition; 2365 | 2366 | if (!this.app) { 2367 | // initial render 2368 | this.app = new this._appConstructor({ 2369 | el: this._appContainer, 2370 | _meta: { 2371 | $route: route 2372 | } 2373 | }); 2374 | } 2375 | 2376 | // check global before hook 2377 | var beforeHooks = this._beforeEachHooks; 2378 | var startTransition = function startTransition() { 2379 | transition.start(function () { 2380 | _this2._postTransition(route, state, anchor); 2381 | }); 2382 | }; 2383 | 2384 | if (beforeHooks.length) { 2385 | transition.runQueue(beforeHooks, function (hook, _, next) { 2386 | if (transition === _this2._currentTransition) { 2387 | transition.callHook(hook, null, next, { 2388 | expectBoolean: true 2389 | }); 2390 | } 2391 | }, startTransition); 2392 | } else { 2393 | startTransition(); 2394 | } 2395 | 2396 | if (!this._rendered && this._startCb) { 2397 | this._startCb.call(null); 2398 | } 2399 | 2400 | // HACK: 2401 | // set rendered to true after the transition start, so 2402 | // that components that are acitvated synchronously know 2403 | // whether it is the initial render. 2404 | this._rendered = true; 2405 | }; 2406 | 2407 | /** 2408 | * Set current to the new transition. 2409 | * This is called by the transition object when the 2410 | * validation of a route has succeeded. 2411 | * 2412 | * @param {Transition} transition 2413 | */ 2414 | 2415 | Router.prototype._onTransitionValidated = function _onTransitionValidated(transition) { 2416 | // set current route 2417 | var route = this._currentRoute = transition.to; 2418 | // update route context for all children 2419 | if (this.app.$route !== route) { 2420 | this.app.$route = route; 2421 | this._children.forEach(function (child) { 2422 | child.$route = route; 2423 | }); 2424 | } 2425 | // call global after hook 2426 | if (this._afterEachHooks.length) { 2427 | this._afterEachHooks.forEach(function (hook) { 2428 | return hook.call(null, { 2429 | to: transition.to, 2430 | from: transition.from 2431 | }); 2432 | }); 2433 | } 2434 | this._currentTransition.done = true; 2435 | }; 2436 | 2437 | /** 2438 | * Handle stuff after the transition. 2439 | * 2440 | * @param {Route} route 2441 | * @param {Object} [state] 2442 | * @param {String} [anchor] 2443 | */ 2444 | 2445 | Router.prototype._postTransition = function _postTransition(route, state, anchor) { 2446 | // handle scroll positions 2447 | // saved scroll positions take priority 2448 | // then we check if the path has an anchor 2449 | var pos = state && state.pos; 2450 | if (pos && this._saveScrollPosition) { 2451 | Vue.nextTick(function () { 2452 | window.scrollTo(pos.x, pos.y); 2453 | }); 2454 | } else if (anchor) { 2455 | Vue.nextTick(function () { 2456 | var el = document.getElementById(anchor.slice(1)); 2457 | if (el) { 2458 | window.scrollTo(window.scrollX, el.offsetTop); 2459 | } 2460 | }); 2461 | } 2462 | }; 2463 | 2464 | /** 2465 | * Normalize named route object / string paths into 2466 | * a string. 2467 | * 2468 | * @param {Object|String|Number} path 2469 | * @return {String} 2470 | */ 2471 | 2472 | Router.prototype._stringifyPath = function _stringifyPath(path) { 2473 | if (path && typeof path === 'object') { 2474 | if (path.name) { 2475 | var params = path.params || {}; 2476 | if (path.query) { 2477 | params.queryParams = path.query; 2478 | } 2479 | return this._recognizer.generate(path.name, params); 2480 | } else if (path.path) { 2481 | var fullPath = path.path; 2482 | if (path.query) { 2483 | var query = this._recognizer.generateQueryString(path.query); 2484 | if (fullPath.indexOf('?') > -1) { 2485 | fullPath += '&' + query.slice(1); 2486 | } else { 2487 | fullPath += query; 2488 | } 2489 | } 2490 | return fullPath; 2491 | } else { 2492 | return ''; 2493 | } 2494 | } else { 2495 | return path ? path + '' : ''; 2496 | } 2497 | }; 2498 | 2499 | return Router; 2500 | })(); 2501 | 2502 | function guardComponent(path, handler) { 2503 | var comp = handler.component; 2504 | if (Vue.util.isPlainObject(comp)) { 2505 | comp = handler.component = Vue.extend(comp); 2506 | } 2507 | /* istanbul ignore if */ 2508 | if (typeof comp !== 'function') { 2509 | handler.component = null; 2510 | warn('invalid component for route "' + path + '".'); 2511 | } 2512 | } 2513 | 2514 | /* Installation */ 2515 | 2516 | Router.installed = false; 2517 | 2518 | /** 2519 | * Installation interface. 2520 | * Install the necessary directives. 2521 | */ 2522 | 2523 | Router.install = function (externalVue) { 2524 | /* istanbul ignore if */ 2525 | if (Router.installed) { 2526 | warn('already installed.'); 2527 | return; 2528 | } 2529 | Vue = externalVue; 2530 | applyOverride(Vue); 2531 | View(Vue); 2532 | Link(Vue); 2533 | exports$1.Vue = Vue; 2534 | Router.installed = true; 2535 | }; 2536 | 2537 | // auto install 2538 | /* istanbul ignore if */ 2539 | if (typeof window !== 'undefined' && window.Vue) { 2540 | window.Vue.use(Router); 2541 | } 2542 | 2543 | return Router; 2544 | 2545 | })); 2546 | //# sourceMappingURL=vue-router.js.map -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "olympos-gulp", 3 | "version": "1.0.0", 4 | "description": "Gulp dependencies for Olympos WordPress theme", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/ivandoric/olympos.git" 12 | }, 13 | "author": "Ivan Dorić", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "gulp": "^3.9.0", 17 | "gulp-autoprefixer": "^3.0.2", 18 | "gulp-livereload": "^3.8.0", 19 | "gulp-sass": "^2.0.4", 20 | "gulp-sourcemaps": "^1.5.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This is a repository for "One Page App With Drupal 8 and Vue.js" tutorial series. You can [check out the series here](http://watch-learn.com/series/one-page-app-with-drupal-and-vue/). 2 | 3 | You can go to [releases](https://github.com/ivandoric/one-page-app-with-drupal-and-vue/releases) to download the code for most of the videos of the series. Releases are made per episode. -------------------------------------------------------------------------------- /sass/_fonts.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /sass/_footer.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivandoric/one-page-app-with-drupal-and-vue/147e312632721ef2455755b7f59a4c5af825a401/sass/_footer.scss -------------------------------------------------------------------------------- /sass/_globals.scss: -------------------------------------------------------------------------------- 1 | /* ======================================================================= 2 | ## ++ Globals 3 | ========================================================================== */ 4 | html{ 5 | font-size: 100%; 6 | } 7 | 8 | body{ 9 | -webkit-font-smoothing: antialiased; 10 | padding:60px 0; 11 | } 12 | 13 | *{ 14 | box-sizing:border-box; 15 | } 16 | 17 | .movie-item{ 18 | border-bottom:1px solid #ccc; 19 | margin-bottom: 20px; 20 | padding:10px; 21 | 22 | img{ 23 | max-width: 100%; 24 | height:auto; 25 | } 26 | 27 | h4{ 28 | margin-top: 0; 29 | } 30 | } 31 | 32 | .filter{ 33 | padding: 40px; 34 | background: #efefef; 35 | margin-bottom:40px; 36 | } 37 | 38 | 39 | /* ======================================================================= 40 | ## ++ Cleafix 41 | ========================================================================== */ 42 | 43 | /* float clearing for IE6 */ 44 | * html .clearfix{ 45 | height: 1%; 46 | overflow: visible; 47 | } 48 | 49 | /* float clearing for IE7 */ 50 | *+html .clearfix{ 51 | min-height: 1%; 52 | } 53 | 54 | /* float clearing for everyone else */ 55 | .clearfix:after{ 56 | clear: both; 57 | content: "."; 58 | display: block; 59 | height: 0; 60 | visibility: hidden; 61 | font-size: 0; 62 | } 63 | 64 | .clr{clear:both;} 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /sass/_header.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivandoric/one-page-app-with-drupal-and-vue/147e312632721ef2455755b7f59a4c5af825a401/sass/_header.scss -------------------------------------------------------------------------------- /sass/_homepage.scss: -------------------------------------------------------------------------------- 1 | .loading-transition{ 2 | transition: all .3s ease; 3 | padding:20px; 4 | color:#fff; 5 | background:#5CB85C; 6 | margin:20px 0; 7 | width:100%; 8 | } 9 | 10 | .loading-enter, 11 | .loading-leave{ 12 | width:0; 13 | } -------------------------------------------------------------------------------- /sass/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* ======================================================================= 2 | ## ++ Media Queries 3 | ========================================================================== */ 4 | 5 | /* 6 | Used for media queries. 7 | Add these mixins in your normal scss flow. 8 | 9 | Eg. 10 | .container{ 11 | width:1024px; 12 | 13 | @include tablets{ 14 | width:90%; 15 | } 16 | } 17 | */ 18 | 19 | @mixin lowresmonitors{ 20 | @media screen and (max-width: 1350px){ @content;} 21 | } 22 | 23 | @mixin tablets{ 24 | @media screen and (max-width: 1100px){ @content; } 25 | } 26 | 27 | @mixin phones{ 28 | @media screen and (max-width: 720px){ @content; } 29 | } 30 | 31 | /* ======================================================================= 32 | ## ++ Unit transform 33 | ========================================================================== */ 34 | 35 | /* 36 | Used for making containers have width in percentages. 37 | Usage: define elemnt width in px and the width of parent elemnt in px. 38 | eg. .block{width:cp(512px, 1024px)} this will result in .block{width:50%;} 39 | */ 40 | 41 | @function cp($target, $container) { 42 | @return ($target / $container) * 100%; 43 | } 44 | 45 | /* 46 | Used for making px values convert to rem values 47 | Usage: define font-size in px and it will convert to rems 48 | eg. font-size: rem(14px); 49 | */ 50 | 51 | @function rem($target, $context: $base-font-size) { 52 | @if $target == 0 { @return 0 } 53 | @return $target / $context + 0rem; 54 | } 55 | $base-font-size:16px; -------------------------------------------------------------------------------- /sass/_su.scss: -------------------------------------------------------------------------------- 1 | // Su 2 | // == 3 | 4 | @import 'susy/su'; 5 | -------------------------------------------------------------------------------- /sass/_susy.scss: -------------------------------------------------------------------------------- 1 | // Susy 2 | // ==== 3 | 4 | @import 'susy/language/susy'; 5 | -------------------------------------------------------------------------------- /sass/_variables.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivandoric/one-page-app-with-drupal-and-vue/147e312632721ef2455755b7f59a4c5af825a401/sass/_variables.scss -------------------------------------------------------------------------------- /sass/style.scss: -------------------------------------------------------------------------------- 1 | /* Vendor */ 2 | @import "susy"; 3 | @import "su"; 4 | 5 | 6 | /* Setup */ 7 | @import "mixins"; 8 | @import "variables"; 9 | @import "fonts"; 10 | @import "globals"; 11 | 12 | @import "header"; 13 | @import "footer"; 14 | @import "homepage"; 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /sass/susy/_su.scss: -------------------------------------------------------------------------------- 1 | // Su 2 | // == 3 | 4 | @import "su/utilities"; 5 | @import "su/settings"; 6 | @import "su/validation"; 7 | @import "su/grid"; 8 | -------------------------------------------------------------------------------- /sass/susy/language/_susy.scss: -------------------------------------------------------------------------------- 1 | // Susy Next Syntax 2 | // ================ 3 | 4 | $susy-version: 2.1; 5 | 6 | @import "../su"; 7 | @import "../output/float"; 8 | 9 | @import "susy/settings"; 10 | @import "susy/validation"; 11 | @import "susy/grids"; 12 | @import "susy/box-sizing"; 13 | @import "susy/context"; 14 | @import "susy/background"; 15 | @import "susy/container"; 16 | @import "susy/span"; 17 | @import "susy/gutters"; 18 | @import "susy/isolate"; 19 | @import "susy/gallery"; 20 | @import "susy/rows"; 21 | @import "susy/margins"; 22 | @import "susy/padding"; 23 | @import "susy/bleed"; 24 | @import "susy/breakpoint-plugin"; 25 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_background.scss: -------------------------------------------------------------------------------- 1 | // Background Grid Syntax 2 | // ====================== 3 | 4 | $susy-overlay-grid-head-exists: false; 5 | 6 | 7 | // Show Grid/s 8 | // ----------- 9 | // Show grid on any element using either background or overlay. 10 | // - [$grid] : 11 | @mixin show-grid( 12 | $grid: $susy 13 | ) { 14 | $inspect: $grid; 15 | $_output: debug-get(output, $grid); 16 | 17 | @include susy-inspect(show-grid, $inspect); 18 | @if $_output == overlay and susy-get(debug image, $grid) != hide { 19 | @include overlay-grid($grid); 20 | } @else { 21 | @include background-grid($grid); 22 | } 23 | } 24 | 25 | @mixin show-grids( 26 | $grid: $susy 27 | ) { 28 | @include show-grid($grid); 29 | } 30 | 31 | // Background Grid 32 | // --------------- 33 | // Show a grid background on any element. 34 | // - [$grid] : 35 | @mixin background-grid( 36 | $grid: $susy 37 | ) { 38 | $inspect : $grid; 39 | $_output : get-background($grid); 40 | 41 | @if length($_output) > 0 { 42 | $_flow: susy-get(flow, $grid); 43 | 44 | $_image: (); 45 | @each $name, $layer in map-get($_output, image) { 46 | $_direction: if($name == baseline, to bottom, to to($_flow)); 47 | $_image: append($_image, linear-gradient($_direction, $layer), comma); 48 | } 49 | $_output: map-merge($_output, (image: $_image)); 50 | 51 | @include background-grid-output($_output...); 52 | @include susy-inspect(background-grid, $inspect); 53 | } 54 | } 55 | 56 | 57 | // Overlay Grid 58 | // ------------ 59 | // Generate an icon to trigger grid-overlays on any given elements. 60 | // $grids... : [] [, ]* 61 | @mixin overlay-grid ( 62 | $grid: $susy 63 | ) { 64 | @if not($susy-overlay-grid-head-exists) { 65 | @at-root head { @include overlay-head($grid); } 66 | @at-root head:before { @include overlay-trigger; } 67 | @at-root head:hover { @include overlay-trigger-hover; } 68 | $susy-overlay-grid-head-exists: true !global; 69 | } 70 | 71 | head:hover ~ &, 72 | head:hover ~ body & { 73 | position: relative; 74 | &:before { 75 | @include grid-overlay-base; 76 | @include background-grid($grid); 77 | } 78 | } 79 | } 80 | 81 | 82 | // [Private] Overlay Trigger 83 | // ------------------------- 84 | @mixin overlay-trigger { 85 | content: "|||"; 86 | display: block; 87 | padding: 5px 10px; 88 | font: { 89 | family: sans-serif; 90 | size: 16px; 91 | weight: bold; 92 | } 93 | } 94 | 95 | 96 | // [Private] Overlay Trigger Hover 97 | // ------------------------------- 98 | @mixin overlay-trigger-hover { 99 | background: rgba(white, .5); 100 | color: red; 101 | } 102 | 103 | 104 | // [Private] Overlay Head 105 | // ---------------------- 106 | // styles to create grid overlay toggle 107 | @mixin overlay-head ( 108 | $grid: $susy 109 | ) { 110 | $_toggle: debug-get(toggle, $grid); 111 | $_horz: null; 112 | $_vert: null; 113 | 114 | @each $side in $_toggle { 115 | $_horz: if($side == left or $side == right, $side, $_horz); 116 | $_vert: if($side == top or $side == bottom, $side, $_vert); 117 | } 118 | 119 | display: block; 120 | position: fixed; 121 | #{$_horz}: 10px; 122 | #{$_vert}: 10px; 123 | z-index: 999; 124 | color: #333; 125 | background: rgba(white, .25); 126 | } 127 | 128 | 129 | // [Private] Grid Overlay Base 130 | // --------------------------- 131 | // Base styles for generating a grid overlay 132 | @mixin grid-overlay-base() { 133 | position: absolute; 134 | top: 0; 135 | left: 0; 136 | bottom: 0; 137 | right: 0; 138 | content: " "; 139 | z-index: 998; 140 | } 141 | 142 | 143 | // Get Symmetrical Background 144 | // -------------------------- 145 | // - $grid: 146 | @function get-background-sym( 147 | $grid 148 | ) { 149 | $grid : parse-grid($grid); 150 | $_gutters : susy-get(gutters, $grid); 151 | $_column-width : susy-get(column-width, $grid); 152 | $_math : susy-get(math, $grid); 153 | 154 | $_color : debug-get(color); 155 | $_trans : transparent; 156 | $_light : lighten($_color, 15%); 157 | 158 | $_end : 1 + $_gutters; 159 | $_after : percentage(1/$_end); 160 | $_stops : (); 161 | $_size : span(1 $grid wide); 162 | 163 | @if is-inside($grid) { 164 | $_stops: $_color, $_light; 165 | } @else if is-split($grid) { 166 | $_split: $_gutters/2; 167 | $_before: percentage($_split/$_end); 168 | $_after: percentage((1 + $_split)/$_end); 169 | $_stops: $_trans $_before, $_color $_before, $_light $_after, $_trans $_after; 170 | } @else { 171 | $_stops: $_color, $_light $_after, $_trans $_after; 172 | } 173 | 174 | @if $_math == static { 175 | $_size: valid-column-math($_math, $_column-width) * $_end; 176 | } 177 | 178 | $_output: ( 179 | image: (columns: $_stops), 180 | size: $_size, 181 | ); 182 | 183 | @return $_output; 184 | } 185 | 186 | 187 | // Get Asymmetrical Inside 188 | // ----------------------- 189 | // - $grid: 190 | @function get-asym-inside( 191 | $grid 192 | ) { 193 | $grid : parse-grid($grid); 194 | $_columns : susy-get(columns, $grid); 195 | 196 | $_color : debug-get(color); 197 | $_light : lighten($_color, 15%); 198 | $_stops : (); 199 | 200 | @for $location from 1 through susy-count($_columns) { 201 | $this-stop: (); 202 | 203 | @if $location == 1 { 204 | $this-stop: append($this-stop, $_color, comma); 205 | } @else { 206 | $start: parse-span(1 at $location $grid); 207 | $start: get-isolation($start); 208 | $this-stop: append($this-stop, $_color $start, comma); 209 | } 210 | 211 | @if $location == susy-count($_columns) { 212 | $this-stop: append($this-stop, $_light, comma); 213 | } @else { 214 | $_end: parse-span(1 at ($location + 1) $grid); 215 | $_end: get-isolation($_end); 216 | $this-stop: append($this-stop, $_light $_end, comma); 217 | } 218 | 219 | $_stops: join($_stops, $this-stop, comma); 220 | } 221 | 222 | @return $_stops; 223 | } 224 | 225 | 226 | // Get Asymmetrical Split 227 | // ---------------------- 228 | // - $grid: 229 | @function get-asym-split( 230 | $grid 231 | ) { 232 | $grid : parse-grid($grid); 233 | $_columns : susy-get(columns, $grid); 234 | 235 | $_color : debug-get(color); 236 | $_light : lighten($_color, 15%); 237 | $_stops : (); 238 | 239 | @for $location from 1 through susy-count($_columns) { 240 | $this-stop: (); 241 | 242 | $start: parse-span(1 at $location $grid); 243 | $start: get-isolation($start); 244 | $this-stop: append($this-stop, transparent $start, comma); 245 | $this-stop: append($this-stop, $_color $start, comma); 246 | 247 | $_end: $start + span(1 at $location $grid); 248 | $this-stop: append($this-stop, $_light $_end, comma); 249 | $this-stop: append($this-stop, transparent $_end, comma); 250 | 251 | $_stops: join($_stops, $this-stop, comma); 252 | } 253 | 254 | @return $_stops; 255 | } 256 | 257 | 258 | // Get Asymmetrical Outside 259 | // ------------------------ 260 | // - $grid: 261 | @function get-asym-outside( 262 | $grid 263 | ) { 264 | $grid : parse-grid($grid); 265 | $_columns : susy-get(columns, $grid); 266 | 267 | $_color : debug-get(color); 268 | $_light : lighten($_color, 15%); 269 | $_trans : transparent; 270 | $_stops : (); 271 | 272 | @for $location from 1 through susy-count($_columns) { 273 | $this-stop: (); 274 | 275 | @if $location == 1 { 276 | $this-stop: append($this-stop, $_color, comma); 277 | } @else { 278 | $start: parse-span(1 at $location $grid); 279 | $start: get-isolation($start); 280 | $this-stop: append($this-stop, $_color $start, comma); 281 | } 282 | 283 | @if $location == susy-count($_columns) { 284 | $this-stop: append($this-stop, $_light, comma); 285 | } @else { 286 | $gutter: get-span-width(first $location $grid); 287 | 288 | $_end: parse-span(1 at ($location + 1) $grid); 289 | $_end: get-isolation($_end); 290 | 291 | $gutter: $_light $gutter, $_trans $gutter, $_trans $_end; 292 | $this-stop: join($this-stop, $gutter, comma); 293 | } 294 | 295 | $_stops: join($_stops, $this-stop, comma); 296 | } 297 | 298 | @return $_stops; 299 | } 300 | 301 | 302 | // Get Asymmetrical Background 303 | // --------------------------- 304 | // - $grid: 305 | @function get-background-asym( 306 | $grid 307 | ) { 308 | $_stops: (); 309 | 310 | @if is-inside($grid) { 311 | $_stops: get-asym-inside($grid); 312 | } @else if is-split($grid) { 313 | $_stops: get-asym-split($grid); 314 | } @else { 315 | $_stops: get-asym-outside($grid); 316 | } 317 | 318 | @return (image: (columns: $_stops)); 319 | } 320 | 321 | 322 | // Get Background 323 | // -------------- 324 | // - $grid: 325 | @function get-background( 326 | $grid 327 | ) { 328 | $grid : parse-grid($grid); 329 | $_show : susy-get(debug image, $grid); 330 | $_return : (); 331 | 332 | @if $_show and $_show != 'hide' { 333 | $_columns: susy-get(columns, $grid); 334 | 335 | @if $_show != 'show-baseline' { 336 | $_sym: is-symmetrical($_columns); 337 | $_return: if($_sym, get-background-sym($grid), get-background-asym($grid)); 338 | $_return: map-merge($_return, (clip: content-box)); 339 | } 340 | 341 | @if $_show != 'show-columns' 342 | and global-variable-exists(base-line-height) 343 | and type-of($base-line-height) == 'number' 344 | and not unitless($base-line-height) { 345 | $_color: variable-exists('grid-background-baseline-color'); 346 | $_color: if($_color, $grid-background-baseline-color, #000); 347 | 348 | $_image: map-get($_return, image); 349 | $_size: map-get($_return, size); 350 | $_baseline: (baseline: ($_color 1px, transparent 1px)); 351 | $_baseline-size: 100% $base-line-height; 352 | 353 | $_return: map-merge($_return, ( 354 | image: if($_image, map-merge($_image, $_baseline), $_baseline), 355 | size: if($_size, ($_size, $_baseline-size), $_baseline-size), 356 | )); 357 | 358 | @if $_show == 'show' { 359 | $_clip: map-get($_return, clip); 360 | $_return: map-merge($_return, (clip: join($_clip, border-box, comma))); 361 | } 362 | } @else if $_show == 'show-baseline' { 363 | @warn 'Please provide a $base-line-height with the desired height and units'; 364 | } 365 | } 366 | 367 | @if map-get($_return, image) { 368 | $_return: map-merge($_return, (flow: susy-get(flow, $grid))); 369 | } 370 | 371 | @return $_return; 372 | } 373 | 374 | 375 | // Get Debug 376 | // --------- 377 | // Return the value of a debug setting 378 | // - $key: 379 | @function debug-get( 380 | $key, 381 | $grid: $susy 382 | ) { 383 | $key: join(debug, $key, space); 384 | @return susy-get($key, $grid); 385 | } 386 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_bleed.scss: -------------------------------------------------------------------------------- 1 | // Bleed Syntax 2 | // ============ 3 | 4 | // Bleed 5 | // ----- 6 | // Add negative margins, and equal positive padding to create bleed. 7 | // - $bleed : 8 | @mixin bleed( 9 | $bleed: 0 gutter() 10 | ) { 11 | $inspect : $bleed; 12 | $output : get-bleed($bleed); 13 | 14 | @if susy-get(global-box-sizing) != content-box { 15 | $output: map-merge((box-sizing: content-box), $output); 16 | } 17 | 18 | @include susy-inspect(bleed, $inspect); 19 | @include output($output); 20 | } 21 | 22 | 23 | // Bleed-x 24 | // ------- 25 | // Shortcut for horizontal bleed. 26 | // - $bleed : 27 | @mixin bleed-x( 28 | $bleed: gutter() 29 | ) { 30 | $bleed : parse-span($bleed); 31 | $trbl : susy-get(span, $bleed); 32 | 33 | @if length($trbl) == 1 { 34 | $bleed: map-merge($bleed, (span: 0 nth($trbl, 1))); 35 | } @else if length($trbl) == 2 { 36 | $bleed: map-merge($bleed, (span: 0 nth($trbl, 2) 0 nth($trbl, 1))); 37 | } @else { 38 | @warn 'bleed-x only takes 2 lengths, but #{length($trbl)} were passed.'; 39 | } 40 | 41 | @include bleed($bleed); 42 | } 43 | 44 | 45 | // Bleed-y 46 | // ------- 47 | // Shortcut for vertical bleed. 48 | // - $bleed : 49 | @mixin bleed-y( 50 | $bleed: if(function-exists(rhythm), rhythm(1), 1em) 51 | ) { 52 | $bleed : parse-span($bleed); 53 | $trbl : susy-get(span, $bleed); 54 | 55 | @if length($trbl) == 1 { 56 | $bleed: map-merge($bleed, (span: nth($trbl, 1) 0)); 57 | } @else if length($trbl) == 2 { 58 | $bleed: map-merge($bleed, (span: nth($trbl, 1) 0 nth($trbl, 2) 0)); 59 | } @else { 60 | @warn 'bleed-y only takes 2 lengths, but #{length($trbl)} were passed.'; 61 | } 62 | 63 | @include bleed($bleed); 64 | } 65 | 66 | 67 | // Get Bleed 68 | // --------- 69 | // Return bleed output values 70 | // - $bleed: 71 | @function get-bleed( 72 | $bleed 73 | ) { 74 | $bleed : map-merge((spread: wide), parse-span($bleed)); 75 | $trbl : susy-get(span, $bleed); 76 | $short : null; 77 | $output : (); 78 | 79 | @for $i from 1 through length($trbl) { 80 | $this: nth($trbl, $i); 81 | $new: (); 82 | $margin: null; 83 | $padding: null; 84 | $padding-x: null; 85 | 86 | @if $this > 0 { 87 | $this: map-merge($bleed, (span: $this)); 88 | $margin: span($this); 89 | $padding: $margin; 90 | $padding-x: $padding; 91 | } 92 | 93 | @if $margin and $margin > 0 { 94 | $margin: - $margin; 95 | 96 | @if is-inside($this) { 97 | $gutter: gutter($this); 98 | $join: if($gutter and comparable($padding, $gutter), true, false); 99 | $padding-x: if($join and $padding > 0, $padding + $gutter, $padding); 100 | } 101 | } 102 | 103 | @if $i == 1 { 104 | $new: ( 105 | margin-top: $margin, 106 | padding-top: $padding, 107 | margin-right: $margin, 108 | padding-right: $padding-x, 109 | margin-bottom: $margin, 110 | padding-bottom: $padding, 111 | margin-left: $margin, 112 | padding-left: $padding-x, 113 | ); 114 | } @else if $i == 2 { 115 | $new: ( 116 | margin-right: $margin, 117 | padding-right: $padding-x, 118 | margin-left: $margin, 119 | padding-left: $padding-x, 120 | ); 121 | } @else if $i == 3 { 122 | $new: ( 123 | margin-bottom: $margin, 124 | padding-bottom: $padding, 125 | ); 126 | } @else if $i == 4 { 127 | $new: ( 128 | margin-left: $margin, 129 | padding-left: $padding-x, 130 | ); 131 | } 132 | 133 | $output: map-merge($output, $new); 134 | } 135 | 136 | @each $prop, $value in $output { 137 | $output: if($value == 0, map-merge($output, ($prop: null)), $output); 138 | } 139 | 140 | @return bleed-shorthand($output); 141 | } 142 | 143 | // Bleed Shorthand 144 | // --------------- 145 | // Convert bleed output into shorthand when possible. 146 | // - $bleed: 147 | @function bleed-shorthand( 148 | $bleed 149 | ) { 150 | $margin: (); 151 | $padding: (); 152 | $return: (); 153 | 154 | @each $key, $value in $bleed { 155 | @if str-index($key, margin) { 156 | $margin: map-merge($margin, ($key: $value)); 157 | } @else if str-index($key, padding) > 0 { 158 | $padding: map-merge($padding, ($key: $value)); 159 | } 160 | } 161 | 162 | $props: ( 163 | margin: $margin, 164 | padding: $padding, 165 | ); 166 | 167 | @each $name, $map in $props { 168 | $four: if(length(map-keys($map)) == 4, true, false); 169 | $null: if(index(map-values($map), null), true, false); 170 | 171 | @if $four and not($null) { 172 | $top: map-get($map, '#{$name}-top'); 173 | $right: map-get($map, '#{$name}-right'); 174 | $bottom: map-get($map, '#{$name}-bottom'); 175 | $left: map-get($map, '#{$name}-left'); 176 | 177 | $tb: if($top == $bottom, $top, null); 178 | $rl: if($right == $left, $right, null); 179 | $all: if($tb == $rl, $tb, null); 180 | 181 | $new: if($all, $all, null); 182 | 183 | @if not($new) { 184 | @if $tb and $rl { 185 | $new: $tb $rl; 186 | } @else if $rl { 187 | $new: $top $rl $bottom; 188 | } @else { 189 | $new: $top $right $bottom $left; 190 | } 191 | } 192 | 193 | $return: map-merge($return, ($name: $new)); 194 | } @else { 195 | $return: map-merge($return, $map); 196 | } 197 | } 198 | 199 | @return $return; 200 | } 201 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_box-sizing.scss: -------------------------------------------------------------------------------- 1 | // Susy Box Sizing 2 | // ================= 3 | 4 | // Global Box Sizing 5 | // ----------------- 6 | // Set a box model globally on all elements. 7 | // - [$box]: border-box | content-box 8 | // - [$inherit]: true | false 9 | @mixin global-box-sizing( 10 | $box: susy-get(global-box-sizing), 11 | $inherit: false 12 | ) { 13 | $inspect: $box; 14 | 15 | @if $inherit { 16 | @at-root { 17 | html { @include output((box-sizing: $box)); } 18 | *, *:before, *:after { box-sizing: inherit; } 19 | } 20 | } @else { 21 | *, *:before, *:after { @include output((box-sizing: $box)); } 22 | } 23 | 24 | @include susy-inspect(global-box-sizing, $inspect); 25 | @include update-box-model($box); 26 | } 27 | 28 | // Border Box Sizing 29 | // ----------------- 30 | // A legacy shortcut... 31 | // - [$inherit]: true | false 32 | @mixin border-box-sizing( 33 | $inherit: false 34 | ) { 35 | @include global-box-sizing(border-box, $inherit); 36 | } 37 | 38 | // Update Box Model 39 | // ---------------- 40 | // PRIVATE: Updates global box model setting 41 | @mixin update-box-model( 42 | $box 43 | ) { 44 | @if $box != susy-get(global-box-sizing) { 45 | @include susy-set(global-box-sizing, $box); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_breakpoint-plugin.scss: -------------------------------------------------------------------------------- 1 | // Breakpoint Integration 2 | // ====================== 3 | 4 | $susy-media: () !default; 5 | $susy-media-fallback: false !default; 6 | 7 | $_susy-media-context: (); 8 | 9 | 10 | // Susy Breakpoint 11 | // --------------- 12 | // Change grids at different media query breakpoints. 13 | // - $query : [] | | 14 | // - $layout : 15 | // - $no-query : | 16 | @mixin susy-breakpoint( 17 | $query, 18 | $layout: false, 19 | $no-query: $susy-media-fallback 20 | ) { 21 | @include susy-media-router($query, $no-query) { 22 | @if $layout { 23 | @include with-layout($layout) { 24 | @content; 25 | } 26 | } @else { 27 | @content; 28 | } 29 | } 30 | } 31 | 32 | 33 | // Susy Media 34 | // ---------- 35 | // - $query: [] | 36 | // - $no-query: | 37 | @mixin susy-media( 38 | $query, 39 | $no-query: $susy-media-fallback 40 | ) { 41 | $old-context: $_susy-media-context; 42 | $name: if(map-has-key($susy-media, $query), $query, null); 43 | $query: susy-get-media($query); 44 | $query: susy-parse-media($query); 45 | 46 | @include susy-media-context($query, $name); 47 | 48 | @if $no-query and type-of($no-query) != string { 49 | @content; 50 | } @else { 51 | @media #{susy-render-media($query)} { 52 | @content; 53 | } 54 | 55 | @if type-of($no-query) == string { 56 | #{$no-query} & { 57 | @content; 58 | } 59 | } 60 | } 61 | 62 | @include susy-media-context($old-context, $clean: true); 63 | } 64 | 65 | 66 | // Media Router 67 | // ------------ 68 | // Rout media arguments to the correct mixin. 69 | @mixin susy-media-router( 70 | $query, 71 | $no-query: $susy-media-fallback 72 | ) { 73 | @if susy-support(breakpoint, (mixin: breakpoint), $warn: false) { 74 | @include breakpoint($query, $no-query) { 75 | @content; 76 | } 77 | } @else { 78 | @include susy-media($query, $no-query) { 79 | @content; 80 | } 81 | } 82 | } 83 | 84 | 85 | // Update Context 86 | // ------------- 87 | // Set the new media context 88 | @mixin susy-media-context( 89 | $query, 90 | $name: null, 91 | $clean: false 92 | ) { 93 | $query: map-merge((name: $name), $query); 94 | 95 | @if $clean { 96 | $_susy-media-context: $query !global; 97 | } @else { 98 | $_susy-media-context: map-merge($_susy-media-context, $query) !global; 99 | } 100 | } 101 | 102 | 103 | // Media Context 104 | // ------------- 105 | // Return the full media context, or a single media property (e.g. min-width) 106 | @function susy-media-context( 107 | $property: false 108 | ) { 109 | @if $property { 110 | @return map-get($_susy-media-context, $property); 111 | } @else { 112 | @return $_susy-media-context; 113 | } 114 | } 115 | 116 | 117 | // Get Media 118 | // --------- 119 | // Return a named media-query from $susy-media. 120 | // - $name: 121 | @function susy-get-media( 122 | $name 123 | ) { 124 | @if map-has-key($susy-media, $name) { 125 | $map-value: map-get($susy-media, $name); 126 | @if ($name == $map-value) { 127 | $name: $map-value; 128 | } @else { 129 | $name: susy-get-media($map-value); 130 | } 131 | } 132 | 133 | @return $name; 134 | } 135 | 136 | 137 | // Render Media 138 | // ------------ 139 | // Build a media-query string from various media settings 140 | @function susy-render-media( 141 | $query 142 | ) { 143 | $output: null; 144 | @each $property, $value in $query { 145 | $string: null; 146 | 147 | @if $property == media { 148 | $string: $value; 149 | } @else { 150 | $string: '(#{$property}: #{$value})'; 151 | } 152 | 153 | $output: if($output, '#{$output} and #{$string}', $string); 154 | } 155 | 156 | @return $output; 157 | } 158 | 159 | 160 | // Parse Media 161 | // ----------- 162 | // Return parsed media-query settings based on shorthand 163 | @function susy-parse-media( 164 | $query 165 | ) { 166 | $mq: null; 167 | @if type-of($query) == map { 168 | $mq: $query; 169 | } @else if type-of($query) == number { 170 | $mq: (min-width: $query); 171 | } @else if type-of($query) == list and length($query) == 2 { 172 | @if type-of(nth($query, 1)) == number { 173 | $mq: ( 174 | min-width: min($query...), 175 | max-width: max($query...), 176 | ); 177 | } @else { 178 | $mq: (nth($query, 1): nth($query, 2)); 179 | } 180 | } @else { 181 | $mq: (media: '#{$query}'); 182 | } 183 | 184 | @return $mq; 185 | } 186 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_container.scss: -------------------------------------------------------------------------------- 1 | // Container Syntax 2 | // ================ 3 | 4 | // Container [mixin] 5 | // ----------------- 6 | // Set a container element 7 | // - [$layout] : 8 | @mixin container( 9 | $layout: $susy 10 | ) { 11 | $inspect : $layout; 12 | $layout : parse-grid($layout); 13 | 14 | $_width : get-container-width($layout); 15 | $_justify : parse-container-position(susy-get(container-position, $layout)); 16 | $_property : if(susy-get(math, $layout) == static, width, max-width); 17 | 18 | $_box : susy-get(box-sizing, $layout); 19 | 20 | @if $_box { 21 | @include output((box-sizing: $_box)); 22 | } 23 | 24 | @include susy-inspect(container, $inspect); 25 | @include float-container($_width, $_justify, $_property); 26 | @include show-grid($layout); 27 | } 28 | 29 | // Container [function] 30 | // -------------------- 31 | // Return container width 32 | // - [$layout] : 33 | @function container( 34 | $layout: $susy 35 | ) { 36 | $layout: parse-grid($layout); 37 | @return get-container-width($layout); 38 | } 39 | 40 | // Get Container Width 41 | // ------------------- 42 | // Calculate the container width 43 | // - [$layout]: 44 | @function get-container-width( 45 | $layout: $susy 46 | ) { 47 | $layout : parse-grid($layout); 48 | $_width : susy-get(container, $layout); 49 | $_column-width : susy-get(column-width, $layout); 50 | $_math : susy-get(math, $layout); 51 | 52 | @if not($_width) or $_width == auto { 53 | @if valid-column-math($_math, $_column-width) { 54 | $_columns : susy-get(columns, $layout); 55 | $_gutters : susy-get(gutters, $layout); 56 | $_spread : if(is-split($layout), wide, narrow); 57 | $_width : susy-sum($_columns, $_gutters, $_spread) * $_column-width; 58 | } @else { 59 | $_width: 100%; 60 | } 61 | } 62 | 63 | @return $_width; 64 | } 65 | 66 | // Parse Container Position 67 | // ------------------------ 68 | // Parse the $container-position into margin values. 69 | // - [$justify] : left | center | right | [] 70 | @function parse-container-position( 71 | $justify: map-get($susy-defaults, container-position) 72 | ) { 73 | $_return: if($justify == left, 0, auto) if($justify == right, 0, auto); 74 | 75 | @if not(index(left right center, $justify)) { 76 | $_return: nth($justify, 1); 77 | $_return: $_return if(length($justify) > 1, nth($justify, 2), $_return); 78 | } 79 | 80 | @return $_return; 81 | } 82 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_context.scss: -------------------------------------------------------------------------------- 1 | // Context Syntax 2 | // ============== 3 | 4 | // Nested [function] 5 | // ----------------- 6 | // Return a subset grid for nested context. 7 | // - $context : 8 | @function nested( 9 | $context 10 | ) { 11 | $context : parse-span($context); 12 | $span : susy-get(span, $context); 13 | $location : get-location($context); 14 | $columns : susy-get(columns, $context); 15 | 16 | @return susy-slice($span, $location, $columns); 17 | } 18 | 19 | // Nested [mixin] 20 | // -------------- 21 | // Use a subset grid for a nested context 22 | // - $context : 23 | // - @content : 24 | @mixin nested( 25 | $context 26 | ) { 27 | $inspect : $context; 28 | $context : parse-span($context); 29 | $old : susy-get(columns); 30 | $susy : map-merge($susy, (columns: nested($context))) !global; 31 | 32 | @include susy-inspect(nested, $inspect); 33 | @content; 34 | 35 | $susy : map-merge($susy, (columns: $old)) !global; 36 | } 37 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_gallery.scss: -------------------------------------------------------------------------------- 1 | // Gallery Syntax 2 | // ============== 3 | 4 | // Gallery 5 | // ------- 6 | // Create an isolated gallery 7 | // - $span : 8 | // - [$selector] : child | of-type 9 | @mixin gallery( 10 | $span, 11 | $selector: child 12 | ) { 13 | $inspect : $span; 14 | $span : parse-span($span); 15 | $span : map-merge($span, (location: 1)); 16 | 17 | $n : susy-get(span, $span); 18 | $columns : susy-get(columns, $span); 19 | $context : susy-count($columns); 20 | $flow : susy-get(flow, $span); 21 | 22 | $inside : is-inside($span); 23 | $from : from($flow); 24 | $line : floor($context / $n); 25 | $symmetrical : is-symmetrical($columns); 26 | 27 | $output: ( 28 | width : null, 29 | float : from, 30 | margin-before : null, 31 | margin-after : null, 32 | padding-before : null, 33 | padding-after : null, 34 | flow : $flow, 35 | ); 36 | 37 | @if $inside { 38 | $gutters: get-gutters($span); 39 | $output: map-merge($output, ( 40 | padding-before: map-get($gutters, before), 41 | padding-after: map-get($gutters, after), 42 | )); 43 | } 44 | 45 | @if $symmetrical { 46 | $output: map-merge($output, (width: get-span-width($span))); 47 | } 48 | 49 | $box : susy-get(box-sizing, $span); 50 | $global-box : if(susy-get(global-box-sizing) == 'border-box', true, false); 51 | 52 | @include susy-inspect(gallery, $inspect); 53 | 54 | // Collective Output 55 | @if $box == border-box or ($inside and not($box) and not($global-box)) { 56 | @include output((box-sizing: border-box)); 57 | } @else if $box == content-box { 58 | @include output((box-sizing: content-box)); 59 | } 60 | 61 | @include float-span-output($output...); 62 | 63 | // Individual Loop 64 | @for $item from 1 through $line { 65 | $nth: '#{$line}n + #{$item}'; 66 | &:nth-#{$selector}(#{$nth}) { 67 | // Individual Prep 68 | $output: ( 69 | width : if($symmetrical, null, get-span-width($span)), 70 | float : null, 71 | margin-before : get-isolation($span), 72 | margin-after : -100%, 73 | padding-before : null, 74 | padding-after : null, 75 | flow : $flow, 76 | ); 77 | 78 | // Individual Output 79 | @include float-span-output($output...); 80 | 81 | @if get-edge($span) == first { 82 | @include break; 83 | @include first($span); 84 | } @else { 85 | @include nobreak; 86 | } 87 | 88 | // Individual Location Increment 89 | $location: get-location($span) + $n; 90 | $location: if($location > $context, 1, $location); 91 | $span: map-merge($span, (location: $location)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_grids.scss: -------------------------------------------------------------------------------- 1 | // Grid Syntax 2 | // =========== 3 | 4 | 5 | // Layout 6 | // ------ 7 | // Set a new layout using a shorthand 8 | // - $layout: 9 | // - $clean: boolean 10 | @mixin layout( 11 | $layout, 12 | $clean: false 13 | ) { 14 | $inspect : $layout; 15 | $susy : _get-layout($layout, $clean) !global; 16 | 17 | @include susy-inspect(layout, $inspect); 18 | } 19 | 20 | 21 | // Use Grid 22 | // -------- 23 | // Use an arbitrary layout for a section of code 24 | // - $layout: 25 | // - $clean: boolean 26 | @mixin with-layout( 27 | $layout, 28 | $clean: false 29 | ) { 30 | $inspect : $layout; 31 | $old : $susy; 32 | $susy : _get-layout($layout, $clean) !global; 33 | 34 | @include susy-inspect(with-layout, $inspect); 35 | 36 | @content; 37 | 38 | $susy: $old !global; 39 | } 40 | 41 | 42 | // Layout 43 | // ------ 44 | // Return a parsed layout map based on shorthand syntax 45 | // - $layout: 46 | @function layout( 47 | $layout: $susy 48 | ) { 49 | @return parse-grid($layout); 50 | } 51 | 52 | 53 | // Get Layout 54 | // ---------- 55 | // Return a new layout based on current and given settings 56 | // - $layout: 57 | // - $clean: boolean 58 | @function _get-layout( 59 | $layout, 60 | $clean: false 61 | ) { 62 | $layout: layout($layout); 63 | @return if($clean, $layout, _susy-deep-merge($susy, $layout)); 64 | } 65 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_gutters.scss: -------------------------------------------------------------------------------- 1 | // Gutter Syntax 2 | // ============= 3 | 4 | 5 | // Gutters 6 | // ------- 7 | // Set gutters on an element. 8 | // - [$span] : 9 | @mixin gutters( 10 | $span: $susy 11 | ) { 12 | $inspect : $span; 13 | $span : parse-gutters($span); 14 | $_gutters : get-gutters($span); 15 | 16 | $_output: ( 17 | before: map-get($_gutters, before), 18 | after: map-get($_gutters, after), 19 | flow: susy-get(flow, $span), 20 | ); 21 | 22 | @include susy-inspect(gutters, $inspect); 23 | 24 | @if is-inside($span) { 25 | @include padding-output($_output...); 26 | } @else { 27 | @include margin-output($_output...); 28 | } 29 | } 30 | 31 | @mixin gutter( 32 | $span: $susy 33 | ) { 34 | @include gutters($span); 35 | } 36 | 37 | 38 | // Gutter 39 | // ------ 40 | // Return the width of a gutter. 41 | // - [$span] : 42 | @function gutter( 43 | $span: $susy 44 | ) { 45 | $span: parse-gutters($span); 46 | 47 | $_gutters: get-gutters($span); 48 | $_gutters: map-get($_gutters, before) or map-get($_gutters, after); 49 | 50 | @return $_gutters; 51 | } 52 | 53 | @function gutters( 54 | $span: $susy 55 | ) { 56 | @return gutter($span); 57 | } 58 | 59 | 60 | // Get Gutter Width 61 | // ---------------- 62 | // Return gutter width. 63 | // - [$context]: 64 | @function get-gutter-width( 65 | $context: $susy 66 | ) { 67 | $context : parse-gutters($context); 68 | 69 | $_gutters : susy-get(gutters, $context); 70 | $_gutter : susy-get(gutter-override, $context); 71 | 72 | @if $_gutters and ($_gutters > 0) and not($_gutter) { 73 | $_column-width: susy-get(column-width, $context); 74 | $_math: gutter-math($context); 75 | @if $_math == static { 76 | $_gutter: $_gutters * valid-column-math($_math, $_column-width); 77 | } @else { 78 | $_columns : susy-get(columns, $context); 79 | $_spread : if(is-split($context), wide, susy-get(spread, $context)); 80 | $_gutter : percentage($_gutters / susy-sum($_columns, $_gutters, $_spread)); 81 | } 82 | } 83 | 84 | $_gutter: if($_gutter == 'no-gutters' or $_gutter == 'no-gutter', null, $_gutter); 85 | 86 | @return $_gutter; 87 | } 88 | 89 | 90 | // Get Gutters 91 | // ----------- 92 | // Return before and after gutter values. 93 | // - [$context]: 94 | @function get-gutters( 95 | $context: $susy 96 | ) { 97 | $context : parse-gutters($context); 98 | 99 | $_gutter-position : susy-get(gutter-position, $context); 100 | $_gutter : get-gutter-width($context); 101 | 102 | $_return : (before: null, after: null); 103 | 104 | @if is-split($context) and $_gutter { 105 | $_gutter: $_gutter / 2; 106 | $_return: map-merge($_return, (before: $_gutter, after: $_gutter)); 107 | } @else { 108 | $_return: map-merge($_return, ($_gutter-position: $_gutter)); 109 | } 110 | 111 | @return $_return; 112 | } 113 | 114 | 115 | // Is Inside 116 | // --------- 117 | // Returns true if gutters are inside. 118 | // $context: 119 | @function is-inside( 120 | $context 121 | ) { 122 | $_inside: inside inside-static; 123 | $_gutter-position: susy-get(gutter-position, $context); 124 | 125 | @return if(index($_inside, $_gutter-position), true, false); 126 | } 127 | 128 | 129 | // Is Split 130 | // -------- 131 | // Returns true if gutters are split. 132 | // $context: 133 | @function is-split( 134 | $context 135 | ) { 136 | $_split: split inside inside-static; 137 | $_gutter-position: susy-get(gutter-position, $context); 138 | 139 | @return if(index($_split, $_gutter-position), true, false); 140 | } 141 | 142 | 143 | // Gutter Math 144 | // ----------- 145 | // Return the math to use for gutter calculations 146 | // $context: 147 | @function gutter-math( 148 | $context: $susy 149 | ) { 150 | $_return : susy-get(math, $context); 151 | $_return : if(susy-get(gutter-position, $context) == inside-static, static, $_return); 152 | 153 | @return $_return; 154 | } 155 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_isolate.scss: -------------------------------------------------------------------------------- 1 | // Isolation Syntax 2 | // ================ 3 | 4 | 5 | // Isolate [Mixin] 6 | // --------------- 7 | // Set isolation as an override. 8 | // - $location: 9 | @mixin isolate( 10 | $isolate: 1 11 | ) { 12 | $inspect: $isolate; 13 | 14 | $output: ( 15 | push: isolate($isolate), 16 | flow: susy-get(flow, $isolate), 17 | ); 18 | 19 | @include susy-inspect(isolate, $inspect); 20 | @include isolate-output($output...); 21 | } 22 | 23 | 24 | // Isolate [function] 25 | // ------------------ 26 | // Return an isolation offset width. 27 | // - $location: 28 | @function isolate( 29 | $isolate: 1 30 | ) { 31 | $isolate: parse-span($isolate); 32 | $isolation: susy-get(span, $isolate); 33 | 34 | @if $isolation and not(get-location($isolate)) { 35 | $new: ( 36 | span: null, 37 | location: $isolation, 38 | ); 39 | $isolate: map-merge($isolate, $new); 40 | } 41 | 42 | @return get-isolation($isolate); 43 | } 44 | 45 | 46 | // Get Isolation 47 | // ------------- 48 | // Return the isolation offset width 49 | // - $input: 50 | @function get-isolation( 51 | $input 52 | ) { 53 | $location : get-location($input); 54 | $columns : susy-get(columns, $input); 55 | $width : null; 56 | 57 | @if type-of($location) == number and not(unitless($location)) { 58 | $width: $location; 59 | } @else if $location { 60 | $push: $location - 1; 61 | @if $push > 0 { 62 | $push: map-merge($input, ( 63 | span: $push, 64 | location: 1, 65 | spread: wide, 66 | )); 67 | $width: get-span-width($push); 68 | } 69 | } 70 | 71 | @if susy-get(gutter-position, $input) == split 72 | and susy-get(gutters, $input) > 0 { 73 | $width: if($width == null, gutters($input), $width + gutters($input)); 74 | } 75 | 76 | @return $width or 0; 77 | } 78 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_margins.scss: -------------------------------------------------------------------------------- 1 | // Margin Syntax 2 | // ============= 3 | 4 | // Pre 5 | // --- 6 | // Add spanning-margins before an element. 7 | // - $span : 8 | @mixin pre( 9 | $span 10 | ) { 11 | $inspect: $span; 12 | $span : map-merge((spread: wide), parse-span($span)); 13 | $flow : susy-get(flow, $span); 14 | $split : if(susy-get(gutter-position, $span) == split, true, false); 15 | $gutter : gutter($span); 16 | $span : span($span); 17 | $width : if($split and $gutter, $span + $gutter, $span); 18 | 19 | @include susy-inspect(pre, $inspect); 20 | @include margin-output($width, null, $flow); 21 | } 22 | 23 | // Post 24 | // ---- 25 | // Add spanning-margins after an element. 26 | // - $span : 27 | @mixin post( 28 | $span 29 | ) { 30 | $inspect : $span; 31 | $span : map-merge((spread: wide), parse-span($span)); 32 | $flow : susy-get(flow, $span); 33 | $split : if(susy-get(gutter-position, $span) == split, true, false); 34 | $width : if($split, span($span) + gutter($span), span($span)); 35 | 36 | @include susy-inspect(post, $inspect); 37 | @include margin-output(null, $width, $flow); 38 | } 39 | 40 | // Push 41 | // ---- 42 | // Simple synonymn for pre. 43 | // - $span : 44 | @mixin push( 45 | $span 46 | ) { 47 | @include pre($span); 48 | } 49 | 50 | // Pull 51 | // ---- 52 | // Add negative spanning-margins before an element. 53 | // - $span : 54 | @mixin pull( 55 | $span 56 | ) { 57 | $inspect : $span; 58 | $span : map-merge((spread: wide), parse-span($span)); 59 | $flow : susy-get(flow, $span); 60 | $split : if(susy-get(gutter-position, $span) == split, true, false); 61 | $width : if($split, 0 - span($span) + gutter($span), 0 - span($span)); 62 | 63 | @include susy-inspect(pull, $inspect); 64 | @include margin-output($width, null, $flow); 65 | } 66 | 67 | // Squish 68 | // ------ 69 | // Add spanning-margins before and after an element. 70 | // - $pre : 71 | // - [$post] : 72 | @mixin squish( 73 | $pre, 74 | $post: false 75 | ) { 76 | $inspect : $pre, $post; 77 | $pre : map-merge((spread: wide), parse-span($pre)); 78 | 79 | @if $post { 80 | $post: map-merge((spread: wide), parse-span($post)); 81 | } @else { 82 | $span: susy-get(span, $pre); 83 | @if length($span) > 1 { 84 | $pre: map-merge($pre, (span: nth($span, 1))); 85 | $post: map-merge($pre, (span: nth($span, 2))); 86 | } @else { 87 | $post: $pre; 88 | } 89 | } 90 | 91 | @include susy-inspect(squish, $inspect...); 92 | @include pre($pre); 93 | @include post($post); 94 | } 95 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_padding.scss: -------------------------------------------------------------------------------- 1 | // Padding Syntax 2 | // ============== 3 | 4 | // Prefix 5 | // ------ 6 | // Add spanning-padding before an element. 7 | // - $span : 8 | @mixin prefix( 9 | $span 10 | ) { 11 | $inspect : $span; 12 | $span : map-merge((spread: wide), parse-span($span)); 13 | $flow : susy-get(flow, $span); 14 | $width : span($span); 15 | 16 | @if is-inside($span) { 17 | $gutter: gutter($span); 18 | $width: if($gutter and comparable($width, $gutter), $width + $gutter, $width); 19 | } 20 | 21 | @include susy-inspect(prefix, $inspect); 22 | @include padding-output($width, null, $flow); 23 | } 24 | 25 | // Suffix 26 | // ------ 27 | // Add spanning-padding after an element. 28 | // - $span : 29 | @mixin suffix( 30 | $span 31 | ) { 32 | $inspect : $span; 33 | $span : map-merge((spread: wide), parse-span($span)); 34 | $flow : susy-get(flow, $span); 35 | $width : span($span); 36 | 37 | @if is-inside($span) { 38 | $gutter: gutter($span); 39 | $width: if($gutter and comparable($width, $gutter), $width + $gutter, $width); 40 | } 41 | 42 | @include susy-inspect(suffix, $inspect); 43 | @include padding-output(null, $width, $flow); 44 | } 45 | 46 | // Pad 47 | // --- 48 | // Add spanning-padding before and after an element. 49 | // - $pre : 50 | // - [$post] : 51 | @mixin pad( 52 | $pre, 53 | $post: false 54 | ) { 55 | $inspect : $pre, $post; 56 | $pre : map-merge((spread: wide), parse-span($pre)); 57 | 58 | @if $post { 59 | $post: map-merge((spread: wide), parse-span($post)); 60 | } @else { 61 | $span: susy-get(span, $pre); 62 | @if length($span) > 1 { 63 | $pre: map-merge($pre, (span: nth($span, 1))); 64 | $post: map-merge($pre, (span: nth($span, 2))); 65 | } @else { 66 | $post: $pre; 67 | } 68 | } 69 | 70 | @include susy-inspect(pad, $inspect...); 71 | @include prefix($pre); 72 | @include suffix($post); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_rows.scss: -------------------------------------------------------------------------------- 1 | // Row Start & End 2 | // =============== 3 | 4 | // Break 5 | // ----- 6 | // Apply to any element that should force a line break. 7 | @mixin break { 8 | @include output((clear: both)); 9 | } 10 | 11 | 12 | // NoBreak 13 | // ------- 14 | // Cancel the break() effect, e.g. when using media queries. 15 | @mixin nobreak { 16 | @include output((clear: none)); 17 | } 18 | 19 | 20 | // Full 21 | // ---- 22 | // - [$context]: 23 | @mixin full( 24 | $context: $susy 25 | ) { 26 | $inspect : $context; 27 | @include susy-inspect(full, $inspect); 28 | @include span(full of parse-grid($context) break); 29 | } 30 | 31 | 32 | // First 33 | // ----- 34 | // - [$context]: 35 | @mixin first( 36 | $context: $susy 37 | ) { 38 | $inspect : $context; 39 | $context : parse-grid($context); 40 | $flow : susy-get(flow, $context); 41 | 42 | @include susy-inspect(first, $inspect); 43 | @if not(is-split($context)) { 44 | @include float-first($flow); 45 | } 46 | } 47 | 48 | @mixin alpha( 49 | $context: $susy 50 | ) { 51 | @include first($context); 52 | } 53 | 54 | 55 | // Last 56 | // ---- 57 | // - [$context]: 58 | @mixin last( 59 | $context: $susy 60 | ) { 61 | $inspect : $context; 62 | $context : parse-grid($context); 63 | 64 | @include susy-inspect(last, $inspect); 65 | 66 | $output: ( 67 | flow: susy-get(flow, $context), 68 | last-flow: susy-get(last-flow, $context), 69 | margin: if(is-split($context), null, 0), 70 | ); 71 | 72 | @include float-last($output...); 73 | } 74 | 75 | @mixin omega( 76 | $context: $susy 77 | ) { 78 | @include last($context); 79 | } 80 | 81 | 82 | // Get Edge 83 | // -------- 84 | // Calculate edge value based on location, if possible 85 | @function get-edge( 86 | $span 87 | ) { 88 | $span : parse-span($span); 89 | $edge : susy-get(edge, $span); 90 | 91 | @if not($edge) { 92 | $count: susy-count(susy-get(columns, $span)); 93 | $location: susy-get(location, $span); 94 | $n: susy-get(span, $span); 95 | 96 | $number: if(type-of($location) == number, true, false); 97 | $index: if($number and unitless($location), true, false); 98 | 99 | @if $n == $count { 100 | $edge: full; 101 | } @else if $location and $n and $index { 102 | @if $location == 1 { 103 | $edge: if($n == $count, full, first); 104 | } @else if $location + $n - 1 == $count { 105 | $edge: last; 106 | } 107 | } 108 | } 109 | 110 | @if $edge == alpha or $edge == omega { 111 | $edge: if($edge == alpha, first, last); 112 | } 113 | 114 | @return $edge; 115 | } 116 | 117 | 118 | // Get Location 119 | // ------------ 120 | // Calculate location value based on edge, if possible 121 | @function get-location( 122 | $span 123 | ) { 124 | $span : parse-span($span); 125 | $location : susy-get(location, $span); 126 | $edge : get-edge($span); 127 | $n : susy-get(span, $span); 128 | 129 | @if $edge and not($location) and type-of($n) == number and unitless($n) { 130 | @if $edge == first { 131 | $location: 1; 132 | } @else if $edge == last { 133 | $location: susy-count(susy-get(columns, $span)) - $n + 1; 134 | } 135 | } 136 | 137 | @return $location 138 | } 139 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_settings.scss: -------------------------------------------------------------------------------- 1 | // Susy Settings 2 | // ============= 3 | 4 | // Susy Language Defaults 5 | // ---------------------- 6 | // - PRIVATE 7 | @include susy-defaults(( 8 | container: auto, 9 | math: fluid, 10 | output: float, 11 | container-position: center, 12 | gutter-position: after, 13 | global-box-sizing: content-box, 14 | debug: ( 15 | image: hide, 16 | color: rgba(#66f, .25), 17 | output: background, 18 | toggle: top right, 19 | ), 20 | )); 21 | 22 | 23 | // Valid Keyword Values 24 | // -------------------- 25 | // - PRIVATE: DONT'T TOUCH 26 | $susy-keywords: ( 27 | container: auto, 28 | math: static fluid, 29 | output: isolate float, 30 | container-position: left center right, 31 | flow: ltr rtl, 32 | gutter-position: before after split inside inside-static, 33 | box-sizing: border-box content-box, 34 | span: full, 35 | edge: first alpha last omega full, 36 | spread: narrow wide wider, 37 | gutter-override: no-gutters no-gutter, 38 | role: nest, 39 | clear: break nobreak, 40 | debug image: show hide show-columns show-baseline, 41 | debug output: background overlay, 42 | ); 43 | 44 | 45 | // Parse Susy Keywords and Maps 46 | // ---------------------------- 47 | @function parse-settings( 48 | $short: $susy 49 | ) { 50 | $_return: (); 51 | 52 | @if type-of($short) == map { 53 | $_return: $short; 54 | } @else { 55 | @each $item in $short { 56 | // strings 57 | @if type-of($item) == string { 58 | @each $key, $value in $susy-keywords { 59 | @if index($value, $item) { 60 | $_key-value: append($key, $item); 61 | $_return: _susy-deep-set($_return, $_key-value...); 62 | } 63 | } 64 | // maps 65 | } @else if type-of($item) == map { 66 | $_return: map-merge($_return, $item); 67 | } 68 | } 69 | } 70 | 71 | @return $_return; 72 | } 73 | 74 | 75 | // Parse Columns & Gutters 76 | // ----------------------- 77 | @function parse-layout( 78 | $short 79 | ) { 80 | $_return: (); 81 | $_columns: (); 82 | $_gutters: null; 83 | 84 | @if not(unitless(nth(nth($short, 1), 1))) { 85 | $_gutters: nth($short, 1); 86 | } @else { 87 | $_columns: (columns: nth($short, 1)); 88 | $_gutters: if(length($short) > 1, nth($short, 2), $_gutters); 89 | } 90 | 91 | @if type-of($_gutters) == list and length($_gutters) > 0 { 92 | $_gutters: ( 93 | gutters: nth($_gutters, 2) / nth($_gutters, 1), 94 | column-width: nth($_gutters, 1), 95 | ); 96 | } @else { 97 | $_gutters: if($_gutters, (gutters: $_gutters), ()); 98 | } 99 | 100 | $_return: map-merge($_return, $_columns); 101 | $_return: map-merge($_return, $_gutters); 102 | 103 | @return $_return; 104 | } 105 | 106 | 107 | // Parse Grid/Context 108 | // ------------------ 109 | @function parse-grid( 110 | $short: $susy 111 | ) { 112 | $_return: parse-settings($short); 113 | $_layout: (); 114 | 115 | @if type-of($short) == map { 116 | $_return: $short; 117 | } @else { 118 | @each $item in $short { 119 | // number or list 120 | @if type-of($item) == number or type-of($item) == list { 121 | @if type-of($item) == list or unitless($item) { 122 | $_layout: append($_layout, $item); 123 | } @else { 124 | $_return: map-merge($_return, (container: $item)); 125 | } 126 | } 127 | } 128 | 129 | $_layout: if(length($_layout) > 0, parse-layout($_layout), $_layout); 130 | } 131 | 132 | @return map-merge($_return, $_layout); 133 | } 134 | 135 | 136 | // Parse Span 137 | // ---------- 138 | @function parse-span( 139 | $short, 140 | $key: span 141 | ) { 142 | $_return: (); 143 | 144 | @if type-of($short) == map { 145 | $_return: $short; 146 | } @else { 147 | $_at: index($short, at); 148 | 149 | @if $_at { 150 | $_loci: $_at + 1; 151 | $_location: nth($short, $_loci); 152 | $_return: map-merge($_return, (location: $_location)); 153 | $short: set-nth($short, $_at, null); 154 | $short: set-nth($short, $_loci, null); 155 | } 156 | 157 | $_i: 1; 158 | $_span: (); 159 | 160 | @while $_i <= length($short) { 161 | $_this: nth($short, $_i); 162 | 163 | @if type-of($_this) == number { 164 | $_span: append($_span, $_this); 165 | $short: set-nth($short, $_i, null); 166 | } @else if $_this == of { 167 | $short: set-nth($short, $_i, null); 168 | $_i: length($short) + 1; 169 | } 170 | 171 | $_i: $_i + 1; 172 | } 173 | 174 | @if length($_span) > 0 { 175 | $_span: if(length($_span) == 1, nth($_span, 1), $_span); 176 | $_return: map-merge($_return, ($key: $_span)); 177 | } 178 | 179 | $_return: map-merge($_return, parse-grid($short)); 180 | } 181 | 182 | @return $_return; 183 | } 184 | 185 | 186 | // Parse Gutters 187 | // ------------- 188 | @function parse-gutters( 189 | $short: $susy 190 | ) { 191 | $_gutters: parse-span($short, gutter-override); 192 | $_span: susy-get(gutter-override, $_gutters); 193 | 194 | @if $_span and not(map-get($_gutters, columns)) { 195 | $_context: (); 196 | $_new: (); 197 | 198 | @each $item in $_span { 199 | @if type-of($item) == number and unitless($item) { 200 | $_context: append($_context, $item); 201 | } @else { 202 | $_new: append($_new, $item); 203 | } 204 | } 205 | 206 | $_context: parse-grid($_context); 207 | $_new: if(length($_new) == 0, null, $_new); 208 | $_new: if(length($_new) == 1, nth($_new, 1), $_new); 209 | $_new: (gutter-override: if($_new != $_span, $_new, $_span)); 210 | 211 | $_gutters: map-merge($_gutters, $_new); 212 | $_gutters: map-merge($_gutters, $_context); 213 | } 214 | 215 | @return $_gutters; 216 | } 217 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_span.scss: -------------------------------------------------------------------------------- 1 | // Span Syntax 2 | // =========== 3 | 4 | // Span [mixin] 5 | // ------------ 6 | // Set a spanning element using shorthand syntax. 7 | // - $span : 8 | @mixin span( 9 | $span 10 | ) { 11 | $inspect: $span; 12 | $span: parse-span($span); 13 | $output: span-math($span); 14 | $nesting: susy-get(span, $span); 15 | $clear: susy-get(clear, $span); 16 | 17 | $box: susy-get(box-sizing, $span); 18 | $content-box: if(susy-get(global-box-sizing) != 'border-box', true, false); 19 | $box: $box or if(is-inside($span) and $content-box, border-box, null); 20 | 21 | @if $clear == break { 22 | @include break; 23 | } @else if $clear == nobreak { 24 | @include nobreak; 25 | } 26 | 27 | @include susy-inspect(span, $inspect); 28 | @include output((box-sizing: $box)); 29 | @include float-span-output($output...); 30 | 31 | @if valid-columns($nesting, silent) { 32 | @include nested($span) { @content; } 33 | } @else { 34 | @content; 35 | } 36 | } 37 | 38 | // Span [function] 39 | // --------------- 40 | // Return the width of a span. 41 | // - $span : 42 | @function span( 43 | $span 44 | ) { 45 | @return get-span-width($span); 46 | } 47 | 48 | // Span Math 49 | // --------- 50 | // Get all the span results. 51 | // - $span: 52 | @function span-math( 53 | $span 54 | ) { 55 | $nest : if(susy-get(role, $span) == nest, true, false); 56 | $split-nest : if(is-split($span) and $nest, true, false); 57 | $edge : get-edge($span); 58 | $location : get-location($span); 59 | 60 | $float : from; 61 | $padding-before : null; 62 | $padding-after : null; 63 | $margin-before : null; 64 | $margin-after : null; 65 | 66 | // calculate widths 67 | $spread: index(map-values($span), spread); 68 | $span: if($split-nest and not($spread), map-merge($span, (spread: wide)), $span); 69 | $width: get-span-width($span); 70 | $gutters: get-gutters($span); 71 | 72 | // apply gutters 73 | @if is-inside($span) { 74 | @if not(susy-get(role, $span)) { 75 | $padding-before: map-get($gutters, before); 76 | $padding-after: map-get($gutters, after); 77 | } 78 | } @else { 79 | @if not($split-nest) { 80 | $margin-before: map-get($gutters, before); 81 | $margin-after: map-get($gutters, after); 82 | } 83 | } 84 | 85 | // special margin handling 86 | @if susy-get(output, $span) == isolate and $location { 87 | $margin-before: get-isolation($span); 88 | $margin-after: -100%; 89 | } @else if $edge { 90 | $is-split: is-split($span); 91 | $pos: susy-get(gutter-position, $span); 92 | 93 | @if $edge == last { 94 | $float: susy-get(last-flow, $span); 95 | } 96 | 97 | @if not($is-split) { 98 | @if $edge == full or ($edge == first and $pos == before) { 99 | $margin-before: 0; 100 | } 101 | @if $edge == full or ($edge == last and $pos == after) { 102 | $margin-after: 0; 103 | } 104 | } 105 | 106 | } 107 | 108 | @return ( 109 | width : $width, 110 | float : $float, 111 | margin-before : $margin-before, 112 | margin-after : $margin-after, 113 | padding-before : $padding-before, 114 | padding-after : $padding-after, 115 | flow : susy-get(flow, $span), 116 | ); 117 | } 118 | 119 | // Get Span Width 120 | // -------------- 121 | // Return span width. 122 | // - $span: 123 | @function get-span-width( 124 | $span 125 | ) { 126 | $span : parse-span($span); 127 | 128 | $n : susy-get(span, $span); 129 | $location : get-location($span); 130 | $columns : susy-get(columns, $span); 131 | $gutters : susy-get(gutters, $span); 132 | $spread : susy-get(spread, $span); 133 | 134 | $context : null; 135 | $span-sum : null; 136 | $width : null; 137 | 138 | @if $n == 'full' { 139 | $pos: susy-get(gutter-position, $span); 140 | $role: susy-get(role, $span); 141 | $n: if($pos == split and $role != nest, susy-count($columns), 100%); 142 | } 143 | 144 | @if type-of($n) != number { 145 | @warn "(#{type-of($n)}) #{$n} is not a valid span."; 146 | } @else if unitless($n) { 147 | $context: susy-sum($columns, $gutters, if(is-split($span), wide, narrow)); 148 | $spread: if(is-inside($span), $spread or wide, $spread); 149 | $span-sum: susy($n, $location, $columns, $gutters, $spread); 150 | 151 | $_math: susy-get(math, $span); 152 | $_column-width: susy-get(column-width, $span); 153 | @if $_math == static { 154 | $width: $span-sum * valid-column-math($_math, $_column-width); 155 | } @else { 156 | $width: percentage($span-sum / $context); 157 | } 158 | } @else { 159 | $width: $n; 160 | } 161 | 162 | @return $width; 163 | } 164 | -------------------------------------------------------------------------------- /sass/susy/language/susy/_validation.scss: -------------------------------------------------------------------------------- 1 | // Validation 2 | // ========== 3 | 4 | 5 | // Validate Column Math 6 | // -------------------- 7 | @function valid-column-math( 8 | $math, 9 | $column-width 10 | ) { 11 | @if $math == static and not($column-width) { 12 | @warn 'Static math requires a valid column-width setting.'; 13 | } @else { 14 | @return $column-width; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sass/susy/output/_float.scss: -------------------------------------------------------------------------------- 1 | // Float API 2 | // ========= 3 | 4 | @import "shared"; 5 | 6 | @import "float/container"; 7 | @import "float/span"; 8 | @import "float/end"; 9 | @import "float/isolate"; 10 | -------------------------------------------------------------------------------- /sass/susy/output/_shared.scss: -------------------------------------------------------------------------------- 1 | // Shared API 2 | // ========== 3 | 4 | @import "support"; 5 | 6 | @import "shared/inspect"; 7 | @import "shared/output"; 8 | @import "shared/direction"; 9 | @import "shared/background"; 10 | @import "shared/container"; 11 | @import "shared/margins"; 12 | @import "shared/padding"; 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sass/susy/output/_support.scss: -------------------------------------------------------------------------------- 1 | // Susy Browser Support 2 | // ==================== 3 | 4 | @import "support/support"; 5 | @import "support/prefix"; 6 | @import "support/background"; 7 | @import "support/box-sizing"; 8 | @import "support/rem"; 9 | @import "support/clearfix"; 10 | -------------------------------------------------------------------------------- /sass/susy/output/float/_container.scss: -------------------------------------------------------------------------------- 1 | // Float Container API 2 | // =================== 3 | 4 | // Float Container 5 | // --------------- 6 | // - [$width] : 7 | // - [$justify] : left | center | right 8 | // - [$math] : fluid | static 9 | @mixin float-container( 10 | $width, 11 | $justify: auto auto, 12 | $property: max-width 13 | ) { 14 | @include susy-clearfix; 15 | @include container-output($width, $justify, $property); 16 | } 17 | -------------------------------------------------------------------------------- /sass/susy/output/float/_end.scss: -------------------------------------------------------------------------------- 1 | // Float Ends API 2 | // ============== 3 | 4 | // Susy End Defaults 5 | // ----------------- 6 | // - PRIVATE 7 | @include susy-defaults(( 8 | last-flow: to, 9 | )); 10 | 11 | // Float Last 12 | // ---------- 13 | // - [$flow] : ltr | rtl 14 | @mixin float-last( 15 | $flow: map-get($susy-defaults, flow), 16 | $last-flow: map-get($susy-defaults, last-flow), 17 | $margin: 0 18 | ) { 19 | $to: to($flow); 20 | 21 | $output: ( 22 | float: if($last-flow == to, $to, null), 23 | margin-#{$to}: $margin, 24 | ); 25 | 26 | @include output($output); 27 | } 28 | 29 | // Float First 30 | // ----------- 31 | // - [$flow] : ltr | rtl 32 | @mixin float-first( 33 | $flow: map-get($susy-defaults, flow) 34 | ) { 35 | $output: ( 36 | margin-#{from($flow)}: 0, 37 | ); 38 | 39 | @include output($output); 40 | } 41 | -------------------------------------------------------------------------------- /sass/susy/output/float/_isolate.scss: -------------------------------------------------------------------------------- 1 | // Float Isolation API 2 | // =================== 3 | 4 | // Isolate Output 5 | // -------------- 6 | // - $push : 7 | // - [$flow] : ltr | rtl 8 | @mixin isolate-output( 9 | $push, 10 | $flow: map-get($susy-defaults, flow) 11 | ) { 12 | $to: to($flow); 13 | $from: from($flow); 14 | 15 | $output: ( 16 | float: $from, 17 | margin-#{$from}: $push, 18 | margin-#{$to}: -100%, 19 | ); 20 | 21 | @include output($output); 22 | } 23 | -------------------------------------------------------------------------------- /sass/susy/output/float/_span.scss: -------------------------------------------------------------------------------- 1 | // Float Span API 2 | // ============== 3 | 4 | // Float Span Output 5 | // ----------------- 6 | // - $width : 7 | // - [$float] : from | to 8 | // - [$margin-before] : 9 | // - [$margin-after] : 10 | // - [$padding-before] : 11 | // - [$padding-after] : 12 | // - [$flow] : ltr | rtl 13 | @mixin float-span-output( 14 | $width, 15 | $float : from, 16 | $margin-before : null, 17 | $margin-after : null, 18 | $padding-before : null, 19 | $padding-after : null, 20 | $flow : map-get($susy-defaults, flow) 21 | ) { 22 | $to : to($flow); 23 | $from : from($flow); 24 | 25 | $output: ( 26 | width: $width, 27 | float: if($float == to, $to, null) or if($float == from, $from, null), 28 | margin-#{$from}: $margin-before, 29 | margin-#{$to}: $margin-after, 30 | padding-#{$from}: $padding-before, 31 | padding-#{$to}: $padding-after, 32 | ); 33 | 34 | @include output($output); 35 | } 36 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_background.scss: -------------------------------------------------------------------------------- 1 | // Grid Background API 2 | // =================== 3 | // - Sub-pixel rounding can lead to several pixels variation between browsers. 4 | 5 | // Grid Background Output 6 | // ---------------------- 7 | // - $image: background-image 8 | // - $size: background-size 9 | // - $clip: background-clip 10 | // - [$flow]: ltr | rtl 11 | @mixin background-grid-output ( 12 | $image, 13 | $size: null, 14 | $clip: null, 15 | $flow: map-get($susy-defaults, flow) 16 | ) { 17 | $output: ( 18 | background-image: $image, 19 | background-size: $size, 20 | background-origin: $clip, 21 | background-clip: $clip, 22 | background-position: from($flow) top, 23 | ); 24 | 25 | @include output($output); 26 | } 27 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_container.scss: -------------------------------------------------------------------------------- 1 | // Shared Container API 2 | // ==================== 3 | 4 | // Container Output 5 | // ---------------- 6 | // - [$width] : 7 | // - [$justify] : left | center | right 8 | // - [$math] : fluid | static 9 | @mixin container-output( 10 | $width, 11 | $justify: auto auto, 12 | $property: max-width 13 | ) { 14 | $output: ( 15 | #{$property}: $width or 100%, 16 | margin-left: nth($justify, 1), 17 | margin-right: nth($justify, 2), 18 | ); 19 | 20 | @include output($output); 21 | } 22 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_direction.scss: -------------------------------------------------------------------------------- 1 | // Direction Helpers 2 | // ================= 3 | 4 | // Susy Flow Defaults 5 | // ------------------ 6 | // - PRIVATE 7 | @include susy-defaults(( 8 | flow: ltr, 9 | )); 10 | 11 | // Get Direction 12 | // ------------- 13 | // Return the 'from' or 'to' direction of a ltr or rtl flow. 14 | // - [$flow] : ltr | rtl 15 | // - [$key] : from | to 16 | @function get-direction( 17 | $flow: map-get($susy-defaults, flow), 18 | $key: from 19 | ) { 20 | $return: if($flow == rtl, (from: right, to: left), (from: left, to: right)); 21 | @return map-get($return, $key); 22 | } 23 | 24 | // To 25 | // -- 26 | // Return the 'to' direction of a flow 27 | // - [$flow] : ltr | rtl 28 | @function to( 29 | $flow: map-get($susy-defaults, flow) 30 | ) { 31 | @return get-direction($flow, to); 32 | } 33 | 34 | // From 35 | // ---- 36 | // Return the 'from' direction of a flow 37 | // - [$flow] : ltr | rtl 38 | @function from( 39 | $flow: map-get($susy-defaults, flow) 40 | ) { 41 | @return get-direction($flow, from); 42 | } 43 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_inspect.scss: -------------------------------------------------------------------------------- 1 | // Debugging 2 | // ========= 3 | 4 | // Susy Inspect 5 | // ------------ 6 | // Output arguments passed to a inspect. 7 | // - $mixin : 8 | // - $inspec : 9 | 10 | @mixin susy-inspect($mixin, $inspect...) { 11 | $show: false; 12 | 13 | @each $item in $inspect { 14 | @if index($item, inspect) { 15 | $show: true; 16 | } 17 | } 18 | 19 | @if $show or susy-get(debug inspect) { 20 | -susy-#{$mixin}: inspect($inspect); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_margins.scss: -------------------------------------------------------------------------------- 1 | // Margins API 2 | // =========== 3 | 4 | // Margin Output 5 | // ------------- 6 | // - $before : 7 | // - $after : 8 | // - [$flow] : ltr | rtl 9 | @mixin margin-output( 10 | $before, 11 | $after, 12 | $flow: map-get($susy-defaults, flow) 13 | ) { 14 | $to: to($flow); 15 | $from: from($flow); 16 | 17 | $output: ( 18 | margin-#{$from}: $before, 19 | margin-#{$to}: $after, 20 | ); 21 | 22 | @include output($output); 23 | } 24 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_output.scss: -------------------------------------------------------------------------------- 1 | // Output 2 | // ====== 3 | 4 | // Output 5 | // ------ 6 | // Output CSS with proper browser support. 7 | // - $styles : 8 | @mixin output( 9 | $styles 10 | ) { 11 | @each $prop, $val in $styles { 12 | @include susy-support($prop, $val); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sass/susy/output/shared/_padding.scss: -------------------------------------------------------------------------------- 1 | // Padding API 2 | // =========== 3 | 4 | // Padding Output 5 | // -------------- 6 | // - $before : 7 | // - $after : 8 | // - [$flow] : ltr | rtl 9 | @mixin padding-output( 10 | $before, 11 | $after, 12 | $flow: map-get($susy-defaults, flow) 13 | ) { 14 | $to: to($flow); 15 | $from: from($flow); 16 | 17 | $output: ( 18 | padding-#{$from}: $before, 19 | padding-#{$to}: $after, 20 | ); 21 | 22 | @include output($output); 23 | } 24 | -------------------------------------------------------------------------------- /sass/susy/output/support/_background.scss: -------------------------------------------------------------------------------- 1 | // Background Properties 2 | // ===================== 3 | 4 | // Susy Background Image 5 | // --------------------- 6 | // Check for an existing support mixin, or provide a simple fallback. 7 | // - $image: 8 | @mixin susy-background-image( 9 | $image 10 | ) { 11 | @if susy-support(background-image, (mixin: background-image), $warn: false) { 12 | @include background-image($image...); 13 | } @else { 14 | background-image: $image; 15 | } 16 | } 17 | 18 | // Susy Background Size 19 | // --------------------- 20 | // Check for an existing support mixin, or provide a simple fallback. 21 | // - $image: 22 | @mixin susy-background-size( 23 | $size 24 | ) { 25 | @if susy-support(background-options, (mixin: background-size)) { 26 | @include background-size($size); 27 | } @else { 28 | background-size: $size; 29 | } 30 | } 31 | 32 | // Susy Background Origin 33 | // ---------------------- 34 | // Check for an existing support mixin, or provide a simple fallback. 35 | // - $image: 36 | @mixin susy-background-origin( 37 | $origin 38 | ) { 39 | @if susy-support(background-options, (mixin: background-origin)) { 40 | @include background-origin($origin); 41 | } @else { 42 | background-origin: $origin; 43 | } 44 | } 45 | 46 | // Susy Background Clip 47 | // -------------------- 48 | // Check for an existing support mixin, or provide a simple fallback. 49 | // - $image: 50 | @mixin susy-background-clip( 51 | $clip 52 | ) { 53 | @if susy-support(background-options, (mixin: background-clip)) { 54 | @include background-clip($clip); 55 | } @else { 56 | background-clip: $clip; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sass/susy/output/support/_box-sizing.scss: -------------------------------------------------------------------------------- 1 | // Box Sizing 2 | // ========== 3 | 4 | // Box Sizing 5 | // ---------- 6 | // Check for an existing support mixin, or provide a simple fallback. 7 | // - $model: 8 | @mixin susy-box-sizing( 9 | $model: content-box 10 | ) { 11 | @if $model { 12 | @if susy-support(box-sizing, (mixin: box-sizing), $warn: false) { 13 | @include box-sizing($model); 14 | } @else { 15 | $prefix: (moz, webkit, official); 16 | @include susy-prefix(box-sizing, $model, $prefix); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sass/susy/output/support/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // Susy Fallback Clearfix 2 | // ====================== 3 | 4 | 5 | // Clearfix 6 | // -------- 7 | // Check for an existing support mixin, or provide a simple fallback. 8 | @mixin susy-clearfix { 9 | @if susy-support(clearfix, (mixin: clearfix)) { 10 | @include clearfix; 11 | } @else { 12 | &:after { 13 | content: " "; 14 | display: block; 15 | clear: both; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sass/susy/output/support/_prefix.scss: -------------------------------------------------------------------------------- 1 | // Susy Prefix 2 | // =========== 3 | 4 | // Prefix 5 | // ------ 6 | // Output simple prefixed properties. 7 | // - $prop : 8 | // - $val : 9 | // - [$prefix] : 10 | @mixin susy-prefix( 11 | $prop, 12 | $val, 13 | $prefix: official 14 | ) { 15 | @each $fix in $prefix { 16 | $fix: if($fix == official or not($fix), $prop, '-#{$fix}-#{$prop}'); 17 | @include susy-rem($fix, $val); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sass/susy/output/support/_rem.scss: -------------------------------------------------------------------------------- 1 | // rem Support 2 | // =========== 3 | 4 | // rem 5 | // --- 6 | // Check for an existing support mixin, or output directly. 7 | // - $prop : 8 | // - $val : 9 | @mixin susy-rem( 10 | $prop, 11 | $val 12 | ) { 13 | $_reqs: ( 14 | variable: rhythm-unit rem-with-px-fallback, 15 | mixin: rem, 16 | ); 17 | @if susy-support(rem, $_reqs, $warn: false) and $rhythm-unit == rem { 18 | @include rem($prop, $val); 19 | } @else { 20 | #{$prop}: $val; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sass/susy/output/support/_support.scss: -------------------------------------------------------------------------------- 1 | // Browser Support 2 | // =============== 3 | 4 | // Susy Support Defaults 5 | // --------------------- 6 | @include susy-defaults(( 7 | use-custom: ( 8 | clearfix: false, 9 | background-image: true, 10 | background-options: false, 11 | breakpoint: true, 12 | box-sizing: true, 13 | rem: true, 14 | ), 15 | )); 16 | 17 | 18 | // Susy Support [mixin] 19 | // -------------------- 20 | // Send property-value pairs to the proper support modules. 21 | // - $prop : 22 | // - $val : 23 | @mixin susy-support( 24 | $prop, 25 | $val 26 | ) { 27 | // Background Support 28 | @if $prop == background-image { 29 | @include susy-background-image($val); 30 | } @else if $prop == background-size { 31 | @include susy-background-size($val); 32 | } @else if $prop == background-origin { 33 | @include susy-background-origin($val); 34 | } @else if $prop == background-clip { 35 | @include susy-background-clip($val); 36 | } 37 | 38 | // Box-Sizing Support 39 | @else if $prop == box-sizing { 40 | @include susy-box-sizing($val); 41 | } 42 | 43 | // Rem Support 44 | @else { 45 | @include susy-rem($prop, $val); 46 | } 47 | } 48 | 49 | 50 | // Susy Support [function] 51 | // ----------------------- 52 | // Check for support of a feature. 53 | // - $feature : 54 | // - e.g "rem" or "box-sizing" 55 | // - $requirements : 56 | // - e.g (variable: rem-with-px-fallback, mixin: rem) 57 | // - $warn : 58 | @function susy-support( 59 | $feature, 60 | $requirements: (), 61 | $warn: true 62 | ) { 63 | $_support: susy-get(use-custom $feature); 64 | 65 | @if $_support { 66 | $_fail: false; 67 | 68 | @each $_type, $_req in $requirements { 69 | @each $_i in $_req { 70 | $_pass: call(#{$_type}-exists, $_i); 71 | 72 | @if not($_pass) { 73 | $_fail: true; 74 | @if $warn { 75 | @warn "You requested custom support of #{$feature}, but the #{$_i} #{$_type} is not available."; 76 | } 77 | } 78 | } 79 | } 80 | 81 | $_support: if($_fail, false, $_support); 82 | } 83 | 84 | @return $_support; 85 | } 86 | -------------------------------------------------------------------------------- /sass/susy/su/_grid.scss: -------------------------------------------------------------------------------- 1 | // Column math 2 | // =========== 3 | 4 | 5 | // Is Symmetrical 6 | // -------------- 7 | // Returns true if a grid is symmetrical. 8 | // - [$columns] : | 9 | @function is-symmetrical( 10 | $columns: susy-get(columns) 11 | ) { 12 | $columns: valid-columns($columns); 13 | @return if(type-of($columns) == number, $columns, null); 14 | } 15 | 16 | 17 | // Susy Count 18 | // ---------- 19 | // Find the number of columns in a given layout 20 | // - [$columns] : | 21 | @function susy-count( 22 | $columns: susy-get(columns) 23 | ) { 24 | $columns: valid-columns($columns); 25 | @return is-symmetrical($columns) or length($columns); 26 | } 27 | 28 | 29 | // Susy Sum 30 | // -------- 31 | // Find the total sum of column-units in a layout 32 | // - [$columns] : | 33 | // - [$gutters] : 34 | // - [$spread] : false/narrow | wide | wider 35 | @function susy-sum( 36 | $columns : susy-get(columns), 37 | $gutters : susy-get(gutters), 38 | $spread : false 39 | ) { 40 | $columns: valid-columns($columns); 41 | $gutters: valid-gutters($gutters); 42 | 43 | $spread: if($spread == wide, 0, if($spread == wider, 1, -1)); 44 | $gutter-sum: (susy-count($columns) + $spread) * $gutters; 45 | $column-sum: is-symmetrical($columns); 46 | 47 | @if not($column-sum) { 48 | @each $column in $columns { 49 | $column-sum: ($column-sum or 0) + $column; 50 | } 51 | } 52 | 53 | @return $column-sum + $gutter-sum; 54 | } 55 | 56 | 57 | // Susy Slice 58 | // ---------- 59 | // Return a subset of columns at a given location. 60 | // - $span : 61 | // - $location : 62 | // - [$columns] : | 63 | @function susy-slice( 64 | $span, 65 | $location, 66 | $columns: susy-get(columns) 67 | ) { 68 | $columns: valid-columns($columns); 69 | $sub-columns: $span; 70 | 71 | @if not(is-symmetrical($columns)) { 72 | $location: $location or 1; 73 | $sub-columns: (); 74 | @for $i from $location to ($location + $span) { 75 | $sub-columns: append($sub-columns, nth($columns, $i)); 76 | } 77 | } 78 | 79 | @return $sub-columns; 80 | } 81 | 82 | 83 | // Susy 84 | // ---- 85 | // Find the sum of a column-span. 86 | // - $span : 87 | // - $location : 88 | // - [$columns] : | 89 | // - [$gutters] : 90 | // - [$spread] : false/narrow | wide | wider 91 | @function susy( 92 | $span, 93 | $location : false, 94 | $columns : susy-get(columns), 95 | $gutters : susy-get(gutters), 96 | $spread : false 97 | ) { 98 | $columns: valid-columns($columns); 99 | $gutters: valid-gutters($gutters); 100 | $span: susy-slice($span, $location, $columns); 101 | 102 | @return susy-sum($span, $gutters, $spread); 103 | } 104 | -------------------------------------------------------------------------------- /sass/susy/su/_settings.scss: -------------------------------------------------------------------------------- 1 | // Settings 2 | // ======== 3 | 4 | // Version 5 | // ------- 6 | $su-version: 1.1; 7 | 8 | 9 | // Default Settings 10 | // ---------------- 11 | // PRIVATE: The basic settings 12 | $susy-defaults: ( 13 | columns: 4, 14 | gutters: .25, 15 | ); 16 | 17 | 18 | // User Settings 19 | // ------------- 20 | // - Define the $susy variable with a map of your own settings. 21 | // - Set EITHER $column-width OR $container 22 | // - Use $column-width for static layouts 23 | $susy: () !default; 24 | 25 | 26 | // Susy Defaults 27 | // ------------- 28 | // PRIVATE: Add defaults to Susy 29 | @mixin susy-defaults( 30 | $defaults 31 | ) { 32 | $susy-defaults: map-merge($susy-defaults, $defaults) !global; 33 | } 34 | 35 | 36 | // Susy Set 37 | // -------- 38 | // Change one setting 39 | // - $key : setting name 40 | // - $value : setting value 41 | @mixin susy-set( 42 | $key-value... 43 | ) { 44 | $susy: _susy-deep-set($susy, $key-value...) !global; 45 | } 46 | 47 | 48 | // Susy Get 49 | // -------- 50 | // Return one setting from a grid 51 | // - $key : 52 | // - $layout : 53 | @function susy-get( 54 | $key, 55 | $layout: map-merge($susy-defaults, $susy) 56 | ) { 57 | $layout: parse-grid($layout); 58 | $_options: $layout $susy $susy-defaults; 59 | $_break: false; 60 | $_return: null; 61 | 62 | @each $opt in $_options { 63 | @if type-of($opt) == map and not($_break) { 64 | $_keyset: _susy-deep-has-key($opt, $key...); 65 | @if $_keyset { 66 | $_return: _susy-deep-get($opt, $key...); 67 | $_break: true; 68 | } 69 | } 70 | } 71 | 72 | @return $_return; 73 | } 74 | -------------------------------------------------------------------------------- /sass/susy/su/_utilities.scss: -------------------------------------------------------------------------------- 1 | // Map Functions 2 | // ============= 3 | 4 | 5 | // Truncate List 6 | // ------------- 7 | // - Return a list, truncated to a given length 8 | @function _susy-truncate-list( 9 | $list, 10 | $length 11 | ) { 12 | $_return: (); 13 | 14 | @for $i from 1 through length($list) { 15 | $_return: if($i <= $length, append($_return, nth($list, $i)), $_return); 16 | } 17 | 18 | @return $_return; 19 | } 20 | 21 | 22 | // Deep Get 23 | // -------- 24 | // - Return a value deep in nested maps 25 | @function _susy-deep-get( 26 | $map, 27 | $keys... 28 | ) { 29 | $_return: $map; 30 | 31 | @each $key in $keys { 32 | @if type-of($_return) == map { 33 | $_return: map-get($_return, $key); 34 | } 35 | } 36 | 37 | @return $_return; 38 | } 39 | 40 | 41 | // Deep Set 42 | // -------- 43 | // - Set a value deep in nested maps 44 | @function _susy-deep-set( 45 | $map, 46 | $keys-value... 47 | ) { 48 | $_value: nth($keys-value, -1); 49 | $_keys: _susy-truncate-list($keys-value, length($keys-value) - 1); 50 | $_length: length($_keys); 51 | $_return: (); 52 | 53 | @for $i from 1 through $_length { 54 | $_n: 0 - $i; 55 | $_level: _susy-truncate-list($_keys, $_length + $_n); 56 | $_level: _susy-deep-get($map, $_level...); 57 | $_merge: nth($_keys, $_n); 58 | $_merge: ($_merge: $_value); 59 | $_return: if($_level, map-merge($_level, $_merge), $_merge); 60 | $_value: $_return; 61 | } 62 | 63 | @return $_return; 64 | } 65 | 66 | 67 | // Deep Merge 68 | // ---------- 69 | // Return 2 objects of any depth, merged 70 | @function _susy-deep-merge( 71 | $map1, 72 | $map2 73 | ) { 74 | 75 | @if type-of($map1) != map or type-of($map2) != map { 76 | $map1: $map2; 77 | } @else { 78 | @each $key, $value in $map2 { 79 | $_new: ($key: _susy_deep-merge(map-get($map1, $key), $value)); 80 | $map1: map-merge($map1, $_new); 81 | } 82 | } 83 | 84 | @return $map1; 85 | } 86 | 87 | 88 | // Deep Has-Key 89 | // ------------ 90 | // - Return true if a deep key exists 91 | @function _susy-deep-has-key( 92 | $map, 93 | $keys... 94 | ) { 95 | $_return: null; 96 | $_stop: false; 97 | 98 | @each $key in $keys { 99 | @if not($_stop) { 100 | $_return: map-has-key($map, $key); 101 | } 102 | 103 | @if $_return { 104 | $map: map-get($map, $key); 105 | } @else { 106 | $_stop: true; 107 | } 108 | } 109 | 110 | @return $_return; 111 | } 112 | -------------------------------------------------------------------------------- /sass/susy/su/_validation.scss: -------------------------------------------------------------------------------- 1 | // Math Validation 2 | // =============== 3 | 4 | 5 | // Valid Columns 6 | // ------------- 7 | // Check that a column setting is valid. 8 | @function valid-columns( 9 | $columns, 10 | $silent: false 11 | ) { 12 | $type: type-of($columns); 13 | $return: null; 14 | 15 | @if $type == number and unitless($columns) { 16 | $return: $columns; 17 | } @else if $type == list { 18 | $fail: null; 19 | @each $col in $columns { 20 | @if type-of($col) == number { 21 | $fail: $fail or if(unitless($col), null, true); 22 | } @else { 23 | $fail: true; 24 | } 25 | } 26 | $return: if($fail, $return, $columns); 27 | } 28 | 29 | @if $return != $columns and not($silent) { 30 | $return: null; 31 | $warn: '$columns must be a unitless number or list of unitless numbers.'; 32 | @warn $warn + ' Current value [#{$type}]: #{$columns}'; 33 | } 34 | 35 | @return $return; 36 | } 37 | 38 | 39 | // Valid Gutters 40 | // ------------- 41 | // Check that a gutter setting is valid. 42 | @function valid-gutters( 43 | $gutters, 44 | $silent: false 45 | ) { 46 | $type: type-of($gutters); 47 | $return: null; 48 | 49 | @if $type == number and unitless($gutters) { 50 | $return: $gutters; 51 | } @else if not($silent) { 52 | $warn: '$gutters must be a unitless number.'; 53 | @warn $warn + ' Current value [#{$type}]: #{$gutters}'; 54 | } 55 | 56 | @return $return; 57 | } 58 | --------------------------------------------------------------------------------