diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dfe9c2d88b55..c57d5bc2d6b2 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -10,7 +10,7 @@ #### What are the steps to reproduce? Providing a Plunker (or similar) is the *best* way to get the team to see your issue. -Plunker template: http://plnkr.co/edit/o077B6uEiiIgkC0S06dd +Plunker template: https://goo.gl/DlHd6U #### What is the use-case or motivation for changing an existing behavior? diff --git a/.gitignore b/.gitignore index 1f3d7145c431..9fded7174921 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /dist /tmp /deploy +/screenshots # dependencies /node_modules diff --git a/.travis.yml b/.travis.yml index 2f9210e483e7..5d3e851f7b2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,7 @@ language: node_js sudo: false node_js: - - '6.9.1' - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - libstdc++6 + - '6.9.4' branches: only: @@ -22,20 +15,16 @@ env: - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - BROWSER_STACK_USERNAME=angularteam1 - BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB - - ARCH=linux-x64 - BROWSER_PROVIDER_READY_FILE=/tmp/angular-material2-build/readyfile - BROWSER_PROVIDER_ERROR_FILE=/tmp/angular-material2-build/errorfile - # GITHUB_TOKEN_ANGULAR - - secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo=" matrix: # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete. - MODE=lint - - MODE=extract_metadata + - MODE=aot + - MODE=payload - MODE=e2e - MODE=saucelabs_required - MODE=browserstack_required - - MODE=saucelabs_optional - - MODE=browserstack_optional matrix: fast_finish: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e30c67f4c2..20213136d1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,249 @@ + +# [2.0.0-beta.2 flannel-papaya](https://github.com/angular/material2/compare/2.0.0-beta.1...2.0.0-beta.2) (2017-02-15) + +### Breaking changes from beta.1 +* Styling is no longer prefixed by `md-`. All styling is now prefixed by `mat-` so that apps can upgrade from AngularJS Material to Angular Material without styling conflicts between the two library components. +See ([#2790](https://github.com/angular/material2/issues/2790)) for the details on the code change and some useful regular expressions that can help migrate styles. +* Checkbox tab index @Input has been changed from `tabindex` to `tabIndex`. ([#2953](https://github.com/angular/material2/issues/2953)) +* Ripple no longer has the `mdRippleBackgroundColor` input to change the background color. ([#2859](https://github.com/angular/material2/issues/2859)) +* The deprecated use of `` and `` has been removed. Use `mdInput` on an input or textarea within a `md-input-container`. `md-prefix` and `md-suffix` are now `mdPrefix` and `mdSuffix`. ([#2788](https://github.com/angular/material2/issues/2788)) + + ```html + + + + ``` + +* The deprecated use of `` has been removed. Use `` instead. ([#2283](https://github.com/angular/material2/issues/2283)) +* Input floating placeholder @Input has changed from a boolean (`true` and `false`) to a state (`always`, `never`, and `auto`) and was renamed from `floatingPlaceholder` to `floatPlaceholder`. For details on when to use which state, see ([#2585](https://github.com/angular/material2/issues/2585)) +* The use of Module `forRoot` has been deprecated and will be removed in the next release. Instead, just simply import MaterialModule directly: + + ```ts + @NgModule({ + imports: [ + ... + MaterialModule, + ... + ] + ... + }); + ``` + +### Bug Fixes + +* **autocomplete:** add mat version of autocomplete [@Input](https://github.com/Input) ([#2928](https://github.com/angular/material2/issues/2928)) ([e5521a8](https://github.com/angular/material2/commit/e5521a8)) +* **autocomplete:** allow basic use without forms directives ([#2958](https://github.com/angular/material2/issues/2958)) ([4ee2980](https://github.com/angular/material2/commit/4ee2980)) +* **autocomplete:** close panel when options list is empty ([#2834](https://github.com/angular/material2/issues/2834)) ([8a3b6fd](https://github.com/angular/material2/commit/8a3b6fd)) +* **autocomplete:** double-clicking input shouldnt close the panel ([#2835](https://github.com/angular/material2/issues/2835)) ([18969f4](https://github.com/angular/material2/commit/18969f4)) +* **autocomplete:** hide instead of close when options empty ([#2997](https://github.com/angular/material2/issues/2997)) ([a022035](https://github.com/angular/material2/commit/a022035)) +* **autocomplete:** placeholder should float while panel is open ([#2730](https://github.com/angular/material2/issues/2730)) ([eec4dc6](https://github.com/angular/material2/commit/eec4dc6)) +* **autocomplete:** scroll options below fold into view ([#2728](https://github.com/angular/material2/issues/2728)) ([6c84603](https://github.com/angular/material2/commit/6c84603)) +* **autocomplete:** support rtl ([#2648](https://github.com/angular/material2/issues/2648)) ([4f59ad0](https://github.com/angular/material2/commit/4f59ad0)) +* **autocomplete:** up arrow should set last item active ([#2776](https://github.com/angular/material2/issues/2776)) ([fd5e4d9](https://github.com/angular/material2/commit/fd5e4d9)) +* **autosize:** export md-autosize directive ([#2432](https://github.com/angular/material2/issues/2432)) ([f2d73da](https://github.com/angular/material2/commit/f2d73da)), closes [#2419](https://github.com/angular/material2/issues/2419) +* **button:** add default color for mat-raised-button ([#3052](https://github.com/angular/material2/issues/3052)) ([6fe1d9a](https://github.com/angular/material2/commit/6fe1d9a)) +* **button:** only flat button and icon buttons should inherit the color ([#2561](https://github.com/angular/material2/issues/2561)) ([ac363df](https://github.com/angular/material2/commit/ac363df)), closes [#2539](https://github.com/angular/material2/issues/2539) +* **button:** raised buttons in dark theme ([#3070](https://github.com/angular/material2/issues/3070)) ([87ab712](https://github.com/angular/material2/commit/87ab712)) +* **button:** reuse _getHostElement() to avoid redundant elementRef.nativeElement calls ([#2625](https://github.com/angular/material2/issues/2625)) ([c7d1c17](https://github.com/angular/material2/commit/c7d1c17)) +* **button-toggle:** add the setDisabledState from ControlValueAccessor ([#2430](https://github.com/angular/material2/issues/2430)) ([fb750b4](https://github.com/angular/material2/commit/fb750b4)) +* **button-toggle:** conflict with radio component ([#2343](https://github.com/angular/material2/issues/2343)) ([9e99374](https://github.com/angular/material2/commit/9e99374)), closes [#2274](https://github.com/angular/material2/issues/2274) +* **button-toggle:** make conform with design specs ([#2570](https://github.com/angular/material2/issues/2570)) ([fed5d7b](https://github.com/angular/material2/commit/fed5d7b)) +* **card:** fix padding for md-card-actions in xs screens ([#2567](https://github.com/angular/material2/issues/2567)) ([ad0df31](https://github.com/angular/material2/commit/ad0df31)) +* **checkbox:** Emit event when checkbox's indeterminate value changes ([#2130](https://github.com/angular/material2/issues/2130)) ([f11c5eb](https://github.com/angular/material2/commit/f11c5eb)) +* **checkbox:** rename tabindex to tabIndex ([#2953](https://github.com/angular/material2/issues/2953)) ([b91964a](https://github.com/angular/material2/commit/b91964a)) +* **checkbox:** ripple color does not change ([#2857](https://github.com/angular/material2/issues/2857)) ([7ac29f8](https://github.com/angular/material2/commit/7ac29f8)) +* **checkbox, radio:** not using theme border color ([#2744](https://github.com/angular/material2/issues/2744)) ([07ec765](https://github.com/angular/material2/commit/07ec765)) +* **compatibility:** add missing mat- selectors ([#2923](https://github.com/angular/material2/issues/2923)) ([f29f7ab](https://github.com/angular/material2/commit/f29f7ab)) +* **connected-position-strategy:** wrong logic when determining whether element is on screen ([#2677](https://github.com/angular/material2/issues/2677)) ([e055d05](https://github.com/angular/material2/commit/e055d05)), closes [#2102](https://github.com/angular/material2/issues/2102) [#2658](https://github.com/angular/material2/issues/2658) +* **dialog:** escape key not working once element loses focus ([#3082](https://github.com/angular/material2/issues/3082)) ([a08dc55](https://github.com/angular/material2/commit/a08dc55)), closes [#3009](https://github.com/angular/material2/issues/3009) +* **dialog:** prevent error when restoring focus on IE ([#2771](https://github.com/angular/material2/issues/2771)) ([153fcd3](https://github.com/angular/material2/commit/153fcd3)), closes [#2760](https://github.com/angular/material2/issues/2760) +* **dialog:** prevent the close button from submitting forms ([#2659](https://github.com/angular/material2/issues/2659)) ([29f939a](https://github.com/angular/material2/commit/29f939a)), closes [#2599](https://github.com/angular/material2/issues/2599) +* **dialog:** use injector from viewContainerRef if provided ([#2655](https://github.com/angular/material2/issues/2655)) ([be0da09](https://github.com/angular/material2/commit/be0da09)) +* **docs:** properly create links in guide files ([#2770](https://github.com/angular/material2/issues/2770)) ([60f03ed](https://github.com/angular/material2/commit/60f03ed)) +* **icon:** add caching of md-icon aria-label ([#2649](https://github.com/angular/material2/issues/2649)) ([08e9d70](https://github.com/angular/material2/commit/08e9d70)), closes [#2642](https://github.com/angular/material2/issues/2642) +* **input:** add more padding so that the hint doesn't overflow the container ([#2246](https://github.com/angular/material2/issues/2246)) ([d7831d9](https://github.com/angular/material2/commit/d7831d9)) +* **input:** camel-case md-prefix and md-suffix ([#2639](https://github.com/angular/material2/issues/2639)) ([7562322](https://github.com/angular/material2/commit/7562322)), closes [#2636](https://github.com/angular/material2/issues/2636) +* **input:** disable underline with reactive forms ([#2565](https://github.com/angular/material2/issues/2565)) ([f9dd34f](https://github.com/angular/material2/commit/f9dd34f)), closes [#2558](https://github.com/angular/material2/issues/2558) +* **input:** disabled inputs should be grayed out ([#2513](https://github.com/angular/material2/issues/2513)) ([ed3ffe0](https://github.com/angular/material2/commit/ed3ffe0)) +* **input:** ensure that property bindings work ([#2431](https://github.com/angular/material2/issues/2431)) ([b4b4224](https://github.com/angular/material2/commit/b4b4224)), closes [#2428](https://github.com/angular/material2/issues/2428) +* **input:** fix chrome 56 warning ([#2906](https://github.com/angular/material2/issues/2906)) ([62189a3](https://github.com/angular/material2/commit/62189a3)) +* **input:** fix placeholder for number input with bad input. ([#2362](https://github.com/angular/material2/issues/2362)) ([52aa715](https://github.com/angular/material2/commit/52aa715)) +* **input:** hints not being read out by screen readers ([#2856](https://github.com/angular/material2/issues/2856)) ([f899b5f](https://github.com/angular/material2/commit/f899b5f)), closes [#2798](https://github.com/angular/material2/issues/2798) +* **input:** horizontal overflow in IE and Edge ([#2784](https://github.com/angular/material2/issues/2784)) ([e0fe635](https://github.com/angular/material2/commit/e0fe635)) +* **input:** properly determine input value ([#2455](https://github.com/angular/material2/issues/2455)) ([3a11927](https://github.com/angular/material2/commit/3a11927)), closes [#2441](https://github.com/angular/material2/issues/2441) [#2363](https://github.com/angular/material2/issues/2363) +* **input:** remove md-input and md-textarea in favor of md-input-container ([#2788](https://github.com/angular/material2/issues/2788)) ([7b30fdc](https://github.com/angular/material2/commit/7b30fdc)) +* **input:** vendor-prefix ::placeholder ([#2547](https://github.com/angular/material2/issues/2547)) ([3b16648](https://github.com/angular/material2/commit/3b16648)) +* **input-container:** prefix and suffix stretching together with parent ([#2496](https://github.com/angular/material2/issues/2496)) ([64f6d1b](https://github.com/angular/material2/commit/64f6d1b)), closes [#2493](https://github.com/angular/material2/issues/2493) [#1881](https://github.com/angular/material2/issues/1881) [#1421](https://github.com/angular/material2/issues/1421) +* **input-container:** reduce redundancy when forwarding the NgControl classes ([#2442](https://github.com/angular/material2/issues/2442)) ([8c0eef2](https://github.com/angular/material2/commit/8c0eef2)) +* **option:** revert duplicate prop ([#3051](https://github.com/angular/material2/issues/3051)) ([516720f](https://github.com/angular/material2/commit/516720f)) +* **overlay:** disable pointer events if overlay is detached ([#2747](https://github.com/angular/material2/issues/2747)) ([453fa7f](https://github.com/angular/material2/commit/453fa7f)), closes [#2739](https://github.com/angular/material2/issues/2739) +* **overlay:** fix pointer events for ie11 ([#3023](https://github.com/angular/material2/issues/3023)) ([597e3de](https://github.com/angular/material2/commit/597e3de)), closes [#3022](https://github.com/angular/material2/issues/3022) +* **progress-bar:** buffer animation not working in IE ([#2941](https://github.com/angular/material2/issues/2941)) ([ab8f98f](https://github.com/angular/material2/commit/ab8f98f)), closes [#2881](https://github.com/angular/material2/issues/2881) +* **progress-bar:** unable to apply visibility in indeterminate mode and reduce CSS ([#2417](https://github.com/angular/material2/issues/2417)) ([eb96b0c](https://github.com/angular/material2/commit/eb96b0c)), closes [#2413](https://github.com/angular/material2/issues/2413) +* **progress-spinner:** fix color input on md-spinner ([#2396](https://github.com/angular/material2/issues/2396)) ([6cb6576](https://github.com/angular/material2/commit/6cb6576)), closes [#2393](https://github.com/angular/material2/issues/2393) +* **radio:** change radio button trigger element to input element ([#2838](https://github.com/angular/material2/issues/2838)) ([2f10a95](https://github.com/angular/material2/commit/2f10a95)) +* **ripple:** camel-cased CSS classes ([#2340](https://github.com/angular/material2/issues/2340)) ([c67f4e5](https://github.com/angular/material2/commit/c67f4e5)) +* **ripple:** make ripples conform with specs ([#2859](https://github.com/angular/material2/issues/2859)) ([6381948](https://github.com/angular/material2/commit/6381948)) +* **select:** avoid going into infinite loop under certain conditions ([#2955](https://github.com/angular/material2/issues/2955)) ([998a583](https://github.com/angular/material2/commit/998a583)), closes [#2950](https://github.com/angular/material2/issues/2950) +* **select:** don't open menu if there are no options ([#2924](https://github.com/angular/material2/issues/2924)) ([cc77ef4](https://github.com/angular/material2/commit/cc77ef4)) +* **select:** fix select panel animation ([#2699](https://github.com/angular/material2/issues/2699)) ([15eb33a](https://github.com/angular/material2/commit/15eb33a)), closes [#2695](https://github.com/angular/material2/issues/2695) +* **select:** fix selection color ([#2697](https://github.com/angular/material2/issues/2697)) ([4e94da4](https://github.com/angular/material2/commit/4e94da4)), closes [#2696](https://github.com/angular/material2/issues/2696) +* **select:** selected option not being highlighted when options are added asynchronously ([#2499](https://github.com/angular/material2/issues/2499)) ([7fc38b9](https://github.com/angular/material2/commit/7fc38b9)), closes [#2497](https://github.com/angular/material2/issues/2497) +* **select:** set default font size ([#2976](https://github.com/angular/material2/issues/2976)) ([40bc486](https://github.com/angular/material2/commit/40bc486)) +* **select:** set select value to trigger height and center text ([#3021](https://github.com/angular/material2/issues/3021)) ([ac9c090](https://github.com/angular/material2/commit/ac9c090)) +* **select:** support use inside a custom value accessor ([#2704](https://github.com/angular/material2/issues/2704)) ([651440f](https://github.com/angular/material2/commit/651440f)), closes [#2609](https://github.com/angular/material2/issues/2609) +* **select:** transparent background when overscrolling ([#2117](https://github.com/angular/material2/issues/2117)) ([d9b2d85](https://github.com/angular/material2/commit/d9b2d85)) +* **select:** trim long labels inside md-option ([#2444](https://github.com/angular/material2/issues/2444)) ([416f56f](https://github.com/angular/material2/commit/416f56f)), closes [#2440](https://github.com/angular/material2/issues/2440) +* **select:** view not updating when using OnPush detection strategy ([#2894](https://github.com/angular/material2/issues/2894)) ([3bcb7c3](https://github.com/angular/material2/commit/3bcb7c3)), closes [#2663](https://github.com/angular/material2/issues/2663) [#2269](https://github.com/angular/material2/issues/2269) +* **select:** parent align affects placeholder ([#2572](https://github.com/angular/material2/issues/2572)) ([a1c90b3](https://github.com/angular/material2/commit/a1c90b3)) +* **sidenav:** animate content resizing for side mode. ([#2486](https://github.com/angular/material2/issues/2486)) ([4d33449](https://github.com/angular/material2/commit/4d33449)) +* **sidenav:** fix animation issue for initially open sidenav ([#3045](https://github.com/angular/material2/issues/3045)) ([37e4bad](https://github.com/angular/material2/commit/37e4bad)) +* **slide-toggle:** consistent naming of aria attributes ([#2688](https://github.com/angular/material2/issues/2688)) ([10bd6da](https://github.com/angular/material2/commit/10bd6da)) +* **slider:** fire change event on value change via keyboard. ([#2807](https://github.com/angular/material2/issues/2807)) ([7f50d11](https://github.com/angular/material2/commit/7f50d11)) +* **slider:** hide ticks when slider is disabled ([#2687](https://github.com/angular/material2/issues/2687)) ([e9ec8ab](https://github.com/angular/material2/commit/e9ec8ab)) +* apply font-family to text components ([#2821](https://github.com/angular/material2/issues/2821)) ([d11673a](https://github.com/angular/material2/commit/d11673a)) +* **slider:** make disabled state look like mocks ([#2604](https://github.com/angular/material2/issues/2604)) ([8263ffb](https://github.com/angular/material2/commit/8263ffb)) +* **slider:** make min value style match mocks ([#2641](https://github.com/angular/material2/issues/2641)) ([737b608](https://github.com/angular/material2/commit/737b608)) +* **slider:** round decimals in the thumb label ([#2527](https://github.com/angular/material2/issues/2527)) ([987897c](https://github.com/angular/material2/commit/987897c)), closes [#2511](https://github.com/angular/material2/issues/2511) +* **snack-bar:** improper button styling and improved handling of long text ([#2991](https://github.com/angular/material2/issues/2991)) ([93937e6](https://github.com/angular/material2/commit/93937e6)), closes [#2979](https://github.com/angular/material2/issues/2979) +* **snack-bar:** prevent error when opening multiple snack bars in fast succession ([#2392](https://github.com/angular/material2/issues/2392)) ([161f319](https://github.com/angular/material2/commit/161f319)), closes [#2390](https://github.com/angular/material2/issues/2390) +* **snack-bar:** SimpleSnackBar not being exported ([#3016](https://github.com/angular/material2/issues/3016)) ([a7a3967](https://github.com/angular/material2/commit/a7a3967)), closes [#3010](https://github.com/angular/material2/issues/3010) +* **tabs:** crashing on chrome under certain conditions ([#2411](https://github.com/angular/material2/issues/2411)) ([727ce53](https://github.com/angular/material2/commit/727ce53)), closes [#2151](https://github.com/angular/material2/issues/2151) +* **tabs:** fix ink not showing on chrome 57 ([#3041](https://github.com/angular/material2/issues/3041)) ([f24832c](https://github.com/angular/material2/commit/f24832c)) +* **tabs:** infinite loop when selectedIndex is set to NaN ([#2389](https://github.com/angular/material2/issues/2389)) ([f4cfc2d](https://github.com/angular/material2/commit/f4cfc2d)) +* **toolbar:** add toolbar role to host element ([#2914](https://github.com/angular/material2/issues/2914)) ([67032ca](https://github.com/angular/material2/commit/67032ca)), closes [#2909](https://github.com/angular/material2/issues/2909) +* **toolbar:** correct font-weight ([#2485](https://github.com/angular/material2/issues/2485)) ([1b44880](https://github.com/angular/material2/commit/1b44880)) +* **toolbar:** prevent content overflow and line-wrapping ([#2454](https://github.com/angular/material2/issues/2454)) ([e728771](https://github.com/angular/material2/commit/e728771)), closes [#2451](https://github.com/angular/material2/issues/2451) +* **tooltip:** better handling of multi-line text ([#2472](https://github.com/angular/material2/issues/2472)) ([7863e38](https://github.com/angular/material2/commit/7863e38)), closes [#2205](https://github.com/angular/material2/issues/2205) +* **tooltip:** not working properly with ChangeDetectionStrategy.OnPush ([#2721](https://github.com/angular/material2/issues/2721)) ([632b964](https://github.com/angular/material2/commit/632b964)), closes [#2713](https://github.com/angular/material2/issues/2713) +* **tooltip:** provide a maximum width ([#2678](https://github.com/angular/material2/issues/2678)) ([fb5e1d4](https://github.com/angular/material2/commit/fb5e1d4)), closes [#2671](https://github.com/angular/material2/issues/2671) + + +### Features + +* **autocomplete:** add autocomplete panel toggling ([#2452](https://github.com/angular/material2/issues/2452)) ([d4ab3d3](https://github.com/angular/material2/commit/d4ab3d3)) +* **autocomplete:** add fallback positions ([#2726](https://github.com/angular/material2/issues/2726)) ([8fc7706](https://github.com/angular/material2/commit/8fc7706)) +* **autocomplete:** add keyboard events to autocomplete ([#2723](https://github.com/angular/material2/issues/2723)) ([fcea9d4](https://github.com/angular/material2/commit/fcea9d4)) +* **autocomplete:** add screenreader support ([#2729](https://github.com/angular/material2/issues/2729)) ([bd7f240](https://github.com/angular/material2/commit/bd7f240)) +* **autocomplete:** add value support ([#2516](https://github.com/angular/material2/issues/2516)) ([5def001](https://github.com/angular/material2/commit/5def001)) +* **autocomplete:** allow use of obj values ([#2792](https://github.com/angular/material2/issues/2792)) ([55e1847](https://github.com/angular/material2/commit/55e1847)) +* **button-toggle:** Show selected option when md-button-toggle is disabled ([#3012](https://github.com/angular/material2/issues/3012)) ([1547440](https://github.com/angular/material2/commit/1547440)), closes [#3007](https://github.com/angular/material2/issues/3007) +* **compatibility:** remove conflicts with material1 css styling ([#2790](https://github.com/angular/material2/issues/2790)) ([210ff02](https://github.com/angular/material2/commit/210ff02)) +* **dialog:** add a config option for passing in data ([#2266](https://github.com/angular/material2/issues/2266)) ([29cbe61](https://github.com/angular/material2/commit/29cbe61)), closes [#2181](https://github.com/angular/material2/issues/2181) +* **dialog:** add events (observables) for open & closeAll ([#2522](https://github.com/angular/material2/issues/2522)) ([23ab152](https://github.com/angular/material2/commit/23ab152)) +* **dialog:** add the ability to align the content of md-dialog-actions ([#2557](https://github.com/angular/material2/issues/2557)) ([e18ab5d](https://github.com/angular/material2/commit/e18ab5d)), closes [#2483](https://github.com/angular/material2/issues/2483) +* **dialog:** support open with TemplateRef ([#2910](https://github.com/angular/material2/issues/2910)) ([bf0f625](https://github.com/angular/material2/commit/bf0f625)) +* **focus-classes:** expose focus origin changes through observable ([#2974](https://github.com/angular/material2/issues/2974)) ([d4ba648](https://github.com/angular/material2/commit/d4ba648)) +* **FocusOriginMonitor:** add support for touch events ([#3020](https://github.com/angular/material2/issues/3020)) ([ec7e2e4](https://github.com/angular/material2/commit/ec7e2e4)) +* **input:** option to imperatively float placeholder ([#2585](https://github.com/angular/material2/issues/2585)) ([fb0cf8a](https://github.com/angular/material2/commit/fb0cf8a)), closes [#2466](https://github.com/angular/material2/issues/2466) +* **list-key-manager:** active descendant support ([#2606](https://github.com/angular/material2/issues/2606)) ([e2ad3a0](https://github.com/angular/material2/commit/e2ad3a0)) +* **menu:** Added ability to show the menu overlay around the menu trigger ([#1771](https://github.com/angular/material2/issues/1771)) ([592f33f](https://github.com/angular/material2/commit/592f33f)) +* **overlay:** add fullscreen-enabled overlay class ([#1949](https://github.com/angular/material2/issues/1949)) ([0640302](https://github.com/angular/material2/commit/0640302)) +* **screenshot:** Add screenshot function to e2e test (button and checkbox) ([#2532](https://github.com/angular/material2/issues/2532)) ([8ba8deb](https://github.com/angular/material2/commit/8ba8deb)) +* **scripts:** push generated docs to material assets repo ([#2720](https://github.com/angular/material2/issues/2720)) ([ba12f44](https://github.com/angular/material2/commit/ba12f44)) +* **select:** emit change event ([#2458](https://github.com/angular/material2/issues/2458)) ([e5bd15c](https://github.com/angular/material2/commit/e5bd15c)), closes [#2248](https://github.com/angular/material2/issues/2248) +* **sidenav:** add disableClose option ([#2501](https://github.com/angular/material2/issues/2501)) ([52ade97](https://github.com/angular/material2/commit/52ade97)), closes [#2462](https://github.com/angular/material2/issues/2462) +* **slide-toggle:** add support for labelPosition ([#2836](https://github.com/angular/material2/issues/2836)) ([68a0c90](https://github.com/angular/material2/commit/68a0c90)), closes [#2820](https://github.com/angular/material2/issues/2820) +* **slider:** emit input event when slider thumb moves ([#2325](https://github.com/angular/material2/issues/2325)) ([99963c4](https://github.com/angular/material2/commit/99963c4)), closes [#2296](https://github.com/angular/material2/issues/2296) +* **snack-bar:** allow addition of extra css classes ([#2804](https://github.com/angular/material2/issues/2804)) ([e783494](https://github.com/angular/material2/commit/e783494)), closes [#2664](https://github.com/angular/material2/issues/2664) +* **style:** add directive to determine how elements were focused. ([#2646](https://github.com/angular/material2/issues/2646)) ([8a6d902](https://github.com/angular/material2/commit/8a6d902)) +* **tabs:** add the ability to invert the header ([#2391](https://github.com/angular/material2/issues/2391)) ([a494c92](https://github.com/angular/material2/commit/a494c92)), closes [#2387](https://github.com/angular/material2/issues/2387) +* **theming:** provide a content wrapper attribute ([#2170](https://github.com/angular/material2/issues/2170)) ([4bf4b87](https://github.com/angular/material2/commit/4bf4b87)), closes [#1938](https://github.com/angular/material2/issues/1938) [#2106](https://github.com/angular/material2/issues/2106) +* **tooltip:** reposition on scroll ([#2703](https://github.com/angular/material2/issues/2703)) ([bc52298](https://github.com/angular/material2/commit/bc52298)) +* **viewport-ruler:** cache document client rect ([#2538](https://github.com/angular/material2/issues/2538)) ([d0c8f18](https://github.com/angular/material2/commit/d0c8f18)) +* add a common class to be used when dealing with selection logic ([#2562](https://github.com/angular/material2/issues/2562)) ([c295fa9](https://github.com/angular/material2/commit/c295fa9)) +* add simplified checkbox component for usage in other components ([#2619](https://github.com/angular/material2/issues/2619)) ([3b6cab0](https://github.com/angular/material2/commit/3b6cab0)) +* remove the need for forRoot on material NgModules ([#2556](https://github.com/angular/material2/issues/2556)) ([b49bfce](https://github.com/angular/material2/commit/b49bfce)) + + +### Performance Improvements + +* **tabs:** reduce amount of reflows when aligning the ink bar ([#2372](https://github.com/angular/material2/issues/2372)) ([dab742f](https://github.com/angular/material2/commit/dab742f)) + + + +# [2.0.0-beta.1 rebar-teacup](https://github.com/angular/material2/compare/2.0.0-beta.0...2.0.0-beta.1) (2016-12-23) + +### Bug Fixes +* Remove `MdAutocompleteModule` that was accidentally included in beta.0 +* Correct path to umd bundle in package.json ([#2368](https://github.com/angular/material2/issues/2368)) ([d286e6d](https://github.com/angular/material2/commit/d286e6d6472910ab41b8437d405cd3e902c9c848)), closes [#2366](https://github.com/angular/material2/issues/2366) + + + +# [2.0.0-beta.0 velvet-pizza](https://github.com/angular/material2/compare/2.0.0-alpha.9...2.0.0-beta.0) (2016-12-22) + +### Breaking changes from alpha.11 +* The `svgSrc` propert of `` has been removed. All SVG URLs must now be explicitly marked +as trusted using Angular's `DomSanitizer` service. +* The `` element is deprecated in favor of ``. This new component +allows for direct access to the native input element. +* All `@Input` properties have been changed to use their camelCase names for binding. The old names +are still available as deprecated but will be removed in the next release. +* All `@Directive` selectors are now camelCase to be consistent with Angular core. For example, +`[md-tooltip]` is now `[mdTooltip]` The old selectors are still available as deprecated but will +be removed in the next release. +* `` has been renamed to ``. The old selector and symbols +are still available as deprecated but will be removed in the next release. +* `` has been renamed to ``. The old selector is still +available as deprecated but will be removed in the next release. +* Several components in `core/`, such as Overlay, have had their prefix changed to `cdk-` (short +for "component dev kit"). This signifies that these are general-purpose tools for building +components that are not coupled to Material Design.The old selectors are still +available as deprecated but will be removed in the next release. The CSS classes have been changed. +* The `align` property for `md-checkbox` and `md-radio-button` has been changed to `labelPosition` +with values `before` and `after`. +* `MdTooltip` properties are now prefixed, e.g., `mdTooltipPosition`, `mdTooltipHideDelay`, etc. + +### Note on HammerJS +HammerJS is now optional. It is still necessary for gestures to work within certain components, but +should no longer throw an error if it is missing. + +### Bug Fixes + +* **a11y:** add all providers to forRoot ([#2222](https://github.com/angular/material2/issues/2222)) ([9272b4b](https://github.com/angular/material2/commit/9272b4b)), closes [#2189](https://github.com/angular/material2/issues/2189) +* add re-exports for symbols needed by Aot ([#2149](https://github.com/angular/material2/issues/2149)) ([c324142](https://github.com/angular/material2/commit/c324142)) +* **icon:** remove svgSrc, only allow trusted urls ([#1933](https://github.com/angular/material2/issues/1933)) ([4571561](https://github.com/angular/material2/commit/4571561)) +* **input:** copy input state classes to md-input-container ([#2191](https://github.com/angular/material2/issues/2191)) ([f0c4148](https://github.com/angular/material2/commit/f0c4148)) +* **input:** fix underline color to match spec ([#2167](https://github.com/angular/material2/issues/2167)) ([b850fed](https://github.com/angular/material2/commit/b850fed)), closes [#2126](https://github.com/angular/material2/issues/2126) +* **input:** label alignment in rtl ([#2047](https://github.com/angular/material2/issues/2047)) ([7b3a059](https://github.com/angular/material2/commit/7b3a059)), closes [#2034](https://github.com/angular/material2/issues/2034) +* **input:** treat number 0 as non-empty ([#2245](https://github.com/angular/material2/issues/2245)) ([a818579](https://github.com/angular/material2/commit/a818579)) +* **input:** unable to focus input in IE 11 ([#2233](https://github.com/angular/material2/issues/2233)) ([8ec3a19](https://github.com/angular/material2/commit/8ec3a19)) +* **overlay:** proper backdrop stacking with multiple overlays ([#2276](https://github.com/angular/material2/issues/2276)) ([b16031a](https://github.com/angular/material2/commit/b16031a)), closes [#2272](https://github.com/angular/material2/issues/2272) +* **platform:** Remove assumption of `window' in MdPlatform ([#2221](https://github.com/angular/material2/issues/2221)) ([e436775](https://github.com/angular/material2/commit/e436775)) +* **positioning:** fallback positions should work while scrolled ([#2193](https://github.com/angular/material2/issues/2193)) ([8df30db](https://github.com/angular/material2/commit/8df30db)) +* **progress-circle, progress-bar:** bind color via [color] rather than [attr.color] ([#2299](https://github.com/angular/material2/issues/2299)) ([e4d2bef](https://github.com/angular/material2/commit/e4d2bef)) +* **progress-spinner:** Rename ProgressCircle to ProgressSpinner ([#2300](https://github.com/angular/material2/issues/2300)) ([221c234](https://github.com/angular/material2/commit/221c234)) +* **select:** clear select if no option matches value ([#2110](https://github.com/angular/material2/issues/2110)) ([2855cc3](https://github.com/angular/material2/commit/2855cc3)), closes [#2109](https://github.com/angular/material2/issues/2109) +* **select:** make invalid selector more specific ([#2166](https://github.com/angular/material2/issues/2166)) ([a7c88c5](https://github.com/angular/material2/commit/a7c88c5)) +* **sidenav:** make `focus-trap` occupy full height of sidenav ([#2145](https://github.com/angular/material2/issues/2145)) ([13223df](https://github.com/angular/material2/commit/13223df)) +* **sidenav:** Rename md-sidenav-layout to md-sidenav-container. ([#2183](https://github.com/angular/material2/issues/2183)) ([8f1c5a9](https://github.com/angular/material2/commit/8f1c5a9)) +* **slider:** prevent thumb from getting stuck on Mobile Safari ([#2142](https://github.com/angular/material2/issues/2142)) ([4adee46](https://github.com/angular/material2/commit/4adee46)) +* **snack-bar:** clean up element when associated viewContainer is destroyed ([#2219](https://github.com/angular/material2/issues/2219)) ([db9608f](https://github.com/angular/material2/commit/db9608f)), closes [#2190](https://github.com/angular/material2/issues/2190) +* **tabs:** observing tab header label changes to recalculate width ([#2186](https://github.com/angular/material2/issues/2186)) ([7ab4430](https://github.com/angular/material2/commit/7ab4430)), closes [#2155](https://github.com/angular/material2/issues/2155) +* TSError: ? Unable to compile TypeScript when running 'gulp:build' ([#2132](https://github.com/angular/material2/issues/2132)) ([714c2a4](https://github.com/angular/material2/commit/714c2a4)) +* **tests:** snackbar ref has unnecessary observable.of ([#2298](https://github.com/angular/material2/issues/2298)) ([d076bd3](https://github.com/angular/material2/commit/d076bd3)) +* **tooltip:** add missing rxjs import ([#2288](https://github.com/angular/material2/issues/2288)) ([ba7053f](https://github.com/angular/material2/commit/ba7053f)) + + +### Features +* make hammerjs optional ([#2280](https://github.com/angular/material2/issues/2280)) ([28691ca](https://github.com/angular/material2/commit/28691ca)) +* **chips:** initial version of md-chip-list. ([#2242](https://github.com/angular/material2/issues/2242)) ([f45c315](https://github.com/angular/material2/commit/f45c315)) ([#2046](https://github.com/angular/material2/issues/2046)) ([ba85883](https://github.com/angular/material2/commit/ba85883)) ([#2332](https://github.com/angular/material2/issues/2332)) ([3f2db27](https://github.com/angular/material2/commit/3f2db27)) +* **dialog:** add dialog content elements ([#2090](https://github.com/angular/material2/issues/2090)) ([cac72aa](https://github.com/angular/material2/commit/cac72aa)), closes [#1624](https://github.com/angular/material2/issues/1624) [#2042](https://github.com/angular/material2/issues/2042) +* **checkbox, radio:** change `align` to `labelPosition` (inverted) ([#2289](https://github.com/angular/material2/issues/2289)) ([a1f9028](https://github.com/angular/material2/commit/a1f9028)) +* **connected-position:** apply the fallback position that shows the largest area of the element ([#2102](https://github.com/angular/material2/issues/2102)) ([4f5b9c5](https://github.com/angular/material2/commit/4f5b9c5)), closes [#2049](https://github.com/angular/material2/issues/2049) +* **core:** add scrollable view properties to connected pos strategy ([#2259](https://github.com/angular/material2/issues/2259)) ([b60d33f](https://github.com/angular/material2/commit/b60d33f)) +* **input:** create md-input-container to eventually replace md-input ([#2052](https://github.com/angular/material2/issues/2052)) ([ca2046b](https://github.com/angular/material2/commit/ca2046b)) +* **scroll:** provide directive and service to listen to scrolling ([#2188](https://github.com/angular/material2/issues/2188)) ([9b68e68](https://github.com/angular/material2/commit/9b68e68)) +* **sidenav:** close via escape key and restore focus to trigger element ([#1990](https://github.com/angular/material2/issues/1990)) ([a1331ec](https://github.com/angular/material2/commit/a1331ec)) +* **tooltip:** add input for delaying show and hide ([#2101](https://github.com/angular/material2/issues/2101)) ([e85d108](https://github.com/angular/material2/commit/e85d108)) +* **toolbar** add responsive heights as per spec ([#2157](https://github.com/angular/material2/issues/2157)) ([78d54fc](https://github.com/angular/material2/commit/78d54fc08491ce35f9ad06dc50488cc4d4c3a5e8)) + +### Performance Improvements + +* **sidenav:** avoid extra repaints while scrolling ([#2156](https://github.com/angular/material2/issues/2156)) ([b967712](https://github.com/angular/material2/commit/b967712)) + + + # [2.0.0-alpha.11 polyester-golem](https://github.com/angular/material2/compare/2.0.0-alpha.9...2.0.0-alpha.11) (2016-12-08) @@ -191,7 +437,7 @@ contains all of the components. Build tools such as [`rollup.js`](http://rollupj tree-shaking to eliminate the code for components that you aren't using. The addition of theming as also changed the directory structure for bringing the core css into your -application. See the new [theming guide](docs/theming.md) for more information. +application. See the new [theming guide](guides/theming.md) for more information. ### Features diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md index 16badedbe0ff..25b69346a9e7 100644 --- a/CODING_STANDARDS.md +++ b/CODING_STANDARDS.md @@ -12,7 +12,8 @@ ES6 or TypeScript. ### General #### Write useful comments -Comments that explain what some block of code does are nice; they can tell you something in less time than it would take to follow through the code itself. +Comments that explain what some block of code does are nice; they can tell you something in less +time than it would take to follow through the code itself. Comments that explain why some block of code exists at all, or does something the way it does, are _invaluable_. The "why" is difficult, or sometimes impossible, to track down without seeking out @@ -42,8 +43,8 @@ if (!$attrs['tabindex']) { For example, rather than doing this: ```html Basic button -FAB -pony +FAB +pony ``` do this: @@ -58,19 +59,122 @@ Keeping modules to a single responsibility makes the code easier to test, consum ES6 modules offer a straightforward way to organize code into logical, granular units. Ideally, individual files are 200 - 300 lines of code. +As a rule of thumb, once a file draws near 400 lines (barring abnormally long constants / comments), +start considering how to refactor into smaller pieces. + #### Less is more Once a feature is released, it never goes away. We should avoid adding features that don't offer high user value for price we pay both in maintenance, complexity, and payload size. When in doubt, leave it out. This applies especially so to providing two different APIs to accomplish the same thing. Always -prefer sticking to a _single_ API for accomplishing something. +prefer sticking to a _single_ API for accomplishing something. + +### 100 column limit +All code and docs in the repo should be 100 columns or fewer. This applies to TypeScript, SCSS, + HTML, bash scripts, and markdown files. ### TypeScript -#### Provide function descriptions -For functions that are more complicated than a simple getter/setter, provide at least a brief -sentence explaining what the function does and/or _why_ it does something. +#### Typing +Avoid `any` where possible. If you find yourself using `any`, consider whether a generic may be +appropriate in your case. + +For methods and properties that are part of a component's public API, all types must be explicitly +specified because our documentation tooling cannot currently infer types in places where TypeScript +can. + +#### Fluent APIs +When creating a fluent or builder-pattern style API, use the `this` return type for methods: +``` +class ConfigBuilder { + withName(name: string): this { + this.config.name = name; + return this; + } +} +``` + +#### Access modifiers +* Omit the `public` keyword as it is the default behavior. +* Use `private` when appropriate and possible, prefixing the name with an underscore. +* Use `protected` when appropriate and possible with no prefix. +* Prefix *library-internal* properties and methods with an underscore without using the `private` +keyword. This is necessary for anything that must be public (to be used by Angular), but should not +be part of the user-facing API. This typically applies to symbols used in template expressions, +`@ViewChildren` / `@ContentChildren` properties, host bindings, and `@Input` / `@Output` properties +(when using an alias). + +Additionally, the `@docs-private` JsDoc annotation can be used to hide any symbol from the public +API docs. + +#### JsDoc comments + +All public APIs must have user-facing comments. These are extracted and shown in the documation +on [material.angular.io](https://material.angular.io). + +Private and internal APIs should have JsDoc when they are not obvious. Ultimately it is the purview +of the code reviwer as to what is "obvious", but the rule of thumb is that *most* classes, +properties, and methods should have a JsDoc description. + +Properties should have a concise description of what the property means: +```ts + /** The label position relative to the checkbox. Defaults to 'after' */ + @Input() labelPosition: 'before' | 'after' = 'after'; +``` + +Methods blocks should describe what the function does and provide a description for each parameter +and the return value: +```ts + /** + * Opens a modal dialog containing the given component. + * @param component Type of the component to load into the dialog. + * @param config Dialog configuration options. + * @returns Reference to the newly-opened dialog. + */ + open(component: ComponentType, config?: MdDialogConfig): MdDialogRef { ... } +``` + +Boolean properties and return values should use "Whether..." as opposed to "True if...": +```ts + /** Whether the button is disabled. */ + disabled: boolean = false; +``` + +#### Naming + +##### General +* Prefer writing out words instead of using abbreviations. +* Prefer *exact* names over short names (within reason). E.g., `labelPosition` is better than +`align` because the former much more exactly communicates what the property means. +* Except for `@Input` properties, use `is` and `has` prefixes for boolean properties / methods. + +##### Classes +Classes should be named based on what they're responsible for. Names should capture what the code +*does*, not how it is used: +``` +/** NO: */ +class RadioService { } + +/** YES: */ +class UniqueSelectionDispatcher { } +``` + +Avoid suffixing a class with "Service", as it communicates nothing about what the class does. Try to +think of the class name as a person's job title. + +##### Methods +The name of a method should capture the action that is performed *by* that method. + +### Angular + +#### Host bindings +Prefer using the `host` object in the directive configuration instead of `@HostBinding` and +`@HostListener`. We do this because TypeScript preserves the type information of methods with +decorators, and when one of the arguments for the method is a native `Event` type, this preserved +type information can lead to runtime errors in non-browser environments (e.g., server-side +pre-rendering). + ### CSS @@ -83,18 +187,18 @@ elements like input and button. #### Use lowest specificity possible Always prioritize lower specificity over other factors. Most style definitions should consist of a -single element or css selector plus necessary state modifiers. Avoid SCSS nesting for the sake of -code organization. This will allow users to much more easily override styles. +single element or css selector plus necessary state modifiers. **Avoid SCSS nesting for the sake of +code organization.** This will allow users to much more easily override styles. For example, rather than doing this: ```scss -md-calendar { +.mat-calendar { display: block; - .md-month { + .mat-month { display: inline-block; - .md-date.md-selected { + .mat-date.mat-selected { font-weight: bold; } } @@ -103,15 +207,15 @@ md-calendar { do this: ```scss -md-calendar { +.mat-calendar { display: block; } -.md-calendar-month { +.mat-calendar-month { display: inline-block; } -.md-calendar-date.md-selected { +.mat-calendar-date.mat-selected { font-weight: bold; } ``` @@ -123,7 +227,7 @@ The end-user of a component should be the one to decide how much margin a compon This makes it easier to override styles when necessary. For example, rather than ```scss -:host { +the-host-element { // ... .some-child-element { @@ -134,7 +238,7 @@ This makes it easier to override styles when necessary. For example, rather than you can write ```scss -:host { +the-host-element { // ... color: red; } @@ -156,11 +260,11 @@ This is a low-effort task that makes a big difference for low-vision users. Exam When it is not super obvious, include a brief description of what a class represents. For example: ```scss // The calendar icon button used to open the calendar pane. -.md-datepicker-button { ... } +.mat-datepicker-button { ... } // Floating pane that contains the calendar at the bottom of the input. -.md-datepicker-calendar-pane { ... } +.mat-datepicker-calendar-pane { ... } // Portion of the floating panel that sits, invisibly, on top of the input. -.md-datepicker-input-mask { } +.mat-datepicker-input-mask { } ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 333a29371e5d..f868890d6724 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to Angular 2 Material +# Contributing to Angular Material -We would love for you to contribute to Angular 2 Material and help make it ever better! +We would love for you to contribute to Angular Material and help make it ever better! As a contributor, here are the guidelines we would like you to follow: - [Code of Conduct](#coc) @@ -19,7 +19,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con If you have questions about how to *use* Angular Material, please direct them to the [Google Group][material-group] discussion list or [StackOverflow][stackoverflow]. -Please note that Angular 2 Material is still in very early development, and the team's capacity +Please note that Angular Material is still in very early development, and the team's capacity to answer usage questions is limited. Community chat is also available on [Gitter][gitter]. ## Found an Issue? @@ -33,7 +33,7 @@ You can help the team even more and [submit a Pull Request](#submit-pr) with a f ## Want a Feature? You can *request* a new feature by [submitting an issue](#submit-issue) to our [GitHub Repository][github]. If you would like to *implement* a new feature, please submit an issue with -a proposal for your work first, to be sure that we can use it. Angular 2 Material is in very early +a proposal for your work first, to be sure that we can use it. Angular Material is in very early stages and we are not ready to accept major contributions ahead of the full release. Please consider what kind of change it is: diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md deleted file mode 100644 index 724dbf0cdd77..000000000000 --- a/GETTING_STARTED.md +++ /dev/null @@ -1,102 +0,0 @@ -Get started with Angular Material 2 using the Angular CLI. - -## Install the CLI - - ```bash - npm install -g angular-cli - ``` - -## Create a new project - - ```bash - ng new my-project - ``` - -The new command creates a project with a build system for your Angular app. - -## Install Angular Material components - -```bash -npm install --save @angular/material -``` - -## Import the Angular Material NgModule - -**src/app/app.module.ts** -```ts -import { MaterialModule } from '@angular/material'; -// other imports -@NgModule({ - imports: [MaterialModule.forRoot()], - ... -}) -export class PizzaPartyAppModule { } -``` - -## Include the core and theme styles: -This is **required** to apply all of the core and theme styles to your application. You can either -use a pre-built theme, or define your own custom theme. - -:trident: See the [theming guide](docs/theming.md) for instructions. - -### Additional setup for `md-slide-toggle` and `md-slider`: -The slide-toggle and slider components have a dependency on [HammerJS](http://hammerjs.github.io/). - -Add HammerJS to your application via [npm](https://www.npmjs.com/package/hammerjs), a CDN -(such as the [Google CDN](https://developers.google.com/speed/libraries/#hammerjs)), or served -directly from your app. - -#### If you want to include HammerJS from npm, you can install it: - -```bash -npm install --save hammerjs -npm install --save-dev @types/hammerjs -``` - -Import HammerJS on your app's module. - -**src/app/app.module.ts** -```ts -import 'hammerjs'; -``` - -Finally, you need to add `hammerjs` to the `types` section of your `tsconfig.json` file: - -```json -{ - "compilerOptions": { - "types": [ - "hammerjs" - ] - } -} -``` - -## Configuring SystemJS -If your project is using SystemJS for module loading, you will need to add `@angular/material` -to the SystemJS configuration: - -```js -System.config({ - // existing configuration options - map: { - ..., - '@angular/material': 'npm:@angular/material/material.umd.js' - } -}); -``` - -### [Optional] Using Material Design icons with `md-icon`: - -- If you want to use Material Design icons in addition to Angular Material components, -load the Material Design font in your `index.html`. -`md-icon` supports any font icons or svg icons, so this is only one option for an icon source. - -**src/index.html** -```html - -``` - -## Sample Angular Material 2 projects -- [Material 2 Sample App](https://github.com/jelbourn/material2-app) -- [Angular Connect 2016 Demo](https://github.com/kara/leashed-in) diff --git a/LICENSE b/LICENSE index 13a6fd789f83..47bfda24adf2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2016 Google, Inc. +Copyright (c) 2017 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ccb6e3b3c351..7a5b8ec63172 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,53 @@ -# Material Design for Angular 2 +# Material Design for Angular [![npm version](https://badge.fury.io/js/%40angular%2Fmaterial.svg)](https://www.npmjs.com/package/%40angular%2Fmaterial) [![Build Status](https://travis-ci.org/angular/material2.svg?branch=master)](https://travis-ci.org/angular/material2) [![Gitter](https://badges.gitter.im/angular/material2.svg)](https://gitter.im/angular/material2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -This is the home for the Angular team's Material Design components built on top of Angular 2. +This is the home for the Angular team's Material Design components built on top of Angular. #### Quick links [Google group](https://groups.google.com/forum/#!forum/angular-material2), [Contributing](https://github.com/angular/material2/blob/master/CONTRIBUTING.md), [Plunker Template](http://plnkr.co/edit/o077B6uEiiIgkC0S06dd?p=preview) -### Getting started +### Installation -See our [Getting Started Guide](https://github.com/angular/material2/blob/master/GETTING_STARTED.md) -if you're building your first project with Angular Material 2. +The latest release of Angular Material can be installed from npm + +`npm install --save @angular/material` + +Playing with the latest changes from [master](https://github.com/angular/material2/tree/master) is also possible + +`npm install --save https://github.com/angular/material2-builds.git` + +### Getting started + +See our [Getting Started Guide][getting-started] +if you're building your first project with Angular Material. ### Project status -Angular Material 2 is currently in alpha and under active development. -During alpha, breaking API and behavior changes will be occurring regularly. +Angular Material is currently in beta and under active development. +During beta, new features will be added regularly and APIs will evolve based on user feedback. -Check out our [directory of design documents](https://github.com/angular/material2/wiki/Design-doc-directory) +Check out our [directory of design documents](https://github.com/angular/material2/wiki/Design-doc-directory) for more insight into our process. -If you'd like to contribute, you must follow our [contributing guidelines](https://github.com/angular/material2/blob/master/CONTRIBUTING.md). -You can look through the issues (which should be up-to-date on who is working on which features -and which pieces are blocked) and make a comment. -Also see our [`Good for community contribution`](https://github.com/angular/material2/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+for+community+contribution%22) +If you'd like to contribute, you must follow our [contributing guidelines](https://github.com/angular/material2/blob/master/CONTRIBUTING.md). +You can look through the issues (which should be up-to-date on who is working on which features +and which pieces are blocked) and make a comment. +Also see our [`Good for community contribution`](https://github.com/angular/material2/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+for+community+contribution%22) label. -High level items planned for December 2016: -* Continued work on initial versions of md-autocomplete and md-chips -* Continued bug bashing -* Continued work on https://material.angular.io site -* Finish major refactoring for md-input (hit a few issues last month) -* Continued expanding e2e test coverage -* Paginated tabs. -* Scroll / resize handling for overlays -* Production build -* AoT compile e2e app -* Release beta.0 +High level items planned for next few months: +- Initial version of datepicker +- Initial version of data table +- Initial version of tree +- Select improvements (multi-select, etc) +- Screenshot test improvements +- Docs site improvements +- A11y audit +- Various bug fixes #### Feature status: @@ -62,16 +70,15 @@ High level items planned for December 2016: | button-toggle | Available | [README][15] | - | | slider | Available | [README][16] | - | | menu | Available | [README][17] | [#119][0119] | -| tooltip | Initial version, needs enhancements | [README][18] | - | +| tooltip | Available | [README][18] | - | | ripples | Available | [README][19] | [#108][0108] | | dialog | Available | [README][22] | [#114][0114] | | snackbar / toast | Available | [README][21] | [#115][0115] | -| select | Will be released in alpha.11 | - | [#118][0118] | -| textarea | Initial version, needs enhancements | - | - | -| autocomplete | Design started | - | [#117][0117] | -| chips | Started | - | [#120][0120] | +| select | Available | [README][23] | [#118][0118] | +| textarea | Available | [README][5] | - | +| autocomplete | Initial version, features evolving | [README][24] | [#117][0117] | +| chips | Initial version, features evolving | - | [#120][0120] | | theming | Available, need guidance overlays | [Guide][20] | - | -| prod build | Not started | - | - | | docs site | UX design and tooling in progress | - | - | | typography | Not started | - | [#205][0205] | | fab speed-dial | Not started | - | [#860][0860] | @@ -79,34 +86,37 @@ High level items planned for December 2016: | bottom-sheet | Not started | - | - | | bottom-nav | Not started | - | [#408][0408] | | virtual-repeat | Not started | - | [#823][0823] | -| datepicker | Not started | - | [#675][0675] | -| data-table | Not started | - | [#581][0581] | +| datepicker | In progress | - | [#675][0675] | +| data-table | Design in-progress | - | [#581][0581] | | stepper | Not started | - | [#508][0508] | -| layout | see [angular/flex-layout][lay_rp] | - | - | +| layout | See [angular/flex-layout][lay_rp] | [Wiki][0] | - | [lay_rp]: https://github.com/angular/flex-layout - [1]: https://github.com/angular/material2/blob/master/src/lib/button/README.md - [2]: https://github.com/angular/material2/blob/master/src/lib/card/README.md - [3]: https://github.com/angular/material2/blob/master/src/lib/checkbox/README.md - [4]: https://github.com/angular/material2/blob/master/src/lib/radio/README.md - [5]: https://github.com/angular/material2/blob/master/src/lib/input/README.md - [6]: https://github.com/angular/material2/blob/master/src/lib/sidenav/README.md - [7]: https://github.com/angular/material2/blob/master/src/lib/toolbar/README.md - [8]: https://github.com/angular/material2/blob/master/src/lib/list/README.md - [9]: https://github.com/angular/material2/blob/master/src/lib/grid-list/README.md -[10]: https://github.com/angular/material2/blob/master/src/lib/icon/README.md -[11]: https://github.com/angular/material2/blob/master/src/lib/progress-spinner/README.md -[12]: https://github.com/angular/material2/blob/master/src/lib/progress-bar/README.md -[13]: https://github.com/angular/material2/blob/master/src/lib/tabs/README.md -[14]: https://github.com/angular/material2/blob/master/src/lib/slide-toggle/README.md -[15]: https://github.com/angular/material2/blob/master/src/lib/button-toggle/README.md -[16]: https://github.com/angular/material2/blob/master/src/lib/slider/README.md -[17]: https://github.com/angular/material2/blob/master/src/lib/menu/README.md -[18]: https://github.com/angular/material2/blob/master/src/lib/tooltip/README.md + [0]: https://github.com/angular/flex-layout/wiki + [1]: https://material.angular.io/components/component/button + [2]: https://material.angular.io/components/component/card + [3]: https://material.angular.io/components/component/checkbox + [4]: https://material.angular.io/components/component/radio + [5]: https://material.angular.io/components/component/input + [6]: https://material.angular.io/components/component/sidenav + [7]: https://material.angular.io/components/component/toolbar + [8]: https://material.angular.io/components/component/list + [9]: https://material.angular.io/components/component/grid-list +[10]: https://material.angular.io/components/component/icon +[11]: https://material.angular.io/components/component/progress-spinner +[12]: https://material.angular.io/components/component/progress-bar +[13]: https://material.angular.io/components/component/tabs +[14]: https://material.angular.io/components/component/slide-toggle +[15]: https://material.angular.io/components/component/button-toggle +[16]: https://material.angular.io/components/component/slider +[17]: https://material.angular.io/components/component/menu +[18]: https://material.angular.io/components/component/tooltip [19]: https://github.com/angular/material2/blob/master/src/lib/core/ripple/README.md -[20]: https://github.com/angular/material2/blob/master/docs/theming.md -[21]: https://github.com/angular/material2/blob/master/src/lib/snack-bar/README.md -[22]: https://github.com/angular/material2/blob/master/src/lib/dialog/README.md +[20]: https://github.com/angular/material2/blob/master/guides/theming.md +[21]: https://material.angular.io/components/component/snack-bar +[22]: https://material.angular.io/components/component/dialog +[23]: https://material.angular.io/components/component/select +[24]: https://material.angular.io/components/component/autocomplete [0107]: https://github.com/angular/material2/issues/107 [0119]: https://github.com/angular/material2/issues/119 @@ -126,13 +136,16 @@ High level items planned for December 2016: [0675]: https://github.com/angular/material2/issues/675 [0581]: https://github.com/angular/material2/issues/581 +[getting-started]: https://github.com/angular/material2/blob/master/guides/getting-started.md +[theming]: https://github.com/angular/material2/blob/master/guides/theming.md + "Available" means that the components or feature is published and available for use, but may still be missing some behaviors or polish. ## The goal of Angular Material -Our goal is to build a set of high-quality UI components built with Angular 2 and TypeScript, -following the Material Design spec. These +Our goal is to build a set of high-quality UI components built with Angular and TypeScript, +following the Material Design spec. These components will serve as an example of how to write Angular code following best practices. ### What do we mean by "high-quality"? @@ -145,7 +158,7 @@ components will serve as an example of how to write Angular code following best * Code is clean and well-documented to serve as an example for Angular devs. ## Browser and screen reader support -Angular Material supports the most recent two versions of all major browsers: +Angular Material supports the most recent two versions of all major browsers: Chrome (including Android), Firefox, Safari (including iOS), and IE11 / Edge We also aim for great user experience with the following screen readers: diff --git a/e2e/components/button/button.e2e.ts b/e2e/components/button/button.e2e.ts index 8bbab6c135b9..91719f606425 100644 --- a/e2e/components/button/button.e2e.ts +++ b/e2e/components/button/button.e2e.ts @@ -1,17 +1,24 @@ -import {browser, by, element} from 'protractor'; +import {browser, by, element, ExpectedConditions} from 'protractor'; +import {screenshot} from '../../screenshot'; -describe('button', function () { - describe('disabling behavior', function () { - beforeEach(function() { - browser.get('/button'); - }); - it('should prevent click handlers from executing when disabled', function () { + +describe('button', () => { + describe('disabling behavior', () => { + beforeEach(() => browser.get('/button')); + + it('should prevent click handlers from executing when disabled', () => { element(by.id('test-button')).click(); expect(element(by.id('click-counter')).getText()).toEqual('1'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('clicked once')); element(by.id('disable-toggle')).click(); element(by.id('test-button')).click(); expect(element(by.id('click-counter')).getText()).toEqual('1'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('click disabled')); }); }); }); diff --git a/e2e/components/checkbox/checkbox.e2e.ts b/e2e/components/checkbox/checkbox.e2e.ts index 18e2d434c348..8c81f63100b0 100644 --- a/e2e/components/checkbox/checkbox.e2e.ts +++ b/e2e/components/checkbox/checkbox.e2e.ts @@ -1,19 +1,47 @@ -import {browser, by, element} from 'protractor'; +import {browser, by, element, Key, ExpectedConditions} from 'protractor'; +import {screenshot} from '../../screenshot'; describe('checkbox', function () { + describe('check behavior', function () { + beforeEach(function() { browser.get('/checkbox'); }); - it('should be checked when clicked, and be unchecked when clicked again', function () { - element(by.id('test-checkbox')).click(); - element(by.css('input[id=input-test-checkbox]')).getAttribute('checked').then((value: string) => { + + it('should be checked when clicked, and be unchecked when clicked again', () => { + let checkboxEl = element(by.id('test-checkbox')); + let inputEl = element(by.css('input[id=input-test-checkbox]')); + + screenshot('start'); + checkboxEl.click(); + inputEl.getAttribute('checked').then((value: string) => { expect(value).toBeTruthy('Expect checkbox "checked" property to be true'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('checked')); }); - element(by.id('test-checkbox')).click(); - element(by.css('input[id=input-test-checkbox]')).getAttribute('checked').then((value: string) => { + checkboxEl.click(); + inputEl.getAttribute('checked').then((value: string) => { expect(value).toBeFalsy('Expect checkbox "checked" property to be false'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('unchecked')); + }); + }); + + it('should toggle the checkbox when pressing space', () => { + let inputEl = element(by.css('input[id=input-test-checkbox]')); + + inputEl.getAttribute('checked').then((value: string) => { + expect(value).toBeFalsy('Expect checkbox "checked" property to be false'); + }); + + inputEl.sendKeys(Key.SPACE); + + inputEl.getAttribute('checked').then((value: string) => { + expect(value).toBeTruthy('Expect checkbox "checked" property to be true'); }); }); }); diff --git a/e2e/components/dialog/dialog.e2e.ts b/e2e/components/dialog/dialog.e2e.ts index 74f1b397c1aa..7a9d862384c0 100644 --- a/e2e/components/dialog/dialog.e2e.ts +++ b/e2e/components/dialog/dialog.e2e.ts @@ -1,11 +1,20 @@ -import {browser, by, element, Key, ProtractorBy} from 'protractor'; +import {browser, by, element, Key} from 'protractor'; +import {expectToExist, expectFocusOn} from '../../util/asserts'; +import {pressKeys, clickElementAtPoint} from '../../util/actions'; +import {waitForElement} from '../../util/query'; describe('dialog', () => { beforeEach(() => browser.get('/dialog')); it('should open a dialog', () => { element(by.id('default')).click(); - waitForDialog().then((isPresent: boolean) => expect(isPresent).toBe(true)); + expectToExist('md-dialog-container'); + }); + + it('should open a template dialog', () => { + expectToExist('.my-template-dialog', false); + element(by.id('template')).click(); + expectToExist('.my-template-dialog'); }); it('should close by clicking on the backdrop', () => { @@ -13,7 +22,7 @@ describe('dialog', () => { waitForDialog().then(() => { clickOnBackrop(); - waitForDialog().then((isPresent: boolean) => expect(isPresent).toBe(false)); + expectToExist('md-dialog-container', false); }); }); @@ -21,8 +30,18 @@ describe('dialog', () => { element(by.id('default')).click(); waitForDialog().then(() => { - pressEscape(); - waitForDialog().then((isPresent: boolean) => expect(isPresent).toBe(false)); + pressKeys(Key.ESCAPE); + expectToExist('md-dialog-container', false); + }); + }); + + it('should close by pressing escape when the first tabbable element has lost focus', () => { + element(by.id('default')).click(); + + waitForDialog().then(() => { + clickElementAtPoint('md-dialog-container', { x: 0, y: 0 }); + pressKeys(Key.ESCAPE); + expectToExist('md-dialog-container', false); }); }); @@ -31,7 +50,7 @@ describe('dialog', () => { waitForDialog().then(() => { element(by.id('close')).click(); - waitForDialog().then((isPresent: boolean) => expect(isPresent).toBe(false)); + expectToExist('md-dialog-container', false); }); }); @@ -39,7 +58,7 @@ describe('dialog', () => { element(by.id('default')).click(); waitForDialog().then(() => { - expectFocusOn(element(by.css('md-dialog-container input'))); + expectFocusOn('md-dialog-container input'); }); }); @@ -60,8 +79,8 @@ describe('dialog', () => { waitForDialog().then(() => { let tab = Key.TAB; - browser.actions().sendKeys(tab, tab, tab).perform(); - expectFocusOn(element(by.id('close'))); + pressKeys(tab, tab, tab); + expectFocusOn('#close'); }); }); @@ -70,7 +89,7 @@ describe('dialog', () => { waitForDialog().then(() => { clickOnBackrop(); - waitForDialog().then((isPresent: boolean) => expect(isPresent).toBe(true)); + expectToExist('md-dialog-container'); }); }); @@ -78,30 +97,16 @@ describe('dialog', () => { element(by.id('disabled')).click(); waitForDialog().then(() => { - pressEscape(); - waitForDialog().then((isPresent: boolean) => expect(isPresent).toBe(true)); + pressKeys(Key.ESCAPE); + expectToExist('md-dialog-container'); }); }); function waitForDialog() { - return browser.isElementPresent(by.css('md-dialog-container') as ProtractorBy); + return waitForElement('md-dialog-container'); } function clickOnBackrop() { - browser.actions() - // We need to move the cursor to the top left so - // the dialog doesn't receive the click accidentally. - .mouseMove(element(by.css('.cdk-overlay-backdrop')).getWebElement(), { x: 0, y: 0 }) - .click() - .perform(); - } - - function pressEscape() { - browser.actions().sendKeys(Key.ESCAPE).perform(); - } - - // TODO(crisbeto): should be moved to a common util. copied from the menu e2e setup. - function expectFocusOn(el: any): void { - expect(browser.driver.switchTo().activeElement().getInnerHtml()).toBe(el.getInnerHtml()); + clickElementAtPoint('.cdk-overlay-backdrop', { x: 0, y: 0 }); } }); diff --git a/e2e/components/fullscreen/fullscreen.e2e.ts b/e2e/components/fullscreen/fullscreen.e2e.ts new file mode 100644 index 000000000000..a9f4a8535c65 --- /dev/null +++ b/e2e/components/fullscreen/fullscreen.e2e.ts @@ -0,0 +1,40 @@ +import {browser, by, element} from 'protractor'; + +describe('fullscreen', () => { + + beforeEach(() => browser.get('/fullscreen')); + + it('should open a dialog inside a fullscreen element and move it to the document body', () => { + element(by.id('fullscreen-open')).click(); + element(by.id('dialog-open')).click(); + + expectOverlayInFullscreen(); + + element(by.id('dialog-fullscreen-exit')).click(); + expectOverlayInBody(); + }); + + it('should open a dialog inside the document body and move it to a fullscreen element', () => { + element(by.id('dialog-open')).click(); + expectOverlayInBody(); + + element(by.id('dialog-fullscreen-open')).click(); + expectOverlayInFullscreen(); + + element(by.id('dialog-fullscreen-exit')).click(); + expectOverlayInBody(); + }); + + /** Expects the overlay container to be inside of the body element. */ + function expectOverlayInBody() { + expect(browser.isElementPresent(by.css('body > .cdk-overlay-container'))) + .toBe(true, 'Expected the overlay container to be inside of the body.'); + } + + /** Expects the overlay container to be in fullscreen mode. */ + function expectOverlayInFullscreen() { + expect(browser.isElementPresent(by.css('#fullscreen-pane > .cdk-overlay-container'))) + .toBe(true, 'Expected the overlay container to be in fullscreen mode.'); + } + +}); diff --git a/e2e/components/grid-list/grid-list.e2e.ts b/e2e/components/grid-list/grid-list.e2e.ts index 64c4647fde5a..848f2b04a3cb 100644 --- a/e2e/components/grid-list/grid-list.e2e.ts +++ b/e2e/components/grid-list/grid-list.e2e.ts @@ -1,14 +1,16 @@ -import {browser, by, element} from 'protractor'; +import {browser} from 'protractor'; +import {expectToExist} from '../../util/asserts'; +import {screenshot} from '../../screenshot'; describe('grid-list', () => { beforeEach(() => browser.get('/grid-list')); it('should render a grid list container', () => { - expect(element(by.css('md-grid-list')).isPresent()).toBe(true); + expectToExist('md-grid-list'); + screenshot(); }); it('should render list items inside the grid list container', () => { - let container = element(by.css('md-grid-list')); - expect(container.isElementPresent(by.css('md-grid-tile'))).toBe(true); + expectToExist('md-grid-list md-grid-tile'); }); }); diff --git a/e2e/components/icon/icon.e2e.ts b/e2e/components/icon/icon.e2e.ts index e0cb1caa6910..1407f517d1ef 100644 --- a/e2e/components/icon/icon.e2e.ts +++ b/e2e/components/icon/icon.e2e.ts @@ -1,4 +1,5 @@ import {browser, by, element} from 'protractor'; +import {screenshot} from '../../screenshot'; describe('icon', () => { describe('font icons by ligature', () => { @@ -13,6 +14,7 @@ describe('icon', () => { testIcon.getAttribute('aria-label').then((attr: string) => { expect(attr).toEqual('favorite'); }); + screenshot(); }); it('should have the correct class when used', () => { diff --git a/e2e/components/list/list.e2e.ts b/e2e/components/list/list.e2e.ts index 329f8a8dd19c..86da9dba1246 100644 --- a/e2e/components/list/list.e2e.ts +++ b/e2e/components/list/list.e2e.ts @@ -1,14 +1,16 @@ -import {browser, by, element} from 'protractor'; +import {browser} from 'protractor'; +import {expectToExist} from '../../util/asserts'; +import {screenshot} from '../../screenshot'; describe('list', () => { beforeEach(() => browser.get('/list')); it('should render a list container', () => { - expect(element(by.css('md-list')).isPresent()).toBe(true); + expectToExist('md-list'); + screenshot(); }); it('should render list items inside the list container', () => { - let container = element(by.css('md-list')); - expect(container.isElementPresent(by.css('md-list-item'))).toBe(true); + expectToExist('md-list md-list-item'); }); }); diff --git a/e2e/components/menu/menu-page.ts b/e2e/components/menu/menu-page.ts index 7100f0d494f1..0d5dc6f82142 100644 --- a/e2e/components/menu/menu-page.ts +++ b/e2e/components/menu/menu-page.ts @@ -1,69 +1,33 @@ -import {browser, by, element, ElementFinder, ProtractorBy} from 'protractor'; +import {browser, by, element, ElementFinder} from 'protractor'; export class MenuPage { + constructor() { browser.get('/menu'); } - constructor() { - browser.get('/menu'); - } + menu(): ElementFinder { return element(by.css('.mat-menu-panel')); } - menu() { return element(by.css('.md-menu-panel')); } + start(): ElementFinder { return element(by.id('start')); } - start() { return element(by.id('start')); } + trigger(): ElementFinder { return element(by.id('trigger')); } - trigger() { return element(by.id('trigger')); } + triggerTwo(): ElementFinder { return element(by.id('trigger-two')); } - triggerTwo() { return element(by.id('trigger-two')); } + backdrop(): ElementFinder { return element(by.css('.cdk-overlay-backdrop')); } - backdrop() { return element(by.css('.cdk-overlay-backdrop')); } + items(index: number): ElementFinder { return element.all(by.css('[md-menu-item]')).get(index); } - items(index: number) { return element.all(by.css('[md-menu-item]')).get(index); } + textArea(): ElementFinder { return element(by.id('text')); } - textArea() { return element(by.id('text')); } + beforeTrigger(): ElementFinder { return element(by.id('before-t')); } - beforeTrigger() { return element(by.id('before-t')); } + aboveTrigger(): ElementFinder { return element(by.id('above-t')); } - aboveTrigger() { return element(by.id('above-t')); } + combinedTrigger(): ElementFinder { return element(by.id('combined-t')); } - combinedTrigger() { return element(by.id('combined-t')); } + beforeMenu(): ElementFinder { return element(by.css('.mat-menu-panel.before')); } - beforeMenu() { return element(by.css('.md-menu-panel.before')); } + aboveMenu(): ElementFinder { return element(by.css('.mat-menu-panel.above')); } - aboveMenu() { return element(by.css('.md-menu-panel.above')); } + combinedMenu(): ElementFinder { return element(by.css('.mat-menu-panel.combined')); } - combinedMenu() { return element(by.css('.md-menu-panel.combined')); } - - // TODO(kara): move to common testing utility - pressKey(key: string): void { - browser.actions().sendKeys(key).perform(); - } - - // TODO(kara): move to common testing utility - expectFocusOn(el: any): void { - expect(browser.driver.switchTo().activeElement().getInnerHtml()) - .toBe(el.getInnerHtml()); - } - - expectMenuPresent(expected: boolean) { - return browser.isElementPresent(by.css('.md-menu-panel') as ProtractorBy) - .then((isPresent: boolean) => { - expect(isPresent).toBe(expected); - }); - } - - expectMenuLocation(el: ElementFinder, {x, y}: {x: number, y: number}) { - el.getLocation().then(loc => { - expect(loc.x).toEqual(x, 'Expect the x-position to be equal'); - expect(loc.y).toEqual(y, 'Expect the y-position to be equal'); - }); - } - - expectMenuAlignedWith(el: ElementFinder, id: string) { - element(by.id(id)).getLocation().then(loc => { - this.expectMenuLocation(el, {x: loc.x, y: loc.y}); - }); - } - - getResultText() { - return this.textArea().getText(); - } + getResultText() { return this.textArea().getText(); } } diff --git a/e2e/components/menu/menu.e2e.ts b/e2e/components/menu/menu.e2e.ts index 79c3c394c77c..26b3e4dbb431 100644 --- a/e2e/components/menu/menu.e2e.ts +++ b/e2e/components/menu/menu.e2e.ts @@ -1,65 +1,72 @@ -import {browser, Key, protractor} from 'protractor'; +import {Key, protractor} from 'protractor'; import {MenuPage} from './menu-page'; +import {expectToExist, expectAlignedWith, expectFocusOn, expectLocation} from '../../util/asserts'; +import {pressKeys} from '../../util/actions'; +import {screenshot} from '../../screenshot'; describe('menu', () => { + const menuSelector = '.mat-menu-panel'; let page: MenuPage; - beforeEach(function() { - page = new MenuPage(); - }); + beforeEach(() => page = new MenuPage()); it('should open menu when the trigger is clicked', () => { - page.expectMenuPresent(false); + expectToExist(menuSelector, false); page.trigger().click(); - page.expectMenuPresent(true); + expectToExist(menuSelector); expect(page.menu().getText()).toEqual('One\nTwo\nThree\nFour'); + screenshot(); }); it('should close menu when menu item is clicked', () => { page.trigger().click(); page.items(0).click(); - page.expectMenuPresent(false); + expectToExist(menuSelector, false); + screenshot(); }); it('should run click handlers on regular menu items', () => { page.trigger().click(); page.items(0).click(); expect(page.getResultText()).toEqual('one'); + screenshot('one'); page.trigger().click(); page.items(1).click(); expect(page.getResultText()).toEqual('two'); + screenshot('two'); }); it('should run not run click handlers on disabled menu items', () => { page.trigger().click(); page.items(2).click(); expect(page.getResultText()).toEqual(''); + screenshot(); }); it('should support multiple triggers opening the same menu', () => { page.triggerTwo().click(); + expect(page.menu().getText()).toEqual('One\nTwo\nThree\nFour'); - page.expectMenuAlignedWith(page.menu(), 'trigger-two'); + expectAlignedWith(page.menu(), '#trigger-two'); page.backdrop().click(); - page.expectMenuPresent(false); + expectToExist(menuSelector, false); - // TODO(kara): temporary, remove when #1607 is fixed - browser.sleep(250); page.trigger().click(); + expect(page.menu().getText()).toEqual('One\nTwo\nThree\nFour'); - page.expectMenuAlignedWith(page.menu(), 'trigger'); + expectAlignedWith(page.menu(), '#trigger'); page.backdrop().click(); - page.expectMenuPresent(false); + expectToExist(menuSelector, false); }); it('should mirror classes on host to menu template in overlay', () => { page.trigger().click(); page.menu().getAttribute('class').then((classes: string) => { - expect(classes).toContain('md-menu-panel custom'); + expect(classes).toContain('mat-menu-panel custom'); }); }); @@ -67,82 +74,71 @@ describe('menu', () => { beforeEach(() => { // click start button to avoid tabbing past navigation page.start().click(); - page.pressKey(Key.TAB); + pressKeys(Key.TAB); }); it('should auto-focus the first item when opened with ENTER', () => { - page.pressKey(Key.ENTER); - page.expectFocusOn(page.items(0)); + pressKeys(Key.ENTER); + expectFocusOn(page.items(0)); }); it('should auto-focus the first item when opened with SPACE', () => { - page.pressKey(Key.SPACE); - page.expectFocusOn(page.items(0)); + pressKeys(Key.SPACE); + expectFocusOn(page.items(0)); }); it('should not focus the first item when opened with mouse', () => { page.trigger().click(); - page.expectFocusOn(page.trigger()); + expectFocusOn(page.trigger()); }); it('should focus subsequent items when down arrow is pressed', () => { - page.pressKey(Key.ENTER); - page.pressKey(Key.DOWN); - page.expectFocusOn(page.items(1)); + pressKeys(Key.ENTER, Key.DOWN); + expectFocusOn(page.items(1)); }); it('should focus previous items when up arrow is pressed', () => { - page.pressKey(Key.ENTER); - page.pressKey(Key.DOWN); - page.pressKey(Key.UP); - page.expectFocusOn(page.items(0)); + pressKeys(Key.ENTER, Key.DOWN, Key.UP); + expectFocusOn(page.items(0)); }); it('should skip disabled items using arrow keys', () => { - page.pressKey(Key.ENTER); - page.pressKey(Key.DOWN); - page.pressKey(Key.DOWN); - page.expectFocusOn(page.items(3)); + pressKeys(Key.ENTER, Key.DOWN, Key.DOWN); + expectFocusOn(page.items(3)); - page.pressKey(Key.UP); - page.expectFocusOn(page.items(1)); + pressKeys(Key.UP); + expectFocusOn(page.items(1)); }); it('should close the menu when tabbing past items', () => { - page.pressKey(Key.ENTER); - page.pressKey(Key.TAB); - page.expectMenuPresent(false); + pressKeys(Key.ENTER, Key.TAB); + expectToExist(menuSelector, false); - page.pressKey(Key.TAB); - page.pressKey(Key.ENTER); - page.expectMenuPresent(true); + pressKeys(Key.TAB, Key.ENTER); + expectToExist(menuSelector); - page.pressKey(protractor.Key.chord(Key.SHIFT, Key.TAB)); - page.expectMenuPresent(false); + pressKeys(protractor.Key.chord(Key.SHIFT, Key.TAB)); + expectToExist(menuSelector, false); }); it('should wrap back to menu when arrow keying past items', () => { - page.pressKey(Key.ENTER); - page.pressKey(Key.DOWN); - page.pressKey(Key.DOWN); - page.pressKey(Key.DOWN); - page.expectFocusOn(page.items(0)); - - page.pressKey(Key.UP); - page.expectFocusOn(page.items(3)); + let down = Key.DOWN; + pressKeys(Key.ENTER, down, down, down); + expectFocusOn(page.items(0)); + + pressKeys(Key.UP); + expectFocusOn(page.items(3)); }); it('should focus before and after trigger when tabbing past items', () => { - page.pressKey(Key.ENTER); - page.pressKey(Key.TAB); - page.expectFocusOn(page.triggerTwo()); + let shiftTab = protractor.Key.chord(Key.SHIFT, Key.TAB); - // navigate back to trigger - page.pressKey(protractor.Key.chord(protractor.Key.SHIFT, protractor.Key.TAB)); - page.pressKey(Key.ENTER); + pressKeys(Key.ENTER, Key.TAB); + expectFocusOn(page.triggerTwo()); - page.pressKey(protractor.Key.chord(protractor.Key.SHIFT, protractor.Key.TAB)); - page.expectFocusOn(page.start()); + // navigate back to trigger + pressKeys(shiftTab, Key.ENTER, shiftTab); + expectFocusOn(page.start()); }); }); @@ -153,7 +149,7 @@ describe('menu', () => { page.trigger().click(); // menu.x should equal trigger.x, menu.y should equal trigger.y - page.expectMenuAlignedWith(page.menu(), 'trigger'); + expectAlignedWith(page.menu(), '#trigger'); }); it('should align overlay end to origin end when x-position is "before"', () => { @@ -164,7 +160,7 @@ describe('menu', () => { // menu = 112px wide. trigger = 60px wide. 112 - 60 = 52px of menu to the left of trigger. // trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x (left corner) // menu.y should equal trigger.y because only x position has changed. - page.expectMenuLocation(page.beforeMenu(), {x: trigger.x - 52, y: trigger.y}); + expectLocation(page.beforeMenu(), {x: trigger.x - 52, y: trigger.y}); }); }); @@ -176,7 +172,7 @@ describe('menu', () => { // menu.x should equal trigger.x because only y position has changed. // menu = 64px high. trigger = 20px high. 64 - 20 = 44px of menu extending up past trigger. // trigger.y (top corner) - 44px (menu above trigger) = expected menu.y (top corner) - page.expectMenuLocation(page.aboveMenu(), {x: trigger.x, y: trigger.y - 44}); + expectLocation(page.aboveMenu(), {x: trigger.x, y: trigger.y - 44}); }); }); @@ -186,7 +182,7 @@ describe('menu', () => { // trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x // trigger.y (top corner) - 44px (menu above trigger) = expected menu.y - page.expectMenuLocation(page.combinedMenu(), {x: trigger.x - 52, y: trigger.y - 44}); + expectLocation(page.combinedMenu(), {x: trigger.x - 52, y: trigger.y - 44}); }); }); diff --git a/e2e/components/progress-bar/progress-bar.e2e.ts b/e2e/components/progress-bar/progress-bar.e2e.ts index 33c5898400b8..7bc4eec54c3e 100644 --- a/e2e/components/progress-bar/progress-bar.e2e.ts +++ b/e2e/components/progress-bar/progress-bar.e2e.ts @@ -1,25 +1,22 @@ -import {browser, by, element} from 'protractor'; +import {browser} from 'protractor'; +import {expectToExist} from '../../util/asserts'; describe('progress-bar', () => { beforeEach(() => browser.get('/progress-bar')); it('should render a determinate progress bar', () => { - shouldExist('md-progress-bar[mode="determinate"]'); + expectToExist('md-progress-bar[mode="determinate"]'); }); it('should render a buffer progress bar', () => { - shouldExist('md-progress-bar[mode="buffer"]'); + expectToExist('md-progress-bar[mode="buffer"]'); }); it('should render a query progress bar', () => { - shouldExist('md-progress-bar[mode="query"]'); + expectToExist('md-progress-bar[mode="query"]'); }); it('should render a indeterminate progress bar', () => { - shouldExist('md-progress-bar[mode="indeterminate"]'); + expectToExist('md-progress-bar[mode="indeterminate"]'); }); - - function shouldExist(selector: string): void { - expect(element(by.css(selector)).isPresent()).toBe(true); - } }); diff --git a/e2e/components/radio/radio.e2e.ts b/e2e/components/radio/radio.e2e.ts index 37c81509dc08..a2a5188838d2 100644 --- a/e2e/components/radio/radio.e2e.ts +++ b/e2e/components/radio/radio.e2e.ts @@ -1,16 +1,17 @@ -import {browser, by, element} from 'protractor'; +import {browser, by, element, ExpectedConditions} from 'protractor'; +import {screenshot} from '../../screenshot'; -describe('radio', function () { +describe('radio', () => { + describe('disabling behavior', () => { + beforeEach(() => browser.get('/radio')); - describe('disabling behavior', function () { - beforeEach(function() { - browser.get('/radio'); - }); - - it('should be checked when clicked', function () { + it('should be checked when clicked', () => { element(by.id('water')).click(); element(by.id('water')).getAttribute('class').then((value: string) => { - expect(value).toContain('md-radio-checked'); + expect(value).toContain('mat-radio-checked'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('water')); }); element(by.css('input[id=water-input]')).getAttribute('checked').then((value: string) => { expect(value).toBeTruthy(); @@ -21,7 +22,10 @@ describe('radio', function () { element(by.id('leaf')).click(); element(by.id('leaf')).getAttribute('class').then((value: string) => { - expect(value).toContain('md-radio-checked'); + expect(value).toContain('mat-radio-checked'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('leaf')); }); element(by.css('input[id=leaf-input]')).getAttribute('checked').then((value: string) => { expect(value).toBeTruthy(); @@ -31,19 +35,24 @@ describe('radio', function () { }); }); - it('should be disabled when disable the radio group', function () { + it('should be disabled when disable the radio group', () => { element(by.id('toggle-disable')).click(); element(by.id('water')).click(); element(by.id('water')).getAttribute('class').then((value: string) => { - expect(value).toContain('md-radio-disabled'); + expect(value).toContain('mat-radio-disabled'); + browser.wait(ExpectedConditions.presenceOf(element(by.css('.mat-radio-disabled')))) + .then(() => screenshot('water')); }); - element(by.css('input[id=water-input]')).getAttribute('disabled').then((value: string) => { + element(by.css('input[id=water-input]')).getAttribute('disabled').then((value: string) => { expect(value).toBeTruthy(); }); element(by.id('leaf')).click(); element(by.id('leaf')).getAttribute('class').then((value: string) => { - expect(value).toContain('md-radio-disabled'); + expect(value).toContain('mat-radio-disabled'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('leaf')); }); element(by.css('input[id=leaf-input]')).getAttribute('disabled').then((value: string) => { expect(value).toBeTruthy(); diff --git a/e2e/components/slide-toggle/slide-toggle.e2e.ts b/e2e/components/slide-toggle/slide-toggle.e2e.ts index c68610615a6a..38808fed43c4 100644 --- a/e2e/components/slide-toggle/slide-toggle.e2e.ts +++ b/e2e/components/slide-toggle/slide-toggle.e2e.ts @@ -1,49 +1,60 @@ -import {browser, element, by, protractor} from 'protractor'; +import {browser, element, by, Key, ExpectedConditions} from 'protractor'; +import {expectToExist} from '../../util/asserts'; +import {screenshot} from '../../screenshot'; describe('slide-toggle', () => { + const getInput = () => element(by.css('#normal-slide-toggle input')); + const getNormalToggle = () => element(by.css('#normal-slide-toggle')); beforeEach(() => browser.get('slide-toggle')); it('should render a slide-toggle', () => { - expect(element(by.css('md-slide-toggle')).isPresent()).toBe(true); + expectToExist('md-slide-toggle'); + screenshot(); }); it('should change the checked state on click', () => { - let slideToggleEl = element(by.css('#normal-slide-toggle')); - let inputEl = element(by.css('#normal-slide-toggle input')); + let inputEl = getInput(); expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); - slideToggleEl.click(); + getNormalToggle().click(); expect(inputEl.getAttribute('checked')).toBeTruthy('Expect slide-toggle to be checked'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot()); }); it('should change the checked state on click', () => { - let slideToggleEl = element(by.css('#normal-slide-toggle')); - let inputEl = element(by.css('#normal-slide-toggle input')); + let inputEl = getInput(); expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); - slideToggleEl.click(); + getNormalToggle().click(); expect(inputEl.getAttribute('checked')).toBeTruthy('Expect slide-toggle to be checked'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot()); }); it('should not change the checked state on click when disabled', () => { - let slideToggleEl = element(by.css('#disabled-slide-toggle')); - let inputEl = element(by.css('#disabled-slide-toggle input')); + let inputEl = getInput(); expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); - slideToggleEl.click(); + element(by.css('#disabled-slide-toggle')).click(); expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot()); }); it('should move the thumb on state change', () => { - let slideToggleEl = element(by.css('#normal-slide-toggle')); - let thumbEl = element(by.css('#normal-slide-toggle .md-slide-toggle-thumb-container')); + let slideToggleEl = getNormalToggle(); + let thumbEl = element(by.css('#normal-slide-toggle .mat-slide-toggle-thumb-container')); let previousX = thumbEl.getLocation().then(pos => pos.x); @@ -52,14 +63,17 @@ describe('slide-toggle', () => { let newX = thumbEl.getLocation().then(pos => pos.x); expect(previousX).not.toBe(newX); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot()); }); it('should toggle the slide-toggle on space key', () => { - let inputEl = element(by.css('#normal-slide-toggle input')); + let inputEl = getInput(); expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); - inputEl.sendKeys(protractor.Key.SPACE); + inputEl.sendKeys(Key.SPACE); expect(inputEl.getAttribute('checked')).toBeTruthy('Expect slide-toggle to be checked'); }); diff --git a/e2e/components/tabs/tabs.e2e.ts b/e2e/components/tabs/tabs.e2e.ts index 9a9fd53a3032..4092aa2b5c4f 100644 --- a/e2e/components/tabs/tabs.e2e.ts +++ b/e2e/components/tabs/tabs.e2e.ts @@ -1,4 +1,14 @@ -import {browser, by, element, ElementArrayFinder, ElementFinder, Key} from 'protractor'; +import { + browser, + by, + element, + ElementArrayFinder, + ElementFinder, + Key, + ExpectedConditions +} from 'protractor'; +import {pressKeys} from '../../util/actions'; +import {screenshot} from '../../screenshot'; describe('tabs', () => { describe('basic behavior', () => { @@ -9,7 +19,7 @@ describe('tabs', () => { beforeEach(() => { browser.get('/tabs'); tabGroup = element(by.css('md-tab-group')); - tabLabels = element.all(by.css('.md-tab-label')); + tabLabels = element.all(by.css('.mat-tab-label')); tabBodies = element.all(by.css('md-tab-body')); }); @@ -17,42 +27,46 @@ describe('tabs', () => { tabLabels.get(1).click(); expect(getLabelActiveStates(tabLabels)).toEqual([false, true, false]); expect(getBodyActiveStates(tabBodies)).toEqual([false, true, false]); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('click1')); tabLabels.get(0).click(); expect(getLabelActiveStates(tabLabels)).toEqual([true, false, false]); expect(getBodyActiveStates(tabBodies)).toEqual([true, false, false]); + browser.wait(ExpectedConditions.not( + ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))) + .then(() => screenshot('click0')); }); it('should change focus with keyboard interaction', () => { + let right = Key.RIGHT; + let left = Key.LEFT; + tabLabels.get(0).click(); expect(getFocusStates(tabLabels)).toEqual([true, false, false]); - pressKey(Key.RIGHT); + pressKeys(right); expect(getFocusStates(tabLabels)).toEqual([false, true, false]); - pressKey(Key.RIGHT); + pressKeys(right); expect(getFocusStates(tabLabels)).toEqual([false, false, true]); - pressKey(Key.RIGHT); + pressKeys(right); expect(getFocusStates(tabLabels)).toEqual([false, false, true]); - pressKey(Key.LEFT); + pressKeys(left); expect(getFocusStates(tabLabels)).toEqual([false, true, false]); - pressKey(Key.LEFT); + pressKeys(left); expect(getFocusStates(tabLabels)).toEqual([true, false, false]); - pressKey(Key.LEFT); + pressKeys(left); expect(getFocusStates(tabLabels)).toEqual([true, false, false]); }); }); }); -/** A helper function to perform the sendKey action. */ -function pressKey(key: string) { - browser.actions().sendKeys(key).perform(); -} - /** * Returns an array of true/false that represents the focus states of the provided elements. */ @@ -68,12 +82,12 @@ function getFocusStates(elements: ElementArrayFinder) { /** Returns an array of true/false that represents the active states for the provided elements. */ function getLabelActiveStates(elements: ElementArrayFinder) { - return getClassStates(elements, 'md-tab-label-active'); + return getClassStates(elements, 'mat-tab-label-active'); } /** Returns an array of true/false that represents the active states for the provided elements */ function getBodyActiveStates(elements: ElementArrayFinder) { - return getClassStates(elements, 'md-tab-body-active'); + return getClassStates(elements, 'mat-tab-body-active'); } /** diff --git a/e2e/index.e2e.ts b/e2e/index.e2e.ts index dded58a17bae..36b27f6bd962 100644 --- a/e2e/index.e2e.ts +++ b/e2e/index.e2e.ts @@ -4,7 +4,7 @@ describe('hello, protractor', function () { describe('index', function () { browser.get('/'); it('should have a title', function () { - expect(browser.getTitle()).toBe('Material2'); + expect(browser.getTitle()).toBe('Angular Material'); }); }); }); diff --git a/e2e/screenshot.ts b/e2e/screenshot.ts new file mode 100644 index 000000000000..f6a2d5af0901 --- /dev/null +++ b/e2e/screenshot.ts @@ -0,0 +1,61 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import {browser} from 'protractor'; + +const OUTPUT_DIR = './screenshots/'; +const HEIGHT = 768; +const WIDTH = 1024; + +let currentJasmineSpecName = ''; + +/** Adds a custom jasmine reporter that simply keeps track of the current test name. */ +function initializeEnvironment(jasmine: any) { + browser.manage().window().setSize(WIDTH, HEIGHT); + let reporter = new jasmine.JsApiReporter({}); + reporter.specStarted = function(result: any) { + currentJasmineSpecName = result.fullName; + }; + jasmine.getEnv().addReporter(reporter); +} + +initializeEnvironment(jasmine); + +export class Screenshot { + id: string; + + /** The filename used to store the screenshot. */ + get filename(): string { + return this.id + .toLowerCase() + .replace(/\s/g, '_') + .replace(/[^/a-z0-9_]+/g, '') + + '.screenshot.png'; + } + + /** The full path to the screenshot */ + get fullPath(): string { + return path.resolve(OUTPUT_DIR, this.filename); + } + + constructor(id?: string) { + this.id = id ? `${currentJasmineSpecName} ${id}` : currentJasmineSpecName; + browser.takeScreenshot().then(png => this.storeScreenshot(png)); + } + + /** Replaces the existing screenshot with the newly generated one. */ + storeScreenshot(png: any) { + if (!fs.existsSync(OUTPUT_DIR)) { + fs.mkdirSync(OUTPUT_DIR, '744'); + } + + if (fs.existsSync(OUTPUT_DIR)) { + fs.writeFileSync(this.fullPath, png, {encoding: 'base64' }); + } + } +} + +export function screenshot(id?: string) { + if (process.env['TRAVIS']) { + return new Screenshot(id); + } +} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 06f19589b388..a4130cff60a6 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -13,7 +13,7 @@ "sourceMap": true, "target": "es5", "typeRoots": [ - "../node_modules/@types/" + "../node_modules/@types" ], "types": [ "jasmine" diff --git a/e2e/util/actions.ts b/e2e/util/actions.ts new file mode 100644 index 000000000000..8199481a8951 --- /dev/null +++ b/e2e/util/actions.ts @@ -0,0 +1,21 @@ +import {browser} from 'protractor'; +import {getElement, FinderResult} from './query'; + +/** + * Presses a single key or a sequence of keys. + */ +export function pressKeys(...keys: string[]): void { + let actions = browser.actions(); + actions.sendKeys.call(actions, keys).perform(); +} + +/** + * Clicks an element at a specific point. Useful if there's another element + * that covers part of the target and can catch the click. + */ +export function clickElementAtPoint(element: FinderResult, coords: Point): void { + let webElement = getElement(element).getWebElement(); + browser.actions().mouseMove(webElement, coords).click().perform(); +} + +export interface Point { x: number; y: number; } diff --git a/e2e/util/asserts.ts b/e2e/util/asserts.ts new file mode 100644 index 000000000000..dc624c00d579 --- /dev/null +++ b/e2e/util/asserts.ts @@ -0,0 +1,39 @@ +import {browser} from 'protractor'; +import {getElement, FinderResult, waitForElement} from './query'; +import {Point} from './actions'; + +/** + * Asserts that an element exists. + */ +export function expectToExist(selector: string, expected = true) { + return waitForElement(selector).then((isPresent: boolean) => { + expect(isPresent).toBe(expected, `Expected "${selector}"${expected ? '' : ' not'} to exist`); + }); +} + +/** + * Asserts that an element is focused. + */ +export function expectFocusOn(element: FinderResult, expected = true): void { + expect(browser.driver.switchTo().activeElement().getId()).toBe( + getElement(element).getId(), `Expected element${expected ? '' : ' not'} to be focused.`); +} + +/** + * Asserts that an element has a certan location. + */ +export function expectLocation(element: FinderResult, {x, y}: Point): void { + getElement(element).getLocation().then((location: Point) => { + expect(location.x).toEqual(x); + expect(location.y).toEqual(y); + }); +} + +/** + * Asserts that one element is aligned with another. + */ +export function expectAlignedWith(element: FinderResult, otherElement: FinderResult): void { + getElement(otherElement).getLocation().then((location: Point) => { + this.expectLocation(getElement(element), location); + }); +} diff --git a/e2e/util/query.ts b/e2e/util/query.ts new file mode 100644 index 000000000000..03f6b061920b --- /dev/null +++ b/e2e/util/query.ts @@ -0,0 +1,18 @@ +import {ElementFinder, by, element, ProtractorBy, browser} from 'protractor'; + +/** + * Normalizes either turning a selector into an + * ElementFinder or returning the finder itself. + */ +export function getElement(el: FinderResult): ElementFinder { + return typeof el === 'string' ? element(by.css(el)) : el; +} + +/** + * Waits for an element to be rendered. + */ +export function waitForElement(selector: string) { + return browser.isElementPresent(by.css(selector) as ProtractorBy); +} + +export type FinderResult = ElementFinder | string; diff --git a/guides/getting-started.md b/guides/getting-started.md new file mode 100644 index 000000000000..679676f8cc07 --- /dev/null +++ b/guides/getting-started.md @@ -0,0 +1,92 @@ +For help getting started with a new Angular app, check out the [Angular CLI](https://cli.angular.io/). + +For existing apps, follow these steps to begin using Angular Material. + +## Step 1: Install Angular Material + +```bash +npm install --save @angular/material +``` + +## Step 2: Import the Module + +Add MaterialModule as an import in your app's root NgModule. + +```ts +import { MaterialModule } from '@angular/material'; + +@NgModule({ + ... + imports: [MaterialModule], + ... +}) +export class PizzaPartyAppModule { } +``` + +## Step 3: Include Theming + +Including a theme is **required** to apply all of the core and theme styles to your application. + +To get started with a prebuilt theme, include the following in your app's index.html: + +```html + +``` + +Note that your app's project structure may have a different relative location for your node_modules. + +For more information on theming and instructions on how to create a custom theme, see the [theming guide](./theming.md). + +## Step 4: Gesture Support + +Some components (`md-slide-toggle`, `md-slider`, `mdTooltip`) rely on +[HammerJS](http://hammerjs.github.io/) for gestures. In order to get the full feature-set of these +components, HammerJS must be loaded into the application. + +You can add HammerJS to your application via [npm](https://www.npmjs.com/package/hammerjs), a CDN +(such as the [Google CDN](https://developers.google.com/speed/libraries/#hammerjs)), or served +directly from your app. + +To install via npm, use the following command: +```bash +npm install --save hammerjs +``` + +After installing, import it on your app's root module. +```ts +import 'hammerjs'; +``` + +## Step 5 (Optional): Add Material Icons + +If you want your `md-icon` components to use [Material Icons](https://material.io/icons/), load the font in your `index.html`. + +```html + +``` + +For more information on using Material Icons, check out the [Material Icons Guide](https://google.github.io/material-design-icons/). + +Note that `md-icon` has support for any font or svg icons, so using Material Icons is just one option. + + +## Configuring SystemJS + +If your project is using SystemJS for module loading, you will need to add `@angular/material` +to the SystemJS configuration: + +```js +System.config({ + // existing configuration options + map: { + ... + '@angular/material': 'npm:@angular/material/bundles/material.umd.js', + ... + } +}); +``` + + +## Sample Angular Material projects +- [Material Sample App](https://github.com/jelbourn/material2-app) +- [Angular Connect 2016 Demo](https://github.com/kara/leashed-in) diff --git a/docs/theming-your-components.md b/guides/theming-your-components.md similarity index 85% rename from docs/theming-your-components.md rename to guides/theming-your-components.md index d413c639f037..6922749b1b29 100644 --- a/docs/theming-your-components.md +++ b/guides/theming-your-components.md @@ -22,10 +22,10 @@ All you need is to create a `@mixin` function in the custom-component-theme.scss $primary: map-get($theme, primary); $accent: map-get($theme, accent); - // Use md-color to extract individual colors from a palette as necessary. + // Use mat-color to extract individual colors from a palette as necessary. .candy-carousel { - background-color: md-color($primary); - border-color: md-color($accent, A400); + background-color: mat-color($primary); + border-color: mat-color($accent, A400); } } ``` @@ -53,7 +53,7 @@ Styles that are affected by the theme should be placed in a separated theming fi ## Using colors from a palette -You can consume the theming functions from the `@angular/material/core/theming/theming` and Material palette vars from `@angular/material/core/theming/palette`. You can use the `md-color` function to extract a specific color from a palette. For example: +You can consume the theming functions from the `@angular/material/core/theming/theming` and Material palette vars from `@angular/material/core/theming/palette`. You can use the `mat-color` function to extract a specific color from a palette. For example: ```scss // Import theming functions @@ -61,9 +61,9 @@ You can consume the theming functions from the `@angular/material/core/theming/t // Import your custom theme @import 'src/unicorn-app-theme.scss'; -// Use md-color to extract individual colors from a palette as necessary. +// Use mat-color to extract individual colors from a palette as necessary. .candy-carousel { - background-color: md-color($candy-app-primary); - border-color: md-color($candy-app-accent, A400); + background-color: mat-color($candy-app-primary); + border-color: mat-color($candy-app-accent, A400); } ``` diff --git a/docs/theming.md b/guides/theming.md similarity index 73% rename from docs/theming.md rename to guides/theming.md index cef94c7b891e..591954ae9ce2 100644 --- a/docs/theming.md +++ b/guides/theming.md @@ -13,7 +13,7 @@ a theme consists of: * A foreground palette: colors for text and icons. * A background palette: colors used for element backgrounds. -In Angular Material 2, all theme styles are generated _statically_ at build-time so that your +In Angular Material, all theme styles are generated _statically_ at build-time so that your app doesn't have to spend cycles generating theme styles on startup. [1]: https://material.google.com/style/color.html#color-color-palette @@ -27,12 +27,12 @@ You can include a theme file directly into your application from `@angular/material/core/theming/prebuilt` If you're using Angular CLI, this is as simple as including one line -in your `style.css` file: +in your `styles.css` file: ```css @import '~@angular/material/core/theming/prebuilt/deeppurple-amber.css'; ``` -Alternatively, you can just reference the file directly. This would look something like +Alternatively, you can just reference the file directly. This would look something like: ```html ``` @@ -40,6 +40,28 @@ The actual path will depend on your server setup. You can also concatenate the file with the rest of your application's css. +Finally, if your app's content **is not** placed inside of a `md-sidenav-container` element, you +need to add the `mat-app-background` class to your wrapper element (for example the `body`). This +ensures that the proper theme background is applied to your page. + +#### Theming overlay-based components +Since certain components (e.g. `dialog`) are inside of a global overlay container, your theme may +not be applied to them. In order to define the theme that will be used for overlay components, you +have to specify it on the global `OverlayContainer` instance: + +```ts +import {OverlayContainer} from '@angular/material'; + +@NgModule({ + // misc config goes here +}) +export class YourAppModule { + constructor(overlayContainer: OverlayContainer) { + overlayContainer.themeClass = 'your-theme'; + } +} +``` + ### Defining a custom theme When you want more customization than a pre-built theme offers, you can create your own theme file. @@ -51,19 +73,19 @@ the corresponding styles. A typical theme file will look something like this: // Include the base styles for Angular Material core. We include this here so that you only // have to load a single css file for Angular Material in your app. -@include md-core(); +@include mat-core(); // Define the palettes for your theme using the Material Design palettes available in palette.scss // (imported above). For each palette, you can optionally specify a default, lighter, and darker // hue. -$candy-app-primary: md-palette($md-indigo); -$candy-app-accent: md-palette($md-pink, A200, A100, A400); +$candy-app-primary: mat-palette($mat-indigo); +$candy-app-accent: mat-palette($mat-pink, A200, A100, A400); // The warn palette is optional (defaults to red). -$candy-app-warn: md-palette($md-red); +$candy-app-warn: mat-palette($mat-red); // Create the theme object (a Sass map containing all of the palettes). -$candy-app-theme: md-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn); +$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn); // Include theme styles for core and each component used in your app. // Alternatively, you can import and @include the theme mixins for each component @@ -92,13 +114,13 @@ some selector. For example, we could append the following to the example above t secondary dark theme: ```scss .unicorn-dark-theme { - $dark-primary: md-palette($md-blue-grey); - $dark-accent: md-palette($md-amber, A200, A100, A400); - $dark-warn: md-palette($md-deep-orange); + $dark-primary: mat-palette($mat-blue-grey); + $dark-accent: mat-palette($mat-amber, A200, A100, A400); + $dark-warn: mat-palette($mat-deep-orange); - $dark-theme: md-dark-theme($dark-primary, $dark-accent, $dark-warn); + $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); - @include angular-material-theme($dark-theme); + @include angular-material-theme($dark-theme); } ``` @@ -106,7 +128,7 @@ With this, any element inside of a parent with the `unicorn-dark-theme` class wi dark theme. ### Theming your own components -For more details about theming your own components, see [theming-your-components.md](https://github.com/angular/material2/blob/master/docs/theming-your-components.md) +For more details about theming your own components, see [theming-your-components.md](https://github.com/angular/material2/blob/master/guides/theming-your-components.md) ### Future work * Once CSS variables (custom properties) are available in all the browsers we support, diff --git a/package.json b/package.json index 7aecd87d1294..0e7ead41f318 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "material2", - "description": "Material Design components for Angular 2", + "name": "material2-srcs", + "description": "Material Design components for Angular", "homepage": "https://github.com/angular/material2", "bugs": "https://github.com/angular/material2/issues", "repository": { @@ -17,92 +17,95 @@ "deploy": "firebase deploy", "webdriver-manager": "webdriver-manager", "docs": "gulp docs", - "api": "gulp api" + "api": "gulp api-docs" }, - "version": "2.0.0-alpha.11-2", + "version": "2.0.0-beta.2", "license": "MIT", "engines": { "node": ">= 5.4.1 < 7" }, "dependencies": { - "@angular/common": "^2.2.0", - "@angular/compiler": "^2.2.0", - "@angular/core": "^2.2.0", - "@angular/forms": "^2.2.0", - "@angular/http": "^2.2.0", - "@angular/platform-browser": "^2.2.0", + "@angular/common": "^2.3.0", + "@angular/compiler": "^2.3.0", + "@angular/core": "^2.3.0", + "@angular/forms": "^2.3.0", + "@angular/http": "^2.3.0", + "@angular/platform-browser": "^2.3.0", "core-js": "^2.4.1", - "rxjs": "5.0.0-beta.12", - "systemjs": "0.19.38", - "zone.js": "^0.6.23" - }, - "optionalDependencies": { - "hammerjs": "^2.0.8" + "rxjs": "^5.0.1", + "systemjs": "0.19.43", + "zone.js": "^0.7.2" }, "devDependencies": { - "@angular/compiler-cli": "^2.2.0", - "@angular/platform-browser-dynamic": "^2.2.0", - "@angular/platform-server": "^2.2.0", - "@angular/router": "^3.2.0", - "@types/glob": "^5.0.29", - "@types/gulp": "^3.8.29", - "@types/hammerjs": "^2.0.30", - "@types/jasmine": "^2.2.31", - "@types/merge2": "0.0.28", - "@types/minimist": "^1.1.28", - "@types/node": "^6.0.34", - "@types/protractor": "^4.0.0", - "@types/run-sequence": "0.0.27", - "@types/rx": "^2.5.33", - "@types/selenium-webdriver": "2.53.36", - "axe-core": "^2.0.7", - "axe-webdriverjs": "^0.4.0", + "@angular/compiler-cli": "^2.3.0", + "@angular/platform-browser-dynamic": "^2.3.0", + "@angular/platform-server": "^2.3.0", + "@angular/router": "^3.3.0", + "@types/fs-extra": "0.0.37", + "@types/glob": "^5.0.30", + "@types/gulp": "^3.8.32", + "@types/hammerjs": "^2.0.34", + "@types/jasmine": "2.5.41", + "@types/merge2": "^0.3.29", + "@types/minimist": "^1.2.0", + "@types/node": "^7.0.5", + "@types/run-sequence": "^0.0.28", + "@types/rx": "2.5.33", + "axe-core": "^2.1.7", + "axe-webdriverjs": "^0.5.0", "conventional-changelog": "^1.1.0", - "dgeni": "^0.4.2", - "dgeni-packages": "^0.16.2", - "express": "^4.14.0", + "dgeni": "^0.4.7", + "dgeni-packages": "^0.16.5", + "firebase-admin": "^4.1.2", "firebase-tools": "^2.2.1", - "fs-extra": "^0.26.5", - "glob": "^6.0.4", + "fs-extra": "^2.0.0", + "glob": "^7.1.1", + "google-cloud": "^0.48.0", "gulp": "^3.9.1", "gulp-autoprefixer": "^3.1.1", "gulp-better-rollup": "^1.0.2", "gulp-clean": "^0.3.2", - "gulp-clean-css": "^2.3.0", + "gulp-clean-css": "^3.0.3", "gulp-cli": "^1.2.2", + "gulp-connect": "^5.0.0", + "gulp-dom": "^0.9.17", + "gulp-flatten": "^0.3.1", + "gulp-highlight-files": "0.0.4", "gulp-htmlmin": "^3.0.0", "gulp-if": "^2.0.2", "gulp-markdown": "^1.2.0", - "gulp-sass": "^2.3.2", - "gulp-server-livereload": "^1.8.2", - "gulp-shell": "^0.5.2", - "gulp-sourcemaps": "^1.6.0", + "gulp-rename": "^1.2.2", + "gulp-sass": "^3.1.0", + "gulp-sourcemaps": "^2.4.1", "gulp-transform": "^1.1.0", - "gulp-typescript": "^2.13.6", - "jasmine-core": "^2.4.1", - "karma": "^1.1.1", - "karma-browserstack-launcher": "^1.0.1", - "karma-chrome-launcher": "^1.0.1", - "karma-firefox-launcher": "^1.0.0", - "karma-jasmine": "^1.0.2", - "karma-sauce-launcher": "^1.0.0", - "madge": "^0.6.0", + "hammerjs": "^2.0.8", + "highlight.js": "^9.9.0", + "image-diff": "^1.6.3", + "jasmine-core": "^2.5.2", + "karma": "^1.5.0", + "karma-browserstack-launcher": "^1.2.0", + "karma-chrome-launcher": "^2.0.0", + "karma-coverage": "^1.1.1", + "karma-firefox-launcher": "^1.0.1", + "karma-jasmine": "^1.1.0", + "karma-sauce-launcher": "^1.1.0", + "karma-sourcemap-loader": "^0.3.7", + "madge": "^1.6.0", "merge2": "^1.0.2", "minimist": "^1.2.0", - "node-sass": "^3.4.2", - "protractor": "^4.0.8", + "node-sass": "^4.5.0", + "protractor": "^5.1.1", "resolve-bin": "^0.4.0", "run-sequence": "^1.2.2", "sass": "^0.5.0", - "selenium-webdriver": "2.53.3", - "strip-ansi": "^3.0.0", - "stylelint": "^7.7.0", - "symlink-or-copy": "^1.0.1", - "travis-after-modes": "0.0.6-2", - "ts-node": "^0.7.3", - "tslint": "^3.13.0", - "typedoc": "^0.5.1", - "typescript": "^2.0.2", - "which": "^1.2.4" + "selenium-webdriver": "^3.1.0", + "stylelint": "^7.8.0", + "travis-after-modes": "0.0.7", + "ts-node": "^2.1.0", + "tslint": "^4.4.2", + "tslint-no-unused-var": "0.0.6", + "typescript": "~2.0.10", + "uglify-js": "^2.8.7", + "web-animations-js": "^2.2.2" } } diff --git a/scripts/browserstack/start-tunnel.sh b/scripts/browserstack/start-tunnel.sh index de270502a489..62df1d3f602b 100755 --- a/scripts/browserstack/start-tunnel.sh +++ b/scripts/browserstack/start-tunnel.sh @@ -33,8 +33,8 @@ rm $TUNNEL_FILE ARGS="" # Set tunnel-id only on Travis, to make local testing easier. -if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then - ARGS="$ARGS --local-identifier $TRAVIS_JOB_NUMBER" +if [ ! -z "$TRAVIS_JOB_ID" ]; then + ARGS="$ARGS --local-identifier $TRAVIS_JOB_ID" fi echo "Starting Browserstack Local in the background, logging into:" diff --git a/scripts/ci/after-success.sh b/scripts/ci/after-success.sh index 37524389d1e3..599f097f2cec 100755 --- a/scripts/ci/after-success.sh +++ b/scripts/ci/after-success.sh @@ -10,4 +10,5 @@ cd $(dirname $0)/../.. if [ "$TRAVIS_PULL_REQUEST" = "false" ] && $(npm bin)/travis-after-modes; then echo "All travis modes passed. Publishing the build artifacts..." ./scripts/release/publish-build-artifacts.sh -fi \ No newline at end of file + ./scripts/release/publish-docs-content.sh +fi diff --git a/scripts/ci/build-and-test.sh b/scripts/ci/build-and-test.sh index 54b3f5684552..53eca7120f6e 100755 --- a/scripts/ci/build-and-test.sh +++ b/scripts/ci/build-and-test.sh @@ -11,15 +11,23 @@ source scripts/ci/sources/mode.sh source scripts/ci/sources/tunnel.sh start_tunnel - wait_for_tunnel + if is_lint; then $(npm bin)/gulp ci:lint elif is_e2e; then $(npm bin)/gulp ci:e2e -elif is_extract_metadata; then - $(npm bin)/gulp ci:extract-metadata +elif is_aot; then + $(npm bin)/gulp ci:aot +elif is_payload; then + $(npm bin)/gulp ci:payload else $(npm bin)/gulp ci:test fi + +# Upload coverage results if those are present. +if [ -f dist/coverage/coverage-summary.json ]; then + $(npm bin)/gulp ci:coverage +fi + teardown_tunnel diff --git a/scripts/ci/forbidden-identifiers.js b/scripts/ci/forbidden-identifiers.js deleted file mode 100755 index 301f22246023..000000000000 --- a/scripts/ci/forbidden-identifiers.js +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -/* - * The forbidden identifiers script will check for blocked statements and also detect invalid - * imports of other scope packages. - * - * When running against a PR, the script will only analyze the specific amount of commits inside - * of the Pull Request. - * - * By default it checks all source files and fail if any errors were found. - */ - -const child_process = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const glob = require('glob').sync; - -const blocked_statements = [ - '\\bddescribe\\(', - '\\bfdescribe\\(', - '\\biit\\(', - '\\bfit\\(', - '\\bxdescribe\\(', - '\\bxit\\(', - '\\bdebugger;', - 'from \\\'rxjs/Rx\\\'', - '\\r' -]; - -const sourceFolders = ['./src', './e2e']; -const blockedRegex = new RegExp(blocked_statements.join('|'), 'g'); - -/* - * Verify that the current PR is not adding any forbidden identifiers. - * Run the forbidden identifiers check against all sources when not verifying a PR. - */ - -findTestFiles() - - /* Only match .js or .ts, and remove .d.ts files. */ - .then(files => files.filter(name => /\.(js|ts)$/.test(name) && !/\.d\.ts$/.test(name))) - - /* Read content of the filtered files */ - .then(files => files.map(name => ({ name, content: fs.readFileSync(name, 'utf-8') }))) - - /* Run checks against content of the filtered files. */ - .then(diffList => findErrors(diffList)) - - /* Groups similar errors to simplify console output */ - .then(errors => groupErrors(errors)) - - /* Print the resolved errors to the console */ - .then(errors => printErrors(errors)) - - .catch(err => { - // An error occurred in this script. Output the error and the stack. - console.error(err.stack || err); - process.exit(2); - }); - -/** - * Finds errors inside of changed files by running them against the blocked statements. - * @param {{name: string, content: string}[]} diffList - */ -function findErrors(diffList) { - return diffList.reduce((errors, diffFile) => { - let fileName = diffFile.name; - let content = diffFile.content.split('\n'); - let lineNumber = 0; - - content.forEach(line => { - lineNumber++; - - let matches = line.match(blockedRegex); - - if (matches) { - - errors.push({ - fileName, - lineNumber, - statement: matches[0] - }); - - } - }); - - return errors; - - }, []); -} - -/** - * Groups similar errors in the same file which are present over a range of lines. - * @param {{fileName: string, lineNumber: number, statement: string}[]} errors - */ -function groupErrors(errors) { - - let initialError, initialLine, previousLine; - - return errors.filter(error => { - - if (initialError && isSimilarError(error)) { - previousLine = error.lineNumber; - - // Overwrite the lineNumber with a string, which indicates the range of lines. - initialError.lineNumber = `${initialLine}-${previousLine}`; - - return false; - } - - initialLine = previousLine = error.lineNumber; - initialError = error; - - return true; - }); - - /** Checks whether the given error is similar to the previous one. */ - function isSimilarError(error) { - return initialError.fileName === error.fileName && - initialError.statement === error.statement && - previousLine === (error.lineNumber - 1); - } -} - -/** - * Prints all errors to the console and terminates the process if errors were present. - * @param {{fileName: string, lineNumber: number, statement: string}[]} errors - */ -function printErrors(errors) { - if (errors.length > 0) { - - console.error('Error: You are using one or more blocked statements:\n'); - - errors.forEach(entry => { - - // Stringify the statement to represent line-endings or other unescaped characters. - let statement = JSON.stringify(entry.statement); - - console.error(` ${entry.fileName}@${entry.lineNumber}, Statement: ${statement}.\n`); - }); - - // Exit the process with an error exit code to notify the CI. - process.exit(1); - } -} - -/** - * Resolves all files, which should run against the forbidden identifiers check. - * @return {Promise.>} Files to be checked. - */ -function findTestFiles() { - if (process.env['TRAVIS_PULL_REQUEST']) { - return findChangedFiles(); - } - - let files = sourceFolders.map(folder => { - return glob(`${folder}/**/*`); - }).reduce((files, fileSet) => files.concat(fileSet), []); - - return Promise.resolve(files); -} - -/** - * List all the files that have been changed or added in the last commit range. - * @returns {Promise.>} Resolves with a list of files that are added or changed. - */ -function findChangedFiles() { - let commitRange = process.env['TRAVIS_COMMIT_RANGE']; - - return exec(`git diff --name-status ${commitRange} ${sourceFolders.join(' ')}`) - .then(rawDiff => { - return rawDiff - .split('\n') - .filter(line => { - // Status: C## => Copied (##% confident) - // R## => Renamed (##% confident) - // D => Deleted - // M => Modified - // A => Added - return line.match(/([CR][0-9]*|[AM])\s+/); - }) - .map(line => line.split(/\s+/, 2)[1]); - }); -} - -/** - * Executes a process command and wraps it inside of a promise. - * @returns {Promise.} - */ -function exec(cmd) { - return new Promise(function(resolve, reject) { - child_process.exec(cmd, function(err, stdout /*, stderr */) { - if (err) { - reject(err); - } else { - resolve(stdout); - } - }); - }); -} diff --git a/scripts/ci/sources/mode.sh b/scripts/ci/sources/mode.sh index 7d31a010ceae..ca22c8d90b0f 100644 --- a/scripts/ci/sources/mode.sh +++ b/scripts/ci/sources/mode.sh @@ -9,6 +9,10 @@ is_lint() { [[ "$MODE" = lint ]] } -is_extract_metadata() { - [[ "$MODE" = extract_metadata ]] +is_aot() { + [[ "$MODE" = aot ]] } + +is_payload() { + [[ "$MODE" = payload ]] +} \ No newline at end of file diff --git a/scripts/release/copy-docs.sh b/scripts/release/copy-docs.sh new file mode 100755 index 000000000000..a18c5acaddcc --- /dev/null +++ b/scripts/release/copy-docs.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Copy docs to material docs site + +# Run this script after `gulp docs` +# Need to specify destination folder +# Use OVERVIEW.html when possible. If there's no OVERVIEW file exists, use README.html + +usage='Usage: copy-docs.sh $destinationFolder' +if [ $# -ne 1 ]; then + echo "Missing destination folder. $usage" + exit +fi + +originFolder=./dist/docs/ +destFolder=$1 + +if [ ! -w $destFolder ]; then + echo "Invalid destination folder. $usage" + exit +fi + +for file in $originFolder* +do + name=${file#$originFolder} + overviewFile=$originFolder$name/$name.html + readmeFile=$originFolder$name/README.html + destFile=$destFolder/$name.html + if [ -f $overviewFile ]; then + cp $overviewFile $destFile + echo "Copied $overviewFile to $destFile" + elif [ -f $readmeFile ]; then + cp $readmeFile $destFile + echo "Copied $readmeFile to $destFile" + fi +done diff --git a/scripts/release/publish-build-artifacts.sh b/scripts/release/publish-build-artifacts.sh index 4e7dc3886044..1fb1f9d96d88 100755 --- a/scripts/release/publish-build-artifacts.sh +++ b/scripts/release/publish-build-artifacts.sh @@ -42,7 +42,7 @@ git config user.name "$commitAuthorName" git config user.email "$commitAuthorEmail" git config credential.helper "store --file=.git/credentials" -echo "https://${MATERIAL2_BUILDS_TOKEN}:@github.com" > .git/credentials +echo "https://${MATERIAL2_DOCS_CONTENT_TOKEN}:@github.com" > .git/credentials git add -A git commit -m "$commitMessage" diff --git a/scripts/release/publish-docs-content.sh b/scripts/release/publish-docs-content.sh new file mode 100755 index 000000000000..591569941afd --- /dev/null +++ b/scripts/release/publish-docs-content.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# Publish material2 docs assets to the material2-docs-content repo +# material.angular.io will pull from this assets repo to get the latest docs + +cd "$(dirname $0)/../../" + +docsPath="./dist/docs" +repoPath="/tmp/material2-docs-content" +repoUrl="https://github.com/angular/material2-docs-content" +examplesSource="./dist/docs/examples" + +# If the docs directory is not present, generate docs +if [ ! -d $docsPath ]; then + $(npm bin)/gulp docs +fi + +# Get git meta info for commit +commitSha="$(git rev-parse --short HEAD)" +commitAuthorName="$(git --no-pager show -s --format='%an' HEAD)" +commitAuthorEmail="$(git --no-pager show -s --format='%ae' HEAD)" +commitMessage="$(git log --oneline -n 1)" + +# create directory and clone test repo +rm -rf $repoPath +mkdir -p $repoPath +git clone $repoUrl $repoPath + +# Clean out repo directory and copy contents of dist/docs into it +rm -rf $repoPath/* +mkdir $repoPath/overview +mkdir $repoPath/guides +mkdir $repoPath/api +mkdir $repoPath/examples + +# Move api files over to $repoPath/api +cp -r $docsPath/api/* $repoPath/api + +# Flatten the markdown docs structure and move it into $repoPath/overview +overviewFiles=$docsPath/markdown/ +for filename in $overviewFiles* +do + if [ -d $filename ]; then + for _ in $filename/* + do + markdownFile=${filename#$overviewFiles}.html + # Filename should be same as folder name with .html extension + if [ -e $filename/$markdownFile ]; then + cp -r $filename/$markdownFile $repoPath/overview/ + fi + done + fi +done + +# Move guide files over to $repoPath/guides +for filename in $overviewFiles* +do + if [ -f $filename ]; then + cp -r $filename $repoPath/guides + fi +done + +# Move highlighted examples into $repoPath +cp -r $examplesSource/* $repoPath/examples + +# Copies assets over to the docs-content repository. +cp LICENSE $repoPath/ + +# Push content to repo +cd $repoPath +git config user.name "$commitAuthorName" +git config user.email "$commitAuthorEmail" +git config credential.helper "store --file=.git/credentials" + +echo "https://${MATERIAL2_BUILDS_TOKEN}:@github.com" > .git/credentials + +git add -A +git commit -m "$commitMessage" +git tag "$commitSha" +git push origin master --tags diff --git a/scripts/saucelabs/start-tunnel.sh b/scripts/saucelabs/start-tunnel.sh index ec8f1b550d03..4110fe34aa91 100755 --- a/scripts/saucelabs/start-tunnel.sh +++ b/scripts/saucelabs/start-tunnel.sh @@ -2,58 +2,43 @@ set -e -o pipefail -# Setup and start Sauce Connect for your TravisCI build -# This script requires your .travis.yml to include the following two private env variables: -# SAUCE_USERNAME -# SAUCE_ACCESS_KEY -# Follow the steps at https://saucelabs.com/opensource/travis to set that up. -# -# Curl and run this script as part of your .travis.yml before_script section: -# before_script: -# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash - -CONNECT_DIR="/tmp/sauce-connect-$RANDOM" -CONNECT_DOWNLOAD="sc-latest-linux.tar.gz" - -CONNECT_LOG="$LOGS_DIR/sauce-connect" -CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout" -CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr" - -# Get the appropriate URL for downloading Sauce Connect -if [ `uname -s` = "Darwin" ]; then - # If the user is running Mac, download the OSX version - # https://en.wikipedia.org/wiki/Darwin_(operating_system) - CONNECT_URL="https://saucelabs.com/downloads/sc-4.4.2-osx.zip" -else - # Otherwise, default to Linux for Travis-CI - CONNECT_URL="https://saucelabs.com/downloads/sc-4.4.2-linux.tar.gz" -fi -mkdir -p $CONNECT_DIR -cd $CONNECT_DIR -curl $CONNECT_URL -o $CONNECT_DOWNLOAD 2> /dev/null 1> /dev/null -mkdir sauce-connect -tar --extract --file=$CONNECT_DOWNLOAD --strip-components=1 --directory=sauce-connect > /dev/null -rm $CONNECT_DOWNLOAD +TUNNEL_FILE="sc-4.4.3-linux.tar.gz" +TUNNEL_URL="https://saucelabs.com/downloads/$TUNNEL_FILE" +TUNNEL_DIR="/tmp/saucelabs-connect" +TUNNEL_LOG="$LOGS_DIR/sauce-connect" SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev` +# Cleanup and create the folder structure for the tunnel connector. +rm -rf $TUNNEL_DIR $BROWSER_PROVIDER_READY_FILE +mkdir -p $TUNNEL_DIR + +cd $TUNNEL_DIR + +# Download the saucelabs connect binaries. +curl $TUNNEL_URL -o $TUNNEL_FILE 2> /dev/null 1> /dev/null + +# Extract the saucelabs connect binaries from the tarball. +mkdir -p sauce-connect +tar --extract --file=$TUNNEL_FILE --strip-components=1 --directory=sauce-connect > /dev/null + +# Cleanup the download directory. +rm $TUNNEL_FILE + ARGS="" # Set tunnel-id only on Travis, to make local testing easier. -if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then - ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_NUMBER" +if [ ! -z "$TRAVIS_JOB_ID" ]; then + ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_ID" fi if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then ARGS="$ARGS --readyfile $BROWSER_PROVIDER_READY_FILE" fi - echo "Starting Sauce Connect in the background, logging into:" -echo " $CONNECT_LOG" -echo " $CONNECT_STDOUT" -echo " $CONNECT_STDERR" +echo " $TUNNEL_LOG" echo " ---" echo " $ARGS" -sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \ - --logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT & + +sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS --logfile $TUNNEL_LOG & \ No newline at end of file diff --git a/src/demo-app/autocomplete/autocomplete-demo.html b/src/demo-app/autocomplete/autocomplete-demo.html index 0745c4ac4ea0..ba732cbc781e 100644 --- a/src/demo-app/autocomplete/autocomplete-demo.html +++ b/src/demo-app/autocomplete/autocomplete-demo.html @@ -1,3 +1,56 @@ +Space above cards: +
- + + Reactive length: {{ reactiveStates.length }} +
Reactive value: {{ stateCtrl.value | json }}
+
Reactive dirty: {{ stateCtrl.dirty }}
+ + + + + + + + + + + +
+ + + +
Template-driven value (currentState): {{ currentState }}
+
Template-driven dirty: {{ modelDir?.dirty }}
+ + + + + + + + + + + + +
+ + + + {{ state.name }} + ({{state.code}}) + + + + + + {{ state.name }} + + \ No newline at end of file diff --git a/src/demo-app/autocomplete/autocomplete-demo.scss b/src/demo-app/autocomplete/autocomplete-demo.scss index 94c86ec8589d..8b4132523acf 100644 --- a/src/demo-app/autocomplete/autocomplete-demo.scss +++ b/src/demo-app/autocomplete/autocomplete-demo.scss @@ -1 +1,18 @@ -.demo-autocomplete {} +.demo-autocomplete { + display: flex; + flex-flow: row wrap; + + .mat-card { + width: 400px; + margin: 24px; + } + + .mat-input-container { + margin-top: 16px; + } +} + +.demo-secondary-text { + color: rgba(0, 0, 0, 0.54); + margin-left: 8px; +} diff --git a/src/demo-app/autocomplete/autocomplete-demo.ts b/src/demo-app/autocomplete/autocomplete-demo.ts index b89197833174..00fbeeb1a15c 100644 --- a/src/demo-app/autocomplete/autocomplete-demo.ts +++ b/src/demo-app/autocomplete/autocomplete-demo.ts @@ -1,9 +1,95 @@ -import {Component} from '@angular/core'; +import {Component, ViewChild, ViewEncapsulation} from '@angular/core'; +import {FormControl, NgModel} from '@angular/forms'; +import 'rxjs/add/operator/startWith'; @Component({ moduleId: module.id, selector: 'autocomplete-demo', templateUrl: 'autocomplete-demo.html', styleUrls: ['autocomplete-demo.css'], + encapsulation: ViewEncapsulation.None }) -export class AutocompleteDemo {} +export class AutocompleteDemo { + stateCtrl: FormControl; + currentState = ''; + topHeightCtrl = new FormControl(0); + + reactiveStates: any; + tdStates: any[]; + + tdDisabled = false; + + @ViewChild(NgModel) modelDir: NgModel; + + states = [ + {code: 'AL', name: 'Alabama'}, + {code: 'AK', name: 'Alaska'}, + {code: 'AZ', name: 'Arizona'}, + {code: 'AR', name: 'Arkansas'}, + {code: 'CA', name: 'California'}, + {code: 'CO', name: 'Colorado'}, + {code: 'CT', name: 'Connecticut'}, + {code: 'DE', name: 'Delaware'}, + {code: 'FL', name: 'Florida'}, + {code: 'GA', name: 'Georgia'}, + {code: 'HI', name: 'Hawaii'}, + {code: 'ID', name: 'Idaho'}, + {code: 'IL', name: 'Illinois'}, + {code: 'IN', name: 'Indiana'}, + {code: 'IA', name: 'Iowa'}, + {code: 'KS', name: 'Kansas'}, + {code: 'KY', name: 'Kentucky'}, + {code: 'LA', name: 'Louisiana'}, + {code: 'ME', name: 'Maine'}, + {code: 'MD', name: 'Maryland'}, + {code: 'MA', name: 'Massachusetts'}, + {code: 'MI', name: 'Michigan'}, + {code: 'MN', name: 'Minnesota'}, + {code: 'MS', name: 'Mississippi'}, + {code: 'MO', name: 'Missouri'}, + {code: 'MT', name: 'Montana'}, + {code: 'NE', name: 'Nebraska'}, + {code: 'NV', name: 'Nevada'}, + {code: 'NH', name: 'New Hampshire'}, + {code: 'NJ', name: 'New Jersey'}, + {code: 'NM', name: 'New Mexico'}, + {code: 'NY', name: 'New York'}, + {code: 'NC', name: 'North Carolina'}, + {code: 'ND', name: 'North Dakota'}, + {code: 'OH', name: 'Ohio'}, + {code: 'OK', name: 'Oklahoma'}, + {code: 'OR', name: 'Oregon'}, + {code: 'PA', name: 'Pennsylvania'}, + {code: 'RI', name: 'Rhode Island'}, + {code: 'SC', name: 'South Carolina'}, + {code: 'SD', name: 'South Dakota'}, + {code: 'TN', name: 'Tennessee'}, + {code: 'TX', name: 'Texas'}, + {code: 'UT', name: 'Utah'}, + {code: 'VT', name: 'Vermont'}, + {code: 'VA', name: 'Virginia'}, + {code: 'WA', name: 'Washington'}, + {code: 'WV', name: 'West Virginia'}, + {code: 'WI', name: 'Wisconsin'}, + {code: 'WY', name: 'Wyoming'}, + ]; + + constructor() { + this.tdStates = this.states; + this.stateCtrl = new FormControl({code: 'CA', name: 'California'}); + this.reactiveStates = this.stateCtrl.valueChanges + .startWith(this.stateCtrl.value) + .map(val => this.displayFn(val)) + .map(name => this.filterStates(name)); + } + + displayFn(value: any): string { + return value && typeof value === 'object' ? value.name : value; + } + + filterStates(val: string) { + return val ? this.states.filter(s => new RegExp(`^${val}`, 'gi').test(s.name)) + : this.states; + } + +} diff --git a/src/demo-app/baseline/baseline-demo.html b/src/demo-app/baseline/baseline-demo.html index e28173d99f94..707b445e3c22 100644 --- a/src/demo-app/baseline/baseline-demo.html +++ b/src/demo-app/baseline/baseline-demo.html @@ -10,10 +10,8 @@ | Text 3 | Radio 3 | Text 4 | - - | Text 5 | - + | Text After @@ -32,10 +30,8 @@

