├── .gitignore ├── LICENSE ├── README.md ├── app ├── app.js ├── index.html ├── nav.controller.js ├── pages │ ├── autoExpand │ │ ├── autoExpand.controller.js │ │ └── autoExpand.html │ ├── group │ │ ├── group.controller.js │ │ ├── group.html │ │ └── panels │ │ │ ├── one │ │ │ ├── one.html │ │ │ └── onePanel.controller.js │ │ │ └── templated │ │ │ ├── templated.controller.js │ │ │ └── templated.html │ ├── home │ │ ├── home.controller.js │ │ └── home.html │ └── multiple │ │ ├── multiple.controller.js │ │ └── multiple.html └── style.css ├── bower.json ├── dist ├── md-expansion-panel.css ├── md-expansion-panel.js ├── md-expansion-panel.min.css └── md-expansion-panel.min.js ├── docs ├── angular-animate │ └── angular-animate.js ├── angular-aria │ └── angular-aria.js ├── angular-material │ ├── angular-material.css │ └── angular-material.js ├── angular-messages │ └── angular-messages.js ├── angular-route │ └── angular-route.js ├── angular │ └── angular.js ├── app.js ├── expansionPanels-theme.css ├── expansionPanels.css ├── expansionPanels.js ├── icons │ └── ic_keyboard_arrow_right_black_24px.svg ├── index.html ├── js │ ├── expansionPanel.directive.js │ ├── expansionPanel.service.js │ ├── expansionPanelCollapsed.directive.js │ ├── expansionPanelExpanded.directive.js │ ├── expansionPanelFooter.directive.js │ ├── expansionPanelGroup.directive.js │ ├── expansionPanelGroup.service.js │ ├── expansionPanelHeader.directive.js │ └── expansionPanelIcon.directive.js ├── nav.controller.js ├── pages │ ├── autoExpand │ │ ├── autoExpand.controller.js │ │ └── autoExpand.html │ ├── group │ │ ├── group.controller.js │ │ ├── group.html │ │ └── panels │ │ │ ├── one │ │ │ ├── one.html │ │ │ └── onePanel.controller.js │ │ │ └── templated │ │ │ ├── templated.controller.js │ │ │ └── templated.html │ ├── home │ │ ├── home.controller.js │ │ └── home.html │ └── multiple │ │ ├── multiple.controller.js │ │ └── multiple.html └── style.css ├── gulp ├── config.js ├── cssBuild.js ├── indexBuild.js └── jsBuild.js ├── gulpfile.js ├── index.js ├── karma.conf.js ├── package.json └── src ├── expansionPanels-theme.scss ├── expansionPanels.js ├── expansionPanels.scss ├── expansionPanels.spec.js ├── icons └── ic_keyboard_arrow_right_black_24px.svg └── js ├── expansionPanel.directive.js ├── expansionPanel.service.js ├── expansionPanelCollapsed.directive.js ├── expansionPanelExpanded.directive.js ├── expansionPanelFooter.directive.js ├── expansionPanelGroup.directive.js ├── expansionPanelGroup.service.js ├── expansionPanelHeader.directive.js └── expansionPanelIcon.directive.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | bower_components 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | 41 | # sass cache 42 | .gulp-scss-cache 43 | .sass-cache 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ben Rubin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('angularMaterialExpansionPanel', [ 2 | 'ngRoute', 3 | 'ngAnimate', 4 | 'ngMaterial', 5 | 'material.components.expansionPanels' 6 | ]) 7 | .config(configApp); 8 | 9 | 10 | configApp.$inject = ['$routeProvider']; 11 | function configApp($routeProvider) { 12 | $routeProvider 13 | .when('/', { 14 | templateUrl: 'pages/home/home.html', 15 | controller: 'HomeController', 16 | controllerAs: 'vm' 17 | }) 18 | .when('/group', { 19 | templateUrl: 'pages/group/group.html', 20 | controller: 'GroupController', 21 | controllerAs: 'vm' 22 | }) 23 | .when('/autoexpand', { 24 | templateUrl: 'pages/autoExpand/autoExpand.html', 25 | controller: 'AutoExpandController', 26 | controllerAs: 'vm' 27 | }) 28 | .when('/multiple', { 29 | templateUrl: 'pages/multiple/multiple.html', 30 | controller: 'MultipleController', 31 | controllerAs: 'vm' 32 | }) 33 | .otherwise('/'); 34 | } 35 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Angular Material Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 33 | 34 | 35 |
36 |
37 |
38 | 39 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/nav.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('NavController', NavController); 4 | 5 | 6 | 7 | NavController.$inject = ['$scope', '$rootScope']; 8 | function NavController($scope, $rootScope) { 9 | $rootScope.$on('$routeChangeSuccess', function(event, current) { 10 | $scope.currentNavItem = current.$$route.originalPath || '/'; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /app/pages/autoExpand/autoExpand.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('AutoExpandController', AutoExpandController); 4 | 5 | 6 | 7 | AutoExpandController.$inject = ['$mdExpansionPanelGroup']; 8 | function AutoExpandController($mdExpansionPanelGroup) { 9 | var vm = this; 10 | 11 | var groupInstance; 12 | 13 | vm.title = 'Panel Title'; 14 | vm.summary = 'Panel Summary text'; 15 | vm.content = 'Many were increasingly of the opinion that they’d all made a big mistake in coming down from the trees in the first place. And some said that even the trees had been a bad move, and that no one should ever have left the oceans.'; 16 | 17 | vm.addTemplated = addTemplated; 18 | 19 | $mdExpansionPanelGroup().waitFor('expansionPanelGroup').then(function (instance) { 20 | groupInstance = instance; 21 | 22 | instance.register('templated', { 23 | templateUrl: 'pages/group/panels/templated/templated.html', 24 | controller: 'TemplatedPanelController', 25 | controllerAs: 'vm' 26 | }); 27 | 28 | instance.add({ 29 | templateUrl: 'pages/group/panels/one/one.html', 30 | controller: 'OnePanelController', 31 | controllerAs: 'vm' 32 | }); 33 | }); 34 | 35 | function addTemplated() { 36 | groupInstance.add('templated', { 37 | title: vm.title, 38 | summary: vm.summary, 39 | content: vm.content 40 | }).then(function (panel) { 41 | // panel.expand().then(function () { 42 | // console.log('opened post animation'); 43 | // }); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/pages/autoExpand/autoExpand.html: -------------------------------------------------------------------------------- 1 |

Group With Auto Expand

2 | 3 | 4 |

5 | Expansion Panel Groups with the auto-expand attribute will expand when added to the group 6 |

7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 |
28 |
required
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
required
37 |
38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 |
46 |
required
47 |
48 |
49 |
50 | 51 | Add Panel 52 |
53 |
54 | -------------------------------------------------------------------------------- /app/pages/group/group.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('GroupController', GroupController); 4 | 5 | 6 | 7 | GroupController.$inject = ['$mdExpansionPanelGroup']; 8 | function GroupController($mdExpansionPanelGroup) { 9 | var vm = this; 10 | 11 | var groupInstance; 12 | 13 | vm.title = 'Panel Title'; 14 | vm.summary = 'Panel Summary text'; 15 | vm.content = 'Many were increasingly of the opinion that they’d all made a big mistake in coming down from the trees in the first place. And some said that even the trees had been a bad move, and that no one should ever have left the oceans.'; 16 | 17 | vm.addTemplated = addTemplated; 18 | 19 | $mdExpansionPanelGroup().waitFor('expansionPanelGroup').then(function (instance) { 20 | groupInstance = instance; 21 | 22 | instance.register('templated', { 23 | templateUrl: 'pages/group/panels/templated/templated.html', 24 | controller: 'TemplatedPanelController', 25 | controllerAs: 'vm' 26 | }); 27 | 28 | instance.add({ 29 | templateUrl: 'pages/group/panels/one/one.html', 30 | controller: 'OnePanelController', 31 | controllerAs: 'vm' 32 | }).then(function (panelInstance) { 33 | panelInstance.expand(); 34 | }); 35 | 36 | var change = instance.onChange(function (count) { 37 | console.log('panel count', count); 38 | }); 39 | 40 | 41 | setTimeout(function () { 42 | change(); 43 | }, 10000); 44 | }); 45 | 46 | function addTemplated() { 47 | groupInstance.add('templated', { 48 | title: vm.title, 49 | summary: vm.summary, 50 | content: vm.content 51 | }).then(function (panel) { 52 | panel.onRemove(function () { 53 | console.log('panel removed'); 54 | }); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/pages/group/group.html: -------------------------------------------------------------------------------- 1 |

Group

2 | 3 |

4 | Expansion Panel Groups allow you to controll a set of panels. You can add panels using templates and controllers. You can also register panels to add by a given name; and you can pass in locals. 5 |

6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 |
27 |
required
28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
required
36 |
37 |
38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 |
required
46 |
47 |
48 |
49 | 50 | Add Panel 51 |
52 |
53 | -------------------------------------------------------------------------------- /app/pages/group/panels/one/one.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Title
5 |
Summary
6 |
7 | 8 | 9 | 10 | 11 |
Expanded Title
12 |
Expanded Summary
13 |
14 | 15 | 16 |

Content

17 |

Put content in here

18 |
19 | 20 | 21 |
22 | Collapse 23 |
24 | 25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /app/pages/group/panels/one/onePanel.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('OnePanelController', OnePanelController); 4 | 5 | 6 | 7 | function OnePanelController() { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /app/pages/group/panels/templated/templated.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('TemplatedPanelController', TemplatedPanelController); 4 | 5 | 6 | 7 | function TemplatedPanelController(title, summary, content) { 8 | var vm = this; 9 | 10 | vm.title = title; 11 | vm.summary = summary; 12 | vm.content = content; 13 | } 14 | -------------------------------------------------------------------------------- /app/pages/group/panels/templated/templated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
{{vm.title}}
5 |
{{vm.summary}}
6 |
7 | 8 | 9 | 10 | 11 |
{{vm.title}}
12 |
{{vm.summary}}
13 |
14 | 15 | 16 |

Content

17 |

18 | {{vm.content}} 19 |

20 |
21 | 22 | 23 |
24 | Remove 25 | Collapse 26 |
27 | 28 |
29 | 30 |
31 | -------------------------------------------------------------------------------- /app/pages/home/home.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('HomeController', HomeController); 4 | 5 | 6 | function HomeController($scope, $mdExpansionPanel) { 7 | $mdExpansionPanel().waitFor('expansionPanelOne').then(function (instance) { 8 | instance.expand(); 9 | }); 10 | 11 | 12 | $scope.collapseOne = function () { 13 | $mdExpansionPanel('expansionPanelOne').collapse(); 14 | }; 15 | 16 | $scope.isDisabled = true; 17 | } 18 | -------------------------------------------------------------------------------- /app/pages/home/home.html: -------------------------------------------------------------------------------- 1 |

Panels

2 | 3 | 4 |

5 | Expansion Panels have an collapsed section and a expanded section. Optionally you can add a header and footer to the expanded sections. By default bothe the header and footer will stick to the tops and bottoms of the md-content container they are in, but you can disable that. 6 |

7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
Title
18 |
Summary
19 | 20 |
21 | 22 | 23 | 24 | 25 |
Expanded Title
26 |
Expanded Summary
27 | 28 |
29 | 30 | 31 |

Content

32 |

Put content in here

33 |
34 | 35 | 36 |
37 | Collapse 38 |
39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 |
Sticky
50 |
header and footer will stick
51 |
52 | 53 | 54 | 55 | 56 | 57 |
Sticky
58 |
header and footer will stick
59 |
60 | 61 | 62 |

Content

63 |

Put content in here

64 | 65 |

Content

66 |

Put content in here

67 | 68 |

Content

69 |

Put content in here

70 | 71 |

Content

72 |

Put content in here

73 | 74 |

Content

75 |

Put content in here

76 | 77 |

Content

78 |

Put content in here

79 | 80 |

Content

81 |

Put content in here

82 | 83 |

Content

84 |

Put content in here

85 | 86 |

Content

87 |

Put content in here

88 |
89 | 90 | 91 |
92 | Collapse 93 |
94 | 95 |
96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
No Sticky
106 |
header and footer sticky disabled
107 |
108 | 109 | 110 | 111 | 112 |
No Sticky
113 |
header and footer sticky disabled
114 |
115 | 116 | 117 |

Content

118 |

Put content in here

119 | 120 |

Content

121 |

Put content in here

122 | 123 |

Content

124 |

Put content in here

125 | 126 |

Content

127 |

Put content in here

128 | 129 |

Content

130 |

Put content in here

131 | 132 |

Content

133 |

Put content in here

134 | 135 |

Content

136 |

Put content in here

137 | 138 |

Content

139 |

Put content in here

140 | 141 |

Content

142 |

Put content in here

143 |
144 | 145 | 146 |
147 | Collapse 148 |
149 | 150 |
151 | 152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 |
Disabled Panel
160 | 161 | 162 |
163 | 164 | 165 | 166 | 167 |
Disabled Panel
168 |
Expanded Summary
169 | 170 |
171 | 172 | 173 |

Content

174 |

Put content in here

175 |
176 | 177 | 178 |
179 | Collapse 180 |
181 | 182 |
183 | 184 |
185 | -------------------------------------------------------------------------------- /app/pages/multiple/multiple.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('MultipleController', MultipleController); 4 | 5 | 6 | 7 | MultipleController.$inject = ['$mdExpansionPanelGroup']; 8 | function MultipleController($mdExpansionPanelGroup) { 9 | var vm = this; 10 | 11 | var groupInstance; 12 | 13 | vm.title = 'Panel Title'; 14 | vm.summary = 'Panel Summary text'; 15 | vm.content = 'Many were increasingly of the opinion that they’d all made a big mistake in coming down from the trees in the first place. And some said that even the trees had been a bad move, and that no one should ever have left the oceans.'; 16 | 17 | vm.addTemplated = addTemplated; 18 | 19 | $mdExpansionPanelGroup().waitFor('expansionPanelGroup').then(function (instance) { 20 | groupInstance = instance; 21 | 22 | instance.register('templated', { 23 | templateUrl: 'pages/group/panels/templated/templated.html', 24 | controller: 'TemplatedPanelController', 25 | controllerAs: 'vm' 26 | }); 27 | 28 | instance.add({ 29 | templateUrl: 'pages/group/panels/one/one.html', 30 | controller: 'OnePanelController', 31 | controllerAs: 'vm' 32 | }).then(function (panelInstance) { 33 | panelInstance.expand(); 34 | }); 35 | }); 36 | 37 | function addTemplated() { 38 | groupInstance.add('templated', { 39 | title: vm.title, 40 | summary: vm.summary, 41 | content: vm.content 42 | }).then(function (panel) { 43 | // panel.expand().then(function () { 44 | // console.log('opened post animation'); 45 | // }); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/pages/multiple/multiple.html: -------------------------------------------------------------------------------- 1 |

Group with Multiple

2 | 3 | 4 |

5 | Expansion Panel Groups with the mulitple attribute allow for more than 1 panel to be expanded at the same time 6 |

7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 |
28 |
required
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
required
37 |
38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 |
46 |
required
47 |
48 |
49 |
50 | 51 | Add Panel 52 |
53 |
54 | -------------------------------------------------------------------------------- /app/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | max-width: 100%; 4 | max-height: 100%; 5 | } 6 | 7 | .view-container { 8 | padding-left: 12px; 9 | padding-right: 12px; 10 | } 11 | 12 | 13 | .nav-bar { 14 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12); 15 | background: #FFF; 16 | z-index: 9; 17 | } 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-expansion-panel", 3 | "version": "0.7.2", 4 | "description": "Material Design Expansion Panels.", 5 | "main": [ 6 | "dist/md-expansion-panel.js", 7 | "dist/md-expansion-panel.css" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/B-3PO/angular-material-expansion-panel.git" 12 | }, 13 | "authors": [ 14 | "Ben Rubin" 15 | ], 16 | "license": "MIT", 17 | "keywords": [ 18 | "material", 19 | "material-design", 20 | "design", 21 | "angular", 22 | "component", 23 | "expansion", 24 | "panel", 25 | "panels" 26 | ], 27 | "ignore": [ 28 | "**/.*", 29 | "node_modules", 30 | "bower_components", 31 | "app", 32 | "src", 33 | "gulp" 34 | ], 35 | "dependencies": { 36 | "angular": "^1.5.7", 37 | "angular-animate": "^1.5.7", 38 | "angular-messages": "^1.5.7", 39 | "angular-aria": "^1.5.7", 40 | "angular-material": "^1.1.0" 41 | }, 42 | "devDependencies": { 43 | "angular-mocks": "^1.5.7", 44 | "angular-route": "^1.5.7" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dist/md-expansion-panel.css: -------------------------------------------------------------------------------- 1 | md-expansion-panel { 2 | background: #FFF; } 3 | md-expansion-panel md-expansion-panel-collapsed, 4 | md-expansion-panel .md-expansion-panel-footer-container, 5 | md-expansion-panel .md-expansion-panel-header-container { 6 | background: #FFF; } 7 | md-expansion-panel md-expansion-panel-collapsed .md-title, 8 | md-expansion-panel md-expansion-panel-collapsed .md-summary, 9 | md-expansion-panel .md-expansion-panel-footer-container .md-title, 10 | md-expansion-panel .md-expansion-panel-footer-container .md-summary, 11 | md-expansion-panel .md-expansion-panel-header-container .md-title, 12 | md-expansion-panel .md-expansion-panel-header-container .md-summary { 13 | color: #333; } 14 | md-expansion-panel md-expansion-panel-footer .md-expansion-panel-footer-container, 15 | md-expansion-panel md-expansion-panel-header .md-expansion-panel-header-container { 16 | border-color: #DDD; } 17 | md-expansion-panel .md-expansion-panel-icon svg { 18 | fill: #999; } 19 | md-expansion-panel[disabled] md-expansion-panel-collapsed .md-title, 20 | md-expansion-panel[disabled] md-expansion-panel-collapsed .md-summary { 21 | color: #DDD; } 22 | md-expansion-panel[disabled] .md-expansion-panel-icon svg { 23 | fill: #DDD; } 24 | md-expansion-panel:not(.md-open):not([disabled]):focus, 25 | md-expansion-panel:not(.md-open):not([disabled]):focus md-expansion-panel-collapsed { 26 | background: #EEE; } 27 | 28 | .layout-padding > md-expansion-panel-group { 29 | padding: 0; } 30 | 31 | /* --- Expansion Panel --- */ 32 | md-expansion-panel { 33 | display: block; 34 | position: relative; 35 | outline: none; 36 | margin-top: 0; 37 | margin-bottom: 0; 38 | padding: 0; 39 | box-shadow: 0 -1px 0 #e5e5e5, 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); } 40 | md-expansion-panel.md-open { 41 | margin-top: 16px; 42 | margin-bottom: 16px; } 43 | md-expansion-panel.md-open:first-of-type { 44 | margin-top: 0; } 45 | md-expansion-panel.md-close { 46 | overflow: hidden; } 47 | md-expansion-panel:not(.md-no-animation).md-open { 48 | -webkit-transition: margin-top 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); 49 | transition: margin-top 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); } 50 | md-expansion-panel:not(.md-no-animation).md-close { 51 | -webkit-transition: margin-top 0.08s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.08s cubic-bezier(0.25, 0.8, 0.25, 1); 52 | transition: margin-top 0.08s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.08s cubic-bezier(0.25, 0.8, 0.25, 1); } 53 | 54 | md-expansion-panel-collapsed .md-title, 55 | md-expansion-panel-header .md-title { 56 | -webkit-box-flex: 1; 57 | -ms-flex: 1; 58 | flex: 1; 59 | font-size: 16px; 60 | font-weight: 600; 61 | min-width: 80px; 62 | max-width: 184px; 63 | overflow: hidden; 64 | text-overflow: ellipsis; 65 | text-align: left; 66 | white-space: nowrap; } 67 | 68 | md-expansion-panel-collapsed .md-summary, 69 | md-expansion-panel-header .md-summary { 70 | -webkit-box-flex: 1; 71 | -ms-flex: 1; 72 | flex: 1; 73 | font-size: 13px; 74 | overflow: hidden; 75 | text-overflow: ellipsis; 76 | text-align: left; 77 | white-space: nowrap; } 78 | 79 | /* --- Expansion Panel Collapsed ---- */ 80 | md-expansion-panel md-expansion-panel-collapsed { 81 | display: -webkit-box; 82 | display: -ms-flexbox; 83 | display: flex; 84 | min-height: 48px; 85 | line-height: 48px; 86 | padding: 0 24px; 87 | opacity: 1; 88 | z-index: 2; 89 | box-sizing: border-box; } 90 | md-expansion-panel md-expansion-panel-collapsed.md-absolute { 91 | position: absolute; } 92 | md-expansion-panel md-expansion-panel-collapsed.md-hide { 93 | opacity: 0; } 94 | 95 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-collapsed.md-show { 96 | -webkit-transition: opacity 0.03s linear; 97 | transition: opacity 0.03s linear; } 98 | 99 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-collapsed.md-hide { 100 | -webkit-transition: opacity 0.1s cubic-bezier(0.25, 0.8, 0.25, 1); 101 | transition: opacity 0.1s cubic-bezier(0.25, 0.8, 0.25, 1); } 102 | 103 | /* --- Expansion Panel Expanded --- */ 104 | md-expansion-panel md-expansion-panel-expanded { 105 | display: none; 106 | min-height: 48px; } 107 | md-expansion-panel md-expansion-panel-expanded.md-show, md-expansion-panel md-expansion-panel-expanded.md-hide { 108 | display: block; } 109 | md-expansion-panel md-expansion-panel-expanded.md-scroll-y { 110 | overflow-y: auto; } 111 | md-expansion-panel md-expansion-panel-expanded.md-overflow { 112 | overflow: hidden; } 113 | md-expansion-panel md-expansion-panel-expanded md-expansion-panel-content { 114 | display: block; 115 | padding: 16px 24px; } 116 | 117 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-expanded.md-show { 118 | -webkit-transition: max-height 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); 119 | transition: max-height 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); } 120 | 121 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-expanded.md-hide { 122 | -webkit-transition: max-height 0.06s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.06s cubic-bezier(0.25, 0.8, 0.25, 1); 123 | transition: max-height 0.06s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.06s cubic-bezier(0.25, 0.8, 0.25, 1); } 124 | 125 | /* --- Expansion Panel Header --- */ 126 | md-expansion-panel-header { 127 | display: block; 128 | position: relative; 129 | outline: none; } 130 | md-expansion-panel-header .md-expansion-panel-header-container { 131 | display: -webkit-box; 132 | display: -ms-flexbox; 133 | display: flex; 134 | min-height: 48px; 135 | line-height: 48px; 136 | padding: 0 24px; 137 | box-sizing: border-box; 138 | border-bottom: 1px solid; 139 | -webkit-box-align: center; 140 | -ms-flex-align: center; 141 | align-items: center; } 142 | md-expansion-panel-header.md-stick .md-expansion-panel-header-container { 143 | position: fixed; 144 | z-index: 2; 145 | -webkit-animation: panelBodyHeaderStickyHoverIn 0.3s ease-out both; 146 | animation: panelBodyHeaderStickyHoverIn 0.3s ease-out both; } 147 | md-expansion-panel-header.md-no-stick .md-expansion-panel-header-container { 148 | -webkit-animation: panelBodyHeaderStickyHoverOut 0.3s ease-out both; 149 | animation: panelBodyHeaderStickyHoverOut 0.3s ease-out both; } 150 | 151 | /* --- Expansion Panel Footer --- */ 152 | md-expansion-panel-footer { 153 | display: block; 154 | position: relative; } 155 | md-expansion-panel-footer.md-show, md-expansion-panel-footer.md-hide { 156 | display: block; } 157 | md-expansion-panel-footer .md-expansion-panel-footer-container { 158 | display: -webkit-box; 159 | display: -ms-flexbox; 160 | display: flex; 161 | min-height: 48px; 162 | line-height: 48px; 163 | padding: 0 24px; 164 | border-top: 1px solid; 165 | box-sizing: border-box; } 166 | md-expansion-panel-footer.md-stick .md-expansion-panel-footer-container { 167 | position: fixed; 168 | z-index: 2; } 169 | 170 | /* --- expand icon --- */ 171 | md-expansion-panel:not(.md-no-animation) .md-expansion-panel-icon { 172 | -webkit-transition: -webkit-transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 173 | transition: -webkit-transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 174 | transition: transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 175 | transition: transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); } 176 | 177 | md-expansion-panel .md-expansion-panel-icon { 178 | -webkit-transform: rotate(90deg); 179 | transform: rotate(90deg); } 180 | md-expansion-panel .md-expansion-panel-icon:first-child { 181 | margin-right: 18px; } 182 | 183 | md-expansion-panel.md-open > md-expansion-panel-expanded > md-expansion-panel-header .md-expansion-panel-header-container .md-expansion-panel-icon { 184 | -webkit-transform: rotate(-90deg); 185 | transform: rotate(-90deg); } 186 | 187 | md-expansion-panel.md-open > md-expansion-panel.md-open > md-expansion-panel-collapsed .md-expansion-panel-icon { 188 | -webkit-transform: rotate(-90deg); 189 | transform: rotate(-90deg); } 190 | 191 | @-webkit-keyframes panelBodyHeaderStickyHoverIn { 192 | 0% { 193 | box-shadow: 0 0 0 0 transparent; } 194 | 100% { 195 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } } 196 | 197 | @keyframes panelBodyHeaderStickyHoverIn { 198 | 0% { 199 | box-shadow: 0 0 0 0 transparent; } 200 | 100% { 201 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } } 202 | 203 | @-webkit-keyframes panelBodyHeaderStickyHoverOut { 204 | 0% { 205 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } 206 | 100% { 207 | box-shadow: 0 0 0 0 transparent; } } 208 | 209 | @keyframes panelBodyHeaderStickyHoverOut { 210 | 0% { 211 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } 212 | 100% { 213 | box-shadow: 0 0 0 0 transparent; } } 214 | -------------------------------------------------------------------------------- /dist/md-expansion-panel.min.css: -------------------------------------------------------------------------------- 1 | md-expansion-panel,md-expansion-panel .md-expansion-panel-footer-container,md-expansion-panel .md-expansion-panel-header-container,md-expansion-panel md-expansion-panel-collapsed{background:#fff}md-expansion-panel .md-expansion-panel-footer-container .md-summary,md-expansion-panel .md-expansion-panel-footer-container .md-title,md-expansion-panel .md-expansion-panel-header-container .md-summary,md-expansion-panel .md-expansion-panel-header-container .md-title,md-expansion-panel md-expansion-panel-collapsed .md-summary,md-expansion-panel md-expansion-panel-collapsed .md-title{color:#333}md-expansion-panel md-expansion-panel-footer .md-expansion-panel-footer-container,md-expansion-panel md-expansion-panel-header .md-expansion-panel-header-container{border-color:#ddd}md-expansion-panel .md-expansion-panel-icon svg{fill:#999}md-expansion-panel[disabled] md-expansion-panel-collapsed .md-summary,md-expansion-panel[disabled] md-expansion-panel-collapsed .md-title{color:#ddd}md-expansion-panel[disabled] .md-expansion-panel-icon svg{fill:#ddd}md-expansion-panel:not(.md-open):not([disabled]):focus,md-expansion-panel:not(.md-open):not([disabled]):focus md-expansion-panel-collapsed{background:#eee}.layout-padding>md-expansion-panel-group{padding:0}md-expansion-panel{display:block;position:relative;outline:none;margin-top:0;margin-bottom:0;padding:0;box-shadow:0 -1px 0 #e5e5e5,0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24)}md-expansion-panel.md-open{margin-top:16px;margin-bottom:16px}md-expansion-panel.md-open:first-of-type{margin-top:0}md-expansion-panel.md-close{overflow:hidden}md-expansion-panel:not(.md-no-animation).md-open{-webkit-transition:margin-top .12s cubic-bezier(.25,.8,.25,1),margin-bottom .12s cubic-bezier(.25,.8,.25,1);transition:margin-top .12s cubic-bezier(.25,.8,.25,1),margin-bottom .12s cubic-bezier(.25,.8,.25,1)}md-expansion-panel:not(.md-no-animation).md-close{-webkit-transition:margin-top .08s cubic-bezier(.25,.8,.25,1),margin-bottom .08s cubic-bezier(.25,.8,.25,1);transition:margin-top .08s cubic-bezier(.25,.8,.25,1),margin-bottom .08s cubic-bezier(.25,.8,.25,1)}md-expansion-panel-collapsed .md-title,md-expansion-panel-header .md-title{-webkit-box-flex:1;-ms-flex:1;flex:1;font-size:16px;font-weight:600;min-width:80px;max-width:184px;overflow:hidden;text-overflow:ellipsis;text-align:left;white-space:nowrap}md-expansion-panel-collapsed .md-summary,md-expansion-panel-header .md-summary{-webkit-box-flex:1;-ms-flex:1;flex:1;font-size:13px;overflow:hidden;text-overflow:ellipsis;text-align:left;white-space:nowrap}md-expansion-panel md-expansion-panel-collapsed{display:-webkit-box;display:-ms-flexbox;display:flex;min-height:48px;line-height:48px;padding:0 24px;opacity:1;z-index:2;box-sizing:border-box}md-expansion-panel md-expansion-panel-collapsed.md-absolute{position:absolute}md-expansion-panel md-expansion-panel-collapsed.md-hide{opacity:0}md-expansion-panel:not(.md-no-animation) md-expansion-panel-collapsed.md-show{-webkit-transition:opacity .03s linear;transition:opacity .03s linear}md-expansion-panel:not(.md-no-animation) md-expansion-panel-collapsed.md-hide{-webkit-transition:opacity .1s cubic-bezier(.25,.8,.25,1);transition:opacity .1s cubic-bezier(.25,.8,.25,1)}md-expansion-panel md-expansion-panel-expanded{display:none;min-height:48px}md-expansion-panel md-expansion-panel-expanded.md-hide,md-expansion-panel md-expansion-panel-expanded.md-show{display:block}md-expansion-panel md-expansion-panel-expanded.md-scroll-y{overflow-y:auto}md-expansion-panel md-expansion-panel-expanded.md-overflow{overflow:hidden}md-expansion-panel md-expansion-panel-expanded md-expansion-panel-content{display:block;padding:16px 24px}md-expansion-panel:not(.md-no-animation) md-expansion-panel-expanded.md-show{-webkit-transition:max-height .12s cubic-bezier(.25,.8,.25,1),opacity .12s cubic-bezier(.25,.8,.25,1);transition:max-height .12s cubic-bezier(.25,.8,.25,1),opacity .12s cubic-bezier(.25,.8,.25,1)}md-expansion-panel:not(.md-no-animation) md-expansion-panel-expanded.md-hide{-webkit-transition:max-height .06s cubic-bezier(.25,.8,.25,1),opacity .06s cubic-bezier(.25,.8,.25,1);transition:max-height .06s cubic-bezier(.25,.8,.25,1),opacity .06s cubic-bezier(.25,.8,.25,1)}md-expansion-panel-header{display:block;position:relative;outline:none}md-expansion-panel-header .md-expansion-panel-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;min-height:48px;line-height:48px;padding:0 24px;box-sizing:border-box;border-bottom:1px solid;-webkit-box-align:center;-ms-flex-align:center;align-items:center}md-expansion-panel-header.md-stick .md-expansion-panel-header-container{position:fixed;z-index:2;-webkit-animation:a .3s ease-out both;animation:a .3s ease-out both}md-expansion-panel-header.md-no-stick .md-expansion-panel-header-container{-webkit-animation:b .3s ease-out both;animation:b .3s ease-out both}md-expansion-panel-footer{display:block;position:relative}md-expansion-panel-footer.md-hide,md-expansion-panel-footer.md-show{display:block}md-expansion-panel-footer .md-expansion-panel-footer-container{display:-webkit-box;display:-ms-flexbox;display:flex;min-height:48px;line-height:48px;padding:0 24px;border-top:1px solid;box-sizing:border-box}md-expansion-panel-footer.md-stick .md-expansion-panel-footer-container{position:fixed;z-index:2}md-expansion-panel:not(.md-no-animation) .md-expansion-panel-icon{-webkit-transition:-webkit-transform .6s cubic-bezier(.25,.8,.25,1);transition:-webkit-transform .6s cubic-bezier(.25,.8,.25,1);transition:transform .6s cubic-bezier(.25,.8,.25,1);transition:transform .6s cubic-bezier(.25,.8,.25,1),-webkit-transform .6s cubic-bezier(.25,.8,.25,1)}md-expansion-panel .md-expansion-panel-icon{-webkit-transform:rotate(90deg);transform:rotate(90deg)}md-expansion-panel .md-expansion-panel-icon:first-child{margin-right:18px}md-expansion-panel.md-open>md-expansion-panel-expanded>md-expansion-panel-header .md-expansion-panel-header-container .md-expansion-panel-icon,md-expansion-panel.md-open>md-expansion-panel.md-open>md-expansion-panel-collapsed .md-expansion-panel-icon{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}@-webkit-keyframes a{0%{box-shadow:0 0 0 0 transparent}to{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}}@keyframes a{0%{box-shadow:0 0 0 0 transparent}to{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}}@-webkit-keyframes b{0%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}to{box-shadow:0 0 0 0 transparent}}@keyframes b{0%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}to{box-shadow:0 0 0 0 transparent}} -------------------------------------------------------------------------------- /dist/md-expansion-panel.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";angular.module("material.components.expansionPanels",["material.core"])}(),function(){"use strict";angular.module("material.components.expansionPanels").run(["$templateCache",function(n){n.put("icons/ic_keyboard_arrow_right_black_24px.svg",'\n \n \n')}])}(),function(){"use strict";function n(){function n(n,e){var o="Invalid HTML for md-expansion-panel: ";if(n.attr("tabindex",e.tabindex||"0"),null===n[0].querySelector("md-expansion-panel-collapsed"))throw Error(o+"Expected a child element of `md-epxansion-panel-collapsed`");if(null===n[0].querySelector("md-expansion-panel-expanded"))throw Error(o+"Expected a child element of `md-epxansion-panel-expanded`");return function(n,e,o,t){var i=t[0],r=t[1];i.epxansionPanelGroupCtrl=r||void 0,i.init()}}function o(n,o,t,i,r,a,s,l,c,d,u,p){function m(n){var e=a.KEY_CODE;switch(n.keyCode){case e.ENTER:g();break;case e.ESCAPE:x()}}function f(){F=!0,M===!0&&v()}function v(){return F===!1?void(M=!0):("function"==typeof q&&(q(),q=void 0),D.componentId&&D.epxansionPanelGroupCtrl&&D.epxansionPanelGroupCtrl.removePanel(D.componentId),void 0===t.mdComponentId&&t.$set("mdComponentId","_expansion_panel_id_"+s.nextUid()),D.componentId=t.mdComponentId,q=l.register({expand:g,collapse:x,remove:C,onRemove:$,isOpen:h,addClickCatcher:S,removeClickCatcher:_,componentId:t.mdComponentId},t.mdComponentId),void(D.epxansionPanelGroupCtrl&&D.epxansionPanelGroupCtrl.addPanel(D.componentId,{expand:g,collapse:x,remove:C,onRemove:$,destroy:E,isOpen:h})))}function h(){return W}function g(n){if(W!==!0&&L!==!0){W=!0,n=n||{};var t=d.defer();return D.epxansionPanelGroupCtrl&&D.epxansionPanelGroupCtrl.expandPanel(D.componentId),o.removeClass("md-close"),o.addClass("md-open"),n.animation===!1?o.addClass("md-no-animation"):o.removeClass("md-no-animation"),w(),O.hide(n),G.show(n),j&&j.show(n),N&&N.show(n),c(function(){t.resolve()},n.animation===!1?0:e),t.promise}}function x(n){if(W!==!1){W=!1,n=n||{};var t=d.defer();return o.addClass("md-close"),o.removeClass("md-open"),n.animation===!1?o.addClass("md-no-animation"):o.removeClass("md-no-animation"),P(),O.show(n),G.hide(n),j&&j.hide(n),N&&N.hide(n),c(function(){t.resolve()},n.animation===!1?0:e),t.promise}}function C(t){t=t||{};var i=d.defer();return D.epxansionPanelGroupCtrl&&D.epxansionPanelGroupCtrl.removePanel(D.componentId),"function"==typeof q&&(q(),q=void 0),t.animation===!1||W===!1?(n.$destroy(),o.remove(),i.resolve(),y()):(x(),c(function(){n.$destroy(),o.remove(),i.resolve(),y()},e)),i.promise}function $(n){B=n}function y(){"function"==typeof B&&(B(),B=void 0)}function E(){n.$destroy()}function w(){(N&&N.noSticky!==!0||j&&j.noSticky!==!0)&&(H=n.$watch(function(){return o[0].offsetTop},K,!0),z=n.$watch(function(){return o[0].offsetWidth},Y,!0),A=s.getNearestContentElement(o),"MD-CONTENT"===A.nodeName?(U=k(A),angular.element(A).on("scroll",K)):U=void 0,G.setHeight===!0&&G.$element.on("scroll",K),angular.element(i).on("scroll",K).on("resize",K).on("resize",Y))}function P(){"function"==typeof H&&(H(),H=void 0),"function"==typeof z&&(z(),z=void 0),A&&"MD-CONTENT"===A.nodeName&&angular.element(A).off("scroll",K),G.setHeight===!0&&G.$element.off("scroll",K),angular.element(i).off("scroll",K).off("resize",K).off("resize",Y)}function k(n){for(var e=n.parentNode;e&&e!==document;){if(b(e,"transform"))return e;e=e.parentNode}}function b(n,e){var o=!1;if(n){var t=i.getComputedStyle(n);o=void 0!==t[e]&&"none"!==t[e]}return o}function I(n){var e,o,t;t=G.setHeight===!0?G.$element[0].getBoundingClientRect():A.getBoundingClientRect();var i=U?U.getBoundingClientRect().top:0;e=Math.max(t.top,0),o=e+t.height,N&&N.noSticky===!1&&N.onScroll(e,o,i),j&&j.noSticky===!1&&j.onScroll(e,o,i)}function R(){var n=o[0].offsetWidth;N&&N.noSticky===!1&&N.onResize(n),j&&j.noSticky===!1&&j.onResize(n)}function S(e){T=s.createBackdrop(n),T[0].tabIndex=-1,"function"==typeof e&&T.on("click",e),u.enter(T,o.parent(),null,{duration:0}),o.css("z-index",60)}function _(){T&&(T.remove(),T.off("click"),T=void 0,o.css("z-index",""))}var O,G,j,N,q,A,H,z,B,U,T,D=this,F=!1,M=!1,W=!1,L=!1,K=r.throttle(I),Y=r.throttle(R);D.registerCollapsed=function(n){O=n},D.registerExpanded=function(n){G=n},D.registerHeader=function(n){j=n},D.registerFooter=function(n){N=n},void 0===t.mdComponentId?(t.$set("mdComponentId","_expansion_panel_id_"+s.nextUid()),v()):t.$observe("mdComponentId",function(){v()}),D.$element=o,D.expand=g,D.collapse=x,D.remove=C,D.destroy=E,D.onRemove=$,D.init=f,void 0!==t.ngDisabled?n.$watch(t.ngDisabled,function(n){L=n,o.attr("tabindex",L?-1:0)}):void 0!==t.disabled&&(L=void 0!==t.disabled&&"false"!==t.disabled&&t.disabled!==!1,o.attr("tabindex",L?-1:0)),o.on("focus",function(n){o.on("keydown",m)}).on("blur",function(n){o.off("keydown",m)}),n.$panel={collapse:x,expand:g,remove:C,isOpen:h},n.$on("$destroy",function(){_(),"function"==typeof q&&(q(),q=void 0),P()})}var t={restrict:"E",require:["mdExpansionPanel","?^^mdExpansionPanelGroup"],scope:!0,compile:n,controller:["$scope","$element","$attrs","$window","$$rAF","$mdConstant","$mdUtil","$mdComponentRegistry","$timeout","$q","$animate","$parse",o]};return t}angular.module("material.components.expansionPanels").directive("mdExpansionPanel",n);var e=180}(),function(){"use strict";function n(n,e,o){function t(t){var i=n.get(t);return i?i:void o.error(e.supplant(r,[t||""]))}function i(e){return n.when(e)["catch"](o.error)}var r="ExpansionPanel '{0}' is not available! Did you use md-component-id='{0}'?",a={find:t,waitFor:i};return function(n){return void 0===n?a:t(n)}}angular.module("material.components.expansionPanels").factory("$mdExpansionPanel",n),n.$inject=["$mdComponentRegistry","$mdUtil","$log"]}(),function(){"use strict";function n(n,e){function o(o,t,i,r){function a(e){t.css("width",t[0].offsetWidth+"px"),r.$element.css("min-height",t[0].offsetHeight+"px");var o={addClass:"md-absolute md-hide",from:{opacity:1},to:{opacity:0}};e.animation===!1&&(o.duration=0),n(t,o).start().then(function(){t.removeClass("md-hide"),t.css("display","none")})}function s(o){t.css("display",""),t.css("width",t[0].parentNode.offsetWidth+"px");var i={addClass:"md-show",from:{opacity:0},to:{opacity:1}};o.animation===!1&&(i.duration=0),n(t,i).start().then(function(){r.$element.css("transition","none"),t.removeClass("md-absolute md-show"),t.css("width",""),r.$element.css("min-height",""),e(function(){r.$element.css("transition","")},0)})}r.registerCollapsed({show:s,hide:a}),t.on("click",function(){r.expand()})}var t={restrict:"E",require:"^^mdExpansionPanel",link:o};return t}angular.module("material.components.expansionPanels").directive("mdExpansionPanelCollapsed",n),n.$inject=["$animateCss","$timeout"]}(),function(){"use strict";function n(n,e){function o(o,t,i,r){function a(e){var o=l?l:t[0].scrollHeight+"px";t.addClass("md-hide md-overflow"),t.removeClass("md-show md-scroll-y");var i={from:{"max-height":o,opacity:1},to:{"max-height":"48px",opacity:0}};e.animation===!1&&(i.duration=0),n(t,i).start().then(function(){t.css("display","none"),t.removeClass("md-hide")})}function s(o){t.css("display",""),t.addClass("md-show md-overflow");var i=l?l:t[0].scrollHeight+"px",r={from:{"max-height":"48px",opacity:0},to:{"max-height":i,opacity:1}};o.animation===!1&&(r.duration=0),n(t,r).start().then(function(){void 0!==l?t.addClass("md-scroll-y"):(t.css("transition","none"),t.css("max-height","none"),e(function(){t.css("transition","")},0)),t.removeClass("md-overflow")})}var l=i.height||void 0;void 0!==l&&(l=l.replace("px","")+"px"),r.registerExpanded({show:s,hide:a,setHeight:void 0!==l,$element:t})}var t={restrict:"E",require:"^^mdExpansionPanel",link:o};return t}angular.module("material.components.expansionPanels").directive("mdExpansionPanelExpanded",n),n.$inject=["$animateCss","$timeout"]}(),function(){"use strict";function n(){function n(n,e,o,t){function i(){}function r(){l()}function a(n,o,i){var r,a,s=e[0].getBoundingClientRect();s.bottom>o?(r=u[0].offsetHeight,a=o-r-i,a',require:"^^mdExpansionPanel",link:n};return e}angular.module("material.components.expansionPanels").directive("mdExpansionPanelFooter",n)}(),function(){"use strict";function n(){function n(n,e,o,t){function i(n){return E.push(n),function(){E.splice(E.indexOf(n),1)}}function r(){var n=u();E.forEach(function(e){e(n)})}function a(n,e){y[n]=e,P===!0&&(e.expand(),p(n)),r()}function s(n){p(n)}function l(n,e){return y[n].remove(e)}function c(n){Object.keys(y).forEach(function(e){y[e].remove(n)})}function d(n){delete y[n],r()}function u(){return Object.keys(y).length}function p(n){w===!1&&Object.keys(y).forEach(function(e){e!==n&&y[e].collapse()})}function m(n,e){if(void 0!==$[n])throw Error('$mdExpansionPanelGroup.register() The name "'+n+'" has already been registered');$[n]=e}function f(n){if(void 0===$[n])throw Error('$mdExpansionPanelGroup.addPanel() Cannot find Panel with name of "'+n+'"');return $[n]}function v(){return Object.keys(y).map(function(n){return y[n]})}function h(){return Object.keys(y).map(function(n){return y[n]}).filter(function(n){return n.isOpen()})}function g(n){var e=n!==!0;Object.keys(y).forEach(function(n){y[n].collapse({animation:e})})}var x,C=this,$={},y={},E=[],w=void 0!==e.mdMultiple||void 0!==e.multiple,P=void 0!==e.mdAutoExpand||void 0!==e.autoExpand;x=t.register({$element:o,register:m,getRegistered:f,getAll:v,getOpen:h,remove:l,removeAll:c,collapseAll:g,onChange:i,count:u},e.mdComponentId),C.addPanel=a,C.expandPanel=s,C.removePanel=d,n.$on("$destroy",function(){"function"==typeof x&&(x(),x=void 0),Object.keys(y).forEach(function(n){y[n].destroy()})})}var e={restrict:"E",controller:["$scope","$attrs","$element","$mdComponentRegistry",n]};return e}angular.module("material.components.expansionPanels").directive("mdExpansionPanelGroup",n)}(),function(){"use strict";function n(n,e,o,t,i,r,a,s,l){function c(o){var t=n.get(o);return t?u(t):void l.error(e.supplant(p,[o||""]))}function d(e){var o=s.defer();return n.when(e).then(function(n){o.resolve(u(n))})["catch"](function(n){o.reject(),l.error(n)}),o.promise}function u(n){function l(e,o){if("string"!=typeof e)throw Error("$mdExpansionPanelGroup.register() Expects name to be a string");g(o),n.register(e,o)}function c(e,o){return n.remove(e,o)}function d(e){n.removeAll(e)}function u(e){return n.onChange(e)}function p(){return n.count()}function m(){return n.getAll()}function f(){return n.getOpen()}function v(e){n.collapseAll(e)}function h(t,l){if(l=l||{},"string"==typeof t)return h(n.getRegistered(t),l);if(g(t),t.componentId&&n.isPanelActive(t.componentId))return s.reject('panel with componentId "'+t.componentId+'" is currently active');var c=s.defer(),d=i.$new();return angular.extend(d,t.scope),x(t,function(i){var s=angular.element(i),u=t.componentId||s.attr("md-component-id")||"_panelComponentId_"+e.nextUid(),p=o().waitFor(u);s.attr("md-component-id",u);var m=r(s);if(t.controller){angular.extend(l,t.locals||{}),l.$scope=d,l.$panel=p;var f=a(t.controller,l,!0),v=f();s.data("$ngControllerController",v),s.children().data("$ngControllerController",v),t.controllerAs&&(d[t.controllerAs]=v)}n.$element.append(s),m(d),p.then(function(n){c.resolve(n)})}),c.promise}function g(n){if("object"!=typeof n||null===n)throw Error("$mdExapnsionPanelGroup.add()/.register() : Requires an options object to be passed in");if(!n.template&&!n.templateUrl)throw Error("$mdExapnsionPanelGroup.add()/.register() : Is missing required paramters to create. Required One of the following: template, templateUrl")}function x(n,e){void 0!==n.templateUrl?t(n.templateUrl).then(function(n){e(n)}):e(n.template)}var C={add:h,register:l,getAll:m,getOpen:f,remove:c,removeAll:d,collapseAll:v,onChange:u,count:p};return C}var p="ExpansionPanelGroup '{0}' is not available! Did you use md-component-id='{0}'?",m={find:c,waitFor:d};return function(n){return void 0===n?m:c(n)}}angular.module("material.components.expansionPanels").factory("$mdExpansionPanelGroup",n),n.$inject=["$mdComponentRegistry","$mdUtil","$mdExpansionPanel","$templateRequest","$rootScope","$compile","$controller","$q","$log"]}(),function(){"use strict";function n(){function n(n,e,o,t){function i(){}function r(){l()}function a(n,o,t){var i,r,a=e[0].getBoundingClientRect();a.top',require:"^^mdExpansionPanel",link:n};return e}angular.module("material.components.expansionPanels").directive("mdExpansionPanelHeader",n),n.$inject=[]}(),function(){"use strict";function n(){var n={restrict:"E",template:'',replace:!0};return n}angular.module("material.components.expansionPanels").directive("mdExpansionPanelIcon",n)}(); -------------------------------------------------------------------------------- /docs/angular-aria/angular-aria.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.5.7 3 | * (c) 2010-2016 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngAria 11 | * @description 12 | * 13 | * The `ngAria` module provides support for common 14 | * [ARIA](http://www.w3.org/TR/wai-aria/) 15 | * attributes that convey state or semantic information about the application for users 16 | * of assistive technologies, such as screen readers. 17 | * 18 | *
19 | * 20 | * ## Usage 21 | * 22 | * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following 23 | * directives are supported: 24 | * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, 25 | * `ngDblClick`, and `ngMessages`. 26 | * 27 | * Below is a more detailed breakdown of the attributes handled by ngAria: 28 | * 29 | * | Directive | Supported Attributes | 30 | * |---------------------------------------------|----------------------------------------------------------------------------------------| 31 | * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | 32 | * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | 33 | * | {@link ng.directive:ngRequired ngRequired} | aria-required 34 | * | {@link ng.directive:ngChecked ngChecked} | aria-checked 35 | * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | 36 | * | {@link ng.directive:ngValue ngValue} | aria-checked | 37 | * | {@link ng.directive:ngShow ngShow} | aria-hidden | 38 | * | {@link ng.directive:ngHide ngHide} | aria-hidden | 39 | * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | 40 | * | {@link module:ngMessages ngMessages} | aria-live | 41 | * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role | 42 | * 43 | * Find out more information about each directive by reading the 44 | * {@link guide/accessibility ngAria Developer Guide}. 45 | * 46 | * ## Example 47 | * Using ngDisabled with ngAria: 48 | * ```html 49 | * 50 | * ``` 51 | * Becomes: 52 | * ```html 53 | * 54 | * ``` 55 | * 56 | * ## Disabling Attributes 57 | * It's possible to disable individual attributes added by ngAria with the 58 | * {@link ngAria.$ariaProvider#config config} method. For more details, see the 59 | * {@link guide/accessibility Developer Guide}. 60 | */ 61 | /* global -ngAriaModule */ 62 | var ngAriaModule = angular.module('ngAria', ['ng']). 63 | provider('$aria', $AriaProvider); 64 | 65 | /** 66 | * Internal Utilities 67 | */ 68 | var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; 69 | 70 | var isNodeOneOf = function(elem, nodeTypeArray) { 71 | if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { 72 | return true; 73 | } 74 | }; 75 | /** 76 | * @ngdoc provider 77 | * @name $ariaProvider 78 | * 79 | * @description 80 | * 81 | * Used for configuring the ARIA attributes injected and managed by ngAria. 82 | * 83 | * ```js 84 | * angular.module('myApp', ['ngAria'], function config($ariaProvider) { 85 | * $ariaProvider.config({ 86 | * ariaValue: true, 87 | * tabindex: false 88 | * }); 89 | * }); 90 | *``` 91 | * 92 | * ## Dependencies 93 | * Requires the {@link ngAria} module to be installed. 94 | * 95 | */ 96 | function $AriaProvider() { 97 | var config = { 98 | ariaHidden: true, 99 | ariaChecked: true, 100 | ariaReadonly: true, 101 | ariaDisabled: true, 102 | ariaRequired: true, 103 | ariaInvalid: true, 104 | ariaValue: true, 105 | tabindex: true, 106 | bindKeypress: true, 107 | bindRoleForClick: true 108 | }; 109 | 110 | /** 111 | * @ngdoc method 112 | * @name $ariaProvider#config 113 | * 114 | * @param {object} config object to enable/disable specific ARIA attributes 115 | * 116 | * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags 117 | * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags 118 | * - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags 119 | * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags 120 | * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags 121 | * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags 122 | * - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags 123 | * - **tabindex** – `{boolean}` – Enables/disables tabindex tags 124 | * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `div` and 125 | * `li` elements with ng-click 126 | * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div` 127 | * using ng-click, making them more accessible to users of assistive technologies 128 | * 129 | * @description 130 | * Enables/disables various ARIA attributes 131 | */ 132 | this.config = function(newConfig) { 133 | config = angular.extend(config, newConfig); 134 | }; 135 | 136 | function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { 137 | return function(scope, elem, attr) { 138 | var ariaCamelName = attr.$normalize(ariaAttr); 139 | if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { 140 | scope.$watch(attr[attrName], function(boolVal) { 141 | // ensure boolean value 142 | boolVal = negate ? !boolVal : !!boolVal; 143 | elem.attr(ariaAttr, boolVal); 144 | }); 145 | } 146 | }; 147 | } 148 | /** 149 | * @ngdoc service 150 | * @name $aria 151 | * 152 | * @description 153 | * @priority 200 154 | * 155 | * The $aria service contains helper methods for applying common 156 | * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. 157 | * 158 | * ngAria injects common accessibility attributes that tell assistive technologies when HTML 159 | * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, 160 | * let's review a code snippet from ngAria itself: 161 | * 162 | *```js 163 | * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { 164 | * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); 165 | * }]) 166 | *``` 167 | * Shown above, the ngAria module creates a directive with the same signature as the 168 | * traditional `ng-disabled` directive. But this ngAria version is dedicated to 169 | * solely managing accessibility attributes on custom elements. The internal `$aria` service is 170 | * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the 171 | * developer, `aria-disabled` is injected as an attribute with its value synchronized to the 172 | * value in `ngDisabled`. 173 | * 174 | * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do 175 | * anything to enable this feature. The `aria-disabled` attribute is automatically managed 176 | * simply as a silent side-effect of using `ng-disabled` with the ngAria module. 177 | * 178 | * The full list of directives that interface with ngAria: 179 | * * **ngModel** 180 | * * **ngChecked** 181 | * * **ngReadonly** 182 | * * **ngRequired** 183 | * * **ngDisabled** 184 | * * **ngValue** 185 | * * **ngShow** 186 | * * **ngHide** 187 | * * **ngClick** 188 | * * **ngDblclick** 189 | * * **ngMessages** 190 | * 191 | * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each 192 | * directive. 193 | * 194 | * 195 | * ## Dependencies 196 | * Requires the {@link ngAria} module to be installed. 197 | */ 198 | this.$get = function() { 199 | return { 200 | config: function(key) { 201 | return config[key]; 202 | }, 203 | $$watchExpr: watchExpr 204 | }; 205 | }; 206 | } 207 | 208 | 209 | ngAriaModule.directive('ngShow', ['$aria', function($aria) { 210 | return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); 211 | }]) 212 | .directive('ngHide', ['$aria', function($aria) { 213 | return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); 214 | }]) 215 | .directive('ngValue', ['$aria', function($aria) { 216 | return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false); 217 | }]) 218 | .directive('ngChecked', ['$aria', function($aria) { 219 | return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); 220 | }]) 221 | .directive('ngReadonly', ['$aria', function($aria) { 222 | return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); 223 | }]) 224 | .directive('ngRequired', ['$aria', function($aria) { 225 | return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); 226 | }]) 227 | .directive('ngModel', ['$aria', function($aria) { 228 | 229 | function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) { 230 | return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)); 231 | } 232 | 233 | function shouldAttachRole(role, elem) { 234 | // if element does not have role attribute 235 | // AND element type is equal to role (if custom element has a type equaling shape) <-- remove? 236 | // AND element is not INPUT 237 | return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT'); 238 | } 239 | 240 | function getShape(attr, elem) { 241 | var type = attr.type, 242 | role = attr.role; 243 | 244 | return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : 245 | ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : 246 | (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : ''; 247 | } 248 | 249 | return { 250 | restrict: 'A', 251 | require: 'ngModel', 252 | priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value 253 | compile: function(elem, attr) { 254 | var shape = getShape(attr, elem); 255 | 256 | return { 257 | pre: function(scope, elem, attr, ngModel) { 258 | if (shape === 'checkbox') { 259 | //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles 260 | ngModel.$isEmpty = function(value) { 261 | return value === false; 262 | }; 263 | } 264 | }, 265 | post: function(scope, elem, attr, ngModel) { 266 | var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); 267 | 268 | function ngAriaWatchModelValue() { 269 | return ngModel.$modelValue; 270 | } 271 | 272 | function getRadioReaction(newVal) { 273 | var boolVal = (attr.value == ngModel.$viewValue); 274 | elem.attr('aria-checked', boolVal); 275 | } 276 | 277 | function getCheckboxReaction() { 278 | elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); 279 | } 280 | 281 | switch (shape) { 282 | case 'radio': 283 | case 'checkbox': 284 | if (shouldAttachRole(shape, elem)) { 285 | elem.attr('role', shape); 286 | } 287 | if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) { 288 | scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? 289 | getRadioReaction : getCheckboxReaction); 290 | } 291 | if (needsTabIndex) { 292 | elem.attr('tabindex', 0); 293 | } 294 | break; 295 | case 'range': 296 | if (shouldAttachRole(shape, elem)) { 297 | elem.attr('role', 'slider'); 298 | } 299 | if ($aria.config('ariaValue')) { 300 | var needsAriaValuemin = !elem.attr('aria-valuemin') && 301 | (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); 302 | var needsAriaValuemax = !elem.attr('aria-valuemax') && 303 | (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); 304 | var needsAriaValuenow = !elem.attr('aria-valuenow'); 305 | 306 | if (needsAriaValuemin) { 307 | attr.$observe('min', function ngAriaValueMinReaction(newVal) { 308 | elem.attr('aria-valuemin', newVal); 309 | }); 310 | } 311 | if (needsAriaValuemax) { 312 | attr.$observe('max', function ngAriaValueMinReaction(newVal) { 313 | elem.attr('aria-valuemax', newVal); 314 | }); 315 | } 316 | if (needsAriaValuenow) { 317 | scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { 318 | elem.attr('aria-valuenow', newVal); 319 | }); 320 | } 321 | } 322 | if (needsTabIndex) { 323 | elem.attr('tabindex', 0); 324 | } 325 | break; 326 | } 327 | 328 | if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required 329 | && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) { 330 | // ngModel.$error.required is undefined on custom controls 331 | attr.$observe('required', function() { 332 | elem.attr('aria-required', !!attr['required']); 333 | }); 334 | } 335 | 336 | if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) { 337 | scope.$watch(function ngAriaInvalidWatch() { 338 | return ngModel.$invalid; 339 | }, function ngAriaInvalidReaction(newVal) { 340 | elem.attr('aria-invalid', !!newVal); 341 | }); 342 | } 343 | } 344 | }; 345 | } 346 | }; 347 | }]) 348 | .directive('ngDisabled', ['$aria', function($aria) { 349 | return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); 350 | }]) 351 | .directive('ngMessages', function() { 352 | return { 353 | restrict: 'A', 354 | require: '?ngMessages', 355 | link: function(scope, elem, attr, ngMessages) { 356 | if (!elem.attr('aria-live')) { 357 | elem.attr('aria-live', 'assertive'); 358 | } 359 | } 360 | }; 361 | }) 362 | .directive('ngClick',['$aria', '$parse', function($aria, $parse) { 363 | return { 364 | restrict: 'A', 365 | compile: function(elem, attr) { 366 | var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true); 367 | return function(scope, elem, attr) { 368 | 369 | if (!isNodeOneOf(elem, nodeBlackList)) { 370 | 371 | if ($aria.config('bindRoleForClick') && !elem.attr('role')) { 372 | elem.attr('role', 'button'); 373 | } 374 | 375 | if ($aria.config('tabindex') && !elem.attr('tabindex')) { 376 | elem.attr('tabindex', 0); 377 | } 378 | 379 | if ($aria.config('bindKeypress') && !attr.ngKeypress) { 380 | elem.on('keypress', function(event) { 381 | var keyCode = event.which || event.keyCode; 382 | if (keyCode === 32 || keyCode === 13) { 383 | scope.$apply(callback); 384 | } 385 | 386 | function callback() { 387 | fn(scope, { $event: event }); 388 | } 389 | }); 390 | } 391 | } 392 | }; 393 | } 394 | }; 395 | }]) 396 | .directive('ngDblclick', ['$aria', function($aria) { 397 | return function(scope, elem, attr) { 398 | if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { 399 | elem.attr('tabindex', 0); 400 | } 401 | }; 402 | }]); 403 | 404 | 405 | })(window, window.angular); 406 | -------------------------------------------------------------------------------- /docs/app.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular.module('angularMaterialExpansionPanel', [ 2 | 'ngRoute', 3 | 'ngAnimate', 4 | 'ngMaterial', 5 | 'material.components.expansionPanels' 6 | ]) 7 | .config(configApp); 8 | 9 | 10 | configApp.$inject = ['$routeProvider']; 11 | function configApp($routeProvider) { 12 | $routeProvider 13 | .when('/', { 14 | templateUrl: 'pages/home/home.html', 15 | controller: 'HomeController', 16 | controllerAs: 'vm' 17 | }) 18 | .when('/group', { 19 | templateUrl: 'pages/group/group.html', 20 | controller: 'GroupController', 21 | controllerAs: 'vm' 22 | }) 23 | .when('/autoexpand', { 24 | templateUrl: 'pages/autoExpand/autoExpand.html', 25 | controller: 'AutoExpandController', 26 | controllerAs: 'vm' 27 | }) 28 | .when('/multiple', { 29 | templateUrl: 'pages/multiple/multiple.html', 30 | controller: 'MultipleController', 31 | controllerAs: 'vm' 32 | }) 33 | .otherwise('/'); 34 | } 35 | }()); -------------------------------------------------------------------------------- /docs/expansionPanels-theme.css: -------------------------------------------------------------------------------- 1 | md-expansion-panel { 2 | background: #FFF; } 3 | md-expansion-panel md-expansion-panel-collapsed, 4 | md-expansion-panel .md-expansion-panel-footer-container, 5 | md-expansion-panel .md-expansion-panel-header-container { 6 | background: #FFF; } 7 | md-expansion-panel md-expansion-panel-collapsed .md-title, 8 | md-expansion-panel md-expansion-panel-collapsed .md-summary, 9 | md-expansion-panel .md-expansion-panel-footer-container .md-title, 10 | md-expansion-panel .md-expansion-panel-footer-container .md-summary, 11 | md-expansion-panel .md-expansion-panel-header-container .md-title, 12 | md-expansion-panel .md-expansion-panel-header-container .md-summary { 13 | color: #333; } 14 | md-expansion-panel md-expansion-panel-footer .md-expansion-panel-footer-container, 15 | md-expansion-panel md-expansion-panel-header .md-expansion-panel-header-container { 16 | border-color: #DDD; } 17 | md-expansion-panel .md-expansion-panel-icon svg { 18 | fill: #999; } 19 | md-expansion-panel[disabled] md-expansion-panel-collapsed { 20 | color: #DDD; } 21 | md-expansion-panel:not(.md-open):not([disabled]):focus, 22 | md-expansion-panel:not(.md-open):not([disabled]):focus md-expansion-panel-collapsed { 23 | background: #EEE; } 24 | -------------------------------------------------------------------------------- /docs/expansionPanels.css: -------------------------------------------------------------------------------- 1 | .layout-padding > md-expansion-panel-group { 2 | padding: 0; } 3 | 4 | /* --- Expansion Panel --- */ 5 | md-expansion-panel { 6 | display: block; 7 | position: relative; 8 | outline: none; 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | padding: 0; 12 | box-shadow: 0 -1px 0 #e5e5e5, 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); } 13 | md-expansion-panel.md-open { 14 | margin-top: 16px; 15 | margin-bottom: 16px; } 16 | md-expansion-panel.md-open:first-of-type { 17 | margin-top: 0; } 18 | md-expansion-panel.md-close { 19 | overflow: hidden; } 20 | md-expansion-panel:not(.md-no-animation).md-open { 21 | -webkit-transition: margin-top 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); 22 | transition: margin-top 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); } 23 | md-expansion-panel:not(.md-no-animation).md-close { 24 | -webkit-transition: margin-top 0.08s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.08s cubic-bezier(0.25, 0.8, 0.25, 1); 25 | transition: margin-top 0.08s cubic-bezier(0.25, 0.8, 0.25, 1), margin-bottom 0.08s cubic-bezier(0.25, 0.8, 0.25, 1); } 26 | 27 | md-expansion-panel-collapsed .md-title, 28 | md-expansion-panel-header .md-title { 29 | -webkit-box-flex: 1; 30 | -ms-flex: 1; 31 | flex: 1; 32 | font-size: 16px; 33 | font-weight: 600; 34 | min-width: 80px; 35 | max-width: 184px; 36 | overflow: hidden; 37 | text-overflow: ellipsis; 38 | text-align: left; 39 | white-space: nowrap; } 40 | 41 | md-expansion-panel-collapsed .md-summary, 42 | md-expansion-panel-header .md-summary { 43 | -webkit-box-flex: 1; 44 | -ms-flex: 1; 45 | flex: 1; 46 | font-size: 13px; 47 | overflow: hidden; 48 | text-overflow: ellipsis; 49 | text-align: left; 50 | white-space: nowrap; } 51 | 52 | /* --- Expansion Panel Collapsed ---- */ 53 | md-expansion-panel md-expansion-panel-collapsed { 54 | display: -webkit-box; 55 | display: -ms-flexbox; 56 | display: flex; 57 | min-height: 48px; 58 | line-height: 48px; 59 | padding: 0 24px; 60 | opacity: 1; 61 | z-index: 2; 62 | box-sizing: border-box; } 63 | md-expansion-panel md-expansion-panel-collapsed.md-absolute { 64 | position: absolute; } 65 | md-expansion-panel md-expansion-panel-collapsed.md-hide { 66 | opacity: 0; } 67 | 68 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-collapsed.md-show { 69 | -webkit-transition: opacity 0.03s linear; 70 | transition: opacity 0.03s linear; } 71 | 72 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-collapsed.md-hide { 73 | -webkit-transition: opacity 0.1s cubic-bezier(0.25, 0.8, 0.25, 1); 74 | transition: opacity 0.1s cubic-bezier(0.25, 0.8, 0.25, 1); } 75 | 76 | /* --- Expansion Panel Expanded --- */ 77 | md-expansion-panel md-expansion-panel-expanded { 78 | display: none; 79 | min-height: 48px; } 80 | md-expansion-panel md-expansion-panel-expanded.md-show, md-expansion-panel md-expansion-panel-expanded.md-hide { 81 | display: block; } 82 | md-expansion-panel md-expansion-panel-expanded.md-scroll-y { 83 | overflow-y: auto; } 84 | md-expansion-panel md-expansion-panel-expanded.md-overflow { 85 | overflow: hidden; } 86 | md-expansion-panel md-expansion-panel-expanded md-expansion-panel-content { 87 | display: block; 88 | padding: 16px 24px; } 89 | 90 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-expanded.md-show { 91 | -webkit-transition: max-height 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); 92 | transition: max-height 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); } 93 | 94 | md-expansion-panel:not(.md-no-animation) md-expansion-panel-expanded.md-hide { 95 | -webkit-transition: max-height 0.06s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.06s cubic-bezier(0.25, 0.8, 0.25, 1); 96 | transition: max-height 0.06s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.06s cubic-bezier(0.25, 0.8, 0.25, 1); } 97 | 98 | /* --- Expansion Panel Header --- */ 99 | md-expansion-panel-header { 100 | display: block; 101 | position: relative; 102 | outline: none; } 103 | md-expansion-panel-header .md-expansion-panel-header-container { 104 | display: -webkit-box; 105 | display: -ms-flexbox; 106 | display: flex; 107 | min-height: 48px; 108 | line-height: 48px; 109 | padding: 0 24px; 110 | box-sizing: border-box; 111 | border-bottom: 1px solid; 112 | -webkit-box-align: center; 113 | -ms-flex-align: center; 114 | align-items: center; } 115 | md-expansion-panel-header.md-stick .md-expansion-panel-header-container { 116 | position: fixed; 117 | z-index: 2; 118 | -webkit-animation: panelBodyHeaderStickyHoverIn 0.3s ease-out both; 119 | animation: panelBodyHeaderStickyHoverIn 0.3s ease-out both; } 120 | md-expansion-panel-header.md-no-stick .md-expansion-panel-header-container { 121 | -webkit-animation: panelBodyHeaderStickyHoverOut 0.3s ease-out both; 122 | animation: panelBodyHeaderStickyHoverOut 0.3s ease-out both; } 123 | 124 | /* --- Expansion Panel Footer --- */ 125 | md-expansion-panel-footer { 126 | display: block; 127 | position: relative; } 128 | md-expansion-panel-footer.md-show, md-expansion-panel-footer.md-hide { 129 | display: block; } 130 | md-expansion-panel-footer .md-expansion-panel-footer-container { 131 | display: -webkit-box; 132 | display: -ms-flexbox; 133 | display: flex; 134 | min-height: 48px; 135 | line-height: 48px; 136 | padding: 0 24px; 137 | border-top: 1px solid; 138 | box-sizing: border-box; } 139 | md-expansion-panel-footer.md-stick .md-expansion-panel-footer-container { 140 | position: fixed; 141 | z-index: 2; } 142 | 143 | /* --- expand icon --- */ 144 | md-expansion-panel:not(.md-no-animation) .md-expansion-panel-icon { 145 | -webkit-transition: -webkit-transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 146 | transition: -webkit-transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 147 | transition: transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 148 | transition: transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1), -webkit-transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); } 149 | 150 | md-expansion-panel .md-expansion-panel-icon { 151 | -webkit-transform: rotate(90deg); 152 | transform: rotate(90deg); } 153 | md-expansion-panel .md-expansion-panel-icon:first-child { 154 | margin-right: 18px; } 155 | 156 | md-expansion-panel.md-open > md-expansion-panel-expanded > md-expansion-panel-header .md-expansion-panel-header-container .md-expansion-panel-icon { 157 | -webkit-transform: rotate(-90deg); 158 | transform: rotate(-90deg); } 159 | 160 | md-expansion-panel.md-open > md-expansion-panel.md-open > md-expansion-panel-collapsed .md-expansion-panel-icon { 161 | -webkit-transform: rotate(-90deg); 162 | transform: rotate(-90deg); } 163 | 164 | @-webkit-keyframes panelBodyHeaderStickyHoverIn { 165 | 0% { 166 | box-shadow: 0 0 0 0 transparent; } 167 | 100% { 168 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } } 169 | 170 | @keyframes panelBodyHeaderStickyHoverIn { 171 | 0% { 172 | box-shadow: 0 0 0 0 transparent; } 173 | 100% { 174 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } } 175 | 176 | @-webkit-keyframes panelBodyHeaderStickyHoverOut { 177 | 0% { 178 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } 179 | 100% { 180 | box-shadow: 0 0 0 0 transparent; } } 181 | 182 | @keyframes panelBodyHeaderStickyHoverOut { 183 | 0% { 184 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } 185 | 100% { 186 | box-shadow: 0 0 0 0 transparent; } } 187 | -------------------------------------------------------------------------------- /docs/expansionPanels.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";/** 2 | * @ngdoc module 3 | * @name material.components.expansionPanels 4 | * 5 | * @description 6 | * Expansion panel component 7 | */ 8 | angular 9 | .module('material.components.expansionPanels', [ 10 | 'material.core' 11 | ]); 12 | }()); -------------------------------------------------------------------------------- /docs/icons/ic_keyboard_arrow_right_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Angular Material Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 53 | 61 | 62 | 63 |
64 |
65 |
66 | 67 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/js/expansionPanel.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanel', expansionPanelDirective); 4 | 5 | 6 | var ANIMATION_TIME = 180; //ms 7 | 8 | 9 | /** 10 | * @ngdoc directive 11 | * @name mdExpansionPanel 12 | * @module material.components.expansionPanels 13 | * 14 | * @restrict E 15 | * 16 | * @description 17 | * `mdExpansionPanel` is the main container for panels 18 | * 19 | * @param {string=} md-component-id - add an id if you want to acces the panel via the `$mdExpansionPanel` service 20 | **/ 21 | function expansionPanelDirective() { 22 | var directive = { 23 | restrict: 'E', 24 | require: ['mdExpansionPanel', '?^^mdExpansionPanelGroup'], 25 | scope: true, 26 | compile: compile, 27 | controller: ['$scope', '$element', '$attrs', '$window', '$$rAF', '$mdConstant', '$mdUtil', '$mdComponentRegistry', '$timeout', '$q', '$animate', '$parse', controller] 28 | }; 29 | return directive; 30 | 31 | 32 | 33 | 34 | function compile(tElement, tAttrs) { 35 | var INVALID_PREFIX = 'Invalid HTML for md-expansion-panel: '; 36 | 37 | tElement.attr('tabindex', tAttrs.tabindex || '0'); 38 | 39 | if (tElement[0].querySelector('md-expansion-panel-collapsed') === null) { 40 | throw Error(INVALID_PREFIX + 'Expected a child element of `md-epxansion-panel-collapsed`'); 41 | } 42 | if (tElement[0].querySelector('md-expansion-panel-expanded') === null) { 43 | throw Error(INVALID_PREFIX + 'Expected a child element of `md-epxansion-panel-expanded`'); 44 | } 45 | 46 | return function postLink(scope, element, attrs, ctrls) { 47 | var epxansionPanelCtrl = ctrls[0]; 48 | var epxansionPanelGroupCtrl = ctrls[1]; 49 | 50 | epxansionPanelCtrl.epxansionPanelGroupCtrl = epxansionPanelGroupCtrl || undefined; 51 | epxansionPanelCtrl.init(); 52 | }; 53 | } 54 | 55 | 56 | 57 | 58 | function controller($scope, $element, $attrs, $window, $$rAF, $mdConstant, $mdUtil, $mdComponentRegistry, $timeout, $q, $animate, $parse) { 59 | /* jshint validthis: true */ 60 | var vm = this; 61 | 62 | var collapsedCtrl; 63 | var expandedCtrl; 64 | var headerCtrl; 65 | var footerCtrl; 66 | var deregister; 67 | var scrollContainer; 68 | var stickyContainer; 69 | var topKiller; 70 | var resizeKiller; 71 | var onRemoveCallback; 72 | var transformParent; 73 | var backdrop; 74 | var inited = false; 75 | var registerOnInit = false; 76 | var _isOpen = false; 77 | var isDisabled = false; 78 | var debouncedUpdateScroll = $$rAF.throttle(updateScroll); 79 | var debouncedUpdateResize = $$rAF.throttle(updateResize); 80 | 81 | vm.registerCollapsed = function (ctrl) { collapsedCtrl = ctrl; }; 82 | vm.registerExpanded = function (ctrl) { expandedCtrl = ctrl; }; 83 | vm.registerHeader = function (ctrl) { headerCtrl = ctrl; }; 84 | vm.registerFooter = function (ctrl) { footerCtrl = ctrl; }; 85 | 86 | 87 | 88 | if ($attrs.mdComponentId === undefined) { 89 | $attrs.$set('mdComponentId', '_expansion_panel_id_' + $mdUtil.nextUid()); 90 | registerPanel(); 91 | } else { 92 | $attrs.$observe('mdComponentId', function() { 93 | registerPanel(); 94 | }); 95 | } 96 | 97 | vm.$element = $element; 98 | vm.expand = expand; 99 | vm.collapse = collapse; 100 | vm.remove = remove; 101 | vm.destroy = destroy; 102 | vm.onRemove = onRemove; 103 | vm.init = init; 104 | 105 | $attrs.$observe('disabled', function(disabled) { 106 | isDisabled = (typeof disabled === 'string' && disabled !== 'false') ? true : false; 107 | 108 | if (isDisabled === true) { 109 | $element.attr('tabindex', '-1'); 110 | } else { 111 | $element.attr('tabindex', '0'); 112 | } 113 | }); 114 | 115 | $element 116 | .on('focus', function (ev) { 117 | $element.on('keydown', handleKeypress); 118 | }) 119 | .on('blur', function (ev) { 120 | $element.off('keydown', handleKeypress); 121 | }); 122 | 123 | function handleKeypress(ev) { 124 | var keyCodes = $mdConstant.KEY_CODE; 125 | switch (ev.keyCode) { 126 | case keyCodes.ENTER: 127 | expand(); 128 | break; 129 | case keyCodes.ESCAPE: 130 | collapse(); 131 | break; 132 | } 133 | } 134 | 135 | 136 | $scope.$panel = { 137 | collapse: collapse, 138 | expand: expand, 139 | remove: remove, 140 | isOpen: isOpen 141 | }; 142 | 143 | $scope.$on('$destroy', function () { 144 | removeClickCatcher(); 145 | 146 | // remove component from registry 147 | if (typeof deregister === 'function') { 148 | deregister(); 149 | deregister = undefined; 150 | } 151 | killEvents(); 152 | }); 153 | 154 | 155 | 156 | 157 | 158 | function init() { 159 | inited = true; 160 | if (registerOnInit === true) { 161 | registerPanel(); 162 | } 163 | } 164 | 165 | 166 | function registerPanel() { 167 | if (inited === false) { 168 | registerOnInit = true; 169 | return; 170 | } 171 | 172 | // deregister if component was already registered 173 | if (typeof deregister === 'function') { 174 | deregister(); 175 | deregister = undefined; 176 | } 177 | // remove component from group ctrl if component was already added 178 | if (vm.componentId && vm.epxansionPanelGroupCtrl) { 179 | vm.epxansionPanelGroupCtrl.removePanel(vm.componentId); 180 | } 181 | 182 | // if componentId was removed then set one 183 | if ($attrs.mdComponentId === undefined) { 184 | $attrs.$set('mdComponentId', '_expansion_panel_id_' + $mdUtil.nextUid()); 185 | } 186 | 187 | vm.componentId = $attrs.mdComponentId; 188 | deregister = $mdComponentRegistry.register({ 189 | expand: expand, 190 | collapse: collapse, 191 | remove: remove, 192 | onRemove: onRemove, 193 | isOpen: isOpen, 194 | addClickCatcher: addClickCatcher, 195 | removeClickCatcher: removeClickCatcher, 196 | componentId: $attrs.mdComponentId 197 | }, $attrs.mdComponentId); 198 | 199 | if (vm.epxansionPanelGroupCtrl) { 200 | vm.epxansionPanelGroupCtrl.addPanel(vm.componentId, { 201 | expand: expand, 202 | collapse: collapse, 203 | remove: remove, 204 | onRemove: onRemove, 205 | destroy: destroy, 206 | isOpen: isOpen 207 | }); 208 | } 209 | } 210 | 211 | 212 | function isOpen() { 213 | return _isOpen; 214 | } 215 | 216 | function expand(options) { 217 | if (_isOpen === true || isDisabled === true) { return; } 218 | _isOpen = true; 219 | options = options || {}; 220 | 221 | var deferred = $q.defer(); 222 | 223 | if (vm.epxansionPanelGroupCtrl) { 224 | vm.epxansionPanelGroupCtrl.expandPanel(vm.componentId); 225 | } 226 | 227 | $element.removeClass('md-close'); 228 | $element.addClass('md-open'); 229 | if (options.animation === false) { 230 | $element.addClass('md-no-animation'); 231 | } else { 232 | $element.removeClass('md-no-animation'); 233 | } 234 | 235 | initEvents(); 236 | collapsedCtrl.hide(options); 237 | expandedCtrl.show(options); 238 | 239 | if (headerCtrl) { headerCtrl.show(options); } 240 | if (footerCtrl) { footerCtrl.show(options); } 241 | 242 | $timeout(function () { 243 | deferred.resolve(); 244 | }, options.animation === false ? 0 : ANIMATION_TIME); 245 | return deferred.promise; 246 | } 247 | 248 | 249 | function collapse(options) { 250 | if (_isOpen === false) { return; } 251 | _isOpen = false; 252 | options = options || {}; 253 | 254 | var deferred = $q.defer(); 255 | 256 | $element.addClass('md-close'); 257 | $element.removeClass('md-open'); 258 | if (options.animation === false) { 259 | $element.addClass('md-no-animation'); 260 | } else { 261 | $element.removeClass('md-no-animation'); 262 | } 263 | 264 | killEvents(); 265 | collapsedCtrl.show(options); 266 | expandedCtrl.hide(options); 267 | 268 | if (headerCtrl) { headerCtrl.hide(options); } 269 | if (footerCtrl) { footerCtrl.hide(options); } 270 | 271 | $timeout(function () { 272 | deferred.resolve(); 273 | }, options.animation === false ? 0 : ANIMATION_TIME); 274 | return deferred.promise; 275 | } 276 | 277 | 278 | function remove(options) { 279 | options = options || {}; 280 | var deferred = $q.defer(); 281 | 282 | if (vm.epxansionPanelGroupCtrl) { 283 | vm.epxansionPanelGroupCtrl.removePanel(vm.componentId); 284 | } 285 | 286 | if (typeof deregister === 'function') { 287 | deregister(); 288 | deregister = undefined; 289 | } 290 | 291 | if (options.animation === false || _isOpen === false) { 292 | $scope.$destroy(); 293 | $element.remove(); 294 | deferred.resolve(); 295 | callbackRemove(); 296 | } else { 297 | collapse(); 298 | $timeout(function () { 299 | $scope.$destroy(); 300 | $element.remove(); 301 | deferred.resolve(); 302 | callbackRemove(); 303 | }, ANIMATION_TIME); 304 | } 305 | 306 | return deferred.promise; 307 | } 308 | 309 | function onRemove(callback) { 310 | onRemoveCallback = callback; 311 | } 312 | 313 | function callbackRemove() { 314 | if (typeof onRemoveCallback === 'function') { 315 | onRemoveCallback(); 316 | onRemoveCallback = undefined; 317 | } 318 | } 319 | 320 | function destroy() { 321 | $scope.$destroy(); 322 | } 323 | 324 | 325 | 326 | function initEvents() { 327 | if ((!footerCtrl || footerCtrl.noSticky === true) && (!headerCtrl || headerCtrl.noSticky === true)) { 328 | return; 329 | } 330 | 331 | // watch for panel position changes 332 | topKiller = $scope.$watch(function () { return $element[0].offsetTop; }, debouncedUpdateScroll, true); 333 | 334 | // watch for panel position changes 335 | resizeKiller = $scope.$watch(function () { return $element[0].offsetWidth; }, debouncedUpdateResize, true); 336 | 337 | // listen to md-content scroll events id we are nested in one 338 | scrollContainer = $mdUtil.getNearestContentElement($element); 339 | if (scrollContainer.nodeName === 'MD-CONTENT') { 340 | transformParent = getTransformParent(scrollContainer); 341 | angular.element(scrollContainer).on('scroll', debouncedUpdateScroll); 342 | } else { 343 | transformParent = undefined; 344 | } 345 | 346 | // listen to expanded content scroll if height is set 347 | if (expandedCtrl.setHeight === true) { 348 | expandedCtrl.$element.on('scroll', debouncedUpdateScroll); 349 | } 350 | 351 | // listen to window scroll events 352 | angular.element($window) 353 | .on('scroll', debouncedUpdateScroll) 354 | .on('resize', debouncedUpdateScroll) 355 | .on('resize', debouncedUpdateResize); 356 | } 357 | 358 | 359 | function killEvents() { 360 | if (typeof topKiller === 'function') { 361 | topKiller(); 362 | topKiller = undefined; 363 | } 364 | 365 | if (typeof resizeKiller === 'function') { 366 | resizeKiller(); 367 | resizeKiller = undefined; 368 | } 369 | 370 | if (scrollContainer && scrollContainer.nodeName === 'MD-CONTENT') { 371 | angular.element(scrollContainer).off('scroll', debouncedUpdateScroll); 372 | } 373 | 374 | if (expandedCtrl.setHeight === true) { 375 | expandedCtrl.$element.off('scroll', debouncedUpdateScroll); 376 | } 377 | 378 | angular.element($window) 379 | .off('scroll', debouncedUpdateScroll) 380 | .off('resize', debouncedUpdateScroll) 381 | .off('resize', debouncedUpdateResize); 382 | } 383 | 384 | 385 | 386 | function getTransformParent(el) { 387 | var parent = el.parentNode; 388 | 389 | while (parent && parent !== document) { 390 | if (hasComputedStyle(parent, 'transform')) { 391 | return parent; 392 | } 393 | parent = parent.parentNode; 394 | } 395 | 396 | return undefined; 397 | } 398 | 399 | function hasComputedStyle(target, key) { 400 | var hasValue = false; 401 | 402 | if (target) { 403 | var computedStyles = $window.getComputedStyle(target); 404 | hasValue = computedStyles[key] !== undefined && computedStyles[key] !== 'none'; 405 | } 406 | 407 | return hasValue; 408 | } 409 | 410 | 411 | function updateScroll(e) { 412 | var top; 413 | var bottom; 414 | var bounds; 415 | if (expandedCtrl.setHeight === true) { 416 | bounds = expandedCtrl.$element[0].getBoundingClientRect(); 417 | } else { 418 | bounds = scrollContainer.getBoundingClientRect(); 419 | } 420 | var transformTop = transformParent ? transformParent.getBoundingClientRect().top : 0; 421 | 422 | // we never want the header going post the top of the page. to prevent this don't allow top to go below 0 423 | top = Math.max(bounds.top, 0); 424 | bottom = top + bounds.height; 425 | 426 | if (footerCtrl && footerCtrl.noSticky === false) { footerCtrl.onScroll(top, bottom, transformTop); } 427 | if (headerCtrl && headerCtrl.noSticky === false) { headerCtrl.onScroll(top, bottom, transformTop); } 428 | } 429 | 430 | 431 | function updateResize() { 432 | var value = $element[0].offsetWidth; 433 | if (footerCtrl && footerCtrl.noSticky === false) { footerCtrl.onResize(value); } 434 | if (headerCtrl && headerCtrl.noSticky === false) { headerCtrl.onResize(value); } 435 | } 436 | 437 | 438 | 439 | 440 | function addClickCatcher(clickCallback) { 441 | backdrop = $mdUtil.createBackdrop($scope); 442 | backdrop[0].tabIndex = -1; 443 | 444 | if (typeof clickCallback === 'function') { 445 | backdrop.on('click', clickCallback); 446 | } 447 | 448 | $animate.enter(backdrop, $element.parent(), null, {duration: 0}); 449 | $element.css('z-index', 60); 450 | } 451 | 452 | function removeClickCatcher() { 453 | if (backdrop) { 454 | backdrop.remove(); 455 | backdrop.off('click'); 456 | backdrop = undefined; 457 | $element.css('z-index', ''); 458 | } 459 | } 460 | } 461 | } 462 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanel.service.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .factory('$mdExpansionPanel', expansionPanelService); 4 | 5 | 6 | /** 7 | * @ngdoc service 8 | * @name $mdExpansionPanel 9 | * @module material.components.expansionPanels 10 | * 11 | * @description 12 | * Expand and collapse Expansion Panel using its `md-component-id` 13 | * 14 | * @example 15 | * $mdExpansionPanel('comonentId').then(function (instance) { 16 | * instance.exapand(); 17 | * instance.collapse({animation: false}); 18 | * instance.remove({animation: false}); 19 | * instance.onRemove(function () {}); 20 | * }); 21 | */ 22 | expansionPanelService.$inject = ['$mdComponentRegistry', '$mdUtil', '$log']; 23 | function expansionPanelService($mdComponentRegistry, $mdUtil, $log) { 24 | var errorMsg = "ExpansionPanel '{0}' is not available! Did you use md-component-id='{0}'?"; 25 | var service = { 26 | find: findInstance, 27 | waitFor: waitForInstance 28 | }; 29 | 30 | return function (handle) { 31 | if (handle === undefined) { return service; } 32 | return findInstance(handle); 33 | }; 34 | 35 | 36 | 37 | function findInstance(handle) { 38 | var instance = $mdComponentRegistry.get(handle); 39 | 40 | if (!instance) { 41 | // Report missing instance 42 | $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) ); 43 | return undefined; 44 | } 45 | 46 | return instance; 47 | } 48 | 49 | function waitForInstance(handle) { 50 | return $mdComponentRegistry.when(handle).catch($log.error); 51 | } 52 | } 53 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelCollapsed.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelCollapsed', expansionPanelCollapsedDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelCollapsed 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelCollapsed` is used to contain content when the panel is collapsed 16 | **/ 17 | expansionPanelCollapsedDirective.$inject = ['$animateCss', '$timeout']; 18 | function expansionPanelCollapsedDirective($animateCss, $timeout) { 19 | var directive = { 20 | restrict: 'E', 21 | require: '^^mdExpansionPanel', 22 | link: link 23 | }; 24 | return directive; 25 | 26 | 27 | function link(scope, element, attrs, expansionPanelCtrl) { 28 | expansionPanelCtrl.registerCollapsed({ 29 | show: show, 30 | hide: hide 31 | }); 32 | 33 | 34 | element.on('click', function () { 35 | expansionPanelCtrl.expand(); 36 | }); 37 | 38 | 39 | function hide(options) { 40 | // set width to maintian demensions when element is set to postion: absolute 41 | element.css('width', element[0].offsetWidth + 'px'); 42 | // set min height so the expansion panel does not shrink when collapsed element is set to position: absolute 43 | expansionPanelCtrl.$element.css('min-height', element[0].offsetHeight + 'px'); 44 | 45 | var animationParams = { 46 | addClass: 'md-absolute md-hide', 47 | from: {opacity: 1}, 48 | to: {opacity: 0} 49 | }; 50 | if (options.animation === false) { animationParams.duration = 0; } 51 | $animateCss(element, animationParams) 52 | .start() 53 | .then(function () { 54 | element.removeClass('md-hide'); 55 | element.css('display', 'none'); 56 | }); 57 | } 58 | 59 | 60 | function show(options) { 61 | element.css('display', ''); 62 | // set width to maintian demensions when element is set to postion: absolute 63 | element.css('width', element[0].parentNode.offsetWidth + 'px'); 64 | 65 | var animationParams = { 66 | addClass: 'md-show', 67 | from: {opacity: 0}, 68 | to: {opacity: 1} 69 | }; 70 | if (options.animation === false) { animationParams.duration = 0; } 71 | $animateCss(element, animationParams) 72 | .start() 73 | .then(function () { 74 | // safari will animate the min-height if transition is not set to 0 75 | expansionPanelCtrl.$element.css('transition', 'none'); 76 | element.removeClass('md-absolute md-show'); 77 | 78 | // remove width when element is no longer position: absolute 79 | element.css('width', ''); 80 | 81 | 82 | // remove min height when element is no longer position: absolute 83 | expansionPanelCtrl.$element.css('min-height', ''); 84 | // remove transition block on next digest 85 | $timeout(function () { 86 | expansionPanelCtrl.$element.css('transition', ''); 87 | }, 0); 88 | }); 89 | } 90 | } 91 | } 92 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelExpanded.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelExpanded', expansionPanelExpandedDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelExpanded 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelExpanded` is used to contain content when the panel is expanded 16 | * 17 | * @param {number=} height - add this aatribute set the max height of the expanded content. The container will be set to scroll 18 | **/ 19 | expansionPanelExpandedDirective.$inject = ['$animateCss', '$timeout']; 20 | function expansionPanelExpandedDirective($animateCss, $timeout) { 21 | var directive = { 22 | restrict: 'E', 23 | require: '^^mdExpansionPanel', 24 | link: link 25 | }; 26 | return directive; 27 | 28 | 29 | function link(scope, element, attrs, expansionPanelCtrl) { 30 | var setHeight = attrs.height || undefined; 31 | if (setHeight !== undefined) { setHeight = setHeight.replace('px', '') + 'px'; } 32 | 33 | expansionPanelCtrl.registerExpanded({ 34 | show: show, 35 | hide: hide, 36 | setHeight: setHeight !== undefined, 37 | $element: element 38 | }); 39 | 40 | 41 | 42 | 43 | function hide(options) { 44 | var height = setHeight ? setHeight : element[0].scrollHeight + 'px'; 45 | element.addClass('md-hide md-overflow'); 46 | element.removeClass('md-show md-scroll-y'); 47 | 48 | var animationParams = { 49 | from: {'max-height': height, opacity: 1}, 50 | to: {'max-height': '48px', opacity: 0} 51 | }; 52 | if (options.animation === false) { animationParams.duration = 0; } 53 | $animateCss(element, animationParams) 54 | .start() 55 | .then(function () { 56 | element.css('display', 'none'); 57 | element.removeClass('md-hide'); 58 | }); 59 | } 60 | 61 | 62 | function show(options) { 63 | element.css('display', ''); 64 | element.addClass('md-show md-overflow'); 65 | // use passed in height or the contents height 66 | var height = setHeight ? setHeight : element[0].scrollHeight + 'px'; 67 | 68 | var animationParams = { 69 | from: {'max-height': '48px', opacity: 0}, 70 | to: {'max-height': height, opacity: 1} 71 | }; 72 | if (options.animation === false) { animationParams.duration = 0; } 73 | $animateCss(element, animationParams) 74 | .start() 75 | .then(function () { 76 | 77 | // if height was passed in then set div to scroll 78 | if (setHeight !== undefined) { 79 | element.addClass('md-scroll-y'); 80 | } else { 81 | // safari will animate the max-height if transition is not set to 0 82 | element.css('transition', 'none'); 83 | element.css('max-height', 'none'); 84 | // remove transition block on next digest 85 | $timeout(function () { 86 | element.css('transition', ''); 87 | }, 0); 88 | } 89 | 90 | element.removeClass('md-overflow'); 91 | }); 92 | } 93 | } 94 | } 95 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelFooter.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelFooter', expansionPanelFooterDirective); 4 | 5 | 6 | 7 | 8 | /** 9 | * @ngdoc directive 10 | * @name mdExpansionPanelFooter 11 | * @module material.components.expansionPanels 12 | * 13 | * @restrict E 14 | * 15 | * @description 16 | * `mdExpansionPanelFooter` is nested inside of `mdExpansionPanelExpanded` and contains content you want at the bottom. 17 | * By default the Footer will stick to the bottom of the page if the panel expands past 18 | * this is optional 19 | * 20 | * @param {boolean=} md-no-sticky - add this aatribute to disable sticky 21 | **/ 22 | function expansionPanelFooterDirective() { 23 | var directive = { 24 | restrict: 'E', 25 | transclude: true, 26 | template: '', 27 | require: '^^mdExpansionPanel', 28 | link: link 29 | }; 30 | return directive; 31 | 32 | 33 | 34 | function link(scope, element, attrs, expansionPanelCtrl) { 35 | var isStuck = false; 36 | var noSticky = attrs.mdNoSticky !== undefined; 37 | var container = angular.element(element[0].querySelector('.md-expansion-panel-footer-container')); 38 | 39 | expansionPanelCtrl.registerFooter({ 40 | show: show, 41 | hide: hide, 42 | onScroll: onScroll, 43 | onResize: onResize, 44 | noSticky: noSticky 45 | }); 46 | 47 | 48 | 49 | function show() { 50 | 51 | } 52 | function hide() { 53 | unstick(); 54 | } 55 | 56 | function onScroll(top, bottom, transformTop) { 57 | var height; 58 | var footerBounds = element[0].getBoundingClientRect(); 59 | var offset; 60 | 61 | if (footerBounds.bottom > bottom) { 62 | height = container[0].offsetHeight; 63 | offset = bottom - height - transformTop; 64 | if (offset < element[0].parentNode.getBoundingClientRect().top) { 65 | offset = element[0].parentNode.getBoundingClientRect().top; 66 | } 67 | 68 | // set container width because element becomes postion fixed 69 | container.css('width', expansionPanelCtrl.$element[0].offsetWidth + 'px'); 70 | 71 | // set element height so it does not loose its height when container is position fixed 72 | element.css('height', height + 'px'); 73 | container.css('top', offset + 'px'); 74 | 75 | element.addClass('md-stick'); 76 | isStuck = true; 77 | } else if (isStuck === true) { 78 | unstick(); 79 | } 80 | } 81 | 82 | function onResize(width) { 83 | if (isStuck === false) { return; } 84 | container.css('width', width + 'px'); 85 | } 86 | 87 | 88 | function unstick() { 89 | isStuck = false; 90 | container.css('width', ''); 91 | container.css('top', ''); 92 | element.css('height', ''); 93 | element.removeClass('md-stick'); 94 | } 95 | } 96 | } 97 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelGroup.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelGroup', expansionPanelGroupDirective); 4 | 5 | /** 6 | * @ngdoc directive 7 | * @name mdExpansionPanelGroup 8 | * @module material.components.expansionPanels 9 | * 10 | * @restrict E 11 | * 12 | * @description 13 | * `mdExpansionPanelGroup` is a container used to manage multiple expansion panels 14 | * 15 | * @param {string=} md-component-id - add an id if you want to acces the panel via the `$mdExpansionPanelGroup` service 16 | * @param {string=} auto-expand - panels expand when added to `` 17 | * @param {string=} multiple - allows for more than one panel to be expanded at a time 18 | **/ 19 | function expansionPanelGroupDirective() { 20 | var directive = { 21 | restrict: 'E', 22 | controller: ['$scope', '$attrs', '$element', '$mdComponentRegistry', controller] 23 | }; 24 | return directive; 25 | 26 | 27 | function controller($scope, $attrs, $element, $mdComponentRegistry) { 28 | /* jshint validthis: true */ 29 | var vm = this; 30 | 31 | var deregister; 32 | var registered = {}; 33 | var panels = {}; 34 | var onChangeFuncs = []; 35 | var multipleExpand = $attrs.mdMultiple !== undefined || $attrs.multiple !== undefined; 36 | var autoExpand = $attrs.mdAutoExpand !== undefined || $attrs.autoExpand !== undefined; 37 | 38 | 39 | deregister = $mdComponentRegistry.register({ 40 | $element: $element, 41 | register: register, 42 | getRegistered: getRegistered, 43 | getAll: getAll, 44 | getOpen: getOpen, 45 | remove: remove, 46 | removeAll: removeAll, 47 | collapseAll: collapseAll, 48 | onChange: onChange, 49 | count: panelCount 50 | }, $attrs.mdComponentId); 51 | 52 | vm.addPanel = addPanel; 53 | vm.expandPanel = expandPanel; 54 | vm.removePanel = removePanel; 55 | 56 | 57 | $scope.$on('$destroy', function () { 58 | if (typeof deregister === 'function') { 59 | deregister(); 60 | deregister = undefined; 61 | } 62 | 63 | // destroy all panels 64 | // for some reason the child panels scopes are not getting destroyed 65 | Object.keys(panels).forEach(function (key) { 66 | panels[key].destroy(); 67 | }); 68 | }); 69 | 70 | 71 | 72 | function onChange(callback) { 73 | onChangeFuncs.push(callback); 74 | 75 | return function () { 76 | onChangeFuncs.splice(onChangeFuncs.indexOf(callback), 1); 77 | }; 78 | } 79 | 80 | function callOnChange() { 81 | var count = panelCount(); 82 | onChangeFuncs.forEach(function (func) { 83 | func(count); 84 | }); 85 | } 86 | 87 | 88 | function addPanel(componentId, panelCtrl) { 89 | panels[componentId] = panelCtrl; 90 | if (autoExpand === true) { 91 | panelCtrl.expand(); 92 | closeOthers(componentId); 93 | } 94 | callOnChange(); 95 | } 96 | 97 | function expandPanel(componentId) { 98 | closeOthers(componentId); 99 | } 100 | 101 | function remove(componentId, options) { 102 | return panels[componentId].remove(options); 103 | } 104 | 105 | function removeAll(options) { 106 | Object.keys(panels).forEach(function (panelId) { 107 | panels[panelId].remove(options); 108 | }); 109 | } 110 | 111 | function removePanel(componentId) { 112 | delete panels[componentId]; 113 | callOnChange(); 114 | } 115 | 116 | function panelCount() { 117 | return Object.keys(panels).length; 118 | } 119 | 120 | function closeOthers(id) { 121 | if (multipleExpand === false) { 122 | Object.keys(panels).forEach(function (panelId) { 123 | if (panelId !== id) { panels[panelId].collapse(); } 124 | }); 125 | } 126 | } 127 | 128 | 129 | function register(name, options) { 130 | if (registered[name] !== undefined) { 131 | throw Error('$mdExpansionPanelGroup.register() The name "' + name + '" has already been registered'); 132 | } 133 | registered[name] = options; 134 | } 135 | 136 | 137 | function getRegistered(name) { 138 | if (registered[name] === undefined) { 139 | throw Error('$mdExpansionPanelGroup.addPanel() Cannot find Panel with name of "' + name + '"'); 140 | } 141 | return registered[name]; 142 | } 143 | 144 | 145 | function getAll() { 146 | return Object.keys(panels).map(function (panelId) { 147 | return panels[panelId]; 148 | }); 149 | } 150 | 151 | function getOpen() { 152 | return Object.keys(panels).map(function (panelId) { 153 | return panels[panelId]; 154 | }).filter(function (instance) { 155 | return instance.isOpen(); 156 | }); 157 | } 158 | 159 | function collapseAll(noAnimation) { 160 | var animation = noAnimation === true ? false : true; 161 | Object.keys(panels).forEach(function (panelId) { 162 | panels[panelId].collapse({animation: animation}); 163 | }); 164 | } 165 | } 166 | } 167 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelGroup.service.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .factory('$mdExpansionPanelGroup', expansionPanelGroupService); 4 | 5 | 6 | /** 7 | * @ngdoc service 8 | * @name $mdExpansionPanelGroup 9 | * @module material.components.expansionPanels 10 | * 11 | * @description 12 | * Expand and collapse Expansion Panel using its `md-component-id` 13 | * 14 | * @example 15 | * $mdExpansionPanelGroup('comonentId').then(function (instance) { 16 | * instance.register({ 17 | * componentId: 'cardComponentId', 18 | * templateUrl: 'template.html', 19 | * controller: 'Controller' 20 | * }); 21 | * instance.add('cardComponentId', {local: localData}); 22 | * instance.remove('cardComponentId', {animation: false}); 23 | * instance.removeAll({animation: false}); 24 | * }); 25 | */ 26 | expansionPanelGroupService.$inject = ['$mdComponentRegistry', '$mdUtil', '$mdExpansionPanel', '$templateRequest', '$rootScope', '$compile', '$controller', '$q', '$log']; 27 | function expansionPanelGroupService($mdComponentRegistry, $mdUtil, $mdExpansionPanel, $templateRequest, $rootScope, $compile, $controller, $q, $log) { 28 | var errorMsg = "ExpansionPanelGroup '{0}' is not available! Did you use md-component-id='{0}'?"; 29 | var service = { 30 | find: findInstance, 31 | waitFor: waitForInstance 32 | }; 33 | 34 | return function (handle) { 35 | if (handle === undefined) { return service; } 36 | return findInstance(handle); 37 | }; 38 | 39 | 40 | 41 | function findInstance(handle) { 42 | var instance = $mdComponentRegistry.get(handle); 43 | 44 | if (!instance) { 45 | // Report missing instance 46 | $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) ); 47 | return undefined; 48 | } 49 | 50 | return createGroupInstance(instance); 51 | } 52 | 53 | function waitForInstance(handle) { 54 | var deffered = $q.defer(); 55 | 56 | $mdComponentRegistry.when(handle).then(function (instance) { 57 | deffered.resolve(createGroupInstance(instance)); 58 | }).catch(function (error) { 59 | deffered.reject(); 60 | $log.error(error); 61 | }); 62 | 63 | return deffered.promise; 64 | } 65 | 66 | 67 | 68 | 69 | 70 | // --- returned service for group instance --- 71 | 72 | function createGroupInstance(instance) { 73 | var service = { 74 | add: add, 75 | register: register, 76 | getAll: getAll, 77 | getOpen: getOpen, 78 | remove: remove, 79 | removeAll: removeAll, 80 | collapseAll: collapseAll, 81 | onChange: onChange, 82 | count: count 83 | }; 84 | 85 | return service; 86 | 87 | 88 | function register(name, options) { 89 | if (typeof name !== 'string') { 90 | throw Error('$mdExpansionPanelGroup.register() Expects name to be a string'); 91 | } 92 | 93 | validateOptions(options); 94 | instance.register(name, options); 95 | } 96 | 97 | function remove(componentId, options) { 98 | return instance.remove(componentId, options); 99 | } 100 | 101 | function removeAll(options) { 102 | instance.removeAll(options); 103 | } 104 | 105 | function onChange(callback) { 106 | return instance.onChange(callback); 107 | } 108 | 109 | function count() { 110 | return instance.count(); 111 | } 112 | 113 | function getAll() { 114 | return instance.getAll(); 115 | } 116 | 117 | function getOpen() { 118 | return instance.getOpen(); 119 | } 120 | 121 | function collapseAll(noAnimation) { 122 | instance.collapseAll(noAnimation); 123 | } 124 | 125 | 126 | function add(options, locals) { 127 | locals = locals || {}; 128 | // assume if options is a string then they are calling a registered card by its component id 129 | if (typeof options === 'string') { 130 | // call add panel with the stored options 131 | return add(instance.getRegistered(options), locals); 132 | } 133 | 134 | validateOptions(options); 135 | if (options.componentId && instance.isPanelActive(options.componentId)) { 136 | return $q.reject('panel with componentId "' + options.componentId + '" is currently active'); 137 | } 138 | 139 | 140 | var deffered = $q.defer(); 141 | var scope = $rootScope.$new(); 142 | angular.extend(scope, options.scope); 143 | 144 | getTemplate(options, function (template) { 145 | var element = angular.element(template); 146 | var componentId = options.componentId || element.attr('md-component-id') || '_panelComponentId_' + $mdUtil.nextUid(); 147 | var panelPromise = $mdExpansionPanel().waitFor(componentId); 148 | element.attr('md-component-id', componentId); 149 | 150 | var linkFunc = $compile(element); 151 | if (options.controller) { 152 | angular.extend(locals, options.locals || {}); 153 | locals.$scope = scope; 154 | locals.$panel = panelPromise; 155 | var invokeCtrl = $controller(options.controller, locals, true); 156 | var ctrl = invokeCtrl(); 157 | element.data('$ngControllerController', ctrl); 158 | element.children().data('$ngControllerController', ctrl); 159 | if (options.controllerAs) { 160 | scope[options.controllerAs] = ctrl; 161 | } 162 | } 163 | 164 | // link after the element is added so we can find card manager directive 165 | instance.$element.append(element); 166 | linkFunc(scope); 167 | 168 | panelPromise.then(function (instance) { 169 | deffered.resolve(instance); 170 | }); 171 | }); 172 | 173 | return deffered.promise; 174 | } 175 | 176 | 177 | function validateOptions(options) { 178 | if (typeof options !== 'object' || options === null) { 179 | throw Error('$mdExapnsionPanelGroup.add()/.register() : Requires an options object to be passed in'); 180 | } 181 | 182 | // if none of these exist then a dialog box cannot be created 183 | if (!options.template && !options.templateUrl) { 184 | throw Error('$mdExapnsionPanelGroup.add()/.register() : Is missing required paramters to create. Required One of the following: template, templateUrl'); 185 | } 186 | } 187 | 188 | 189 | 190 | function getTemplate(options, callback) { 191 | var template; 192 | 193 | if (options.templateUrl !== undefined) { 194 | $templateRequest(options.templateUrl) 195 | .then(function(response) { 196 | callback(response); 197 | }); 198 | } else { 199 | callback(options.template); 200 | } 201 | } 202 | } 203 | } 204 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelHeader.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelHeader', expansionPanelHeaderDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelHeader 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelHeader` is nested inside of `mdExpansionPanelExpanded` and contains content you want in place of the collapsed content 16 | * this is optional 17 | * 18 | * @param {boolean=} md-no-sticky - add this aatribute to disable sticky 19 | **/ 20 | expansionPanelHeaderDirective.$inject = []; 21 | function expansionPanelHeaderDirective() { 22 | var directive = { 23 | restrict: 'E', 24 | transclude: true, 25 | template: '
', 26 | require: '^^mdExpansionPanel', 27 | link: link 28 | }; 29 | return directive; 30 | 31 | 32 | 33 | function link(scope, element, attrs, expansionPanelCtrl) { 34 | var isStuck = false; 35 | var noSticky = attrs.mdNoSticky !== undefined; 36 | var container = angular.element(element[0].querySelector('.md-expansion-panel-header-container')); 37 | 38 | expansionPanelCtrl.registerHeader({ 39 | show: show, 40 | hide: hide, 41 | noSticky: noSticky, 42 | onScroll: onScroll, 43 | onResize: onResize 44 | }); 45 | 46 | 47 | function show() { 48 | 49 | } 50 | function hide() { 51 | unstick(); 52 | } 53 | 54 | 55 | function onScroll(top, bottom, transformTop) { 56 | var offset; 57 | var panelbottom; 58 | var bounds = element[0].getBoundingClientRect(); 59 | 60 | 61 | if (bounds.top < top) { 62 | offset = top - transformTop; 63 | panelbottom = element[0].parentNode.getBoundingClientRect().bottom - top - bounds.height; 64 | if (panelbottom < 0) { 65 | offset += panelbottom; 66 | } 67 | 68 | // set container width because element becomes postion fixed 69 | container.css('width', element[0].offsetWidth + 'px'); 70 | container.css('top', offset + 'px'); 71 | 72 | // set element height so it does not shink when container is position fixed 73 | element.css('height', container[0].offsetHeight + 'px'); 74 | 75 | element.removeClass('md-no-stick'); 76 | element.addClass('md-stick'); 77 | isStuck = true; 78 | } else if (isStuck === true) { 79 | unstick(); 80 | } 81 | } 82 | 83 | function onResize(width) { 84 | if (isStuck === false) { return; } 85 | container.css('width', width + 'px'); 86 | } 87 | 88 | 89 | function unstick() { 90 | isStuck = false; 91 | container.css('width', ''); 92 | element.css('height', ''); 93 | element.css('top', ''); 94 | element.removeClass('md-stick'); 95 | element.addClass('md-no-stick'); 96 | } 97 | } 98 | } 99 | }()); -------------------------------------------------------------------------------- /docs/js/expansionPanelIcon.directive.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelIcon', mdExpansionPanelIconDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelIcon 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelIcon` can be used in both `md-expansion-panel-collapsed` and `md-expansion-panel-header` as the first or last element. 16 | * Adding this will provide a animated arrow for expanded and collapsed states 17 | **/ 18 | function mdExpansionPanelIconDirective() { 19 | var directive = { 20 | restrict: 'E', 21 | template: '', 22 | replace: true 23 | }; 24 | return directive; 25 | } 26 | }()); -------------------------------------------------------------------------------- /docs/nav.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('NavController', NavController); 4 | 5 | 6 | 7 | NavController.$inject = ['$scope', '$rootScope']; 8 | function NavController($scope, $rootScope) { 9 | $rootScope.$on('$routeChangeSuccess', function(event, current) { 10 | $scope.currentNavItem = current.$$route.originalPath || '/'; 11 | }); 12 | } 13 | }()); -------------------------------------------------------------------------------- /docs/pages/autoExpand/autoExpand.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('AutoExpandController', AutoExpandController); 4 | 5 | 6 | 7 | AutoExpandController.$inject = ['$mdExpansionPanelGroup']; 8 | function AutoExpandController($mdExpansionPanelGroup) { 9 | var vm = this; 10 | 11 | var groupInstance; 12 | 13 | vm.title = 'Panel Title'; 14 | vm.summary = 'Panel Summary text'; 15 | vm.content = 'Many were increasingly of the opinion that they’d all made a big mistake in coming down from the trees in the first place. And some said that even the trees had been a bad move, and that no one should ever have left the oceans.'; 16 | 17 | vm.addTemplated = addTemplated; 18 | 19 | $mdExpansionPanelGroup().waitFor('expansionPanelGroup').then(function (instance) { 20 | groupInstance = instance; 21 | 22 | instance.register('templated', { 23 | templateUrl: 'pages/group/panels/templated/templated.html', 24 | controller: 'TemplatedPanelController', 25 | controllerAs: 'vm' 26 | }); 27 | 28 | instance.add({ 29 | templateUrl: 'pages/group/panels/one/one.html', 30 | controller: 'OnePanelController', 31 | controllerAs: 'vm' 32 | }); 33 | }); 34 | 35 | function addTemplated() { 36 | groupInstance.add('templated', { 37 | title: vm.title, 38 | summary: vm.summary, 39 | content: vm.content 40 | }).then(function (panel) { 41 | // panel.expand().then(function () { 42 | // console.log('opened post animation'); 43 | // }); 44 | }); 45 | } 46 | } 47 | }()); -------------------------------------------------------------------------------- /docs/pages/autoExpand/autoExpand.html: -------------------------------------------------------------------------------- 1 |

Group With Auto Expand

2 | 3 | 4 |

5 | Expansion Panel Groups with the auto-expand attribute will expand when added to the group 6 |

7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 |
28 |
required
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
required
37 |
38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 |
46 |
required
47 |
48 |
49 |
50 | 51 | Add Panel 52 |
53 |
54 | -------------------------------------------------------------------------------- /docs/pages/group/group.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('GroupController', GroupController); 4 | 5 | 6 | 7 | GroupController.$inject = ['$mdExpansionPanelGroup']; 8 | function GroupController($mdExpansionPanelGroup) { 9 | var vm = this; 10 | 11 | var groupInstance; 12 | 13 | vm.title = 'Panel Title'; 14 | vm.summary = 'Panel Summary text'; 15 | vm.content = 'Many were increasingly of the opinion that they’d all made a big mistake in coming down from the trees in the first place. And some said that even the trees had been a bad move, and that no one should ever have left the oceans.'; 16 | 17 | vm.addTemplated = addTemplated; 18 | 19 | $mdExpansionPanelGroup().waitFor('expansionPanelGroup').then(function (instance) { 20 | groupInstance = instance; 21 | 22 | instance.register('templated', { 23 | templateUrl: 'pages/group/panels/templated/templated.html', 24 | controller: 'TemplatedPanelController', 25 | controllerAs: 'vm' 26 | }); 27 | 28 | instance.add({ 29 | templateUrl: 'pages/group/panels/one/one.html', 30 | controller: 'OnePanelController', 31 | controllerAs: 'vm' 32 | }).then(function (panelInstance) { 33 | panelInstance.expand(); 34 | }); 35 | 36 | var change = instance.onChange(function (count) { 37 | console.log('panel count', count); 38 | }); 39 | 40 | 41 | setTimeout(function () { 42 | change(); 43 | }, 10000); 44 | }); 45 | 46 | function addTemplated() { 47 | groupInstance.add('templated', { 48 | title: vm.title, 49 | summary: vm.summary, 50 | content: vm.content 51 | }).then(function (panel) { 52 | panel.onRemove(function () { 53 | console.log('panel removed'); 54 | }); 55 | }); 56 | } 57 | } 58 | }()); -------------------------------------------------------------------------------- /docs/pages/group/group.html: -------------------------------------------------------------------------------- 1 |

Group

2 | 3 |

4 | Expansion Panel Groups allow you to controll a set of panels. You can add panels using templates and controllers. You can also register panels to add by a given name; and you can pass in locals. 5 |

6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 |
27 |
required
28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
required
36 |
37 |
38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 |
required
46 |
47 |
48 |
49 | 50 | Add Panel 51 |
52 |
53 | -------------------------------------------------------------------------------- /docs/pages/group/panels/one/one.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Title
5 |
Summary
6 |
7 | 8 | 9 | 10 | 11 |
Expanded Title
12 |
Expanded Summary
13 |
14 | 15 | 16 |

Content

17 |

Put content in here

18 |
19 | 20 | 21 |
22 | Collapse 23 |
24 | 25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /docs/pages/group/panels/one/onePanel.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('OnePanelController', OnePanelController); 4 | 5 | 6 | 7 | function OnePanelController() { 8 | 9 | } 10 | }()); -------------------------------------------------------------------------------- /docs/pages/group/panels/templated/templated.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('TemplatedPanelController', TemplatedPanelController); 4 | 5 | 6 | 7 | function TemplatedPanelController(title, summary, content) { 8 | var vm = this; 9 | 10 | vm.title = title; 11 | vm.summary = summary; 12 | vm.content = content; 13 | } 14 | }()); -------------------------------------------------------------------------------- /docs/pages/group/panels/templated/templated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
{{vm.title}}
5 |
{{vm.summary}}
6 |
7 | 8 | 9 | 10 | 11 |
{{vm.title}}
12 |
{{vm.summary}}
13 |
14 | 15 | 16 |

Content

17 |

18 | {{vm.content}} 19 |

20 |
21 | 22 | 23 |
24 | Remove 25 | Collapse 26 |
27 | 28 |
29 | 30 |
31 | -------------------------------------------------------------------------------- /docs/pages/home/home.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('HomeController', HomeController); 4 | 5 | 6 | function HomeController($scope, $mdExpansionPanel) { 7 | $mdExpansionPanel().waitFor('expansionPanelOne').then(function (instance) { 8 | instance.expand(); 9 | }); 10 | 11 | 12 | $scope.collapseOne = function () { 13 | $mdExpansionPanel('expansionPanelOne').collapse(); 14 | }; 15 | } 16 | }()); -------------------------------------------------------------------------------- /docs/pages/home/home.html: -------------------------------------------------------------------------------- 1 |

Panels

2 | 3 | 4 |

5 | Expansion Panels have an collapsed section and a expanded section. Optionally you can add a header and footer to the expanded sections. By default bothe the header and footer will stick to the tops and bottoms of the md-content container they are in, but you can disable that. 6 |

7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
Title
18 |
Summary
19 | 20 |
21 | 22 | 23 | 24 | 25 |
Expanded Title
26 |
Expanded Summary
27 | 28 |
29 | 30 | 31 |

Content

32 |

Put content in here

33 |
34 | 35 | 36 |
37 | Collapse 38 |
39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 |
Sticky
50 |
header and footer will stick
51 |
52 | 53 | 54 | 55 | 56 | 57 |
Sticky
58 |
header and footer will stick
59 |
60 | 61 | 62 |

Content

63 |

Put content in here

64 | 65 |

Content

66 |

Put content in here

67 | 68 |

Content

69 |

Put content in here

70 | 71 |

Content

72 |

Put content in here

73 | 74 |

Content

75 |

Put content in here

76 | 77 |

Content

78 |

Put content in here

79 | 80 |

Content

81 |

Put content in here

82 | 83 |

Content

84 |

Put content in here

85 | 86 |

Content

87 |

Put content in here

88 |
89 | 90 | 91 |
92 | Collapse 93 |
94 | 95 |
96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
No Sticky
106 |
header and footer sticky disabled
107 |
108 | 109 | 110 | 111 | 112 |
No Sticky
113 |
header and footer sticky disabled
114 |
115 | 116 | 117 |

Content

118 |

Put content in here

119 | 120 |

Content

121 |

Put content in here

122 | 123 |

Content

124 |

Put content in here

125 | 126 |

Content

127 |

Put content in here

128 | 129 |

Content

130 |

Put content in here

131 | 132 |

Content

133 |

Put content in here

134 | 135 |

Content

136 |

Put content in here

137 | 138 |

Content

139 |

Put content in here

140 | 141 |

Content

142 |

Put content in here

143 |
144 | 145 | 146 |
147 | Collapse 148 |
149 | 150 |
151 | 152 |
153 | -------------------------------------------------------------------------------- /docs/pages/multiple/multiple.controller.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular 2 | .module('angularMaterialExpansionPanel') 3 | .controller('MultipleController', MultipleController); 4 | 5 | 6 | 7 | MultipleController.$inject = ['$mdExpansionPanelGroup']; 8 | function MultipleController($mdExpansionPanelGroup) { 9 | var vm = this; 10 | 11 | var groupInstance; 12 | 13 | vm.title = 'Panel Title'; 14 | vm.summary = 'Panel Summary text'; 15 | vm.content = 'Many were increasingly of the opinion that they’d all made a big mistake in coming down from the trees in the first place. And some said that even the trees had been a bad move, and that no one should ever have left the oceans.'; 16 | 17 | vm.addTemplated = addTemplated; 18 | 19 | $mdExpansionPanelGroup().waitFor('expansionPanelGroup').then(function (instance) { 20 | groupInstance = instance; 21 | 22 | instance.register('templated', { 23 | templateUrl: 'pages/group/panels/templated/templated.html', 24 | controller: 'TemplatedPanelController', 25 | controllerAs: 'vm' 26 | }); 27 | 28 | instance.add({ 29 | templateUrl: 'pages/group/panels/one/one.html', 30 | controller: 'OnePanelController', 31 | controllerAs: 'vm' 32 | }).then(function (panelInstance) { 33 | panelInstance.expand(); 34 | }); 35 | }); 36 | 37 | function addTemplated() { 38 | groupInstance.add('templated', { 39 | title: vm.title, 40 | summary: vm.summary, 41 | content: vm.content 42 | }).then(function (panel) { 43 | // panel.expand().then(function () { 44 | // console.log('opened post animation'); 45 | // }); 46 | }); 47 | } 48 | } 49 | }()); -------------------------------------------------------------------------------- /docs/pages/multiple/multiple.html: -------------------------------------------------------------------------------- 1 |

Group with Multiple

2 | 3 | 4 |

5 | Expansion Panel Groups with the mulitple attribute allow for more than 1 panel to be expanded at the same time 6 |

7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 |
28 |
required
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
required
37 |
38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 |
46 |
required
47 |
48 |
49 |
50 | 51 | Add Panel 52 |
53 |
54 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | max-width: 100%; 4 | max-height: 100%; } 5 | 6 | .view-container { 7 | padding-left: 12px; 8 | padding-right: 12px; } 9 | 10 | .nav-bar { 11 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12); 12 | background: #FFF; 13 | z-index: 9; } 14 | -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | exports.paths = { 2 | src: 'src/', 3 | app: 'app/', 4 | dest: 'public/', 5 | build: 'dist/', 6 | scripts: ['src/expansionPanel.js', 'src/*.js', 'src/**/*.js', '!src/*spec.js', '!src/**/*spec.js'], 7 | appScripts: ['app/app.js', 'app/*.js', 'app/**/*.js'], 8 | css: ['src/*.scss', 'src/*.css', '!src/*spec.css'], 9 | appCss: ['app/style.css', 'app/**/*.css'], 10 | injectCss: ['public/*.css', 'public/**/*.css'], 11 | partials: ['app/**/*.html'], 12 | icons: ['src/**/*.svg'], 13 | bower: './bower.json' 14 | }; 15 | -------------------------------------------------------------------------------- /gulp/cssBuild.js: -------------------------------------------------------------------------------- 1 | var paths = require('./config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var gutil = require('gulp-util'); 5 | var autoprefixer = require('gulp-autoprefixer'); 6 | var gulpFilter = require('gulp-filter'); 7 | var concat = require('gulp-concat'); 8 | var cssnano = require('gulp-cssnano'); 9 | var sass = require('gulp-sass'); 10 | var rename = require('gulp-rename'); 11 | 12 | exports.getDev = function (srcs) { 13 | srcs = srcs || paths.css.concat(paths.appCss); 14 | 15 | return function dev() { 16 | return gulp.src(srcs) 17 | .pipe(sass()) 18 | .pipe(autoprefixer()) 19 | .pipe(gulp.dest(paths.dest)) 20 | .on('end', function(){ 21 | gutil.log(gutil.colors.green('✔ CSS dev'), 'Finished'); 22 | }); 23 | }; 24 | }; 25 | 26 | 27 | exports.release = function () { 28 | return gulp.src(paths.css) 29 | .pipe(sass()) 30 | .pipe(concat('md-expansion-panel.css')) 31 | .pipe(autoprefixer()) 32 | .pipe(gulp.dest(paths.build)) 33 | .pipe(cssnano({zindex: false})) 34 | .pipe(rename('md-expansion-panel.min.css')) 35 | .pipe(gulp.dest(paths.build)) 36 | .on('end', function(){ 37 | gutil.log(gutil.colors.green('✔ CSS Build'), 'Finished'); 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /gulp/indexBuild.js: -------------------------------------------------------------------------------- 1 | var paths = require('./config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var inject = require('gulp-inject'); 5 | var mainBowerFiles = require('gulp-main-bower-files'); 6 | 7 | 8 | exports.inject = function () { 9 | var scripts = gulp.src(paths.scripts, {read: false}); 10 | var appScripts = gulp.src(paths.appScripts, {read: false}); 11 | var appCss = gulp.src(paths.appCss, {read: false}); 12 | var bower = gulp.src(paths.bower).pipe(mainBowerFiles({includeDev: true})); 13 | var css = gulp.src(paths.injectCss, {read: false}); 14 | 15 | return gulp.src(paths.app + 'index.html') 16 | .pipe(inject(css, { 17 | name: 'css', 18 | relative: true, 19 | ignorePath: '../public' 20 | })) 21 | .pipe(inject(scripts, { 22 | name: 'scripts', 23 | relative: true, 24 | ignorePath: '../src' 25 | })) 26 | .pipe(inject(appScripts, { 27 | name: 'appscripts', 28 | relative: true, 29 | ignorePath: '../' 30 | })) 31 | .pipe(inject(bower, { 32 | name: 'bower', 33 | relative: true, 34 | ignorePath: '../bower_components/' 35 | })) 36 | .pipe(gulp.dest(paths.dest)); 37 | }; 38 | -------------------------------------------------------------------------------- /gulp/jsBuild.js: -------------------------------------------------------------------------------- 1 | var paths = require('./config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var jshint = require('gulp-jshint'); 5 | var wrap = require("gulp-wrap"); 6 | var concat = require('gulp-concat'); 7 | var uglify = require('gulp-uglify'); 8 | var stripDebug = require('gulp-strip-debug'); 9 | var rename = require("gulp-rename"); 10 | var filter = require('gulp-filter'); 11 | var gutil = require('gulp-util'); 12 | 13 | 14 | 15 | exports.getDevSrc = function (srcs) { 16 | srcs = srcs || paths.scripts; 17 | 18 | return function dev() { 19 | return gulp.src(srcs, {base: paths.src}) 20 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 21 | .pipe(jshint()) 22 | .pipe(jshint.reporter('default')) 23 | .pipe(gulp.dest(paths.dest)) 24 | .on('end', function() { 25 | gutil.log(gutil.colors.green('✔ JS Dev'), 'Finished'); 26 | }); 27 | }; 28 | } 29 | 30 | 31 | exports.getDevApp = function (srcs) { 32 | srcs = srcs || paths.appScripts; 33 | 34 | return function dev() { 35 | return gulp.src(srcs, {base: paths.app}) 36 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 37 | .pipe(jshint()) 38 | .pipe(jshint.reporter('default')) 39 | .pipe(gulp.dest(paths.dest)) 40 | .on('end', function() { 41 | gutil.log(gutil.colors.green('✔ JS Dev'), 'Finished'); 42 | }); 43 | }; 44 | } 45 | 46 | 47 | exports.release = function () { 48 | return gulp.src(paths.scripts) 49 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 50 | .pipe(jshint()) 51 | .pipe(jshint.reporter('default')) 52 | .pipe(concat('md-expansion-panel.js')) 53 | .pipe(stripDebug()) 54 | .pipe(gulp.dest(paths.build)) 55 | .pipe(uglify()) 56 | .pipe(rename('md-expansion-panel.min.js')) 57 | .pipe(gulp.dest(paths.build)) 58 | .on('end', function() { 59 | gutil.log(gutil.colors.green('✔ JS build'), 'Finished'); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var paths = require('./gulp/config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var serve = require('gulp-serve'); 5 | var gulpSequence = require('gulp-sequence'); 6 | var del = require('del'); 7 | var bump = require('gulp-bump'); 8 | var templateCache = require('gulp-angular-templatecache'); 9 | var KarmaServer = require('karma').Server; 10 | 11 | 12 | var jsBuild = require('./gulp/jsBuild'); 13 | var cssBuild = require('./gulp/cssBuild'); 14 | var indexBuild = require('./gulp/indexBuild'); 15 | 16 | 17 | 18 | gulp.task('jsSrcBuild', jsBuild.getDevSrc()); 19 | gulp.task('jsAppBuild', jsBuild.getDevApp()); 20 | gulp.task('jsReleaseBuild', jsBuild.release); 21 | gulp.task('cssBuild', cssBuild.getDev()); 22 | gulp.task('cssReleaseBuild', cssBuild.release); 23 | gulp.task('indexBuild', indexBuild.inject); 24 | 25 | 26 | 27 | // -- main tasks. use these to watch and build and release 28 | 29 | gulp.task('default', gulpSequence('buildLocal', ['serve', 'watch'])); 30 | gulp.task('buildLocal', gulpSequence( 31 | 'clean', 32 | [ 33 | 'jsSrcBuild', 34 | 'jsAppBuild', 35 | 'cssBuild', 36 | 'copyPartials', 37 | 'copyIcons' 38 | ], 39 | 'indexBuild' 40 | )); 41 | 42 | gulp.task('build', gulpSequence('buildIconCache', ['jsReleaseBuild', 'cssReleaseBuild'], 'cleanIconCache')); 43 | gulp.task('docs', gulpSequence( 44 | 'buildLocal', 45 | 'copyPublicToDocs', 46 | 'copyBowerToDocs' 47 | )); 48 | 49 | gulp.task('copyPublicToDocs', function () { 50 | return gulp.src('public/**/*') 51 | .pipe(gulp.dest('docs/')); 52 | }); 53 | 54 | gulp.task('copyBowerToDocs', function () { 55 | return gulp.src('bower_components/**/*') 56 | .pipe(gulp.dest('docs/')); 57 | }); 58 | 59 | 60 | 61 | gulp.task('clean', function () { 62 | return del(paths.dest); 63 | }); 64 | 65 | 66 | gulp.task('copyPartials', function () { 67 | return gulp.src(paths.partials, {base: paths.app}) 68 | .pipe(gulp.dest(paths.dest)); 69 | }); 70 | 71 | gulp.task('copyIcons', function () { 72 | return gulp.src(paths.icons, {base: paths.src}) 73 | .pipe(gulp.dest(paths.dest)); 74 | }); 75 | 76 | gulp.task('buildIconCache', function () { 77 | return gulp.src(paths.icons) 78 | .pipe(templateCache({module: 'material.components.expansionPanels'})) 79 | .pipe(gulp.dest(paths.src)); 80 | }); 81 | 82 | gulp.task('cleanIconCache', function () { 83 | return del('src/templates.js'); 84 | }); 85 | 86 | gulp.task('serve', serve({ 87 | root: ['public', 'bower_components'], 88 | port: 8080 89 | })); 90 | 91 | 92 | 93 | gulp.task('test-karma', function (done) { 94 | new KarmaServer({ 95 | configFile: __dirname + '/karma.conf.js', 96 | singleRun: true 97 | }, function (errorCode) { 98 | if (errorCode !== 0) { 99 | console.log('Karma exited with error code ' + errorCode); 100 | done(); 101 | return process.exit(errorCode); 102 | } 103 | done(); 104 | }).start(); 105 | }); 106 | 107 | gulp.task('test', gulpSequence('build', 'test-karma')); 108 | 109 | 110 | 111 | 112 | gulp.task('watch', function () { 113 | gulp.watch(paths.scripts, function (event) { 114 | jsBuild.getDevSrc(event.path)() 115 | .on('end', function () { 116 | if (event.type !== 'changed') { indexBuild.inject(); } 117 | }); 118 | }); 119 | 120 | gulp.watch(paths.appScripts, function (event) { 121 | jsBuild.getDevApp(event.path)() 122 | .on('end', function () { 123 | if (event.type !== 'changed') { indexBuild.inject(); } 124 | }); 125 | }); 126 | 127 | 128 | gulp.watch(paths.css.concat(paths.appCss), function (event) { 129 | cssBuild.getDev(event.path)() 130 | .on('end', function () { 131 | if (event.type !== 'changed') { indexBuild.inject(); } 132 | }); 133 | }); 134 | 135 | 136 | gulp.watch(paths.partials, function (event) { 137 | return gulp.src(event.path, {base: paths.app}) 138 | .pipe(gulp.dest(paths.dest)); 139 | }); 140 | }); 141 | 142 | 143 | 144 | 145 | 146 | 147 | gulp.task('major', function(){ 148 | gulp.src(['./bower.json', './package.json']) 149 | .pipe(bump({type:'major'})) 150 | .pipe(gulp.dest('./')); 151 | }); 152 | 153 | gulp.task('minor', function(){ 154 | gulp.src(['./bower.json', './package.json']) 155 | .pipe(bump({type:'minor'})) 156 | .pipe(gulp.dest('./')); 157 | }); 158 | 159 | gulp.task('patch', function(){ 160 | gulp.src(['./bower.json', './package.json']) 161 | .pipe(bump({type:'patch'})) 162 | .pipe(gulp.dest('./')); 163 | }); 164 | 165 | gulp.task('prerelease', function(){ 166 | gulp.src(['./bower.json', './package.json']) 167 | .pipe(bump({type:'prerelease'})) 168 | .pipe(gulp.dest('./')); 169 | }); 170 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/md-expansion-panel'); 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed May 11 2016 11:02:59 GMT-0500 (CDT) 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'dist/md-expansion-panel.css', 18 | 'bower_components/angular-material/angular-material.css', 19 | 20 | 'bower_components/angular/angular.js', 21 | 'bower_components/angular-animate/angular-animate.js', 22 | 'bower_components/angular-material/angular-material.js', 23 | 'bower_components/angular-mocks/angular-mocks.js', 24 | 'src/expansionPanels.js', 25 | 'src/**/*.js' 26 | ], 27 | 28 | 29 | // test results reporter to use 30 | // possible values: 'dots', 'progress' 31 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 32 | reporters: [ 'progress'], 33 | 34 | 35 | // web server port 36 | port: 9876, 37 | 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | 43 | // level of logging 44 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_INFO, 46 | 47 | 48 | // enable / disable watching file and executing tests whenever any file changes 49 | autoWatch: true, 50 | 51 | 52 | // start these browsers 53 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 54 | // browsers: ['Chrome'], 55 | browsers: ['PhantomJS'], 56 | 57 | 58 | // Continuous Integration mode 59 | // if true, Karma captures browsers, runs the tests and exits 60 | singleRun: false, 61 | 62 | // Concurrency level 63 | // how many browser should be started simultaneous 64 | concurrency: Infinity 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-expansion-panel", 3 | "version": "0.7.2", 4 | "author": "Ben Rubin", 5 | "description": "Material Design Expansion Panels for angular material", 6 | "keywords": "material, material-design, design, angular, component, expansion, panel, panels", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/B-3PO/angular-material-expansion-panel.git" 11 | }, 12 | "main": "index.js", 13 | "devDependencies": { 14 | "bump": "^0.2.5", 15 | "del": "^2.2.0", 16 | "gulp": "^3.9.1", 17 | "gulp-angular-templatecache": "^1.9.1", 18 | "gulp-autoprefixer": "^3.1.0", 19 | "gulp-bump": "^1.0.0", 20 | "gulp-concat": "^2.6.0", 21 | "gulp-cssnano": "^2.1.0", 22 | "gulp-filter": "^4.0.0", 23 | "gulp-flatten": "^0.3.0", 24 | "gulp-if": "^2.0.1", 25 | "gulp-inject": "^4.0.0", 26 | "gulp-jasmine": "^2.4.0", 27 | "gulp-jshint": "^2.0.0", 28 | "gulp-main-bower-files": "^1.5.1", 29 | "gulp-ng-constant": "^1.1.0", 30 | "gulp-preprocess": "^2.0.0", 31 | "gulp-rename": "^1.2.2", 32 | "gulp-sass": "^2.3.2", 33 | "gulp-sequence": "^0.4.5", 34 | "gulp-serve": "^1.2.0", 35 | "gulp-strip-debug": "^1.1.0", 36 | "gulp-uglify": "^1.5.3", 37 | "gulp-util": "^3.0.7", 38 | "gulp-wrap": "^0.11.0", 39 | "jshint": "^2.8.0", 40 | "karma": "^1.1.0", 41 | "karma-jasmine": "^1.0.2", 42 | "karma-phantomjs-launcher": "^1.0.1", 43 | "karma-spec-reporter": "0.0.26" 44 | }, 45 | "scripts": { 46 | "local": "gulp", 47 | "build": "gulp build" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/expansionPanels-theme.scss: -------------------------------------------------------------------------------- 1 | md-expansion-panel { 2 | background: #FFF; 3 | 4 | md-expansion-panel-collapsed, 5 | .md-expansion-panel-footer-container, 6 | .md-expansion-panel-header-container { 7 | background: #FFF; 8 | 9 | .md-title, 10 | .md-summary { 11 | color: #333; 12 | } 13 | } 14 | 15 | 16 | md-expansion-panel-footer .md-expansion-panel-footer-container, 17 | md-expansion-panel-header .md-expansion-panel-header-container { 18 | border-color: #DDD; 19 | } 20 | 21 | .md-expansion-panel-icon svg { 22 | fill: #999; 23 | } 24 | 25 | 26 | &[disabled] { 27 | md-expansion-panel-collapsed { 28 | .md-title, 29 | .md-summary { 30 | color: #DDD; 31 | } 32 | } 33 | 34 | .md-expansion-panel-icon svg { 35 | fill: #DDD; 36 | } 37 | } 38 | 39 | &:not(.md-open) { 40 | &:not([disabled]) { 41 | &:focus, 42 | &:focus md-expansion-panel-collapsed { 43 | background: #EEE; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/expansionPanels.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc module 3 | * @name material.components.expansionPanels 4 | * 5 | * @description 6 | * Expansion panel component 7 | */ 8 | angular 9 | .module('material.components.expansionPanels', [ 10 | 'material.core' 11 | ]); 12 | -------------------------------------------------------------------------------- /src/expansionPanels.scss: -------------------------------------------------------------------------------- 1 | .layout-padding > md-expansion-panel-group { 2 | padding: 0; 3 | } 4 | 5 | /* --- Expansion Panel --- */ 6 | 7 | md-expansion-panel { 8 | display: block; 9 | position: relative; 10 | outline: none; 11 | margin-top: 0; 12 | margin-bottom: 0; 13 | padding: 0; 14 | box-shadow: 0 -1px 0 #e5e5e5, 15 | 0 0 2px rgba(0,0,0,.12), 16 | 0 2px 4px rgba(0,0,0,.24); 17 | 18 | 19 | &.md-open { 20 | margin-top: 16px; 21 | margin-bottom: 16px; 22 | 23 | &:first-of-type { 24 | margin-top: 0; 25 | } 26 | } 27 | 28 | &.md-close { 29 | overflow: hidden; 30 | } 31 | 32 | &:not(.md-no-animation) { 33 | &.md-open { 34 | transition: margin-top 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), 35 | margin-bottom 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); 36 | } 37 | 38 | &.md-close { 39 | transition: margin-top 0.08s cubic-bezier(0.25, 0.8, 0.25, 1), 40 | margin-bottom 0.08s cubic-bezier(0.25, 0.8, 0.25, 1); 41 | } 42 | } 43 | } 44 | 45 | 46 | 47 | 48 | 49 | md-expansion-panel-collapsed .md-title, 50 | md-expansion-panel-header .md-title { 51 | flex: 1; 52 | font-size: 16px; 53 | font-weight: 600; 54 | min-width: 80px; 55 | max-width: 184px; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | text-align: left; 59 | white-space: nowrap; 60 | } 61 | 62 | md-expansion-panel-collapsed .md-summary, 63 | md-expansion-panel-header .md-summary { 64 | flex: 1; 65 | font-size: 13px; 66 | overflow: hidden; 67 | text-overflow: ellipsis; 68 | text-align: left; 69 | white-space: nowrap; 70 | } 71 | 72 | 73 | 74 | /* --- Expansion Panel Collapsed ---- */ 75 | 76 | md-expansion-panel { 77 | md-expansion-panel-collapsed { 78 | display: flex; 79 | min-height: 48px; 80 | line-height: 48px; 81 | padding: 0 24px; 82 | opacity: 1; 83 | z-index: 2; 84 | box-sizing: border-box; 85 | 86 | 87 | &.md-absolute { 88 | position: absolute; 89 | } 90 | 91 | &.md-hide { 92 | opacity: 0; 93 | } 94 | } 95 | 96 | &:not(.md-no-animation) { 97 | md-expansion-panel-collapsed { 98 | &.md-show { 99 | transition: opacity 0.03s linear; 100 | } 101 | 102 | &.md-hide { 103 | transition: opacity 0.1s cubic-bezier(0.25, 0.8, 0.25, 1); 104 | } 105 | } 106 | } 107 | } 108 | 109 | 110 | 111 | 112 | /* --- Expansion Panel Expanded --- */ 113 | 114 | md-expansion-panel { 115 | md-expansion-panel-expanded { 116 | display: none; 117 | min-height: 48px; 118 | 119 | 120 | &.md-show, 121 | &.md-hide { 122 | display: block; 123 | } 124 | 125 | &.md-scroll-y { 126 | overflow-y: auto; 127 | } 128 | 129 | &.md-overflow { 130 | overflow: hidden 131 | } 132 | 133 | 134 | md-expansion-panel-content { 135 | display: block; 136 | padding: 16px 24px; 137 | } 138 | } 139 | 140 | &:not(.md-no-animation) { 141 | md-expansion-panel-expanded { 142 | &.md-show { 143 | transition: max-height 0.12s cubic-bezier(0.25, 0.8, 0.25, 1), 144 | opacity 0.12s cubic-bezier(0.25, 0.8, 0.25, 1); 145 | } 146 | 147 | &.md-hide { 148 | transition: max-height 0.06s cubic-bezier(0.25, 0.8, 0.25, 1), 149 | opacity 0.06s cubic-bezier(0.25, 0.8, 0.25, 1); 150 | } 151 | } 152 | } 153 | } 154 | 155 | 156 | 157 | /* --- Expansion Panel Header --- */ 158 | 159 | md-expansion-panel-header { 160 | display: block; 161 | position: relative; 162 | outline: none; 163 | 164 | 165 | .md-expansion-panel-header-container { 166 | display: flex; 167 | min-height: 48px; 168 | line-height: 48px; 169 | padding: 0 24px; 170 | box-sizing: border-box; 171 | border-bottom: 1px solid; 172 | align-items: center; 173 | } 174 | 175 | &.md-stick { 176 | .md-expansion-panel-header-container { 177 | position: fixed; 178 | z-index: 2; 179 | animation: panelBodyHeaderStickyHoverIn 0.3s ease-out both; 180 | } 181 | } 182 | 183 | &.md-no-stick { 184 | .md-expansion-panel-header-container { 185 | animation: panelBodyHeaderStickyHoverOut 0.3s ease-out both; 186 | } 187 | } 188 | } 189 | 190 | 191 | 192 | 193 | /* --- Expansion Panel Footer --- */ 194 | 195 | 196 | md-expansion-panel-footer { 197 | display: block; 198 | position: relative; 199 | 200 | &.md-show, 201 | &.md-hide { 202 | display: block; 203 | } 204 | 205 | 206 | .md-expansion-panel-footer-container { 207 | display: flex; 208 | min-height: 48px; 209 | line-height: 48px; 210 | padding: 0 24px; 211 | border-top: 1px solid; 212 | box-sizing: border-box; 213 | } 214 | 215 | 216 | &.md-stick { 217 | .md-expansion-panel-footer-container { 218 | position: fixed; 219 | z-index: 2; 220 | } 221 | } 222 | } 223 | 224 | 225 | 226 | 227 | /* --- expand icon --- */ 228 | 229 | md-expansion-panel { 230 | &:not(.md-no-animation) { 231 | .md-expansion-panel-icon { 232 | transition: transform 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); 233 | } 234 | } 235 | 236 | .md-expansion-panel-icon { 237 | transform: rotate(90deg); 238 | &:first-child { 239 | margin-right: 18px; 240 | } 241 | } 242 | 243 | 244 | &.md-open { 245 | > md-expansion-panel-expanded > md-expansion-panel-header .md-expansion-panel-header-container .md-expansion-panel-icon { 246 | transform: rotate(-90deg); 247 | } 248 | 249 | > md-expansion-panel.md-open > md-expansion-panel-collapsed .md-expansion-panel-icon { 250 | transform: rotate(-90deg); 251 | } 252 | } 253 | } 254 | 255 | 256 | 257 | @keyframes panelBodyHeaderStickyHoverIn { 258 | 0% { 259 | box-shadow: 0 0 0 0 transparent; 260 | } 261 | 100% { 262 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); 263 | } 264 | } 265 | 266 | @keyframes panelBodyHeaderStickyHoverOut { 267 | 0% { 268 | box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); 269 | } 270 | 100% { 271 | box-shadow: 0 0 0 0 transparent; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/icons/ic_keyboard_arrow_right_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/js/expansionPanel.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanel', expansionPanelDirective); 4 | 5 | 6 | var ANIMATION_TIME = 180; //ms 7 | 8 | 9 | /** 10 | * @ngdoc directive 11 | * @name mdExpansionPanel 12 | * @module material.components.expansionPanels 13 | * 14 | * @restrict E 15 | * 16 | * @description 17 | * `mdExpansionPanel` is the main container for panels 18 | * 19 | * @param {string=} md-component-id - add an id if you want to acces the panel via the `$mdExpansionPanel` service 20 | **/ 21 | function expansionPanelDirective() { 22 | var directive = { 23 | restrict: 'E', 24 | require: ['mdExpansionPanel', '?^^mdExpansionPanelGroup'], 25 | scope: true, 26 | compile: compile, 27 | controller: ['$scope', '$element', '$attrs', '$window', '$$rAF', '$mdConstant', '$mdUtil', '$mdComponentRegistry', '$timeout', '$q', '$animate', '$parse', controller] 28 | }; 29 | return directive; 30 | 31 | 32 | 33 | 34 | function compile(tElement, tAttrs) { 35 | var INVALID_PREFIX = 'Invalid HTML for md-expansion-panel: '; 36 | 37 | tElement.attr('tabindex', tAttrs.tabindex || '0'); 38 | 39 | if (tElement[0].querySelector('md-expansion-panel-collapsed') === null) { 40 | throw Error(INVALID_PREFIX + 'Expected a child element of `md-epxansion-panel-collapsed`'); 41 | } 42 | if (tElement[0].querySelector('md-expansion-panel-expanded') === null) { 43 | throw Error(INVALID_PREFIX + 'Expected a child element of `md-epxansion-panel-expanded`'); 44 | } 45 | 46 | return function postLink(scope, element, attrs, ctrls) { 47 | var epxansionPanelCtrl = ctrls[0]; 48 | var epxansionPanelGroupCtrl = ctrls[1]; 49 | 50 | epxansionPanelCtrl.epxansionPanelGroupCtrl = epxansionPanelGroupCtrl || undefined; 51 | epxansionPanelCtrl.init(); 52 | }; 53 | } 54 | 55 | 56 | 57 | 58 | function controller($scope, $element, $attrs, $window, $$rAF, $mdConstant, $mdUtil, $mdComponentRegistry, $timeout, $q, $animate, $parse) { 59 | /* jshint validthis: true */ 60 | var vm = this; 61 | 62 | var collapsedCtrl; 63 | var expandedCtrl; 64 | var headerCtrl; 65 | var footerCtrl; 66 | var deregister; 67 | var scrollContainer; 68 | var stickyContainer; 69 | var topKiller; 70 | var resizeKiller; 71 | var onRemoveCallback; 72 | var transformParent; 73 | var backdrop; 74 | var inited = false; 75 | var registerOnInit = false; 76 | var _isOpen = false; 77 | var isDisabled = false; 78 | var debouncedUpdateScroll = $$rAF.throttle(updateScroll); 79 | var debouncedUpdateResize = $$rAF.throttle(updateResize); 80 | 81 | vm.registerCollapsed = function (ctrl) { collapsedCtrl = ctrl; }; 82 | vm.registerExpanded = function (ctrl) { expandedCtrl = ctrl; }; 83 | vm.registerHeader = function (ctrl) { headerCtrl = ctrl; }; 84 | vm.registerFooter = function (ctrl) { footerCtrl = ctrl; }; 85 | 86 | 87 | 88 | if ($attrs.mdComponentId === undefined) { 89 | $attrs.$set('mdComponentId', '_expansion_panel_id_' + $mdUtil.nextUid()); 90 | registerPanel(); 91 | } else { 92 | $attrs.$observe('mdComponentId', function() { 93 | registerPanel(); 94 | }); 95 | } 96 | 97 | vm.$element = $element; 98 | vm.expand = expand; 99 | vm.collapse = collapse; 100 | vm.remove = remove; 101 | vm.destroy = destroy; 102 | vm.onRemove = onRemove; 103 | vm.init = init; 104 | 105 | if ($attrs.ngDisabled !== undefined) { 106 | $scope.$watch($attrs.ngDisabled, function(value) { 107 | isDisabled = value; 108 | $element.attr('tabindex', isDisabled ? -1 : 0); 109 | }); 110 | } else if ($attrs.disabled !== undefined) { 111 | isDisabled = ($attrs.disabled !== undefined && $attrs.disabled !== 'false' && $attrs.disabled !== false); 112 | $element.attr('tabindex', isDisabled ? -1 : 0); 113 | } 114 | 115 | $element 116 | .on('focus', function (ev) { 117 | $element.on('keydown', handleKeypress); 118 | }) 119 | .on('blur', function (ev) { 120 | $element.off('keydown', handleKeypress); 121 | }); 122 | 123 | function handleKeypress(ev) { 124 | var keyCodes = $mdConstant.KEY_CODE; 125 | switch (ev.keyCode) { 126 | case keyCodes.ENTER: 127 | expand(); 128 | break; 129 | case keyCodes.ESCAPE: 130 | collapse(); 131 | break; 132 | } 133 | } 134 | 135 | 136 | $scope.$panel = { 137 | collapse: collapse, 138 | expand: expand, 139 | remove: remove, 140 | isOpen: isOpen 141 | }; 142 | 143 | $scope.$on('$destroy', function () { 144 | removeClickCatcher(); 145 | 146 | // remove component from registry 147 | if (typeof deregister === 'function') { 148 | deregister(); 149 | deregister = undefined; 150 | } 151 | killEvents(); 152 | }); 153 | 154 | 155 | 156 | 157 | 158 | function init() { 159 | inited = true; 160 | if (registerOnInit === true) { 161 | registerPanel(); 162 | } 163 | } 164 | 165 | 166 | function registerPanel() { 167 | if (inited === false) { 168 | registerOnInit = true; 169 | return; 170 | } 171 | 172 | // deregister if component was already registered 173 | if (typeof deregister === 'function') { 174 | deregister(); 175 | deregister = undefined; 176 | } 177 | // remove component from group ctrl if component was already added 178 | if (vm.componentId && vm.epxansionPanelGroupCtrl) { 179 | vm.epxansionPanelGroupCtrl.removePanel(vm.componentId); 180 | } 181 | 182 | // if componentId was removed then set one 183 | if ($attrs.mdComponentId === undefined) { 184 | $attrs.$set('mdComponentId', '_expansion_panel_id_' + $mdUtil.nextUid()); 185 | } 186 | 187 | vm.componentId = $attrs.mdComponentId; 188 | deregister = $mdComponentRegistry.register({ 189 | expand: expand, 190 | collapse: collapse, 191 | remove: remove, 192 | onRemove: onRemove, 193 | isOpen: isOpen, 194 | addClickCatcher: addClickCatcher, 195 | removeClickCatcher: removeClickCatcher, 196 | componentId: $attrs.mdComponentId 197 | }, $attrs.mdComponentId); 198 | 199 | if (vm.epxansionPanelGroupCtrl) { 200 | vm.epxansionPanelGroupCtrl.addPanel(vm.componentId, { 201 | expand: expand, 202 | collapse: collapse, 203 | remove: remove, 204 | onRemove: onRemove, 205 | destroy: destroy, 206 | isOpen: isOpen 207 | }); 208 | } 209 | } 210 | 211 | 212 | function isOpen() { 213 | return _isOpen; 214 | } 215 | 216 | function expand(options) { 217 | if (_isOpen === true || isDisabled === true) { return; } 218 | _isOpen = true; 219 | options = options || {}; 220 | 221 | var deferred = $q.defer(); 222 | 223 | if (vm.epxansionPanelGroupCtrl) { 224 | vm.epxansionPanelGroupCtrl.expandPanel(vm.componentId); 225 | } 226 | 227 | $element.removeClass('md-close'); 228 | $element.addClass('md-open'); 229 | if (options.animation === false) { 230 | $element.addClass('md-no-animation'); 231 | } else { 232 | $element.removeClass('md-no-animation'); 233 | } 234 | 235 | initEvents(); 236 | collapsedCtrl.hide(options); 237 | expandedCtrl.show(options); 238 | 239 | if (headerCtrl) { headerCtrl.show(options); } 240 | if (footerCtrl) { footerCtrl.show(options); } 241 | 242 | $timeout(function () { 243 | deferred.resolve(); 244 | }, options.animation === false ? 0 : ANIMATION_TIME); 245 | return deferred.promise; 246 | } 247 | 248 | 249 | function collapse(options) { 250 | if (_isOpen === false) { return; } 251 | _isOpen = false; 252 | options = options || {}; 253 | 254 | var deferred = $q.defer(); 255 | 256 | $element.addClass('md-close'); 257 | $element.removeClass('md-open'); 258 | if (options.animation === false) { 259 | $element.addClass('md-no-animation'); 260 | } else { 261 | $element.removeClass('md-no-animation'); 262 | } 263 | 264 | killEvents(); 265 | collapsedCtrl.show(options); 266 | expandedCtrl.hide(options); 267 | 268 | if (headerCtrl) { headerCtrl.hide(options); } 269 | if (footerCtrl) { footerCtrl.hide(options); } 270 | 271 | $timeout(function () { 272 | deferred.resolve(); 273 | }, options.animation === false ? 0 : ANIMATION_TIME); 274 | return deferred.promise; 275 | } 276 | 277 | 278 | function remove(options) { 279 | options = options || {}; 280 | var deferred = $q.defer(); 281 | 282 | if (vm.epxansionPanelGroupCtrl) { 283 | vm.epxansionPanelGroupCtrl.removePanel(vm.componentId); 284 | } 285 | 286 | if (typeof deregister === 'function') { 287 | deregister(); 288 | deregister = undefined; 289 | } 290 | 291 | if (options.animation === false || _isOpen === false) { 292 | $scope.$destroy(); 293 | $element.remove(); 294 | deferred.resolve(); 295 | callbackRemove(); 296 | } else { 297 | collapse(); 298 | $timeout(function () { 299 | $scope.$destroy(); 300 | $element.remove(); 301 | deferred.resolve(); 302 | callbackRemove(); 303 | }, ANIMATION_TIME); 304 | } 305 | 306 | return deferred.promise; 307 | } 308 | 309 | function onRemove(callback) { 310 | onRemoveCallback = callback; 311 | } 312 | 313 | function callbackRemove() { 314 | if (typeof onRemoveCallback === 'function') { 315 | onRemoveCallback(); 316 | onRemoveCallback = undefined; 317 | } 318 | } 319 | 320 | function destroy() { 321 | $scope.$destroy(); 322 | } 323 | 324 | 325 | 326 | function initEvents() { 327 | if ((!footerCtrl || footerCtrl.noSticky === true) && (!headerCtrl || headerCtrl.noSticky === true)) { 328 | return; 329 | } 330 | 331 | // watch for panel position changes 332 | topKiller = $scope.$watch(function () { return $element[0].offsetTop; }, debouncedUpdateScroll, true); 333 | 334 | // watch for panel position changes 335 | resizeKiller = $scope.$watch(function () { return $element[0].offsetWidth; }, debouncedUpdateResize, true); 336 | 337 | // listen to md-content scroll events id we are nested in one 338 | scrollContainer = $mdUtil.getNearestContentElement($element); 339 | if (scrollContainer.nodeName === 'MD-CONTENT') { 340 | transformParent = getTransformParent(scrollContainer); 341 | angular.element(scrollContainer).on('scroll', debouncedUpdateScroll); 342 | } else { 343 | transformParent = undefined; 344 | } 345 | 346 | // listen to expanded content scroll if height is set 347 | if (expandedCtrl.setHeight === true) { 348 | expandedCtrl.$element.on('scroll', debouncedUpdateScroll); 349 | } 350 | 351 | // listen to window scroll events 352 | angular.element($window) 353 | .on('scroll', debouncedUpdateScroll) 354 | .on('resize', debouncedUpdateScroll) 355 | .on('resize', debouncedUpdateResize); 356 | } 357 | 358 | 359 | function killEvents() { 360 | if (typeof topKiller === 'function') { 361 | topKiller(); 362 | topKiller = undefined; 363 | } 364 | 365 | if (typeof resizeKiller === 'function') { 366 | resizeKiller(); 367 | resizeKiller = undefined; 368 | } 369 | 370 | if (scrollContainer && scrollContainer.nodeName === 'MD-CONTENT') { 371 | angular.element(scrollContainer).off('scroll', debouncedUpdateScroll); 372 | } 373 | 374 | if (expandedCtrl.setHeight === true) { 375 | expandedCtrl.$element.off('scroll', debouncedUpdateScroll); 376 | } 377 | 378 | angular.element($window) 379 | .off('scroll', debouncedUpdateScroll) 380 | .off('resize', debouncedUpdateScroll) 381 | .off('resize', debouncedUpdateResize); 382 | } 383 | 384 | 385 | 386 | function getTransformParent(el) { 387 | var parent = el.parentNode; 388 | 389 | while (parent && parent !== document) { 390 | if (hasComputedStyle(parent, 'transform')) { 391 | return parent; 392 | } 393 | parent = parent.parentNode; 394 | } 395 | 396 | return undefined; 397 | } 398 | 399 | function hasComputedStyle(target, key) { 400 | var hasValue = false; 401 | 402 | if (target) { 403 | var computedStyles = $window.getComputedStyle(target); 404 | hasValue = computedStyles[key] !== undefined && computedStyles[key] !== 'none'; 405 | } 406 | 407 | return hasValue; 408 | } 409 | 410 | 411 | function updateScroll(e) { 412 | var top; 413 | var bottom; 414 | var bounds; 415 | if (expandedCtrl.setHeight === true) { 416 | bounds = expandedCtrl.$element[0].getBoundingClientRect(); 417 | } else { 418 | bounds = scrollContainer.getBoundingClientRect(); 419 | } 420 | var transformTop = transformParent ? transformParent.getBoundingClientRect().top : 0; 421 | 422 | // we never want the header going post the top of the page. to prevent this don't allow top to go below 0 423 | top = Math.max(bounds.top, 0); 424 | bottom = top + bounds.height; 425 | 426 | if (footerCtrl && footerCtrl.noSticky === false) { footerCtrl.onScroll(top, bottom, transformTop); } 427 | if (headerCtrl && headerCtrl.noSticky === false) { headerCtrl.onScroll(top, bottom, transformTop); } 428 | } 429 | 430 | 431 | function updateResize() { 432 | var value = $element[0].offsetWidth; 433 | if (footerCtrl && footerCtrl.noSticky === false) { footerCtrl.onResize(value); } 434 | if (headerCtrl && headerCtrl.noSticky === false) { headerCtrl.onResize(value); } 435 | } 436 | 437 | 438 | 439 | 440 | function addClickCatcher(clickCallback) { 441 | backdrop = $mdUtil.createBackdrop($scope); 442 | backdrop[0].tabIndex = -1; 443 | 444 | if (typeof clickCallback === 'function') { 445 | backdrop.on('click', clickCallback); 446 | } 447 | 448 | $animate.enter(backdrop, $element.parent(), null, {duration: 0}); 449 | $element.css('z-index', 60); 450 | } 451 | 452 | function removeClickCatcher() { 453 | if (backdrop) { 454 | backdrop.remove(); 455 | backdrop.off('click'); 456 | backdrop = undefined; 457 | $element.css('z-index', ''); 458 | } 459 | } 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/js/expansionPanel.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .factory('$mdExpansionPanel', expansionPanelService); 4 | 5 | 6 | /** 7 | * @ngdoc service 8 | * @name $mdExpansionPanel 9 | * @module material.components.expansionPanels 10 | * 11 | * @description 12 | * Expand and collapse Expansion Panel using its `md-component-id` 13 | * 14 | * @example 15 | * $mdExpansionPanel('comonentId').then(function (instance) { 16 | * instance.exapand(); 17 | * instance.collapse({animation: false}); 18 | * instance.remove({animation: false}); 19 | * instance.onRemove(function () {}); 20 | * }); 21 | */ 22 | expansionPanelService.$inject = ['$mdComponentRegistry', '$mdUtil', '$log']; 23 | function expansionPanelService($mdComponentRegistry, $mdUtil, $log) { 24 | var errorMsg = "ExpansionPanel '{0}' is not available! Did you use md-component-id='{0}'?"; 25 | var service = { 26 | find: findInstance, 27 | waitFor: waitForInstance 28 | }; 29 | 30 | return function (handle) { 31 | if (handle === undefined) { return service; } 32 | return findInstance(handle); 33 | }; 34 | 35 | 36 | 37 | function findInstance(handle) { 38 | var instance = $mdComponentRegistry.get(handle); 39 | 40 | if (!instance) { 41 | // Report missing instance 42 | $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) ); 43 | return undefined; 44 | } 45 | 46 | return instance; 47 | } 48 | 49 | function waitForInstance(handle) { 50 | return $mdComponentRegistry.when(handle).catch($log.error); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/js/expansionPanelCollapsed.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelCollapsed', expansionPanelCollapsedDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelCollapsed 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelCollapsed` is used to contain content when the panel is collapsed 16 | **/ 17 | expansionPanelCollapsedDirective.$inject = ['$animateCss', '$timeout']; 18 | function expansionPanelCollapsedDirective($animateCss, $timeout) { 19 | var directive = { 20 | restrict: 'E', 21 | require: '^^mdExpansionPanel', 22 | link: link 23 | }; 24 | return directive; 25 | 26 | 27 | function link(scope, element, attrs, expansionPanelCtrl) { 28 | expansionPanelCtrl.registerCollapsed({ 29 | show: show, 30 | hide: hide 31 | }); 32 | 33 | 34 | element.on('click', function () { 35 | expansionPanelCtrl.expand(); 36 | }); 37 | 38 | 39 | function hide(options) { 40 | // set width to maintian demensions when element is set to postion: absolute 41 | element.css('width', element[0].offsetWidth + 'px'); 42 | // set min height so the expansion panel does not shrink when collapsed element is set to position: absolute 43 | expansionPanelCtrl.$element.css('min-height', element[0].offsetHeight + 'px'); 44 | 45 | var animationParams = { 46 | addClass: 'md-absolute md-hide', 47 | from: {opacity: 1}, 48 | to: {opacity: 0} 49 | }; 50 | if (options.animation === false) { animationParams.duration = 0; } 51 | $animateCss(element, animationParams) 52 | .start() 53 | .then(function () { 54 | element.removeClass('md-hide'); 55 | element.css('display', 'none'); 56 | }); 57 | } 58 | 59 | 60 | function show(options) { 61 | element.css('display', ''); 62 | // set width to maintian demensions when element is set to postion: absolute 63 | element.css('width', element[0].parentNode.offsetWidth + 'px'); 64 | 65 | var animationParams = { 66 | addClass: 'md-show', 67 | from: {opacity: 0}, 68 | to: {opacity: 1} 69 | }; 70 | if (options.animation === false) { animationParams.duration = 0; } 71 | $animateCss(element, animationParams) 72 | .start() 73 | .then(function () { 74 | // safari will animate the min-height if transition is not set to 0 75 | expansionPanelCtrl.$element.css('transition', 'none'); 76 | element.removeClass('md-absolute md-show'); 77 | 78 | // remove width when element is no longer position: absolute 79 | element.css('width', ''); 80 | 81 | 82 | // remove min height when element is no longer position: absolute 83 | expansionPanelCtrl.$element.css('min-height', ''); 84 | // remove transition block on next digest 85 | $timeout(function () { 86 | expansionPanelCtrl.$element.css('transition', ''); 87 | }, 0); 88 | }); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/js/expansionPanelExpanded.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelExpanded', expansionPanelExpandedDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelExpanded 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelExpanded` is used to contain content when the panel is expanded 16 | * 17 | * @param {number=} height - add this aatribute set the max height of the expanded content. The container will be set to scroll 18 | **/ 19 | expansionPanelExpandedDirective.$inject = ['$animateCss', '$timeout']; 20 | function expansionPanelExpandedDirective($animateCss, $timeout) { 21 | var directive = { 22 | restrict: 'E', 23 | require: '^^mdExpansionPanel', 24 | link: link 25 | }; 26 | return directive; 27 | 28 | 29 | function link(scope, element, attrs, expansionPanelCtrl) { 30 | var setHeight = attrs.height || undefined; 31 | if (setHeight !== undefined) { setHeight = setHeight.replace('px', '') + 'px'; } 32 | 33 | expansionPanelCtrl.registerExpanded({ 34 | show: show, 35 | hide: hide, 36 | setHeight: setHeight !== undefined, 37 | $element: element 38 | }); 39 | 40 | 41 | 42 | 43 | function hide(options) { 44 | var height = setHeight ? setHeight : element[0].scrollHeight + 'px'; 45 | element.addClass('md-hide md-overflow'); 46 | element.removeClass('md-show md-scroll-y'); 47 | 48 | var animationParams = { 49 | from: {'max-height': height, opacity: 1}, 50 | to: {'max-height': '48px', opacity: 0} 51 | }; 52 | if (options.animation === false) { animationParams.duration = 0; } 53 | $animateCss(element, animationParams) 54 | .start() 55 | .then(function () { 56 | element.css('display', 'none'); 57 | element.removeClass('md-hide'); 58 | }); 59 | } 60 | 61 | 62 | function show(options) { 63 | element.css('display', ''); 64 | element.addClass('md-show md-overflow'); 65 | // use passed in height or the contents height 66 | var height = setHeight ? setHeight : element[0].scrollHeight + 'px'; 67 | 68 | var animationParams = { 69 | from: {'max-height': '48px', opacity: 0}, 70 | to: {'max-height': height, opacity: 1} 71 | }; 72 | if (options.animation === false) { animationParams.duration = 0; } 73 | $animateCss(element, animationParams) 74 | .start() 75 | .then(function () { 76 | 77 | // if height was passed in then set div to scroll 78 | if (setHeight !== undefined) { 79 | element.addClass('md-scroll-y'); 80 | } else { 81 | // safari will animate the max-height if transition is not set to 0 82 | element.css('transition', 'none'); 83 | element.css('max-height', 'none'); 84 | // remove transition block on next digest 85 | $timeout(function () { 86 | element.css('transition', ''); 87 | }, 0); 88 | } 89 | 90 | element.removeClass('md-overflow'); 91 | }); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/js/expansionPanelFooter.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelFooter', expansionPanelFooterDirective); 4 | 5 | 6 | 7 | 8 | /** 9 | * @ngdoc directive 10 | * @name mdExpansionPanelFooter 11 | * @module material.components.expansionPanels 12 | * 13 | * @restrict E 14 | * 15 | * @description 16 | * `mdExpansionPanelFooter` is nested inside of `mdExpansionPanelExpanded` and contains content you want at the bottom. 17 | * By default the Footer will stick to the bottom of the page if the panel expands past 18 | * this is optional 19 | * 20 | * @param {boolean=} md-no-sticky - add this aatribute to disable sticky 21 | **/ 22 | function expansionPanelFooterDirective() { 23 | var directive = { 24 | restrict: 'E', 25 | transclude: true, 26 | template: '', 27 | require: '^^mdExpansionPanel', 28 | link: link 29 | }; 30 | return directive; 31 | 32 | 33 | 34 | function link(scope, element, attrs, expansionPanelCtrl) { 35 | var isStuck = false; 36 | var noSticky = attrs.mdNoSticky !== undefined; 37 | var container = angular.element(element[0].querySelector('.md-expansion-panel-footer-container')); 38 | 39 | expansionPanelCtrl.registerFooter({ 40 | show: show, 41 | hide: hide, 42 | onScroll: onScroll, 43 | onResize: onResize, 44 | noSticky: noSticky 45 | }); 46 | 47 | 48 | 49 | function show() { 50 | 51 | } 52 | function hide() { 53 | unstick(); 54 | } 55 | 56 | function onScroll(top, bottom, transformTop) { 57 | var height; 58 | var footerBounds = element[0].getBoundingClientRect(); 59 | var offset; 60 | 61 | if (footerBounds.bottom > bottom) { 62 | height = container[0].offsetHeight; 63 | offset = bottom - height - transformTop; 64 | if (offset < element[0].parentNode.getBoundingClientRect().top) { 65 | offset = element[0].parentNode.getBoundingClientRect().top; 66 | } 67 | 68 | // set container width because element becomes postion fixed 69 | container.css('width', expansionPanelCtrl.$element[0].offsetWidth + 'px'); 70 | 71 | // set element height so it does not loose its height when container is position fixed 72 | element.css('height', height + 'px'); 73 | container.css('top', offset + 'px'); 74 | 75 | element.addClass('md-stick'); 76 | isStuck = true; 77 | } else if (isStuck === true) { 78 | unstick(); 79 | } 80 | } 81 | 82 | function onResize(width) { 83 | if (isStuck === false) { return; } 84 | container.css('width', width + 'px'); 85 | } 86 | 87 | 88 | function unstick() { 89 | isStuck = false; 90 | container.css('width', ''); 91 | container.css('top', ''); 92 | element.css('height', ''); 93 | element.removeClass('md-stick'); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/js/expansionPanelGroup.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelGroup', expansionPanelGroupDirective); 4 | 5 | /** 6 | * @ngdoc directive 7 | * @name mdExpansionPanelGroup 8 | * @module material.components.expansionPanels 9 | * 10 | * @restrict E 11 | * 12 | * @description 13 | * `mdExpansionPanelGroup` is a container used to manage multiple expansion panels 14 | * 15 | * @param {string=} md-component-id - add an id if you want to acces the panel via the `$mdExpansionPanelGroup` service 16 | * @param {string=} auto-expand - panels expand when added to `` 17 | * @param {string=} multiple - allows for more than one panel to be expanded at a time 18 | **/ 19 | function expansionPanelGroupDirective() { 20 | var directive = { 21 | restrict: 'E', 22 | controller: ['$scope', '$attrs', '$element', '$mdComponentRegistry', controller] 23 | }; 24 | return directive; 25 | 26 | 27 | function controller($scope, $attrs, $element, $mdComponentRegistry) { 28 | /* jshint validthis: true */ 29 | var vm = this; 30 | 31 | var deregister; 32 | var registered = {}; 33 | var panels = {}; 34 | var onChangeFuncs = []; 35 | var multipleExpand = $attrs.mdMultiple !== undefined || $attrs.multiple !== undefined; 36 | var autoExpand = $attrs.mdAutoExpand !== undefined || $attrs.autoExpand !== undefined; 37 | 38 | 39 | deregister = $mdComponentRegistry.register({ 40 | $element: $element, 41 | register: register, 42 | getRegistered: getRegistered, 43 | getAll: getAll, 44 | getOpen: getOpen, 45 | remove: remove, 46 | removeAll: removeAll, 47 | collapseAll: collapseAll, 48 | onChange: onChange, 49 | count: panelCount 50 | }, $attrs.mdComponentId); 51 | 52 | vm.addPanel = addPanel; 53 | vm.expandPanel = expandPanel; 54 | vm.removePanel = removePanel; 55 | 56 | 57 | $scope.$on('$destroy', function () { 58 | if (typeof deregister === 'function') { 59 | deregister(); 60 | deregister = undefined; 61 | } 62 | 63 | // destroy all panels 64 | // for some reason the child panels scopes are not getting destroyed 65 | Object.keys(panels).forEach(function (key) { 66 | panels[key].destroy(); 67 | }); 68 | }); 69 | 70 | 71 | 72 | function onChange(callback) { 73 | onChangeFuncs.push(callback); 74 | 75 | return function () { 76 | onChangeFuncs.splice(onChangeFuncs.indexOf(callback), 1); 77 | }; 78 | } 79 | 80 | function callOnChange() { 81 | var count = panelCount(); 82 | onChangeFuncs.forEach(function (func) { 83 | func(count); 84 | }); 85 | } 86 | 87 | 88 | function addPanel(componentId, panelCtrl) { 89 | panels[componentId] = panelCtrl; 90 | if (autoExpand === true) { 91 | panelCtrl.expand(); 92 | closeOthers(componentId); 93 | } 94 | callOnChange(); 95 | } 96 | 97 | function expandPanel(componentId) { 98 | closeOthers(componentId); 99 | } 100 | 101 | function remove(componentId, options) { 102 | return panels[componentId].remove(options); 103 | } 104 | 105 | function removeAll(options) { 106 | Object.keys(panels).forEach(function (panelId) { 107 | panels[panelId].remove(options); 108 | }); 109 | } 110 | 111 | function removePanel(componentId) { 112 | delete panels[componentId]; 113 | callOnChange(); 114 | } 115 | 116 | function panelCount() { 117 | return Object.keys(panels).length; 118 | } 119 | 120 | function closeOthers(id) { 121 | if (multipleExpand === false) { 122 | Object.keys(panels).forEach(function (panelId) { 123 | if (panelId !== id) { panels[panelId].collapse(); } 124 | }); 125 | } 126 | } 127 | 128 | 129 | function register(name, options) { 130 | if (registered[name] !== undefined) { 131 | throw Error('$mdExpansionPanelGroup.register() The name "' + name + '" has already been registered'); 132 | } 133 | registered[name] = options; 134 | } 135 | 136 | 137 | function getRegistered(name) { 138 | if (registered[name] === undefined) { 139 | throw Error('$mdExpansionPanelGroup.addPanel() Cannot find Panel with name of "' + name + '"'); 140 | } 141 | return registered[name]; 142 | } 143 | 144 | 145 | function getAll() { 146 | return Object.keys(panels).map(function (panelId) { 147 | return panels[panelId]; 148 | }); 149 | } 150 | 151 | function getOpen() { 152 | return Object.keys(panels).map(function (panelId) { 153 | return panels[panelId]; 154 | }).filter(function (instance) { 155 | return instance.isOpen(); 156 | }); 157 | } 158 | 159 | function collapseAll(noAnimation) { 160 | var animation = noAnimation === true ? false : true; 161 | Object.keys(panels).forEach(function (panelId) { 162 | panels[panelId].collapse({animation: animation}); 163 | }); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/js/expansionPanelGroup.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .factory('$mdExpansionPanelGroup', expansionPanelGroupService); 4 | 5 | 6 | /** 7 | * @ngdoc service 8 | * @name $mdExpansionPanelGroup 9 | * @module material.components.expansionPanels 10 | * 11 | * @description 12 | * Expand and collapse Expansion Panel using its `md-component-id` 13 | * 14 | * @example 15 | * $mdExpansionPanelGroup('comonentId').then(function (instance) { 16 | * instance.register({ 17 | * componentId: 'cardComponentId', 18 | * templateUrl: 'template.html', 19 | * controller: 'Controller' 20 | * }); 21 | * instance.add('cardComponentId', {local: localData}); 22 | * instance.remove('cardComponentId', {animation: false}); 23 | * instance.removeAll({animation: false}); 24 | * }); 25 | */ 26 | expansionPanelGroupService.$inject = ['$mdComponentRegistry', '$mdUtil', '$mdExpansionPanel', '$templateRequest', '$rootScope', '$compile', '$controller', '$q', '$log']; 27 | function expansionPanelGroupService($mdComponentRegistry, $mdUtil, $mdExpansionPanel, $templateRequest, $rootScope, $compile, $controller, $q, $log) { 28 | var errorMsg = "ExpansionPanelGroup '{0}' is not available! Did you use md-component-id='{0}'?"; 29 | var service = { 30 | find: findInstance, 31 | waitFor: waitForInstance 32 | }; 33 | 34 | return function (handle) { 35 | if (handle === undefined) { return service; } 36 | return findInstance(handle); 37 | }; 38 | 39 | 40 | 41 | function findInstance(handle) { 42 | var instance = $mdComponentRegistry.get(handle); 43 | 44 | if (!instance) { 45 | // Report missing instance 46 | $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) ); 47 | return undefined; 48 | } 49 | 50 | return createGroupInstance(instance); 51 | } 52 | 53 | function waitForInstance(handle) { 54 | var deffered = $q.defer(); 55 | 56 | $mdComponentRegistry.when(handle).then(function (instance) { 57 | deffered.resolve(createGroupInstance(instance)); 58 | }).catch(function (error) { 59 | deffered.reject(); 60 | $log.error(error); 61 | }); 62 | 63 | return deffered.promise; 64 | } 65 | 66 | 67 | 68 | 69 | 70 | // --- returned service for group instance --- 71 | 72 | function createGroupInstance(instance) { 73 | var service = { 74 | add: add, 75 | register: register, 76 | getAll: getAll, 77 | getOpen: getOpen, 78 | remove: remove, 79 | removeAll: removeAll, 80 | collapseAll: collapseAll, 81 | onChange: onChange, 82 | count: count 83 | }; 84 | 85 | return service; 86 | 87 | 88 | function register(name, options) { 89 | if (typeof name !== 'string') { 90 | throw Error('$mdExpansionPanelGroup.register() Expects name to be a string'); 91 | } 92 | 93 | validateOptions(options); 94 | instance.register(name, options); 95 | } 96 | 97 | function remove(componentId, options) { 98 | return instance.remove(componentId, options); 99 | } 100 | 101 | function removeAll(options) { 102 | instance.removeAll(options); 103 | } 104 | 105 | function onChange(callback) { 106 | return instance.onChange(callback); 107 | } 108 | 109 | function count() { 110 | return instance.count(); 111 | } 112 | 113 | function getAll() { 114 | return instance.getAll(); 115 | } 116 | 117 | function getOpen() { 118 | return instance.getOpen(); 119 | } 120 | 121 | function collapseAll(noAnimation) { 122 | instance.collapseAll(noAnimation); 123 | } 124 | 125 | 126 | function add(options, locals) { 127 | locals = locals || {}; 128 | // assume if options is a string then they are calling a registered card by its component id 129 | if (typeof options === 'string') { 130 | // call add panel with the stored options 131 | return add(instance.getRegistered(options), locals); 132 | } 133 | 134 | validateOptions(options); 135 | if (options.componentId && instance.isPanelActive(options.componentId)) { 136 | return $q.reject('panel with componentId "' + options.componentId + '" is currently active'); 137 | } 138 | 139 | 140 | var deffered = $q.defer(); 141 | var scope = $rootScope.$new(); 142 | angular.extend(scope, options.scope); 143 | 144 | getTemplate(options, function (template) { 145 | var element = angular.element(template); 146 | var componentId = options.componentId || element.attr('md-component-id') || '_panelComponentId_' + $mdUtil.nextUid(); 147 | var panelPromise = $mdExpansionPanel().waitFor(componentId); 148 | element.attr('md-component-id', componentId); 149 | 150 | var linkFunc = $compile(element); 151 | if (options.controller) { 152 | angular.extend(locals, options.locals || {}); 153 | locals.$scope = scope; 154 | locals.$panel = panelPromise; 155 | var invokeCtrl = $controller(options.controller, locals, true); 156 | var ctrl = invokeCtrl(); 157 | element.data('$ngControllerController', ctrl); 158 | element.children().data('$ngControllerController', ctrl); 159 | if (options.controllerAs) { 160 | scope[options.controllerAs] = ctrl; 161 | } 162 | } 163 | 164 | // link after the element is added so we can find card manager directive 165 | instance.$element.append(element); 166 | linkFunc(scope); 167 | 168 | panelPromise.then(function (instance) { 169 | deffered.resolve(instance); 170 | }); 171 | }); 172 | 173 | return deffered.promise; 174 | } 175 | 176 | 177 | function validateOptions(options) { 178 | if (typeof options !== 'object' || options === null) { 179 | throw Error('$mdExapnsionPanelGroup.add()/.register() : Requires an options object to be passed in'); 180 | } 181 | 182 | // if none of these exist then a dialog box cannot be created 183 | if (!options.template && !options.templateUrl) { 184 | throw Error('$mdExapnsionPanelGroup.add()/.register() : Is missing required paramters to create. Required One of the following: template, templateUrl'); 185 | } 186 | } 187 | 188 | 189 | 190 | function getTemplate(options, callback) { 191 | var template; 192 | 193 | if (options.templateUrl !== undefined) { 194 | $templateRequest(options.templateUrl) 195 | .then(function(response) { 196 | callback(response); 197 | }); 198 | } else { 199 | callback(options.template); 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/js/expansionPanelHeader.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelHeader', expansionPanelHeaderDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelHeader 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelHeader` is nested inside of `mdExpansionPanelExpanded` and contains content you want in place of the collapsed content 16 | * this is optional 17 | * 18 | * @param {boolean=} md-no-sticky - add this aatribute to disable sticky 19 | **/ 20 | expansionPanelHeaderDirective.$inject = []; 21 | function expansionPanelHeaderDirective() { 22 | var directive = { 23 | restrict: 'E', 24 | transclude: true, 25 | template: '
', 26 | require: '^^mdExpansionPanel', 27 | link: link 28 | }; 29 | return directive; 30 | 31 | 32 | 33 | function link(scope, element, attrs, expansionPanelCtrl) { 34 | var isStuck = false; 35 | var noSticky = attrs.mdNoSticky !== undefined; 36 | var container = angular.element(element[0].querySelector('.md-expansion-panel-header-container')); 37 | 38 | expansionPanelCtrl.registerHeader({ 39 | show: show, 40 | hide: hide, 41 | noSticky: noSticky, 42 | onScroll: onScroll, 43 | onResize: onResize 44 | }); 45 | 46 | 47 | function show() { 48 | 49 | } 50 | function hide() { 51 | unstick(); 52 | } 53 | 54 | 55 | function onScroll(top, bottom, transformTop) { 56 | var offset; 57 | var panelbottom; 58 | var bounds = element[0].getBoundingClientRect(); 59 | 60 | 61 | if (bounds.top < top) { 62 | offset = top - transformTop; 63 | panelbottom = element[0].parentNode.getBoundingClientRect().bottom - top - bounds.height; 64 | if (panelbottom < 0) { 65 | offset += panelbottom; 66 | } 67 | 68 | // set container width because element becomes postion fixed 69 | container.css('width', element[0].offsetWidth + 'px'); 70 | container.css('top', offset + 'px'); 71 | 72 | // set element height so it does not shink when container is position fixed 73 | element.css('height', container[0].offsetHeight + 'px'); 74 | 75 | element.removeClass('md-no-stick'); 76 | element.addClass('md-stick'); 77 | isStuck = true; 78 | } else if (isStuck === true) { 79 | unstick(); 80 | } 81 | } 82 | 83 | function onResize(width) { 84 | if (isStuck === false) { return; } 85 | container.css('width', width + 'px'); 86 | } 87 | 88 | 89 | function unstick() { 90 | isStuck = false; 91 | container.css('width', ''); 92 | element.css('height', ''); 93 | element.css('top', ''); 94 | element.removeClass('md-stick'); 95 | element.addClass('md-no-stick'); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/js/expansionPanelIcon.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.expansionPanels') 3 | .directive('mdExpansionPanelIcon', mdExpansionPanelIconDirective); 4 | 5 | 6 | 7 | /** 8 | * @ngdoc directive 9 | * @name mdExpansionPanelIcon 10 | * @module material.components.expansionPanels 11 | * 12 | * @restrict E 13 | * 14 | * @description 15 | * `mdExpansionPanelIcon` can be used in both `md-expansion-panel-collapsed` and `md-expansion-panel-header` as the first or last element. 16 | * Adding this will provide a animated arrow for expanded and collapsed states 17 | **/ 18 | function mdExpansionPanelIconDirective() { 19 | var directive = { 20 | restrict: 'E', 21 | template: '', 22 | replace: true 23 | }; 24 | return directive; 25 | } 26 | --------------------------------------------------------------------------------