| Text 3 | Radio 3 | Text 4 | - - | Text 5 | - + | Text After

diff --git a/src/demo-app/baseline/baseline-demo.scss b/src/demo-app/baseline/baseline-demo.scss index 32ba259cffaa..259047d9b26b 100644 --- a/src/demo-app/baseline/baseline-demo.scss +++ b/src/demo-app/baseline/baseline-demo.scss @@ -2,20 +2,13 @@ .demo-basic { padding: 0; } -.demo-basic md-card-content { +.demo-basic .mat-card-content { padding: 16px; } .demo-full-width { width: 100%; } -.demo-icons { - font-size: 100%; - height: inherit; - vertical-align: top; - width: inherit; -} - .demo-card { margin: 16px; } diff --git a/src/demo-app/button-toggle/button-toggle-demo.html b/src/demo-app/button-toggle/button-toggle-demo.html index eaf7ac01dfc2..0d158c21ecc3 100644 --- a/src/demo-app/button-toggle/button-toggle-demo.html +++ b/src/demo-app/button-toggle/button-toggle-demo.html @@ -2,6 +2,10 @@ Show Button Toggles Vertical

+

+ Disable Button Toggle Items +

+

Exclusive Selection

@@ -9,14 +13,14 @@

Exclusive Selection

format_align_left format_align_center format_align_right - format_align_justify + format_align_justify

Disabled Group

- + format_bold @@ -35,7 +39,7 @@

Multiple Selection

Flour Eggs Sugar - Milk (disabled) + Milk
diff --git a/src/demo-app/button-toggle/button-toggle-demo.ts b/src/demo-app/button-toggle/button-toggle-demo.ts index 67726b664518..d03d18396bf2 100644 --- a/src/demo-app/button-toggle/button-toggle-demo.ts +++ b/src/demo-app/button-toggle/button-toggle-demo.ts @@ -9,6 +9,7 @@ import {UniqueSelectionDispatcher} from '@angular/material'; }) export class ButtonToggleDemo { isVertical = false; + isDisabled = false; favoritePie = 'Apple'; pieOptions = [ 'Apple', diff --git a/src/demo-app/card/card-demo.html b/src/demo-app/card/card-demo.html index 1bb780497625..98cc322d20e3 100644 --- a/src/demo-app/card/card-demo.html +++ b/src/demo-app/card/card-demo.html @@ -51,7 +51,7 @@ - + Easily customizable diff --git a/src/demo-app/card/card-demo.scss b/src/demo-app/card/card-demo.scss index e5dfdfce005b..309b1e2d1366 100644 --- a/src/demo-app/card/card-demo.scss +++ b/src/demo-app/card/card-demo.scss @@ -2,7 +2,7 @@ display: flex; flex-flow: column nowrap; - md-card { + .mat-card { margin: 0 16px 16px 0; width: 350px; } @@ -15,7 +15,7 @@ .demo-card-blue { background-color: #b0becc; - md-card-actions { + .mat-card-actions { display: flex; flex-direction: column; } diff --git a/src/demo-app/checkbox/checkbox-demo.html b/src/demo-app/checkbox/checkbox-demo.html index 713f8b008d4c..28760dac161c 100644 --- a/src/demo-app/checkbox/checkbox-demo.html +++ b/src/demo-app/checkbox/checkbox-demo.html @@ -1,7 +1,8 @@

md-checkbox: Basic Example

md-checkbox: Basic Example +

Pseudo checkboxes

+ + + + + + + + +

Nested Checklist

diff --git a/src/demo-app/checkbox/checkbox-demo.ts b/src/demo-app/checkbox/checkbox-demo.ts index aed0335dc761..160bd59c452e 100644 --- a/src/demo-app/checkbox/checkbox-demo.ts +++ b/src/demo-app/checkbox/checkbox-demo.ts @@ -25,7 +25,7 @@ export class MdCheckboxDemoNestedChecklist { subtasks: [ { name: 'Cook Dinner', completed: false }, { name: 'Read the Material Design Spec', completed: false }, - { name: 'Upgrade Application to Angular2', completed: false } + { name: 'Upgrade Application to Angular', completed: false } ] }, { diff --git a/src/demo-app/chips/chips-demo.html b/src/demo-app/chips/chips-demo.html index d3ba62b3824f..859d638695f6 100644 --- a/src/demo-app/chips/chips-demo.html +++ b/src/demo-app/chips/chips-demo.html @@ -44,7 +44,7 @@

Input Container

- +

Stacked Chips

@@ -54,7 +54,7 @@

Stacked Chips

(focus) event to run custom code.

- + {{aColor.name}} @@ -62,4 +62,4 @@

Stacked Chips

- \ No newline at end of file + diff --git a/src/demo-app/chips/chips-demo.scss b/src/demo-app/chips/chips-demo.scss index f0ba734b465f..c44263807d75 100644 --- a/src/demo-app/chips/chips-demo.scss +++ b/src/demo-app/chips/chips-demo.scss @@ -1,23 +1,23 @@ .chips-demo { - .md-chip-list-stacked { + .mat-chip-list-stacked { display: block; max-width: 200px; } - md-card { + .mat-card { padding: 0; margin: 16px; - & md-toolbar { + & .mat-toolbar { margin: 0; } - & md-card-content { + & .mat-card-content { padding: 24px; } } - md-basic-chip { + .mat-basic-chip { margin: auto 10px; } } \ No newline at end of file diff --git a/src/demo-app/chips/chips-demo.ts b/src/demo-app/chips/chips-demo.ts index 16135f235cf0..07e007d5509f 100644 --- a/src/demo-app/chips/chips-demo.ts +++ b/src/demo-app/chips/chips-demo.ts @@ -1,5 +1,4 @@ import {Component} from '@angular/core'; -import {MdInput} from '@angular/material'; export interface Person { name: string; @@ -40,7 +39,7 @@ export class ChipsDemo { alert(message); } - add(input: MdInput): void { + add(input: HTMLInputElement): void { if (input.value && input.value.trim() != '') { this.people.push({ name: input.value.trim() }); input.value = ''; diff --git a/src/demo-app/demo-app-module.ts b/src/demo-app/demo-app-module.ts index b0cccd849597..0263eff112fc 100644 --- a/src/demo-app/demo-app-module.ts +++ b/src/demo-app/demo-app-module.ts @@ -4,14 +4,18 @@ import {HttpModule} from '@angular/http'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {DemoApp, Home} from './demo-app/demo-app'; import {RouterModule} from '@angular/router'; -import {MaterialModule} from '@angular/material'; +import { + MaterialModule, + OverlayContainer, + FullscreenOverlayContainer, + MdSelectionModule, +} from '@angular/material'; import {DEMO_APP_ROUTES} from './demo-app/routes'; import {ProgressBarDemo} from './progress-bar/progress-bar-demo'; -import {JazzDialog, ContentElementDialog, DialogDemo} from './dialog/dialog-demo'; +import {JazzDialog, ContentElementDialog, DialogDemo, IFrameDialog} from './dialog/dialog-demo'; import {RippleDemo} from './ripple/ripple-demo'; import {IconDemo} from './icon/icon-demo'; import {GesturesDemo} from './gestures/gestures-demo'; -import {InputDemo} from './input/input-demo'; import {CardDemo} from './card/card-demo'; import {ChipsDemo} from './chips/chips-demo'; import {RadioDemo} from './radio/radio-demo'; @@ -37,7 +41,8 @@ import {TabsDemo, SunnyTabContent, RainyTabContent, FoggyTabContent} from './tab import {ProjectionDemo, ProjectionTestComponent} from './projection/projection-demo'; import {PlatformDemo} from './platform/platform-demo'; import {AutocompleteDemo} from './autocomplete/autocomplete-demo'; -import {InputContainerDemo} from './input/input-container-demo'; +import {InputDemo} from './input/input-demo'; +import {StyleDemo} from './style/style-demo'; @NgModule({ imports: [ @@ -47,6 +52,7 @@ import {InputContainerDemo} from './input/input-container-demo'; ReactiveFormsModule, RouterModule.forRoot(DEMO_APP_ROUTES), MaterialModule.forRoot(), + MdSelectionModule, ], declarations: [ AutocompleteDemo, @@ -63,9 +69,9 @@ import {InputContainerDemo} from './input/input-container-demo'; Home, IconDemo, InputDemo, - InputContainerDemo, JazzDialog, ContentElementDialog, + IFrameDialog, ListDemo, LiveAnnouncerDemo, MdCheckboxDemoNestedChecklist, @@ -86,18 +92,23 @@ import {InputContainerDemo} from './input/input-container-demo'; SliderDemo, SlideToggleDemo, SpagettiPanel, + StyleDemo, ToolbarDemo, TooltipDemo, TabsDemo, SunnyTabContent, RainyTabContent, FoggyTabContent, - PlatformDemo + PlatformDemo, + ], + providers: [ + {provide: OverlayContainer, useClass: FullscreenOverlayContainer} ], entryComponents: [ DemoApp, JazzDialog, ContentElementDialog, + IFrameDialog, RotiniPanel, ScienceJoke, SpagettiPanel, diff --git a/src/demo-app/typings.d.ts b/src/demo-app/demo-app-types.d.ts similarity index 100% rename from src/demo-app/typings.d.ts rename to src/demo-app/demo-app-types.d.ts diff --git a/src/demo-app/demo-app/demo-app.html b/src/demo-app/demo-app/demo-app.html index b48e4a1efab9..fe1fab9478cc 100644 --- a/src/demo-app/demo-app/demo-app.html +++ b/src/demo-app/demo-app/demo-app.html @@ -24,7 +24,10 @@ menu
-

Angular Material 2 Demos

+

Angular Material Demos

+ diff --git a/src/demo-app/demo-app/demo-app.scss b/src/demo-app/demo-app/demo-app.scss index 17338c88abf2..ee4512261ed3 100644 --- a/src/demo-app/demo-app/demo-app.scss +++ b/src/demo-app/demo-app/demo-app.scss @@ -8,10 +8,10 @@ body { -moz-osx-font-smoothing: grayscale; } - md-sidenav { - min-width: 15%; + .mat-sidenav { + min-width: 15vw; - [md-button] { + .mat-button { width: 100%; position: relative; bottom: 0; @@ -21,10 +21,11 @@ body { .demo-content { padding: 32px; + box-sizing: border-box; } - md-toolbar { - md-icon { + .mat-toolbar { + .mat-icon { cursor: pointer; } @@ -39,3 +40,10 @@ body { font-size: 20px; } } + +// stretch to screen size in fullscreen mode +.demo-content { + width: 100%; + height: 100%; + box-sizing: border-box; +} diff --git a/src/demo-app/demo-app/demo-app.ts b/src/demo-app/demo-app/demo-app.ts index 57b8f96049c9..746a18056915 100644 --- a/src/demo-app/demo-app/demo-app.ts +++ b/src/demo-app/demo-app/demo-app.ts @@ -1,10 +1,10 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import {Component, ViewEncapsulation, ElementRef} from '@angular/core'; @Component({ selector: 'home', template: ` -

Welcome to the development demos for Angular Material 2!

+

Welcome to the development demos for Angular Material!

Open the sidenav to select a demo.

` }) @@ -31,7 +31,6 @@ export class DemoApp { {name: 'Grid List', route: 'grid-list'}, {name: 'Icon', route: 'icon'}, {name: 'Input', route: 'input'}, - {name: 'Input Container', route: 'input-container'}, {name: 'List', route: 'list'}, {name: 'Menu', route: 'menu'}, {name: 'Live Announcer', route: 'live-announcer'}, @@ -50,6 +49,24 @@ export class DemoApp { {name: 'Tabs', route: 'tabs'}, {name: 'Toolbar', route: 'toolbar'}, {name: 'Tooltip', route: 'tooltip'}, - {name: 'Platform', route: 'platform'} + {name: 'Platform', route: 'platform'}, + {name: 'Style', route: 'style'} ]; + + constructor(private _element: ElementRef) { + + } + + toggleFullscreen() { + let elem = this._element.nativeElement.querySelector('.demo-content'); + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.webkitRequestFullScreen) { + elem.webkitRequestFullScreen(); + } else if (elem.mozRequestFullScreen) { + elem.mozRequestFullScreen(); + } else if (elem.msRequestFullScreen) { + elem.msRequestFullScreen(); + } + } } diff --git a/src/demo-app/demo-app/routes.ts b/src/demo-app/demo-app/routes.ts index 7af0ae91863e..d2e2360799f6 100644 --- a/src/demo-app/demo-app/routes.ts +++ b/src/demo-app/demo-app/routes.ts @@ -10,7 +10,6 @@ import {LiveAnnouncerDemo} from '../live-announcer/live-announcer-demo'; import {ListDemo} from '../list/list-demo'; import {IconDemo} from '../icon/icon-demo'; import {ToolbarDemo} from '../toolbar/toolbar-demo'; -import {InputDemo} from '../input/input-demo'; import {CheckboxDemo} from '../checkbox/checkbox-demo'; import {OverlayDemo} from '../overlay/overlay-demo'; import {PortalDemo} from '../portal/portal-demo'; @@ -32,7 +31,8 @@ import {ProjectionDemo} from '../projection/projection-demo'; import {TABS_DEMO_ROUTES} from '../tabs/routes'; import {PlatformDemo} from '../platform/platform-demo'; import {AutocompleteDemo} from '../autocomplete/autocomplete-demo'; -import {InputContainerDemo} from '../input/input-container-demo'; +import {InputDemo} from '../input/input-demo'; +import {StyleDemo} from '../style/style-demo'; export const DEMO_APP_ROUTES: Routes = [ {path: '', component: Home}, @@ -52,7 +52,6 @@ export const DEMO_APP_ROUTES: Routes = [ {path: 'overlay', component: OverlayDemo}, {path: 'checkbox', component: CheckboxDemo}, {path: 'input', component: InputDemo}, - {path: 'input-container', component: InputContainerDemo}, {path: 'toolbar', component: ToolbarDemo}, {path: 'icon', component: IconDemo}, {path: 'list', component: ListDemo}, @@ -67,5 +66,6 @@ export const DEMO_APP_ROUTES: Routes = [ {path: 'dialog', component: DialogDemo}, {path: 'tooltip', component: TooltipDemo}, {path: 'snack-bar', component: SnackBarDemo}, - {path: 'platform', component: PlatformDemo} + {path: 'platform', component: PlatformDemo}, + {path: 'style', component: StyleDemo}, ]; diff --git a/src/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index 491e8031393f..ec05d8f1ae04 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -1,33 +1,72 @@

Dialog demo

- - + + +

Dialog dimensions

- - + + + + + +

Dialog position

- - + + + + + +

- - + + + + + +

Other options

- Disable close +

+ + Start + End + Center + +

+ +

+ + + +

+ +

+ Disable close +

Last close result: {{lastCloseResult}}

+ + diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 7323d8f67173..f82ecbfbce4c 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -1,5 +1,7 @@ -import {Component} from '@angular/core'; -import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; +import {Component, Inject, ViewChild, TemplateRef} from '@angular/core'; +import {DOCUMENT} from '@angular/platform-browser'; +import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/material'; + @Component({ moduleId: module.id, @@ -10,6 +12,7 @@ import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; export class DialogDemo { dialogRef: MdDialogRef; lastCloseResult: string; + actionsAlignment: string; config: MdDialogConfig = { disableClose: false, width: '', @@ -19,22 +22,46 @@ export class DialogDemo { bottom: '', left: '', right: '' + }, + data: { + message: 'Jazzy jazz jazz' } }; + numTemplateOpens = 0; - constructor(public dialog: MdDialog) { } + @ViewChild(TemplateRef) template: TemplateRef; + + constructor(public dialog: MdDialog, @Inject(DOCUMENT) doc: any) { + // Possible useful example for the open and closeAll events. + // Adding a class to the body if a dialog opens and + // removing it after all open dialogs are closed + dialog.afterOpen.subscribe((ref: MdDialogRef) => { + if (!doc.body.classList.contains('no-scroll')) { + doc.body.classList.add('no-scroll'); + } + }); + dialog.afterAllClosed.subscribe(() => { + doc.body.classList.remove('no-scroll'); + }); + } openJazz() { this.dialogRef = this.dialog.open(JazzDialog, this.config); - this.dialogRef.afterClosed().subscribe(result => { + this.dialogRef.afterClosed().subscribe((result: string) => { this.lastCloseResult = result; this.dialogRef = null; }); } openContentElement() { - this.dialog.open(ContentElementDialog, this.config); + let dialogRef = this.dialog.open(ContentElementDialog, this.config); + dialogRef.componentInstance.actionsAlignment = this.actionsAlignment; + } + + openTemplate() { + this.numTemplateOpens++; + this.dialog.open(this.template, this.config); } } @@ -44,13 +71,13 @@ export class DialogDemo { template: `

It's Jazz!

-

{{ jazzMessage }}

+

{{ data.message }}

` }) export class JazzDialog { - jazzMessage = 'Jazzy jazz jazz'; - - constructor(public dialogRef: MdDialogRef) { } + constructor( + public dialogRef: MdDialogRef, + @Inject(MD_DIALOG_DATA) public data: any) { } } @@ -78,7 +105,7 @@ export class JazzDialog {

- + + + ` +}) +export class ContentElementDialog { + actionsAlignment: string; + + constructor(public dialog: MdDialog) { } + + showInStackedDialog() { + this.dialog.open(IFrameDialog); + } +} + +@Component({ + selector: 'demo-iframe-dialog', + styles: [ + `iframe { + width: 800px; + }` + ], + template: ` +

Neptune

+ + + + + + + ` }) -export class ContentElementDialog { } +export class IFrameDialog { +} diff --git a/src/demo-app/grid-list/grid-list-demo.scss b/src/demo-app/grid-list/grid-list-demo.scss index 686a09d7380b..c2d99ad3e12b 100644 --- a/src/demo-app/grid-list/grid-list-demo.scss +++ b/src/demo-app/grid-list/grid-list-demo.scss @@ -1,7 +1,7 @@ .demo-grid-list { width: 1100px; - md-card { + .mat-card { margin: 16px 0; } @@ -9,7 +9,7 @@ margin: 16px; } - .demo-basic-list md-grid-tile { + .demo-basic-list .mat-grid-tile { background: rgba(0, 0, 0, 0.32); } diff --git a/src/demo-app/icon/icon-demo.scss b/src/demo-app/icon/icon-demo.scss index 02af5f002624..b1e8e3b288ab 100644 --- a/src/demo-app/icon/icon-demo.scss +++ b/src/demo-app/icon/icon-demo.scss @@ -1,3 +1,3 @@ -.demo-icon md-icon.green { +.demo-icon .mat-icon.green { color: green; } diff --git a/src/demo-app/index.html b/src/demo-app/index.html index 8498715397d1..3099472c8633 100644 --- a/src/demo-app/index.html +++ b/src/demo-app/index.html @@ -3,7 +3,7 @@ - Material2 + Angular Material @@ -17,6 +17,7 @@ Loading... + diff --git a/src/demo-app/input/input-container-demo.html b/src/demo-app/input/input-container-demo.html deleted file mode 100644 index 617a000e5c78..000000000000 --- a/src/demo-app/input/input-container-demo.html +++ /dev/null @@ -1,272 +0,0 @@ - - Basic - - - - - - - - - -
- - - - - - - -
-

- - - - - - -

- - - - -
- - - - - - - - - - - {{postalCode.value.length}} / 5 - -
- -
-
- - - Prefix + Suffix - - - - - .00 - - - - - - Divider Colors - -

Input

- - - - - - - - - - -

Textarea

- - - - - - - - - -
-
- - - Hints - -

Input

-

- - - {{characterCountInputHintExample.value.length}} / 100 - -

- -

Textarea

-

- - - {{characterCountTextareaHintExample.value.length}} / 100 - -

-
-
- - - - Hello  - - - , - how are you? - - -

- - - - - - -

-

- - - -

-

- - - {{input.value.length}} / 100 - -

-

- - - -

- -

- - - - I favorite bold placeholder - - - I also home italic hint labels - - -

-

- - - {{hintLabelWithCharCount.value.length}} - -

-

- Check to change the divider color: - - - -

-

- Check to make required: - - - -

-

- Check to make floating label: - - - -

- -

- - -

Example: 
- - - - .00 € - -
- Both: - - - - email -   - - -  @gmail.com - - -

- -

- Empty: - -

-
-
- - - Number Inputs - - - - - - - - - - - - -
Table - - - - -
{{i+1}} - - - - - - {{item.value}}
-
-
- - - Forms - - - - - - - - Textarea Autosize - - -
- - - -
-
-
diff --git a/src/demo-app/input/input-container-demo.ts b/src/demo-app/input/input-container-demo.ts deleted file mode 100644 index 73e5d3308804..000000000000 --- a/src/demo-app/input/input-container-demo.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {Component} from '@angular/core'; -import {FormControl, Validators} from '@angular/forms'; - - -let max = 5; - -@Component({ - moduleId: module.id, - selector: 'input-container-demo', - templateUrl: 'input-container-demo.html', - styleUrls: ['input-demo.css'], -}) -export class InputContainerDemo { - dividerColor: boolean; - requiredField: boolean; - floatingLabel: boolean; - name: string; - items: any[] = [ - { value: 10 }, - { value: 20 }, - { value: 30 }, - { value: 40 }, - { value: 50 }, - ]; - rows = 8; - formControl = new FormControl('hello', Validators.required); - model = 'hello'; - - addABunch(n: number) { - for (let x = 0; x < n; x++) { - this.items.push({ value: ++max }); - } - } -} diff --git a/src/demo-app/input/input-demo.html b/src/demo-app/input/input-demo.html index cd4b7fe8e0a0..7559ddf3891e 100644 --- a/src/demo-app/input/input-demo.html +++ b/src/demo-app/input/input-demo.html @@ -2,25 +2,50 @@ Basic
- - + + + - - + +
+ + + + + + + +

- - + + + + + +

- - - + + +
- {{postalCode.characterCount}} / 5 - + + + + + + + + + + + + mode_edit + {{postalCode.value.length}} / 5 + + +
@@ -29,10 +54,19 @@ Prefix + Suffix - - - .00 - +

Text

+ + + + .00 + + +

Icons

+ + + attach_money + mode_edit +
@@ -40,15 +74,26 @@ Divider Colors

Input

- - - + + + + + + + + +

Textarea

- - - - + + + + + + + + +
@@ -57,103 +102,138 @@

Textarea

Input

- - {{characterCountHintExample.characterCount}} / 100 - + + + {{characterCountInputHintExample.value.length}} / 100 +

Textarea

- - {{characterCountHintExampleTextarea.characterCount}} / 100 - + + + {{characterCountTextareaHintExample.value.length}} / 100 +

- - - Hello , + + + Hello  + + + , how are you? - +

- - + + + + + +

- + + +

- - {{input.characterCount}} / 100 - + + + {{input.value.length}} / 100 +

- + + +

- + + - I favorite bold placeholder + I favorite bold placeholder - I also home italic hint labels + I also home italic hint labels - +

- + + + {{hintLabelWithCharCount.value.length}} +

Check to change the divider color: - + + +

Check to make required: - + + +

- Check to make floating label: - + + Auto Float + Always Float + Never Float +

- -

Example: 
- - - .00 € - + + + +

+ +

+ + +

Example: 
+ + + + .00 € +
Both: - - email  -  @gmail.com - + + + email  +  @gmail.com +

- Empty: - + Empty: +

- - - + + Number Inputs + +
+ - - - - - - - -
Table @@ -161,22 +241,53 @@

Textarea

{{i+1}} - - - - {{item.value}}
+ + + {{i+1}} + + + + + + + + + {{item.value}} + + +
+ + Forms + + + + + + + + + - -

textarea autosize

- + + Textarea Autosize + + +
+ + + +
+
diff --git a/src/demo-app/input/input-demo.scss b/src/demo-app/input/input-demo.scss index b70dd6f1e573..2325e7c67896 100644 --- a/src/demo-app/input/input-demo.scss +++ b/src/demo-app/input/input-demo.scss @@ -1,20 +1,13 @@ .demo-basic { padding: 0; } -.demo-basic md-card-content { +.demo-basic .mat-card-content { padding: 16px; } .demo-full-width { width: 100%; } -.demo-icons { - font-size: 100%; - height: inherit; - vertical-align: top; - width: inherit; -} - .demo-card { margin: 16px; } diff --git a/src/demo-app/input/input-demo.ts b/src/demo-app/input/input-demo.ts index 49dcfe0f56a8..e8fb5838141c 100644 --- a/src/demo-app/input/input-demo.ts +++ b/src/demo-app/input/input-demo.ts @@ -1,4 +1,5 @@ import {Component} from '@angular/core'; +import {FormControl, Validators} from '@angular/forms'; let max = 5; @@ -10,9 +11,11 @@ let max = 5; styleUrls: ['input-demo.css'], }) export class InputDemo { + floatingLabel: string = 'auto'; dividerColor: boolean; requiredField: boolean; - floatingLabel: boolean; + ctrlDisabled = false; + name: string; items: any[] = [ { value: 10 }, @@ -22,6 +25,8 @@ export class InputDemo { { value: 50 }, ]; rows = 8; + formControl = new FormControl('hello', Validators.required); + model = 'hello'; addABunch(n: number) { for (let x = 0; x < n; x++) { diff --git a/src/demo-app/list/list-demo.scss b/src/demo-app/list/list-demo.scss index 268668fa15d3..bcf8a770c381 100644 --- a/src/demo-app/list/list-demo.scss +++ b/src/demo-app/list/list-demo.scss @@ -3,7 +3,7 @@ display: flex; flex-flow: row wrap; - md-list, md-nav-list { + .mat-list, .mat-nav-list { border: 1px solid rgba(0, 0, 0, 0.12); width: 350px; margin: 20px 20px 0 0; @@ -13,11 +13,11 @@ margin-top: 20px; } - md-icon { + .mat-icon { color: rgba(0, 0, 0, 0.12); } - [md-list-icon] { + .mat-list-icon { color: white; background: rgba(0, 0, 0, 0.3); } diff --git a/src/demo-app/main-aot.ts b/src/demo-app/main-aot.ts new file mode 100644 index 000000000000..0478e00da2a9 --- /dev/null +++ b/src/demo-app/main-aot.ts @@ -0,0 +1,8 @@ +/** + * This is the main entry-point for the AOT compilation. File will be used to test AOT support. + */ + +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {DemoAppModuleNgFactory} from './demo-app-module.ngfactory'; + +platformBrowserDynamic().bootstrapModuleFactory(DemoAppModuleNgFactory); diff --git a/src/demo-app/menu/menu-demo.html b/src/demo-app/menu/menu-demo.html index 1d818adf3e7f..61c0bfb79209 100644 --- a/src/demo-app/menu/menu-demo.html +++ b/src/demo-app/menu/menu-demo.html @@ -38,7 +38,7 @@ - + - + + + +
+ + +
+ + + - - + +
@@ -51,10 +51,13 @@

Sidenav Already Opened

Dynamic Alignment Sidenav

- Drawer + Start + End
- - + +
diff --git a/src/demo-app/sidenav/sidenav-demo.scss b/src/demo-app/sidenav/sidenav-demo.scss index 9805892bd0db..a04b3bb58124 100644 --- a/src/demo-app/sidenav/sidenav-demo.scss +++ b/src/demo-app/sidenav/sidenav-demo.scss @@ -1,7 +1,7 @@ .demo-sidenav-container { border: 3px solid black; - .md-sidenav-focus-trap > .cdk-focus-trap-content { + .mat-sidenav-focus-trap > .cdk-focus-trap-content { padding: 10px; } } diff --git a/src/demo-app/sidenav/sidenav-demo.ts b/src/demo-app/sidenav/sidenav-demo.ts index ed5b512679d2..5d72a469a5df 100644 --- a/src/demo-app/sidenav/sidenav-demo.ts +++ b/src/demo-app/sidenav/sidenav-demo.ts @@ -9,5 +9,5 @@ import {Component, ViewEncapsulation} from '@angular/core'; encapsulation: ViewEncapsulation.None, }) export class SidenavDemo { - side = 'start'; + invert = false; } diff --git a/src/demo-app/slide-toggle/slide-toggle-demo.html b/src/demo-app/slide-toggle/slide-toggle-demo.html index 03a1c71a03f0..609c56444b7f 100644 --- a/src/demo-app/slide-toggle/slide-toggle-demo.html +++ b/src/demo-app/slide-toggle/slide-toggle-demo.html @@ -1,4 +1,4 @@ -
+
Default Slide Toggle diff --git a/src/demo-app/slide-toggle/slide-toggle-demo.scss b/src/demo-app/slide-toggle/slide-toggle-demo.scss index e69de29bb2d1..167b6e7239ff 100644 --- a/src/demo-app/slide-toggle/slide-toggle-demo.scss +++ b/src/demo-app/slide-toggle/slide-toggle-demo.scss @@ -0,0 +1,5 @@ +.slide-toggle-example { + display: flex; + flex-direction: column; + align-items: flex-start; +} \ No newline at end of file diff --git a/src/demo-app/slider/slider-demo.html b/src/demo-app/slider/slider-demo.html index 160231e67072..88eab53625d9 100644 --- a/src/demo-app/slider/slider-demo.html +++ b/src/demo-app/slider/slider-demo.html @@ -9,8 +9,8 @@

Slider with Min and Max

Disabled Slider

- -{{slider3.value}} + +

Slider with set value

diff --git a/src/demo-app/slider/slider-demo.ts b/src/demo-app/slider/slider-demo.ts index adf66ca9e442..780c6a61b0c2 100644 --- a/src/demo-app/slider/slider-demo.ts +++ b/src/demo-app/slider/slider-demo.ts @@ -11,4 +11,5 @@ export class SliderDemo { val: number = 50; min: number = 0; max: number = 100; + disabledValue = 0; } diff --git a/src/demo-app/snack-bar/snack-bar-demo.html b/src/demo-app/snack-bar/snack-bar-demo.html index bbfefec80e4c..9fdc08ac9d21 100644 --- a/src/demo-app/snack-bar/snack-bar-demo.html +++ b/src/demo-app/snack-bar/snack-bar-demo.html @@ -1,13 +1,13 @@

SnackBar demo

- Message: + Message:

Show button on snack bar

- SnackBar demo

Auto hide after duration

- SnackBar demo
+ +

+ Add extra class to container +

diff --git a/src/demo-app/snack-bar/snack-bar-demo.scss b/src/demo-app/snack-bar/snack-bar-demo.scss index 8d737ab226f6..b0b1052eae2e 100644 --- a/src/demo-app/snack-bar/snack-bar-demo.scss +++ b/src/demo-app/snack-bar/snack-bar-demo.scss @@ -1,3 +1,17 @@ -.demo-button-label-input { - display: inline-block; -} \ No newline at end of file +.party { + animation: party 5000ms infinite; +} + +@keyframes party { + 0% { background: #00f; } + 10% { background: #8e44ad; } + 20% { background: #1abc9c; } + 30% { background: #d35400; } + 40% { background: #00f; } + 50% { background: #34495e; } + 60% { background: #00f; } + 70% { background: #2980b9; } + 80% { background: #f1c40f; } + 90% { background: #2980b9; } + 100% { background: #0ff; } +} diff --git a/src/demo-app/snack-bar/snack-bar-demo.ts b/src/demo-app/snack-bar/snack-bar-demo.ts index 344d3ed11eb1..baad2beac03d 100644 --- a/src/demo-app/snack-bar/snack-bar-demo.ts +++ b/src/demo-app/snack-bar/snack-bar-demo.ts @@ -1,24 +1,27 @@ -import {Component} from '@angular/core'; +import {Component, ViewEncapsulation} from '@angular/core'; import {MdSnackBar, MdSnackBarConfig} from '@angular/material'; @Component({ moduleId: module.id, selector: 'snack-bar-demo', + styleUrls: ['snack-bar-demo.css'], templateUrl: 'snack-bar-demo.html', + encapsulation: ViewEncapsulation.None, }) export class SnackBarDemo { message: string = 'Snack Bar opened.'; actionButtonLabel: string = 'Retry'; action: boolean = false; setAutoHide: boolean = true; - autoHide: number = 0; + autoHide: number = 10000; + addExtraClass: boolean = false; - constructor( - public snackBar: MdSnackBar) { } + constructor(public snackBar: MdSnackBar) { } open() { let config = new MdSnackBarConfig(); config.duration = this.autoHide; + config.extraClasses = this.addExtraClass ? ['party'] : null; this.snackBar.open(this.message, this.action && this.actionButtonLabel, config); } } diff --git a/src/demo-app/style/style-demo.html b/src/demo-app/style/style-demo.html new file mode 100644 index 000000000000..81786735f85c --- /dev/null +++ b/src/demo-app/style/style-demo.html @@ -0,0 +1,21 @@ + + + + + + + + +
Active classes: {{b.classList}}
+ +
+ +
+

div with element focus monitored

+ +
+ +
+

div with subtree focus monitored

+ +
diff --git a/src/demo-app/style/style-demo.scss b/src/demo-app/style/style-demo.scss new file mode 100644 index 000000000000..ddfd64de6bb3 --- /dev/null +++ b/src/demo-app/style/style-demo.scss @@ -0,0 +1,19 @@ +.demo-focusable.cdk-focused { + border: 2px solid red; +} + +.demo-focusable.cdk-mouse-focused { + background: green; +} + +.demo-focusable.cdk-keyboard-focused { + background: yellow; +} + +.demo-focusable.cdk-program-focused { + background: blue; +} + +.demo-focusable.cdk-touch-focused { + background: purple; +} diff --git a/src/demo-app/style/style-demo.ts b/src/demo-app/style/style-demo.ts new file mode 100644 index 000000000000..e1151da131f8 --- /dev/null +++ b/src/demo-app/style/style-demo.ts @@ -0,0 +1,13 @@ +import {Component, Renderer} from '@angular/core'; +import {FocusOriginMonitor} from '@angular/material'; + + +@Component({ + moduleId: module.id, + selector: 'style-demo', + templateUrl: 'style-demo.html', + styleUrls: ['style-demo.css'], +}) +export class StyleDemo { + constructor(public renderer: Renderer, public fom: FocusOriginMonitor) {} +} diff --git a/src/demo-app/system-config.ts b/src/demo-app/system-config.ts index 897388093249..58a48554a870 100644 --- a/src/demo-app/system-config.ts +++ b/src/demo-app/system-config.ts @@ -1,5 +1,5 @@ /** Type declaration for ambient System. */ -declare var System: any; +declare const System: any; // Apply the CLI SystemJS configuration. System.config({ diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index 26f9455ab1a1..902009497c67 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -18,7 +18,9 @@

Tab Group Demo - Dynamic Tabs

Selected tab index: - + + +
@@ -33,7 +35,9 @@

Tab Group Demo - Dynamic Tabs

Position: - + + +
+
+ + +
+ +

Tab Group Demo - Dynamic Height

+ + + + +
{{tab.content}}

@@ -77,101 +134,56 @@

Tab Group Demo - Dynamic Tabs



- -

- -
-
-
- -

Tab Group Demo - Dynamic Height

- - - - - {{tab.content}} -
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. - Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. - In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, - feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, - orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius - gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat - diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod - ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim - venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. - Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec - orci posuere, nec luctus mauris semper. -
-
- Morbi viverra, ante vel aliquet tincidunt, leo dolor pharetra quam, at semper massa orci nec - magna. Donec posuere nec sapien sed laoreet. Etiam cursus nunc in condimentum facilisis. - Etiam in tempor tortor. Vivamus faucibus egestas enim, at convallis diam pulvinar vel. - Cras ac orci eget nisi maximus cursus. Nunc urna libero, viverra sit amet nisl at, hendrerit - tempor turpis. Maecenas facilisis convallis mi vel tempor. Nullam vitae nunc leo. Cras sed - nisl consectetur, rhoncus sapien sit amet, tempus sapien. -
-
- Integer turpis erat, porttitor vitae mi faucibus, laoreet interdum tellus. Curabitur posuere - molestie dictum. Morbi eget congue risus, quis rhoncus quam. Suspendisse vitae hendrerit erat, - at posuere mi. Cras eu fermentum nunc. Sed id ante eu orci commodo volutpat non ac est. - Praesent ligula diam, congue eu enim scelerisque, finibus commodo lectus. + + +
-
-
- - -

Tab Group Demo - Fixed Height

- + - {{tab.content}} -
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. - Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. - In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, - feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, - orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius - gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat - diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod - ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim - venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. - Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec - orci posuere, nec luctus mauris semper. +
+ {{tab.content}}

- Morbi viverra, ante vel aliquet tincidunt, leo dolor pharetra quam, at semper massa orci nec - magna. Donec posuere nec sapien sed laoreet. Etiam cursus nunc in condimentum facilisis. - Etiam in tempor tortor. Vivamus faucibus egestas enim, at convallis diam pulvinar vel. - Cras ac orci eget nisi maximus cursus. Nunc urna libero, viverra sit amet nisl at, hendrerit - tempor turpis. Maecenas facilisis convallis mi vel tempor. Nullam vitae nunc leo. Cras sed - nisl consectetur, rhoncus sapien sit amet, tempus sapien. +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. + Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. + In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, + feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, + orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius + gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat + diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod + ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim + venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. + Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec + orci posuere, nec luctus mauris semper. +
+
+ Morbi viverra, ante vel aliquet tincidunt, leo dolor pharetra quam, at semper massa orci nec + magna. Donec posuere nec sapien sed laoreet. Etiam cursus nunc in condimentum facilisis. + Etiam in tempor tortor. Vivamus faucibus egestas enim, at convallis diam pulvinar vel. + Cras ac orci eget nisi maximus cursus. Nunc urna libero, viverra sit amet nisl at, hendrerit + tempor turpis. Maecenas facilisis convallis mi vel tempor. Nullam vitae nunc leo. Cras sed + nisl consectetur, rhoncus sapien sit amet, tempus sapien. +
+
+ Integer turpis erat, porttitor vitae mi faucibus, laoreet interdum tellus. Curabitur posuere + molestie dictum. Morbi eget congue risus, quis rhoncus quam. Suspendisse vitae hendrerit erat, + at posuere mi. Cras eu fermentum nunc. Sed id ante eu orci commodo volutpat non ac est. + Praesent ligula diam, congue eu enim scelerisque, finibus commodo lectus. +


- Integer turpis erat, porttitor vitae mi faucibus, laoreet interdum tellus. Curabitur posuere - molestie dictum. Morbi eget congue risus, quis rhoncus quam. Suspendisse vitae hendrerit erat, - at posuere mi. Cras eu fermentum nunc. Sed id ante eu orci commodo volutpat non ac est. - Praesent ligula diam, congue eu enim scelerisque, finibus commodo lectus. + + +
-
-
- - - @@ -180,7 +192,9 @@

Stretched Tabs

- {{tab.content}} +
+ {{tab.content}} +
@@ -190,13 +204,16 @@

Async Tabs

- {{tab.content}} -
-
-
- - - + +
+ {{tab.content}} +
+
+
+ + + +
@@ -204,9 +221,25 @@

Async Tabs

Tabs with simplified api

- This tab is about the Earth! +
+ This tab is about the Earth! +
This tab is about combustion!
+ +

Inverted tabs

+ + +
+ This tab is about the Earth! +
+
+ +
+ This tab is about combustion! +
+
+
diff --git a/src/demo-app/tabs/tabs-demo.scss b/src/demo-app/tabs/tabs-demo.scss index 6a3df2e25f5a..4d93d9598cae 100644 --- a/src/demo-app/tabs/tabs-demo.scss +++ b/src/demo-app/tabs/tabs-demo.scss @@ -1,7 +1,7 @@ .demo-nav-bar { border: 1px solid #e0e0e0; margin-bottom: 40px; - [md-tab-nav-bar] { + .mat-tab-nav-bar { background: #f9f9f9; } sunny-routed-content, @@ -15,15 +15,13 @@ .demo-tab-group { border: 1px solid #e0e0e0; margin-bottom: 40px; - .md-tab-header { + flex-grow: 1; + .mat-tab-header { background: #f9f9f9; } - .md-tab-body-content { - padding: 12px; - } } -tabs-demo md-card { +tabs-demo .mat-card { width: 160px; } @@ -32,22 +30,22 @@ tabs-demo md-card { display: flex; width: 100%; - md-tab-group { + .mat-tab-group { min-width: 200px; margin-bottom: 0; flex-grow: 1; } - md-card { + .mat-card { min-width: 160px; margin-right: 16px; - md-checkbox { + .mat-checkbox { display: block; margin-top: 8px; } - md-input { + .mat-input-container { width: 100px; } @@ -55,4 +53,8 @@ tabs-demo md-card { width: 100%; } } -} \ No newline at end of file +} + +.tab-content { + padding: 16px; +} diff --git a/src/demo-app/tooltip/tooltip-demo.html b/src/demo-app/tooltip/tooltip-demo.html index 13031ea41301..15025d7a1df3 100644 --- a/src/demo-app/tooltip/tooltip-demo.html +++ b/src/demo-app/tooltip/tooltip-demo.html @@ -1,17 +1,19 @@

Tooltip Demo

-

+

-

+
Scroll down while tooltip is open to see it hide automatically
+
+

@@ -26,17 +28,21 @@

Tooltip Demo

Message: - +

Show Delay (ms): - + + +

Hide Delay (ms): - + + +

Mouse over to diff --git a/src/demo-app/tooltip/tooltip-demo.scss b/src/demo-app/tooltip/tooltip-demo.scss index 39c9602a9e72..c2483c856273 100644 --- a/src/demo-app/tooltip/tooltip-demo.scss +++ b/src/demo-app/tooltip/tooltip-demo.scss @@ -1,8 +1,14 @@ .demo-tooltip { .centered { text-align: center; + height: 200px; + overflow: auto; + + button { + margin: 16px; + } } - md-radio-button { + .mat-radio-button { display: block; } } diff --git a/src/demo-app/tooltip/tooltip-demo.ts b/src/demo-app/tooltip/tooltip-demo.ts index 56046e7156c7..96a59b7e53cb 100644 --- a/src/demo-app/tooltip/tooltip-demo.ts +++ b/src/demo-app/tooltip/tooltip-demo.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, ChangeDetectionStrategy} from '@angular/core'; import {TooltipPosition} from '@angular/material'; @@ -7,10 +7,11 @@ import {TooltipPosition} from '@angular/material'; selector: 'tooltip-demo', templateUrl: 'tooltip-demo.html', styleUrls: ['tooltip-demo.css'], + changeDetection: ChangeDetectionStrategy.OnPush // make sure tooltip also works OnPush }) export class TooltipDemo { position: TooltipPosition = 'below'; message: string = 'Here is the tooltip'; showDelay = 0; - hideDelay = 0; + hideDelay = 1000; } diff --git a/src/demo-app/tsconfig-aot.json b/src/demo-app/tsconfig-aot.json new file mode 100644 index 000000000000..9bc63d65e2c2 --- /dev/null +++ b/src/demo-app/tsconfig-aot.json @@ -0,0 +1,31 @@ +/* Config file for the Angular Compiler. Paths need to be relative to the dist folder. */ +{ + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es6", "es2015", "dom"], + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "target": "es5", + "baseUrl": "", + "typeRoots": [ + "../node_modules/@types/!(node)" + ], + "paths": { + "@angular/material": [ + "./@angular/material" + ] + } + }, + "files": [ + "./demo-app-types.d.ts", + "./demo-app-module.ts", + "./main-aot.ts" + ], + "angularCompilerOptions": { + "skipTemplateCodegen": false, + "debug": true + } +} diff --git a/src/demo-app/tsconfig.json b/src/demo-app/tsconfig.json index f8acc9304ae2..21b70e287a20 100644 --- a/src/demo-app/tsconfig.json +++ b/src/demo-app/tsconfig.json @@ -1,10 +1,9 @@ { "compilerOptions": { - "declaration": true, + "declaration": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": ["es6", "es2015", "dom"], - "mapRoot": "/", "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, @@ -15,7 +14,7 @@ "stripInternal": false, "baseUrl": "", "typeRoots": [ - "../../node_modules/@types" + "../../node_modules/@types/!(node)" ], "paths": { "@angular/material": [ @@ -23,6 +22,12 @@ ] } }, + "files": [ + "./demo-app-types.d.ts", + "./demo-app-module.ts", + "./system-config.ts", + "./main.ts" + ], "angularCompilerOptions": { "genDir": "../../dist", "skipTemplateCodegen": true, diff --git a/src/e2e-app/dialog/dialog-e2e.html b/src/e2e-app/dialog/dialog-e2e.html index 827bfee99ca0..b66e8dfd8c51 100644 --- a/src/e2e-app/dialog/dialog-e2e.html +++ b/src/e2e-app/dialog/dialog-e2e.html @@ -1,2 +1,5 @@ + + + diff --git a/src/e2e-app/dialog/dialog-e2e.ts b/src/e2e-app/dialog/dialog-e2e.ts index 068406bbc5cb..09f833d96d0b 100644 --- a/src/e2e-app/dialog/dialog-e2e.ts +++ b/src/e2e-app/dialog/dialog-e2e.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, ViewChild, TemplateRef} from '@angular/core'; import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; @Component({ @@ -9,6 +9,8 @@ import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; export class DialogE2E { dialogRef: MdDialogRef; + @ViewChild(TemplateRef) templateRef: TemplateRef; + constructor (private _dialog: MdDialog) { } private _openDialog(config?: MdDialogConfig) { @@ -28,6 +30,10 @@ export class DialogE2E { disableClose: true }); } + + openTemplate() { + this.dialogRef = this._dialog.open(this.templateRef); + } } @Component({ diff --git a/src/e2e-app/e2e-app-module.ts b/src/e2e-app/e2e-app-module.ts index a54832b2b831..091698206b8f 100644 --- a/src/e2e-app/e2e-app-module.ts +++ b/src/e2e-app/e2e-app-module.ts @@ -13,11 +13,11 @@ import {GridListE2E} from './grid-list/grid-list-e2e'; import {ListE2E} from './list/list-e2e'; import {ProgressBarE2E} from './progress-bar/progress-bar-e2e'; import {ProgressSpinnerE2E} from './progress-spinner/progress-spinner-e2e'; -import {MaterialModule} from '@angular/material'; +import {FullscreenE2E, TestDialog as TestDialogFullScreen} from './fullscreen/fullscreen-e2e'; +import {MaterialModule, OverlayContainer, FullscreenOverlayContainer} from '@angular/material'; import {E2E_APP_ROUTES} from './e2e-app/routes'; import {SlideToggleE2E} from './slide-toggle/slide-toggle-e2e'; - @NgModule({ imports: [ BrowserModule, @@ -39,12 +39,15 @@ import {SlideToggleE2E} from './slide-toggle/slide-toggle-e2e'; ListE2E, ProgressBarE2E, ProgressSpinnerE2E, - SlideToggleE2E + SlideToggleE2E, + FullscreenE2E, + TestDialogFullScreen ], bootstrap: [E2EApp], providers: [ - {provide: AnimationDriver, useValue: AnimationDriver.NOOP} + {provide: AnimationDriver, useValue: AnimationDriver.NOOP}, + {provide: OverlayContainer, useClass: FullscreenOverlayContainer} ], - entryComponents: [TestDialog] + entryComponents: [TestDialog, TestDialogFullScreen] }) export class E2eAppModule { } diff --git a/src/e2e-app/typings.d.ts b/src/e2e-app/e2e-app-types.d.ts similarity index 100% rename from src/e2e-app/typings.d.ts rename to src/e2e-app/e2e-app-types.d.ts diff --git a/src/e2e-app/e2e-app/e2e-app.html b/src/e2e-app/e2e-app/e2e-app.html index 43a251d80bac..753f9be0f8be 100644 --- a/src/e2e-app/e2e-app/e2e-app.html +++ b/src/e2e-app/e2e-app/e2e-app.html @@ -13,6 +13,7 @@ Radios Slide Toggle Tabs + Fullscreen
diff --git a/src/e2e-app/e2e-app/routes.ts b/src/e2e-app/e2e-app/routes.ts index 94b69fc33c04..5d9d6e4687a0 100644 --- a/src/e2e-app/e2e-app/routes.ts +++ b/src/e2e-app/e2e-app/routes.ts @@ -12,6 +12,7 @@ import {ListE2E} from '../list/list-e2e'; import {ProgressBarE2E} from '../progress-bar/progress-bar-e2e'; import {ProgressSpinnerE2E} from '../progress-spinner/progress-spinner-e2e'; import {SlideToggleE2E} from '../slide-toggle/slide-toggle-e2e'; +import {FullscreenE2E} from '../fullscreen/fullscreen-e2e'; export const E2E_APP_ROUTES: Routes = [ {path: '', component: Home}, @@ -26,5 +27,6 @@ export const E2E_APP_ROUTES: Routes = [ {path: 'list', component: ListE2E}, {path: 'progress-bar', component: ProgressBarE2E}, {path: 'progress-spinner', component: ProgressSpinnerE2E}, - {path: 'slide-toggle', component: SlideToggleE2E} + {path: 'slide-toggle', component: SlideToggleE2E}, + {path: 'fullscreen', component: FullscreenE2E} ]; diff --git a/src/e2e-app/fullscreen/fullscreen-e2e.html b/src/e2e-app/fullscreen/fullscreen-e2e.html new file mode 100644 index 000000000000..834885caefb1 --- /dev/null +++ b/src/e2e-app/fullscreen/fullscreen-e2e.html @@ -0,0 +1,6 @@ + + +
+ + +
diff --git a/src/e2e-app/fullscreen/fullscreen-e2e.ts b/src/e2e-app/fullscreen/fullscreen-e2e.ts new file mode 100644 index 000000000000..f95dc7a46147 --- /dev/null +++ b/src/e2e-app/fullscreen/fullscreen-e2e.ts @@ -0,0 +1,62 @@ +import {Component, ElementRef, Output, EventEmitter} from '@angular/core'; +import {MdDialog, MdDialogRef} from '@angular/material'; + +@Component({ + moduleId: module.id, + selector: 'fullscreen-e2e', + templateUrl: 'fullscreen-e2e.html' +}) +export class FullscreenE2E { + + dialogRef: MdDialogRef; + + constructor (private _element: ElementRef, private _dialog: MdDialog) { } + + openDialog() { + this.dialogRef = this._dialog.open(TestDialog); + + this.dialogRef.componentInstance.openFullscreen.subscribe(() => this.openFullscreen()); + this.dialogRef.componentInstance.exitFullscreen.subscribe(() => this.exitFullscreen()); + this.dialogRef.afterClosed().subscribe(() => this.dialogRef = null); + } + + openFullscreen() { + let element = this._element.nativeElement.querySelector('#fullscreen-pane'); + + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.webkitRequestFullScreen) { + element.webkitRequestFullScreen(); + } else if ((element as any).mozRequestFullScreen) { + (element as any).mozRequestFullScreen(); + } else if ((element as any).msRequestFullScreen) { + (element as any).msRequestFullScreen(); + } + } + + exitFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if ((document as any).mozExitFullScreen) { + (document as any).mozExitFullScreen(); + } else if ((document as any).msExitFullScreen) { + (document as any).msExitFullScreen(); + } + } +} + +@Component({ + template: ` + + + + ` +}) +export class TestDialog { + @Output() openFullscreen = new EventEmitter(); + @Output() exitFullscreen = new EventEmitter(); + + constructor(public dialogRef: MdDialogRef) {} +} diff --git a/src/e2e-app/index.html b/src/e2e-app/index.html index 33f6caabcb10..e9ce94a3d3f7 100644 --- a/src/e2e-app/index.html +++ b/src/e2e-app/index.html @@ -3,7 +3,7 @@ - Material2 + Angular Material @@ -17,6 +17,7 @@ Loading... + diff --git a/src/e2e-app/menu/menu-e2e.html b/src/e2e-app/menu/menu-e2e.html index d9c8040161d8..bd80754a8dc6 100644 --- a/src/e2e-app/menu/menu-e2e.html +++ b/src/e2e-app/menu/menu-e2e.html @@ -5,7 +5,7 @@ - + @@ -15,20 +15,20 @@ - +
- + - + diff --git a/src/e2e-app/radio/radio-e2e.ts b/src/e2e-app/radio/radio-e2e.ts index 5df27b942fcc..1959c19bcc08 100644 --- a/src/e2e-app/radio/radio-e2e.ts +++ b/src/e2e-app/radio/radio-e2e.ts @@ -7,4 +7,5 @@ import {Component} from '@angular/core'; }) export class SimpleRadioButtons { isGroupDisabled: boolean = false; + groupValue: string; } diff --git a/src/e2e-app/system-config.ts b/src/e2e-app/system-config.ts index ae360cddecfd..e5615524cb8e 100644 --- a/src/e2e-app/system-config.ts +++ b/src/e2e-app/system-config.ts @@ -1,5 +1,5 @@ /** Type declaration for ambient System. */ -declare var System: any; +declare const System: any; // Apply the CLI SystemJS configuration. System.config({ diff --git a/src/e2e-app/tsconfig.json b/src/e2e-app/tsconfig.json index 898fe4d4aceb..bc4ee42255a7 100644 --- a/src/e2e-app/tsconfig.json +++ b/src/e2e-app/tsconfig.json @@ -4,7 +4,6 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": ["es6", "es2015", "dom"], - "mapRoot": "", "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, @@ -15,8 +14,7 @@ "stripInternal": false, "baseUrl": "", "typeRoots": [ - "../../node_modules/@types", - "../../node_modules" + "../../node_modules/@types/!(node)" ], "paths": { "@angular/material": [ diff --git a/src/examples/autocomplete-overview/autocomplete-overview-example.css b/src/examples/autocomplete-overview/autocomplete-overview-example.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/examples/autocomplete-overview/autocomplete-overview-example.html b/src/examples/autocomplete-overview/autocomplete-overview-example.html new file mode 100644 index 000000000000..e5217f9c2f52 --- /dev/null +++ b/src/examples/autocomplete-overview/autocomplete-overview-example.html @@ -0,0 +1,9 @@ + + + + + + + {{ state }} + + \ No newline at end of file diff --git a/src/examples/autocomplete-overview/autocomplete-overview-example.ts b/src/examples/autocomplete-overview/autocomplete-overview-example.ts new file mode 100644 index 000000000000..7b28b28b998b --- /dev/null +++ b/src/examples/autocomplete-overview/autocomplete-overview-example.ts @@ -0,0 +1,78 @@ +import {Component} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import 'rxjs/add/operator/startWith'; + +@Component({ + selector: 'autocomplete-overview-example', + templateUrl: './autocomplete-overview-example.html', +}) +export class AutocompleteOverviewExample { + stateCtrl: FormControl; + filteredStates: any; + + states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', + ]; + + constructor() { + this.stateCtrl = new FormControl(); + this.filteredStates = this.stateCtrl.valueChanges + .startWith(null) + .map(name => this.filterStates(name)); + } + + filterStates(val: string) { + return val ? this.states.filter(s => new RegExp(`^${val}`, 'gi').test(s)) + : this.states; + } + +} diff --git a/src/examples/button-overview/button-overview-example.css b/src/examples/button-overview/button-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/button-overview/button-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/button-overview/button-overview-example.html b/src/examples/button-overview/button-overview-example.html new file mode 100644 index 000000000000..56f6fa44fb6b --- /dev/null +++ b/src/examples/button-overview/button-overview-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/button-overview/button-overview-example.ts b/src/examples/button-overview/button-overview-example.ts new file mode 100644 index 000000000000..41a52022f263 --- /dev/null +++ b/src/examples/button-overview/button-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'button-overview-example', + templateUrl: './button-overview-example.html', +}) +export class ButtonOverviewExample {} diff --git a/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.css b/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.css new file mode 100644 index 000000000000..d21ca3ce57fb --- /dev/null +++ b/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.css @@ -0,0 +1,3 @@ +.example-selected-value { + margin: 15px 0; +} diff --git a/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.html b/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.html new file mode 100644 index 000000000000..dd5cd2771788 --- /dev/null +++ b/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.html @@ -0,0 +1,15 @@ + + + format_align_left + + + format_align_center + + + format_align_right + + + format_align_justify + + +
Selected value: {{group.value}}
diff --git a/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.ts b/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.ts new file mode 100644 index 000000000000..d014b8a2846d --- /dev/null +++ b/src/examples/button-toggle-exclusive/button-toggle-exclusive-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'button-toggle-exclusive-example', + templateUrl: './button-toggle-exclusive-example.html', + styleUrls: ['./button-toggle-exclusive-example.css'], +}) +export class ButtonToggleExclusiveExample {} diff --git a/src/examples/button-toggle-overview/button-toggle-overview-example.css b/src/examples/button-toggle-overview/button-toggle-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/button-toggle-overview/button-toggle-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/button-toggle-overview/button-toggle-overview-example.html b/src/examples/button-toggle-overview/button-toggle-overview-example.html new file mode 100644 index 000000000000..bb215d5b5425 --- /dev/null +++ b/src/examples/button-toggle-overview/button-toggle-overview-example.html @@ -0,0 +1 @@ +Toggle me! diff --git a/src/examples/button-toggle-overview/button-toggle-overview-example.ts b/src/examples/button-toggle-overview/button-toggle-overview-example.ts new file mode 100644 index 000000000000..38a4afd0561d --- /dev/null +++ b/src/examples/button-toggle-overview/button-toggle-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'button-toggle-overview-example', + templateUrl: './button-toggle-overview-example.html', +}) +export class ButtonToggleOverviewExample {} diff --git a/src/examples/button-types/button-types-example.css b/src/examples/button-types/button-types-example.css new file mode 100644 index 000000000000..b0b34952fcc1 --- /dev/null +++ b/src/examples/button-types/button-types-example.css @@ -0,0 +1,9 @@ +.example-container { + width: 500px; +} + +.example-button-row { + display: flex; + align-items: center; + justify-content: space-around; +} diff --git a/src/examples/button-types/button-types-example.html b/src/examples/button-types/button-types-example.html new file mode 100644 index 000000000000..4c5783de9c7a --- /dev/null +++ b/src/examples/button-types/button-types-example.html @@ -0,0 +1,17 @@ +
+

Normal Buttons

+
+ + + + +
+ +

Link Buttons

+ +
diff --git a/src/examples/button-types/button-types-example.ts b/src/examples/button-types/button-types-example.ts new file mode 100644 index 000000000000..9dbb329f1e0d --- /dev/null +++ b/src/examples/button-types/button-types-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'button-types-example', + templateUrl: './button-types-example.html', + styleUrls: ['./button-types-example.css'], +}) +export class ButtonTypesExample {} diff --git a/src/examples/card-fancy/card-fancy-example.css b/src/examples/card-fancy/card-fancy-example.css new file mode 100644 index 000000000000..daef28f16152 --- /dev/null +++ b/src/examples/card-fancy/card-fancy-example.css @@ -0,0 +1,8 @@ +.example-card { + width: 400px; +} + +.example-header-image { + background-image: url('assets/img/examples/shiba1.jpg'); + background-size: cover; +} diff --git a/src/examples/card-fancy/card-fancy-example.html b/src/examples/card-fancy/card-fancy-example.html new file mode 100644 index 000000000000..07d25dfb8d8e --- /dev/null +++ b/src/examples/card-fancy/card-fancy-example.html @@ -0,0 +1,19 @@ + + +
+ Shiba Inu + Dog Breed +
+ + +

+ The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. + A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally + bred for hunting. +

+
+ + + + +
diff --git a/src/examples/card-fancy/card-fancy-example.ts b/src/examples/card-fancy/card-fancy-example.ts new file mode 100644 index 000000000000..8e19840d7cd4 --- /dev/null +++ b/src/examples/card-fancy/card-fancy-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'card-fancy-example', + templateUrl: './card-fancy-example.html', + styleUrls: ['./card-fancy-example.css'], +}) +export class CardFancyExample {} diff --git a/src/examples/card-overview/card-overview-example.css b/src/examples/card-overview/card-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/card-overview/card-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/card-overview/card-overview-example.html b/src/examples/card-overview/card-overview-example.html new file mode 100644 index 000000000000..5e0c2b6dfe13 --- /dev/null +++ b/src/examples/card-overview/card-overview-example.html @@ -0,0 +1 @@ +Simple card diff --git a/src/examples/card-overview/card-overview-example.ts b/src/examples/card-overview/card-overview-example.ts new file mode 100644 index 000000000000..55fc9a690d54 --- /dev/null +++ b/src/examples/card-overview/card-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'card-overview-example', + templateUrl: './card-overview-example.html', +}) +export class CardOverviewExample {} diff --git a/src/examples/checkbox-configurable/checkbox-configurable-example.css b/src/examples/checkbox-configurable/checkbox-configurable-example.css new file mode 100644 index 000000000000..558293fbb5b8 --- /dev/null +++ b/src/examples/checkbox-configurable/checkbox-configurable-example.css @@ -0,0 +1,14 @@ +.example-h2 { + margin: 10px; +} + +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 60px; +} + +.example-margin { + margin: 0 10px; +} diff --git a/src/examples/checkbox-configurable/checkbox-configurable-example.html b/src/examples/checkbox-configurable/checkbox-configurable-example.html new file mode 100644 index 000000000000..5876c05c6c46 --- /dev/null +++ b/src/examples/checkbox-configurable/checkbox-configurable-example.html @@ -0,0 +1,40 @@ + + +

Checkbox configuration

+ +
+ Checked + Indeterminate +
+ +
+ + + Start + End + +
+ +
+ Disabled +
+
+
+ + + +

Result

+ +
+ + I'm a checkbox + +
+
+
+ diff --git a/src/examples/checkbox-configurable/checkbox-configurable-example.ts b/src/examples/checkbox-configurable/checkbox-configurable-example.ts new file mode 100644 index 000000000000..2c0149199cc5 --- /dev/null +++ b/src/examples/checkbox-configurable/checkbox-configurable-example.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'checkbox-configurable-example', + templateUrl: './checkbox-configurable-example.html', + styleUrls: ['./checkbox-configurable-example.css'], +}) +export class CheckboxConfigurableExample { + checked = false; + indeterminate = false; + align = 'start'; + disabled = false; +} diff --git a/src/examples/checkbox-overview/checkbox-overview-example.css b/src/examples/checkbox-overview/checkbox-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/checkbox-overview/checkbox-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/checkbox-overview/checkbox-overview-example.html b/src/examples/checkbox-overview/checkbox-overview-example.html new file mode 100644 index 000000000000..c301bbe5ccc2 --- /dev/null +++ b/src/examples/checkbox-overview/checkbox-overview-example.html @@ -0,0 +1 @@ +Check me! diff --git a/src/examples/checkbox-overview/checkbox-overview-example.ts b/src/examples/checkbox-overview/checkbox-overview-example.ts new file mode 100644 index 000000000000..be9aefc592f0 --- /dev/null +++ b/src/examples/checkbox-overview/checkbox-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'checkbox-overview-example', + templateUrl: './checkbox-overview-example.html', +}) +export class CheckboxOverviewExample {} diff --git a/src/examples/chips-overview/chips-overview-example.css b/src/examples/chips-overview/chips-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/chips-overview/chips-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/chips-overview/chips-overview-example.html b/src/examples/chips-overview/chips-overview-example.html new file mode 100644 index 000000000000..bb0410569251 --- /dev/null +++ b/src/examples/chips-overview/chips-overview-example.html @@ -0,0 +1,6 @@ + + One fish + Two fish + Primary fish + Accent fish + diff --git a/src/examples/chips-overview/chips-overview-example.ts b/src/examples/chips-overview/chips-overview-example.ts new file mode 100644 index 000000000000..fa1bbf7c12c4 --- /dev/null +++ b/src/examples/chips-overview/chips-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'chips-overview-example', + templateUrl: './chips-overview-example.html', +}) +export class ChipsOverviewExample {} diff --git a/src/examples/chips-stacked/chips-stacked-example.css b/src/examples/chips-stacked/chips-stacked-example.css new file mode 100644 index 000000000000..dfb2f8c00aa2 --- /dev/null +++ b/src/examples/chips-stacked/chips-stacked-example.css @@ -0,0 +1,3 @@ +md-chip { + max-width: 200px; +} diff --git a/src/examples/chips-stacked/chips-stacked-example.html b/src/examples/chips-stacked/chips-stacked-example.html new file mode 100644 index 000000000000..27677b1c6e6d --- /dev/null +++ b/src/examples/chips-stacked/chips-stacked-example.html @@ -0,0 +1,7 @@ + + + {{chipColor.name}} + + diff --git a/src/examples/chips-stacked/chips-stacked-example.ts b/src/examples/chips-stacked/chips-stacked-example.ts new file mode 100644 index 000000000000..8c48ccc929e0 --- /dev/null +++ b/src/examples/chips-stacked/chips-stacked-example.ts @@ -0,0 +1,18 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'chips-stacked-example', + templateUrl: './chips-stacked-example.html', + styleUrls: ['./chips-stacked-example.css'], +}) +export class ChipsStackedExample { + color: string; + + availableColors = [ + { name: 'none', color: '' }, + { name: 'Primary', color: 'primary' }, + { name: 'Accent', color: 'accent' }, + { name: 'Warn', color: 'warn' } + ]; +} diff --git a/src/examples/dialog-elements/dialog-elements-example-dialog.html b/src/examples/dialog-elements/dialog-elements-example-dialog.html new file mode 100644 index 000000000000..5238b0eb6c19 --- /dev/null +++ b/src/examples/dialog-elements/dialog-elements-example-dialog.html @@ -0,0 +1,5 @@ +

Dialog with elements

+
This dialog showcases the title, close, content and actions elements.
+
+ +
diff --git a/src/examples/dialog-elements/dialog-elements-example.css b/src/examples/dialog-elements/dialog-elements-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/dialog-elements/dialog-elements-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/dialog-elements/dialog-elements-example.html b/src/examples/dialog-elements/dialog-elements-example.html new file mode 100644 index 000000000000..40637c4bd638 --- /dev/null +++ b/src/examples/dialog-elements/dialog-elements-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/dialog-elements/dialog-elements-example.ts b/src/examples/dialog-elements/dialog-elements-example.ts new file mode 100644 index 000000000000..aba941a571b0 --- /dev/null +++ b/src/examples/dialog-elements/dialog-elements-example.ts @@ -0,0 +1,22 @@ +import {Component} from '@angular/core'; +import {MdDialog} from '@angular/material'; + + +@Component({ + selector: 'dialog-elements-example', + templateUrl: './dialog-elements-example.html', +}) +export class DialogElementsExample { + constructor(public dialog: MdDialog) { } + + openDialog() { + this.dialog.open(DialogElementsExampleDialog); + } +} + + +@Component({ + selector: 'dialog-elements-example-dialog', + templateUrl: './dialog-elements-example-dialog.html', +}) +export class DialogElementsExampleDialog { } diff --git a/src/examples/dialog-overview/dialog-overview-example-dialog.html b/src/examples/dialog-overview/dialog-overview-example-dialog.html new file mode 100644 index 000000000000..751e1339b43c --- /dev/null +++ b/src/examples/dialog-overview/dialog-overview-example-dialog.html @@ -0,0 +1 @@ +

Hi, I'm a dialog!

diff --git a/src/examples/dialog-overview/dialog-overview-example.css b/src/examples/dialog-overview/dialog-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/dialog-overview/dialog-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/dialog-overview/dialog-overview-example.html b/src/examples/dialog-overview/dialog-overview-example.html new file mode 100644 index 000000000000..176a3c411e7f --- /dev/null +++ b/src/examples/dialog-overview/dialog-overview-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/dialog-overview/dialog-overview-example.ts b/src/examples/dialog-overview/dialog-overview-example.ts new file mode 100644 index 000000000000..9bc9882ac034 --- /dev/null +++ b/src/examples/dialog-overview/dialog-overview-example.ts @@ -0,0 +1,22 @@ +import {Component} from '@angular/core'; +import {MdDialog} from '@angular/material'; + + +@Component({ + selector: 'dialog-overview-example', + templateUrl: './dialog-overview-example.html', +}) +export class DialogOverviewExample { + constructor(public dialog: MdDialog) {} + + openDialog() { + this.dialog.open(DialogOverviewExampleDialog); + } +} + + +@Component({ + selector: 'dialog-overview-example-dialog', + templateUrl: './dialog-overview-example-dialog.html', +}) +export class DialogOverviewExampleDialog {} diff --git a/src/examples/dialog-result/dialog-result-example-dialog.html b/src/examples/dialog-result/dialog-result-example-dialog.html new file mode 100644 index 000000000000..0e4dd6339a94 --- /dev/null +++ b/src/examples/dialog-result/dialog-result-example-dialog.html @@ -0,0 +1,6 @@ +

Dialog

+
What would you like to do?
+
+ + +
diff --git a/src/examples/dialog-result/dialog-result-example.css b/src/examples/dialog-result/dialog-result-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/dialog-result/dialog-result-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/dialog-result/dialog-result-example.html b/src/examples/dialog-result/dialog-result-example.html new file mode 100644 index 000000000000..04850b42d247 --- /dev/null +++ b/src/examples/dialog-result/dialog-result-example.html @@ -0,0 +1,2 @@ + +You chose: {{selectedOption}} diff --git a/src/examples/dialog-result/dialog-result-example.ts b/src/examples/dialog-result/dialog-result-example.ts new file mode 100644 index 000000000000..ec25f92f1341 --- /dev/null +++ b/src/examples/dialog-result/dialog-result-example.ts @@ -0,0 +1,29 @@ +import {Component} from '@angular/core'; +import {MdDialog, MdDialogRef} from '@angular/material'; + + +@Component({ + selector: 'dialog-result-example', + templateUrl: './dialog-result-example.html', +}) +export class DialogResultExample { + selectedOption: string; + + constructor(public dialog: MdDialog) {} + + openDialog() { + let dialogRef = this.dialog.open(DialogResultExampleDialog); + dialogRef.afterClosed().subscribe(result => { + this.selectedOption = result; + }); + } +} + + +@Component({ + selector: 'dialog-result-example-dialog', + templateUrl: './dialog-result-example-dialog.html', +}) +export class DialogResultExampleDialog { + constructor(public dialogRef: MdDialogRef) {} +} diff --git a/src/examples/example-data.ts b/src/examples/example-data.ts new file mode 100644 index 000000000000..a1fc1c8838a6 --- /dev/null +++ b/src/examples/example-data.ts @@ -0,0 +1,48 @@ +import {EXAMPLE_COMPONENTS} from './example-module'; + + +/** + * Example data + * with information about Component name, selector, files used in example, and path to examples + */ +export class ExampleData { + // TODO: figure out how do we get these variables. + description: string = 'Some description for material'; + // TODO: use real example and delete the example/ folder. + examplePath = '/assets/example/'; + exampleFiles = ['button-demo.html', 'button-demo.scss', 'button-demo.ts']; + + // TODO: extract these variables from example code. + selectorName = 'button-demo'; + indexFilename = 'button-demo'; + componentName = 'ButtonDemo'; + + constructor(example: string) { + if (example && EXAMPLE_COMPONENTS[example]) { + this.examplePath = `/assets/plunker/examples/${example}/`; + // TODO(tinayuangao): Do not hard-code extensions + this.exampleFiles = ['html', 'ts', 'css'] + .map((extension) => `${example}-example.${extension}`); + if (EXAMPLE_COMPONENTS[example].additionalFiles) { + this.exampleFiles = this.exampleFiles.concat(EXAMPLE_COMPONENTS[example].additionalFiles); + } + this.selectorName = this.indexFilename = `${example}-example`; + + var exampleName = example.replace(/(?:^\w|\b\w)/g, function(letter) { + return letter.toUpperCase(); + }); + + if (EXAMPLE_COMPONENTS[example].title) { + this.description = EXAMPLE_COMPONENTS[example].title; + } else { + this.description = exampleName.replace(/[\-]+/g, ' ') + ' Example'; + } + + if (EXAMPLE_COMPONENTS[example].selectorName) { + this.componentName = EXAMPLE_COMPONENTS[example].selectorName; + } else { + this.componentName = exampleName.replace(/[\-]+/g, '') + 'Example'; + } + } + } +} diff --git a/src/examples/example-module.ts b/src/examples/example-module.ts new file mode 100644 index 000000000000..9c1b974af865 --- /dev/null +++ b/src/examples/example-module.ts @@ -0,0 +1,236 @@ +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {CommonModule} from '@angular/common'; +import {MaterialModule} from '@angular/material'; +import {AutocompleteOverviewExample} from './autocomplete-overview/autocomplete-overview-example'; +import {ButtonOverviewExample} from './button-overview/button-overview-example'; +import {ButtonTypesExample} from './button-types/button-types-example'; +import {CheckboxOverviewExample} from './checkbox-overview/checkbox-overview-example'; +import {SliderConfigurableExample} from './slider-configurable/slider-configurable-example'; +import {TabsOverviewExample} from './tabs-overview/tabs-overview-example'; +import { + PizzaPartyComponent, + SnackBarComponentExample +} from './snack-bar-component/snack-bar-component-example'; +import { + ProgressBarConfigurableExample +} from './progress-bar-configurable/progress-bar-configurable-example'; +import { + DialogOverviewExampleDialog, + DialogOverviewExample +} from './dialog-overview/dialog-overview-example'; +import {RadioNgModelExample} from './radio-ng-model/radio-ng-model-example'; +import {CardFancyExample} from './card-fancy/card-fancy-example'; +import {ToolbarOverviewExample} from './toolbar-overview/toolbar-overview-example'; +import {ToolbarMultirowExample} from './toolbar-multirow/toolbar-multirow-example'; +import {MenuIconsExample} from './menu-icons/menu-icons-example'; +import {GridListDynamicExample} from './grid-list-dynamic/grid-list-dynamic-example'; +import {IconOverviewExample} from './icon-overview/icon-overview-example'; +import {ProgressBarOverviewExample} from './progress-bar-overview/progress-bar-overview-example'; +import {SlideToggleOverviewExample} from './slide-toggle-overview/slide-toggle-overview-example'; +import {InputOverviewExample} from './input-overview/input-overview-example'; +import {MenuOverviewExample} from './menu-overview/menu-overview-example'; +import {CheckboxConfigurableExample} from './checkbox-configurable/checkbox-configurable-example'; +import { + ButtonToggleExclusiveExample +} from './button-toggle-exclusive/button-toggle-exclusive-example'; +import {ListSectionsExample} from './list-sections/list-sections-example'; +import {SnackBarOverviewExample} from './snack-bar-overview/snack-bar-overview-example'; +import { + DialogResultExampleDialog, + DialogResultExample +} from './dialog-result/dialog-result-example'; +import { + DialogElementsExampleDialog, + DialogElementsExample +} from './dialog-elements/dialog-elements-example'; +import {TooltipOverviewExample} from './tooltip-overview/tooltip-overview-example'; +import {ButtonToggleOverviewExample} from './button-toggle-overview/button-toggle-overview-example'; +import {GridListOverviewExample} from './grid-list-overview/grid-list-overview-example'; +import {TooltipPositionExample} from './tooltip-position/tooltip-position-example'; +import { + ProgressSpinnerConfigurableExample +} from './progress-spinner-configurable/progress-spinner-configurable-example'; +import {InputFormExample} from './input-form/input-form-example'; +import {ListOverviewExample} from './list-overview/list-overview-example'; +import {SliderOverviewExample} from './slider-overview/slider-overview-example'; +import { + SlideToggleConfigurableExample +} from './slide-toggle-configurable/slide-toggle-configurable-example'; +import {IconSvgExample} from './icon-svg-example/icon-svg-example'; +import {SidenavFabExample} from './sidenav-fab/sidenav-fab-example'; +import {CardOverviewExample} from './card-overview/card-overview-example'; +import { + ProgressSpinnerOverviewExample +} from './progress-spinner-overview/progress-spinner-overview-example'; +import {TabsTemplateLabelExample} from './tabs-template-label/tabs-template-label-example'; +import {RadioOverviewExample} from './radio-overview/radio-overview-example'; +import {SidenavOverviewExample} from './sidenav-overview/sidenav-overview-example'; +import {SelectOverviewExample} from './select-overview/select-overview-example'; +import {ChipsOverviewExample} from './chips-overview/chips-overview-example'; +import {ChipsStackedExample} from './chips-stacked/chips-stacked-example'; +import {SelectFormExample} from './select-form/select-form-example'; + + +export interface LiveExample { + title: string; + component: any; + additionalFiles: string[]; + selectorName: string; +} + +/** + * The list of example components. + * Key is the example name which will be used in `material-docs-example="key"`. + * Value is the component. + */ +export const EXAMPLE_COMPONENTS = { + 'autocomplete-overview': {title: 'Basic autocomplete', component: AutocompleteOverviewExample}, + 'button-overview': {title: 'Basic buttons', component: ButtonOverviewExample}, + 'button-types': {title: 'Button varieties', component: ButtonTypesExample}, + 'button-toggle-exclusive': { + title: 'Exclusive selection', + component: ButtonToggleExclusiveExample + }, + 'button-toggle-overview': {title: 'Basic button-toggles', component: ButtonToggleOverviewExample}, + 'chips-overview': {title: 'Basic chips', component: ChipsOverviewExample}, + 'chips-stacked': {title: 'Stacked chips', component: ChipsStackedExample}, + 'card-fancy': {title: 'Card with multiple sections', component: CardFancyExample}, + 'card-overview': {title: 'Basic cards', component: CardOverviewExample}, + 'checkbox-configurable': {title: 'Configurable checkbox', component: CheckboxConfigurableExample}, + 'checkbox-overview': {title: 'Basic checkboxes', component: CheckboxOverviewExample}, + 'dialog-overview': { + title: 'Basic dialog', + component: DialogOverviewExample, + additionalFiles: ['dialog-overview-example-dialog.html'], + selectorName: 'DialogOverviewExample, DialogOverviewExampleDialog', + }, + 'dialog-result': { + title: 'Dialog with a result', + component: DialogResultExample, + additionalFiles: ['dialog-result-example-dialog.html'], + selectorName: 'DialogResultExample, DialogResultExampleDialog', + }, + 'dialog-elements': { + title: 'Dialog elements', + component: DialogElementsExample, + additionalFiles: ['dialog-elements-example-dialog.html'], + selectorName: 'DialogElementsExample, DialogElementsExampleDialog', + }, + 'grid-list-dynamic': {title: 'Dynamic grid-list', component: GridListDynamicExample}, + 'grid-list-overview': {title: 'Basic grid-list', component: GridListOverviewExample}, + 'icon-overview': {title: 'Basic icons', component: IconOverviewExample}, + 'icon-svg': {title: 'SVG icons', component: IconSvgExample}, + 'input-form': {title: 'Inputs in a form', component: InputFormExample}, + 'input-overview': {title: 'Basic inputs', component: InputOverviewExample}, + 'list-overview': {title: 'Basic list', component: ListOverviewExample}, + 'list-sections': {title: 'List with sections', component: ListSectionsExample}, + 'menu-icons': {title: 'Menu with icons', component: MenuIconsExample}, + 'menu-overview': {title: 'Basic menu', component: MenuOverviewExample}, + 'progress-bar-configurable': { + title: 'Configurable progress-bar', + component: ProgressBarConfigurableExample + }, + 'progress-bar-overview': {title: 'Basic progress-bar', component: ProgressBarOverviewExample}, + 'progress-spinner-configurable': { + title: 'Configurable progress-bar', + component: ProgressSpinnerConfigurableExample + }, + 'progress-spinner-overview': { + title: 'Basic progress-spinner', + component: ProgressSpinnerOverviewExample + }, + 'radio-ng-model': {title: 'Radios with ngModel', component: RadioNgModelExample}, + 'radio-overview': {title: 'Basic radios', component: RadioOverviewExample}, + 'select-overview': {title: 'Basic select', component: SelectOverviewExample}, + 'select-form': {title: 'Select in a form', component: SelectFormExample}, + 'sidenav-fab': {title: 'Sidenav with a FAB', component: SidenavFabExample}, + 'sidenav-overview': {title: 'Basic sidenav', component: SidenavOverviewExample}, + 'slider-configurable': {title: 'Configurable slider', component: SliderConfigurableExample}, + 'slider-overview': {title: 'Basic slider', component: SliderOverviewExample}, + 'slide-toggle-configurable': { + title: 'Configurable slide-toggle', + component: SlideToggleConfigurableExample + }, + 'slide-toggle-overview': {title: 'Basic slide-toggles', component: SlideToggleOverviewExample}, + 'snack-bar-component': { + title: 'Snack-bar with a custom component', + component: SnackBarComponentExample + }, + 'snack-bar-overview': {title: 'Basic snack-bar', component: SnackBarOverviewExample}, + 'tabs-overview': {title: 'Basic tabs', component: TabsOverviewExample}, + 'tabs-template-label': {title: 'Coming soon!', component: TabsTemplateLabelExample}, + 'toolbar-multirow': {title: 'Multi-row toolbar', component: ToolbarMultirowExample}, + 'toolbar-overview': {title: 'basic toolbar', component: ToolbarOverviewExample}, + 'tooltip-overview': {title: 'Basic tooltip', component: TooltipOverviewExample}, + 'tooltip-position': {title: 'Tooltip with custom position', component: TooltipPositionExample}, +}; + +/** + * The list of all example components. + * We need to put them in both `declarations` and `entryComponents` to make them work. + */ +export const EXAMPLE_LIST = [ + AutocompleteOverviewExample, + ButtonOverviewExample, + ButtonToggleExclusiveExample, + ButtonToggleOverviewExample, + ButtonTypesExample, + CardFancyExample, + CardOverviewExample, + ChipsOverviewExample, + ChipsStackedExample, + CheckboxConfigurableExample, + CheckboxOverviewExample, + DialogOverviewExample, + DialogOverviewExampleDialog, + DialogResultExample, + DialogResultExampleDialog, + DialogElementsExample, + DialogElementsExampleDialog, + GridListDynamicExample, + GridListOverviewExample, + IconOverviewExample, + IconSvgExample, + InputFormExample, + InputOverviewExample, + ListOverviewExample, + ListSectionsExample, + MenuIconsExample, + MenuOverviewExample, + ProgressBarConfigurableExample, + ProgressBarOverviewExample, + ProgressSpinnerConfigurableExample, + ProgressSpinnerOverviewExample, + RadioNgModelExample, + RadioOverviewExample, + SidenavFabExample, + SelectOverviewExample, + SelectFormExample, + SidenavOverviewExample, + SliderConfigurableExample, + SliderOverviewExample, + SlideToggleConfigurableExample, + SlideToggleOverviewExample, + SnackBarComponentExample, + PizzaPartyComponent, + SnackBarOverviewExample, + TabsOverviewExample, + TabsTemplateLabelExample, + ToolbarMultirowExample, + ToolbarOverviewExample, + TooltipOverviewExample, + TooltipPositionExample, +]; + +@NgModule({ + declarations: EXAMPLE_LIST, + entryComponents: EXAMPLE_LIST, + imports: [ + MaterialModule, + FormsModule, + ReactiveFormsModule, + CommonModule, + ] +}) +export class ExampleModule { } diff --git a/src/examples/grid-list-dynamic/grid-list-dynamic-example.css b/src/examples/grid-list-dynamic/grid-list-dynamic-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/grid-list-dynamic/grid-list-dynamic-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/grid-list-dynamic/grid-list-dynamic-example.html b/src/examples/grid-list-dynamic/grid-list-dynamic-example.html new file mode 100644 index 000000000000..19c84dd5b853 --- /dev/null +++ b/src/examples/grid-list-dynamic/grid-list-dynamic-example.html @@ -0,0 +1,9 @@ + + + {{tile.text}} + + diff --git a/src/examples/grid-list-dynamic/grid-list-dynamic-example.ts b/src/examples/grid-list-dynamic/grid-list-dynamic-example.ts new file mode 100644 index 000000000000..8f0834fa0aeb --- /dev/null +++ b/src/examples/grid-list-dynamic/grid-list-dynamic-example.ts @@ -0,0 +1,15 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'grid-list-dynamic-example', + templateUrl: './grid-list-dynamic-example.html', +}) +export class GridListDynamicExample { + tiles = [ + {text: 'One', cols: 3, rows: 1, color: 'lightblue'}, + {text: 'Two', cols: 1, rows: 2, color: 'lightgreen'}, + {text: 'Three', cols: 1, rows: 1, color: 'lightpink'}, + {text: 'Four', cols: 2, rows: 1, color: '#DDBDF1'}, + ]; +} diff --git a/src/examples/grid-list-overview/grid-list-overview-example.css b/src/examples/grid-list-overview/grid-list-overview-example.css new file mode 100644 index 000000000000..2598f58f5190 --- /dev/null +++ b/src/examples/grid-list-overview/grid-list-overview-example.css @@ -0,0 +1,3 @@ +md-grid-tile { + background: lightblue; +} diff --git a/src/examples/grid-list-overview/grid-list-overview-example.html b/src/examples/grid-list-overview/grid-list-overview-example.html new file mode 100644 index 000000000000..6651887f603f --- /dev/null +++ b/src/examples/grid-list-overview/grid-list-overview-example.html @@ -0,0 +1,6 @@ + + 1 + 2 + 3 + 4 + diff --git a/src/examples/grid-list-overview/grid-list-overview-example.ts b/src/examples/grid-list-overview/grid-list-overview-example.ts new file mode 100644 index 000000000000..ba23f5740b98 --- /dev/null +++ b/src/examples/grid-list-overview/grid-list-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'grid-list-overview-example', + styleUrls: ['./grid-list-overview-example.css'], + templateUrl: './grid-list-overview-example.html', +}) +export class GridListOverviewExample {} diff --git a/src/examples/icon-overview/icon-overview-example.css b/src/examples/icon-overview/icon-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/icon-overview/icon-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/icon-overview/icon-overview-example.html b/src/examples/icon-overview/icon-overview-example.html new file mode 100644 index 000000000000..dd46af80f6cb --- /dev/null +++ b/src/examples/icon-overview/icon-overview-example.html @@ -0,0 +1 @@ +home diff --git a/src/examples/icon-overview/icon-overview-example.ts b/src/examples/icon-overview/icon-overview-example.ts new file mode 100644 index 000000000000..90ed28a61266 --- /dev/null +++ b/src/examples/icon-overview/icon-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'icon-overview-example', + templateUrl: './icon-overview-example.html', +}) +export class IconOverviewExample {} diff --git a/src/examples/icon-svg-example/icon-svg-example.css b/src/examples/icon-svg-example/icon-svg-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/icon-svg-example/icon-svg-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/icon-svg-example/icon-svg-example.html b/src/examples/icon-svg-example/icon-svg-example.html new file mode 100644 index 000000000000..ac74cf7908dd --- /dev/null +++ b/src/examples/icon-svg-example/icon-svg-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/icon-svg-example/icon-svg-example.ts b/src/examples/icon-svg-example/icon-svg-example.ts new file mode 100644 index 000000000000..b9689b4208a1 --- /dev/null +++ b/src/examples/icon-svg-example/icon-svg-example.ts @@ -0,0 +1,16 @@ +import {Component} from '@angular/core'; +import {DomSanitizer} from '@angular/platform-browser'; +import {MdIconRegistry} from '@angular/material'; + + +@Component({ + selector: 'icon-svg-example', + templateUrl: './icon-svg-example.html', +}) +export class IconSvgExample { + constructor(iconRegistry: MdIconRegistry, sanitizer: DomSanitizer) { + iconRegistry.addSvgIcon( + 'thumbs-up', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/examples/thumbup-icon.svg')); + } +} diff --git a/src/examples/input-form/input-form-example.css b/src/examples/input-form/input-form-example.css new file mode 100644 index 000000000000..269cb291ad0c --- /dev/null +++ b/src/examples/input-form/input-form-example.css @@ -0,0 +1,7 @@ +.example-form { + width: 500px; +} + +.example-full-width { + width: 100%; +} diff --git a/src/examples/input-form/input-form-example.html b/src/examples/input-form/input-form-example.html new file mode 100644 index 000000000000..04c3bb5ae756 --- /dev/null +++ b/src/examples/input-form/input-form-example.html @@ -0,0 +1,36 @@ +
+ + + + + + + +
+ + + +
+ +

+ + + + + + +

+ + + + + +
+ + + + + + {{postalCode.value.length}} / 5 +
+
diff --git a/src/examples/input-form/input-form-example.ts b/src/examples/input-form/input-form-example.ts new file mode 100644 index 000000000000..2e209d9f50ad --- /dev/null +++ b/src/examples/input-form/input-form-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'input-form-example', + templateUrl: './input-form-example.html', + styleUrls: ['./input-form-example.css'], +}) +export class InputFormExample {} diff --git a/src/examples/input-overview/input-overview-example.css b/src/examples/input-overview/input-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/input-overview/input-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/input-overview/input-overview-example.html b/src/examples/input-overview/input-overview-example.html new file mode 100644 index 000000000000..c864c5d3622d --- /dev/null +++ b/src/examples/input-overview/input-overview-example.html @@ -0,0 +1,3 @@ + + + diff --git a/src/examples/input-overview/input-overview-example.ts b/src/examples/input-overview/input-overview-example.ts new file mode 100644 index 000000000000..2bdc3fd6b206 --- /dev/null +++ b/src/examples/input-overview/input-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'input-overview-example', + templateUrl: './input-overview-example.html', +}) +export class InputOverviewExample {} diff --git a/src/examples/list-overview/list-overview-example.css b/src/examples/list-overview/list-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/list-overview/list-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/list-overview/list-overview-example.html b/src/examples/list-overview/list-overview-example.html new file mode 100644 index 000000000000..ada715ee0acf --- /dev/null +++ b/src/examples/list-overview/list-overview-example.html @@ -0,0 +1,5 @@ + + Item 1 + Item 2 + Item 3 + diff --git a/src/examples/list-overview/list-overview-example.ts b/src/examples/list-overview/list-overview-example.ts new file mode 100644 index 000000000000..f38e186020d8 --- /dev/null +++ b/src/examples/list-overview/list-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'list-overview-example', + templateUrl: './list-overview-example.html', +}) +export class ListOverviewExample {} diff --git a/src/examples/list-sections/list-sections-example.css b/src/examples/list-sections/list-sections-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/list-sections/list-sections-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/list-sections/list-sections-example.html b/src/examples/list-sections/list-sections-example.html new file mode 100644 index 000000000000..38d20f97eedc --- /dev/null +++ b/src/examples/list-sections/list-sections-example.html @@ -0,0 +1,15 @@ + +

Folders

+ + folder +

{{folder.name}}

+

{{folder.updated | date}}

+
+ +

Notes

+ + note +

{{note.name}}

+

{{note.updated | date}}

+
+
diff --git a/src/examples/list-sections/list-sections-example.ts b/src/examples/list-sections/list-sections-example.ts new file mode 100644 index 000000000000..d6fd703390aa --- /dev/null +++ b/src/examples/list-sections/list-sections-example.ts @@ -0,0 +1,33 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'list-sections-example', + templateUrl: './list-sections-example.html', +}) +export class ListSectionsExample { + folders = [ + { + name: 'Photos', + updated: new Date('1/1/16'), + }, + { + name: 'Recipes', + updated: new Date('1/17/16'), + }, + { + name: 'Work', + updated: new Date('1/28/16'), + } + ]; + notes = [ + { + name: 'Vacation Itinerary', + updated: new Date('2/20/16'), + }, + { + name: 'Kitchen Remodel', + updated: new Date('1/18/16'), + } + ]; +} diff --git a/src/examples/menu-icons/menu-icons-example.css b/src/examples/menu-icons/menu-icons-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/menu-icons/menu-icons-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/menu-icons/menu-icons-example.html b/src/examples/menu-icons/menu-icons-example.html new file mode 100644 index 000000000000..e14942451644 --- /dev/null +++ b/src/examples/menu-icons/menu-icons-example.html @@ -0,0 +1,17 @@ + + + + + + diff --git a/src/examples/menu-icons/menu-icons-example.ts b/src/examples/menu-icons/menu-icons-example.ts new file mode 100644 index 000000000000..070bc434bdb7 --- /dev/null +++ b/src/examples/menu-icons/menu-icons-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'menu-icons-example', + templateUrl: './menu-icons-example.html', +}) +export class MenuIconsExample {} diff --git a/src/examples/menu-overview/menu-overview-example.css b/src/examples/menu-overview/menu-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/menu-overview/menu-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/menu-overview/menu-overview-example.html b/src/examples/menu-overview/menu-overview-example.html new file mode 100644 index 000000000000..2b5d246c298e --- /dev/null +++ b/src/examples/menu-overview/menu-overview-example.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/examples/menu-overview/menu-overview-example.ts b/src/examples/menu-overview/menu-overview-example.ts new file mode 100644 index 000000000000..b5b8c8a8df47 --- /dev/null +++ b/src/examples/menu-overview/menu-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'menu-overview-example', + templateUrl: './menu-overview-example.html', +}) +export class MenuOverviewExample {} diff --git a/src/examples/progress-bar-configurable/progress-bar-configurable-example.css b/src/examples/progress-bar-configurable/progress-bar-configurable-example.css new file mode 100644 index 000000000000..558293fbb5b8 --- /dev/null +++ b/src/examples/progress-bar-configurable/progress-bar-configurable-example.css @@ -0,0 +1,14 @@ +.example-h2 { + margin: 10px; +} + +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 60px; +} + +.example-margin { + margin: 0 10px; +} diff --git a/src/examples/progress-bar-configurable/progress-bar-configurable-example.html b/src/examples/progress-bar-configurable/progress-bar-configurable-example.html new file mode 100644 index 000000000000..93937b77a8b8 --- /dev/null +++ b/src/examples/progress-bar-configurable/progress-bar-configurable-example.html @@ -0,0 +1,63 @@ + + +

Progress bar configuration

+ +
+ + + + Primary + + + Accent + + + Warn + + +
+ +
+ + + + Determinate + + + Indeterminate + + + Buffer + + + Query + + +
+ +
+ + +
+
+ + +
+
+
+ + + +

Result

+ +
+ + +
+
+
diff --git a/src/examples/progress-bar-configurable/progress-bar-configurable-example.ts b/src/examples/progress-bar-configurable/progress-bar-configurable-example.ts new file mode 100644 index 000000000000..f3fa937826bd --- /dev/null +++ b/src/examples/progress-bar-configurable/progress-bar-configurable-example.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'progress-bar-configurable-example', + templateUrl: './progress-bar-configurable-example.html', + styleUrls: ['./progress-bar-configurable-example.css'], +}) +export class ProgressBarConfigurableExample { + color = 'primary'; + mode = 'determinate'; + value = 50; + bufferValue = 75; +} diff --git a/src/examples/progress-bar-overview/progress-bar-overview-example.css b/src/examples/progress-bar-overview/progress-bar-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/progress-bar-overview/progress-bar-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/progress-bar-overview/progress-bar-overview-example.html b/src/examples/progress-bar-overview/progress-bar-overview-example.html new file mode 100644 index 000000000000..8ab16e9cfc13 --- /dev/null +++ b/src/examples/progress-bar-overview/progress-bar-overview-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/progress-bar-overview/progress-bar-overview-example.ts b/src/examples/progress-bar-overview/progress-bar-overview-example.ts new file mode 100644 index 000000000000..cf11f1cf2578 --- /dev/null +++ b/src/examples/progress-bar-overview/progress-bar-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'progress-bar-overview-example', + templateUrl: './progress-bar-overview-example.html', +}) +export class ProgressBarOverviewExample {} diff --git a/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.css b/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.css new file mode 100644 index 000000000000..558293fbb5b8 --- /dev/null +++ b/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.css @@ -0,0 +1,14 @@ +.example-h2 { + margin: 10px; +} + +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 60px; +} + +.example-margin { + margin: 0 10px; +} diff --git a/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.html b/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.html new file mode 100644 index 000000000000..a034e2f85150 --- /dev/null +++ b/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.html @@ -0,0 +1,49 @@ + + +

Progress circle configuration

+ +
+ + + + Primary + + + Accent + + + Warn + + +
+ +
+ + + + Determinate + + + Indeterminate + + +
+ +
+ + +
+
+
+ + +

Result

+ + + +
+
diff --git a/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.ts b/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.ts new file mode 100644 index 000000000000..b621aaa51e6f --- /dev/null +++ b/src/examples/progress-spinner-configurable/progress-spinner-configurable-example.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'progress-spinner-configurable-example', + templateUrl: './progress-spinner-configurable-example.html', + styleUrls: ['./progress-spinner-configurable-example.css'], +}) +export class ProgressSpinnerConfigurableExample { + color = 'primary'; + mode = 'determinate'; + value = 50; +} diff --git a/src/examples/progress-spinner-overview/progress-spinner-overview-example.css b/src/examples/progress-spinner-overview/progress-spinner-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/progress-spinner-overview/progress-spinner-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/progress-spinner-overview/progress-spinner-overview-example.html b/src/examples/progress-spinner-overview/progress-spinner-overview-example.html new file mode 100644 index 000000000000..cd768c67385c --- /dev/null +++ b/src/examples/progress-spinner-overview/progress-spinner-overview-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/progress-spinner-overview/progress-spinner-overview-example.ts b/src/examples/progress-spinner-overview/progress-spinner-overview-example.ts new file mode 100644 index 000000000000..89cce852a1f1 --- /dev/null +++ b/src/examples/progress-spinner-overview/progress-spinner-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'progress-spinner-overview-example', + templateUrl: './progress-spinner-overview-example.html', +}) +export class ProgressSpinnerOverviewExample {} diff --git a/src/examples/radio-ng-model/radio-ng-model-example.css b/src/examples/radio-ng-model/radio-ng-model-example.css new file mode 100644 index 000000000000..f70c1b3b53dd --- /dev/null +++ b/src/examples/radio-ng-model/radio-ng-model-example.css @@ -0,0 +1,12 @@ +.example-radio-group { + display: inline-flex; + flex-direction: column; +} + +.example-radio-button { + margin: 5px; +} + +.example-selected-value { + margin: 15px 0; +} diff --git a/src/examples/radio-ng-model/radio-ng-model-example.html b/src/examples/radio-ng-model/radio-ng-model-example.html new file mode 100644 index 000000000000..108971b5739a --- /dev/null +++ b/src/examples/radio-ng-model/radio-ng-model-example.html @@ -0,0 +1,6 @@ + + + {{season}} + + +
Your favorite season is: {{favoriteSeason}}
diff --git a/src/examples/radio-ng-model/radio-ng-model-example.ts b/src/examples/radio-ng-model/radio-ng-model-example.ts new file mode 100644 index 000000000000..42e93bc5d1ba --- /dev/null +++ b/src/examples/radio-ng-model/radio-ng-model-example.ts @@ -0,0 +1,18 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'radio-ng-model-example', + templateUrl: './radio-ng-model-example.html', + styleUrls: ['./radio-ng-model-example.css'], +}) +export class RadioNgModelExample { + favoriteSeason: string; + + seasons = [ + 'Winter', + 'Spring', + 'Summer', + 'Autumn', + ]; +} diff --git a/src/examples/radio-overview/radio-overview-example.css b/src/examples/radio-overview/radio-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/radio-overview/radio-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/radio-overview/radio-overview-example.html b/src/examples/radio-overview/radio-overview-example.html new file mode 100644 index 000000000000..4038381cc49e --- /dev/null +++ b/src/examples/radio-overview/radio-overview-example.html @@ -0,0 +1,4 @@ + + Option 1 + Option 2 + diff --git a/src/examples/radio-overview/radio-overview-example.ts b/src/examples/radio-overview/radio-overview-example.ts new file mode 100644 index 000000000000..a845835e41f4 --- /dev/null +++ b/src/examples/radio-overview/radio-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'radio-overview-example', + templateUrl: './radio-overview-example.html', +}) +export class RadioOverviewExample {} diff --git a/src/examples/select-form/select-form-example.css b/src/examples/select-form/select-form-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/select-form/select-form-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/select-form/select-form-example.html b/src/examples/select-form/select-form-example.html new file mode 100644 index 000000000000..6dea33cc5963 --- /dev/null +++ b/src/examples/select-form/select-form-example.html @@ -0,0 +1,9 @@ +
+ + + {{food.viewValue}} + + + +

Selected value: {{selectedValue}}

+
diff --git a/src/examples/select-form/select-form-example.ts b/src/examples/select-form/select-form-example.ts new file mode 100644 index 000000000000..684bf016e5a9 --- /dev/null +++ b/src/examples/select-form/select-form-example.ts @@ -0,0 +1,16 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'select-form-example', + templateUrl: './select-form-example.html', +}) +export class SelectFormExample { + selectedValue: string; + + foods = [ + {value: 'steak-0', viewValue: 'Steak'}, + {value: 'pizza-1', viewValue: 'Pizza'}, + {value: 'tacos-2', viewValue: 'Tacos'} + ]; +} diff --git a/src/examples/select-overview/select-overview-example.css b/src/examples/select-overview/select-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/select-overview/select-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/select-overview/select-overview-example.html b/src/examples/select-overview/select-overview-example.html new file mode 100644 index 000000000000..a4017ca5c242 --- /dev/null +++ b/src/examples/select-overview/select-overview-example.html @@ -0,0 +1,5 @@ + + + {{ food.viewValue }} + + diff --git a/src/examples/select-overview/select-overview-example.ts b/src/examples/select-overview/select-overview-example.ts new file mode 100644 index 000000000000..8162dc7f616d --- /dev/null +++ b/src/examples/select-overview/select-overview-example.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'select-overview-example', + templateUrl: './select-overview-example.html', +}) +export class SelectOverviewExample { + foods = [ + {value: 'steak-0', viewValue: 'Steak'}, + {value: 'pizza-1', viewValue: 'Pizza'}, + {value: 'tacos-2', viewValue: 'Tacos'} + ]; +} diff --git a/src/examples/sidenav-fab/sidenav-fab-example.css b/src/examples/sidenav-fab/sidenav-fab-example.css new file mode 100644 index 000000000000..98d497acfe3d --- /dev/null +++ b/src/examples/sidenav-fab/sidenav-fab-example.css @@ -0,0 +1,25 @@ +.example-sidenav-fab-container { + width: 500px; + height: 300px; + border: 1px solid rgba(0, 0, 0, 0.5); +} + +.example-sidenav-fab-container md-sidenav { + max-width: 200px; +} + +.example-sidenav-fab-container .md-sidenav-content, +.example-sidenav-fab-container md-sidenav { + display: flex; + overflow: visible; +} + +.example-scrolling-content { + overflow: auto; +} + +.example-fab { + position: absolute; + right: 20px; + bottom: 10px; +} diff --git a/src/examples/sidenav-fab/sidenav-fab-example.html b/src/examples/sidenav-fab/sidenav-fab-example.html new file mode 100644 index 000000000000..f194c09852e2 --- /dev/null +++ b/src/examples/sidenav-fab/sidenav-fab-example.html @@ -0,0 +1,32 @@ + + + +
+ Lorem ipsum dolor sit amet, pede a libero aenean phasellus, lectus metus sint ut risus, + fusce vel in pellentesque. Nisl rutrum etiam morbi consectetuer tempor magna, aenean nullam + nunc id, neque vivamus interdum sociis nulla scelerisque sem, dolor id wisi turpis magna + aliquam magna. Risus accumsan hac eget etiam donec sed, senectus erat mattis quam, tempor + vel urna occaecat cras, metus urna augue nec at. Et morbi amet dui praesent, nec eu at, + ligula ipsum dui sollicitudin, quis nisl massa viverra ligula, mauris fermentum orci arcu + enim fringilla. Arcu erat nulla in aenean lacinia ullamcorper, urna ante nam et sagittis, + tristique vehicula nibh ipsum vivamus, proin proin. Porta commodo nibh quis libero amet. + Taciti dui, sapien consectetuer. +
+
+ +
+ Lorem ipsum dolor sit amet, pede a libero aenean phasellus, lectus metus sint ut risus, fusce + vel in pellentesque. Nisl rutrum etiam morbi consectetuer tempor magna, aenean nullam nunc id, + neque vivamus interdum sociis nulla scelerisque sem, dolor id wisi turpis magna aliquam magna. + Risus accumsan hac eget etiam donec sed, senectus erat mattis quam, tempor vel urna occaecat + cras, metus urna augue nec at. Et morbi amet dui praesent, nec eu at, ligula ipsum dui + sollicitudin, quis nisl massa viverra ligula, mauris fermentum orci arcu enim fringilla. Arcu + erat nulla in aenean lacinia ullamcorper, urna ante nam et sagittis, tristique vehicula nibh + ipsum vivamus, proin proin. Porta commodo nibh quis libero amet. Taciti dui, sapien + consectetuer. +
+
diff --git a/src/examples/sidenav-fab/sidenav-fab-example.ts b/src/examples/sidenav-fab/sidenav-fab-example.ts new file mode 100644 index 000000000000..a86050b2957d --- /dev/null +++ b/src/examples/sidenav-fab/sidenav-fab-example.ts @@ -0,0 +1,10 @@ +import {Component, ViewEncapsulation} from '@angular/core'; + + +@Component({ + selector: 'sidenav-fab-example', + templateUrl: './sidenav-fab-example.html', + styleUrls: ['./sidenav-fab-example.css'], + encapsulation: ViewEncapsulation.None, +}) +export class SidenavFabExample {} diff --git a/src/examples/sidenav-overview/sidenav-overview-example.css b/src/examples/sidenav-overview/sidenav-overview-example.css new file mode 100644 index 000000000000..8ab00588bb34 --- /dev/null +++ b/src/examples/sidenav-overview/sidenav-overview-example.css @@ -0,0 +1,16 @@ +.example-container { + width: 500px; + height: 300px; + border: 1px solid rgba(0, 0, 0, 0.5); +} + +.example-sidenav-content { + display: flex; + height: 100%; + align-items: center; + justify-content: center; +} + +.example-sidenav { + padding: 20px; +} diff --git a/src/examples/sidenav-overview/sidenav-overview-example.html b/src/examples/sidenav-overview/sidenav-overview-example.html new file mode 100644 index 000000000000..83f34793b0a4 --- /dev/null +++ b/src/examples/sidenav-overview/sidenav-overview-example.html @@ -0,0 +1,12 @@ + + + Jolly good! + + +
+ +
+ +
diff --git a/src/examples/sidenav-overview/sidenav-overview-example.ts b/src/examples/sidenav-overview/sidenav-overview-example.ts new file mode 100644 index 000000000000..533802a9099f --- /dev/null +++ b/src/examples/sidenav-overview/sidenav-overview-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'sidenav-overview-example', + templateUrl: './sidenav-overview-example.html', + styleUrls: ['./sidenav-overview-example.css'], +}) +export class SidenavOverviewExample {} diff --git a/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.css b/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.css new file mode 100644 index 000000000000..060532ee7d9f --- /dev/null +++ b/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.css @@ -0,0 +1,14 @@ +.example-h2 { + margin: 10px; +} + +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 60px; +} + +.example-margin { + margin: 10px; +} diff --git a/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.html b/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.html new file mode 100644 index 000000000000..88099f4b0254 --- /dev/null +++ b/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.html @@ -0,0 +1,44 @@ + + +

Slider configuration

+ +
+ + + + Primary + + + Accent + + + Warn + + +
+ +
+ Checked +
+ +
+ Disabled +
+
+
+ + + +

Result

+ +
+ + Slide me! + +
+
+
diff --git a/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.ts b/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.ts new file mode 100644 index 000000000000..cfa28ceddd4d --- /dev/null +++ b/src/examples/slide-toggle-configurable/slide-toggle-configurable-example.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'slide-toggle-configurable-example', + templateUrl: './slide-toggle-configurable-example.html', + styleUrls: ['./slide-toggle-configurable-example.css'], +}) +export class SlideToggleConfigurableExample { + color = 'accent'; + checked = false; + disabled = false; +} diff --git a/src/examples/slide-toggle-overview/slide-toggle-overview-example.css b/src/examples/slide-toggle-overview/slide-toggle-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/slide-toggle-overview/slide-toggle-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/slide-toggle-overview/slide-toggle-overview-example.html b/src/examples/slide-toggle-overview/slide-toggle-overview-example.html new file mode 100644 index 000000000000..6b9586e148fc --- /dev/null +++ b/src/examples/slide-toggle-overview/slide-toggle-overview-example.html @@ -0,0 +1 @@ +Slide me! diff --git a/src/examples/slide-toggle-overview/slide-toggle-overview-example.ts b/src/examples/slide-toggle-overview/slide-toggle-overview-example.ts new file mode 100644 index 000000000000..0f35c9d5e9b1 --- /dev/null +++ b/src/examples/slide-toggle-overview/slide-toggle-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'slide-toggle-overview-example', + templateUrl: './slide-toggle-overview-example.html', +}) +export class SlideToggleOverviewExample {} diff --git a/src/examples/slider-configurable/slider-configurable-example.css b/src/examples/slider-configurable/slider-configurable-example.css new file mode 100644 index 000000000000..389e65bd7ac0 --- /dev/null +++ b/src/examples/slider-configurable/slider-configurable-example.css @@ -0,0 +1,22 @@ +.example-h2 { + margin: 10px; +} + +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 60px; +} + +.example-margin { + margin: 10px; +} + +.md-slider-horizontal { + width: 300px; +} + +.md-slider-vertical { + height: 300px; +} diff --git a/src/examples/slider-configurable/slider-configurable-example.html b/src/examples/slider-configurable/slider-configurable-example.html new file mode 100644 index 000000000000..ba886e760363 --- /dev/null +++ b/src/examples/slider-configurable/slider-configurable-example.html @@ -0,0 +1,63 @@ + + +

Slider configuration

+ +
+ + + + + + + + + + + + +
+ +
+ Show ticks + + Auto ticks + + + + +
+ +
+ Show thumb label +
+ +
+ Vertical + Inverted +
+ +
+ Disabled +
+ +
+
+ + + +

Result

+ + + +
+
diff --git a/src/examples/slider-configurable/slider-configurable-example.ts b/src/examples/slider-configurable/slider-configurable-example.ts new file mode 100644 index 000000000000..933a9dee7311 --- /dev/null +++ b/src/examples/slider-configurable/slider-configurable-example.ts @@ -0,0 +1,29 @@ +import {Component, ViewEncapsulation} from '@angular/core'; + + +@Component({ + selector: 'slider-configurable-example', + templateUrl: './slider-configurable-example.html', + styleUrls: ['./slider-configurable-example.css'], + encapsulation: ViewEncapsulation.None, +}) +export class SliderConfigurableExample { + autoTicks = false; + disabled = false; + invert = false; + max = 100; + min = 0; + showTicks = false; + step = 1; + thumbLabel = false; + value = 0; + vertical = false; + + get tickInterval(): number | 'auto' { + return this.showTicks ? (this.autoTicks ? 'auto' : this._tickInterval) : null; + } + set tickInterval(v) { + this._tickInterval = Number(v); + } + private _tickInterval = 1; +} diff --git a/src/examples/slider-overview/slider-overview-example.css b/src/examples/slider-overview/slider-overview-example.css new file mode 100644 index 000000000000..8dfe08ccc21d --- /dev/null +++ b/src/examples/slider-overview/slider-overview-example.css @@ -0,0 +1,4 @@ +/** No CSS for this example */ +md-slider { + width: 300px; +} diff --git a/src/examples/slider-overview/slider-overview-example.html b/src/examples/slider-overview/slider-overview-example.html new file mode 100644 index 000000000000..9a92c8f867dd --- /dev/null +++ b/src/examples/slider-overview/slider-overview-example.html @@ -0,0 +1 @@ + diff --git a/src/examples/slider-overview/slider-overview-example.ts b/src/examples/slider-overview/slider-overview-example.ts new file mode 100644 index 000000000000..1f020fd662ff --- /dev/null +++ b/src/examples/slider-overview/slider-overview-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'slider-overview-example', + templateUrl: './slider-overview-example.html', + styleUrls: ['./slider-overview-example.css'], +}) +export class SliderOverviewExample {} diff --git a/src/examples/snack-bar-component/snack-bar-component-example-snack.css b/src/examples/snack-bar-component/snack-bar-component-example-snack.css new file mode 100644 index 000000000000..fa7dc6749acf --- /dev/null +++ b/src/examples/snack-bar-component/snack-bar-component-example-snack.css @@ -0,0 +1,3 @@ +.example-pizza-party { + color: hotpink; +} diff --git a/src/examples/snack-bar-component/snack-bar-component-example-snack.html b/src/examples/snack-bar-component/snack-bar-component-example-snack.html new file mode 100644 index 000000000000..d43b91e9a961 --- /dev/null +++ b/src/examples/snack-bar-component/snack-bar-component-example-snack.html @@ -0,0 +1,3 @@ + + 🍕🍕🍕🍕🍕 Pizza party!!! 🍕🍕🍕🍕🍕 + diff --git a/src/examples/snack-bar-component/snack-bar-component-example.html b/src/examples/snack-bar-component/snack-bar-component-example.html new file mode 100644 index 000000000000..69fe01bc3dcb --- /dev/null +++ b/src/examples/snack-bar-component/snack-bar-component-example.html @@ -0,0 +1,3 @@ + diff --git a/src/examples/snack-bar-component/snack-bar-component-example.ts b/src/examples/snack-bar-component/snack-bar-component-example.ts new file mode 100644 index 000000000000..03ef19e8a70d --- /dev/null +++ b/src/examples/snack-bar-component/snack-bar-component-example.ts @@ -0,0 +1,25 @@ +import {Component} from '@angular/core'; +import {MdSnackBar} from '@angular/material'; + + +@Component({ + selector: 'snack-bar-component-example', + templateUrl: './snack-bar-component-example.html', +}) +export class SnackBarComponentExample { + constructor(public snackBar: MdSnackBar) {} + + openSnackBar() { + this.snackBar.openFromComponent(PizzaPartyComponent, { + duration: 500, + }); + } +} + + +@Component({ + selector: 'snack-bar-component-example-snack', + templateUrl: './snack-bar-component-example-snack.html', + styleUrls: ['./snack-bar-component-example-snack.css'], +}) +export class PizzaPartyComponent {} diff --git a/src/examples/snack-bar-overview/snack-bar-overview-example.css b/src/examples/snack-bar-overview/snack-bar-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/snack-bar-overview/snack-bar-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/snack-bar-overview/snack-bar-overview-example.html b/src/examples/snack-bar-overview/snack-bar-overview-example.html new file mode 100644 index 000000000000..91cb38c8b317 --- /dev/null +++ b/src/examples/snack-bar-overview/snack-bar-overview-example.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/examples/snack-bar-overview/snack-bar-overview-example.ts b/src/examples/snack-bar-overview/snack-bar-overview-example.ts new file mode 100644 index 000000000000..312de83ea671 --- /dev/null +++ b/src/examples/snack-bar-overview/snack-bar-overview-example.ts @@ -0,0 +1,17 @@ +import {Component} from '@angular/core'; +import {MdSnackBar} from '@angular/material'; + + +@Component({ + selector: 'snack-bar-overview-example', + templateUrl: './snack-bar-overview-example.html', +}) +export class SnackBarOverviewExample { + constructor(public snackBar: MdSnackBar) {} + + openSnackBar(message: string, action: string) { + this.snackBar.open(message, action, { + duration: 2000, + }); + } +} diff --git a/src/examples/tabs-overview/tabs-overview-example.css b/src/examples/tabs-overview/tabs-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/tabs-overview/tabs-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/tabs-overview/tabs-overview-example.html b/src/examples/tabs-overview/tabs-overview-example.html new file mode 100644 index 000000000000..173ece5dc477 --- /dev/null +++ b/src/examples/tabs-overview/tabs-overview-example.html @@ -0,0 +1,4 @@ + + Content 1 + Content 2 + diff --git a/src/examples/tabs-overview/tabs-overview-example.ts b/src/examples/tabs-overview/tabs-overview-example.ts new file mode 100644 index 000000000000..4f222c112621 --- /dev/null +++ b/src/examples/tabs-overview/tabs-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'tabs-overview-example', + templateUrl: './tabs-overview-example.html', +}) +export class TabsOverviewExample {} diff --git a/src/examples/tabs-template-label/tabs-template-label-example.css b/src/examples/tabs-template-label/tabs-template-label-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/tabs-template-label/tabs-template-label-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/tabs-template-label/tabs-template-label-example.html b/src/examples/tabs-template-label/tabs-template-label-example.html new file mode 100644 index 000000000000..bde67b8e9138 --- /dev/null +++ b/src/examples/tabs-template-label/tabs-template-label-example.html @@ -0,0 +1 @@ +More examples coming soon! diff --git a/src/examples/tabs-template-label/tabs-template-label-example.ts b/src/examples/tabs-template-label/tabs-template-label-example.ts new file mode 100644 index 000000000000..7015b1f74182 --- /dev/null +++ b/src/examples/tabs-template-label/tabs-template-label-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'tabs-template-label-example', + templateUrl: './tabs-template-label-example.html', +}) +export class TabsTemplateLabelExample {} diff --git a/src/examples/toolbar-multirow/toolbar-multirow-example.css b/src/examples/toolbar-multirow/toolbar-multirow-example.css new file mode 100644 index 000000000000..d7c40156ba97 --- /dev/null +++ b/src/examples/toolbar-multirow/toolbar-multirow-example.css @@ -0,0 +1,7 @@ +.example-icon { + padding: 0 14px; +} + +.example-spacer { + flex: 1 1 auto; +} diff --git a/src/examples/toolbar-multirow/toolbar-multirow-example.html b/src/examples/toolbar-multirow/toolbar-multirow-example.html new file mode 100644 index 000000000000..9b178a21249f --- /dev/null +++ b/src/examples/toolbar-multirow/toolbar-multirow-example.html @@ -0,0 +1,16 @@ + + Custom Toolbar + + + Second Line + + verified_user + + + + Third Line + + favorite + delete + + diff --git a/src/examples/toolbar-multirow/toolbar-multirow-example.ts b/src/examples/toolbar-multirow/toolbar-multirow-example.ts new file mode 100644 index 000000000000..132b98174f67 --- /dev/null +++ b/src/examples/toolbar-multirow/toolbar-multirow-example.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'toolbar-multirow-example', + templateUrl: './toolbar-multirow-example.html', + styleUrls: ['./toolbar-multirow-example.css'], +}) +export class ToolbarMultirowExample {} diff --git a/src/examples/toolbar-overview/toolbar-overview-example.css b/src/examples/toolbar-overview/toolbar-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/toolbar-overview/toolbar-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/toolbar-overview/toolbar-overview-example.html b/src/examples/toolbar-overview/toolbar-overview-example.html new file mode 100644 index 000000000000..bf8cc3998630 --- /dev/null +++ b/src/examples/toolbar-overview/toolbar-overview-example.html @@ -0,0 +1 @@ +My App diff --git a/src/examples/toolbar-overview/toolbar-overview-example.ts b/src/examples/toolbar-overview/toolbar-overview-example.ts new file mode 100644 index 000000000000..6919aca4f3fc --- /dev/null +++ b/src/examples/toolbar-overview/toolbar-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'toolbar-overview-example', + templateUrl: './toolbar-overview-example.html', +}) +export class ToolbarOverviewExample {} diff --git a/src/examples/tooltip-overview/tooltip-overview-example.css b/src/examples/tooltip-overview/tooltip-overview-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/examples/tooltip-overview/tooltip-overview-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/examples/tooltip-overview/tooltip-overview-example.html b/src/examples/tooltip-overview/tooltip-overview-example.html new file mode 100644 index 000000000000..8319a8545846 --- /dev/null +++ b/src/examples/tooltip-overview/tooltip-overview-example.html @@ -0,0 +1 @@ +I have a tooltip diff --git a/src/examples/tooltip-overview/tooltip-overview-example.ts b/src/examples/tooltip-overview/tooltip-overview-example.ts new file mode 100644 index 000000000000..cf9eefc94557 --- /dev/null +++ b/src/examples/tooltip-overview/tooltip-overview-example.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'tooltip-overview-example', + templateUrl: './tooltip-overview-example.html', +}) +export class TooltipOverviewExample {} diff --git a/src/examples/tooltip-position/tooltip-position-example.css b/src/examples/tooltip-position/tooltip-position-example.css new file mode 100644 index 000000000000..04ec59dd1fc7 --- /dev/null +++ b/src/examples/tooltip-position/tooltip-position-example.css @@ -0,0 +1,9 @@ +.example-tooltip-host { + display: inline-flex; + align-items: center; + margin: 50px; +} + +.example-select { + margin: 0 10px; +} diff --git a/src/examples/tooltip-position/tooltip-position-example.html b/src/examples/tooltip-position/tooltip-position-example.html new file mode 100644 index 000000000000..156a5bf68492 --- /dev/null +++ b/src/examples/tooltip-position/tooltip-position-example.html @@ -0,0 +1,11 @@ +
+ Show tooltip + + Before + After + Above + Below + Left + Right + +
diff --git a/src/examples/tooltip-position/tooltip-position-example.ts b/src/examples/tooltip-position/tooltip-position-example.ts new file mode 100644 index 000000000000..0b580c722bfc --- /dev/null +++ b/src/examples/tooltip-position/tooltip-position-example.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; + + +@Component({ + selector: 'tooltip-position-example', + templateUrl: './tooltip-position-example.html', + styleUrls: ['./tooltip-position-example.css'], +}) +export class TooltipPositionExample { + position = 'before'; +} diff --git a/src/lib/autocomplete/README.md b/src/lib/autocomplete/README.md index 1bc0814e9eec..73bddeb68ee6 100644 --- a/src/lib/autocomplete/README.md +++ b/src/lib/autocomplete/README.md @@ -1,5 +1 @@ - -## Not yet implemented! - -The autocomplete is not yet implemented. This is only a scaffold to make -subsequent PRs easier to read. Please do not try to use yet :) \ No newline at end of file +See documentation on [material.angular.io](https://material.angular.io/). \ No newline at end of file diff --git a/src/lib/autocomplete/_autocomplete-theme.scss b/src/lib/autocomplete/_autocomplete-theme.scss index b77fff253e0c..7cce6dfe1c56 100644 --- a/src/lib/autocomplete/_autocomplete-theme.scss +++ b/src/lib/autocomplete/_autocomplete-theme.scss @@ -1,5 +1,19 @@ @import '../core/theming/theming'; -@mixin md-autocomplete-theme($theme) { +@mixin mat-autocomplete-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-autocomplete-panel { + background: mat-color($background, card); + color: mat-color($foreground, text); + + .mat-option { + &.mat-selected:not(.mat-active) { + background: mat-color($background, card); + color: mat-color($foreground, text); + } + } + } } \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts new file mode 100644 index 000000000000..6d3c550022fc --- /dev/null +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -0,0 +1,371 @@ +import { + Directive, + ElementRef, + forwardRef, + Host, + Input, + NgZone, + Optional, + OnDestroy, + ViewContainerRef, +} from '@angular/core'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core'; +import {MdAutocomplete} from './autocomplete'; +import {PositionStrategy} from '../core/overlay/position/position-strategy'; +import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy'; +import {Observable} from 'rxjs/Observable'; +import {MdOptionSelectionChange, MdOption} from '../core/option/option'; +import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes'; +import {Dir} from '../core/rtl/dir'; +import {Subscription} from 'rxjs/Subscription'; +import {Subject} from 'rxjs/Subject'; +import 'rxjs/add/observable/merge'; +import 'rxjs/add/operator/startWith'; +import 'rxjs/add/operator/switchMap'; +import {MdInputContainer} from '../input/input-container'; + +/** + * The following style constants are necessary to save here in order + * to properly calculate the scrollTop of the panel. Because we are not + * actually focusing the active item, scroll must be handled manually. + */ + +/** The height of each autocomplete option. */ +export const AUTOCOMPLETE_OPTION_HEIGHT = 48; + +/** The total height of the autocomplete panel. */ +export const AUTOCOMPLETE_PANEL_HEIGHT = 256; + +/** + * Provider that allows the autocomplete to register as a ControlValueAccessor. + * @docs-private + */ +export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MdAutocompleteTrigger), + multi: true +}; + +@Directive({ + selector: 'input[mdAutocomplete], input[matAutocomplete]', + host: { + 'role': 'combobox', + 'autocomplete': 'off', + 'aria-autocomplete': 'list', + 'aria-multiline': 'false', + '[attr.aria-activedescendant]': 'activeOption?.id', + '[attr.aria-expanded]': 'panelOpen.toString()', + '[attr.aria-owns]': 'autocomplete?.id', + '(focus)': 'openPanel()', + '(blur)': '_handleBlur($event.relatedTarget?.tagName)', + '(input)': '_handleInput($event)', + '(keydown)': '_handleKeydown($event)', + }, + providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR] +}) +export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { + private _overlayRef: OverlayRef; + private _portal: TemplatePortal; + private _panelOpen: boolean = false; + + /** The subscription to positioning changes in the autocomplete panel. */ + private _panelPositionSubscription: Subscription; + + private _positionStrategy: ConnectedPositionStrategy; + + /** Stream of blur events that should close the panel. */ + private _blurStream = new Subject(); + + /** Whether or not the placeholder state is being overridden. */ + private _manuallyFloatingPlaceholder = false; + + /** View -> model callback called when value changes */ + _onChange = (value: any) => {}; + + /** View -> model callback called when autocomplete has been touched */ + _onTouched = () => {}; + + /* The autocomplete panel to be attached to this trigger. */ + @Input('mdAutocomplete') autocomplete: MdAutocomplete; + + /** Property with mat- prefix for no-conflict mode. */ + @Input('matAutocomplete') + get _matAutocomplete(): MdAutocomplete { + return this.autocomplete; + } + + set _matAutocomplete(autocomplete: MdAutocomplete) { + this.autocomplete = autocomplete; + } + + constructor(private _element: ElementRef, private _overlay: Overlay, + private _viewContainerRef: ViewContainerRef, + @Optional() private _dir: Dir, private _zone: NgZone, + @Optional() @Host() private _inputContainer: MdInputContainer) {} + + ngOnDestroy() { + if (this._panelPositionSubscription) { + this._panelPositionSubscription.unsubscribe(); + } + + this._destroyPanel(); + } + + /* Whether or not the autocomplete panel is open. */ + get panelOpen(): boolean { + return this._panelOpen && this.autocomplete.showPanel; + } + + /** Opens the autocomplete suggestion panel. */ + openPanel(): void { + if (!this._overlayRef) { + this._createOverlay(); + } else { + /** Update the panel width, in case the host width has changed */ + this._overlayRef.getState().width = this._getHostWidth(); + } + + if (!this._overlayRef.hasAttached()) { + this._overlayRef.attach(this._portal); + this._subscribeToClosingActions(); + } + + this._panelOpen = true; + this._floatPlaceholder(); + } + + /** Closes the autocomplete suggestion panel. */ + closePanel(): void { + if (this._overlayRef && this._overlayRef.hasAttached()) { + this._overlayRef.detach(); + } + + this._panelOpen = false; + this._resetPlaceholder(); + } + + /** + * A stream of actions that should close the autocomplete panel, including + * when an option is selected, on blur, and when TAB is pressed. + */ + get panelClosingActions(): Observable { + return Observable.merge( + this.optionSelections, + this._blurStream.asObservable(), + this.autocomplete._keyManager.tabOut + ); + } + + /** Stream of autocomplete option selections. */ + get optionSelections(): Observable { + return Observable.merge(...this.autocomplete.options.map(option => option.onSelectionChange)); + } + + /** The currently active option, coerced to MdOption type. */ + get activeOption(): MdOption { + if (this.autocomplete._keyManager) { + return this.autocomplete._keyManager.activeItem as MdOption; + } + } + + /** + * Sets the autocomplete's value. Part of the ControlValueAccessor interface + * required to integrate with Angular's core forms API. + * + * @param value New value to be written to the model. + */ + writeValue(value: any): void { + Promise.resolve(null).then(() => this._setTriggerValue(value)); + } + + /** + * Saves a callback function to be invoked when the autocomplete's value + * changes from user input. Part of the ControlValueAccessor interface + * required to integrate with Angular's core forms API. + * + * @param fn Callback to be triggered when the value changes. + */ + registerOnChange(fn: (value: any) => {}): void { + this._onChange = fn; + } + + /** + * Saves a callback function to be invoked when the autocomplete is blurred + * by the user. Part of the ControlValueAccessor interface required + * to integrate with Angular's core forms API. + * + * @param fn Callback to be triggered when the component has been touched. + */ + registerOnTouched(fn: () => {}) { + this._onTouched = fn; + } + + _handleKeydown(event: KeyboardEvent): void { + if (this.activeOption && event.keyCode === ENTER) { + this.activeOption._selectViaInteraction(); + } else { + this.autocomplete._keyManager.onKeydown(event); + if (event.keyCode === UP_ARROW || event.keyCode === DOWN_ARROW) { + this.openPanel(); + Promise.resolve().then(() => this._scrollToOption()); + } + } + } + + _handleInput(event: KeyboardEvent): void { + // We need to ensure that the input is focused, because IE will fire the `input` + // event on focus/blur/load if the input has a placeholder. See: + // https://connect.microsoft.com/IE/feedback/details/885747/ + if (document.activeElement === event.target) { + this._onChange((event.target as HTMLInputElement).value); + this.openPanel(); + } + } + + _handleBlur(newlyFocusedTag: string): void { + this._onTouched(); + + // Only emit blur event if the new focus is *not* on an option. + if (newlyFocusedTag !== 'MD-OPTION') { + this._blurStream.next(null); + } + } + + /** + * In "auto" mode, the placeholder will animate down as soon as focus is lost. + * This causes the value to jump when selecting an option with the mouse. + * This method manually floats the placeholder until the panel can be closed. + */ + private _floatPlaceholder(): void { + if (this._inputContainer && this._inputContainer.floatPlaceholder === 'auto') { + this._inputContainer.floatPlaceholder = 'always'; + this._manuallyFloatingPlaceholder = true; + } + } + + /** If the placeholder has been manually elevated, return it to its normal state. */ + private _resetPlaceholder(): void { + if (this._manuallyFloatingPlaceholder) { + this._inputContainer.floatPlaceholder = 'auto'; + this._manuallyFloatingPlaceholder = false; + } + } + + /** + * Given that we are not actually focusing active options, we must manually adjust scroll + * to reveal options below the fold. First, we find the offset of the option from the top + * of the panel. The new scrollTop will be that offset - the panel height + the option + * height, so the active option will be just visible at the bottom of the panel. + */ + private _scrollToOption(): void { + const optionOffset = + this.autocomplete._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT; + const newScrollTop = + Math.max(0, optionOffset - AUTOCOMPLETE_PANEL_HEIGHT + AUTOCOMPLETE_OPTION_HEIGHT); + this.autocomplete._setScrollTop(newScrollTop); + } + + /** + * This method listens to a stream of panel closing actions and resets the + * stream every time the option list changes. + */ + private _subscribeToClosingActions(): void { + // When the zone is stable initially, and when the option list changes... + Observable.merge(this._zone.onStable.first(), this.autocomplete.options.changes) + // create a new stream of panelClosingActions, replacing any previous streams + // that were created, and flatten it so our stream only emits closing events... + .switchMap(() => { + this._resetPanel(); + return this.panelClosingActions; + }) + // when the first closing event occurs... + .first() + // set the value, close the panel, and complete. + .subscribe(event => this._setValueAndClose(event)); + } + + /** Destroys the autocomplete suggestion panel. */ + private _destroyPanel(): void { + if (this._overlayRef) { + this.closePanel(); + this._overlayRef.dispose(); + this._overlayRef = null; + } + } + + private _setTriggerValue(value: any): void { + const toDisplay = this.autocomplete.displayWith ? this.autocomplete.displayWith(value) : value; + this._element.nativeElement.value = toDisplay || ''; + } + + /** + * This method closes the panel, and if a value is specified, also sets the associated + * control to that value. It will also mark the control as dirty if this interaction + * stemmed from the user. + */ + private _setValueAndClose(event: MdOptionSelectionChange | null): void { + if (event) { + this._setTriggerValue(event.source.value); + this._onChange(event.source.value); + } + + this.closePanel(); + } + + private _createOverlay(): void { + this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef); + this._overlayRef = this._overlay.create(this._getOverlayConfig()); + } + + private _getOverlayConfig(): OverlayState { + const overlayState = new OverlayState(); + overlayState.positionStrategy = this._getOverlayPosition(); + overlayState.width = this._getHostWidth(); + overlayState.direction = this._dir ? this._dir.value : 'ltr'; + return overlayState; + } + + private _getOverlayPosition(): PositionStrategy { + this._positionStrategy = this._overlay.position().connectedTo( + this._element, + {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}) + .withFallbackPosition( + {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'bottom'} + ); + this._subscribeToPositionChanges(this._positionStrategy); + return this._positionStrategy; + } + + /** + * This method subscribes to position changes in the autocomplete panel, so the panel's + * y-offset can be adjusted to match the new position. + */ + private _subscribeToPositionChanges(strategy: ConnectedPositionStrategy) { + this._panelPositionSubscription = strategy.onPositionChange.subscribe(change => { + this.autocomplete.positionY = change.connectionPair.originY === 'top' ? 'above' : 'below'; + }); + } + + /** Returns the width of the input element, so the panel width can match it. */ + private _getHostWidth(): number { + return this._element.nativeElement.getBoundingClientRect().width; + } + + /** Reset active item to null so arrow events will activate the correct options.*/ + private _resetActiveItem(): void { + this.autocomplete._keyManager.setActiveItem(null); + } + + /** + * Resets the active item and re-calculates alignment of the panel in case its size + * has changed due to fewer or greater number of options. + */ + private _resetPanel() { + this._resetActiveItem(); + this._positionStrategy.recalculateLastPosition(); + this.autocomplete._setVisibility(); + } + +} + diff --git a/src/lib/autocomplete/autocomplete.html b/src/lib/autocomplete/autocomplete.html index f0d1cbc032a0..cd94ceeb7340 100644 --- a/src/lib/autocomplete/autocomplete.html +++ b/src/lib/autocomplete/autocomplete.html @@ -1 +1,5 @@ -I'm an autocomplete! \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/autocomplete/autocomplete.md b/src/lib/autocomplete/autocomplete.md new file mode 100644 index 000000000000..f5d139ae100d --- /dev/null +++ b/src/lib/autocomplete/autocomplete.md @@ -0,0 +1,154 @@ + +The autocomplete is a normal text input enhanced by a panel of suggested options. You can read more about +autocompletes in the [Material Design spec](https://material.io/guidelines/components/text-fields.html#text-fields-auto-complete-text-field). + +### Simple autocomplete + +Start by adding a regular `mdInput` to the page. Let's assume you're using the `formControl` directive from the +`@angular/forms` module to track the value of the input. + +*my-comp.html* +```html + + + +``` + +Next, create the autocomplete panel and the options displayed inside it. Each option should be defined by an +`md-option` tag. Set each option's value property to whatever you'd like the value of the text input to be +upon that option's selection. + +*my-comp.html* +```html + + + {{ option }} + + +``` + +Now we'll need to link the text input to its panel. We can do this by exporting the autocomplete panel instance into a +local template variable (here we called it "auto"), and binding that variable to the input's `mdAutocomplete` property. + +*my-comp.html* +```html + + + + + + + {{ option }} + + +``` + +### Adding a custom filter + +At this point, the autocomplete panel should be toggleable on focus and options should be selectable. But if we want +our options to filter when we type, we need to add a custom filter. + +You can filter the options in any way you like based on the text input*. Here we will perform a simple string test on +the option value to see if it matches the input value, starting from the option's first letter. We already have access +to the built-in `valueChanges` observable on the `FormControl`, so we can simply map the text input's values to the +suggested options by passing them through this filter. The resulting observable (`filteredOptions`) can be added to the +template in place of the `options` property using the `async` pipe. + +Below we are also priming our value change stream with `null` so that the options are filtered by that value on init +(before there are any value changes). + +*For optimal accessibility, you may want to consider adding text guidance on the page to explain filter criteria. +This is especially helpful for screenreader users if you're using a non-standard filter that doesn't limit matches +to the beginning of the string. + +*my-comp.ts* +```ts +class MyComp { + myControl = new FormControl(); + options = [ + 'One', + 'Two', + 'Three' + ]; + filteredOptions: Observable; + + ngOnInit() { + this.filteredOptions = this.myControl.valueChanges + .startWith(null) + .map(val => val ? this.filter(val) : this.options.slice()); + } + + filter(val: string): string[] { + return this.options.filter(option => new RegExp(`^${val}`, 'gi').test(option)); + } +} +``` + +*my-comp.html* +```html + + + + + + + {{ option }} + + +``` + +### Setting separate control and display values + +If you want the option's control value (what is saved in the form) to be different than the option's display value +(what is displayed in the actual text field), you'll need to set the `displayWith` property on your autocomplete +element. A common use case for this might be if you want to save your data as an object, but display just one of +the option's string properties. + +To make this work, create a function on your component class that maps the control value to the desired display value. +Then bind it to the autocomplete's `displayWith` property. + +```html + + + + + + + {{ option.name }} + + +``` + +*my-comp.ts* +```ts +class MyComp { + myControl = new FormControl(); + options = [ + new User('Mary'), + new User('Shelley'), + new User('Igor') + ]; + filteredOptions: Observable; + + ngOnInit() { + this.filteredOptions = this.myControl.valueChanges + .startWith(null) + .map(user => user && typeof user === 'object' ? user.name : user) + .map(name => name ? this.filter(name) : this.options.slice()); + } + + filter(name: string): User[] { + return this.options.filter(option => new RegExp(`^${name}`, 'gi').test(option)); + } + + displayFn(user: User): string { + return user ? user.name : user; + } +} +``` + + +#### Keyboard interaction: +- DOWN_ARROW: Next option becomes active. +- UP_ARROW: Previous option becomes active. +- ENTER: Select currently active item. diff --git a/src/lib/autocomplete/autocomplete.scss b/src/lib/autocomplete/autocomplete.scss index e69de29bb2d1..a72841cbbd54 100644 --- a/src/lib/autocomplete/autocomplete.scss +++ b/src/lib/autocomplete/autocomplete.scss @@ -0,0 +1,38 @@ +@import '../core/style/menu-common'; + +/** + * The max-height of the panel, currently matching md-select value. + * TODO: Check value with MD team. + */ +$mat-autocomplete-panel-max-height: 256px !default; + +/** When in "below" position, the panel needs a slight y-offset to ensure the input underline displays. */ +$mat-autocomplete-panel-below-offset: 6px !default; + +/** When in "above" position, the panel needs a larger y-offset to ensure the label has room to display. */ +$mat-autocomplete-panel-above-offset: -24px !default; + +.mat-autocomplete-panel { + @include mat-menu-base(); + visibility: hidden; + + max-width: none; + max-height: $mat-autocomplete-panel-max-height; + position: relative; + + &.mat-autocomplete-panel-below { + top: $mat-autocomplete-panel-below-offset; + } + + &.mat-autocomplete-panel-above { + top: $mat-autocomplete-panel-above-offset; + } + + &.mat-autocomplete-visible { + visibility: visible; + } + + &.mat-autocomplete-hidden { + visibility: hidden; + } +} diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 158a6cf263d6..7118f73ae3cb 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -1,29 +1,1149 @@ -import {TestBed, async} from '@angular/core/testing'; -import {Component} from '@angular/core'; -import {MdAutocompleteModule} from './index'; +import {TestBed, async, fakeAsync, tick, ComponentFixture} from '@angular/core/testing'; +import {Component, OnDestroy, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {MdAutocompleteModule, MdAutocompleteTrigger} from './index'; +import {OverlayContainer} from '../core/overlay/overlay-container'; +import {MdInputModule} from '../input/index'; +import {Dir, LayoutDirection} from '../core/rtl/dir'; +import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {Subscription} from 'rxjs/Subscription'; +import {ENTER, DOWN_ARROW, SPACE, UP_ARROW} from '../core/keyboard/keycodes'; +import {MdOption} from '../core/option/option'; +import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; +import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler'; +import {MdAutocomplete} from './autocomplete'; +import {MdInputContainer} from '../input/input-container'; +import {Observable} from 'rxjs/Observable'; +import {dispatchFakeEvent} from '../core/testing/dispatch-events'; +import {typeInElement} from '../core/testing/type-in-element'; + +import 'rxjs/add/operator/map'; describe('MdAutocomplete', () => { + let overlayContainerElement: HTMLElement; + let dir: LayoutDirection; beforeEach(async(() => { + dir = 'ltr'; TestBed.configureTestingModule({ - imports: [MdAutocompleteModule.forRoot()], - declarations: [SimpleAutocomplete], - providers: [] + imports: [ + MdAutocompleteModule.forRoot(), MdInputModule.forRoot(), FormsModule, ReactiveFormsModule + ], + declarations: [ + SimpleAutocomplete, + AutocompleteWithoutForms, + NgIfAutocomplete, + AutocompleteWithNgModel + ], + providers: [ + {provide: OverlayContainer, useFactory: () => { + overlayContainerElement = document.createElement('div'); + document.body.appendChild(overlayContainerElement); + + // remove body padding to keep consistent cross-browser + document.body.style.padding = '0'; + document.body.style.margin = '0'; + + return {getContainerElement: () => overlayContainerElement}; + }}, + {provide: Dir, useFactory: () => { + return {value: dir}; + }}, + {provide: ViewportRuler, useClass: FakeViewportRuler} + ] }); TestBed.compileComponents(); })); - it('should have a test', () => { - expect(true).toBe(true); + describe('panel toggling', () => { + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + input = fixture.debugElement.query(By.css('input')).nativeElement; + }); + + it('should open the panel when the input is focused', async(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel state to start out closed.`); + + dispatchFakeEvent(input, 'focus'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to read open when input is focused.`); + expect(overlayContainerElement.textContent) + .toContain('Alabama', `Expected panel to display when input is focused.`); + expect(overlayContainerElement.textContent) + .toContain('California', `Expected panel to display when input is focused.`); + }); + })); + + it('should open the panel programmatically', async(() => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel state to start out closed.`); + + fixture.componentInstance.trigger.openPanel(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to read open when opened programmatically.`); + expect(overlayContainerElement.textContent) + .toContain('Alabama', `Expected panel to display when opened programmatically.`); + expect(overlayContainerElement.textContent) + .toContain('California', `Expected panel to display when opened programmatically.`); + }); + })); + + it('should close the panel when blurred', async(() => { + dispatchFakeEvent(input, 'focus'); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + dispatchFakeEvent(input, 'blur'); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected clicking outside the panel to set its state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected clicking outside the panel to close the panel.`); + }); + })); + + it('should close the panel when an option is clicked', async(() => { + dispatchFakeEvent(input, 'focus'); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const option = overlayContainerElement.querySelector('md-option') as HTMLElement; + option.click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected clicking an option to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected clicking an option to close the panel.`); + }); + })); + + it('should close the panel when a newly created option is clicked', async(() => { + dispatchFakeEvent(input, 'focus'); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + // Filter down the option list to a subset of original options ('Alabama', 'California') + typeInElement('al', input); + fixture.detectChanges(); + + let options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[0].click(); + + // Changing value from 'Alabama' to 'al' to re-populate the option list, + // ensuring that 'California' is created new. + typeInElement('al', input); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected clicking a new option to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected clicking a new option to close the panel.`); + }); + }); + })); + + it('should close the panel programmatically', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected closing programmatically to set the panel state to closed.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected closing programmatically to close the panel.`); + }); + + it('should hide the panel when the options list is empty', async(() => { + dispatchFakeEvent(input, 'focus'); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + const panel = + overlayContainerElement.querySelector('.mat-autocomplete-panel') as HTMLElement; + expect(panel.classList) + .toContain('mat-autocomplete-visible', `Expected panel to start out visible.`); + + // Filter down the option list such that no options match the value + typeInElement('af', input); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(panel.classList) + .toContain('mat-autocomplete-hidden', `Expected panel to hide itself when empty.`); + }); + }); + })); + + it('should keep the label floating until the panel closes', async(() => { + fixture.componentInstance.trigger.openPanel(); + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('always', 'Expected placeholder to float as soon as panel opens.'); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('auto', 'Expected placeholder to return to auto state after panel closes.'); + }); + })); + + it('should not open the panel when the `input` event is invoked on a non-focused input', () => { + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel state to start out closed.`); + + input.value = 'Alabama'; + dispatchFakeEvent(input, 'input'); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel state to stay closed.`); + }); + + it('should not mess with placeholder placement if set to never', async(() => { + fixture.componentInstance.placeholder = 'never'; + fixture.detectChanges(); + + fixture.componentInstance.trigger.openPanel(); + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('never', 'Expected placeholder to stay static.'); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('never', 'Expected placeholder to stay in static state after close.'); + }); + })); + + it('should not mess with placeholder placement if set to always', async(() => { + fixture.componentInstance.placeholder = 'always'; + fixture.detectChanges(); + + fixture.componentInstance.trigger.openPanel(); + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('always', 'Expected placeholder to stay elevated on open.'); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('always', 'Expected placeholder to stay elevated after close.'); + }); + })); + }); + it('should have the correct text direction in RTL', () => { + dir = 'rtl'; + + const rtlFixture = TestBed.createComponent(SimpleAutocomplete); + rtlFixture.detectChanges(); + + rtlFixture.componentInstance.trigger.openPanel(); + rtlFixture.detectChanges(); + + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + expect(overlayPane.getAttribute('dir')).toEqual('rtl'); + + }); + + describe('forms integration', () => { + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + input = fixture.debugElement.query(By.css('input')).nativeElement; + }); + + it('should update control value as user types with input value', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + typeInElement('a', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.value) + .toEqual('a', 'Expected control value to be updated as user types.'); + + typeInElement('al', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.value) + .toEqual('al', 'Expected control value to be updated as user types.'); + }); + + it('should update control value when option is selected with option value', async(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.value) + .toEqual({code: 'CA', name: 'California'}, + 'Expected control value to equal the selected option value.'); + }); + })); + + it('should update control back to string if user types after option is selected', async(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + typeInElement('Californi', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.value) + .toEqual('Californi', 'Expected control value to revert back to string.'); + }); + })); + + it('should fill the text field with display value when an option is selected', async(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(input.value) + .toContain('California', `Expected text field to fill with selected value.`); + }); + })); + + it('should fill the text field with value if displayWith is not set', async(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.componentInstance.panel.displayWith = null; + fixture.componentInstance.options.toArray()[1].value = 'test value'; + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + + fixture.detectChanges(); + expect(input.value) + .toContain('test value', `Expected input to fall back to selected option's value.`); + }); + })); + + it('should fill the text field correctly if value is set to obj programmatically', async(() => { + fixture.whenStable().then(() => { + fixture.componentInstance.stateCtrl.setValue({code: 'AL', name: 'Alabama'}); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(input.value) + .toContain('Alabama', `Expected input to fill with matching option's viewValue.`); + }); + }); + })); + + it('should clear the text field if value is reset programmatically', async(() => { + typeInElement('Alabama', input); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.componentInstance.stateCtrl.reset(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(input.value).toEqual('', `Expected input value to be empty after reset.`); + }); + }); + })); + + it('should disable input in view when disabled programmatically', () => { + const inputUnderline = + fixture.debugElement.query(By.css('.mat-input-underline')).nativeElement; + + expect(input.disabled) + .toBe(false, `Expected input to start out enabled in view.`); + expect(inputUnderline.classList.contains('mat-disabled')) + .toBe(false, `Expected input underline to start out with normal styles.`); + + fixture.componentInstance.stateCtrl.disable(); + fixture.detectChanges(); + + expect(input.disabled) + .toBe(true, `Expected input to be disabled in view when disabled programmatically.`); + expect(inputUnderline.classList.contains('mat-disabled')) + .toBe(true, `Expected input underline to display disabled styles.`); + }); + + + it('should mark the autocomplete control as dirty as user types', () => { + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(false, `Expected control to start out pristine.`); + + typeInElement('a', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(true, `Expected control to become dirty when the user types into the input.`); + }); + + it('should mark the autocomplete control as dirty when an option is selected', async(() => { + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(false, `Expected control to start out pristine.`); + + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + options[1].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(true, `Expected control to become dirty when an option was selected.`); + }); + })); + + it('should not mark the control dirty when the value is set programmatically', () => { + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(false, `Expected control to start out pristine.`); + + fixture.componentInstance.stateCtrl.setValue('AL'); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(false, `Expected control to stay pristine if value is set programmatically.`); + }); + + it('should mark the autocomplete control as touched on blur', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(false, `Expected control to start out untouched.`); + + dispatchFakeEvent(input, 'blur'); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.touched) + .toBe(true, `Expected control to become touched on blur.`); + }); + + }); + + describe('keyboard events', () => { + let fixture: ComponentFixture; + let input: HTMLInputElement; + let DOWN_ARROW_EVENT: KeyboardEvent; + let ENTER_EVENT: KeyboardEvent; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + input = fixture.debugElement.query(By.css('input')).nativeElement; + DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent; + ENTER_EVENT = new MockKeyboardEvent(ENTER) as KeyboardEvent; + + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + }); + + it('should not focus the option when DOWN key is pressed', async(() => { + fixture.whenStable().then(() => { + spyOn(fixture.componentInstance.options.first, 'focus'); + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + expect(fixture.componentInstance.options.first.focus).not.toHaveBeenCalled(); + }); + })); + + it('should not close the panel when DOWN key is pressed', async(() => { + fixture.whenStable().then(() => { + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to stay open when DOWN key is pressed.`); + expect(overlayContainerElement.textContent) + .toContain('Alabama', `Expected panel to keep displaying when DOWN key is pressed.`); + expect(overlayContainerElement.textContent) + .toContain('California', `Expected panel to keep displaying when DOWN key is pressed.`); + }); + })); + + it('should set the active item to the first option when DOWN key is pressed', fakeAsync(() => { + tick(); + const optionEls = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.activeOption) + .toBe(fixture.componentInstance.options.first, 'Expected first option to be active.'); + expect(optionEls[0].classList).toContain('mat-active'); + expect(optionEls[1].classList).not.toContain('mat-active'); + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.activeOption) + .toBe(fixture.componentInstance.options.toArray()[1], + 'Expected second option to be active.'); + expect(optionEls[0].classList).not.toContain('mat-active'); + expect(optionEls[1].classList).toContain('mat-active'); + })); + + it('should set the active item to the last option when UP key is pressed', fakeAsync(() => { + tick(); + const optionEls = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + + const UP_ARROW_EVENT = new MockKeyboardEvent(UP_ARROW) as KeyboardEvent; + fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.activeOption) + .toBe(fixture.componentInstance.options.last, 'Expected last option to be active.'); + expect(optionEls[10].classList).toContain('mat-active'); + expect(optionEls[0].classList).not.toContain('mat-active'); + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.activeOption) + .toBe(fixture.componentInstance.options.first, + 'Expected first option to be active.'); + expect(optionEls[0].classList).toContain('mat-active'); + })); + + it('should set the active item properly after filtering', fakeAsync(() => { + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + typeInElement('o', input); + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + const optionEls = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + + expect(fixture.componentInstance.trigger.activeOption) + .toBe(fixture.componentInstance.options.first, + 'Expected first option to be active.'); + expect(optionEls[0].classList).toContain('mat-active'); + expect(optionEls[1].classList).not.toContain('mat-active'); + })); + + it('should fill the text field when an option is selected with ENTER', async(() => { + fixture.whenStable().then(() => { + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); + fixture.detectChanges(); + expect(input.value) + .toContain('Alabama', `Expected text field to fill with selected value on ENTER.`); + }); + }); + })); + + it('should fill the text field, not select an option, when SPACE is entered', async(() => { + fixture.whenStable().then(() => { + typeInElement('New', input); + fixture.detectChanges(); + + const SPACE_EVENT = new MockKeyboardEvent(SPACE) as KeyboardEvent; + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + fixture.componentInstance.trigger._handleKeydown(SPACE_EVENT); + fixture.detectChanges(); + + expect(input.value) + .not.toContain('New York', `Expected option not to be selected on SPACE.`); + }); + }); + })); + + it('should mark the control dirty when selecting an option from the keyboard', async(() => { + fixture.whenStable().then(() => { + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(false, `Expected control to start out pristine.`); + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + fixture.whenStable().then(() => { + fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); + fixture.detectChanges(); + + expect(fixture.componentInstance.stateCtrl.dirty) + .toBe(true, `Expected control to become dirty when option was selected by ENTER.`); + }); + }); + })); + + it('should open the panel again when typing after making a selection', async(() => { + fixture.whenStable().then(() => { + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + fixture.whenStable().then(() => { + fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(false, `Expected panel state to read closed after ENTER key.`); + expect(overlayContainerElement.textContent) + .toEqual('', `Expected panel to close after ENTER key.`); + + typeInElement('Alabama', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to read open when typing in input.`); + expect(overlayContainerElement.textContent) + .toContain('Alabama', `Expected panel to display when typing in input.`); + }); + }); + })); + + it('should scroll to active options below the fold', fakeAsync(() => { + tick(); + const scrollContainer = + document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel'); + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to scroll.`); + + // These down arrows will set the 6th option active, below the fold. + [1, 2, 3, 4, 5].forEach(() => { + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + }); + + // Expect option bottom minus the panel height (288 - 256 = 32) + expect(scrollContainer.scrollTop) + .toEqual(32, `Expected panel to reveal the sixth option.`); + })); + + it('should scroll to active options on UP arrow', fakeAsync(() => { + tick(); + const scrollContainer = + document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel'); + + const UP_ARROW_EVENT = new MockKeyboardEvent(UP_ARROW) as KeyboardEvent; + fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + // Expect option bottom minus the panel height (528 - 256 = 272) + expect(scrollContainer.scrollTop).toEqual(272, `Expected panel to reveal last option.`); + })); + + }); + + describe('aria', () => { + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + input = fixture.debugElement.query(By.css('input')).nativeElement; + }); + + it('should set role of input to combobox', () => { + expect(input.getAttribute('role')) + .toEqual('combobox', 'Expected role of input to be combobox.'); + }); + + it('should set role of autocomplete panel to listbox', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + const panel = fixture.debugElement.query(By.css('.mat-autocomplete-panel')).nativeElement; + + expect(panel.getAttribute('role')) + .toEqual('listbox', 'Expected role of the panel to be listbox.'); + }); + + it('should set aria-autocomplete to list', () => { + expect(input.getAttribute('aria-autocomplete')) + .toEqual('list', 'Expected aria-autocomplete attribute to equal list.'); + }); + + it('should set aria-multiline to false', () => { + expect(input.getAttribute('aria-multiline')) + .toEqual('false', 'Expected aria-multiline attribute to equal false.'); + }); + + it('should set aria-activedescendant based on the active option', async(() => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(input.hasAttribute('aria-activedescendant')) + .toBe(false, 'Expected aria-activedescendant to be absent if no active item.'); + + const DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent; + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(input.getAttribute('aria-activedescendant')) + .toEqual(fixture.componentInstance.options.first.id, + 'Expected aria-activedescendant to match the active item after 1 down arrow.'); + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(input.getAttribute('aria-activedescendant')) + .toEqual(fixture.componentInstance.options.toArray()[1].id, + 'Expected aria-activedescendant to match the active item after 2 down arrows.'); + }); + }); + + }); + })); + + it('should set aria-expanded based on whether the panel is open', async(() => { + expect(input.getAttribute('aria-expanded')) + .toBe('false', 'Expected aria-expanded to be false while panel is closed.'); + + fixture.componentInstance.trigger.openPanel(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(input.getAttribute('aria-expanded')) + .toBe('true', 'Expected aria-expanded to be true while panel is open.'); + + fixture.componentInstance.trigger.closePanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(input.getAttribute('aria-expanded')) + .toBe('false', 'Expected aria-expanded to be false when panel closes again.'); + }); + }); + })); + + it('should set aria-expanded properly when the panel is hidden', async(() => { + fixture.componentInstance.trigger.openPanel(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(input.getAttribute('aria-expanded')) + .toBe('true', 'Expected aria-expanded to be true while panel is open.'); + + typeInElement('zz', input); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(input.getAttribute('aria-expanded')) + .toBe('false', 'Expected aria-expanded to be false when panel hides itself.'); + }); + }); + }); + })); + + it('should set aria-owns based on the attached autocomplete', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + const panel = fixture.debugElement.query(By.css('.mat-autocomplete-panel')).nativeElement; + + expect(input.getAttribute('aria-owns')) + .toEqual(panel.getAttribute('id'), 'Expected aria-owns to match attached autocomplete.'); + + }); + + }); + + describe('Fallback positions', () => { + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + input = fixture.debugElement.query(By.css('input')).nativeElement; + }); + + it('should use below positioning by default', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + const inputBottom = input.getBoundingClientRect().bottom; + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panelTop = panel.getBoundingClientRect().top; + + // Panel is offset by 6px in styles so that the underline has room to display. + expect((inputBottom + 6).toFixed(1)) + .toEqual(panelTop.toFixed(1), `Expected panel top to match input bottom by default.`); + expect(fixture.componentInstance.trigger.autocomplete.positionY) + .toEqual('below', `Expected autocomplete positionY to default to below.`); + }); + + it('should fall back to above position if panel cannot fit below', () => { + // Push the autocomplete trigger down so it won't have room to open "below" + input.style.top = '600px'; + input.style.position = 'relative'; + + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + const inputTop = input.getBoundingClientRect().top; + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panelBottom = panel.getBoundingClientRect().bottom; + + // Panel is offset by 24px in styles so that the label has room to display. + expect((inputTop - 24).toFixed(1)) + .toEqual(panelBottom.toFixed(1), `Expected panel to fall back to above position.`); + expect(fixture.componentInstance.trigger.autocomplete.positionY) + .toEqual('above', `Expected autocomplete positionY to be "above" if panel won't fit.`); + }); + + it('should align panel properly when filtering in "above" position', async(() => { + // Push the autocomplete trigger down so it won't have room to open "below" + input.style.top = '600px'; + input.style.position = 'relative'; + + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + typeInElement('f', input); + fixture.detectChanges(); + + const inputTop = input.getBoundingClientRect().top; + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panelBottom = panel.getBoundingClientRect().bottom; + + // Panel is offset by 24px in styles so that the label has room to display. + expect((inputTop - 24).toFixed(1)) + .toEqual(panelBottom.toFixed(1), `Expected panel to stay aligned after filtering.`); + expect(fixture.componentInstance.trigger.autocomplete.positionY) + .toEqual('above', `Expected autocomplete positionY to be "above" if panel won't fit.`); + }); + })); + + }); + + describe('misc', () => { + + it('should allow basic use without any forms directives', () => { + expect(() => { + const fixture = TestBed.createComponent(AutocompleteWithoutForms); + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')).nativeElement; + typeInElement('d', input); + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + expect(options.length).toBe(1); + }).not.toThrowError(); + }); + + it('should display an empty input when the value is undefined with ngModel', async(() => { + const fixture = TestBed.createComponent(AutocompleteWithNgModel); + + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const input = fixture.debugElement.query(By.css('input')).nativeElement; + + expect(input.value).toBe(''); + }); + })); + + it('should work when input is wrapped in ngIf', async(() => { + const fixture = TestBed.createComponent(NgIfAutocomplete); + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')).nativeElement; + dispatchFakeEvent(input, 'focus'); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.panelOpen) + .toBe(true, `Expected panel state to read open when input is focused.`); + expect(overlayContainerElement.textContent) + .toContain('One', `Expected panel to display when input is focused.`); + expect(overlayContainerElement.textContent) + .toContain('Two', `Expected panel to display when input is focused.`); + }); + })); + + it('should filter properly with ngIf after setting the active item', fakeAsync(() => { + const fixture = TestBed.createComponent(NgIfAutocomplete); + fixture.detectChanges(); + + fixture.componentInstance.trigger.openPanel(); + tick(); + fixture.detectChanges(); + + const DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent; + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')).nativeElement; + typeInElement('o', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.mdOptions.length).toBe(2); + })); + + }); + + it('should have correct width when opened', () => { + const widthFixture = TestBed.createComponent(SimpleAutocomplete); + widthFixture.componentInstance.width = 300; + widthFixture.detectChanges(); + + widthFixture.componentInstance.trigger.openPanel(); + widthFixture.detectChanges(); + + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + // Firefox, edge return a decimal value for width, so we need to parse and round it to verify + expect(Math.ceil(parseFloat(overlayPane.style.width))).toEqual(300); + + widthFixture.componentInstance.trigger.closePanel(); + widthFixture.detectChanges(); + + widthFixture.componentInstance.width = 500; + widthFixture.detectChanges(); + + widthFixture.componentInstance.trigger.openPanel(); + widthFixture.detectChanges(); + + // Firefox, edge return a decimal value for width, so we need to parse and round it to verify + expect(Math.ceil(parseFloat(overlayPane.style.width))).toEqual(500); + + }); }); @Component({ template: ` - + + + + + + + {{ state.code }}: {{ state.name }} + + + ` +}) +class SimpleAutocomplete implements OnDestroy { + stateCtrl = new FormControl(); + filteredStates: any[]; + valueSub: Subscription; + placeholder = 'auto'; + width: number; + + @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; + @ViewChild(MdAutocomplete) panel: MdAutocomplete; + @ViewChild(MdInputContainer) inputContainer: MdInputContainer; + @ViewChildren(MdOption) options: QueryList; + + states = [ + {code: 'AL', name: 'Alabama'}, + {code: 'CA', name: 'California'}, + {code: 'FL', name: 'Florida'}, + {code: 'KS', name: 'Kansas'}, + {code: 'MA', name: 'Massachusetts'}, + {code: 'NY', name: 'New York'}, + {code: 'OR', name: 'Oregon'}, + {code: 'PA', name: 'Pennsylvania'}, + {code: 'TN', name: 'Tennessee'}, + {code: 'VA', name: 'Virginia'}, + {code: 'WY', name: 'Wyoming'}, + ]; + + + constructor() { + this.filteredStates = this.states; + this.valueSub = this.stateCtrl.valueChanges.subscribe(val => { + this.filteredStates = val ? this.states.filter((s) => s.name.match(new RegExp(val, 'gi'))) + : this.states; + }); + } + + displayFn(value: any): string { + return value ? value.name : value; + } + + ngOnDestroy() { + this.valueSub.unsubscribe(); + } + +} + +@Component({ + template: ` + + + + + + + {{option}} + + ` }) -class SimpleAutocomplete {} +class NgIfAutocomplete { + optionCtrl = new FormControl(); + filteredOptions: Observable; + isVisible = true; + options = ['One', 'Two', 'Three']; + + @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; + @ViewChildren(MdOption) mdOptions: QueryList; + + constructor() { + this.filteredOptions = this.optionCtrl.valueChanges.startWith(null).map((val) => { + return val ? this.options.filter(option => new RegExp(val, 'gi').test(option)) + : this.options.slice(); + }); + } +} + + +@Component({ + template: ` + + + + + + + {{ state }} + + + ` +}) +class AutocompleteWithoutForms { + filteredStates: any[]; + states = ['Alabama', 'California', 'Florida']; + + constructor() { + this.filteredStates = this.states.slice(); + } + + onInput(value: any) { + this.filteredStates = this.states.filter(s => new RegExp(value, 'gi').test(s)); + } + +} + + +@Component({ + template: ` + + + + + + + {{ state }} + + + ` +}) +class AutocompleteWithNgModel { + filteredStates: any[]; + selectedState: string; + states = ['New York', 'Washington', 'Oregon']; + + constructor() { + this.filteredStates = this.states.slice(); + } + + onInput(value: any) { + this.filteredStates = this.states.filter(s => new RegExp(value, 'gi').test(s)); + } + +} +/** This is a mock keyboard event to test keyboard events in the autocomplete. */ +class MockKeyboardEvent { + constructor(public keyCode: number) {} + preventDefault() {} +} diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index 18545cb2c6e2..f516c80c532c 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -1,4 +1,24 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import { + AfterContentInit, + Component, + ContentChildren, + ElementRef, + Input, + QueryList, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import {MdOption} from '../core'; +import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-manager'; + +/** + * Autocomplete IDs need to be unique across components, so this counter exists outside of + * the component definition. + */ +let _uniqueAutocompleteIdCounter = 0; + +export type AutocompletePositionY = 'above' | 'below'; @Component({ moduleId: module.id, @@ -6,6 +26,60 @@ import {Component, ViewEncapsulation} from '@angular/core'; templateUrl: 'autocomplete.html', styleUrls: ['autocomplete.css'], encapsulation: ViewEncapsulation.None, + exportAs: 'mdAutocomplete', + host: { + '[class.mat-autocomplete]': 'true' + } }) -export class MdAutocomplete {} +export class MdAutocomplete implements AfterContentInit { + + /** Manages active item in option list based on key events. */ + _keyManager: ActiveDescendantKeyManager; + + /** Whether the autocomplete panel displays above or below its trigger. */ + positionY: AutocompletePositionY = 'below'; + + /** Whether the autocomplete panel should be visible, depending on option length. */ + showPanel = false; + + @ViewChild(TemplateRef) template: TemplateRef; + @ViewChild('panel') panel: ElementRef; + @ContentChildren(MdOption) options: QueryList; + + /** Function that maps an option's control value to its display value in the trigger. */ + @Input() displayWith: (value: any) => string; + + /** Unique ID to be used by autocomplete trigger's "aria-owns" property. */ + id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`; + + ngAfterContentInit() { + this._keyManager = new ActiveDescendantKeyManager(this.options).withWrap(); + } + + /** + * Sets the panel scrollTop. This allows us to manually scroll to display + * options below the fold, as they are not actually being focused when active. + */ + _setScrollTop(scrollTop: number): void { + if (this.panel) { + this.panel.nativeElement.scrollTop = scrollTop; + } + } + + /** Panel should hide itself when the option list is empty. */ + _setVisibility() { + Promise.resolve().then(() => this.showPanel = !!this.options.length); + } + + /** Sets a class on the panel based on its position (used to set y-offset). */ + _getClassList() { + return { + 'mat-autocomplete-panel-below': this.positionY === 'below', + 'mat-autocomplete-panel-above': this.positionY === 'above', + 'mat-autocomplete-visible': this.showPanel, + 'mat-autocomplete-hidden': !this.showPanel + }; + } + +} diff --git a/src/lib/autocomplete/index.ts b/src/lib/autocomplete/index.ts index 92bb41a39a65..7694e665ebbc 100644 --- a/src/lib/autocomplete/index.ts +++ b/src/lib/autocomplete/index.ts @@ -1,18 +1,25 @@ import {ModuleWithProviders, NgModule} from '@angular/core'; -import {DefaultStyleCompatibilityModeModule} from '../core'; + +import {MdOptionModule, OverlayModule, OVERLAY_PROVIDERS, CompatibilityModule} from '../core'; +import {CommonModule} from '@angular/common'; import {MdAutocomplete} from './autocomplete'; -export * from './autocomplete'; +import {MdAutocompleteTrigger} from './autocomplete-trigger'; @NgModule({ - imports: [DefaultStyleCompatibilityModeModule], - exports: [MdAutocomplete, DefaultStyleCompatibilityModeModule], - declarations: [MdAutocomplete], + imports: [MdOptionModule, OverlayModule, CompatibilityModule, CommonModule], + exports: [MdAutocomplete, MdOptionModule, MdAutocompleteTrigger, CompatibilityModule], + declarations: [MdAutocomplete, MdAutocompleteTrigger], }) export class MdAutocompleteModule { + /** @deprecated */ static forRoot(): ModuleWithProviders { return { ngModule: MdAutocompleteModule, - providers: [] + providers: [OVERLAY_PROVIDERS] }; } } + + +export * from './autocomplete'; +export * from './autocomplete-trigger'; diff --git a/src/lib/button-toggle/README.md b/src/lib/button-toggle/README.md index befdfbebdff9..fb4499d1cda0 100644 --- a/src/lib/button-toggle/README.md +++ b/src/lib/button-toggle/README.md @@ -1,139 +1 @@ -# md-button-toggle - -`MdButtonToggle` is a group of buttons that can be toggled. -There are two modes, `multiple` and `exclusive`. -When in 'exclusive' mode, only one button can be selected at a time (like radio buttons). -When in 'multiple' mode, multiple buttons can be selected at once (like checkboxes). -You can read more about button toggles in the -[Material Design spec](https://material.google.com/components/buttons.html#buttons-toggle-buttons). - -## Usage - -### Basic Usage - -`md-button-toggle` can be used on its own and acts as a checkbox. - -```html -Bold -``` - -Output: - -![Basic Toggle Button Example](https://material.angularjs.org/material2_assets/button-toggle/basic-toggle.png) - -### Exclusive Selection - -`md-button-toggle` can be used in an exclusive selection group when surrounded by -`md-button-toggle-group`. This styles all buttons within the group to appear as a single -group of button toggles and allows only one button toggle to be selected at a time. - -```html - - format_align_left - format_align_center - format_align_right - format_align_justify - -``` - -Output: - -![Exclusive Toggle Button Example](https://material.angularjs.org/material2_assets/button-toggle/exclusive-toggle.png) - -### Multiple Selection - -`md-button-toggle` can be used in a multiple selection group when surrounded by -`md-button-toggle-group multiple`. This styles all buttons within the group to appear as a single -group of button toggles. This component does not yet support `NgModel` when using `multiple` mode. - -```html - - Flour - Eggs - Sugar - Milk - -``` - -Output: - -![Multiple Toggle Button Example](https://material.angularjs.org/material2_assets/button-toggle/multi-toggle.png) - -## Dynamic Exclusive Selection - -`md-button-toggle`s can be used with `ngModel` to dynamically create groups and update the value for -a group. - -```html - - - {{pie}} - - -

Your favorite type of pie is: {{favoritePie}}

-``` - -### Disabled Button Toggle - -`md-button-toggle-group` and `md-button-toggle` can both be disabled by adding a `disabled` -attribute to either one. Adding `disabled` to an exclusive group or a multiple group will disable -the entire group. Adding `disabled` to a single toggle will disable that toggle. - -```html - - One - Two - Three - - - - Red - Blue - -``` - -Output: - -![Disabled Toggle Buttons Example](https://material.angularjs.org/material2_assets/button-toggle/disabled-toggles.png) - -## `` - -### Bound Properties - -| Name | Type | Description | -| --- | --- | --- | -| `id` | string | The unique ID of the toggle. IDs are generated by default when not specified. | -| `name` | string | Optional, defaults to parent group name if one exists for exclusive selection toggles, otherwise null. This is used to differentiate between different groups. | -| `value` | any | Value of the toggle. Only used when in a group to determine which are selected. | -| `checked` | boolean | Optional, default = `false`. Whether or not the toggle is checked. | -| `disabled` | boolean | Optional, default = `false`. Whether or not the toggle is disabled | - -### Events - -| Name | Description | -| --- | --- | -| `change` | Emitted when the `checked` value is changed. | - -## `` - -### Bound Properties - -| Name | Type | Description | -| --- | --- | --- | -| `name` | string | Optional, the name of the group. | -| `disabled` | boolean | Optional, default = `false`. | -| `value` | any | The current value for the group. Will be set to the value of the selected toggle or a list of values from the selected toggles. | -| `selected` | `mdButtonToggle` | The current selected toggle or a list of the selected toggles. | -| `vertical` | boolean | Whether the group should show the toggles vertically. Default is `false`. | - -### Attributes - -| Name | Type | Description | -| --- | --- | --- | -| `multiple` | boolean | Optional, default = `false`. Whether or not the group allows multiple selection. | - -### Events - -| Name | Description | -| --- | --- | -| `change` | Emitted when the `value` of the group changes. | +Please see the official documentation at https://material.angular.io/components/component/button-toggle \ No newline at end of file diff --git a/src/lib/button-toggle/_button-toggle-theme.scss b/src/lib/button-toggle/_button-toggle-theme.scss index 23ede0f16c3b..bebdd1e7be3a 100644 --- a/src/lib/button-toggle/_button-toggle-theme.scss +++ b/src/lib/button-toggle/_button-toggle-theme.scss @@ -1,14 +1,38 @@ @import '../core/theming/palette'; @import '../core/theming/theming'; +// Applies a focus style to an md-button-toggle element for each of the supported palettes. +@mixin _mat-button-toggle-focus-color($theme) { + $background: map-get($theme, background); -@mixin md-button-toggle-theme($theme) { + .mat-button-toggle-focus-overlay { + background-color: mat-color($background, focused-button); + } +} + +@mixin mat-button-toggle-theme($theme) { $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-button-toggle { + color: mat-color($foreground, hint-text); - .md-button-toggle-checked .md-button-toggle-label-content { - background-color: md-color($md-grey, 300); + &.cdk-focused { + @include _mat-button-toggle-focus-color($theme); + } } - .md-button-toggle-disabled .md-button-toggle-label-content { - background-color: md-color($foreground, disabled); + + .mat-button-toggle-checked { + background-color: mat-color($background, selected-button); + color: mat-color($foreground, base); + } + + .mat-button-toggle-disabled { + background-color: mat-color($background, disabled-button-toggle); + color: mat-color($foreground, disabled-button); + + &.mat-button-toggle-checked { + background-color: mat-color($background, selected-disabled-button); + } } } diff --git a/src/lib/button-toggle/button-toggle.html b/src/lib/button-toggle/button-toggle.html index 60cccfd2e44a..fffdfa7c3e25 100644 --- a/src/lib/button-toggle/button-toggle.html +++ b/src/lib/button-toggle/button-toggle.html @@ -1,5 +1,5 @@ -