diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index ec97dc16fd3..fedb80263d7 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -28,7 +28,7 @@ jobs: - name: Setup node uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 registry-url: https://registry.npmjs.org/ - name: Cache dependencies uses: actions/cache@v2 diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 0b078f503a3..3a63043a643 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -20,7 +20,7 @@ jobs: - name: Setup node uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 registry-url: https://registry.npmjs.org/ - name: Cache dependencies uses: actions/cache@v2 diff --git a/.github/workflows/release-pull-request.yml b/.github/workflows/release-pull-request.yml index 904d45cabd7..b5ed4ac20d2 100644 --- a/.github/workflows/release-pull-request.yml +++ b/.github/workflows/release-pull-request.yml @@ -29,7 +29,7 @@ jobs: - name: Setup node uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 registry-url: https://registry.npmjs.org/ - name: Cache dependencies uses: actions/cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a2bf33446f..c1fe037b85a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - name: Setup node uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 registry-url: https://registry.npmjs.org/ - name: Cache dependencies uses: actions/cache@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ed869d6251..717f9eae242 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 - uses: actions/cache@v2 with: path: ~/.npm @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 - uses: actions/cache@v2 with: path: | @@ -64,7 +64,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 - uses: actions/cache@v2 with: path: | @@ -96,7 +96,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 10 + node-version: 14 - uses: actions/cache@v2 with: path: | @@ -108,22 +108,3 @@ jobs: - name: Run dependency tests run: | npm run test:dependency - sass-test: - runs-on: ubuntu-latest - needs: install - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 10 - - uses: actions/cache@v2 - with: - path: | - node_modules - packages/*/node_modules - !packages/*/node_modules/@material - key: ${{ runner.os }}-lerna-${{ hashFiles('package-lock.json', 'packages/**/package.json') }} - - run: npm run link - - name: Run Sass tests - run: | - npm run test:sass diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index 55b7b5b9fa5..6ed38c1ee4b 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -42,7 +42,11 @@ rules: - true - ignorePseudoElements: ng-deep - scss/at-rule-no-unknown: true + scss/at-rule-no-unknown: + - true + - ignoreAtRules: + - provide + - require # No config allows this: # # ```scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 476792df58d..e95565aabe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,199 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [14.0.0](https://github.com/material-components/material-components-web/compare/v13.0.0...v14.0.0) (2022-04-27) + + +### Bug Fixes + +* **button:** update HCM shim to use the existing focus-ring ([a657abb](https://github.com/material-components/material-components-web/commit/a657abb61a2c7d23c2bd92c3cbd54ed404d0bafe)) +* **checkbox:** Add explicit system color for checkmark in HCM. ([8c4da22](https://github.com/material-components/material-components-web/commit/8c4da223a7d35c4ca0a4f27534ecdb13087d0f1b)) +* **checkbox:** move forced-colors theme out of static styles ([bbd1126](https://github.com/material-components/material-components-web/commit/bbd11268f0ea4fd45205a642f1d26a4c4ff34e05)) +* **checkbox:** Update checkbox theme styles mixin to accept css vars ([c14e977](https://github.com/material-components/material-components-web/commit/c14e977ee36c26074b04dea5ce37e2300e9d3735)) +* **chips:** Fix typography selector in GMDC-Wiz chips theming ([43c7d87](https://github.com/material-components/material-components-web/commit/43c7d87dc0c928d74652ceaebebca51c69b6eaaf)) +* **datatable:** Adjust data table last row border-radius to support setting row background-color. ([ba78e87](https://github.com/material-components/material-components-web/commit/ba78e87246af31e1fe7dd03241a7565f2892fd64)) +* **dialog:** Render dividers in Firefox 94 on Windows HCM ([fae6c65](https://github.com/material-components/material-components-web/commit/fae6c652d7debc3e2875ab9049d806513ddc2421)) +* **dialog:** Set default z-index for close button in FloatingSheet dialog. ([3366a71](https://github.com/material-components/material-components-web/commit/3366a71d72f48e946a3f3b314b315395fafb3a1f)) +* **fab:** Add focus ring in HCM. ([d57ec74](https://github.com/material-components/material-components-web/commit/d57ec74c7e9afe5db07162202e6f05b28dd581db)) +* **focus-ring:** add 2d padding customizability, RTL bugfix ([f81fb1d](https://github.com/material-components/material-components-web/commit/f81fb1d232901c17d5794499c26e746ccab1af19)) +* **focus-ring:** box-sizing bugfix to content-box. If box-sizing border-box is inherited the ring spacing will collapse. ([e58552c](https://github.com/material-components/material-components-web/commit/e58552c6efa0442a36f441efe27cd01f3df2a524)) +* **focus-ring:** ignore pointer events ([3ef470e](https://github.com/material-components/material-components-web/commit/3ef470efe6f1d213935dca37ad213030cdd30d88)) +* **focus-ring:** RTL bugfix ([e00181e](https://github.com/material-components/material-components-web/commit/e00181e59cb1f29f4a8280f48b377a667a3e783b)) +* **iconbutton:** Fixed max width and height for high contrast mode focus ring on icon buttons. Display only in forced colors mode. ([cf42927](https://github.com/material-components/material-components-web/commit/cf429277817af685fd0b23a21a2e10ddabc4b9ef)) +* **iconbutton:** Set icon button ripple z-index to -1. ([586e740](https://github.com/material-components/material-components-web/commit/586e740ddcaa674c409d68ab5faf9ee8c61e2d91)) +* **list:** Improve a11y for multi-select lists ([9736ddc](https://github.com/material-components/material-components-web/commit/9736ddce9c12f5485e746984225919568541e88d)) +* **list:** Remove conflicting validation for checkbox list in setEnabled ([353ca7e](https://github.com/material-components/material-components-web/commit/353ca7e9f21b3589a17d25d8892198cba811dd13)) +* **list:** Update lastSelectedIndex when toggling a checkbox range ([dcba26f](https://github.com/material-components/material-components-web/commit/dcba26fe1028cf151ebf34c5947082a49cc85f10)) +* **menusurface:** Add a getOwnerDocument() method to MDCMenuSurfaceAdapter to provide a reference to the document that owns the menu surface DOM element. ([3486659](https://github.com/material-components/material-components-web/commit/348665978ce73694ad4518626dd70cdf5b984113)) +* **radio:** Fix disabled state in Firefox Windows high contrast mode ([23043ac](https://github.com/material-components/material-components-web/commit/23043acd0e5341729230cc6e1840460977056115)) +* **radio:** Modify theme styles Sass mixin validation to validate only keys ([390220e](https://github.com/material-components/material-components-web/commit/390220e422be57e8f38b74e04d8e8aba6082d333)) +* **select:** Add border to select menu in HCM. ([5d80969](https://github.com/material-components/material-components-web/commit/5d809696c6699bf9563e8faf8f79ff6ebd78febf)) +* **select:** revert down/up arrow on anchor changing selected index ([43d08ba](https://github.com/material-components/material-components-web/commit/43d08ba77e44dffbca99194ee18dbd202c8684f7)) +* **slider:** Fix bug where secondary click moves slider thumb. ([3ab9565](https://github.com/material-components/material-components-web/commit/3ab956515feb5c861498b156e68493e6f7f2a578)) +* **slider:** Fix IE11 bug - `unset` is unsupported in IE. ([f460e23](https://github.com/material-components/material-components-web/commit/f460e23dae619c6d09de114cc8c319972b7d1b10)) +* **slider:** In updateUI, fix behavior to match jsdoc claim that when thumb param is undefined it updates both thumbs. Input attributes were not being updated at all. ([cc4ed13](https://github.com/material-components/material-components-web/commit/cc4ed13ccda7b44edce0070edd6ee215e98cc792)) +* **slider:** Make the slider errors easier to debug by providing all relevant values in the error message. ([8687937](https://github.com/material-components/material-components-web/commit/868793776d9610ab068af706297a4f4599c7f6f1)) +* **snackbar:** address Trusted Types violation ([cbd9358](https://github.com/material-components/material-components-web/commit/cbd9358a61e7626ebc12af2cdd3dc465e11ce8bf)) +* **tooltip:** Adjusts logic in `validateTooltipWithCaretDistances` method. ([3e30054](https://github.com/material-components/material-components-web/commit/3e30054fb92c75b85d2f6186f4656d36ea05d6a1)) +* **typography:** Fixes typography `theme-styles` mixin... the value being retreived from the `$theme` map and css property name was swapped. The mixin would request `font-size`/`font-weight`/`letter-spacing` from the `$theme` map (which expects `size`/`weight`/`tracking`)... so these values would always be `null`. ([32b3913](https://github.com/material-components/material-components-web/commit/32b391398aa70f2fbb917cd4649b84d87876cd8e)) +* Remove /** [@override](https://github.com/override) */ tags from TypeScript code. ([c3cdff0](https://github.com/material-components/material-components-web/commit/c3cdff07b59adb0f44b40dbbca2cf05868138528)) +* Simplify MDCAttachable interface to be any object (Function) that has `attachTo`. ([05db65e](https://github.com/material-components/material-components-web/commit/05db65ec07db5a230e2ba1144000046b09d4c44b)) +* Snackbar action button ripple color is applied to the ripple element. ([4e66fb2](https://github.com/material-components/material-components-web/commit/4e66fb2e181b35ac47ae3a6863c101d3ff4f885e)) +* Work around bug in Sass ([037285f](https://github.com/material-components/material-components-web/commit/037285f9bda55dfb2e011f7d58338b29bfa37b6b)), closes [sass/sass#3259](https://github.com/sass/sass/issues/3259) +* **switch:** Restore Firefox 94 HCM outlines ([39cf14b](https://github.com/material-components/material-components-web/commit/39cf14bc3b0ef053219328d36faaf2636e0f77f4)) +* **textfield:** Fix breaking tests due to no valid pointerId being associated with pointer events. ([15db4f1](https://github.com/material-components/material-components-web/commit/15db4f1641bd3657ed230afacb41da84fb1077bc)) +* **tooltip:** Only sends notification of a tooltip being hidden if `showTimeout` is not set (indicating that this tooltip is about to be re-shown). ([6ca8b8f](https://github.com/material-components/material-components-web/commit/6ca8b8f858f0d508a56cf09bcb4b11221f901acb)) + + +### Features + +* **banner:** Add disableAutoClose params for both banner actions to prevent the banner buttons from automatically closing the banner. Add adapter #notifyActionClicked method. ([b094eaa](https://github.com/material-components/material-components-web/commit/b094eaa4e7a69132eb09f7b9d6c1d429a3f702a0)) +* **chips:** add focus ring styles ([783f6fd](https://github.com/material-components/material-components-web/commit/783f6fd5a29a56f6c72917546b7426f196bf4cae)) +* **chips:** Added elevation tint layer color support in chips ([c78ff04](https://github.com/material-components/material-components-web/commit/c78ff042967de7cf823a9f9826f8f613be9e4846)) +* **data-table:** separate table structure into its own mixin ([9f9d928](https://github.com/material-components/material-components-web/commit/9f9d928b2c6701707c5e5d32414c0c4db6a4d564)) +* **dialog:** Add styling for floating sheets ([78305b6](https://github.com/material-components/material-components-web/commit/78305b6d547b07aa06db04ad47b765b8f92851fa)) +* **dialog:** Add styling for floating sheets with content padding ([3e20c1d](https://github.com/material-components/material-components-web/commit/3e20c1de85fd72ff5efb3107c442036fc6317b4a)) +* **Dialog:** Adds an API to hide the header for GMDC Fullscreen Dialog in non-fullscreen mode ([ab4aba1](https://github.com/material-components/material-components-web/commit/ab4aba1afaba8f75042ed774f9e618f811bec45e)) +* **Dialog:** Adds an API to set custom position for GMDC Dialog ([ea9b5b4](https://github.com/material-components/material-components-web/commit/ea9b5b463c694804f79deaf8125c73779d34f5ce)) +* **Dialog:** Adds an API to set custom z-index for GMDC Dialog ([96ea061](https://github.com/material-components/material-components-web/commit/96ea061c1787cc329e64e9054ba1402c442a15f0)) +* **focus-ring:** added a new mixin so we can override just the focus-ring color ([641ed08](https://github.com/material-components/material-components-web/commit/641ed08513037285879a13708b95661d69c2f8b0)) +* **focus-ring:** added a new mixin so we can override just the focus-ring radius ([7321d62](https://github.com/material-components/material-components-web/commit/7321d6254d63f701b2f381c9cf11eb0708b56a6e)) +* **iconbutton:** Add link icon button Sass. ([9803d2d](https://github.com/material-components/material-components-web/commit/9803d2dc1c88e44b43a56249916f474581f5382e)) +* **mdc-list:** introduce selection change event ([7d8ea46](https://github.com/material-components/material-components-web/commit/7d8ea4624e7dcdc5ce69edf1e747c77354457f42)) +* **menu:** allow preferentially opening surface below anchor ([261f2db](https://github.com/material-components/material-components-web/commit/261f2db59382d561d6c89a7c41dafb6daad5a717)) +* **MenuSurface:** Add opening event for menus. ([53b3cad](https://github.com/material-components/material-components-web/commit/53b3cad2f5ef4d0c9d5cf03c752f27035dd7c818)) +* **select:** Add theming mixin boilerplate code to select ([ae8a6a3](https://github.com/material-components/material-components-web/commit/ae8a6a3a3e650fd068461d1236c5173c8e0467c8)) +* **select:** Add validation getter methods. ([bdf1d37](https://github.com/material-components/material-components-web/commit/bdf1d3771cd5a3c5b23a5cea63d3eaf97ded257d)) +* **select:** Added theme mixins to MDC select ([dcfe49c](https://github.com/material-components/material-components-web/commit/dcfe49c98ac25f5f5d64db021219d8a6c1caf6de)) +* **slider:** Add `minRange` param to range sliders to request a minimum gap between the two thumbs. ([8fffcb5](https://github.com/material-components/material-components-web/commit/8fffcb5ddfb93f1459d62c67f4a051b035bf9940)) +* **slider:** Add an option to hide focus styles after pointer interaction. ([ec54d90](https://github.com/material-components/material-components-web/commit/ec54d904621360bcb27e6d3cd1f33112e2a54aac)) +* **slider:** Keep the slider value indicator within the bounds of the slider if possible. ([c047f7c](https://github.com/material-components/material-components-web/commit/c047f7c19a9452aaced85ce1608b0bc2a63db223)) +* **state:** make context aware ([b2fe352](https://github.com/material-components/material-components-web/commit/b2fe3528bc1603df715c833abb5d905080681102)) +* **switch:** Add high contrast mode focus ring to switch ([f31a833](https://github.com/material-components/material-components-web/commit/f31a833fae6f132318068f30a24a12c6c0cc192f)) +* **text-field:** Add theming mixin boilerplate code to text-field ([eb382f3](https://github.com/material-components/material-components-web/commit/eb382f31889be98f4eebea1670b78c7c10f7016b)) +* **text-field:** Added theme mixins to MDC text field ([344d528](https://github.com/material-components/material-components-web/commit/344d528233437e5f9ff960913957022f05bbdc04)) +* **textfield:** adding input-font-size mixin ([207230e](https://github.com/material-components/material-components-web/commit/207230eb81e8c10cd5f962725af8c0184b8f3b62)) +* **theme:** allow custom property strings in theme.validate-theme() ([4e372fb](https://github.com/material-components/material-components-web/commit/4e372fb4937f0fe71cce7c4c829b099f9f3dcf6b)) +* add new class and mixin for open state of a menu item ([9a02b6e](https://github.com/material-components/material-components-web/commit/9a02b6ef8e8f235c7bd07cd8c6ce9078a46dbb78)) +* Indicate which thumb `valueToAriaValueTextFn` and `valueToValueIndicatorTextFn` functions are called for. ([b6510c8](https://github.com/material-components/material-components-web/commit/b6510c8c1cc3a91937180f03418ea20a0cf2387b)) +* **textfield:** adding input-font-family mixin ([991fb99](https://github.com/material-components/material-components-web/commit/991fb99f715872b49c89c44a6c98e82b4507fd14)) +* Describe how to add child lists into a list item. ([758ce31](https://github.com/material-components/material-components-web/commit/758ce31d94d065b93d09db0016ec86b56d9197ec)) + + +### BREAKING CHANGES + +* **MenuSurface:** Adds #notifyOpening method to menu surface adapter. + +PiperOrigin-RevId: 444830518 +* **slider:** Adds #getValueIndicatorContainerWidth method to slider adapter. + +PiperOrigin-RevId: 419837612 + + + + + +# [13.0.0](https://github.com/material-components/material-components-web/compare/v12.0.0...v13.0.0) (2021-09-24) + + +### Bug Fixes + +* Fix missing $ripple-target param for ripple mixin ([1340ee9](https://github.com/material-components/material-components-web/commit/1340ee9f7507e6653537d081c53826b5c25b3e22)) +* **banner:** Adjusting theme api selectors to use `mdc-button`. ([15981e9](https://github.com/material-components/material-components-web/commit/15981e9d95097895247fbcbd6ad9ad14c46be20e)) +* **banner:** Correcting incorrect theme values passed through to button's `theme-mixin`. ([0de2f2e](https://github.com/material-components/material-components-web/commit/0de2f2edcb53e05a97ae79c7f5fc181033fbb0cc)) +* **banner:** exclude source from npm package ([#7381](https://github.com/material-components/material-components-web/issues/7381)) ([d48a017](https://github.com/material-components/material-components-web/commit/d48a01771a5c5b080179ac34e4ef34146de5e209)), closes [#7360](https://github.com/material-components/material-components-web/issues/7360) +* **banner:** Removing `action--label-text-color` values from MDC `light-theme` map. ([d97f8f1](https://github.com/material-components/material-components-web/commit/d97f8f133c7d59bbddc49fe39dd9c714bcbb01d4)) +* **button:** cleanup outlined button theme keys ([28d0d75](https://github.com/material-components/material-components-web/commit/28d0d75bb554be14171de19f50d082f837125f37)) +* **button:** fix touch target reset in context of link buttons ([3b8d442](https://github.com/material-components/material-components-web/commit/3b8d4429e3373661fef202613389e4f2f00b33ad)) +* **button:** remove negative padding around icons ([d470693](https://github.com/material-components/material-components-web/commit/d4706933f473925ec3a1ce6f7152208b31664538)) +* **button:** remove rem/em transformers from typography theme-styles ([a395972](https://github.com/material-components/material-components-web/commit/a395972cfaf2260da9f50875a8fe772cc3c69d83)) +* **button:** stack ripple behind content ([e1e69fd](https://github.com/material-components/material-components-web/commit/e1e69fd8e5fb5624173bc3836f92e6824596ad04)) +* **density:** typo in variable exports ([6df682e](https://github.com/material-components/material-components-web/commit/6df682e746d1c417fe00376ba6ec33bd72159757)) +* **dom:** Support providing an owner document for announcer messages. ([6236f35](https://github.com/material-components/material-components-web/commit/6236f3576a7f39f452175206f96c08b08315444b)) +* **elevation:** reduce warnings when not providing elevation tokens ([adb9f1a](https://github.com/material-components/material-components-web/commit/adb9f1ad8c85e016bbe714d9b8f2d7d28e610f91)) +* **iconbutton:** Fix icon button theme keys/light theme values based on updated tokens. ([42d175e](https://github.com/material-components/material-components-web/commit/42d175efc20e9b36eb86a843b23a60626d36e065)) +* **menu:** apply elevation overlay to new lists ([0ad12ed](https://github.com/material-components/material-components-web/commit/0ad12ed3cffe78a27c7005e2ed9b83643ebb5114)) +* **sass:** Wrap templated calc expressions in strings ([818f4ee](https://github.com/material-components/material-components-web/commit/818f4ee93de968dfaa9d64929b3edf2d9f8dbe01)), closes [#7391](https://github.com/material-components/material-components-web/issues/7391) +* **slider:** Reorder such that dragstart event is emitted before any other events when handling drag start. ([877e3fb](https://github.com/material-components/material-components-web/commit/877e3fb0dbdaf06cf3a9b4fb0fa731df2093901c)) +* **slider:** Replace `innerHTML` with `firstChild` ([37d4db8](https://github.com/material-components/material-components-web/commit/37d4db86667c967ba173e318a124b72109cc1bb5)) +* Fix compilation issues with TypeScript 4.4 ([7246447](https://github.com/material-components/material-components-web/commit/72464476cea3755fbcbb64df832e9933ea7b1170)) +* **switch:** add pointer cursor ([12f5622](https://github.com/material-components/material-components-web/commit/12f5622e14b68c12542cb2bf7236c5a1f5492add)) +* **switch:** distribute correct css ([#7292](https://github.com/material-components/material-components-web/issues/7292)) ([7b6bcb8](https://github.com/material-components/material-components-web/commit/7b6bcb85874e81f33956d1ec544aedcdc882ffed)) +* **switch:** elevation theme custom properties not working ([2865629](https://github.com/material-components/material-components-web/commit/28656298a9c01bd585fdb995be7aa96d3c3395e7)) +* **switch:** use correct colors for icons in all HCM themes ([d86fb6f](https://github.com/material-components/material-components-web/commit/d86fb6facd014e2c0c1a88108ddbb59595dea5ac)) +* **theme:** ensure state selectors negate properly ([7249a30](https://github.com/material-components/material-components-web/commit/7249a3060c6b15eef338b44b77065b47e0b26d52)) +* **tooltip:** Add a getActiveElement() method to MDCTooltipAdapter to delegate getting the active element from the correct document. ([e334676](https://github.com/material-components/material-components-web/commit/e3346766f22b23b6c1e04cb2821565d388d57054)) +* **tooltip:** Adjust tooltip `focusout` handler. Ensures that interactive tooltips remain open when ChromeVox uses linear navigation to read non-focusable content inside the tooltip. ([7c96e6b](https://github.com/material-components/material-components-web/commit/7c96e6b98a25839d249e1d56478e919564b5ff07)) +* **tooltip:** non-persistent tooltips disappear on scroll ([1f9259b](https://github.com/material-components/material-components-web/commit/1f9259b9d7821181d8655537cf80e95b9856dd7c)) +* update combined mdc package to use new switch CSS ([077dcfc](https://github.com/material-components/material-components-web/commit/077dcfcfe483b8631f51cc16a89557d056b4db58)), closes [#7304](https://github.com/material-components/material-components-web/issues/7304) +* **tooltip:** allow the Mac zoom service to access plain tooltip contents ([510cf90](https://github.com/material-components/material-components-web/commit/510cf90f289177cf148b2d72cdb773047410731b)) + + +### Code Refactoring + +* **fab:** Deprecate legacy Fab theme mixins ([83bdd02](https://github.com/material-components/material-components-web/commit/83bdd022246c1699de71346d5c162e1ded5a0836)) +* **iconbutton:** Forward only theme mixins from MDC icon button index module. ([0a90693](https://github.com/material-components/material-components-web/commit/0a906930027e2b55054be08aa8ce0d48dec8c25b)) +* **theme:** Rename validate-keys() to validate-theme() ([2fb068f](https://github.com/material-components/material-components-web/commit/2fb068fb0f7a1b0e038ede3a2ab27a972e5b2ee4)) + + +### Features + +* **button:** add custom props to outlined button theme-styles ([bf405d2](https://github.com/material-components/material-components-web/commit/bf405d22ae54eef77bbe437228540900aad2f0e0)) +* **button:** add custom props to protected button theme-styles ([4ca11fe](https://github.com/material-components/material-components-web/commit/4ca11fe76395824dff6b3e35d954af817ace1591)) +* **button:** add custom props to text button theme-styles ([3dd6110](https://github.com/material-components/material-components-web/commit/3dd61109132cf17b5a92a941ecc0f03b0a1cc8d5)) +* **button:** add missing transitions to box-shadow/border ([3b92903](https://github.com/material-components/material-components-web/commit/3b9290351308626b4699e2cbdaeb4dc7f04ce1d9)) +* **button:** add static-styles-without-ripple for MWC consumption ([f4241a4](https://github.com/material-components/material-components-web/commit/f4241a42a49d130fcf5b5a9df2239276628a85f1)) +* **button:** add theme mixin that emits custom properties instead ([4c40586](https://github.com/material-components/material-components-web/commit/4c405863bde72948dd131b07847b798cd8669764)) +* **button:** emit custom properties fill button theme-styles ([a80c8b2](https://github.com/material-components/material-components-web/commit/a80c8b2c263b4f69a9df57e9837f7cb4ca438428)) +* **button:** m3 elevation + icon base theme modules ([2da3606](https://github.com/material-components/material-components-web/commit/2da3606b97553ef152c9ef485432df2e0287b5de)) +* **button:** resolve elevation keys in theme mixin ([843342f](https://github.com/material-components/material-components-web/commit/843342f99a2f76895fedb1ad1b2ff88a96b3fd7d)) +* **chips:** Add theming Sass mixin to MDC Filter Chip ([8390093](https://github.com/material-components/material-components-web/commit/83900936a87a32accaab8bc8a1bdc5a998fcf18f)) +* **chips:** Add theming Sass mixin to MDC input & suggestion Chip ([860ad06](https://github.com/material-components/material-components-web/commit/860ad06a1dd8bc76334ee5954109b4623f3682db)) +* **chips:** Added theme mixins to Assist Chip ([d4e16a6](https://github.com/material-components/material-components-web/commit/d4e16a6c4876a3f144fdd1bade201f5b607b2bf6)) +* **chips:** Export all non-deprecated members through chips index ([8647986](https://github.com/material-components/material-components-web/commit/864798678626ba41619324bcd10cf5e070bdd147)) +* **chips:** Rename action's exported members to avoid naming collisions ([b49359c](https://github.com/material-components/material-components-web/commit/b49359c3581208ed7f84835c490a094699936f95)) +* **chips:** Rename chip set's exported members to avoid naming collisions ([13db34b](https://github.com/material-components/material-components-web/commit/13db34b342741b4bc35b3c6a65a74e2291e41100)) +* **chips:** Rename chip's exported members to avoid naming collisions ([470bd34](https://github.com/material-components/material-components-web/commit/470bd34e89b6683cd3a8f71bd1d3acfdf0aac5bf)) +* **data-table:** Implement row click feature to MDC data table ([8de07c0](https://github.com/material-components/material-components-web/commit/8de07c02a50247f41cefcbd292b874b82f6d09b1)) +* **data-table:** use new select + list templates for pagination ([08398f8](https://github.com/material-components/material-components-web/commit/08398f88046bfc1c3fad494b82c6e905d2fad890)) +* **dialog:** Add theme styles mixin to dialog ([21ece53](https://github.com/material-components/material-components-web/commit/21ece536071235455a6905957f3c15dd3a7ddcf8)) +* **dialog:** Separate static styles from dialog core-styles mixin ([43d2eed](https://github.com/material-components/material-components-web/commit/43d2eed2a908bae0d747b1ce4459b38cbd68c94a)) +* **fab:** create theming file for small fabs ([d082790](https://github.com/material-components/material-components-web/commit/d082790f045f4542a5ebec082ba72ba0a106bcca)) +* **fab:** prepare fab-extended for theming in MWC ([ce25bc3](https://github.com/material-components/material-components-web/commit/ce25bc3ecc6836d6c46e3789ff6eeb6faf7c07cf)) +* **iconbutton:** Add `.mdc-icon-button--display-flex` class that centers icon via flexbox. When using the new theme API, the icon button should have this class. ([8355e14](https://github.com/material-components/material-components-web/commit/8355e14dc31c618a2102a846cd8cbefa08ad6007)) +* **iconbutton:** Add MDC theme mixin that declares custom properties. ([fa7520f](https://github.com/material-components/material-components-web/commit/fa7520f6274cbab3ae7d8298554c4b0ff9e21a54)) +* **iconbutton:** Add theme styles mixin. ([65aa63b](https://github.com/material-components/material-components-web/commit/65aa63b0ca587845437a4ee2a0b47556574d800b)) +* **menu:** Added getter method to check fixed position status of menu ([fb76c50](https://github.com/material-components/material-components-web/commit/fb76c5069ebe5f62a1b01f6b2f4613d7c6bdeaae)) +* **menu:** Adds option to prevent focus from being restored after an item action. ([65084ba](https://github.com/material-components/material-components-web/commit/65084baffaca256dd9eb77aae8fbafd379d8da00)) +* **select:** start compatibility work for evolution lists ([e8554db](https://github.com/material-components/material-components-web/commit/e8554dbbf4e9886dbf7a335c4953c1611c378b68)) +* **theme:** Added `validate-theme-keys()` mixin to validate theme keys only ([457d89a](https://github.com/material-components/material-components-web/commit/457d89aadf13d719af27435758feb8f6e254fe1e)) + + +### BREAKING CHANGES + +* **menu:** Adds new menu adapter method: + + /** + * @return the attribute string if present on an element at the index + * provided, null otherwise. + */ + getAttributeFromElementAtIndex(index: number, attr: string): string|null; + +PiperOrigin-RevId: 398575780 +* **iconbutton:** MDC iconbutton `_index` Sass module will only export theme mixins. + +PiperOrigin-RevId: 391773229 +* **theme:** Renamed Sass mixins `validate-keys()` to `validate-theme()` in `@material/theme` + +PiperOrigin-RevId: 390671152 +* **fab:** Renamed Fab's mixins to deprecate legacy theme mixins. + +PiperOrigin-RevId: 387378201 + + + + + # [12.0.0](https://github.com/material-components/material-components-web/compare/v11.0.0...v12.0.0) (2021-07-27) diff --git a/README.md b/README.md index 8b263642190..e801477a637 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ [![Version](https://img.shields.io/npm/v/material-components-web.svg)](https://www.npmjs.com/package/material-components-web) [![Chat](https://img.shields.io/discord/259087343246508035.svg)](https://discord.gg/material-components) +Note: + +This project is no longer actively maintained. While automated updates may +still occur, the team will not be prioritizing new features or bug fixes, and +those updates will be turned off in the future. + +For Angular users, our friends at Angular Material moved away from this +library, and they expect that this may actually allow for faster iteration - see +their [blog post](https://blog.angular.dev/the-future-of-material-support-in-angular-7fa0662ecc4b) +for more information. + # Material Components for the web Material Components for the web helps developers execute [Material Design](https://www.material.io). @@ -47,7 +58,7 @@ Material Components for the web is the successor to [Material Design Lite](https ``` @@ -96,7 +107,7 @@ Import `MDCTextField` module to instantiate text field component. ```js import {MDCTextField} from '@material/textfield'; -const textField = new MDCTextField(document.querySelector('.mdc-text-field')); +const textField = new MDCTextField(document.querySelector('.mdc-text-field')); ``` This'll initialize text field component on a single `.mdc-text-field` element. diff --git a/docs/authoring-components.md b/docs/authoring-components.md index 37d30c344dd..859bb0e83d5 100644 --- a/docs/authoring-components.md +++ b/docs/authoring-components.md @@ -127,7 +127,7 @@ class RedblueTogglePrototype { toggle(isToggled = undefined) { const wasToggledExplicitlySet = isToggled === Boolean(isToggled); const toggled = wasToggledExplicitlySet ? isToggled : !this.toggled; - const toggleColorEl = this.root.querySelector('.redblue-toggle__color'); + const toggleColorEl = this.root.querySelector('.redblue-toggle__color'); let toggleColor; this.root.setAttribute('aria-pressed', String(toggled)); @@ -142,7 +142,7 @@ class RedblueTogglePrototype { } } -new RedblueTogglePrototype(document.querySelector('.redblue-toggle')); +new RedblueTogglePrototype(document.querySelector('.redblue-toggle')); ``` Note how the JS Component does not reference MDC Web in any way, nor does it have any notion @@ -335,7 +335,7 @@ class RedblueToggle extends MDCComponent { addClass: className => this.root.classList.add(className), removeClass: className => this.root.classList.remove(className), setToggleColorTextContent: textContent => { - this.root.querySelector('.redblue-toggle__color').textContent = textContent; + this.root.querySelector('.redblue-toggle__color').textContent = textContent; }, }); } diff --git a/docs/docsite-index.md b/docs/docsite-index.md index 367b7baaad2..8c6228ed8a3 100644 --- a/docs/docsite-index.md +++ b/docs/docsite-index.md @@ -42,7 +42,7 @@ Then include MDC markup... ...and instantiate JavaScript: ```js -mdc.ripple.MDCRipple.attachTo(document.querySelector('.foo-button')); +mdc.ripple.MDCRipple.attachTo(document.querySelector('.foo-button')); ``` However, it is highly recommended to install Material Components for the web via npm and consume its ES Modules and Sass directly. This is outlined in the steps below. @@ -105,7 +105,7 @@ However, it is highly recommended to install Material Components for the web via ```js import {MDCRipple} from '@material/ripple'; - const ripple = new MDCRipple(document.querySelector('.foo-button')); + const ripple = new MDCRipple(document.querySelector('.foo-button')); ``` This will produce a Material Design ripple on the button when it is pressed! diff --git a/docs/getting-started.md b/docs/getting-started.md index 08420eada44..c88fbc95c5d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -38,7 +38,7 @@ Then include MDC markup... ...and instantiate JavaScript: ```js -mdc.ripple.MDCRipple.attachTo(document.querySelector('.foo-button')); +mdc.ripple.MDCRipple.attachTo(document.querySelector('.foo-button')); ``` ## Installing Locally @@ -392,7 +392,7 @@ We need to tell our `app.js` to import the ES2015 file for `@material/ripple`. W ```js import {MDCRipple} from '@material/ripple/index'; -const ripple = new MDCRipple(document.querySelector('.foo-button')); +const ripple = new MDCRipple(document.querySelector('.foo-button')); ``` > Note: We explicitly reference `index` within each MDC Web package in order to import the ES2015 source directly. diff --git a/karma.conf.js b/karma.conf.js index 9e51623ec25..fe97827a82a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -26,22 +26,10 @@ const HEADLESS_LAUNCHERS = { base: 'ChromeHeadless', flags: ['--no-sandbox'], }, - 'FirefoxHeadless': { - base: 'Firefox', - flags: ['-headless'], - }, -}; -const SAUCE_LAUNCHERS = { - 'sl-ie': { - base: 'SauceLabs', - browserName: 'internet explorer', - version: '11', - platform: 'Windows 10', - }, }; const USE_SAUCE = Boolean(process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY); const PROGRESS = USE_SAUCE ? 'dots' : 'progress'; -const customLaunchers = Object.assign({}, USE_SAUCE ? SAUCE_LAUNCHERS : {}, HEADLESS_LAUNCHERS); +const customLaunchers = Object.assign({}, HEADLESS_LAUNCHERS); const browsers = USE_SAUCE ? Object.keys(customLaunchers) : ['Chrome']; // Files to include in Jasmine tests. diff --git a/lerna.json b/lerna.json index db81c4df265..817c9b2d0d3 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "12.0.0", + "version": "14.0.0", "command": { "version": { "conventionalCommits": true diff --git a/package-lock.json b/package-lock.json index 524eb63055a..1c07583edae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8224,12 +8224,6 @@ "repeating": "^2.0.0" } }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true - }, "dezalgo": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", @@ -9893,15 +9887,6 @@ "reusify": "^1.0.4" } }, - "fibers": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-4.0.2.tgz", - "integrity": "sha512-FhICi1K4WZh9D6NC18fh2ODF3EWy1z0gzIdV9P7+s2pRjfRBnCkMDJ6x3bV1DkVymKH8HGrQa/FNOBjYvnJ/tQ==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3" - } - }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -19065,9 +19050,9 @@ "dev": true }, "sass": { - "version": "1.34.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.34.1.tgz", - "integrity": "sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.41.1.tgz", + "integrity": "sha512-vIjX7izRxw3Wsiez7SX7D+j76v7tenfO18P59nonjr/nzCkZuoHuF7I/Fo0ZRZPKr88v29ivIdE9BqGDgQD/Nw==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0" @@ -23406,9 +23391,9 @@ } }, "typescript": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", - "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 2989a4fe457..bdd70792175 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "eslint": "^5.1.0", "eslint-config-google": "^0.11.0", "eslint-plugin-mocha": "^5.0.0", - "fibers": "^4.0.2", "fs-extra": "^7.0.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.1.6", @@ -83,7 +82,7 @@ "prettier": "^2.2.1", "recast": "^0.17.3", "resolve": "^1.3.2", - "sass": "^1.34.1", + "sass": "^1.41.1", "sass-loader": "^7.1.0", "semver": "^5.3.0", "stylelint": "^13.8.0", @@ -97,7 +96,7 @@ "ts-node": "^8.0.3", "tslib": "^2.1.0", "tslint": "^5.12.0", - "typescript": "^4.2.2", + "typescript": "^4.3.5", "uglifyjs-webpack-plugin": "^2.1.3", "webpack": "^4.34.0", "webpack-cli": "^3.3.11" diff --git a/packages/material-components-web/index.ts b/packages/material-components-web/index.ts index 96b6bcb2338..cf462f4d181 100644 --- a/packages/material-components-web/index.ts +++ b/packages/material-components-web/index.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import autoInit, {MDCAttachable} from '@material/auto-init/index'; +import autoInit from '@material/auto-init/index'; import * as banner from '@material/banner/index'; import * as base from '@material/base/index'; import * as checkbox from '@material/checkbox/index'; @@ -79,8 +79,7 @@ autoInit.register('MDCSegmentedButton', segmentedButton.MDCSegmentedButton); autoInit.register('MDCSelect', select.MDCSelect); autoInit.register('MDCSlider', slider.MDCSlider); autoInit.register('MDCSnackbar', snackbar.MDCSnackbar); -autoInit.register( - 'MDCSwitch', switchControl.MDCSwitch as unknown as MDCAttachable); +autoInit.register('MDCSwitch', switchControl.MDCSwitch); autoInit.register('MDCTabBar', tabBar.MDCTabBar); autoInit.register('MDCTextField', textField.MDCTextField); autoInit.register('MDCTooltip', tooltip.MDCTooltip); diff --git a/packages/material-components-web/material-components-web.scss b/packages/material-components-web/material-components-web.scss index 0f9237eda12..32d1af47507 100644 --- a/packages/material-components-web/material-components-web.scss +++ b/packages/material-components-web/material-components-web.scss @@ -49,7 +49,7 @@ @use '@material/segmented-button/styles' as segmented-button-styles; @use '@material/slider/styles' as slider-styles; @use '@material/snackbar/mdc-snackbar'; -@use '@material/switch/deprecated/mdc-switch'; +@use '@material/switch/styles'; @use '@material/tab/mdc-tab'; @use '@material/tab-bar/mdc-tab-bar'; @use '@material/tab-indicator/mdc-tab-indicator'; diff --git a/packages/material-components-web/package.json b/packages/material-components-web/package.json index d529337e530..2101972ce2a 100644 --- a/packages/material-components-web/package.json +++ b/packages/material-components-web/package.json @@ -1,7 +1,7 @@ { "name": "material-components-web", "description": "Modular and customizable Material Design UI components for the web", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "keywords": [ "material components", @@ -17,53 +17,55 @@ "directory": "packages/material-components-web" }, "dependencies": { - "@material/animation": "^12.0.0", - "@material/auto-init": "^12.0.0", - "@material/banner": "^12.0.0", - "@material/base": "^12.0.0", - "@material/button": "^12.0.0", - "@material/card": "^12.0.0", - "@material/checkbox": "^12.0.0", - "@material/chips": "^12.0.0", - "@material/circular-progress": "^12.0.0", - "@material/data-table": "^12.0.0", - "@material/density": "^12.0.0", - "@material/dialog": "^12.0.0", - "@material/dom": "^12.0.0", - "@material/drawer": "^12.0.0", - "@material/elevation": "^12.0.0", - "@material/fab": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/floating-label": "^12.0.0", - "@material/form-field": "^12.0.0", - "@material/icon-button": "^12.0.0", - "@material/image-list": "^12.0.0", - "@material/layout-grid": "^12.0.0", - "@material/line-ripple": "^12.0.0", - "@material/linear-progress": "^12.0.0", - "@material/list": "^12.0.0", - "@material/menu": "^12.0.0", - "@material/menu-surface": "^12.0.0", - "@material/notched-outline": "^12.0.0", - "@material/radio": "^12.0.0", - "@material/ripple": "^12.0.0", - "@material/rtl": "^12.0.0", - "@material/segmented-button": "^12.0.0", - "@material/select": "^12.0.0", - "@material/shape": "^12.0.0", - "@material/slider": "^12.0.0", - "@material/snackbar": "^12.0.0", - "@material/switch": "^12.0.0", - "@material/tab": "^12.0.0", - "@material/tab-bar": "^12.0.0", - "@material/tab-indicator": "^12.0.0", - "@material/tab-scroller": "^12.0.0", - "@material/textfield": "^12.0.0", - "@material/theme": "^12.0.0", - "@material/tokens": "^12.0.0", - "@material/tooltip": "^12.0.0", - "@material/top-app-bar": "^12.0.0", - "@material/touch-target": "^12.0.0", - "@material/typography": "^12.0.0" + "@material/animation": "^14.0.0", + "@material/auto-init": "^14.0.0", + "@material/banner": "^14.0.0", + "@material/base": "^14.0.0", + "@material/button": "^14.0.0", + "@material/card": "^14.0.0", + "@material/checkbox": "^14.0.0", + "@material/chips": "^14.0.0", + "@material/circular-progress": "^14.0.0", + "@material/data-table": "^14.0.0", + "@material/density": "^14.0.0", + "@material/dialog": "^14.0.0", + "@material/dom": "^14.0.0", + "@material/drawer": "^14.0.0", + "@material/elevation": "^14.0.0", + "@material/fab": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/floating-label": "^14.0.0", + "@material/focus": "^14.0.0", + "@material/focus-ring": "^14.0.0", + "@material/form-field": "^14.0.0", + "@material/icon-button": "^14.0.0", + "@material/image-list": "^14.0.0", + "@material/layout-grid": "^14.0.0", + "@material/line-ripple": "^14.0.0", + "@material/linear-progress": "^14.0.0", + "@material/list": "^14.0.0", + "@material/menu": "^14.0.0", + "@material/menu-surface": "^14.0.0", + "@material/notched-outline": "^14.0.0", + "@material/radio": "^14.0.0", + "@material/ripple": "^14.0.0", + "@material/rtl": "^14.0.0", + "@material/segmented-button": "^14.0.0", + "@material/select": "^14.0.0", + "@material/shape": "^14.0.0", + "@material/slider": "^14.0.0", + "@material/snackbar": "^14.0.0", + "@material/switch": "^14.0.0", + "@material/tab": "^14.0.0", + "@material/tab-bar": "^14.0.0", + "@material/tab-indicator": "^14.0.0", + "@material/tab-scroller": "^14.0.0", + "@material/textfield": "^14.0.0", + "@material/theme": "^14.0.0", + "@material/tokens": "^14.0.0", + "@material/tooltip": "^14.0.0", + "@material/top-app-bar": "^14.0.0", + "@material/touch-target": "^14.0.0", + "@material/typography": "^14.0.0" } } diff --git a/packages/mdc-animation/index.ts b/packages/mdc-animation/index.ts index 1591d7efb70..09d00a7ce69 100644 --- a/packages/mdc-animation/index.ts +++ b/packages/mdc-animation/index.ts @@ -23,7 +23,7 @@ import * as util from './util'; -export {util}; // New namespace +export {util}; // New namespace export * from './animationframe'; export * from './types'; -export * from './util'; // Old namespace for backward compatibility +export * from './util'; // Old namespace for backward compatibility diff --git a/packages/mdc-animation/package.json b/packages/mdc-animation/package.json index 16336e3a328..d33d3972fc2 100644 --- a/packages/mdc-animation/package.json +++ b/packages/mdc-animation/package.json @@ -1,7 +1,7 @@ { "name": "@material/animation", "description": "Animation Variables and Mixins used by Material Components for the web", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "keywords": [ "material components", diff --git a/packages/mdc-animation/test/component.test.ts b/packages/mdc-animation/test/component.test.ts index 83bfb6cf28a..a9865b36f14 100644 --- a/packages/mdc-animation/test/component.test.ts +++ b/packages/mdc-animation/test/component.test.ts @@ -22,7 +22,7 @@ */ -import {getCorrectEventName, getCorrectPropertyName,} from '../../mdc-animation/index'; +import {getCorrectEventName, getCorrectPropertyName} from '../../mdc-animation/index'; // Has no properties without a prefix const legacyWindowObj = { diff --git a/packages/mdc-animation/types.ts b/packages/mdc-animation/types.ts index 20dc6a96ea8..812686b29c3 100644 --- a/packages/mdc-animation/types.ts +++ b/packages/mdc-animation/types.ts @@ -21,17 +21,16 @@ * THE SOFTWARE. */ -export type StandardCssPropertyName = - 'animation' | 'transform' | 'transition'; +export type StandardCssPropertyName = 'animation'|'transform'|'transition'; export type PrefixedCssPropertyName = - '-webkit-animation' | '-webkit-transform' | '-webkit-transition'; + '-webkit-animation'|'-webkit-transform'|'-webkit-transition'; export type StandardJsEventType = - 'animationend' | 'animationiteration' | 'animationstart' | 'transitionend'; + 'animationend'|'animationiteration'|'animationstart'|'transitionend'; -export type PrefixedJsEventType = - 'webkitAnimationEnd' | 'webkitAnimationIteration' | 'webkitAnimationStart' | 'webkitTransitionEnd'; +export type PrefixedJsEventType = 'webkitAnimationEnd'| + 'webkitAnimationIteration'|'webkitAnimationStart'|'webkitTransitionEnd'; export interface CssVendorProperty { prefixed: PrefixedCssPropertyName; @@ -44,5 +43,9 @@ export interface JsVendorProperty { standard: StandardJsEventType; } -export type CssVendorPropertyMap = { [K in StandardCssPropertyName]: CssVendorProperty }; -export type JsVendorPropertyMap = { [K in StandardJsEventType]: JsVendorProperty }; +export type CssVendorPropertyMap = { + [K in StandardCssPropertyName]: CssVendorProperty +}; +export type JsVendorPropertyMap = { + [K in StandardJsEventType]: JsVendorProperty +}; diff --git a/packages/mdc-animation/util.ts b/packages/mdc-animation/util.ts index 553b1e68170..de63a65a0bf 100644 --- a/packages/mdc-animation/util.ts +++ b/packages/mdc-animation/util.ts @@ -21,11 +21,7 @@ * THE SOFTWARE. */ -import { - CssVendorPropertyMap, JsVendorPropertyMap, - PrefixedCssPropertyName, PrefixedJsEventType, - StandardCssPropertyName, StandardJsEventType, -} from './types'; +import {CssVendorPropertyMap, JsVendorPropertyMap, PrefixedCssPropertyName, PrefixedJsEventType, StandardCssPropertyName, StandardJsEventType} from './types'; const cssPropertyNameMap: CssVendorPropertyMap = { animation: { @@ -66,11 +62,13 @@ const jsEventTypeMap: JsVendorPropertyMap = { }; function isWindow(windowObj: Window): boolean { - return Boolean(windowObj.document) && typeof windowObj.document.createElement === 'function'; + return Boolean(windowObj.document) && + typeof windowObj.document.createElement === 'function'; } -export function getCorrectPropertyName(windowObj: Window, cssProperty: StandardCssPropertyName): - StandardCssPropertyName | PrefixedCssPropertyName { +export function getCorrectPropertyName( + windowObj: Window, cssProperty: StandardCssPropertyName): + StandardCssPropertyName|PrefixedCssPropertyName { if (isWindow(windowObj) && cssProperty in cssPropertyNameMap) { const el = windowObj.document.createElement('div'); const {standard, prefixed} = cssPropertyNameMap[cssProperty]; @@ -80,8 +78,9 @@ export function getCorrectPropertyName(windowObj: Window, cssProperty: StandardC return cssProperty; } -export function getCorrectEventName(windowObj: Window, eventType: StandardJsEventType): - StandardJsEventType | PrefixedJsEventType { +export function getCorrectEventName( + windowObj: Window, eventType: StandardJsEventType): StandardJsEventType| + PrefixedJsEventType { if (isWindow(windowObj) && eventType in jsEventTypeMap) { const el = windowObj.document.createElement('div'); const {standard, prefixed, cssProperty} = jsEventTypeMap[eventType]; diff --git a/packages/mdc-auto-init/README.md b/packages/mdc-auto-init/README.md index 34dcf5a3554..3215940097f 100644 --- a/packages/mdc-auto-init/README.md +++ b/packages/mdc-auto-init/README.md @@ -65,7 +65,7 @@ Once `mdc.autoInit()` is called, you can access the component instance via an `M property on that element. ```js -document.querySelector('.mdc-text-field').MDCTextField.disabled = true; +document.querySelector('.mdc-text-field').MDCTextField.disabled = true; ``` #### Calling subsequent `mdc.autoInit()` diff --git a/packages/mdc-auto-init/constants.ts b/packages/mdc-auto-init/constants.ts index 7147addc1a4..f7b806aae84 100644 --- a/packages/mdc-auto-init/constants.ts +++ b/packages/mdc-auto-init/constants.ts @@ -23,6 +23,6 @@ export const strings = { AUTO_INIT_ATTR: 'data-mdc-auto-init', - AUTO_INIT_STATE_ATTR: 'data-mdc-auto-init-state', + DATASET_AUTO_INIT_STATE: 'mdcAutoInitState', INITIALIZED_STATE: 'initialized', }; diff --git a/packages/mdc-auto-init/index.ts b/packages/mdc-auto-init/index.ts index 18b1246b996..b0eedfa3b49 100644 --- a/packages/mdc-auto-init/index.ts +++ b/packages/mdc-auto-init/index.ts @@ -28,13 +28,11 @@ import {MDCFoundation} from '@material/base/foundation'; import {strings} from './constants'; -const {AUTO_INIT_ATTR, AUTO_INIT_STATE_ATTR, INITIALIZED_STATE} = strings; +const {AUTO_INIT_ATTR, DATASET_AUTO_INIT_STATE, INITIALIZED_STATE} = strings; -export interface MDCAttachable { - new(root: Element, foundation?: F, ...args: Array): MDCComponent; - - // Static method. - attachTo(root: Element): MDCComponent; +/** MDC Attachable */ +export interface MDCAttachable extends Function { + attachTo(root: HTMLElement): MDCComponent; } interface InternalComponentRegistry { @@ -43,31 +41,35 @@ interface InternalComponentRegistry { const registry: InternalComponentRegistry = {}; -const CONSOLE_WARN = console.warn.bind(console); // tslint:disable-line:no-console +// tslint:disable-next-line:no-console +const CONSOLE_WARN = console.warn.bind(console); -function emit(evtType: string, evtData: T, shouldBubble = false) { - let evt; +function emit( + eventType: string, eventData: T, shouldBubble = false) { + let event; if (typeof CustomEvent === 'function') { - evt = new CustomEvent(evtType, { + event = new CustomEvent(eventType, { bubbles: shouldBubble, - detail: evtData, + detail: eventData, }); } else { - evt = document.createEvent('CustomEvent'); - evt.initCustomEvent(evtType, shouldBubble, false, evtData); + event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventType, shouldBubble, false, eventData); } - document.dispatchEvent(evt); + document.dispatchEvent(event); } /* istanbul ignore next: optional argument is not a branch statement */ /** * Auto-initializes all MDC components on a page. */ -function mdcAutoInit(root = document) { +function mdcAutoInit(root: ParentNode = document) { const components = []; - let nodes: Element[] = [].slice.call(root.querySelectorAll(`[${AUTO_INIT_ATTR}]`)); - nodes = nodes.filter((node) => node.getAttribute(AUTO_INIT_STATE_ATTR) !== INITIALIZED_STATE); + let nodes = + Array.from(root.querySelectorAll(`[${AUTO_INIT_ATTR}]`)); + nodes = nodes.filter( + (node) => node.dataset[DATASET_AUTO_INIT_STATE] !== INITIALIZED_STATE); for (const node of nodes) { const ctorName = node.getAttribute(AUTO_INIT_ATTR); @@ -75,14 +77,17 @@ function mdcAutoInit(root = document) { throw new Error('(mdc-auto-init) Constructor name must be given.'); } - const Constructor = registry[ctorName]; // tslint:disable-line:variable-name + // tslint:disable-next-line:enforce-name-casing + const Constructor = registry[ctorName]; if (typeof Constructor !== 'function') { throw new Error( - `(mdc-auto-init) Could not find constructor in registry for ${ctorName}`); + `(mdc-auto-init) Could not find constructor in registry for ${ + ctorName}`); } // TODO: Should we make an eslint rule for an attachTo() static method? - // See https://github.com/Microsoft/TypeScript/issues/14600 for discussion of static interface support in TS + // See https://github.com/Microsoft/TypeScript/issues/14600 for discussion + // of static interface support in TS const component = Constructor.attachTo(node); Object.defineProperty(node, ctorName, { configurable: true, @@ -91,22 +96,27 @@ function mdcAutoInit(root = document) { writable: false, }); components.push(component); - node.setAttribute(AUTO_INIT_STATE_ATTR, INITIALIZED_STATE); + node.dataset[DATASET_AUTO_INIT_STATE] = INITIALIZED_STATE; } emit('MDCAutoInit:End', {}); return components; } -// Constructor is PascalCased because it is a direct reference to a class, rather than an instance of a class. -// tslint:disable-next-line:variable-name -mdcAutoInit.register = function(componentName: string, Constructor: MDCAttachable, warn = CONSOLE_WARN) { +// Constructor is PascalCased because it is a direct reference to a class, +// rather than an instance of a class. +mdcAutoInit.register = function( + componentName: string, + // tslint:disable-next-line:enforce-name-casing + Constructor: MDCAttachable, warn = CONSOLE_WARN) { if (typeof Constructor !== 'function') { - throw new Error(`(mdc-auto-init) Invalid Constructor value: ${Constructor}. Expected function.`); + throw new Error(`(mdc-auto-init) Invalid Constructor value: ${ + Constructor}. Expected function.`); } const registryValue = registry[componentName]; if (registryValue) { - warn(`(mdc-auto-init) Overriding registration for ${componentName} with ${Constructor}. Was: ${registryValue}`); + warn(`(mdc-auto-init) Overriding registration for ${componentName} with ${ + Constructor}. Was: ${registryValue}`); } registry[componentName] = Constructor; }; @@ -117,8 +127,9 @@ mdcAutoInit.deregister = function(componentName: string) { /** @nocollapse */ mdcAutoInit.deregisterAll = function() { - const keys = Object.keys(registry) as string[]; - keys.forEach(this.deregister, this); + for (const componentName of Object.keys(registry)) { + mdcAutoInit.deregister(componentName); + } }; // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier. diff --git a/packages/mdc-auto-init/package.json b/packages/mdc-auto-init/package.json index 1b01a7cac7b..01536fbe2df 100644 --- a/packages/mdc-auto-init/package.json +++ b/packages/mdc-auto-init/package.json @@ -1,7 +1,7 @@ { "name": "@material/auto-init", "description": "Declarative, easy-to-use auto-initialization for Material Components for the web", - "version": "12.0.0", + "version": "14.0.0", "main": "dist/mdc.autoInit.js", "module": "index.js", "sideEffects": false, @@ -12,7 +12,7 @@ "directory": "packages/mdc-auto-init" }, "dependencies": { - "@material/base": "^12.0.0", + "@material/base": "^14.0.0", "tslib": "^2.1.0" } } diff --git a/packages/mdc-auto-init/test/mdc-auto-init.test.ts b/packages/mdc-auto-init/test/mdc-auto-init.test.ts index e7183cce00f..9decba00643 100644 --- a/packages/mdc-auto-init/test/mdc-auto-init.test.ts +++ b/packages/mdc-auto-init/test/mdc-auto-init.test.ts @@ -21,11 +21,12 @@ * THE SOFTWARE. */ +import {createFixture, html} from '../../../testing/dom'; import {MDCAttachable, mdcAutoInit} from '../index'; class FakeComponent { static attachTo(node: HTMLElement) { - return new this(node); + return new FakeComponent(node); } constructor(readonly node: HTMLElement) { @@ -43,31 +44,27 @@ interface FakeHTMLElement extends HTMLElement { FakeComponent: FakeComponent; } -const createFixture = () => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` +function getFixture() { + return createFixture(html`

Fake Element

-`; - const el = wrapper.firstElementChild as unknown as Document; - wrapper.removeChild(el); - return el; -}; +`); +} -const setupTest = () => { +function setupTest() { mdcAutoInit.deregisterAll(); mdcAutoInit.register( 'FakeComponent', FakeComponent as unknown as MDCAttachable); - return createFixture(); -}; + return getFixture(); +} -const setupInvalidTest = () => { +function setupInvalidTest() { mdcAutoInit.deregisterAll(); mdcAutoInit.register( 'InvalidComponent', InvalidComponent as unknown as MDCAttachable); - return createFixture(); -}; + return getFixture(); +} describe('MDCAutoInit', () => { it('calls attachTo() on components registered for identifier on nodes w/ data-mdc-auto-init attr', @@ -75,11 +72,8 @@ describe('MDCAutoInit', () => { const root = setupTest(); mdcAutoInit(root); - expect( - (root.querySelector('.mdc-fake') as FakeHTMLElement) - .FakeComponent instanceof - FakeComponent) - .toBeTruthy(); + expect(root.querySelector('.mdc-fake')!.FakeComponent) + .toBeInstanceOf(FakeComponent); }); it('throws when attachTo() is missing', () => { @@ -92,14 +86,14 @@ describe('MDCAutoInit', () => { const root = setupTest(); mdcAutoInit(root); - const fake = root.querySelector('.mdc-fake') as FakeHTMLElement; + const fake = root.querySelector('.mdc-fake')!; expect(fake.FakeComponent.node).toEqual(fake); }); it('throws when no constructor name is specified within "data-mdc-auto-init"', () => { const root = setupTest(); - (root.querySelector('.mdc-fake') as HTMLElement).dataset['mdcAutoInit'] = + root.querySelector('.mdc-fake')!.dataset['mdcAutoInit'] = ''; expect(() => mdcAutoInit(root)).toThrow(); @@ -107,7 +101,7 @@ describe('MDCAutoInit', () => { it('throws when constructor is not registered', () => { const root = setupTest(); - (root.querySelector('.mdc-fake') as HTMLElement).dataset['mdcAutoInit'] = + root.querySelector('.mdc-fake')!.dataset['mdcAutoInit'] = 'MDCUnregisteredComponent'; expect(() => mdcAutoInit(root)).toThrow(); @@ -150,7 +144,7 @@ describe('MDCAutoInit', () => { try { mdcAutoInit(document); } finally { - (window as unknown as WindowWithCustomEvent).CustomEvent = customEvent; + (window as unknown as WindowWithCustomEvent).CustomEvent = customEvent; } expect(handler).toHaveBeenCalledWith(jasmine.objectContaining({type})); @@ -172,8 +166,8 @@ describe('MDCAutoInit', () => { mdcAutoInit(root); expect( - (root.querySelector('.mdc-fake') as FakeHTMLElement) - .FakeComponent instanceof + root.querySelector( + '.mdc-fake')!.FakeComponent instanceof FakeComponent) .toBe(false); }); diff --git a/packages/mdc-banner/.npmignore b/packages/mdc-banner/.npmignore new file mode 100644 index 00000000000..9881061c12a --- /dev/null +++ b/packages/mdc-banner/.npmignore @@ -0,0 +1,3 @@ +*.ts +!*.d.ts +test/** diff --git a/packages/mdc-banner/_banner-theme.scss b/packages/mdc-banner/_banner-theme.scss index 0d793c30816..dc94b23630f 100644 --- a/packages/mdc-banner/_banner-theme.scss +++ b/packages/mdc-banner/_banner-theme.scss @@ -28,7 +28,7 @@ @use 'sass:meta'; @use '@material/elevation/elevation-theme'; @use '@material/feature-targeting/feature-targeting'; -@use '@material/rtl/mixins' as rtl-mixins; +@use '@material/rtl/rtl'; @use '@material/shape/mixins' as shape-mixins; @use '@material/button/button-text-theme'; @use '@material/theme/theme'; @@ -47,27 +47,29 @@ $primary-action-text-color: primary; $secondary-action-text-color: primary; $mobile-breakpoint: 480px; -$z-index: 1; +$custom-property-prefix: 'banner'; $light-theme: ( action-focus-state-layer-color: theme-color.$primary, action-focus-state-layer-opacity: 0.12, - action-focus-label-text-color: theme-color.$on-primary, + action-focus-label-text-color: null, action-hover-state-layer-color: theme-color.$primary, action-hover-state-layer-opacity: 0.04, - action-hover-label-text-color: theme-color.$on-primary, + action-hover-label-text-color: null, action-label-text-color: theme-color.$primary, - action-label-text-font: typography.get-font(button), - action-label-text-size: typography.get-size(button), - action-label-text-tracking: typography.get-tracking(button), - action-label-text-weight: typography.get-weight(button), + // TODO(b/197004146): Support action label typography. + action-label-text-font: null, + action-label-text-size: null, + action-label-text-tracking: null, + action-label-text-weight: null, action-pressed-state-layer-color: theme-color.$primary, action-pressed-state-layer-opacity: 0.1, - action-pressed-label-text-color: theme-color.$on-primary, + action-pressed-label-text-color: null, container-color: theme-color.$surface, - container-elevation: 0, + container-elevation: null, container-shadow-color: null, container-shape: 0, + content-max-width: 720px, divider-color: $divider-color, divider-height: 1px, supporting-text-color: theme-color.$on-surface, @@ -78,13 +80,19 @@ $light-theme: ( supporting-text-weight: typography.get-weight(body2), with-image-image-shape: $graphic-shape-radius, with-image-image-size: 40px, + with-image-image-color: $graphic-color, + with-image-image-container-color: $graphic-background-color, + z-index: 1, ); @mixin theme($theme, $resolvers: resolvers.$material) { - @include theme.validate-keys($light-theme, $theme); - + // TODO(b/251881053): Replace with `validate-theme`. + @include theme.validate-theme-styles($light-theme, $theme); $theme: _resolve-theme($theme, $resolvers); - @include keys.declare-custom-properties($theme, banner); + @include keys.declare-custom-properties( + $theme, + $prefix: $custom-property-prefix + ); } @function _resolve-theme($theme, $resolvers) { @@ -123,7 +131,24 @@ $light-theme: ( $resolver: resolvers.$material, $query: feature-targeting.all() ) { - @include theme.validate-keys($light-theme, $theme); + @include theme.validate-theme-styles($light-theme, $theme); + $theme: keys.create-theme-properties( + $theme, + $prefix: $custom-property-prefix + ); + + @include z-index(map.get($theme, 'z-index'), $query: $query); + + @include max-width(map.get($theme, 'content-max-width'), $query: $query); + + @include graphic-color( + map.get($theme, 'with-image-image-color'), + $query: $query + ); + @include graphic-background-color( + map.get($theme, 'with-image-image-container-color'), + $query: $query + ); @include text-color(map.get($theme, supporting-text-color), $query: $query); @include _supporting-text-typography( @@ -157,15 +182,14 @@ $light-theme: ( $query ); - .mdc-banner__primary-action, - .mdc-banner__secondary-action { + .mdc-button { @include button-text-theme.theme-styles( ( - focus-label-text-color: map.get($theme, action-focus-state-layer-color), + focus-label-text-color: map.get($theme, action-focus-label-text-color), focus-state-layer-color: map.get($theme, action-focus-state-layer-color), focus-state-layer-opacity: map.get($theme, action-focus-state-layer-opacity), - hover-label-text-color: map.get($theme, action-hover-state-layer-color), + hover-label-text-color: map.get($theme, action-hover-label-text-color), hover-state-layer-color: map.get($theme, action-hover-state-layer-color), hover-state-layer-opacity: map.get($theme, action-hover-state-layer-opacity), @@ -175,7 +199,7 @@ $light-theme: ( label-text-tracking: map.get($theme, action-label-text-tracking), label-text-weight: map.get($theme, action-label-text-weight), pressed-label-text-color: - map.get($theme, action-pressed-state-layer-color), + map.get($theme, action-pressed-label-text-color), pressed-state-layer-color: map.get($theme, action-pressed-state-layer-color), pressed-state-layer-opacity: @@ -329,7 +353,7 @@ $light-theme: ( .mdc-banner__content { @include feature-targeting.targets($feat-structure) { - max-width: $max-width; + @include theme.property(max-width, $max-width); } } } @@ -366,7 +390,7 @@ $light-theme: ( } .mdc-banner__text { - @include rtl-mixins.reflexive-property(margin, 16px, 8px); + @include rtl.reflexive-property(margin, 16px, 8px); padding-bottom: 4px; } @@ -382,7 +406,7 @@ $light-theme: ( $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { - z-index: $z-index; + @include theme.property(z-index, $z-index); } } diff --git a/packages/mdc-banner/_banner.scss b/packages/mdc-banner/_banner.scss index 2de077fff88..f932a792587 100644 --- a/packages/mdc-banner/_banner.scss +++ b/packages/mdc-banner/_banner.scss @@ -29,12 +29,11 @@ @use '@material/typography/typography'; @use '@material/button/button-theme'; @use '@material/ripple/ripple-theme'; -@use '@material/rtl/mixins' as rtl-mixins; +@use '@material/rtl/rtl'; @use './banner-theme'; $_text-type-scale: body2; $_min-width: 344px; -$_max-width: 720px; // Minimum banner height minus standard text height, divided by 2 for both top // and bottom padding. This is used to support two/three line banners. $_text-padding-top-bottom: math.div(52px - 20px, 2); @@ -47,16 +46,7 @@ $_exit-duration: 250ms; $feat-structure: feature-targeting.create-target($query, structure); $feat-animation: feature-targeting.create-target($query, animation); - @include banner-theme.graphic-color( - banner-theme.$graphic-color, - $query: $query - ); - @include banner-theme.graphic-background-color( - banner-theme.$graphic-background-color, - $query: $query - ); @include banner-theme.min-width($_min-width, $query: $query); - @include banner-theme.max-width($_max-width, $query: $query); @include static-styles($query: $query); .mdc-banner { @@ -68,7 +58,7 @@ $_exit-duration: 250ms; .mdc-banner__secondary-action { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(margin, 0, 8px); + @include rtl.reflexive-property(margin, 0, 8px); } } } @@ -78,8 +68,6 @@ $_exit-duration: 250ms; $feat-animation: feature-targeting.create-target($query, animation); .mdc-banner { - @include banner-theme.z-index(banner-theme.$z-index, $query: $query); - @include feature-targeting.targets($feat-structure) { // Mobile view styles. @media (max-width: banner-theme.$mobile-breakpoint) { @@ -90,7 +78,7 @@ $_exit-duration: 250ms; } .mdc-banner__text { - @include rtl-mixins.reflexive-property(margin, 16px, 36px); + @include rtl.reflexive-property(margin, 16px, 36px); } } @@ -179,7 +167,7 @@ $_exit-duration: 250ms; .mdc-banner__graphic { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(margin, 16px, 0); + @include rtl.reflexive-property(margin, 16px, 0); flex-shrink: 0; margin-top: 16px; @@ -198,7 +186,7 @@ $_exit-duration: 250ms; .mdc-banner__text { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(margin, 24px, 90px); + @include rtl.reflexive-property(margin, 24px, 90px); align-self: center; flex-grow: 1; @@ -210,7 +198,7 @@ $_exit-duration: 250ms; .mdc-banner__actions { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(padding, 0, 8px); + @include rtl.reflexive-property(padding, 0, 8px); align-self: flex-end; display: flex; @@ -222,7 +210,7 @@ $_exit-duration: 250ms; .mdc-banner__secondary-action { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(margin, 0, 8px); + @include rtl.reflexive-property(margin, 0, 8px); } } } diff --git a/packages/mdc-banner/adapter.ts b/packages/mdc-banner/adapter.ts index d45df148ca0..c820ef54569 100644 --- a/packages/mdc-banner/adapter.ts +++ b/packages/mdc-banner/adapter.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {CloseReason} from './constants'; +import {Action, CloseReason} from './constants'; /** * Defines the shape of the adapter expected by the foundation. @@ -61,6 +61,12 @@ export interface MDCBannerAdapter { */ notifyOpening(): void; + /** + * Broadcasts an event denoting that a banner button was clicked without + * changing the banner state. + */ + notifyActionClicked(action: Action): void; + /** * Releases focus from banner and restores focus to the previously focused * element. diff --git a/packages/mdc-banner/component.ts b/packages/mdc-banner/component.ts index a2dfa453697..fdb0720fa75 100644 --- a/packages/mdc-banner/component.ts +++ b/packages/mdc-banner/component.ts @@ -27,16 +27,15 @@ import {FocusTrap} from '@material/dom/focus-trap'; import {closest} from '@material/dom/ponyfill'; import {MDCBannerAdapter} from './adapter'; -import {CloseReason, events, MDCBannerCloseEventDetail, MDCBannerFocusTrapFactory, selectors} from './constants'; +import {CloseReason, events, MDCBannerActionEventDetail, MDCBannerCloseEventDetail, MDCBannerFocusTrapFactory, selectors} from './constants'; import {MDCBannerFoundation} from './foundation'; -/** Vanilla JS implementation of banner component. */ +/** Vanilla implementation of banner component. */ export class MDCBanner extends MDCComponent { - static attachTo(root: Element) { + static override attachTo(root: HTMLElement) { return new MDCBanner(root); } - root!: HTMLElement; // Assigned in MDCComponent constructor. private handleContentClick!: SpecificEventListener<'click'>; // Assigned in #initialize. private primaryActionEl!: HTMLElement; // Assigned in #initialize. @@ -47,20 +46,20 @@ export class MDCBanner extends MDCComponent { private focusTrapFactory!: MDCBannerFocusTrapFactory; // assigned in initialize() - initialize( + override initialize( focusTrapFactory: MDCBannerFocusTrapFactory = (el, focusOptions) => new FocusTrap(el, focusOptions), ) { - this.contentEl = this.root.querySelector(selectors.CONTENT) as HTMLElement; - this.textEl = this.root.querySelector(selectors.TEXT) as HTMLElement; + this.contentEl = this.root.querySelector(selectors.CONTENT)!; + this.textEl = this.root.querySelector(selectors.TEXT)!; this.primaryActionEl = - this.root.querySelector(selectors.PRIMARY_ACTION) as HTMLElement; + this.root.querySelector(selectors.PRIMARY_ACTION)!; this.secondaryActionEl = - this.root.querySelector(selectors.SECONDARY_ACTION) as HTMLElement; + this.root.querySelector(selectors.SECONDARY_ACTION)!; this.focusTrapFactory = focusTrapFactory; - this.handleContentClick = (evt) => { - const target = evt.target as Element; + this.handleContentClick = (event) => { + const target = event.target as Element; if (closest(target, selectors.PRIMARY_ACTION)) { this.foundation.handlePrimaryActionClick(); } else if (closest(target, selectors.SECONDARY_ACTION)) { @@ -69,13 +68,13 @@ export class MDCBanner extends MDCComponent { }; } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.registerContentClickHandler(this.handleContentClick); this.focusTrap = this.focusTrapFactory( this.root, {initialFocusEl: this.primaryActionEl}); } - destroy() { + override destroy() { super.destroy(); this.deregisterContentClickHandler(this.handleContentClick); } @@ -105,7 +104,7 @@ export class MDCBanner extends MDCComponent { this.foundation.close(reason); } - getDefaultFoundation() { + override getDefaultFoundation() { // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. @@ -128,6 +127,9 @@ export class MDCBanner extends MDCComponent { notifyOpening: () => { this.emit(events.OPENING, {}); }, + notifyActionClicked: (action) => { + this.emit(events.ACTION_CLICKED, {action}); + }, releaseFocus: () => { this.focusTrap.releaseFocus(); }, diff --git a/packages/mdc-banner/constants.ts b/packages/mdc-banner/constants.ts index db39b545621..cc406c4b632 100644 --- a/packages/mdc-banner/constants.ts +++ b/packages/mdc-banner/constants.ts @@ -42,6 +42,7 @@ export const events = { CLOSING: 'MDCBanner:closing', OPENED: 'MDCBanner:opened', OPENING: 'MDCBanner:opening', + ACTION_CLICKED: 'MDCBanner:actionClicked', }; /** Banner selectors. */ @@ -63,11 +64,25 @@ export enum CloseReason { UNSPECIFIED, } +/** + * Payload of actionClicked events to signify which action emitted the event. + */ +export enum Action { + PRIMARY, + SECONDARY, + UNKNOWN +} + /** Interface for the detail of the closing and closed events emitted. */ export interface MDCBannerCloseEventDetail { reason: CloseReason; } +/** Interface for the detail of the closing and closed events emitted. */ +export interface MDCBannerActionEventDetail { + action: Action; +} + /** */ export type MDCBannerFocusTrapFactory = ( element: HTMLElement, diff --git a/packages/mdc-banner/foundation.ts b/packages/mdc-banner/foundation.ts index 52eb8063c3e..040f73917a5 100644 --- a/packages/mdc-banner/foundation.ts +++ b/packages/mdc-banner/foundation.ts @@ -24,7 +24,7 @@ import {MDCFoundation} from '@material/base/foundation'; import {MDCBannerAdapter} from './adapter'; -import {CloseReason, cssClasses, numbers} from './constants'; +import {Action, CloseReason, cssClasses, numbers} from './constants'; const {OPENING, OPEN, CLOSING} = cssClasses; @@ -33,7 +33,7 @@ const {OPENING, OPEN, CLOSING} = cssClasses; * banner. */ export class MDCBannerFoundation extends MDCFoundation { - static get defaultAdapter(): MDCBannerAdapter { + static override get defaultAdapter(): MDCBannerAdapter { return { addClass: () => undefined, getContentHeight: () => 0, @@ -41,6 +41,7 @@ export class MDCBannerFoundation extends MDCFoundation { notifyClosing: () => undefined, notifyOpened: () => undefined, notifyOpening: () => undefined, + notifyActionClicked: () => undefined, releaseFocus: () => undefined, removeClass: () => undefined, setStyleProperty: () => undefined, @@ -60,7 +61,7 @@ export class MDCBannerFoundation extends MDCFoundation { super({...MDCBannerFoundation.defaultAdapter, ...adapter}); } - destroy() { + override destroy() { cancelAnimationFrame(this.animationFrame); this.animationFrame = 0; clearTimeout(this.animationTimer); @@ -69,9 +70,9 @@ export class MDCBannerFoundation extends MDCFoundation { open() { this.isOpened = true; - this.adapter.notifyOpening(); this.adapter.removeClass(CLOSING); this.adapter.addClass(OPENING); + this.adapter.notifyOpening(); const contentHeight = this.adapter.getContentHeight(); this.animationFrame = requestAnimationFrame(() => { @@ -122,12 +123,21 @@ export class MDCBannerFoundation extends MDCFoundation { return this.isOpened; } - handlePrimaryActionClick() { - this.close(CloseReason.PRIMARY); + handlePrimaryActionClick(disableAutoClose = false) { + if (disableAutoClose) { + this.adapter.notifyActionClicked(Action.PRIMARY); + } else { + this.close(CloseReason.PRIMARY); + } } - handleSecondaryActionClick() { - this.close(CloseReason.SECONDARY); + handleSecondaryActionClick(disableAutoClose = false) { + if (disableAutoClose) { + this.adapter.notifyActionClicked(Action.SECONDARY); + + } else { + this.close(CloseReason.SECONDARY); + } } layout() { diff --git a/packages/mdc-banner/package.json b/packages/mdc-banner/package.json index 58e54b6899f..04f642ac930 100644 --- a/packages/mdc-banner/package.json +++ b/packages/mdc-banner/package.json @@ -1,7 +1,7 @@ { "name": "@material/banner", "description": "The Material Components Web banner component.", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "keywords": [ "material components", @@ -17,17 +17,17 @@ "directory": "packages/mdc-banner" }, "dependencies": { - "@material/base": "^12.0.0", - "@material/button": "^12.0.0", - "@material/dom": "^12.0.0", - "@material/elevation": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/ripple": "^12.0.0", - "@material/rtl": "^12.0.0", - "@material/shape": "^12.0.0", - "@material/theme": "^12.0.0", - "@material/tokens": "^12.0.0", - "@material/typography": "^12.0.0", + "@material/base": "^14.0.0", + "@material/button": "^14.0.0", + "@material/dom": "^14.0.0", + "@material/elevation": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/ripple": "^14.0.0", + "@material/rtl": "^14.0.0", + "@material/shape": "^14.0.0", + "@material/theme": "^14.0.0", + "@material/tokens": "^14.0.0", + "@material/typography": "^14.0.0", "tslib": "^2.1.0" }, "publishConfig": { diff --git a/packages/mdc-banner/test/component.test.ts b/packages/mdc-banner/test/component.test.ts index dcd366c1cb1..85f923708c1 100644 --- a/packages/mdc-banner/test/component.test.ts +++ b/packages/mdc-banner/test/component.test.ts @@ -21,17 +21,19 @@ * THE SOFTWARE. */ -import {getFixture} from '../../../testing/dom'; +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; import {setUpMdcTestEnvironment} from '../../../testing/helpers/setup'; import {CloseReason, cssClasses, events, numbers, selectors} from '../constants'; import {MDCBanner} from '../index'; function setupTest(fixture: HTMLElement) { - const contentEl = fixture.querySelector(selectors.CONTENT)!; - const textEl = fixture.querySelector(selectors.TEXT)!; - const primaryActionEl = fixture.querySelector(selectors.PRIMARY_ACTION)!; - const secondaryActionEl = fixture.querySelector(selectors.SECONDARY_ACTION)!; + const contentEl = fixture.querySelector(selectors.CONTENT)!; + const textEl = fixture.querySelector(selectors.TEXT)!; + const primaryActionEl = + fixture.querySelector(selectors.PRIMARY_ACTION)!; + const secondaryActionEl = + fixture.querySelector(selectors.SECONDARY_ACTION)!; const component = new MDCBanner(fixture); return { @@ -49,7 +51,7 @@ describe('MDCBanner', () => { let fixture: HTMLElement; beforeEach(() => { - fixture = getFixture(`
+ fixture = createFixture(html`
+
``` @@ -172,6 +177,7 @@ Note that `data-indeterminate="true"` is necessary on the input element for init
+
``` diff --git a/packages/mdc-checkbox/_checkbox-theme.scss b/packages/mdc-checkbox/_checkbox-theme.scss index 4644c61fb80..905ea04bd4e 100644 --- a/packages/mdc-checkbox/_checkbox-theme.scss +++ b/packages/mdc-checkbox/_checkbox-theme.scss @@ -20,8 +20,8 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern @use 'sass:map'; @use 'sass:math'; @@ -72,8 +72,9 @@ $custom-property-prefix: 'checkbox'; // TODO(b/188417756): State layer (ripple) size token is missing including // `state-layer-size`. -// TODO(b/187852988): `selected-checkmark-color` does not exist in tokens. +// TODO(b/188529841): `selected-checkmark-color` and `disabled-selected-checkmark-color` does not exist in tokens. $light-theme: ( + disabled-selected-checkmark-color: $mark-color, disabled-selected-icon-color: $disabled-color, disabled-selected-icon-opacity: null, disabled-unselected-icon-color: $disabled-color, @@ -107,8 +108,18 @@ $light-theme: ( map.get(ripple-theme.$dark-ink-opacities, pressed), ); +$forced-colors-theme: ( + disabled-selected-checkmark-color: ButtonFace, + disabled-selected-icon-color: GrayText, + disabled-selected-icon-opacity: 1, + disabled-unselected-icon-color: GrayText, + disabled-unselected-icon-opacity: 1, + selected-checkmark-color: ButtonText, +); + @mixin theme($theme) { - @include theme.validate-keys($light-theme, $theme); + // TODO(b/251881053): Replace with `validate-theme`. + @include theme.validate-theme-styles($light-theme, $theme); @include keys.declare-custom-properties( $theme, $prefix: $custom-property-prefix @@ -116,7 +127,7 @@ $light-theme: ( } @mixin theme-styles($theme) { - @include theme.validate-keys($light-theme, $theme); + @include theme.validate-theme-styles($light-theme, $theme); $theme: keys.create-theme-properties( $theme, $prefix: $custom-property-prefix @@ -128,7 +139,9 @@ $light-theme: ( ); @include ink-color(map.get($theme, selected-checkmark-color)); - @include disabled-ink-color(map.get($theme, selected-checkmark-color)); + @include disabled-ink-color( + map.get($theme, disabled-selected-checkmark-color) + ); @include _icon-color( map.get($theme, unselected-icon-color), @@ -157,7 +170,10 @@ $light-theme: ( } @include ripple-color( - $color: map.get($theme, unselected-hover-state-layer-color), + $color-map: ( + hover: map.get($theme, unselected-hover-state-layer-color), + press: map.get($theme, unselected-pressed-state-layer-color), + ), $opacity-map: ( hover: map.get($theme, unselected-hover-state-layer-opacity), focus: map.get($theme, unselected-focus-state-layer-opacity), @@ -200,7 +216,7 @@ $light-theme-deprecated: ( /// Only emits theme related styles. /// @param {Map} $theme - Theme configuration to use for theming checkbox. @mixin theme-deprecated($theme, $query: feature-targeting.all()) { - @include theme.validate-keys($light-theme-deprecated, $theme); + @include theme.validate-theme($light-theme-deprecated, $theme); $ripple-color: map.get($theme, ripple-color); $ripple-opacity: map.get($theme, ripple-opacity); @@ -326,7 +342,7 @@ $light-theme-deprecated: ( $ripple-size ); } - $checkbox-padding: calc((_ripple-size - _icon-size) / 2); + $checkbox-padding: 'calc((_ripple-size - _icon-size) / 2)'; $replace: ( _ripple-size: $ripple-size, _icon-size: $icon-size, @@ -363,7 +379,7 @@ $light-theme-deprecated: ( ); } - $margin: calc((_touch-target-size - _ripple-size) / 2); + $margin: 'calc((_touch-target-size - _ripple-size) / 2)'; $replace: ( _touch-target-size: $touch-target-size, _ripple-size: $ripple-size, @@ -373,7 +389,7 @@ $light-theme-deprecated: ( @include theme.property(margin, $margin, $replace: $replace); } - $offset: calc((_ripple-size - _touch-target-size) / 2); + $offset: 'calc((_ripple-size - _touch-target-size) / 2)'; @include feature-targeting.targets($feat-structure) { .mdc-checkbox__native-control { @@ -495,8 +511,6 @@ $light-theme-deprecated: ( ); } - // stylelint-disable max-nesting-depth - #{$anim-selector} { &-unchecked-checked, &-unchecked-indeterminate { @@ -516,8 +530,6 @@ $light-theme-deprecated: ( } } } - - // stylelint-enable max-nesting-depth } } @@ -545,6 +557,10 @@ $light-theme-deprecated: ( ); } + @if $unmarked-stroke-color == null { + $unmarked-fill-color: null; + } + @include if-unmarked-disabled_ { @include container-colors_( $unmarked-stroke-color, @@ -563,6 +579,18 @@ $light-theme-deprecated: ( ); } + @if $marked-fill-color and + custom-properties.get-fallback($marked-fill-color) == + GrayText + { + // Transparent appears white in HCM + $marked-stroke-color: GrayText; + } + + @if $marked-fill-color == null { + $marked-stroke-color: null; + } + @include if-marked-disabled_ { @include container-colors_( $marked-stroke-color, @@ -608,16 +636,30 @@ $light-theme-deprecated: ( /// Sets ripple color when checkbox is not in checked state. @mixin ripple-color( - $color, + $color: null, + $color-map: null, $opacity-map: null, $query: feature-targeting.all() ) { + @if $color == null { + // deprecated approach - always use 'hover' color. + $color: map.get($color-map, hover); + } + @include ripple-theme.states( $color: $color, $opacity-map: $opacity-map, $query: $query, $ripple-target: $ripple-target ); + + @if $color-map { + @include ripple-theme.states-colors( + $color-map: $color-map, + $query: $query, + $ripple-target: $ripple-target + ); + } } /// Sets focus indicator color when checkbox is in checked state. @@ -678,7 +720,9 @@ $light-theme-deprecated: ( /// @access private /// @mixin if-unmarked-enabled_ { - .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate='true']) + .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not( + [data-indeterminate='true'] + ) ~ { @content; } @@ -693,7 +737,9 @@ $light-theme-deprecated: ( // Note: we must use `[disabled]` instead of `:disabled` below because Edge does not always recalculate the style // property when the `:disabled` pseudo-class is followed by a sibling combinator. See: // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11295231/ - .mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate='true']) + .mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not( + [data-indeterminate='true'] + ) ~ { @content; } diff --git a/packages/mdc-checkbox/_checkbox.scss b/packages/mdc-checkbox/_checkbox.scss index eff1ed8a835..45111a2a9ab 100644 --- a/packages/mdc-checkbox/_checkbox.scss +++ b/packages/mdc-checkbox/_checkbox.scss @@ -20,8 +20,8 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern @use 'sass:map'; @use 'sass:math'; @@ -30,6 +30,8 @@ @use '@material/density/functions' as density-functions; @use '@material/dom/dom'; @use '@material/feature-targeting/feature-targeting'; +@use '@material/focus-ring/focus-ring'; +@use '@material/rtl/rtl'; @use '@material/ripple/ripple'; @use '@material/ripple/ripple-theme'; @use '@material/touch-target/mixins' as touch-target-mixins; @@ -76,18 +78,27 @@ @include feature-targeting.targets($feat-structure) { @include base_; } + + @include ripple-theme.focus { + .mdc-checkbox__focus-ring { + @include focus-ring.focus-ring( + $query: $query, + $container-outer-padding-vertical: 0, + $container-outer-padding-horizontal: 0 + ); + } + } + + // Turn off focus ring for IE when HCM is turn off. For some reason this + // adds space to the bottom on the focused checkbox inside a dialog. + @media all and (-ms-high-contrast: none) { + .mdc-checkbox__focus-ring { + display: none; + } + } } @include dom.forced-colors-mode { - @include checkbox-theme.disabled-container-colors( - $unmarked-stroke-color: GrayText, - $unmarked-fill-color: transparent, - $marked-stroke-color: GrayText, - $marked-fill-color: transparent, - $query: $query - ); - @include checkbox-theme.disabled-ink-color(GrayText, $query: $query); - .mdc-checkbox__mixedmark { @include feature-targeting.targets($feat-structure) { margin: 0 1px; // Extra horizontal space around mixedmark symbol. @@ -245,6 +256,9 @@ @mixin base_ { display: inline-block; + &[hidden] { + display: none; + } position: relative; flex: 0 0 checkbox-theme.$icon-size; box-sizing: content-box; @@ -270,7 +284,7 @@ @mixin anim_ { $mdc-checkbox-indeterminate-change-duration_: 500ms; - // stylelint-disable selector-max-type + // stylelint-disable no-unknown-animations -- allow unknown animations &-unchecked-checked, &-unchecked-indeterminate, @@ -339,14 +353,13 @@ &-indeterminate-unchecked { .mdc-checkbox__mixedmark { - // stylelint-disable-next-line declaration-colon-space-after animation: mdc-checkbox-indeterminate-unchecked-mixedmark $mdc-checkbox-indeterminate-change-duration_ * 0.6 linear 0s; transition: none; } } - // stylelint-enable selector-max-type + // stylelint-enable no-unknown-animations } @mixin background_($query: feature-targeting.all()) { @@ -450,6 +463,7 @@ $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { + @include rtl.ignore-next-line(); transform: rotate(45deg); opacity: 0; } @@ -499,6 +513,7 @@ @include feature-targeting.targets($feat-structure) { width: 100%; height: 0; + @include rtl.ignore-next-line(); transform: scaleX(0) rotate(0deg); border-width: math.div(math.floor(checkbox-theme.$mark-stroke-size), 2); border-style: solid; @@ -511,10 +526,12 @@ } @mixin mixedmark--checked_ { + @include rtl.ignore-next-line(); transform: scaleX(1) rotate(-45deg); } @mixin mixedmark--indeterminate_ { + @include rtl.ignore-next-line(); transform: scaleX(1) rotate(0deg); opacity: 1; } @@ -586,11 +603,13 @@ $indeterminate-checked-easing-function_: cubic-bezier(0.14, 0, 0, 1) !default; @keyframes mdc-checkbox-checked-indeterminate-checkmark { from { animation-timing-function: animation-variables.$deceleration-curve-timing-function; + @include rtl.ignore-next-line(); transform: rotate(0deg); opacity: 1; } to { + @include rtl.ignore-next-line(); transform: rotate(45deg); opacity: 0; } @@ -599,11 +618,13 @@ $indeterminate-checked-easing-function_: cubic-bezier(0.14, 0, 0, 1) !default; @keyframes mdc-checkbox-indeterminate-checked-checkmark { from { animation-timing-function: $indeterminate-checked-easing-function_; + @include rtl.ignore-next-line(); transform: rotate(45deg); opacity: 0; } to { + @include rtl.ignore-next-line(); transform: rotate(360deg); opacity: 1; } @@ -612,11 +633,13 @@ $indeterminate-checked-easing-function_: cubic-bezier(0.14, 0, 0, 1) !default; @keyframes mdc-checkbox-checked-indeterminate-mixedmark { from { animation-timing-function: mdc-animation-deceleration-curve-timing-function; + @include rtl.ignore-next-line(); transform: rotate(-45deg); opacity: 0; } to { + @include rtl.ignore-next-line(); transform: rotate(0deg); opacity: 1; } @@ -625,11 +648,13 @@ $indeterminate-checked-easing-function_: cubic-bezier(0.14, 0, 0, 1) !default; @keyframes mdc-checkbox-indeterminate-checked-mixedmark { from { animation-timing-function: $indeterminate-checked-easing-function_; + @include rtl.ignore-next-line(); transform: rotate(0deg); opacity: 1; } to { + @include rtl.ignore-next-line(); transform: rotate(315deg); opacity: 0; } diff --git a/packages/mdc-checkbox/component.ts b/packages/mdc-checkbox/component.ts index 5545aaf61dd..10cac078a07 100644 --- a/packages/mdc-checkbox/component.ts +++ b/packages/mdc-checkbox/component.ts @@ -34,17 +34,16 @@ import {MDCCheckboxAdapter} from './adapter'; import {strings} from './constants'; import {MDCCheckboxFoundation} from './foundation'; -/** - * This type is needed for compatibility with Closure Compiler. - */ -type PropertyDescriptorGetter = (() => unknown) | undefined; - const CB_PROTO_PROPS = ['checked', 'indeterminate']; -export type MDCCheckboxFactory = (el: Element, foundation?: MDCCheckboxFoundation) => MDCCheckbox; +/** MDC Checkbox Factory */ +export type MDCCheckboxFactory = + (el: HTMLElement, foundation?: MDCCheckboxFoundation) => MDCCheckbox; -export class MDCCheckbox extends MDCComponent implements MDCRippleCapableSurface { - static attachTo(root: Element) { +/** MDC Checkbox */ +export class MDCCheckbox extends MDCComponent implements + MDCRippleCapableSurface { + static override attachTo(root: HTMLElement) { return new MDCCheckbox(root); } @@ -89,7 +88,7 @@ export class MDCCheckbox extends MDCComponent implements private handleAnimationEnd!: EventListener; // assigned in initialSyncWithDOM() - initialize() { + override initialize() { const {DATA_INDETERMINATE_ATTR} = strings; this.getNativeControl().indeterminate = this.getNativeControl().getAttribute(DATA_INDETERMINATE_ATTR) === @@ -97,7 +96,7 @@ export class MDCCheckbox extends MDCComponent implements this.getNativeControl().removeAttribute(DATA_INDETERMINATE_ATTR); } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.handleChange = () => { this.foundation.handleChange(); }; @@ -110,7 +109,7 @@ export class MDCCheckbox extends MDCComponent implements this.installPropertyChangeHooks(); } - destroy() { + override destroy() { this.rippleSurface.destroy(); this.getNativeControl().removeEventListener('change', this.handleChange); this.unlisten( @@ -119,12 +118,15 @@ export class MDCCheckbox extends MDCComponent implements super.destroy(); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCCheckboxAdapter = { - addClass: (className) => this.root.classList.add(className), - forceLayout: () => (this.root as HTMLElement).offsetWidth, + addClass: (className) => { + this.root.classList.add(className); + }, + forceLayout: () => this.root.offsetWidth, hasNativeControl: () => !!this.getNativeControl(), isAttachedToDOM: () => Boolean(this.root.parentNode), isChecked: () => this.checked, @@ -136,7 +138,7 @@ export class MDCCheckbox extends MDCComponent implements this.getNativeControl().removeAttribute(attr); }, setNativeControlAttr: (attr, value) => { - this.getNativeControl().setAttribute(attr, value); + this.safeSetAttribute(this.getNativeControl(), attr, value); }, setNativeControlDisabled: (disabled) => { this.getNativeControl().disabled = disabled; @@ -146,19 +148,20 @@ export class MDCCheckbox extends MDCComponent implements } private createRipple(): MDCRipple { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCRippleAdapter = { ...MDCRipple.createAdapter(this), - deregisterInteractionHandler: (evtType, handler) => { + deregisterInteractionHandler: (eventType, handler) => { this.getNativeControl().removeEventListener( - evtType, handler, applyPassive()); + eventType, handler, applyPassive()); }, isSurfaceActive: () => matches(this.getNativeControl(), ':active'), isUnbounded: () => true, - registerInteractionHandler: (evtType, handler) => { + registerInteractionHandler: (eventType, handler) => { this.getNativeControl().addEventListener( - evtType, handler, applyPassive()); + eventType, handler, applyPassive()); }, }; return new MDCRipple(this.root, new MDCRippleFoundation(adapter)); @@ -170,14 +173,14 @@ export class MDCCheckbox extends MDCComponent implements for (const controlState of CB_PROTO_PROPS) { const desc = Object.getOwnPropertyDescriptor(cbProto, controlState); - // We have to check for this descriptor, since some browsers (Safari) don't support its return. - // See: https://bugs.webkit.org/show_bug.cgi?id=49739 + // We have to check for this descriptor, since some browsers (Safari) + // don't support its return. See: + // https://bugs.webkit.org/show_bug.cgi?id=49739 if (!validDescriptor(desc)) { return; } - // Type cast is needed for compatibility with Closure Compiler. - const nativeGetter = (desc as {get: PropertyDescriptorGetter}).get; + const nativeGetter = desc.get; const nativeCbDesc = { configurable: desc.configurable, @@ -210,12 +213,14 @@ export class MDCCheckbox extends MDCComponent implements const el = this.root.querySelector(NATIVE_CONTROL_SELECTOR); if (!el) { - throw new Error(`Checkbox component requires a ${NATIVE_CONTROL_SELECTOR} element`); + throw new Error( + `Checkbox component requires a ${NATIVE_CONTROL_SELECTOR} element`); } return el; } } -function validDescriptor(inputPropDesc: PropertyDescriptor | undefined): inputPropDesc is PropertyDescriptor { +function validDescriptor(inputPropDesc: PropertyDescriptor| + undefined): inputPropDesc is PropertyDescriptor { return !!inputPropDesc && typeof inputPropDesc.set === 'function'; } diff --git a/packages/mdc-checkbox/foundation.ts b/packages/mdc-checkbox/foundation.ts index fdfeb41c4ae..0423f633486 100644 --- a/packages/mdc-checkbox/foundation.ts +++ b/packages/mdc-checkbox/foundation.ts @@ -22,23 +22,25 @@ */ import {MDCFoundation} from '@material/base/foundation'; + import {MDCCheckboxAdapter} from './adapter'; import {cssClasses, numbers, strings} from './constants'; +/** MDC Checkbox Foundation */ export class MDCCheckboxFoundation extends MDCFoundation { - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get strings() { + static override get strings() { return strings; } - static get numbers() { + static override get numbers() { return numbers; } - static get defaultAdapter(): MDCCheckboxAdapter { + static override get defaultAdapter(): MDCCheckboxAdapter { return { addClass: () => undefined, forceLayout: () => undefined, @@ -62,13 +64,13 @@ export class MDCCheckboxFoundation extends MDCFoundation { super({...MDCCheckboxFoundation.defaultAdapter, ...adapter}); } - init() { + override init() { this.currentCheckState = this.determineCheckState(); this.updateAriaChecked(); this.adapter.addClass(cssClasses.UPGRADED); } - destroy() { + override destroy() { clearTimeout(this.animEndLatchTimer); } @@ -125,8 +127,9 @@ export class MDCCheckboxFoundation extends MDCFoundation { this.adapter.addClass(SELECTED); } - // Check to ensure that there isn't a previously existing animation class, in case for example - // the user interacted with the checkbox before the animation was finished. + // Check to ensure that there isn't a previously existing animation class, + // in case for example the user interacted with the checkbox before the + // animation was finished. if (this.currentAnimationClass.length > 0) { clearTimeout(this.animEndLatchTimer); this.adapter.forceLayout(); @@ -137,8 +140,8 @@ export class MDCCheckboxFoundation extends MDCFoundation { this.getTransitionAnimationClass(oldState, newState); this.currentCheckState = newState; - // Check for parentNode so that animations are only run when the element is attached - // to the DOM. + // Check for parentNode so that animations are only run when the element is + // attached to the DOM. if (this.adapter.isAttachedToDOM() && this.currentAnimationClass.length > 0) { this.adapter.addClass(this.currentAnimationClass); @@ -182,18 +185,27 @@ export class MDCCheckboxFoundation extends MDCFoundation { if (newState === TRANSITION_STATE_UNCHECKED) { return ''; } - return newState === TRANSITION_STATE_CHECKED ? ANIM_INDETERMINATE_CHECKED : ANIM_INDETERMINATE_UNCHECKED; + return newState === TRANSITION_STATE_CHECKED ? + ANIM_INDETERMINATE_CHECKED : + ANIM_INDETERMINATE_UNCHECKED; case TRANSITION_STATE_UNCHECKED: - return newState === TRANSITION_STATE_CHECKED ? ANIM_UNCHECKED_CHECKED : ANIM_UNCHECKED_INDETERMINATE; + return newState === TRANSITION_STATE_CHECKED ? + ANIM_UNCHECKED_CHECKED : + ANIM_UNCHECKED_INDETERMINATE; case TRANSITION_STATE_CHECKED: - return newState === TRANSITION_STATE_UNCHECKED ? ANIM_CHECKED_UNCHECKED : ANIM_CHECKED_INDETERMINATE; - default: // TRANSITION_STATE_INDETERMINATE - return newState === TRANSITION_STATE_CHECKED ? ANIM_INDETERMINATE_CHECKED : ANIM_INDETERMINATE_UNCHECKED; + return newState === TRANSITION_STATE_UNCHECKED ? + ANIM_CHECKED_UNCHECKED : + ANIM_CHECKED_INDETERMINATE; + default: // TRANSITION_STATE_INDETERMINATE + return newState === TRANSITION_STATE_CHECKED ? + ANIM_INDETERMINATE_CHECKED : + ANIM_INDETERMINATE_UNCHECKED; } } private updateAriaChecked() { - // Ensure aria-checked is set to mixed if checkbox is in indeterminate state. + // Ensure aria-checked is set to mixed if checkbox is in indeterminate + // state. if (this.adapter.isIndeterminate()) { this.adapter.setNativeControlAttr( strings.ARIA_CHECKED_ATTR, strings.ARIA_CHECKED_INDETERMINATE_VALUE); diff --git a/packages/mdc-checkbox/mdc-checkbox.import.scss b/packages/mdc-checkbox/mdc-checkbox.import.scss index 5040d5b678e..912c7d6f3bd 100644 --- a/packages/mdc-checkbox/mdc-checkbox.import.scss +++ b/packages/mdc-checkbox/mdc-checkbox.import.scss @@ -1,7 +1,5 @@ @forward "@material/animation/variables" as mdc-animation-*; @forward "@material/density/variables" as mdc-density-*; -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; @forward "@material/base/mixins" as mdc-base-*; @forward "@material/theme/variables" as mdc-theme-*; @forward "@material/ripple/variables" as mdc-* hide $mdc-dark-ink-opacities, $mdc-fade-in-duration, $mdc-fade-out-duration, $mdc-light-ink-opacities, $mdc-pressed-dark-ink-opacity, $mdc-pressed-light-ink-opacity, $mdc-translate-duration; diff --git a/packages/mdc-checkbox/mdc-checkbox.scss b/packages/mdc-checkbox/mdc-checkbox.scss index 8bd88f75da4..cca00aba33d 100644 --- a/packages/mdc-checkbox/mdc-checkbox.scss +++ b/packages/mdc-checkbox/mdc-checkbox.scss @@ -20,5 +20,17 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use '@material/dom/dom'; +@use './checkbox-theme'; @use './checkbox'; + @include checkbox.core-styles; + +.mdc-checkbox { + @include dom.forced-colors-mode { + @include checkbox-theme.theme-styles(checkbox-theme.$forced-colors-theme); + } +} diff --git a/packages/mdc-checkbox/package.json b/packages/mdc-checkbox/package.json index 7878f1668a1..51bad7ef7b2 100644 --- a/packages/mdc-checkbox/package.json +++ b/packages/mdc-checkbox/package.json @@ -1,7 +1,7 @@ { "name": "@material/checkbox", "description": "The Material Components for the web checkbox component", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "keywords": [ "material components", @@ -17,14 +17,16 @@ "directory": "packages/mdc-checkbox" }, "dependencies": { - "@material/animation": "^12.0.0", - "@material/base": "^12.0.0", - "@material/density": "^12.0.0", - "@material/dom": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/ripple": "^12.0.0", - "@material/theme": "^12.0.0", - "@material/touch-target": "^12.0.0", + "@material/animation": "^14.0.0", + "@material/base": "^14.0.0", + "@material/density": "^14.0.0", + "@material/dom": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/focus-ring": "^14.0.0", + "@material/ripple": "^14.0.0", + "@material/rtl": "^14.0.0", + "@material/theme": "^14.0.0", + "@material/touch-target": "^14.0.0", "tslib": "^2.1.0" } } diff --git a/packages/mdc-checkbox/test/component.test.ts b/packages/mdc-checkbox/test/component.test.ts index 39c1537d8bb..cf5818994be 100644 --- a/packages/mdc-checkbox/test/component.test.ts +++ b/packages/mdc-checkbox/test/component.test.ts @@ -23,15 +23,15 @@ import {MDCRipple} from '../../mdc-ripple/index'; import {supportsCssVariables} from '../../mdc-ripple/util'; +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; import {createMockFoundation} from '../../../testing/helpers/foundation'; import {setUpMdcTestEnvironment} from '../../../testing/helpers/setup'; import {strings} from '../constants'; import {MDCCheckbox, MDCCheckboxFoundation} from '../index'; -function getFixture(): Element { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` +function getFixture() { + return createFixture(html`
- `; - const el = wrapper.firstElementChild as Element; - wrapper.removeChild(el); - return el; + `); } function setupTest() { const root = getFixture(); const cb = - root.querySelector(strings.NATIVE_CONTROL_SELECTOR) as HTMLInputElement; + root.querySelector(strings.NATIVE_CONTROL_SELECTOR)!; const component = new MDCCheckbox(root); return {root, cb, component}; } @@ -65,7 +62,7 @@ function setupTest() { function setupMockFoundationTest() { const root = getFixture(); const cb = - root.querySelector(strings.NATIVE_CONTROL_SELECTOR) as HTMLInputElement; + root.querySelector(strings.NATIVE_CONTROL_SELECTOR)!; const mockFoundation = createMockFoundation(MDCCheckboxFoundation); const component = new MDCCheckbox(root, mockFoundation); return {root, cb, component, mockFoundation}; @@ -78,7 +75,7 @@ describe('MDCCheckbox', () => { it('#constructor initializes the root element with a ripple', () => { const {root} = setupTest(); jasmine.clock().tick(1); - expect(root.classList.contains('mdc-ripple-upgraded')).toBeTruthy(); + expect(root).toHaveClass('mdc-ripple-upgraded'); }); it('#destroy removes the ripple', () => { @@ -86,26 +83,24 @@ describe('MDCCheckbox', () => { jasmine.clock().tick(1); component.destroy(); jasmine.clock().tick(1); - expect(root.classList.contains('mdc-ripple-upgraded')).toBeFalsy(); + expect(root).not.toHaveClass('mdc-ripple-upgraded'); }); it('(regression) activates ripple on keydown when the input element surface is active', () => { const {root} = setupTest(); - const input = root.querySelector('input') as HTMLInputElement; + const input = root.querySelector('input')!; jasmine.clock().tick(1); const fakeMatches = jasmine.createSpy('.matches'); fakeMatches.and.returnValue(true); input.matches = fakeMatches; - expect(root.classList.contains('mdc-ripple-upgraded')).toBe(true); + expect(root).toHaveClass('mdc-ripple-upgraded'); emitEvent(input, 'keydown'); jasmine.clock().tick(1); - expect(root.classList.contains( - 'mdc-ripple-upgraded--foreground-activation')) - .toBe(true); + expect(root).toHaveClass('mdc-ripple-upgraded--foreground-activation'); }); } @@ -161,8 +156,7 @@ describe('MDCCheckbox', () => { const {root, component} = setupTest(); (component as any).foundation.handleAnimationEnd = jasmine.createSpy(); emitEvent(root, 'animationend'); - expect((component as any).foundation.handleAnimationEnd) - .toHaveBeenCalled(); + expect((component as any).foundation.handleAnimationEnd).toHaveBeenCalled(); }); it('"checked" property change hook calls foundation#handleChange', () => { @@ -212,14 +206,14 @@ describe('MDCCheckbox', () => { it('adapter#addClass adds a class to the root element', () => { const {root, component} = setupTest(); (component.getDefaultFoundation() as any).adapter.addClass('foo'); - expect(root.classList.contains('foo')).toBeTruthy(); + expect(root).toHaveClass('foo'); }); it('adapter#removeClass removes a class from the root element', () => { const {root, component} = setupTest(); root.classList.add('foo'); (component.getDefaultFoundation() as any).adapter.removeClass('foo'); - expect(root.classList.contains('foo')).toBeFalsy(); + expect(root).not.toHaveClass('foo'); }); it('adapter#setNativeControlAttr sets an attribute on the input element', @@ -306,8 +300,7 @@ describe('MDCCheckbox', () => { it('#adapter.hasNativeControl returns true when checkbox exists', () => { const {component} = setupTest(); - expect( - (component.getDefaultFoundation() as any).adapter.hasNativeControl()) + expect((component.getDefaultFoundation() as any).adapter.hasNativeControl()) .toBe(true); }); diff --git a/packages/mdc-checkbox/test/foundation.test.ts b/packages/mdc-checkbox/test/foundation.test.ts index 0e9ebe902bf..f46bd950ebe 100644 --- a/packages/mdc-checkbox/test/foundation.test.ts +++ b/packages/mdc-checkbox/test/foundation.test.ts @@ -82,7 +82,7 @@ function setupChangeHandlerTest() { function testChangeHandler( desc: string, changes: CheckboxState|CheckboxState[], expectedClass: string) { - changes = Array.isArray(changes) ? changes : [changes] + changes = Array.isArray(changes) ? changes : [changes]; it(`changeHandler: ${desc}`, () => { const {mockAdapter, change} = setupChangeHandlerTest(); @@ -134,10 +134,10 @@ describe('MDCCheckboxFoundation', () => { /* * Shims Object.getOwnPropertyDescriptor for the checkbox's WebIDL attributes. - * Used to test the behavior of overridding WebIDL properties in different + * Used to test the behavior of overriding WebIDL properties in different * browser environments. For example, in Safari WebIDL attributes don't * return get/set in descriptors. - */ + */ function withMockCheckboxDescriptorReturning( descriptor: undefined|typeof DESC_UNDEFINED, runTests: () => void) { const mockGetOwnPropertyDescriptor = @@ -150,11 +150,10 @@ describe('MDCCheckboxFoundation', () => { const originalDesc = Object.getOwnPropertyDescriptor(Object, 'getOwnPropertyDescriptor'); - Object.defineProperty( - Object, 'getOwnPropertyDescriptor', { - ...originalDesc, - value: mockGetOwnPropertyDescriptor, - }); + Object.defineProperty(Object, 'getOwnPropertyDescriptor', { + ...originalDesc, + value: mockGetOwnPropertyDescriptor, + }); runTests(); // After running tests, restore original property. diff --git a/packages/mdc-checkbox/test/mdc-checkbox.scss.test.ts b/packages/mdc-checkbox/test/mdc-checkbox.scss.test.ts index 41d7cac613e..0edf7d5b2e9 100644 --- a/packages/mdc-checkbox/test/mdc-checkbox.scss.test.ts +++ b/packages/mdc-checkbox/test/mdc-checkbox.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-checkbox.scss', () => { diff --git a/packages/mdc-chips/_assist-chip-theme.scss b/packages/mdc-chips/_assist-chip-theme.scss new file mode 100644 index 00000000000..dca22952632 --- /dev/null +++ b/packages/mdc-chips/_assist-chip-theme.scss @@ -0,0 +1,92 @@ +// +// Copyright 2021 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@use 'sass:map'; +@use '@material/theme/theme'; +@use '@material/theme/keys'; +@use '@material/tokens/resolvers'; +@use './chip-theme'; + +$_light-theme: ( + container-shape: null, + container-height: null, + flat-container-elevation: null, + flat-outline-color: null, + flat-outline-width: null, + elevated-container-elevation: null, + elevated-container-color: null, + elevated-container-shadow-color: null, + container-surface-tint-layer-color: null, + label-text-font: null, + label-text-line-height: null, + label-text-size: null, + label-text-weight: null, + label-text-tracking: null, + label-text-color: null, + with-icon-icon-size: null, + with-icon-icon-color: null, + flat-disabled-outline-color: null, + flat-disabled-outline-opacity: null, + disabled-label-text-color: null, + disabled-label-text-opacity: null, + with-icon-disabled-icon-color: null, + with-icon-disabled-icon-opacity: null, + elevated-disabled-container-elevation: null, + elevated-disabled-container-color: null, + elevated-disabled-container-opacity: null, + hover-state-layer-color: null, + hover-state-layer-opacity: null, + hover-label-text-color: null, + with-icon-hover-icon-color: null, + elevated-hover-container-elevation: null, + focus-state-layer-color: null, + focus-state-layer-opacity: null, + focus-label-text-color: null, + flat-focus-outline-color: null, + with-icon-focus-icon-color: null, + elevated-focus-container-elevation: null, + pressed-state-layer-color: null, + pressed-state-layer-opacity: null, + pressed-label-text-color: null, + with-icon-pressed-icon-color: null, + elevated-pressed-container-elevation: null, +); + +$_custom-property-prefix: 'assist-chip'; + +@mixin theme($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme($_light-theme, $theme); + $theme: chip-theme.resolve-theme($theme, $resolvers: $resolvers); + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +@mixin theme-styles($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme-styles($_light-theme, $theme); + $theme: keys.create-theme-properties( + $theme, + $prefix: $_custom-property-prefix + ); + @include chip-theme.theme-styles($theme, $resolvers: $resolvers); +} diff --git a/packages/mdc-chips/_chip-theme.scss b/packages/mdc-chips/_chip-theme.scss index 2633e144566..0ae101dd0ac 100644 --- a/packages/mdc-chips/_chip-theme.scss +++ b/packages/mdc-chips/_chip-theme.scss @@ -24,14 +24,20 @@ // stylelint-disable selector-class-pattern -- // Internal styling for Chip MDC component. +@use 'sass:map'; +@use 'sass:meta'; @use 'sass:math'; @use 'sass:color'; +@use '@material/dom/dom'; @use '@material/density/density'; @use '@material/feature-targeting/feature-targeting'; @use '@material/shape/shape'; @use '@material/ripple/ripple-theme'; @use '@material/rtl/rtl'; @use '@material/theme/theme'; +@use '@material/theme/keys'; +@use '@material/elevation/elevation-theme'; +@use '@material/tokens/resolvers'; @use '@material/theme/state'; @use '@material/theme/theme-color'; @use '@material/typography/typography'; @@ -82,6 +88,479 @@ $filter-selected-container-color: color.mix( 8% ); +$light-theme: ( + container-elevation: null, + container-height: null, + container-shadow-color: null, + container-shape: null, + disabled-label-text-color: null, + disabled-label-text-opacity: null, + disabled-outline-color: null, + disabled-outline-opacity: null, + elevated-container-color: null, + elevated-container-elevation: null, + elevated-container-shadow-color: null, + container-surface-tint-layer-color: null, + elevated-disabled-container-color: null, + elevated-disabled-container-elevation: null, + elevated-disabled-container-opacity: null, + elevated-focus-container-elevation: null, + elevated-hover-container-elevation: null, + elevated-pressed-container-elevation: null, + elevated-selected-container-color: null, + elevated-selected-container-elevation: null, + elevated-unselected-container-color: null, + flat-container-elevation: null, + flat-disabled-outline-color: null, + flat-disabled-outline-opacity: null, + flat-disabled-selected-container-color: null, + flat-disabled-selected-container-opacity: null, + flat-disabled-selected-outline-color: null, + flat-disabled-selected-outline-opacity: null, + flat-disabled-unselected-outline-color: null, + flat-disabled-unselected-outline-opacity: null, + flat-focus-outline-color: null, + flat-outline-color: null, + flat-outline-width: null, + flat-selected-container-color: null, + flat-selected-focus-container-elevation: null, + flat-selected-hover-container-elevation: null, + flat-selected-outline-color: null, + flat-selected-outline-width: null, + flat-selected-pressed-container-elevation: null, + flat-unselected-focus-container-elevation: null, + flat-unselected-focus-outline-color: null, + flat-unselected-hover-container-elevation: null, + flat-unselected-outline-color: null, + flat-unselected-outline-width: null, + flat-unselected-pressed-container-elevation: null, + focus-label-text-color: null, + focus-outline-color: null, + focus-state-layer-color: null, + focus-state-layer-opacity: null, + hover-label-text-color: null, + hover-state-layer-color: null, + hover-state-layer-opacity: null, + label-text-color: null, + label-text-font: null, + label-text-line-height: null, + label-text-size: null, + label-text-tracking: null, + label-text-weight: null, + outline-color: null, + outline-width: null, + pressed-label-text-color: null, + pressed-state-layer-color: null, + pressed-state-layer-opacity: null, + selected-focus-label-text-color: null, + selected-focus-state-layer-color: null, + selected-focus-state-layer-opacity: null, + selected-hover-label-text-color: null, + selected-hover-state-layer-color: null, + selected-hover-state-layer-opacity: null, + selected-label-text-color: null, + selected-pressed-label-text-color: null, + selected-pressed-state-layer-color: null, + selected-pressed-state-layer-opacity: null, + unselected-focus-label-text-color: null, + unselected-focus-state-layer-color: null, + unselected-focus-state-layer-opacity: null, + unselected-hover-label-text-color: null, + unselected-hover-state-layer-color: null, + unselected-hover-state-layer-opacity: null, + unselected-label-text-color: null, + unselected-pressed-label-text-color: null, + unselected-pressed-state-layer-color: null, + unselected-pressed-state-layer-opacity: null, + with-avatar-avatar-shape: null, + with-avatar-avatar-size: null, + with-avatar-disabled-avatar-opacity: null, + with-icon-disabled-icon-color: null, + with-icon-disabled-icon-opacity: null, + with-icon-focus-icon-color: null, + with-icon-hover-icon-color: null, + with-icon-icon-color: null, + with-icon-icon-size: null, + with-icon-pressed-icon-color: null, + with-icon-selected-focus-icon-color: null, + with-icon-selected-hover-icon-color: null, + with-icon-selected-icon-color: null, + with-icon-selected-pressed-icon-color: null, + with-icon-unselected-focus-icon-color: null, + with-icon-unselected-hover-icon-color: null, + with-icon-unselected-icon-color: null, + with-icon-unselected-pressed-icon-color: null, + with-leading-icon-disabled-leading-icon-color: null, + with-leading-icon-disabled-leading-icon-opacity: null, + with-leading-icon-focus-leading-icon-color: null, + with-leading-icon-hover-leading-icon-color: null, + with-leading-icon-leading-icon-color: null, + with-leading-icon-leading-icon-size: null, + with-leading-icon-pressed-leading-icon-color: null, + with-trailing-icon-disabled-trailing-icon-color: null, + with-trailing-icon-disabled-trailing-icon-opacity: null, + with-trailing-icon-focus-trailing-icon-color: null, + with-trailing-icon-hover-trailing-icon-color: null, + with-trailing-icon-pressed-trailing-icon-color: null, + with-trailing-icon-trailing-icon-color: null, + with-trailing-icon-trailing-icon-size: null, +); +$_custom-property-prefix: 'chip'; + +// TODO(b/259513131): Move theme resolution to variant theme +// and remove custom property declaration when all customers are ready to be migrated +@mixin theme($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme($light-theme, $theme); + $theme: resolve-theme($theme, $resolvers: $resolvers); + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +// TODO(b/259513131): Move theme resolution to variant theme +// and remove custom property declaration when all customers are ready to be migrated +@mixin theme-styles($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme-styles($light-theme, $theme); + $theme: resolve-theme($theme, $resolvers: $resolvers); + $theme: keys.create-theme-properties( + $theme, + $prefix: $_custom-property-prefix + ); + + @include _container-shape(map.get($theme, container-shape)); + .mdc-evolution-chip__icon--primary { + @include shape.radius(map.get($theme, with-avatar-avatar-shape)); + } + @include graphic-size(map.get($theme, with-avatar-avatar-size)); + @include height(map.get($theme, container-height)); + @include elevation-theme.overlay-container-color( + map.get($theme, container-surface-tint-layer-color) + ); + @include _container-elevation( + $map: ( + default: map.get($theme, container-elevation), + ) + ); + @include _container-elevation( + $map: ( + enabled: map.get($theme, flat-container-elevation), + ) + ); + @include _container-elevation( + $map: ( + enabled: map.get($theme, elevated-container-elevation), + disabled: map.get($theme, elevated-disabled-container-elevation), + hover: map.get($theme, elevated-hover-container-elevation), + focus: map.get($theme, elevated-focus-container-elevation), + pressed: map.get($theme, elevated-pressed-container-elevation), + ) + ); + &.mdc-evolution-chip--selected { + @include _container-elevation( + ( + enabled: map.get($theme, elevated-selected-container-elevation), + disabled: + map.get($theme, elevated-selected-disabled-container-elevation), + hover: map.get($theme, elevated-selected-hover-container-elevation), + focus: map.get($theme, elevated-selected-focus-container-elevation), + pressed: map.get($theme, elevated-selected-pressed-container-elevation), + ) + ); + @include _container-elevation( + ( + enabled: map.get($theme, flat-selected-container-elevation), + disabled: map.get($theme, flat-selected-disabled-container-elevation), + hover: map.get($theme, flat-selected-hover-container-elevation), + focus: map.get($theme, flat-selected-focus-container-elevation), + pressed: map.get($theme, flat-selected-pressed-container-elevation), + ) + ); + } + &:not(.mdc-evolution-chip--selected) { + @include _container-elevation( + ( + enabled: map.get($theme, flat-unselected-container-elevation), + disabled: map.get($theme, flat-unselected-disabled-container-elevation), + hover: map.get($theme, flat-unselected-hover-container-elevation), + focus: map.get($theme, flat-unselected-focus-container-elevation), + pressed: map.get($theme, flat-unselected-pressed-container-elevation), + ) + ); + } + @include outline-color( + ( + enabled: map.get($theme, outline-color), + focus: map.get($theme, focus-outline-color), + disabled: map.get($theme, disabled-outline-color), + ) + ); + @include outline-color( + ( + enabled: map.get($theme, flat-outline-color), + focus: map.get($theme, flat-focus-outline-color), + disabled: map.get($theme, flat-disabled-outline-color), + ) + ); + @include selected-outline-color( + ( + enabled: map.get($theme, flat-selected-outline-color), + disabled: map.get($theme, flat-disabled-selected-outline-color), + ) + ); + &:not(.mdc-evolution-chip--selected) { + @include outline-color( + ( + disabled: map.get($theme, flat-disabled-unselected-outline-color), + focus: map.get($theme, flat-unselected-focus-outline-color), + enabled: map.get($theme, flat-unselected-outline-color), + ) + ); + } + @include outline-width(map.get($theme, outline-width)); + @include outline-width(map.get($theme, flat-outline-width)); + &.mdc-evolution-chip--selected { + @include outline-width(map.get($theme, flat-selected-outline-width)); + } + &:not(.mdc-evolution-chip--selected) { + @include outline-width(map.get($theme, flat-unselected-outline-width)); + } + @include container-color( + ( + enabled: map.get($theme, elevated-container-color), + disabled: map.get($theme, elevated-disabled-container-color), + ) + ); + @include selected-container-color( + ( + enabled: map.get($theme, elevated-selected-container-color), + disabled: map.get($theme, elevated-disabled-container-color), + ) + ); + // TODO(b/255716167) Separate theme-styles mixin for hairline and elevated chip variants. + @include selected-container-color( + ( + enabled: map.get($theme, flat-selected-container-color), + disabled: map.get($theme, flat-disabled-selected-container-color), + ) + ); + &:not(.mdc-evolution-chip--selected) { + @include container-color( + ( + enabled: map.get($theme, elevated-unselected-container-color), + ) + ); + } + .mdc-evolution-chip__text-label { + @include typography.theme-styles( + ( + font: map.get($theme, label-text-font), + line-height: map.get($theme, label-text-line-height), + size: map.get($theme, label-text-size), + weight: map.get($theme, label-text-weight), + tracking: map.get($theme, label-text-tracking), + ) + ); + } + @include text-label-color( + ( + enabled: map.get($theme, label-text-color), + hover: map.get($theme, hover-label-text-color), + focus: map.get($theme, focus-label-text-color), + pressed: map.get($theme, pressed-label-text-color), + disabled: map.get($theme, disabled-label-text-color), + ) + ); + @include selected-text-label-color( + ( + enabled: map.get($theme, selected-label-text-color), + hover: map.get($theme, selected-hover-label-text-color), + focus: map.get($theme, selected-focus-label-text-color), + pressed: map.get($theme, selected-pressed-label-text-color), + disabled: map.get($theme, disabled-label-text-color), + ) + ); + &:not(.mdc-evolution-chip--selected) { + @include text-label-color( + ( + enabled: map.get($theme, unselected-label-text-color), + hover: map.get($theme, unselected-hover-label-text-color), + focus: map.get($theme, unselected-focus-label-text-color), + pressed: map.get($theme, unselected-pressed-label-text-color), + disabled: map.get($theme, unselected-disabled-label-text-color), + ) + ); + } + + @include icon-size(map.get($theme, with-icon-icon-size)); + @include icon-color( + ( + enabled: map.get($theme, with-icon-icon-color), + disabled: map.get($theme, with-icon-disabled-icon-color), + hover: map.get($theme, with-icon-hover-icon-color), + focus: map.get($theme, with-icon-focus-icon-color), + pressed: map.get($theme, with-icon-pressed-icon-color), + ) + ); + @include checkmark-color( + ( + enabled: map.get($theme, with-icon-selected-icon-color), + disabled: map.get($theme, with-icon-disabled-icon-color), + hover: map.get($theme, with-icon-selected-hover-icon-color), + focus: map.get($theme, with-icon-selected-focus-icon-color), + pressed: map.get($theme, with-icon-selected-pressed-icon-color), + ) + ); + &:not(.mdc-evolution-chip--selected) { + @include icon-color( + ( + enabled: map.get($theme, with-icon-unselected-icon-color), + disabled: map.get($theme, with-icon-unselected-disabled-icon-color), + hover: map.get($theme, with-icon-unselected-hover-icon-color), + focus: map.get($theme, with-icon-unselected-focus-icon-color), + pressed: map.get($theme, with-icon-unselected-pressed-icon-color), + ) + ); + } + @include icon-color( + ( + disabled: map.get($theme, with-leading-icon-disabled-leading-icon-color), + focus: map.get($theme, with-leading-icon-focus-leading-icon-color), + hover: map.get($theme, with-leading-icon-hover-leading-icon-color), + enabled: map.get($theme, with-leading-icon-leading-icon-color), + pressed: map.get($theme, with-leading-icon-pressed-leading-icon-color), + ) + ); + @include trailing-action-color( + ( + disabled: map.get($theme, with-trailing-icon-disabled-trailing-icon-color), + focus: map.get($theme, with-trailing-icon-focus-trailing-icon-color), + hover: map.get($theme, with-trailing-icon-hover-trailing-icon-color), + enabled: map.get($theme, with-trailing-icon-trailing-icon-color), + pressed: map.get($theme, with-trailing-icon-pressed-trailing-icon-color), + ) + ); + @include _ripple-theme( + ( + focus-state-layer-color: map.get($theme, focus-state-layer-color), + focus-state-layer-opacity: map.get($theme, focus-state-layer-opacity), + hover-state-layer-color: map.get($theme, hover-state-layer-color), + hover-state-layer-opacity: map.get($theme, hover-state-layer-opacity), + pressed-state-layer-color: map.get($theme, pressed-state-layer-color), + pressed-state-layer-opacity: map.get($theme, pressed-state-layer-opacity), + ) + ); + &.mdc-evolution-chip--selected { + @include _ripple-theme( + ( + focus-state-layer-color: + map.get($theme, selected-focus-state-layer-color), + focus-state-layer-opacity: + map.get($theme, selected-focus-state-layer-opacity), + hover-state-layer-color: + map.get($theme, selected-hover-state-layer-color), + hover-state-layer-opacity: + map.get($theme, selected-hover-state-layer-opacity), + pressed-state-layer-color: + map.get($theme, selected-pressed-state-layer-color), + pressed-state-layer-opacity: + map.get($theme, selected-pressed-state-layer-opacity), + ) + ); + } + &:not(.mdc-evolution-chip--selected) { + @include _ripple-theme( + ( + focus-state-layer-color: + map.get($theme, unselected-focus-state-layer-color), + focus-state-layer-opacity: + map.get($theme, unselected-focus-state-layer-opacity), + hover-state-layer-color: + map.get($theme, unselected-hover-state-layer-color), + hover-state-layer-opacity: + map.get($theme, unselected-hover-state-layer-opacity), + pressed-state-layer-color: + map.get($theme, unselected-pressed-state-layer-color), + pressed-state-layer-opacity: + map.get($theme, unselected-pressed-state-layer-opacity), + ) + ); + } +} + +@function resolve-theme($theme, $resolvers) { + $elevation-resolver: map.get($resolvers, elevation); + @if $elevation-resolver == null { + @return $theme; + } + + $elevation-pairs: ( + container-shadow-color: ( + container-elevation, + ), + flat-container-shadow-color: ( + flat-container-elevation, + ), + elevated-container-shadow-color: ( + elevated-container-elevation, + elevated-disabled-container-elevation, + elevated-focus-container-elevation, + elevated-hover-container-elevation, + elevated-pressed-container-elevation, + elevated-selected-container-elevation, + elevated-selected-disabled-container-elevation, + elevated-selected-focus-container-elevation, + elevated-selected-hover-container-elevation, + elevated-selected-pressed-container-elevation, + flat-selected-focus-container-elevation, + flat-selected-hover-container-elevation, + flat-selected-pressed-container-elevation, + flat-unselected-focus-container-elevation, + flat-unselected-hover-container-elevation, + flat-unselected-pressed-container-elevation, + ), + ); + + @each $shadow-color-key, $elevation-keys in $elevation-pairs { + $shadow-color: map.get($theme, $shadow-color-key); + @if $shadow-color != null { + @each $key in $elevation-keys { + $elevation: map.get($theme, $key); + @if $elevation != null { + $resolved-value: meta.call( + $elevation-resolver, + $elevation: $elevation, + $shadow-color: $shadow-color + ); + // Update the key with the resolved value. + $theme: map.set($theme, $key, $resolved-value); + } + } + } + } + @return $theme; +} + +@mixin _ripple-theme($ripple-theme) { + .mdc-evolution-chip__action--primary { + @include ripple-theme.internal-theme-styles( + ( + focus-state-layer-color: map.get($ripple-theme, focus-state-layer-color), + focus-state-layer-opacity: + map.get($ripple-theme, focus-state-layer-opacity), + hover-state-layer-color: map.get($ripple-theme, hover-state-layer-color), + hover-state-layer-opacity: + map.get($ripple-theme, hover-state-layer-opacity), + pressed-state-layer-color: + map.get($ripple-theme, pressed-state-layer-color), + pressed-state-layer-opacity: + map.get($ripple-theme, pressed-state-layer-opacity), + ), + $ripple-target: $ripple-target + ); + } +} + /// /// Sets the ripple color of the chip. /// @param {Color} $color - The color of the ripple. @@ -202,6 +681,18 @@ $filter-selected-container-color: color.mix( } } +@mixin _container-shape($radius) { + @include shape.radius($radius); + + #{$ripple-target} { + @include shape.radius($radius); + } + + .mdc-evolution-chip__action--primary:before { + @include shape.radius($radius); + } +} + /// /// Sets the shape radius of the chip. /// @param {Number|List} $radius - Shape radius in `border-radius` CSS format. @@ -235,8 +726,7 @@ $filter-selected-container-color: color.mix( ); } - .mdc-evolution-chip__action--primary:before, - .mdc-evolution-chip__action--primary:after { + .mdc-evolution-chip__action--primary:before { @include shape.radius( $radius, $rtl-reflexive, @@ -273,7 +763,7 @@ $filter-selected-container-color: color.mix( /// Customizes the outline color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `focus`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `focus`, `disabled`. /// /// @example /// @include outline-color(blue); @@ -283,6 +773,9 @@ $filter-selected-container-color: color.mix( /// @mixin outline-color($color-or-map, $query: feature-targeting.all()) { @include _outline-color(state.get-default-state($color-or-map), $query); + &:not(.mdc-evolution-chip--disabled) { + @include _outline-color(state.get-enabled-state($color-or-map), $query); + } @include _focus-outline-color(state.get-focus-state($color-or-map), $query); &.mdc-evolution-chip--disabled { @include _outline-color(state.get-disabled-state($color-or-map), $query); @@ -293,7 +786,7 @@ $filter-selected-container-color: color.mix( /// Customizes the selected outline color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `focus`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `focus`, `disabled`. /// /// @example /// @include selected-outline-color(blue); @@ -307,7 +800,7 @@ $filter-selected-container-color: color.mix( } } -@mixin _outline-color($color, $query) { +@mixin _outline-color($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); .mdc-evolution-chip__action--primary:before { @@ -315,11 +808,18 @@ $filter-selected-container-color: color.mix( @if $color { @include theme.property(border-color, $color); } + + // TODO(b/206694742): Find a better solution. + @if $color == transparent { + @include dom.forced-colors-mode($exclude-ie11: true) { + @include theme.property(border-color, CanvasText); + } + } } } } -@mixin _focus-outline-color($color, $query) { +@mixin _focus-outline-color($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); .mdc-evolution-chip__action--primary:not(.mdc-evolution-chip__action--presentational) { @@ -353,7 +853,7 @@ $filter-selected-container-color: color.mix( /// Customizes the container color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `disabled`. /// /// @example /// @include container-color(blue); @@ -363,6 +863,9 @@ $filter-selected-container-color: color.mix( /// @mixin container-color($color-or-map, $query: feature-targeting.all()) { @include _container-color(state.get-default-state($color-or-map), $query); + &:not(.mdc-evolution-chip--disabled) { + @include _container-color(state.get-enabled-state($color-or-map), $query); + } &.mdc-evolution-chip--disabled { @include _container-color(state.get-disabled-state($color-or-map), $query); } @@ -372,7 +875,7 @@ $filter-selected-container-color: color.mix( /// Customizes the selected container color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `disabled`. /// /// @example /// @include selected-container-color(blue); @@ -389,7 +892,7 @@ $filter-selected-container-color: color.mix( } } -@mixin _container-color($color, $query) { +@mixin _container-color($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @include feature-targeting.targets($feat-color) { @@ -403,7 +906,7 @@ $filter-selected-container-color: color.mix( /// Customizes the text label color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `hover`, `focus`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `hover`, `focus`, `disabled`. /// /// @example /// @include text-label-color(blue); @@ -413,6 +916,9 @@ $filter-selected-container-color: color.mix( /// @mixin text-label-color($color-or-map, $query: feature-targeting.all()) { @include _text-label-color(state.get-default-state($color-or-map), $query); + &:not(.mdc-evolution-chip--disabled) { + @include _text-label-color(state.get-enabled-state($color-or-map), $query); + } .mdc-evolution-chip__action--primary:not(.mdc-evolution-chip__action--presentational):hover { @include _text-label-color(state.get-hover-state($color-or-map), $query); } @@ -485,7 +991,9 @@ $filter-selected-container-color: color.mix( // computed based on the unselected bounding client react which will not have // enough horizontal space to account for the growth in width. &.mdc-evolution-chip--selectable:not(.mdc-evolution-chip--with-primary-icon) { - --mdc-chip-graphic-selected-width: #{$size}; + @if $size { + @include theme.property(--mdc-chip-graphic-selected-width, $size); + } } .mdc-evolution-chip__graphic { @@ -501,7 +1009,7 @@ $filter-selected-container-color: color.mix( /// Customizes the icon color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `focus`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `focus`, `disabled`. /// /// @example /// @include icon-color(blue); @@ -512,6 +1020,10 @@ $filter-selected-container-color: color.mix( @mixin icon-color($color-or-map, $query: feature-targeting.all()) { @include _icon-color(state.get-default-state($color-or-map), $query); + &:not(.mdc-evolution-chip--disabled) { + @include _icon-color(state.get-enabled-state($color-or-map), $query); + } + .mdc-evolution-chip__action--primary:not(.mdc-evolution-chip__action--presentational):hover { @include _icon-color(state.get-hover-state($color-or-map), $query); } @@ -616,9 +1128,7 @@ $filter-selected-container-color: color.mix( @mixin trailing-action-size($size, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); - .mdc-evolution-chip__icon--trailing, - // :after pseudo-element is used for Windows high-contrast mode focus indicator - .mdc-evolution-chip__action--trailing:after { + .mdc-evolution-chip__icon--trailing { @include feature-targeting.targets($feat-structure) { @include theme.property(height, $size); @include theme.property(width, $size); @@ -645,6 +1155,13 @@ $filter-selected-container-color: color.mix( $query ); + &:not(.mdc-evolution-chip--disabled) { + @include _trailing-action-color( + state.get-enabled-state($color-or-map), + $query + ); + } + .mdc-evolution-chip__action--trailing:hover { @include _trailing-action-color( state.get-hover-state($color-or-map), @@ -700,7 +1217,7 @@ $filter-selected-container-color: color.mix( /// Customizes the checkmark color, using a Color or state Map. /// - To set only the default color, provide a single Color. /// - To set one or more state colors, provide a state Map with optional keys. -/// - Supported state Map keys: `default`, `hover`, `focus`, `disabled`. +/// - Supported state Map keys: `default`, `enabled`, `hover`, `focus`, `disabled`. /// /// @example /// @include checkmark-color(blue); @@ -711,6 +1228,10 @@ $filter-selected-container-color: color.mix( @mixin checkmark-color($color-or-map, $query: feature-targeting.all()) { @include _checkmark-color(state.get-default-state($color-or-map), $query); + &:not(.mdc-evolution-chip--disabled) { + @include _checkmark-color(state.get-enabled-state($color-or-map), $query); + } + .mdc-evolution-chip__action--primary:not(.mdc-evolution-chip__action--presentational):hover { @include _checkmark-color(state.get-hover-state($color-or-map), $query); } @@ -862,13 +1383,6 @@ $filter-selected-container-color: color.mix( } } - // :after is used for high-contrast mode focus indicator - .mdc-evolution-chip__action--trailing:after { - @include feature-targeting.targets($feat-structure) { - @include rtl.reflexive-position(left, $trailing-action-leading); - } - } - .mdc-evolution-chip__action--primary { @include feature-targeting.targets($feat-structure) { @include rtl.reflexive-property(padding, $primary-leading, 0); @@ -919,13 +1433,6 @@ $filter-selected-container-color: color.mix( } } - // :after is used for high-contrast mode focus indicator - .mdc-evolution-chip__action--trailing:after { - @include feature-targeting.targets($feat-structure) { - @include rtl.reflexive-position(left, $trailing-action-leading); - } - } - .mdc-evolution-chip__action--primary { @include feature-targeting.targets($feat-structure) { @include rtl.reflexive-property(padding, 0, 0); @@ -933,3 +1440,67 @@ $filter-selected-container-color: color.mix( } } } + +@mixin _container-elevation-theme($theme) { + @if map.get($theme, shadow) { + @include elevation-theme.shadow(map.get($theme, shadow)); + } + @if map.get($theme, overlay-opacity) { + @include elevation-theme.overlay-opacity(map.get($theme, overlay-opacity)); + } + // TODO(b/260053182): Remove setting overlay color after migration of customers + @if map.get($theme, overlay-color) { + @include elevation-theme.overlay-container-color( + map.get($theme, overlay-color) + ); + } +} + +// TODO(b/259913622): Use elevation's theme-style() mixin. +@mixin _container-elevation($map) { + $default-state: state.get-default-state($map); + @if $default-state { + @include _container-elevation-theme($default-state); + } + + &:not(.mdc-evolution-chip--disabled) { + $enabled-state: state.get-enabled-state($map); + @if $enabled-state { + @include _container-elevation-theme($enabled-state); + } + } + + @include ripple-theme.hover() { + $hover-state: state.get-hover-state($map); + @if $hover-state { + @include _container-elevation-theme($hover-state); + } + } + + @include ripple-theme.focus() { + $focus-state: state.get-focus-state($map); + @if $focus-state { + @include _container-elevation-theme($focus-state); + } + } + + @include ripple-theme.pressed() { + $pressed-state: state.get-pressed-state($map); + @if $pressed-state { + @include _container-elevation-theme($pressed-state); + } + } + + &.mdc-evolution-chip--disabled { + $disabled-state: state.get-disabled-state($map); + @if $disabled-state { + @include _container-elevation-theme($disabled-state); + } + } +} + +@mixin button-cursor($cursor) { + .mdc-evolution-chip__action { + cursor: $cursor; + } +} diff --git a/packages/mdc-chips/_chip.scss b/packages/mdc-chips/_chip.scss index 18b88371e5b..5a1cca5e916 100644 --- a/packages/mdc-chips/_chip.scss +++ b/packages/mdc-chips/_chip.scss @@ -27,9 +27,11 @@ @use '@material/animation/animation'; @use '@material/dom/dom'; @use '@material/elevation/elevation-theme'; +@use '@material/focus-ring/focus-ring'; @use '@material/feature-targeting/feature-targeting'; @use '@material/ripple/ripple'; @use '@material/ripple/ripple-theme'; +@use '@material/rtl/rtl'; @use '@material/theme/theme-color'; @use '@material/touch-target/touch-target'; @use './chip-theme'; @@ -40,7 +42,7 @@ } @mixin without-ripple-styles($query: feature-targeting.all()) { - @include _static-styles($query); + @include static-styles($query); @include _theme-styles($query); } @@ -86,7 +88,7 @@ height: 100%; width: 100%; top: 0; - /* @noflip */ + @include rtl.ignore-next-line(); left: 0; } } @@ -99,7 +101,7 @@ } } -@mixin _static-styles($query: feature-targeting.all()) { +@mixin static-styles($query: feature-targeting.all()) { $feat-animation: feature-targeting.create-target($query, animation); $feat-color: feature-targeting.create-target($query, color); $feat-structure: feature-targeting.create-target($query, structure); @@ -181,6 +183,7 @@ .mdc-evolution-chip__action--trailing { @include feature-targeting.targets($feat-structure) { position: relative; + overflow: visible; } } @@ -235,7 +238,7 @@ position: absolute; opacity: 0; top: 50%; - /* @noflip */ + @include rtl.ignore-next-line(); left: 50%; } } @@ -282,6 +285,7 @@ .mdc-evolution-chip__checkmark { @include feature-targeting.targets($feat-animation) { transition: animation.standard(transform, 150ms); + @include rtl.ignore-next-line(); transform: translate(-75%, -50%); } } @@ -304,6 +308,7 @@ @include feature-targeting.targets($feat-animation) { transition: animation.linear(opacity, 50ms), animation.standard(transform, 100ms); + @include rtl.ignore-next-line(); transform: translate(-75%, -50%); } } @@ -341,6 +346,7 @@ @include feature-targeting.targets($feat-animation) { transition: animation.standard(opacity, 75ms); // Ensure the checkmark stays statically positioned + @include rtl.ignore-next-line(); transform: translate(-50%, -50%); } } @@ -362,6 +368,7 @@ .mdc-evolution-chip__checkmark { @include feature-targeting.targets($feat-structure) { + @include rtl.ignore-next-line(); transform: translate(-50%, -50%); opacity: 1; } @@ -425,84 +432,103 @@ $feat-structure: feature-targeting.create-target($query, structure); @include dom.forced-colors-mode { - @include chip-theme.outline-color( - ( - disabled: GrayText, - ), - $query: $query - ); - - @include chip-theme.text-label-color( - ( - disabled: GrayText, - ), - $query: $query - ); - - @include chip-theme.icon-color( - ( - disabled: GrayText, - ), - $query: $query - ); - - @include chip-theme.checkmark-color( - ( - disabled: GrayText, - ), - $query: $query - ); - - @include chip-theme.trailing-action-color( - ( - disabled: GrayText, - ), - $query: $query - ); + &.mdc-evolution-chip { + @include chip-theme.outline-color( + ( + default: CanvasText, + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.selected-outline-color( + ( + default: CanvasText, + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.text-label-color( + ( + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.selected-text-label-color( + ( + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.icon-color( + ( + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.checkmark-color( + ( + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.trailing-action-color( + ( + disabled: GrayText, + ), + $query: $query + ); + + @include chip-theme.container-color( + ( + disabled: Canvas, + ), + $query: $query + ); + + @include chip-theme.selected-container-color( + ( + disabled: Canvas, + ), + $query: $query + ); + } } /// /// Renders a transparent border on focus in Windows high-contrast mode. /// - .mdc-evolution-chip__action:after { - @include feature-targeting.targets($feat-structure) { - position: absolute; - content: ''; - border: 0px solid transparent; - pointer-events: none; - box-sizing: border-box; - z-index: 1; // Position above touch target - } + .mdc-evolution-chip__focus-ring { + display: none; } - .mdc-evolution-chip__action:not(.mdc-evolution-chip__action--presentational) { + .mdc-evolution-chip__action--primary:not(.mdc-evolution-chip__action--presentational) { @include ripple-theme.focus() { - &:after { - @include feature-targeting.targets($feat-structure) { - border-width: 5px; - border-style: double; - } + .mdc-evolution-chip__focus-ring { + z-index: 1; + display: block; + @include focus-ring.focus-ring($query: $query); } } } - .mdc-evolution-chip__action--primary:after { - @include feature-targeting.targets($feat-structure) { - height: 100%; - width: 100%; - top: 50%; - /* @noflip */ - left: 50%; - transform: translate(-50%, -50%); - } - } - - .mdc-evolution-chip__action--trailing:after { - @include feature-targeting.targets($feat-structure) { - top: 50%; - transform: translateY(-50%); - border-radius: 50%; + .mdc-evolution-chip__action--trailing:not(.mdc-evolution-chip__action--presentational) { + @include ripple-theme.focus() { + .mdc-evolution-chip__focus-ring { + z-index: 1; + display: block; + @include focus-ring.focus-ring( + $container-outer-padding-vertical: 2px, + $container-outer-padding-horizontal: -2px, + $query: $query + ); + } } } } diff --git a/packages/mdc-chips/_filter-chip-theme.scss b/packages/mdc-chips/_filter-chip-theme.scss new file mode 100644 index 00000000000..6ef639d7046 --- /dev/null +++ b/packages/mdc-chips/_filter-chip-theme.scss @@ -0,0 +1,121 @@ +// +// Copyright 2021 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@use 'sass:map'; +@use '@material/theme/theme'; +@use '@material/theme/keys'; +@use '@material/tokens/resolvers'; +@use './chip-theme'; + +$_light-theme: ( + container-shape: null, + container-height: null, + elevated-container-shadow-color: null, + flat-container-elevation: null, + flat-unselected-outline-color: null, + flat-unselected-outline-width: null, + flat-selected-container-color: null, + flat-selected-outline-color: null, + flat-selected-outline-width: null, + elevated-container-elevation: null, + elevated-unselected-container-color: null, + elevated-selected-container-color: null, + label-text-font: null, + label-text-line-height: null, + label-text-size: null, + label-text-weight: null, + label-text-tracking: null, + unselected-label-text-color: null, + selected-label-text-color: null, + with-icon-icon-size: null, + with-icon-unselected-icon-color: null, + with-icon-selected-icon-color: null, + disabled-label-text-color: null, + disabled-label-text-opacity: null, + flat-disabled-unselected-outline-color: null, + flat-disabled-unselected-outline-opacity: null, + flat-disabled-selected-outline-color: null, + flat-disabled-selected-container-color: null, + flat-disabled-selected-container-opacity: null, + flat-disabled-selected-outline-opacity: null, + with-icon-disabled-icon-color: null, + with-icon-disabled-icon-opacity: null, + elevated-disabled-container-elevation: null, + elevated-disabled-container-color: null, + elevated-disabled-container-opacity: null, + unselected-hover-state-layer-color: null, + unselected-hover-state-layer-opacity: null, + unselected-hover-label-text-color: null, + selected-hover-state-layer-color: null, + selected-hover-state-layer-opacity: null, + selected-hover-label-text-color: null, + with-icon-unselected-hover-icon-color: null, + with-icon-selected-hover-icon-color: null, + elevated-hover-container-elevation: null, + flat-selected-hover-container-elevation: null, + flat-unselected-hover-container-elevation: null, + unselected-focus-state-layer-color: null, + unselected-focus-state-layer-opacity: null, + unselected-focus-label-text-color: null, + selected-focus-state-layer-color: null, + selected-focus-state-layer-opacity: null, + selected-focus-label-text-color: null, + flat-unselected-focus-outline-color: null, + with-icon-unselected-focus-icon-color: null, + with-icon-selected-focus-icon-color: null, + elevated-focus-container-elevation: null, + elevated-pressed-container-elevation: null, + flat-selected-focus-container-elevation: null, + flat-unselected-focus-container-elevation: null, + unselected-pressed-state-layer-color: null, + unselected-pressed-state-layer-opacity: null, + unselected-pressed-label-text-color: null, + selected-pressed-state-layer-color: null, + selected-pressed-state-layer-opacity: null, + selected-pressed-label-text-color: null, + with-icon-unselected-pressed-icon-color: null, + with-icon-selected-pressed-icon-color: null, + elevated-selected-container-elevation: null, + flat-selected-pressed-container-elevation: null, + flat-unselected-pressed-container-elevation: null, + container-surface-tint-layer-color: null, +); + +$_custom-property-prefix: 'filter-chip'; + +@mixin theme($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme($_light-theme, $theme); + $theme: chip-theme.resolve-theme($theme, $resolvers: $resolvers); + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +@mixin theme-styles($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme-styles($_light-theme, $theme); + $theme: keys.create-theme-properties( + $theme, + $prefix: $_custom-property-prefix + ); + @include chip-theme.theme-styles($theme, $resolvers: $resolvers); +} diff --git a/packages/mdc-chips/_input-chip-theme.scss b/packages/mdc-chips/_input-chip-theme.scss new file mode 100644 index 00000000000..f6ad833cde3 --- /dev/null +++ b/packages/mdc-chips/_input-chip-theme.scss @@ -0,0 +1,94 @@ +// +// Copyright 2021 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@use 'sass:map'; +@use '@material/theme/theme'; +@use '@material/theme/keys'; +@use '@material/tokens/resolvers'; +@use './chip-theme'; + +$_light-theme: ( + container-shape: null, + container-height: null, + container-elevation: null, + outline-color: null, + outline-width: null, + container-shadow-color: null, + label-text-font: null, + label-text-line-height: null, + label-text-size: null, + label-text-weight: null, + label-text-tracking: null, + label-text-color: null, + with-trailing-icon-trailing-icon-size: null, + with-trailing-icon-trailing-icon-color: null, + with-leading-icon-leading-icon-size: null, + with-leading-icon-leading-icon-color: null, + with-avatar-avatar-size: null, + with-avatar-avatar-shape: null, + disabled-outline-color: null, + disabled-outline-opacity: null, + disabled-label-text-color: null, + disabled-label-text-opacity: null, + with-trailing-icon-disabled-trailing-icon-color: null, + with-trailing-icon-disabled-trailing-icon-opacity: null, + with-leading-icon-disabled-leading-icon-color: null, + with-leading-icon-disabled-leading-icon-opacity: null, + with-avatar-disabled-avatar-opacity: null, + hover-state-layer-color: null, + hover-state-layer-opacity: null, + hover-label-text-color: null, + with-trailing-icon-hover-trailing-icon-color: null, + with-leading-icon-hover-leading-icon-color: null, + focus-state-layer-color: null, + focus-state-layer-opacity: null, + focus-label-text-color: null, + focus-outline-color: null, + with-trailing-icon-focus-trailing-icon-color: null, + with-leading-icon-focus-leading-icon-color: null, + pressed-state-layer-color: null, + pressed-state-layer-opacity: null, + pressed-label-text-color: null, + with-trailing-icon-pressed-trailing-icon-color: null, + with-leading-icon-pressed-leading-icon-color: null, + container-surface-tint-layer-color: null, +); + +$_custom-property-prefix: 'input-chip'; + +@mixin theme($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme($_light-theme, $theme); + $theme: chip-theme.resolve-theme($theme, $resolvers: $resolvers); + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +@mixin theme-styles($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme-styles($_light-theme, $theme); + $theme: keys.create-theme-properties( + $theme, + $prefix: $_custom-property-prefix + ); + @include chip-theme.theme-styles($theme, $resolvers: $resolvers); +} diff --git a/packages/mdc-chips/_suggestion-chip-theme.scss b/packages/mdc-chips/_suggestion-chip-theme.scss new file mode 100644 index 00000000000..1de61878228 --- /dev/null +++ b/packages/mdc-chips/_suggestion-chip-theme.scss @@ -0,0 +1,93 @@ +// +// Copyright 2021 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@use 'sass:map'; +@use '@material/theme/theme'; +@use '@material/theme/keys'; +@use '@material/tokens/resolvers'; +@use './chip-theme'; + +$_light-theme: ( + container-shape: null, + container-height: null, + flat-container-elevation: null, + flat-outline-color: null, + flat-outline-width: null, + elevated-container-elevation: null, + elevated-container-color: null, + elevated-container-shadow-color: null, + label-text-font: null, + label-text-line-height: null, + label-text-size: null, + label-text-weight: null, + label-text-tracking: null, + label-text-type: null, + label-text-color: null, + flat-disabled-outline-color: null, + flat-disabled-outline-opacity: null, + disabled-label-text-color: null, + disabled-label-text-opacity: null, + elevated-disabled-container-elevation: null, + elevated-disabled-container-color: null, + elevated-disabled-container-opacity: null, + hover-state-layer-color: null, + hover-state-layer-opacity: null, + hover-label-text-color: null, + elevated-hover-container-elevation: null, + focus-state-layer-color: null, + focus-state-layer-opacity: null, + focus-label-text-color: null, + flat-focus-outline-color: null, + elevated-focus-container-elevation: null, + pressed-state-layer-color: null, + pressed-state-layer-opacity: null, + pressed-label-text-color: null, + elevated-pressed-container-elevation: null, + container-surface-tint-layer-color: null, + with-leading-icon-disabled-leading-icon-color: null, + with-leading-icon-disabled-leading-icon-opacity: null, + with-leading-icon-focus-leading-icon-color: null, + with-leading-icon-hover-leading-icon-color: null, + with-leading-icon-leading-icon-color: null, + with-leading-icon-leading-icon-size: null, + with-leading-icon-pressed-leading-icon-color: null, +); + +$_custom-property-prefix: 'suggestion-chip'; + +@mixin theme($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme($_light-theme, $theme); + $theme: chip-theme.resolve-theme($theme, $resolvers: $resolvers); + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +@mixin theme-styles($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme-styles($_light-theme, $theme); + $theme: keys.create-theme-properties( + $theme, + $prefix: $_custom-property-prefix + ); + @include chip-theme.theme-styles($theme, $resolvers: $resolvers); +} diff --git a/packages/mdc-chips/action/README.md b/packages/mdc-chips/action/README.md index 3b93c521003..dd8efff1b8e 100644 --- a/packages/mdc-chips/action/README.md +++ b/packages/mdc-chips/action/README.md @@ -118,7 +118,7 @@ All Sass mixins for actions are provided by the chip Sass. The `MDCChipAction` is exposed only to be called by the parent [`MDCChip`](../chip). Users should not interact with the `MDCChipAction` component nor rely on any exposed APIs or events. -### `MDCChipAction` events +### `MDCChipActionEvents` These events are only emitted for consumption by the parent [`MDCChip`](../chip). Non-wrapping clients **should not** listen to these events. @@ -131,12 +131,12 @@ Event name | Detail | Description Method Signature | Description --- | --- -`emitEvent(eventName: Events, eventDetail: D): void` | Emits the given `eventName` with the given `eventDetail`. +`emitEvent(eventName: MDCChipActionEvents, eventDetail: D): void` | Emits the given `eventName` with the given `eventDetail`. `focus(): void` | Focuses the action root. -`getAttribute(attr: Attributes): string\|null` | Returns the attribute on the action root or `null` if none exists. +`getAttribute(attr: MDCChipActionAttributes): string\|null` | Returns the attribute on the action root or `null` if none exists. `getElementID(): string` | Returns the `[id](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id)` of the action root. -`removeAttribute(attr: Attributes): void` | Removes the attribute from the action root. -`setAttribute(attr: Attributes, value: string): void` | Sets the action root attribute to the given `value` +`removeAttribute(attr: MDCChipActionAttributes): void` | Removes the attribute from the action root. +`setAttribute(attr: MDCChipActionAttributes, value: string): void` | Sets the action root attribute to the given `value` ### `MDCChipActionFoundation` @@ -148,11 +148,11 @@ Method Signature | Description `handleKeydown(event: KeyboardEvent): void` | Handles the [keydown](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event) event. `setDisabled(isDisabled: boolean): void` | Sets the disabled state. `isDisabled(): boolean` | Returns the disabled state. -`setFocus(behavior: FocusBehavior): void` | Set the focus behavior. +`setFocus(behavior: MDCChipActionFocusBehavior): void` | Set the focus behavior. `isFocusable(): boolean` | Returns whether the action if focusable. `setSelected(isSelected: boolean): void` | Sets the selected state. `isSelected(): boolean` | Returns the selected state. -`abstract actionType(): ActionType` | Returns the type of the action. +`abstract actionType(): MDCChipActionType` | Returns the type of the action. `abstract isSelectable(): boolean` | Returns whether the action is selectable. #### Subclasses diff --git a/packages/mdc-chips/action/adapter.ts b/packages/mdc-chips/action/adapter.ts index 455a1bf9149..c98178ed5fd 100644 --- a/packages/mdc-chips/action/adapter.ts +++ b/packages/mdc-chips/action/adapter.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {Attributes, Events} from './constants'; +import {MDCChipActionAttributes, MDCChipActionEvents} from './constants'; /** * Defines the shape of the adapter expected by the foundation. @@ -31,15 +31,15 @@ import {Attributes, Events} from './constants'; * https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md */ export interface MDCChipActionAdapter { - emitEvent(eventName: Events, eventDetail: D): void; + emitEvent(name: MDCChipActionEvents, detail: D): void; focus(): void; - getAttribute(attr: Attributes): string|null; + getAttribute(attr: MDCChipActionAttributes): string|null; getElementID(): string; - removeAttribute(attr: Attributes): void; + removeAttribute(attr: MDCChipActionAttributes): void; - setAttribute(attr: Attributes, value: string): void; + setAttribute(attr: MDCChipActionAttributes, value: string): void; } diff --git a/packages/mdc-chips/action/component-ripple.ts b/packages/mdc-chips/action/component-ripple.ts index 9d5e588eb14..21db164a745 100644 --- a/packages/mdc-chips/action/component-ripple.ts +++ b/packages/mdc-chips/action/component-ripple.ts @@ -26,7 +26,7 @@ * rect and the selected width graphic style property. */ export function computePrimaryActionRippleClientRect( - clientRect: ClientRect, graphicSelectedWidthStyleValue: string): ClientRect { + clientRect: DOMRect, graphicSelectedWidthStyleValue: string): DOMRect { // parseInt is banned so we need to manually format and parse the string. const graphicWidth = Number(graphicSelectedWidthStyleValue.replace('px', '')); if (Number.isNaN(graphicWidth)) { @@ -39,8 +39,8 @@ export function computePrimaryActionRippleClientRect( top: clientRect.top, right: clientRect.right, bottom: clientRect.bottom, - left: clientRect.left, - }; + left: clientRect.left + } as any; } /** diff --git a/packages/mdc-chips/action/component.ts b/packages/mdc-chips/action/component.ts index 5091151d4e5..68f6dd791af 100644 --- a/packages/mdc-chips/action/component.ts +++ b/packages/mdc-chips/action/component.ts @@ -28,10 +28,19 @@ import {MDCRippleAdapter} from '@material/ripple/adapter'; import {MDCRipple, MDCRippleFactory} from '@material/ripple/component'; import {MDCRippleFoundation} from '@material/ripple/foundation'; import {MDCRippleCapableSurface} from '@material/ripple/types'; +import {safeAttrPrefix} from 'safevalues'; +import {setElementPrefixedAttribute} from 'safevalues/dom'; import {MDCChipActionAdapter} from './adapter'; -import {computePrimaryActionRippleClientRect, GRAPHIC_SELECTED_WIDTH_STYLE_PROP} from './component-ripple'; -import {ActionType, CssClasses, FocusBehavior} from './constants'; +import { + computePrimaryActionRippleClientRect, + GRAPHIC_SELECTED_WIDTH_STYLE_PROP, +} from './component-ripple'; +import { + MDCChipActionCssClasses, + MDCChipActionFocusBehavior, + MDCChipActionType, +} from './constants'; import {MDCChipActionFoundation} from './foundation'; import {MDCChipPrimaryActionFoundation} from './primary-foundation'; import {MDCChipTrailingActionFoundation} from './trailing-foundation'; @@ -40,20 +49,32 @@ import {MDCChipTrailingActionFoundation} from './trailing-foundation'; * MDCChipActionFactory is used by the parent MDCChip component to initialize * chip actions. */ -export type MDCChipActionFactory = - (el: Element, foundation?: MDCChipActionFoundation) => MDCChipAction; +export type MDCChipActionFactory = ( + el: HTMLElement, + foundation?: MDCChipActionFoundation, +) => MDCChipAction; + +const ALLOWED_ATTR_PREFIXES = [ + safeAttrPrefix`aria-`, + safeAttrPrefix`data-`, + safeAttrPrefix`disabled`, + safeAttrPrefix`role`, + safeAttrPrefix`tabindex`, +]; /** * MDCChipAction provides component encapsulation of the different foundation * implementations. */ -export class MDCChipAction extends - MDCComponent implements MDCRippleCapableSurface { - static attachTo(root: Element): MDCChipAction { +export class MDCChipAction + extends MDCComponent + implements MDCRippleCapableSurface +{ + static override attachTo(root: HTMLElement): MDCChipAction { return new MDCChipAction(root); } - private readonly rootHTML = this.root as HTMLElement; + private readonly rootHTML = this.root; // Assigned in #initialize() private rippleInstance!: MDCRipple; @@ -65,18 +86,21 @@ export class MDCChipAction extends return this.rippleInstance; } - initialize( - rippleFactory: MDCRippleFactory = (el, foundation) => - new MDCRipple(el, foundation)) { + override initialize( + rippleFactory: MDCRippleFactory = (el, foundation) => + new MDCRipple(el, foundation), + ) { const rippleAdapter: MDCRippleAdapter = { ...MDCRipple.createAdapter(this), computeBoundingRect: () => this.computeRippleClientRect(), }; - this.rippleInstance = - rippleFactory(this.root, new MDCRippleFoundation(rippleAdapter)); + this.rippleInstance = rippleFactory( + this.root, + new MDCRippleFoundation(rippleAdapter), + ); } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.handleClick = () => { this.foundation.handleClick(); }; @@ -89,14 +113,14 @@ export class MDCChipAction extends this.listen('keydown', this.handleKeydown); } - destroy() { + override destroy() { this.ripple.destroy(); this.unlisten('click', this.handleClick); this.unlisten('keydown', this.handleKeydown); super.destroy(); } - getDefaultFoundation() { + override getDefaultFoundation() { // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. @@ -113,11 +137,16 @@ export class MDCChipAction extends this.root.removeAttribute(name); }, setAttribute: (name, value) => { - this.root.setAttribute(name, value); + setElementPrefixedAttribute( + ALLOWED_ATTR_PREFIXES, + this.root, + name, + value, + ); }, }; - if (this.root.classList.contains(CssClasses.TRAILING_ACTION)) { + if (this.root.classList.contains(MDCChipActionCssClasses.TRAILING_ACTION)) { return new MDCChipTrailingActionFoundation(adapter); } @@ -133,7 +162,7 @@ export class MDCChipAction extends return this.foundation.isDisabled(); } - setFocus(behavior: FocusBehavior) { + setFocus(behavior: MDCChipActionFocusBehavior) { this.foundation.setFocus(behavior); } @@ -153,19 +182,25 @@ export class MDCChipAction extends return this.foundation.isSelectable(); } - actionType(): ActionType { + actionType(): MDCChipActionType { return this.foundation.actionType(); } - private computeRippleClientRect(): ClientRect { - if (this.root.classList.contains(CssClasses.PRIMARY_ACTION)) { - const chipRoot = closest(this.root, `.${CssClasses.CHIP_ROOT}`); + private computeRippleClientRect(): DOMRect { + if (this.root.classList.contains(MDCChipActionCssClasses.PRIMARY_ACTION)) { + const chipRoot = closest( + this.root, + `.${MDCChipActionCssClasses.CHIP_ROOT}`, + ); // Return the root client rect since it's better than nothing if (!chipRoot) return this.root.getBoundingClientRect(); - const graphicWidth = window.getComputedStyle(chipRoot).getPropertyValue( - GRAPHIC_SELECTED_WIDTH_STYLE_PROP); + const graphicWidth = window + .getComputedStyle(chipRoot) + .getPropertyValue(GRAPHIC_SELECTED_WIDTH_STYLE_PROP); return computePrimaryActionRippleClientRect( - chipRoot.getBoundingClientRect(), graphicWidth); + chipRoot.getBoundingClientRect(), + graphicWidth, + ); } return this.root.getBoundingClientRect(); diff --git a/packages/mdc-chips/action/constants.ts b/packages/mdc-chips/action/constants.ts index f7b56562773..3225f3a688a 100644 --- a/packages/mdc-chips/action/constants.ts +++ b/packages/mdc-chips/action/constants.ts @@ -22,19 +22,20 @@ */ /** - * CssClasses provides the classes to be queried and manipulated on the root. + * MDCChipActionCssClasses provides the classes to be queried and manipulated on + * the root. */ -export enum CssClasses { +export enum MDCChipActionCssClasses { PRIMARY_ACTION = 'mdc-evolution-chip__action--primary', TRAILING_ACTION = 'mdc-evolution-chip__action--trailing', CHIP_ROOT = 'mdc-evolution-chip', } /** - * InteractionTrigger provides detail of the different triggers for action - * interactions. + * MDCChipActionInteractionTrigger provides detail of the different triggers for + * action interactions. */ -export enum InteractionTrigger { +export enum MDCChipActionInteractionTrigger { UNSPECIFIED, // Default type CLICK, BACKSPACE_KEY, @@ -44,35 +45,36 @@ export enum InteractionTrigger { } /** - * ActionType provides the different types of available actions. + * MDCChipActionType provides the different types of available actions. */ -export enum ActionType { +export enum MDCChipActionType { UNSPECIFIED, // Default type PRIMARY, TRAILING, } /** - * Events provides the different events emitted by the action. + * MDCChipActionEvents provides the different events emitted by the action. */ -export enum Events { +export enum MDCChipActionEvents { INTERACTION = 'MDCChipAction:interaction', NAVIGATION = 'MDCChipAction:navigation', } /** - * FocusBehavior provides configurations for focusing or unfocusing an action. + * MDCChipActionFocusBehavior provides configurations for focusing or unfocusing + * an action. */ -export enum FocusBehavior { +export enum MDCChipActionFocusBehavior { FOCUSABLE, FOCUSABLE_AND_FOCUSED, NOT_FOCUSABLE, } /** - * Attributes provides the HTML attributes used by the foundation. + * MDCChipActionAttributes provides the HTML attributes used by the foundation. */ -export enum Attributes { +export enum MDCChipActionAttributes { ARIA_DISABLED = 'aria-disabled', ARIA_HIDDEN = 'aria-hidden', ARIA_SELECTED = 'aria-selected', diff --git a/packages/mdc-chips/action/foundation.ts b/packages/mdc-chips/action/foundation.ts index c94658103b2..42f3a5b9e9a 100644 --- a/packages/mdc-chips/action/foundation.ts +++ b/packages/mdc-chips/action/foundation.ts @@ -25,14 +25,14 @@ import {MDCFoundation} from '@material/base/foundation'; import {isNavigationEvent, KEY, normalizeKey} from '@material/dom/keyboard'; import {MDCChipActionAdapter} from './adapter'; -import {ActionType, Attributes, Events, FocusBehavior, InteractionTrigger} from './constants'; +import {MDCChipActionAttributes, MDCChipActionEvents, MDCChipActionFocusBehavior, MDCChipActionInteractionTrigger, MDCChipActionType} from './constants'; import {MDCChipActionInteractionEventDetail, MDCChipActionNavigationEventDetail} from './types'; -const triggerMap = new Map(); -triggerMap.set(KEY.SPACEBAR, InteractionTrigger.SPACEBAR_KEY); -triggerMap.set(KEY.ENTER, InteractionTrigger.ENTER_KEY); -triggerMap.set(KEY.DELETE, InteractionTrigger.DELETE_KEY); -triggerMap.set(KEY.BACKSPACE, InteractionTrigger.BACKSPACE_KEY); +const triggerMap = new Map(); +triggerMap.set(KEY.SPACEBAR, MDCChipActionInteractionTrigger.SPACEBAR_KEY); +triggerMap.set(KEY.ENTER, MDCChipActionInteractionTrigger.ENTER_KEY); +triggerMap.set(KEY.DELETE, MDCChipActionInteractionTrigger.DELETE_KEY); +triggerMap.set(KEY.BACKSPACE, MDCChipActionInteractionTrigger.BACKSPACE_KEY); /** @@ -41,7 +41,7 @@ triggerMap.set(KEY.BACKSPACE, InteractionTrigger.BACKSPACE_KEY); */ export abstract class MDCChipActionFoundation extends MDCFoundation { - static get defaultAdapter(): MDCChipActionAdapter { + static override get defaultAdapter(): MDCChipActionAdapter { return { emitEvent: () => undefined, focus: () => undefined, @@ -60,7 +60,7 @@ export abstract class MDCChipActionFoundation extends // Early exit for cases where the click comes from a source other than the // user's pointer (i.e. programmatic click from AT). if (this.isDisabled()) return; - this.emitInteraction(InteractionTrigger.CLICK); + this.emitInteraction(MDCChipActionInteractionTrigger.CLICK); } handleKeydown(event: KeyboardEvent) { @@ -76,56 +76,61 @@ export abstract class MDCChipActionFoundation extends this.emitNavigation(key); return; } + + // signal to propagate the event since this Key isn't handled by chip + return true; } setDisabled(isDisabled: boolean) { // Use `aria-disabled` for the selectable (listbox) disabled state if (this.isSelectable()) { - this.adapter.setAttribute(Attributes.ARIA_DISABLED, `${isDisabled}`); + this.adapter.setAttribute( + MDCChipActionAttributes.ARIA_DISABLED, `${isDisabled}`); return; } if (isDisabled) { - this.adapter.setAttribute(Attributes.DISABLED, 'true'); + this.adapter.setAttribute(MDCChipActionAttributes.DISABLED, 'true'); } else { - this.adapter.removeAttribute(Attributes.DISABLED); + this.adapter.removeAttribute(MDCChipActionAttributes.DISABLED); } } isDisabled(): boolean { - if (this.adapter.getAttribute(Attributes.ARIA_DISABLED) === 'true') { + if (this.adapter.getAttribute(MDCChipActionAttributes.ARIA_DISABLED) === + 'true') { return true; } - if (this.adapter.getAttribute(Attributes.DISABLED) !== null) { + if (this.adapter.getAttribute(MDCChipActionAttributes.DISABLED) !== null) { return true; } return false; } - setFocus(behavior: FocusBehavior) { + setFocus(behavior: MDCChipActionFocusBehavior) { // Early exit if not focusable if (!this.isFocusable()) { return; } // Add it to the tab order and give focus - if (behavior === FocusBehavior.FOCUSABLE_AND_FOCUSED) { - this.adapter.setAttribute(Attributes.TAB_INDEX, '0'); + if (behavior === MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED) { + this.adapter.setAttribute(MDCChipActionAttributes.TAB_INDEX, '0'); this.adapter.focus(); return; } // Add to the tab order - if (behavior === FocusBehavior.FOCUSABLE) { - this.adapter.setAttribute(Attributes.TAB_INDEX, '0'); + if (behavior === MDCChipActionFocusBehavior.FOCUSABLE) { + this.adapter.setAttribute(MDCChipActionAttributes.TAB_INDEX, '0'); return; } // Remove it from the tab order - if (behavior === FocusBehavior.NOT_FOCUSABLE) { - this.adapter.setAttribute(Attributes.TAB_INDEX, '-1'); + if (behavior === MDCChipActionFocusBehavior.NOT_FOCUSABLE) { + this.adapter.setAttribute(MDCChipActionAttributes.TAB_INDEX, '-1'); return; } } @@ -135,7 +140,8 @@ export abstract class MDCChipActionFoundation extends return false; } - if (this.adapter.getAttribute(Attributes.ARIA_HIDDEN) === 'true') { + if (this.adapter.getAttribute(MDCChipActionAttributes.ARIA_HIDDEN) === + 'true') { return false; } @@ -148,16 +154,18 @@ export abstract class MDCChipActionFoundation extends return; } - this.adapter.setAttribute(Attributes.ARIA_SELECTED, `${isSelected}`); + this.adapter.setAttribute( + MDCChipActionAttributes.ARIA_SELECTED, `${isSelected}`); } isSelected(): boolean { - return this.adapter.getAttribute(Attributes.ARIA_SELECTED) === 'true'; + return this.adapter.getAttribute(MDCChipActionAttributes.ARIA_SELECTED) === + 'true'; } - private emitInteraction(trigger: InteractionTrigger) { + private emitInteraction(trigger: MDCChipActionInteractionTrigger) { this.adapter.emitEvent( - Events.INTERACTION, { + MDCChipActionEvents.INTERACTION, { actionID: this.adapter.getElementID(), source: this.actionType(), trigger, @@ -166,7 +174,7 @@ export abstract class MDCChipActionFoundation extends private emitNavigation(key: string) { this.adapter.emitEvent( - Events.NAVIGATION, { + MDCChipActionEvents.NAVIGATION, { source: this.actionType(), key, }); @@ -187,17 +195,17 @@ export abstract class MDCChipActionFoundation extends return false; } - private getTriggerFromKey(key: string): InteractionTrigger { + private getTriggerFromKey(key: string): MDCChipActionInteractionTrigger { const trigger = triggerMap.get(key); if (trigger) { return trigger; } // Default case, should ideally never be returned - return InteractionTrigger.UNSPECIFIED; + return MDCChipActionInteractionTrigger.UNSPECIFIED; } - abstract actionType(): ActionType; + abstract actionType(): MDCChipActionType; abstract isSelectable(): boolean; diff --git a/packages/mdc-chips/action/index.ts b/packages/mdc-chips/action/index.ts index de3f4d5024f..1e0b2fda35c 100644 --- a/packages/mdc-chips/action/index.ts +++ b/packages/mdc-chips/action/index.ts @@ -27,3 +27,4 @@ export * from './constants'; export * from './foundation'; export * from './primary-foundation'; export * from './trailing-foundation'; +export * from './types'; diff --git a/packages/mdc-chips/action/primary-foundation.ts b/packages/mdc-chips/action/primary-foundation.ts index b537a64ef39..5a73366a851 100644 --- a/packages/mdc-chips/action/primary-foundation.ts +++ b/packages/mdc-chips/action/primary-foundation.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {ActionType, Attributes} from './constants'; +import {MDCChipActionAttributes, MDCChipActionType} from './constants'; import {MDCChipActionFoundation} from './foundation'; /** @@ -30,15 +30,16 @@ import {MDCChipActionFoundation} from './foundation'; */ export class MDCChipPrimaryActionFoundation extends MDCChipActionFoundation { isSelectable() { - return this.adapter.getAttribute(Attributes.ROLE) === 'option'; + return this.adapter.getAttribute(MDCChipActionAttributes.ROLE) === 'option'; } actionType() { - return ActionType.PRIMARY; + return MDCChipActionType.PRIMARY; } protected shouldEmitInteractionOnRemoveKey() { - return this.adapter.getAttribute(Attributes.DATA_DELETABLE) === 'true'; + return this.adapter.getAttribute(MDCChipActionAttributes.DATA_DELETABLE) === + 'true'; } } diff --git a/packages/mdc-chips/action/test/component-ripple.test.ts b/packages/mdc-chips/action/test/component-ripple.test.ts index 83aa0a98415..969c32a36f2 100644 --- a/packages/mdc-chips/action/test/component-ripple.test.ts +++ b/packages/mdc-chips/action/test/component-ripple.test.ts @@ -33,7 +33,7 @@ describe('ripple', () => { left: 10, bottom: 42, right: 110, - }; + } as any; expect(computePrimaryActionRippleClientRect(rect, '')).toEqual(rect); }); @@ -47,7 +47,7 @@ describe('ripple', () => { left: 10, bottom: 42, right: 110, - }; + } as any; expect(computePrimaryActionRippleClientRect(rect, 'xyz')) .toEqual(rect); @@ -61,7 +61,7 @@ describe('ripple', () => { left: 10, bottom: 42, right: 110, - }; + } as any; expect(computePrimaryActionRippleClientRect(rect, '20YYpx')) .toEqual(rect); @@ -75,7 +75,7 @@ describe('ripple', () => { left: 10, bottom: 42, right: 110, - }; + } as any; expect(computePrimaryActionRippleClientRect(rect, '20px')).toEqual({ ...rect, diff --git a/packages/mdc-chips/action/test/component.test.ts b/packages/mdc-chips/action/test/component.test.ts index 8dba26d0dd4..9af7afee2b5 100644 --- a/packages/mdc-chips/action/test/component.test.ts +++ b/packages/mdc-chips/action/test/component.test.ts @@ -22,8 +22,9 @@ */ +import {createFixture, html} from '../../../../testing/dom'; import {createKeyboardEvent, emitEvent} from '../../../../testing/dom/events'; -import {ActionType, Attributes, Events, FocusBehavior, MDCChipAction} from '../index'; +import {MDCChipAction, MDCChipActionAttributes, MDCChipActionEvents, MDCChipActionFocusBehavior, MDCChipActionType} from '../index'; interface TestOptions { readonly isDisabled?: boolean; @@ -38,16 +39,12 @@ function getFixture({ isFocusable, isSelectable, }: TestOptions): HTMLButtonElement { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html` `; - const el = wrapper.firstElementChild as HTMLButtonElement; - wrapper.removeChild(el); - return el; + ${isDisabled ? 'disabled' : ''}>Label `); } function setupTest(options: TestOptions = { @@ -108,7 +105,7 @@ describe('MDCChipAction', () => { expect(component.isDisabled()).toBeFalse(); }); - it(`#setFocus(${FocusBehavior.FOCUSABLE_AND_FOCUSED}) gives` + + it(`#setFocus(${MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED}) gives` + ` focus to the root`, () => { const {component, root} = setupTest({ @@ -116,31 +113,31 @@ describe('MDCChipAction', () => { }); document.body.appendChild(root); - component.setFocus(FocusBehavior.FOCUSABLE_AND_FOCUSED); + component.setFocus(MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); expect(root.getAttribute('tabindex')).toBe('0'); expect(document.activeElement).toBe(root); document.body.removeChild(root); }); - it(`#setFocus(${FocusBehavior.FOCUSABLE}) makes the` + + it(`#setFocus(${MDCChipActionFocusBehavior.FOCUSABLE}) makes the` + ` root focusable`, () => { const {component, root} = setupTest({ isFocusable: true, }); - component.setFocus(FocusBehavior.FOCUSABLE); + component.setFocus(MDCChipActionFocusBehavior.FOCUSABLE); expect(root.getAttribute('tabindex')).toBe('0'); }); - it(`#setFocus(${FocusBehavior.NOT_FOCUSABLE}) makes the` + + it(`#setFocus(${MDCChipActionFocusBehavior.NOT_FOCUSABLE}) makes the` + ` root unfocusable`, () => { const {component, root} = setupTest({ isFocusable: true, }); - component.setFocus(FocusBehavior.NOT_FOCUSABLE); + component.setFocus(MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(root.getAttribute('tabindex')).toBe('-1'); }); @@ -165,7 +162,8 @@ describe('MDCChipAction', () => { }); component.setSelected(true); - expect(root.getAttribute(Attributes.ARIA_SELECTED)).toBe('true'); + expect(root.getAttribute(MDCChipActionAttributes.ARIA_SELECTED)) + .toBe('true'); }); it('#setSelected(false) updates the selected state when selectable', () => { @@ -175,7 +173,8 @@ describe('MDCChipAction', () => { }); component.setSelected(false); - expect(root.getAttribute(Attributes.ARIA_SELECTED)).toBe('false'); + expect(root.getAttribute(MDCChipActionAttributes.ARIA_SELECTED)) + .toBe('false'); }); it('#isSelected() returns true when selected', () => { @@ -215,66 +214,70 @@ describe('MDCChipAction', () => { expect(component.isSelectable()).toBe(false); }); - it(`#actionType() returns ${ActionType.TRAILING} for trailing action`, () => { - const {component} = setupTest({ - isTrailing: true, - isFocusable: true, - }); + it(`#actionType() returns ${MDCChipActionType.TRAILING} for trailing action`, + () => { + const {component} = setupTest({ + isTrailing: true, + isFocusable: true, + }); - expect(component.actionType()).toBe(ActionType.TRAILING); - }); + expect(component.actionType()).toBe(MDCChipActionType.TRAILING); + }); - it(`#actionType() returns ${ActionType.PRIMARY} for primary action`, () => { - const {component} = setupTest({ - isFocusable: true, - }); + it(`#actionType() returns ${MDCChipActionType.PRIMARY} for primary action`, + () => { + const {component} = setupTest({ + isFocusable: true, + }); - expect(component.actionType()).toBe(ActionType.PRIMARY); - }); + expect(component.actionType()).toBe(MDCChipActionType.PRIMARY); + }); - it(`click on root emits ${Events.INTERACTION}`, () => { + it(`click on root emits ${MDCChipActionEvents.INTERACTION}`, () => { const {root, component} = setupTest(); const handler = jasmine.createSpy('emitInteractionHandler'); - component.listen(Events.INTERACTION, handler); + component.listen(MDCChipActionEvents.INTERACTION, handler); emitEvent(root, 'click', {bubbles: true}); - component.unlisten(Events.INTERACTION, handler); + component.unlisten(MDCChipActionEvents.INTERACTION, handler); expect(handler).toHaveBeenCalled(); }); - it(`click on root does not emit ${Events.INTERACTION} when disabled`, () => { - const {root, component} = setupTest({ - isDisabled: true, - }); + it(`click on root does not emit ${ + MDCChipActionEvents.INTERACTION} when disabled`, + () => { + const {root, component} = setupTest({ + isDisabled: true, + }); - const handler = jasmine.createSpy('emitInteractionHandler'); - component.listen(Events.INTERACTION, handler); - emitEvent(root, 'click', {bubbles: true}); - component.unlisten(Events.INTERACTION, handler); - expect(handler).not.toHaveBeenCalled(); - }); + const handler = jasmine.createSpy('emitInteractionHandler'); + component.listen(MDCChipActionEvents.INTERACTION, handler); + emitEvent(root, 'click', {bubbles: true}); + component.unlisten(MDCChipActionEvents.INTERACTION, handler); + expect(handler).not.toHaveBeenCalled(); + }); - it(`keydown on root emits ${Events.INTERACTION}`, () => { + it(`keydown on root emits ${MDCChipActionEvents.INTERACTION}`, () => { const {root, component} = setupTest(); const handler = jasmine.createSpy('emitInteractionHandler'); - component.listen(Events.INTERACTION, handler); + component.listen(MDCChipActionEvents.INTERACTION, handler); root.dispatchEvent(createKeyboardEvent('keydown', { key: 'Enter', })); - component.unlisten(Events.INTERACTION, handler); + component.unlisten(MDCChipActionEvents.INTERACTION, handler); expect(handler).toHaveBeenCalled(); }); - it(`keydown on root emits ${Events.NAVIGATION}`, () => { + it(`keydown on root emits ${MDCChipActionEvents.NAVIGATION}`, () => { const {root, component} = setupTest(); const handler = jasmine.createSpy('emitInteractionHandler'); - component.listen(Events.NAVIGATION, handler); + component.listen(MDCChipActionEvents.NAVIGATION, handler); root.dispatchEvent(createKeyboardEvent('keydown', { key: 'ArrowLeft', })); - component.unlisten(Events.NAVIGATION, handler); + component.unlisten(MDCChipActionEvents.NAVIGATION, handler); expect(handler).toHaveBeenCalled(); }); @@ -283,8 +286,8 @@ describe('MDCChipAction', () => { component.destroy(); const handler = jasmine.createSpy('handler'); - component.listen(Events.INTERACTION, handler); - component.listen(Events.NAVIGATION, handler); + component.listen(MDCChipActionEvents.INTERACTION, handler); + component.listen(MDCChipActionEvents.NAVIGATION, handler); emitEvent(root, 'click', {bubbles: true}); root.dispatchEvent(createKeyboardEvent('keydown', { key: 'Spacebar', diff --git a/packages/mdc-chips/action/test/foundation.test.ts b/packages/mdc-chips/action/test/foundation.test.ts index af8fe2f0e9f..2d34e76cb4d 100644 --- a/packages/mdc-chips/action/test/foundation.test.ts +++ b/packages/mdc-chips/action/test/foundation.test.ts @@ -22,12 +22,12 @@ */ import {setUpFoundationTest} from '../../../../testing/helpers/setup'; -import {ActionType, Attributes, Events, FocusBehavior, InteractionTrigger} from '../constants'; +import {MDCChipActionAttributes, MDCChipActionEvents, MDCChipActionFocusBehavior, MDCChipActionInteractionTrigger, MDCChipActionType} from '../constants'; import {MDCChipActionFoundation} from '../foundation'; class SelectableMDCChipActionFoundation extends MDCChipActionFoundation { actionType() { - return ActionType.UNSPECIFIED; + return MDCChipActionType.UNSPECIFIED; } isSelectable() { @@ -41,7 +41,7 @@ class SelectableMDCChipActionFoundation extends MDCChipActionFoundation { class NonselectableMDCChipActionFoundation extends MDCChipActionFoundation { actionType() { - return ActionType.UNSPECIFIED; + return MDCChipActionType.UNSPECIFIED; } isSelectable() { @@ -56,7 +56,7 @@ class NonselectableMDCChipActionFoundation extends MDCChipActionFoundation { class SelectableDeletableMDCChipActionFoundation extends MDCChipActionFoundation { actionType() { - return ActionType.UNSPECIFIED; + return MDCChipActionType.UNSPECIFIED; } isSelectable() { @@ -76,105 +76,133 @@ describe('MDCChipActionFoundation', () => { return {foundation, mockAdapter}; }; - it(`#actionType returns "${ActionType.UNSPECIFIED}"`, () => { + it(`#actionType returns "${MDCChipActionType.UNSPECIFIED}"`, () => { const {foundation} = setupTest(); - expect(foundation.actionType()).toBe(ActionType.UNSPECIFIED); + expect(foundation.actionType()).toBe(MDCChipActionType.UNSPECIFIED); }); it('#setFocus() does nothing when aria-hidden == true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_HIDDEN) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_HIDDEN) .and.returnValue('true'); - foundation.setFocus(FocusBehavior.FOCUSABLE_AND_FOCUSED); - foundation.setFocus(FocusBehavior.FOCUSABLE); - foundation.setFocus(FocusBehavior.NOT_FOCUSABLE); + foundation.setFocus(MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); + foundation.setFocus(MDCChipActionFocusBehavior.FOCUSABLE); + foundation.setFocus(MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setAttribute).not.toHaveBeenCalled(); expect(mockAdapter.focus).not.toHaveBeenCalled(); }); - it(`#setFocus(${FocusBehavior.FOCUSABLE_AND_FOCUSED}) sets tabindex="0"` + + it(`#setFocus(${ + MDCChipActionFocusBehavior + .FOCUSABLE_AND_FOCUSED}) sets tabindex="0"` + ` and focuses the root`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setFocus(FocusBehavior.FOCUSABLE_AND_FOCUSED); + foundation.setFocus(MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); + // TODO: Wait until b/208710526 is fixed, then remove this + // autogenerated error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type + // '"tabindex"' is not assignable to parameter of type + // 'Expected'. expect(mockAdapter.setAttribute).toHaveBeenCalledWith('tabindex', '0'); expect(mockAdapter.focus).toHaveBeenCalled(); }); - it(`#setFocus(${FocusBehavior.FOCUSABLE}) sets tabindex="0"`, () => { - const {foundation, mockAdapter} = setupTest(); - foundation.setFocus(FocusBehavior.FOCUSABLE); - expect(mockAdapter.setAttribute).toHaveBeenCalledWith('tabindex', '0'); - expect(mockAdapter.focus).not.toHaveBeenCalled(); - }); + it(`#setFocus(${MDCChipActionFocusBehavior.FOCUSABLE}) sets tabindex="0"`, + () => { + const {foundation, mockAdapter} = setupTest(); + foundation.setFocus(MDCChipActionFocusBehavior.FOCUSABLE); + // TODO: Wait until b/208710526 is fixed, then remove this + // autogenerated error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type + // '"tabindex"' is not assignable to parameter of type + // 'Expected'. + expect(mockAdapter.setAttribute).toHaveBeenCalledWith('tabindex', '0'); + expect(mockAdapter.focus).not.toHaveBeenCalled(); + }); - it(`#setFocused(${FocusBehavior.NOT_FOCUSABLE}) sets tabindex="-1"`, () => { - const {foundation, mockAdapter} = setupTest(); - foundation.setFocus(FocusBehavior.NOT_FOCUSABLE); - expect(mockAdapter.setAttribute).toHaveBeenCalledWith('tabindex', '-1'); - }); + it(`#setFocused(${ + MDCChipActionFocusBehavior.NOT_FOCUSABLE}) sets tabindex="-1"`, + () => { + const {foundation, mockAdapter} = setupTest(); + foundation.setFocus(MDCChipActionFocusBehavior.NOT_FOCUSABLE); + expect(mockAdapter.setAttribute) + // TODO: Wait until b/208710526 is fixed, then remove this + // autogenerated error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type + // '"tabindex"' is not assignable to parameter of type + // 'Expected'. + .toHaveBeenCalledWith('tabindex', '-1'); + }); it('#isFocusable returns true if aria-hidden != true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_HIDDEN) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_HIDDEN) .and.returnValue(null); expect(foundation.isFocusable()).toBe(true); }); it('#isFocusable returns false if aria-hidden == true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_HIDDEN) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_HIDDEN) .and.returnValue('true'); expect(foundation.isFocusable()).toBe(false); }); it('#isFocusable returns true if aria-disabled != true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_DISABLED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_DISABLED) .and.returnValue('false'); expect(foundation.isFocusable()).toBe(true); }); it('#isFocusable returns false if aria-disabled == true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_DISABLED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_DISABLED) .and.returnValue('true'); expect(foundation.isFocusable()).toBe(false); }); it('#isSelected returns true if aria-checked == true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_SELECTED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_SELECTED) .and.returnValue('true'); expect(foundation.isSelected()).toBe(true); }); - it(`#isSelected returns false if ${Attributes.ARIA_SELECTED} != true`, + it(`#isSelected returns false if ${ + MDCChipActionAttributes.ARIA_SELECTED} != true`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_SELECTED) + mockAdapter.getAttribute + .withArgs(MDCChipActionAttributes.ARIA_SELECTED) .and.returnValue('false'); expect(foundation.isSelected()).toBe(false); }); - it(`#handleClick does not emit ${Events.INTERACTION} when disabled`, () => { - const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_DISABLED) - .and.returnValue('true'); - foundation.handleClick(); - expect(mockAdapter.emitEvent).not.toHaveBeenCalled(); - }); + it(`#handleClick does not emit ${ + MDCChipActionEvents.INTERACTION} when disabled`, + () => { + const {foundation, mockAdapter} = setupTest(); + mockAdapter.getAttribute + .withArgs(MDCChipActionAttributes.ARIA_DISABLED) + .and.returnValue('true'); + foundation.handleClick(); + expect(mockAdapter.emitEvent).not.toHaveBeenCalled(); + }); - it(`#handleClick emits ${Events.INTERACTION} with detail`, () => { - const {foundation, mockAdapter} = setupTest(); - mockAdapter.getElementID.and.returnValue('foo'); - foundation.handleClick(); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.INTERACTION, { - actionID: 'foo', - source: ActionType.UNSPECIFIED, - trigger: InteractionTrigger.CLICK, - }); - }); + it(`#handleClick emits ${MDCChipActionEvents.INTERACTION} with detail`, + () => { + const {foundation, mockAdapter} = setupTest(); + mockAdapter.getElementID.and.returnValue('foo'); + foundation.handleClick(); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipActionEvents.INTERACTION, { + actionID: 'foo', + source: MDCChipActionType.UNSPECIFIED, + trigger: MDCChipActionInteractionTrigger.CLICK, + }); + }); }); describe('[non-deletable]', () => { @@ -185,26 +213,27 @@ describe('MDCChipActionFoundation', () => { }; const emittingKeys = [ - {key: 'Enter', trigger: InteractionTrigger.ENTER_KEY}, - {key: 'Spacebar', trigger: InteractionTrigger.SPACEBAR_KEY}, + {key: 'Enter', trigger: MDCChipActionInteractionTrigger.ENTER_KEY}, + {key: 'Spacebar', trigger: MDCChipActionInteractionTrigger.SPACEBAR_KEY}, ]; for (const {key, trigger} of emittingKeys) { - it(`#handleKeydown(${key}) emits ${Events.INTERACTION} with detail`, + it(`#handleKeydown(${key}) emits ${ + MDCChipActionEvents.INTERACTION} with detail`, () => { const {foundation, mockAdapter} = setupTest(); - const evt = { + const event = { preventDefault: jasmine.createSpy('preventDefault') as Function, key, } as KeyboardEvent; - foundation.handleKeydown(evt); + foundation.handleKeydown(event); expect(mockAdapter.emitEvent) - .toHaveBeenCalledWith(Events.INTERACTION, { + .toHaveBeenCalledWith(MDCChipActionEvents.INTERACTION, { actionID: '', - source: ActionType.UNSPECIFIED, + source: MDCChipActionType.UNSPECIFIED, trigger, }); - expect(evt.preventDefault).toHaveBeenCalled(); + expect(event.preventDefault).toHaveBeenCalled(); }); } @@ -214,11 +243,13 @@ describe('MDCChipActionFoundation', () => { ]; for (const key of nonemittingKeys) { - it(`#handleKeydown(${key}) does not emit ${Events.INTERACTION}`, () => { - const {foundation, mockAdapter} = setupTest(); - foundation.handleKeydown({key} as KeyboardEvent); - expect(mockAdapter.emitEvent).not.toHaveBeenCalled(); - }); + it(`#handleKeydown(${key}) does not emit ${ + MDCChipActionEvents.INTERACTION}`, + () => { + const {foundation, mockAdapter} = setupTest(); + foundation.handleKeydown({key} as KeyboardEvent); + expect(mockAdapter.emitEvent).not.toHaveBeenCalled(); + }); } }); @@ -230,28 +261,32 @@ describe('MDCChipActionFoundation', () => { }; const emittingKeys = [ - {key: 'Enter', trigger: InteractionTrigger.ENTER_KEY}, - {key: 'Spacebar', trigger: InteractionTrigger.SPACEBAR_KEY}, - {key: 'Backspace', trigger: InteractionTrigger.BACKSPACE_KEY}, - {key: 'Delete', trigger: InteractionTrigger.DELETE_KEY}, + {key: 'Enter', trigger: MDCChipActionInteractionTrigger.ENTER_KEY}, + {key: 'Spacebar', trigger: MDCChipActionInteractionTrigger.SPACEBAR_KEY}, + { + key: 'Backspace', + trigger: MDCChipActionInteractionTrigger.BACKSPACE_KEY + }, + {key: 'Delete', trigger: MDCChipActionInteractionTrigger.DELETE_KEY}, ]; for (const {key, trigger} of emittingKeys) { - it(`#handleKeydown(${key}) emits ${Events.INTERACTION} with detail`, + it(`#handleKeydown(${key}) emits ${ + MDCChipActionEvents.INTERACTION} with detail`, () => { const {foundation, mockAdapter} = setupTest(); - const evt = { + const event = { preventDefault: jasmine.createSpy('preventDefault') as Function, key, } as KeyboardEvent; - foundation.handleKeydown(evt); + foundation.handleKeydown(event); expect(mockAdapter.emitEvent) - .toHaveBeenCalledWith(Events.INTERACTION, { + .toHaveBeenCalledWith(MDCChipActionEvents.INTERACTION, { actionID: '', - source: ActionType.UNSPECIFIED, + source: MDCChipActionType.UNSPECIFIED, trigger, }); - expect(evt.preventDefault).toHaveBeenCalled(); + expect(event.preventDefault).toHaveBeenCalled(); }); } }); @@ -263,51 +298,60 @@ describe('MDCChipActionFoundation', () => { return {foundation, mockAdapter}; }; - it(`#setSelected(true) sets ${Attributes.ARIA_SELECTED} to true`, () => { - const {foundation, mockAdapter} = setupTest(); - foundation.setSelected(true); - expect(mockAdapter.setAttribute) - .toHaveBeenCalledWith(Attributes.ARIA_SELECTED, 'true'); - }); + it(`#setSelected(true) sets ${ + MDCChipActionAttributes.ARIA_SELECTED} to true`, + () => { + const {foundation, mockAdapter} = setupTest(); + foundation.setSelected(true); + expect(mockAdapter.setAttribute) + .toHaveBeenCalledWith( + MDCChipActionAttributes.ARIA_SELECTED, 'true'); + }); - it(`#setSelected(false) sets ${Attributes.ARIA_SELECTED} to false`, () => { - const {foundation, mockAdapter} = setupTest(); - foundation.setSelected(false); - expect(mockAdapter.setAttribute) - .toHaveBeenCalledWith(Attributes.ARIA_SELECTED, 'false'); - }); + it(`#setSelected(false) sets ${ + MDCChipActionAttributes.ARIA_SELECTED} to false`, + () => { + const {foundation, mockAdapter} = setupTest(); + foundation.setSelected(false); + expect(mockAdapter.setAttribute) + .toHaveBeenCalledWith( + MDCChipActionAttributes.ARIA_SELECTED, 'false'); + }); - it(`#setDisabled(true) sets ${Attributes.ARIA_DISABLED} to true`, () => { - const {foundation, mockAdapter} = setupTest(); - foundation.setDisabled(true); - expect(mockAdapter.setAttribute) - .toHaveBeenCalledWith(Attributes.ARIA_DISABLED, 'true'); - }); + it(`#setDisabled(true) sets ${ + MDCChipActionAttributes.ARIA_DISABLED} to true`, + () => { + const {foundation, mockAdapter} = setupTest(); + foundation.setDisabled(true); + expect(mockAdapter.setAttribute) + .toHaveBeenCalledWith( + MDCChipActionAttributes.ARIA_DISABLED, 'true'); + }); it(`#setDisabled(false) sets aria-hidden="false"`, () => { const {foundation, mockAdapter} = setupTest(); foundation.setDisabled(false); expect(mockAdapter.setAttribute) - .toHaveBeenCalledWith(Attributes.ARIA_DISABLED, 'false'); + .toHaveBeenCalledWith(MDCChipActionAttributes.ARIA_DISABLED, 'false'); }); it(`#setDisabled(true) sets aria-hidden="true"`, () => { const {foundation, mockAdapter} = setupTest(); foundation.setDisabled(true); expect(mockAdapter.setAttribute) - .toHaveBeenCalledWith(Attributes.ARIA_DISABLED, 'true'); + .toHaveBeenCalledWith(MDCChipActionAttributes.ARIA_DISABLED, 'true'); }); it(`#isDisabled() return true when aria-hidden="true"`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_DISABLED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_DISABLED) .and.returnValue('true'); expect(foundation.isDisabled()).toBeTrue(); }); it(`#isDisabled() return false when aria-hidden="false"`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ARIA_DISABLED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ARIA_DISABLED) .and.returnValue('false'); expect(foundation.isDisabled()).toBeFalse(); }); @@ -320,7 +364,8 @@ describe('MDCChipActionFoundation', () => { return {foundation, mockAdapter}; }; - it(`#setSelected(true|false) does not set ${Attributes.ARIA_SELECTED}`, + it(`#setSelected(true|false) does not set ${ + MDCChipActionAttributes.ARIA_SELECTED}`, () => { const {foundation, mockAdapter} = setupTest(); foundation.setSelected(true); @@ -332,19 +377,19 @@ describe('MDCChipActionFoundation', () => { const {foundation, mockAdapter} = setupTest(); foundation.setDisabled(false); expect(mockAdapter.removeAttribute) - .toHaveBeenCalledWith(Attributes.DISABLED); + .toHaveBeenCalledWith(MDCChipActionAttributes.DISABLED); }); it(`#setDisabled(true) sets disabled="true"`, () => { const {foundation, mockAdapter} = setupTest(); foundation.setDisabled(true); expect(mockAdapter.setAttribute) - .toHaveBeenCalledWith(Attributes.DISABLED, 'true'); + .toHaveBeenCalledWith(MDCChipActionAttributes.DISABLED, 'true'); }); it(`#isDisabled() return true when the disabled attribute exists`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.DISABLED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.DISABLED) .and.returnValue(''); expect(foundation.isDisabled()).toBeTrue(); }); @@ -352,7 +397,7 @@ describe('MDCChipActionFoundation', () => { it(`#isDisabled() return false when the disabled attribute is absent`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.DISABLED) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.DISABLED) .and.returnValue(null); expect(foundation.isDisabled()).toBeFalse(); }); diff --git a/packages/mdc-chips/action/test/primary-foundation.test.ts b/packages/mdc-chips/action/test/primary-foundation.test.ts index 191b1895d4f..aacc504dec6 100644 --- a/packages/mdc-chips/action/test/primary-foundation.test.ts +++ b/packages/mdc-chips/action/test/primary-foundation.test.ts @@ -22,7 +22,7 @@ */ import {setUpFoundationTest} from '../../../../testing/helpers/setup'; -import {ActionType, Attributes} from '../constants'; +import {MDCChipActionAttributes, MDCChipActionType} from '../constants'; import {MDCChipPrimaryActionFoundation} from '../primary-foundation'; describe('MDCChipPrimaryActionFoundation', () => { @@ -32,30 +32,30 @@ describe('MDCChipPrimaryActionFoundation', () => { return {foundation, mockAdapter}; }; - it(`#actionType returns "${ActionType.PRIMARY}"`, () => { + it(`#actionType returns "${MDCChipActionType.PRIMARY}"`, () => { const {foundation} = setupTest(); - expect(foundation.actionType()).toBe(ActionType.PRIMARY); + expect(foundation.actionType()).toBe(MDCChipActionType.PRIMARY); }); it(`#isSelectable returns true when role="option"`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ROLE) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ROLE) .and.returnValue('option'); expect(foundation.isSelectable()).toBe(true); }); it(`#isSelectable returns false when role != "option"`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.ROLE) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.ROLE) .and.returnValue('button'); expect(foundation.isSelectable()).toBe(false); }); it(`#shouldEmitInteractionOnRemoveKey() returns true if ${ - Attributes.DATA_DELETABLE} == 'true'`, + MDCChipActionAttributes.DATA_DELETABLE} == 'true'`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getAttribute.withArgs(Attributes.DATA_DELETABLE) + mockAdapter.getAttribute.withArgs(MDCChipActionAttributes.DATA_DELETABLE) .and.returnValue('true'); expect((foundation as any).shouldEmitInteractionOnRemoveKey()) .toBe(true); diff --git a/packages/mdc-chips/action/test/trailing-foundation.test.ts b/packages/mdc-chips/action/test/trailing-foundation.test.ts index 9a0a28e93cf..0d8b72babf5 100644 --- a/packages/mdc-chips/action/test/trailing-foundation.test.ts +++ b/packages/mdc-chips/action/test/trailing-foundation.test.ts @@ -22,7 +22,7 @@ */ import {setUpFoundationTest} from '../../../../testing/helpers/setup'; -import {ActionType} from '../constants'; +import {MDCChipActionType} from '../constants'; import {MDCChipTrailingActionFoundation} from '../trailing-foundation'; describe('MDCChipTrailingActionFoundation', () => { @@ -32,9 +32,9 @@ describe('MDCChipTrailingActionFoundation', () => { return {foundation, mockAdapter}; }; - it(`#actionType returns "${ActionType.TRAILING}"`, () => { + it(`#actionType returns "${MDCChipActionType.TRAILING}"`, () => { const {foundation} = setupTest(); - expect(foundation.actionType()).toBe(ActionType.TRAILING); + expect(foundation.actionType()).toBe(MDCChipActionType.TRAILING); }); it(`#isSelectable returns false`, () => { diff --git a/packages/mdc-chips/action/trailing-foundation.ts b/packages/mdc-chips/action/trailing-foundation.ts index 273fbdd47e9..7ba4c69ac60 100644 --- a/packages/mdc-chips/action/trailing-foundation.ts +++ b/packages/mdc-chips/action/trailing-foundation.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {ActionType} from './constants'; +import {MDCChipActionType} from './constants'; import {MDCChipActionFoundation} from './foundation'; /** @@ -34,7 +34,7 @@ export class MDCChipTrailingActionFoundation extends MDCChipActionFoundation { } actionType() { - return ActionType.TRAILING; + return MDCChipActionType.TRAILING; } protected shouldEmitInteractionOnRemoveKey() { diff --git a/packages/mdc-chips/action/types.ts b/packages/mdc-chips/action/types.ts index 00da55f1904..198159fb566 100644 --- a/packages/mdc-chips/action/types.ts +++ b/packages/mdc-chips/action/types.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {ActionType, InteractionTrigger} from './constants'; +import {MDCChipActionInteractionTrigger, MDCChipActionType} from './constants'; /** * MDCChipActionInteractionEventDetail provides the details for the interaction @@ -29,8 +29,8 @@ import {ActionType, InteractionTrigger} from './constants'; */ export interface MDCChipActionInteractionEventDetail { actionID: string; - source: ActionType; - trigger: InteractionTrigger; + source: MDCChipActionType; + trigger: MDCChipActionInteractionTrigger; } /** @@ -38,6 +38,6 @@ export interface MDCChipActionInteractionEventDetail { * event. */ export interface MDCChipActionNavigationEventDetail { - source: ActionType; + source: MDCChipActionType; key: string; } diff --git a/packages/mdc-chips/chip-set/README.md b/packages/mdc-chips/chip-set/README.md index 878c91bdad1..11f45046e2a 100644 --- a/packages/mdc-chips/chip-set/README.md +++ b/packages/mdc-chips/chip-set/README.md @@ -85,11 +85,11 @@ Method Signature | Description `getChipIndexByID(chipID: string): number` | Returns the index of the chip with the given `id`. `getChipIdAtIndex(index: number): string` | Returns the `id` of the chip at the given index. `getSelectedChipIndexes(): ReadonlySet` | Returns the indexes of the selcted chips (if any). Only supported for [listbox chip sets](#listbox-chip-sets). -`setChipSelected(index: number, action: ActionType, isSelected: boolean): void` | Sets the chip to be selected at the given index. Only supported for [listbox chip sets](#listbox-chip-sets). -`isChipSelected(index: number, action: ActionType): boolean` | Returns the selected state of the chip at the given index. Only supported for [listbox chip sets](#listbox-chip-sets). +`setChipSelected(index: number, action: MDCChipActionType, isSelected: boolean): void` | Sets the chip to be selected at the given index. Only supported for [listbox chip sets](#listbox-chip-sets). +`isChipSelected(index: number, action: MDCChipActionType): boolean` | Returns the selected state of the chip at the given index. Only supported for [listbox chip sets](#listbox-chip-sets). `removeChip(index: number): boolean` | Returns the selected state of the chip at the given index. Only supported for [listbox chip sets](#listbox-chip-sets). -### `MDCChipSet` events +### `MDCChipSetEvents` Event name | Detail | Description --- | --- | --- @@ -102,18 +102,18 @@ Event name | Detail | Description Method Signature | Description --- | --- `announceMessage(message: string): void` | Announces the message to screen readers via an [`aria-live` region](https://www.w3.org/TR/wai-aria-1.1/#aria-live). -`emitEvent(eventName: Events, eventDetail: D): void` | Emits the given `eventName` with the given `eventDetail`. -`getAttribute(attrName: Attributes): string\|null` | Returns the value for the given if attribute or `null` if it does not exist. -`getChipActionsAtIndex(index: number): ActionType[]` | Returns the actions provided by the child chip at the given index. +`emitEvent(eventName: MDCChipSetEvents, eventDetail: D): void` | Emits the given `eventName` with the given `eventDetail`. +`getAttribute(attrName: MDCChipSetAttributes): string\|null` | Returns the value for the given if attribute or `null` if it does not exist. +`getChipActionsAtIndex(index: number): MDCChipActionType[]` | Returns the actions provided by the child chip at the given index. `getChipCount(): number` | Returns the number of child chips. `getChipIdAtIndex(index: number): string` | Returns the ID of the chip at the given index. `getChipIndexById(chipID: string): number` | Returns the index of the child chip with the matching ID. -`isChipFocusableAtIndex(index: number, actionType: ActionType): boolean` | Proxies to the `MDCChip#isActionFocusable` method. -`isChipSelectableAtIndex(index: number, actionType: ActionType): boolean` | Proxies to the `MDCChip#isActionSelectable` method. -`isChipSelectedAtIndex(index: number, actionType: ActionType): boolean` | Proxies to the `MDCChip#isActionSelected` method. +`isChipFocusableAtIndex(index: number, actionType: MDCChipActionType): boolean` | Proxies to the `MDCChip#isActionFocusable` method. +`isChipSelectableAtIndex(index: number, actionType: MDCChipActionType): boolean` | Proxies to the `MDCChip#isActionSelectable` method. +`isChipSelectedAtIndex(index: number, actionType: MDCChipActionType): boolean` | Proxies to the `MDCChip#isActionSelected` method. `removeChipAtIndex(index: number): void` | Removes the chip at the given index. -`setChipFocusAtIndex(index: number, action: ActionType, focus: FocusBehavior): void` | Proxies to the `MDCChip#setActionFocus` method. -`setChipSelectedAtIndex(index: number, actionType: ActionType, isSelected: boolean): void` | Proxies to the `MDCChip#setActionSelected` method. +`setChipFocusAtIndex(index: number, action: MDCChipActionType, focus: FocusBehavior): void` | Proxies to the `MDCChip#setActionFocus` method. +`setChipSelectedAtIndex(index: number, actionType: MDCChipActionType, isSelected: boolean): void` | Proxies to the `MDCChip#setActionSelected` method. `startChipAnimationAtIndex(index: number, animation: Animation): void` | Starts the chip animation at the given index. ### `MDCChipSetFoundation` @@ -124,8 +124,8 @@ Method Signature | Description `handleChipInteraction(event: ChipInteractionEvent): void` | Handles the chip interaction event. `handleChipNavigation(event: ChipNavigationEvent): void` | Handles the chip navigation event. `getSelectedChipIndexes(): ReadonlySet` | Returns the unique selected indexes of the chips (if any). -`setChipSelected(index: number, action: ActionType, isSelected: boolean): void` | Sets the selected state of the chip at the given index and action. -`isChipSelected(index: number, action: ActionType): boolean` | Returns the selected state of the chip at the given index and action. +`setChipSelected(index: number, action: MDCChipActionType, isSelected: boolean): void` | Sets the selected state of the chip at the given index and action. +`isChipSelected(index: number, action: MDCChipActionType): boolean` | Returns the selected state of the chip at the given index and action. `removeChip(index: number): void` | Removes the chip at the given index. `addChip(index: number): void` | Animates the addition of the chip at the given index. diff --git a/packages/mdc-chips/chip-set/adapter.ts b/packages/mdc-chips/chip-set/adapter.ts index b78f74fe8a0..556693d3da2 100644 --- a/packages/mdc-chips/chip-set/adapter.ts +++ b/packages/mdc-chips/chip-set/adapter.ts @@ -21,9 +21,10 @@ * THE SOFTWARE. */ -import {ActionType, FocusBehavior} from '../action/constants'; -import {Animation} from '../chip/constants'; -import {Attributes, Events} from './constants'; +import {MDCChipActionFocusBehavior, MDCChipActionType} from '../action/constants'; +import {MDCChipAnimation} from '../chip/constants'; + +import {MDCChipSetAttributes, MDCChipSetEvents} from './constants'; /** * Defines the shape of the adapter expected by the foundation. @@ -37,13 +38,14 @@ export interface MDCChipSetAdapter { announceMessage(message: string): void; /** Emits the given event with the given detail. */ - emitEvent(eventName: Events, eventDetail: D): void; + emitEvent(eventName: MDCChipSetEvents, eventDetail: D): + void; /** Returns the value for the given attribute, if it exists. */ - getAttribute(attrName: Attributes): string|null; + getAttribute(attrName: MDCChipSetAttributes): string|null; /** Returns the actions provided by the child chip at the given index. */ - getChipActionsAtIndex(index: number): ActionType[]; + getChipActionsAtIndex(index: number): MDCChipActionType[]; /** Returns the number of child chips. */ getChipCount(): number; @@ -55,25 +57,27 @@ export interface MDCChipSetAdapter { getChipIndexById(chipID: string): number; /** Proxies to the MDCChip#isActionFocusable method. */ - isChipFocusableAtIndex(index: number, actionType: ActionType): boolean; + isChipFocusableAtIndex(index: number, actionType: MDCChipActionType): boolean; /** Proxies to the MDCChip#isActionSelectable method. */ - isChipSelectableAtIndex(index: number, actionType: ActionType): boolean; + isChipSelectableAtIndex(index: number, actionType: MDCChipActionType): + boolean; /** Proxies to the MDCChip#isActionSelected method. */ - isChipSelectedAtIndex(index: number, actionType: ActionType): boolean; + isChipSelectedAtIndex(index: number, actionType: MDCChipActionType): boolean; /** Removes the chip at the given index. */ removeChipAtIndex(index: number): void; /** Proxies to the MDCChip#setActionFocus method. */ - setChipFocusAtIndex(index: number, action: ActionType, focus: FocusBehavior): - void; + setChipFocusAtIndex( + index: number, action: MDCChipActionType, + focus: MDCChipActionFocusBehavior): void; /** Proxies to the MDCChip#setActionSelected method. */ setChipSelectedAtIndex( - index: number, actionType: ActionType, isSelected: boolean): void; + index: number, actionType: MDCChipActionType, isSelected: boolean): void; /** Starts the chip animation at the given index. */ - startChipAnimationAtIndex(index: number, animation: Animation): void; + startChipAnimationAtIndex(index: number, animation: MDCChipAnimation): void; } diff --git a/packages/mdc-chips/chip-set/component.ts b/packages/mdc-chips/chip-set/component.ts index 01a326e0f80..2792582ce7c 100644 --- a/packages/mdc-chips/chip-set/component.ts +++ b/packages/mdc-chips/chip-set/component.ts @@ -25,12 +25,12 @@ import {MDCComponent} from '@material/base/component'; import {CustomEventListener} from '@material/base/types'; import {announce} from '@material/dom/announce'; -import {ActionType} from '../action/constants'; +import {MDCChipActionType} from '../action/constants'; import {MDCChip, MDCChipFactory} from '../chip/component'; -import {Events} from '../chip/constants'; +import {MDCChipEvents} from '../chip/constants'; import {MDCChipSetAdapter} from './adapter'; -import {CssClasses} from './constants'; +import {MDCChipSetCssClasses} from './constants'; import {MDCChipSetFoundation} from './foundation'; import {ChipAnimationEvent, ChipInteractionEvent, ChipNavigationEvent} from './types'; @@ -38,7 +38,7 @@ import {ChipAnimationEvent, ChipInteractionEvent, ChipNavigationEvent} from './t * MDCChip provides component encapsulation of the foundation implementation. */ export class MDCChipSet extends MDCComponent { - static attachTo(root: Element): MDCChipSet { + static override attachTo(root: HTMLElement): MDCChipSet { return new MDCChipSet(root); } @@ -48,16 +48,18 @@ export class MDCChipSet extends MDCComponent { private handleChipNavigation!: CustomEventListener; private chips!: MDCChip[]; - initialize(chipFactory: MDCChipFactory = (el: Element) => new MDCChip(el)) { + override initialize( + chipFactory: MDCChipFactory = (el: HTMLElement) => new MDCChip(el)) { this.chips = []; - const chipEls = this.root.querySelectorAll(`.${CssClasses.CHIP}`); + const chipEls = this.root.querySelectorAll( + `.${MDCChipSetCssClasses.CHIP}`); for (let i = 0; i < chipEls.length; i++) { const chip = chipFactory(chipEls[i]); this.chips.push(chip); } } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.handleChipAnimation = (event) => { this.foundation.handleChipAnimation(event); }; @@ -70,19 +72,19 @@ export class MDCChipSet extends MDCComponent { this.foundation.handleChipNavigation(event); }; - this.listen(Events.ANIMATION, this.handleChipAnimation); - this.listen(Events.INTERACTION, this.handleChipInteraction); - this.listen(Events.NAVIGATION, this.handleChipNavigation); + this.listen(MDCChipEvents.ANIMATION, this.handleChipAnimation); + this.listen(MDCChipEvents.INTERACTION, this.handleChipInteraction); + this.listen(MDCChipEvents.NAVIGATION, this.handleChipNavigation); } - destroy() { - this.unlisten(Events.ANIMATION, this.handleChipAnimation); - this.unlisten(Events.INTERACTION, this.handleChipInteraction); - this.unlisten(Events.NAVIGATION, this.handleChipNavigation); + override destroy() { + this.unlisten(MDCChipEvents.ANIMATION, this.handleChipAnimation); + this.unlisten(MDCChipEvents.INTERACTION, this.handleChipInteraction); + this.unlisten(MDCChipEvents.NAVIGATION, this.handleChipNavigation); super.destroy(); } - getDefaultFoundation() { + override getDefaultFoundation() { // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. @@ -161,12 +163,13 @@ export class MDCChipSet extends MDCComponent { } /** Sets the selection state of the chip. */ - setChipSelected(index: number, action: ActionType, isSelected: boolean) { + setChipSelected( + index: number, action: MDCChipActionType, isSelected: boolean) { this.foundation.setChipSelected(index, action, isSelected); } /** Returns the selection state of the chip. */ - isChipSelected(index: number, action: ActionType) { + isChipSelected(index: number, action: MDCChipActionType) { return this.foundation.isChipSelected(index, action); } diff --git a/packages/mdc-chips/chip-set/constants.ts b/packages/mdc-chips/chip-set/constants.ts index 119f41b6c1e..314ae0ead65 100644 --- a/packages/mdc-chips/chip-set/constants.ts +++ b/packages/mdc-chips/chip-set/constants.ts @@ -22,23 +22,24 @@ */ /** - * Events provides the named constants for strings used by the foundation. + * MDCChipSetAttributes provides the named constants for attributes used by the + * foundation. */ -export enum Attributes { +export enum MDCChipSetAttributes { ARIA_MULTISELECTABLE = 'aria-multiselectable', } /** - * CssClasses provides the named constants for class names. + * MDCChipSetCssClasses provides the named constants for class names. */ -export enum CssClasses { +export enum MDCChipSetCssClasses { CHIP = 'mdc-evolution-chip', } /** - * Events provides the constants for emitted events. + * MDCChipSetEvents provides the constants for emitted events. */ -export enum Events { +export enum MDCChipSetEvents { INTERACTION = 'MDCChipSet:interaction', REMOVAL = 'MDCChipSet:removal', SELECTION = 'MDCChipSet:selection', diff --git a/packages/mdc-chips/chip-set/foundation.ts b/packages/mdc-chips/chip-set/foundation.ts index 580b139732a..f2f5283b26a 100644 --- a/packages/mdc-chips/chip-set/foundation.ts +++ b/packages/mdc-chips/chip-set/foundation.ts @@ -24,15 +24,15 @@ import {MDCFoundation} from '@material/base/foundation'; import {KEY} from '@material/dom/keyboard'; -import {ActionType, FocusBehavior} from '../action/constants'; -import {Animation} from '../chip/constants'; +import {MDCChipActionFocusBehavior, MDCChipActionType} from '../action/constants'; +import {MDCChipAnimation} from '../chip/constants'; import {MDCChipSetAdapter} from './adapter'; -import {Attributes, Events} from './constants'; +import {MDCChipSetAttributes, MDCChipSetEvents} from './constants'; import {ChipAnimationEvent, ChipInteractionEvent, ChipNavigationEvent, MDCChipSetInteractionEventDetail, MDCChipSetRemovalEventDetail, MDCChipSetSelectionEventDetail} from './types'; interface FocusAction { - action: ActionType; + action: MDCChipActionType; index: number; } @@ -45,7 +45,7 @@ enum Operator { * MDCChipSetFoundation provides a foundation for all chips. */ export class MDCChipSetFoundation extends MDCFoundation { - static get defaultAdapter(): MDCChipSetAdapter { + static override get defaultAdapter(): MDCChipSetAdapter { return { announceMessage: () => undefined, emitEvent: () => undefined, @@ -78,7 +78,7 @@ export class MDCChipSetFoundation extends MDCFoundation { } = detail; const index = this.adapter.getChipIndexById(chipID); - if (animation === Animation.EXIT && isComplete) { + if (animation === MDCChipAnimation.EXIT && isComplete) { if (removedAnnouncement) { this.adapter.announceMessage(removedAnnouncement); } @@ -86,7 +86,8 @@ export class MDCChipSetFoundation extends MDCFoundation { return; } - if (animation === Animation.ENTER && isComplete && addedAnnouncement) { + if (animation === MDCChipAnimation.ENTER && isComplete && + addedAnnouncement) { this.adapter.announceMessage(addedAnnouncement); return; } @@ -101,9 +102,9 @@ export class MDCChipSetFoundation extends MDCFoundation { return; } - this.focusChip(index, source, FocusBehavior.FOCUSABLE); + this.focusChip(index, source, MDCChipActionFocusBehavior.FOCUSABLE); this.adapter.emitEvent( - Events.INTERACTION, { + MDCChipSetEvents.INTERACTION, { chipIndex: index, chipID, }); @@ -172,14 +173,15 @@ export class MDCChipSetFoundation extends MDCFoundation { } /** Sets the selected state of the chip at the given index and action. */ - setChipSelected(index: number, action: ActionType, isSelected: boolean) { + setChipSelected( + index: number, action: MDCChipActionType, isSelected: boolean) { if (this.adapter.isChipSelectableAtIndex(index, action)) { this.setSelection(index, action, isSelected); } } /** Returns the selected state of the chip at the given index and action. */ - isChipSelected(index: number, action: ActionType): boolean { + isChipSelected(index: number, action: MDCChipActionType): boolean { return this.adapter.isChipSelectedAtIndex(index, action); } @@ -187,30 +189,34 @@ export class MDCChipSetFoundation extends MDCFoundation { removeChip(index: number) { // Early exit if the index is out of bounds if (index >= this.adapter.getChipCount() || index < 0) return; - this.adapter.startChipAnimationAtIndex(index, Animation.EXIT); - this.adapter.emitEvent(Events.REMOVAL, { - chipID: this.adapter.getChipIdAtIndex(index), - chipIndex: index, - isComplete: false, - }); + this.adapter.startChipAnimationAtIndex(index, MDCChipAnimation.EXIT); + this.adapter.emitEvent( + MDCChipSetEvents.REMOVAL, { + chipID: this.adapter.getChipIdAtIndex(index), + chipIndex: index, + isComplete: false, + }); } addChip(index: number) { // Early exit if the index is out of bounds if (index >= this.adapter.getChipCount() || index < 0) return; - this.adapter.startChipAnimationAtIndex(index, Animation.ENTER); + this.adapter.startChipAnimationAtIndex(index, MDCChipAnimation.ENTER); } /** * Increments to find the first focusable chip. */ - private focusNextChipFrom(startIndex: number, targetAction?: ActionType) { + private focusNextChipFrom( + startIndex: number, targetAction?: MDCChipActionType) { const chipCount = this.adapter.getChipCount(); for (let i = startIndex; i < chipCount; i++) { const focusableAction = this.getFocusableAction(i, Operator.INCREMENT, targetAction); if (focusableAction) { - this.focusChip(i, focusableAction, FocusBehavior.FOCUSABLE_AND_FOCUSED); + this.focusChip( + i, focusableAction, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); return; } } @@ -220,12 +226,15 @@ export class MDCChipSetFoundation extends MDCFoundation { * Decrements to find the first focusable chip. Takes an optional target * action that can be used to focus the first matching focusable action. */ - private focusPrevChipFrom(startIndex: number, targetAction?: ActionType) { + private focusPrevChipFrom( + startIndex: number, targetAction?: MDCChipActionType) { for (let i = startIndex; i > -1; i--) { const focusableAction = this.getFocusableAction(i, Operator.DECREMENT, targetAction); if (focusableAction) { - this.focusChip(i, focusableAction, FocusBehavior.FOCUSABLE_AND_FOCUSED); + this.focusChip( + i, focusableAction, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); return; } } @@ -233,7 +242,8 @@ export class MDCChipSetFoundation extends MDCFoundation { /** Returns the appropriate focusable action, or null if none exist. */ private getFocusableAction( - index: number, op: Operator, targetAction?: ActionType): ActionType|null { + index: number, op: Operator, + targetAction?: MDCChipActionType): MDCChipActionType|null { const actions = this.adapter.getChipActionsAtIndex(index); // Reverse the actions if decrementing if (op === Operator.DECREMENT) actions.reverse(); @@ -249,8 +259,8 @@ export class MDCChipSetFoundation extends MDCFoundation { * Returs the first focusable action, regardless of type, or null if no * focusable actions exist. */ - private getFirstFocusableAction(index: number, actions: ActionType[]): - ActionType|null { + private getFirstFocusableAction(index: number, actions: MDCChipActionType[]): + MDCChipActionType|null { for (const action of actions) { if (this.adapter.isChipFocusableAtIndex(index, action)) { return action; @@ -265,8 +275,8 @@ export class MDCChipSetFoundation extends MDCFoundation { * focusable action exists. */ private getMatchingFocusableAction( - index: number, actions: ActionType[], - targetAction: ActionType): ActionType|null { + index: number, actions: MDCChipActionType[], + targetAction: MDCChipActionType): MDCChipActionType|null { let focusableAction = null; for (const action of actions) { if (this.adapter.isChipFocusableAtIndex(index, action)) { @@ -281,7 +291,9 @@ export class MDCChipSetFoundation extends MDCFoundation { return focusableAction; } - private focusChip(index: number, action: ActionType, focus: FocusBehavior) { + private focusChip( + index: number, action: MDCChipActionType, + focus: MDCChipActionFocusBehavior) { this.adapter.setChipFocusAtIndex(index, action, focus); const chipCount = this.adapter.getChipCount(); for (let i = 0; i < chipCount; i++) { @@ -290,23 +302,25 @@ export class MDCChipSetFoundation extends MDCFoundation { // Skip the action and index provided since we set it above if (chipAction === action && i === index) continue; this.adapter.setChipFocusAtIndex( - i, chipAction, FocusBehavior.NOT_FOCUSABLE); + i, chipAction, MDCChipActionFocusBehavior.NOT_FOCUSABLE); } } } private supportsMultiSelect(): boolean { - return this.adapter.getAttribute(Attributes.ARIA_MULTISELECTABLE) === - 'true'; + return this.adapter.getAttribute( + MDCChipSetAttributes.ARIA_MULTISELECTABLE) === 'true'; } - private setSelection(index: number, action: ActionType, isSelected: boolean) { + private setSelection( + index: number, action: MDCChipActionType, isSelected: boolean) { this.adapter.setChipSelectedAtIndex(index, action, isSelected); - this.adapter.emitEvent(Events.SELECTION, { - chipID: this.adapter.getChipIdAtIndex(index), - chipIndex: index, - isSelected, - }); + this.adapter.emitEvent( + MDCChipSetEvents.SELECTION, { + chipID: this.adapter.getChipIdAtIndex(index), + chipIndex: index, + isSelected, + }); // Early exit if we support multi-selection if (this.supportsMultiSelect()) { return; @@ -327,11 +341,12 @@ export class MDCChipSetFoundation extends MDCFoundation { private removeAfterAnimation(index: number, chipID: string) { this.adapter.removeChipAtIndex(index); - this.adapter.emitEvent(Events.REMOVAL, { - chipIndex: index, - isComplete: true, - chipID, - }); + this.adapter.emitEvent( + MDCChipSetEvents.REMOVAL, { + chipIndex: index, + isComplete: true, + chipID, + }); const chipCount = this.adapter.getChipCount(); // Early exit if we have an empty chip set @@ -357,11 +372,11 @@ export class MDCChipSetFoundation extends MDCFoundation { let incrIndex = index; while (decrIndex > -1 || incrIndex < chipCount) { const focusAction = this.getNearestFocusableAction( - decrIndex, incrIndex, ActionType.TRAILING); + decrIndex, incrIndex, MDCChipActionType.TRAILING); if (focusAction) { this.focusChip( focusAction.index, focusAction.action, - FocusBehavior.FOCUSABLE_AND_FOCUSED); + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); return; } @@ -372,7 +387,7 @@ export class MDCChipSetFoundation extends MDCFoundation { private getNearestFocusableAction( decrIndex: number, incrIndex: number, - actionType?: ActionType): FocusAction|null { + actionType?: MDCChipActionType): FocusAction|null { const decrAction = this.getFocusableAction(decrIndex, Operator.DECREMENT, actionType); if (decrAction) { diff --git a/packages/mdc-chips/chip-set/index.ts b/packages/mdc-chips/chip-set/index.ts index ac92b9a0b32..605cac4a233 100644 --- a/packages/mdc-chips/chip-set/index.ts +++ b/packages/mdc-chips/chip-set/index.ts @@ -25,3 +25,4 @@ export * from './adapter'; export * from './component'; export * from './constants'; export * from './foundation'; +export * from './types'; diff --git a/packages/mdc-chips/chip-set/test/component.test.ts b/packages/mdc-chips/chip-set/test/component.test.ts index 5bcd02ddc66..5295a318e79 100644 --- a/packages/mdc-chips/chip-set/test/component.test.ts +++ b/packages/mdc-chips/chip-set/test/component.test.ts @@ -22,11 +22,12 @@ */ import {DATA_MDC_DOM_ANNOUNCE} from '../../../mdc-dom/announce'; +import {createFixture, html} from '../../../../testing/dom'; import {createKeyboardEvent, emitEvent} from '../../../../testing/dom/events'; import {createMockFoundation} from '../../../../testing/helpers/foundation'; import {setUpMdcTestEnvironment} from '../../../../testing/helpers/setup'; -import {ActionType} from '../../action/constants'; -import {Animation, CssClasses, Events} from '../../chip/constants'; +import {MDCChipActionType} from '../../action/constants'; +import {MDCChipAnimation, MDCChipCssClasses, MDCChipEvents} from '../../chip/constants'; import {MDCChipAnimationEventDetail} from '../../chip/types'; import {MDCChipSet, MDCChipSetFoundation} from '../index'; @@ -66,14 +67,10 @@ function chipFixture({primary, trailing, id}: ChipOptions): string { } function getFixture({chips, isMultiselectable}: TestOptions): HTMLElement { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html`
${chips.map((chip) => chipFixture(chip))} -
`; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; + `); } function setupTest(options: TestOptions) { @@ -141,7 +138,8 @@ describe('MDCChipSet', () => { isMultiselectable: false, }); - const primaryActionEl = root.querySelector('.mdc-evolution-chip__action')!; + const primaryActionEl = + root.querySelector('.mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); @@ -152,10 +150,11 @@ describe('MDCChipSet', () => { })); expect(mockFoundation.handleChipNavigation).toHaveBeenCalled(); - emitEvent(root.querySelector('#c0')!, Events.ANIMATION, { - bubbles: true, - cancelable: false, - }); + emitEvent( + root.querySelector('#c0')!, MDCChipEvents.ANIMATION, { + bubbles: true, + cancelable: false, + }); expect(mockFoundation.handleChipAnimation).toHaveBeenCalled(); component.destroy(); }); @@ -170,7 +169,8 @@ describe('MDCChipSet', () => { }); component.destroy(); - const primaryActionEl = root.querySelector('.mdc-evolution-chip__action')!; + const primaryActionEl = + root.querySelector('.mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); @@ -181,10 +181,11 @@ describe('MDCChipSet', () => { })); expect(mockFoundation.handleChipNavigation).not.toHaveBeenCalled(); - emitEvent(root.querySelector('#c0')!, Events.ANIMATION, { - bubbles: true, - cancelable: false, - }); + emitEvent( + root.querySelector('#c0')!, MDCChipEvents.ANIMATION, { + bubbles: true, + cancelable: false, + }); expect(mockFoundation.handleChipAnimation).not.toHaveBeenCalled(); }); @@ -264,10 +265,10 @@ describe('MDCChipSet', () => { isMultiselectable: true, }); - component.setChipSelected(1, ActionType.PRIMARY, true); + component.setChipSelected(1, MDCChipActionType.PRIMARY, true); - expect(root.querySelector('#c1 .mdc-evolution-chip__action')!.getAttribute( - 'aria-selected')) + expect(root.querySelector('#c1 .mdc-evolution-chip__action')! + .getAttribute('aria-selected')) .toBe('true'); }); @@ -281,7 +282,7 @@ describe('MDCChipSet', () => { isMultiselectable: true, }); - expect(component.isChipSelected(0, ActionType.PRIMARY)).toBe(true); + expect(component.isChipSelected(0, MDCChipActionType.PRIMARY)).toBe(true); }); it('#isChipSelected() returns false if the chip is not selected', () => { @@ -294,7 +295,7 @@ describe('MDCChipSet', () => { isMultiselectable: true, }); - expect(component.isChipSelected(1, ActionType.PRIMARY)).toBe(false); + expect(component.isChipSelected(1, MDCChipActionType.PRIMARY)).toBe(false); }); it('#removeChip() proxies to the foundation', () => { @@ -321,13 +322,13 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); - expect(root.querySelector('#c1 .mdc-evolution-chip__action')!.getAttribute( - 'tabindex')) + expect(root.querySelector( + '#c1 .mdc-evolution-chip__action')!.getAttribute('tabindex')) .toBe('0'); }); @@ -341,18 +342,20 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); - expect(root.querySelector('#c0 .mdc-evolution-chip__action')!.getAttribute( - 'tabindex')) + expect(root.querySelector( + '#c0 .mdc-evolution-chip__action')!.getAttribute('tabindex')) .toBe('-1'); - expect(root.querySelector('#c0 .mdc-evolution-chip__action--trailing')! + expect(root.querySelector( + '#c0 .mdc-evolution-chip__action--trailing')! .getAttribute('tabindex')) .toBe('-1'); - expect(root.querySelector('#c1 .mdc-evolution-chip__action--trailing')! + expect(root.querySelector( + '#c1 .mdc-evolution-chip__action--trailing')! .getAttribute('tabindex')) .toBe('-1'); }); @@ -367,13 +370,13 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); - expect(root.querySelector('#c1 .mdc-evolution-chip__action')!.getAttribute( - 'tabindex')) + expect(root.querySelector( + '#c1 .mdc-evolution-chip__action')!.getAttribute('tabindex')) .not.toBe('0'); }); @@ -387,13 +390,13 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c0 .mdc-evolution-chip__action')!; + root.querySelector('#c0 .mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); - expect(root.querySelector('#c0 .mdc-evolution-chip__action')!.getAttribute( - 'aria-selected')) + expect(root.querySelector('#c0 .mdc-evolution-chip__action')! + .getAttribute('aria-selected')) .toBe('true'); }); @@ -408,14 +411,14 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); expect( - root.querySelector('#c0 .mdc-evolution-chip__action')!.getAttribute( - 'aria-selected')) + root.querySelector('#c0 .mdc-evolution-chip__action')! + .getAttribute('aria-selected')) .toBe('false'); }); @@ -430,14 +433,14 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; emitEvent(primaryActionEl, 'click', { bubbles: true, }); expect( - root.querySelector('#c0 .mdc-evolution-chip__action')!.getAttribute( - 'aria-selected')) + root.querySelector('#c0 .mdc-evolution-chip__action')! + .getAttribute('aria-selected')) .toBe('true'); }); @@ -451,12 +454,13 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; primaryActionEl.dispatchEvent(createKeyboardEvent('keydown', { key: 'ArrowLeft', })); - expect(root.querySelector('#c0 .mdc-evolution-chip__action--trailing')! + expect(root.querySelector( + '#c0 .mdc-evolution-chip__action--trailing')! .getAttribute('tabindex')) .toBe('0'); }); @@ -471,54 +475,56 @@ describe('MDCChipSet', () => { }); const primaryActionEl = - root.querySelector('#c1 .mdc-evolution-chip__action')!; + root.querySelector('#c1 .mdc-evolution-chip__action')!; primaryActionEl.dispatchEvent(createKeyboardEvent('keydown', { key: 'ArrowLeft', })); - expect(root.querySelector('#c0 .mdc-evolution-chip__action')!.getAttribute( - 'tabindex')) + expect(root.querySelector( + '#c0 .mdc-evolution-chip__action')!.getAttribute('tabindex')) .toBe('-1'); - expect(root.querySelector('#c1 .mdc-evolution-chip__action')!.getAttribute( - 'tabindex')) + expect(root.querySelector( + '#c1 .mdc-evolution-chip__action')!.getAttribute('tabindex')) .toBe('-1'); - expect(root.querySelector('#c1 .mdc-evolution-chip__action--trailing')! + expect(root.querySelector( + '#c1 .mdc-evolution-chip__action--trailing')! .getAttribute('tabindex')) .toBe('-1'); }); - fit('announces chip addition when enter animation is complete' + - ' and addition announcement is present', - () => { - const {root} = setupTest({ - chips: [ - multiActionInputChip('c0'), - multiActionInputChip('c1'), - ], - isMultiselectable: false, - }); - - const detail: MDCChipAnimationEventDetail = { - isComplete: true, - addedAnnouncement: 'Added a chip', - animation: Animation.ENTER, - chipID: 'c0', - }; - - emitEvent(root.querySelector('#c0')!, Events.ANIMATION, { - bubbles: true, - cancelable: false, - detail, - }); + it('announces chip addition when enter animation is complete' + + ' and addition announcement is present', + () => { + const {root} = setupTest({ + chips: [ + multiActionInputChip('c0'), + multiActionInputChip('c1'), + ], + isMultiselectable: false, + }); - // Tick clock forward to account for setTimeout inside "announce". - jasmine.clock().tick(1); - const liveRegion = - document.querySelector(`[${DATA_MDC_DOM_ANNOUNCE}="true"]`)!; - expect(liveRegion.textContent).toEqual('Added a chip'); - // Clean up the live region. - liveRegion.parentNode!.removeChild(liveRegion); - }); + const detail: MDCChipAnimationEventDetail = { + isComplete: true, + addedAnnouncement: 'Added a chip', + animation: MDCChipAnimation.ENTER, + chipID: 'c0', + }; + + emitEvent( + root.querySelector('#c0')!, MDCChipEvents.ANIMATION, { + bubbles: true, + cancelable: false, + detail, + }); + + // Tick clock forward to account for setTimeout inside "announce". + jasmine.clock().tick(1); + const liveRegion = document.querySelector( + `[${DATA_MDC_DOM_ANNOUNCE}="true"]`)!; + expect(liveRegion.textContent).toEqual('Added a chip'); + // Clean up the live region. + liveRegion.parentNode!.removeChild(liveRegion); + }); it('removes the chip from the DOM when removal animation is complete', () => { const {component, root} = setupTest({ @@ -532,15 +538,16 @@ describe('MDCChipSet', () => { const detail: MDCChipAnimationEventDetail = { isComplete: true, removedAnnouncement: 'Removed a chip', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, chipID: 'c0', }; - emitEvent(root.querySelector('#c0')!, Events.ANIMATION, { - bubbles: true, - cancelable: false, - detail, - }); + emitEvent( + root.querySelector('#c0')!, MDCChipEvents.ANIMATION, { + bubbles: true, + cancelable: false, + detail, + }); expect(component.getChipIndexByID('c0')).toBe(-1); }); @@ -554,8 +561,8 @@ describe('MDCChipSet', () => { isMultiselectable: false, }); - const chip0 = root.querySelector('#c0')!; + const chip0 = root.querySelector('#c0')!; component.addChip(0); - expect(chip0.classList.contains(CssClasses.ENTER)).toBeTrue(); + expect(chip0).toHaveClass(MDCChipCssClasses.ENTER); }); }); diff --git a/packages/mdc-chips/chip-set/test/foundation.test.ts b/packages/mdc-chips/chip-set/test/foundation.test.ts index 8e962adafcb..3b0c2e507bf 100644 --- a/packages/mdc-chips/chip-set/test/foundation.test.ts +++ b/packages/mdc-chips/chip-set/test/foundation.test.ts @@ -22,14 +22,14 @@ */ import {setUpFoundationTest} from '../../../../testing/helpers/setup'; -import {ActionType, FocusBehavior} from '../../action/constants'; -import {Animation} from '../../chip/constants'; -import {Attributes, Events} from '../constants'; +import {MDCChipActionFocusBehavior, MDCChipActionType} from '../../action/constants'; +import {MDCChipAnimation} from '../../chip/constants'; +import {MDCChipSetAttributes, MDCChipSetEvents} from '../constants'; import {MDCChipSetFoundation} from '../foundation'; import {ChipAnimationEvent, ChipInteractionEvent, ChipNavigationEvent} from '../types'; interface FakeAction { - type: ActionType; + type: MDCChipActionType; isSelectable: boolean; isSelected: boolean; isFocusable: boolean; @@ -44,13 +44,13 @@ function fakeMultiActionChip(id: string): FakeChip { return { actions: [ { - type: ActionType.PRIMARY, + type: MDCChipActionType.PRIMARY, isSelectable: false, isSelected: false, isFocusable: true }, { - type: ActionType.TRAILING, + type: MDCChipActionType.TRAILING, isSelectable: false, isSelected: false, isFocusable: true @@ -64,13 +64,13 @@ function fakeSingleActionChip(id: string): FakeChip { return { actions: [ { - type: ActionType.PRIMARY, + type: MDCChipActionType.PRIMARY, isSelectable: false, isSelected: false, isFocusable: true }, { - type: ActionType.TRAILING, + type: MDCChipActionType.TRAILING, isSelectable: false, isSelected: false, isFocusable: false @@ -84,13 +84,13 @@ function fakeDisabledMultiActionChip(id: string): FakeChip { return { actions: [ { - type: ActionType.PRIMARY, + type: MDCChipActionType.PRIMARY, isSelectable: false, isSelected: false, isFocusable: false }, { - type: ActionType.TRAILING, + type: MDCChipActionType.TRAILING, isSelectable: false, isSelected: false, isFocusable: false @@ -103,7 +103,7 @@ function fakeDisabledMultiActionChip(id: string): FakeChip { function fakeSelectableChip(id: string, isSelected: boolean = false): FakeChip { return { actions: [{ - type: ActionType.PRIMARY, + type: MDCChipActionType.PRIMARY, isSelectable: true, isFocusable: true, isSelected @@ -142,14 +142,15 @@ describe('MDCChipSetFoundation', () => { mockAdapter.getChipCount.and.callFake(() => { return chips.length; }); - mockAdapter.getAttribute.and.callFake((attr: Attributes) => { - if (attr === Attributes.ARIA_MULTISELECTABLE && supportsMultiSelection) { + mockAdapter.getAttribute.and.callFake((attr: MDCChipSetAttributes) => { + if (attr === MDCChipSetAttributes.ARIA_MULTISELECTABLE && + supportsMultiSelection) { return 'true'; } return null; }); mockAdapter.isChipSelectableAtIndex.and.callFake( - (index: number, action: ActionType) => { + (index: number, action: MDCChipActionType) => { if (index < 0 || index >= chips.length) { return false; } @@ -160,7 +161,7 @@ describe('MDCChipSetFoundation', () => { return actions[0].isSelectable; }); mockAdapter.isChipSelectedAtIndex.and.callFake( - (index: number, action: ActionType) => { + (index: number, action: MDCChipActionType) => { if (index < 0 || index >= chips.length) { return false; } @@ -171,7 +172,7 @@ describe('MDCChipSetFoundation', () => { return actions[0].isSelected; }); mockAdapter.isChipFocusableAtIndex.and.callFake( - (index: number, action: ActionType) => { + (index: number, action: MDCChipActionType) => { if (index < 0 || index >= chips.length) { return false; } @@ -199,13 +200,14 @@ describe('MDCChipSetFoundation', () => { supportsMultiSelection: false, }); foundation.handleChipInteraction({ - detail: {source: ActionType.PRIMARY, chipID: 'c0'}, + detail: {source: MDCChipActionType.PRIMARY, chipID: 'c0'}, } as ChipInteractionEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.INTERACTION, { - chipID: 'c0', - chipIndex: 0, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipSetEvents.INTERACTION, { + chipID: 'c0', + chipIndex: 0, + }); }); it(`#handleChipInteraction focuses the source chip action`, () => { @@ -218,11 +220,12 @@ describe('MDCChipSetFoundation', () => { supportsMultiSelection: false, }); foundation.handleChipInteraction({ - detail: {source: ActionType.PRIMARY, chipID: 'c0'}, + detail: {source: MDCChipActionType.PRIMARY, chipID: 'c0'}, } as ChipInteractionEvent); expect(mockAdapter.setChipFocusAtIndex) - .toHaveBeenCalledWith(0, ActionType.PRIMARY, FocusBehavior.FOCUSABLE); + .toHaveBeenCalledWith( + 0, MDCChipActionType.PRIMARY, MDCChipActionFocusBehavior.FOCUSABLE); }); it(`#handleChipInteraction unfocuses all other chip actions`, () => { @@ -235,24 +238,29 @@ describe('MDCChipSetFoundation', () => { supportsMultiSelection: false, }); foundation.handleChipInteraction({ - detail: {source: ActionType.PRIMARY, chipID: 'c0'}, + detail: {source: MDCChipActionType.PRIMARY, chipID: 'c0'}, } as ChipInteractionEvent); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipInteraction emits a selection event when the chip is selectable`, @@ -267,18 +275,19 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipInteraction({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', isSelectable: true, isSelected: false }, } as ChipInteractionEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.SELECTION, { - chipID: 'c0', - chipIndex: 0, - isSelected: true, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipSetEvents.SELECTION, { + chipID: 'c0', + chipIndex: 0, + isSelected: true, + }); }); it(`#handleChipInteraction selects the source chip when not multiselectable`, @@ -293,7 +302,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipInteraction({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', isSelectable: true, isSelected: false @@ -301,7 +310,7 @@ describe('MDCChipSetFoundation', () => { } as ChipInteractionEvent); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(0, ActionType.PRIMARY, true); + .toHaveBeenCalledWith(0, MDCChipActionType.PRIMARY, true); }); it(`#handleChipInteraction unselects other chips when not multiselectable`, @@ -316,7 +325,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipInteraction({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', isSelectable: true, isSelected: false @@ -324,9 +333,9 @@ describe('MDCChipSetFoundation', () => { } as ChipInteractionEvent); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(1, ActionType.PRIMARY, false); + .toHaveBeenCalledWith(1, MDCChipActionType.PRIMARY, false); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(2, ActionType.PRIMARY, false); + .toHaveBeenCalledWith(2, MDCChipActionType.PRIMARY, false); }); it(`#handleChipInteraction only selects the source chip when multiselectable`, @@ -341,7 +350,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipInteraction({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', isSelectable: true, isSelected: false @@ -364,7 +373,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipInteraction({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', isSelectable: true, isSelected: false @@ -372,7 +381,7 @@ describe('MDCChipSetFoundation', () => { } as ChipInteractionEvent); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(0, ActionType.PRIMARY, true); + .toHaveBeenCalledWith(0, MDCChipActionType.PRIMARY, true); }); it(`#handleChipInteraction does not unselect other chips when multiselectable`, @@ -387,7 +396,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipInteraction({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', isSelectable: true, isSelected: false @@ -416,7 +425,7 @@ describe('MDCChipSetFoundation', () => { } as ChipInteractionEvent); expect(mockAdapter.startChipAnimationAtIndex) - .toHaveBeenCalledWith(1, Animation.EXIT); + .toHaveBeenCalledWith(1, MDCChipAnimation.EXIT); }); it(`#handleChipInteraction emits a removal event when the source chip is removable`, @@ -436,11 +445,12 @@ describe('MDCChipSetFoundation', () => { }, } as ChipInteractionEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.REMOVAL, { - chipID: 'c1', - chipIndex: 1, - isComplete: false, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipSetEvents.REMOVAL, { + chipID: 'c1', + chipIndex: 1, + isComplete: false, + }); }); it(`#handleChipInteraction does not emit an interaction event when the source chip is removable`, @@ -461,7 +471,8 @@ describe('MDCChipSetFoundation', () => { } as ChipInteractionEvent); expect(mockAdapter.emitEvent) - .not.toHaveBeenCalledWith(Events.INTERACTION, jasmine.anything()); + .not.toHaveBeenCalledWith( + MDCChipSetEvents.INTERACTION, jasmine.anything()); }); it(`#handleChipInteraction does not change focus when the source chip is removable`, @@ -496,7 +507,7 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.ENTER, + animation: MDCChipAnimation.ENTER, addedAnnouncement: 'Added foo', isComplete: true, }, @@ -518,7 +529,7 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, isComplete: true, }, } as ChipAnimationEvent); @@ -539,16 +550,17 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, isComplete: true, }, } as ChipAnimationEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.REMOVAL, { - chipID: 'c1', - chipIndex: 1, - isComplete: true, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipSetEvents.REMOVAL, { + chipID: 'c1', + chipIndex: 1, + isComplete: true, + }); }); it(`#handleChipAnimation does not remove the source chip when the animation is incomplete`, @@ -564,7 +576,7 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, isComplete: false, }, } as ChipAnimationEvent); @@ -585,7 +597,7 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.ENTER, + animation: MDCChipAnimation.ENTER, isComplete: true, }, } as ChipAnimationEvent); @@ -606,14 +618,15 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, isComplete: true, }, } as ChipAnimationEvent); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipAnimation focuses the nearest focusable chip (0), avoiding disabled chips`, @@ -631,14 +644,15 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c4', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, isComplete: true, }, } as ChipAnimationEvent); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipAnimation focuses the nearest focusable chip (3), avoiding disabled chips`, @@ -656,14 +670,15 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c0', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, isComplete: true, }, } as ChipAnimationEvent); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 3, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 3, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipAnimation focuses no chip when all remaining are disabled`, @@ -679,7 +694,7 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, removedAnnouncement: undefined, isComplete: true, }, @@ -703,7 +718,7 @@ describe('MDCChipSetFoundation', () => { foundation.handleChipAnimation({ detail: { chipID: 'c1', - animation: Animation.EXIT, + animation: MDCChipAnimation.EXIT, removedAnnouncement: 'Removed foo', isComplete: true, }, @@ -724,7 +739,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c0', key: 'ArrowRight', isRTL: false, @@ -733,7 +748,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with ArrowRight`, @@ -748,7 +764,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c0', key: 'ArrowRight', isRTL: false, @@ -757,19 +773,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation focuses the next available action with ArrowLeft`, @@ -784,7 +805,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowLeft', isRTL: false, @@ -793,7 +814,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with ArrowLeft`, () => { @@ -807,7 +829,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowLeft', isRTL: false, @@ -816,19 +838,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation focuses the next available action with ArrowRight in RTL`, @@ -843,7 +870,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowRight', isRTL: true, @@ -852,7 +879,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with ArrowRight in RTL`, @@ -867,7 +895,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowRight', isRTL: true, @@ -876,19 +904,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation focuses the next available action with ArrowLeft in TRL`, @@ -903,7 +936,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c1', key: 'ArrowLeft', isRTL: true, @@ -912,7 +945,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with ArrowLeft in RTL`, @@ -927,7 +961,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c1', key: 'ArrowLeft', isRTL: true, @@ -936,19 +970,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation does not focus unfocusable actions`, () => { @@ -962,7 +1001,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c0', key: 'ArrowRight', isRTL: false, @@ -971,10 +1010,12 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .not.toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); expect(mockAdapter.setChipFocusAtIndex) .not.toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation focuses the next matching action with ArrowUp`, @@ -989,7 +1030,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowUp', isRTL: false, @@ -998,7 +1039,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with ArrowUp`, () => { @@ -1012,7 +1054,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowUp', isRTL: false, @@ -1021,19 +1063,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation focuses the previous matching action with ArrowDown`, @@ -1048,7 +1095,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowDown', isRTL: false, @@ -1057,7 +1104,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with ArrowDown`, () => { @@ -1071,7 +1119,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c1', key: 'ArrowDown', isRTL: false, @@ -1080,19 +1128,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation focuses the first matching action with Home`, @@ -1107,7 +1160,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c2', key: 'Home', isRTL: false, @@ -1116,7 +1169,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with Home`, () => { @@ -1130,7 +1184,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c2', key: 'Home', isRTL: false, @@ -1139,19 +1193,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation focuses the first matching action with End`, () => { @@ -1165,7 +1224,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', key: 'End', isRTL: false, @@ -1174,7 +1233,8 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 2, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation unfocuses all other actions with End`, () => { @@ -1188,7 +1248,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, chipID: 'c0', key: 'End', isRTL: false, @@ -1197,19 +1257,24 @@ describe('MDCChipSetFoundation', () => { expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 2, ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + 2, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); it(`#handleChipNavigation does not focus unfocusable actions with ArrowUp`, @@ -1224,7 +1289,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c1', key: 'ArrowUp', isRTL: false, @@ -1234,7 +1299,8 @@ describe('MDCChipSetFoundation', () => { // Verify that the 0th index trailing action is not focused expect(mockAdapter.setChipFocusAtIndex) .not.toHaveBeenCalledWith( - 0, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation focuses the available focusable action with ArrowUp`, @@ -1249,7 +1315,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c1', key: 'ArrowUp', isRTL: false, @@ -1259,7 +1325,8 @@ describe('MDCChipSetFoundation', () => { // Verify that the primary action is focused expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 0, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 0, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation does not focus unfocusable actions with ArrowDown`, @@ -1274,7 +1341,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c0', key: 'ArrowDown', isRTL: false, @@ -1284,7 +1351,8 @@ describe('MDCChipSetFoundation', () => { // Verify that the 0th index trailing action is not focused expect(mockAdapter.setChipFocusAtIndex) .not.toHaveBeenCalledWith( - 1, ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 1, MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); it(`#handleChipNavigation focuses the available focusable action with ArrowDown`, @@ -1299,7 +1367,7 @@ describe('MDCChipSetFoundation', () => { }); foundation.handleChipNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, chipID: 'c0', key: 'ArrowDown', isRTL: false, @@ -1309,27 +1377,28 @@ describe('MDCChipSetFoundation', () => { // Verify that the primary action is focused expect(mockAdapter.setChipFocusAtIndex) .toHaveBeenCalledWith( - 1, ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + 1, MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); - it(`#setChipSelected emits a selection event`, - () => { - const {foundation, mockAdapter} = setupChipSetTest({ - chips: [ - fakeSelectableChip('c0'), - fakeSelectableChip('c1'), - fakeSelectableChip('c2'), - ], - supportsMultiSelection: false, - }); - foundation.setChipSelected(0, ActionType.PRIMARY, true); + it(`#setChipSelected emits a selection event`, () => { + const {foundation, mockAdapter} = setupChipSetTest({ + chips: [ + fakeSelectableChip('c0'), + fakeSelectableChip('c1'), + fakeSelectableChip('c2'), + ], + supportsMultiSelection: false, + }); + foundation.setChipSelected(0, MDCChipActionType.PRIMARY, true); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.SELECTION, { - chipID: 'c0', - chipIndex: 0, - isSelected: true, - }); - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipSetEvents.SELECTION, { + chipID: 'c0', + chipIndex: 0, + isSelected: true, + }); + }); it(`#setChipSelected selects the target chip when not multiselectable`, () => { @@ -1341,10 +1410,10 @@ describe('MDCChipSetFoundation', () => { ], supportsMultiSelection: false, }); - foundation.setChipSelected(0, ActionType.PRIMARY, true); + foundation.setChipSelected(0, MDCChipActionType.PRIMARY, true); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(0, ActionType.PRIMARY, true); + .toHaveBeenCalledWith(0, MDCChipActionType.PRIMARY, true); }); it(`#setChipSelected unselects other chips when not multiselectable`, () => { @@ -1356,12 +1425,12 @@ describe('MDCChipSetFoundation', () => { ], supportsMultiSelection: false, }); - foundation.setChipSelected(0, ActionType.PRIMARY, true); + foundation.setChipSelected(0, MDCChipActionType.PRIMARY, true); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(1, ActionType.PRIMARY, false); + .toHaveBeenCalledWith(1, MDCChipActionType.PRIMARY, false); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(2, ActionType.PRIMARY, false); + .toHaveBeenCalledWith(2, MDCChipActionType.PRIMARY, false); }); it(`#setChipSelected selects the target chip when multiselectable`, () => { @@ -1373,10 +1442,10 @@ describe('MDCChipSetFoundation', () => { ], supportsMultiSelection: true, }); - foundation.setChipSelected(0, ActionType.PRIMARY, true); + foundation.setChipSelected(0, MDCChipActionType.PRIMARY, true); expect(mockAdapter.setChipSelectedAtIndex) - .toHaveBeenCalledWith(0, ActionType.PRIMARY, true); + .toHaveBeenCalledWith(0, MDCChipActionType.PRIMARY, true); }); it(`#setChipSelected does not unselect other chips when multiselectable`, @@ -1389,7 +1458,7 @@ describe('MDCChipSetFoundation', () => { ], supportsMultiSelection: true, }); - foundation.setChipSelected(0, ActionType.PRIMARY, true); + foundation.setChipSelected(0, MDCChipActionType.PRIMARY, true); // Only expect it to be called once for the selection expect(mockAdapter.setChipSelectedAtIndex).toHaveBeenCalledTimes(1); @@ -1405,7 +1474,7 @@ describe('MDCChipSetFoundation', () => { supportsMultiSelection: true, }); - expect(foundation.isChipSelected(0, ActionType.PRIMARY)).toBe(true); + expect(foundation.isChipSelected(0, MDCChipActionType.PRIMARY)).toBe(true); }); it(`#isChipSelected returns false if the chip is not selected`, () => { @@ -1418,7 +1487,7 @@ describe('MDCChipSetFoundation', () => { supportsMultiSelection: true, }); - expect(foundation.isChipSelected(1, ActionType.PRIMARY)).toBe(false); + expect(foundation.isChipSelected(1, MDCChipActionType.PRIMARY)).toBe(false); }); it(`#getSelectedChipIndexes returns the selected chip indexes`, () => { @@ -1448,7 +1517,7 @@ describe('MDCChipSetFoundation', () => { foundation.removeChip(1); expect(mockAdapter.startChipAnimationAtIndex) - .toHaveBeenCalledWith(1, Animation.EXIT); + .toHaveBeenCalledWith(1, MDCChipAnimation.EXIT); }); it(`#removeChip emits the removal event at the given index`, () => { @@ -1462,11 +1531,12 @@ describe('MDCChipSetFoundation', () => { }); foundation.removeChip(1); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.REMOVAL, { - chipID: 'c1', - chipIndex: 1, - isComplete: false, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipSetEvents.REMOVAL, { + chipID: 'c1', + chipIndex: 1, + isComplete: false, + }); }); it(`#removeChip does nothing if the index is out of bounds`, () => { @@ -1497,7 +1567,7 @@ describe('MDCChipSetFoundation', () => { foundation.addChip(0); expect(mockAdapter.startChipAnimationAtIndex) - .toHaveBeenCalledWith(0, Animation.ENTER); + .toHaveBeenCalledWith(0, MDCChipAnimation.ENTER); }); it(`#addChip does nothing if the index is out of bounds`, () => { diff --git a/packages/mdc-chips/chip/README.md b/packages/mdc-chips/chip/README.md index 1a9865d44d9..d3d2ba9ed11 100644 --- a/packages/mdc-chips/chip/README.md +++ b/packages/mdc-chips/chip/README.md @@ -219,7 +219,7 @@ Mixin | Description The `MDCChip` is exposed only to be called by the parent [`MDCChipSet`](../chip-set). Users should not interact with the `MDCChip` component nor rely on any exposed APIs or events. -### `MDCChip` events +### `MDCChipEvents` These events are only emitted for consumption by the parent [`MDCChipSet`](../chip-set). Non-wrapping clients **should not** listen to these events. @@ -233,22 +233,22 @@ Event name | Detail | Description Method Signature | Description --- | --- -`addClass(className: ClassName): void` | Adds the class name to the chip root. -`emitEvent(eventName: Events, eventDetail: D): void` | Emits the given `eventName` with the given `eventDetail`. -`getActions(): ActionType[]` | Returns the actions of the chip in [DOM order](https://developers.google.com/web/fundamentals/accessibility/focus/dom-order-matters). -`getAttribute(attrName: Attributes): string\|null` | Returns the value of the attribute or `null` if it does not exist. +`addClass(className: MDCChipClassNames): void` | Adds the class name to the chip root. +`emitEvent(eventName: MDCChipEvents, eventDetail: D): void` | Emits the given `eventName` with the given `eventDetail`. +`getActions(): MDCChipActionType[]` | Returns the actions of the chip in [DOM order](https://developers.google.com/web/fundamentals/accessibility/focus/dom-order-matters). +`getAttribute(attrName: MDCChipAttributes): string\|null` | Returns the value of the attribute or `null` if it does not exist. `getElementID(): string` | Returns the [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id) of the root element. `getOffsetWidth(): number` | Returns the [`offsetWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth) of the root element. -`hasClass(className: CssClasses): boolean` | Returns `true` if the class exists on the root element, otherwise returns `false`. -`isActionSelectable(action: ActionType): boolean` | Returns the seletability of the action with the given type. -`isActionSelected(action: ActionType): boolean` | Returns the selected state of the action with the given type. -`isActionFocusable(action: ActionType): boolean` | Returns the focusability of the action with the given type. -`isActionDisabled(action: ActionType): boolean` | Returns the disabled state of the action with the given type. +`hasClass(className: MDCChipCssClasses): boolean` | Returns `true` if the class exists on the root element, otherwise returns `false`. +`isActionSelectable(action: MDCChipActionType): boolean` | Returns the seletability of the action with the given type. +`isActionSelected(action: MDCChipActionType): boolean` | Returns the selected state of the action with the given type. +`isActionFocusable(action: MDCChipActionType): boolean` | Returns the focusability of the action with the given type. +`isActionDisabled(action: MDCChipActionType): boolean` | Returns the disabled state of the action with the given type. `isRTL(): boolean` | Returns `true` if the chip is rendered in an RTL context, otherwise returns `false`. -`removeClass(className: CssClasses): void` | Remove the given class from the root. -`setActionDisabled(action: ActionType, isDisabled: boolean): void` | Sets the disabled state of the action with the given type. -`setActionFocus(action: ActionType, behavior: FocusBehavior): void` | Sets the focus behavior of the action with the given type. -`setActionSelected(action: ActionType, isSelected: boolean): void` | Sets the selected state of the action with the given type. +`removeClass(className: MDCChipCssClasses): void` | Remove the given class from the root. +`setActionDisabled(action: MDCChipActionType, isDisabled: boolean): void` | Sets the disabled state of the action with the given type. +`setActionFocus(action: MDCChipActionType, behavior: MDCChipActionFocusBehavior): void` | Sets the focus behavior of the action with the given type. +`setActionSelected(action: MDCChipActionType, isSelected: boolean): void` | Sets the selected state of the action with the given type. `setStyleProperty(property: string, value: string): void` | Sets the style property on the root to the given value. ### `MDCChipFoundation` @@ -263,11 +263,11 @@ Method Signature | Description `setDisabled(isDisabled: boolean): void` | Sets the disabled state of the chip. `isDisabled(): boolean` | Returns the disabled state of the chip. `getActions(): ActionType[]` | Returns the actions of the chip. -`isActionFocusable(action: ActionType): boolean` | Returns the focusability of the given action. -`isActionSelectable(action: ActionType): boolean` | Returns the selectability of the given action. -`isActionSelected(action: ActionType): boolean` | Returns the selected state of the given action. -`setActionFocus(action: ActionType, focus: FocusBehavior): void` | Sets the focus behavior of the given action. -`setActionSelected(action: ActionType, isSelected: boolean): void` | Sets the selected state of the given action. +`isActionFocusable(action: MDCChipActionType): boolean` | Returns the focusability of the given action. +`isActionSelectable(action: MDCChipActionType): boolean` | Returns the selectability of the given action. +`isActionSelected(action: MDCChipActionType): boolean` | Returns the selected state of the given action. +`setActionFocus(action: MDCChipActionType, focus: MDCChipActionFocusBehavior): void` | Sets the focus behavior of the given action. +`setActionSelected(action: MDCChipActionType, isSelected: boolean): void` | Sets the selected state of the given action. `startAnimation(animation: Animation): void` | Starts the given animation on the chip. ### Usage within frameworks diff --git a/packages/mdc-chips/chip/adapter.ts b/packages/mdc-chips/chip/adapter.ts index c05d9357d06..85a2bc11aad 100644 --- a/packages/mdc-chips/chip/adapter.ts +++ b/packages/mdc-chips/chip/adapter.ts @@ -21,8 +21,9 @@ * THE SOFTWARE. */ -import {ActionType, FocusBehavior} from '../action/constants'; -import {Attributes, CssClasses, Events} from './constants'; +import {MDCChipActionFocusBehavior, MDCChipActionType} from '../action/constants'; + +import {MDCChipAttributes, MDCChipCssClasses, MDCChipEvents} from './constants'; /** * Defines the shape of the adapter expected by the foundation. @@ -33,16 +34,16 @@ import {Attributes, CssClasses, Events} from './constants'; */ export interface MDCChipAdapter { /** Adds the given class to the root element. */ - addClass(className: CssClasses): void; + addClass(className: MDCChipCssClasses): void; /** Emits the given event with the given detail. */ - emitEvent(eventName: Events, eventDetail: D): void; + emitEvent(eventName: MDCChipEvents, eventDetail: D): void; /** Returns the child actions provided by the chip. */ - getActions(): ActionType[]; + getActions(): MDCChipActionType[]; /** Returns the value for the given attribute, if it exists. */ - getAttribute(attrName: Attributes): string|null; + getAttribute(attrName: MDCChipAttributes): string|null; /** Returns the ID of the root element. */ getElementID(): string; @@ -51,34 +52,35 @@ export interface MDCChipAdapter { getOffsetWidth(): number; /** Returns true if the root element has the given class. */ - hasClass(className: CssClasses): boolean; + hasClass(className: MDCChipCssClasses): boolean; /** Proxies to the MDCChipAction#isSelectable method. */ - isActionSelectable(action: ActionType): boolean; + isActionSelectable(action: MDCChipActionType): boolean; /** Proxies to the MDCChipAction#isSelected method. */ - isActionSelected(action: ActionType): boolean; + isActionSelected(action: MDCChipActionType): boolean; /** Proxies to the MDCChipAction#isFocusable method. */ - isActionFocusable(action: ActionType): boolean; + isActionFocusable(action: MDCChipActionType): boolean; /** Proxies to the MDCChipAction#isDisabled method. */ - isActionDisabled(action: ActionType): boolean; + isActionDisabled(action: MDCChipActionType): boolean; /** Returns true if the text direction is right-to-left. */ isRTL(): boolean; /** Removes the given class from the root element. */ - removeClass(className: CssClasses): void; + removeClass(className: MDCChipCssClasses): void; /** Proxies to the MDCChipAction#setDisabled method. */ - setActionDisabled(action: ActionType, isDisabled: boolean): void; + setActionDisabled(action: MDCChipActionType, isDisabled: boolean): void; /** Proxies to the MDCChipAction#setFocus method. */ - setActionFocus(action: ActionType, behavior: FocusBehavior): void; + setActionFocus( + action: MDCChipActionType, behavior: MDCChipActionFocusBehavior): void; /** Proxies to the MDCChipAction#setSelected method. */ - setActionSelected(action: ActionType, isSelected: boolean): void; + setActionSelected(action: MDCChipActionType, isSelected: boolean): void; /** Sets the style property to the given value. */ setStyleProperty(property: string, value: string): void; diff --git a/packages/mdc-chips/chip/component.ts b/packages/mdc-chips/chip/component.ts index 1ebdd1b0cea..e05a713b63f 100644 --- a/packages/mdc-chips/chip/component.ts +++ b/packages/mdc-chips/chip/component.ts @@ -25,10 +25,10 @@ import {MDCComponent} from '@material/base/component'; import {CustomEventListener} from '@material/base/types'; import {MDCChipAction, MDCChipActionFactory} from '../action/component'; -import {ActionType, Events, FocusBehavior} from '../action/constants'; +import {MDCChipActionEvents, MDCChipActionFocusBehavior, MDCChipActionType} from '../action/constants'; import {MDCChipAdapter} from './adapter'; -import {Animation} from './constants'; +import {MDCChipAnimation} from './constants'; import {MDCChipFoundation} from './foundation'; import {ActionInteractionEvent, ActionNavigationEvent} from './types'; @@ -36,36 +36,35 @@ import {ActionInteractionEvent, ActionNavigationEvent} from './types'; * MDCChipFactory is used by the parent MDCChipSet component to initialize * chips. */ -export type MDCChipFactory = (el: Element, foundation?: MDCChipFoundation) => - MDCChip; +export type MDCChipFactory = + (el: HTMLElement, foundation?: MDCChipFoundation) => MDCChip; /** * MDCChip provides component encapsulation of the foundation implementation. */ export class MDCChip extends MDCComponent { - static attachTo(root: Element): MDCChip { + static override attachTo(root: HTMLElement): MDCChip { return new MDCChip(root); } - private readonly rootHTML = this.root as HTMLElement; - // Below properties are all assigned in #initialize() private handleActionInteraction!: CustomEventListener; private handleActionNavigation!: CustomEventListener; - private actions!: Map; + private actions!: Map; - initialize( + override initialize( actionFactory: - MDCChipActionFactory = (el: Element) => new MDCChipAction(el)) { + MDCChipActionFactory = (el: HTMLElement) => new MDCChipAction(el)) { this.actions = new Map(); - const actionEls = this.root.querySelectorAll('.mdc-evolution-chip__action'); + const actionEls = + this.root.querySelectorAll('.mdc-evolution-chip__action'); for (let i = 0; i < actionEls.length; i++) { const action = actionFactory(actionEls[i]); this.actions.set(action.actionType(), action); } } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.handleActionInteraction = (event) => { this.foundation.handleActionInteraction(event); }; @@ -74,17 +73,18 @@ export class MDCChip extends MDCComponent { this.foundation.handleActionNavigation(event); }; - this.listen(Events.INTERACTION, this.handleActionInteraction); - this.listen(Events.NAVIGATION, this.handleActionNavigation); + this.listen(MDCChipActionEvents.INTERACTION, this.handleActionInteraction); + this.listen(MDCChipActionEvents.NAVIGATION, this.handleActionNavigation); } - destroy() { - this.unlisten(Events.INTERACTION, this.handleActionInteraction); - this.unlisten(Events.NAVIGATION, this.handleActionNavigation); + override destroy() { + this.unlisten( + MDCChipActionEvents.INTERACTION, this.handleActionInteraction); + this.unlisten(MDCChipActionEvents.NAVIGATION, this.handleActionNavigation); super.destroy(); } - getDefaultFoundation() { + override getDefaultFoundation() { // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. @@ -96,40 +96,40 @@ export class MDCChip extends MDCComponent { this.emit(eventName, eventDetail, true /* shouldBubble */); }, getActions: () => { - const actions: ActionType[] = []; + const actions: MDCChipActionType[] = []; for (const [key] of this.actions) { actions.push(key); } return actions; }, getAttribute: (attrName) => this.root.getAttribute(attrName), - getElementID: () => this.rootHTML.id, + getElementID: () => this.root.id, getOffsetWidth: () => { - return this.rootHTML.offsetWidth; + return this.root.offsetWidth; }, hasClass: (className) => this.root.classList.contains(className), - isActionSelectable: (actionType: ActionType) => { + isActionSelectable: (actionType: MDCChipActionType) => { const action = this.actions.get(actionType); if (action) { return action.isSelectable(); } return false; }, - isActionSelected: (actionType: ActionType) => { + isActionSelected: (actionType: MDCChipActionType) => { const action = this.actions.get(actionType); if (action) { return action.isSelected(); } return false; }, - isActionFocusable: (actionType: ActionType) => { + isActionFocusable: (actionType: MDCChipActionType) => { const action = this.actions.get(actionType); if (action) { return action.isFocusable(); } return false; }, - isActionDisabled: (actionType: ActionType) => { + isActionDisabled: (actionType: MDCChipActionType) => { const action = this.actions.get(actionType); if (action) { return action.isDisabled(); @@ -141,26 +141,30 @@ export class MDCChip extends MDCComponent { removeClass: (className) => { this.root.classList.remove(className); }, - setActionDisabled: (actionType: ActionType, isDisabled: boolean) => { - const action = this.actions.get(actionType); - if (action) { - action.setDisabled(isDisabled); - } - }, - setActionFocus: (actionType: ActionType, behavior: FocusBehavior) => { - const action = this.actions.get(actionType); - if (action) { - action.setFocus(behavior); - } - }, - setActionSelected: (actionType: ActionType, isSelected: boolean) => { - const action = this.actions.get(actionType); - if (action) { - action.setSelected(isSelected); - } - }, + setActionDisabled: + (actionType: MDCChipActionType, isDisabled: boolean) => { + const action = this.actions.get(actionType); + if (action) { + action.setDisabled(isDisabled); + } + }, + setActionFocus: + (actionType: MDCChipActionType, + behavior: MDCChipActionFocusBehavior) => { + const action = this.actions.get(actionType); + if (action) { + action.setFocus(behavior); + } + }, + setActionSelected: + (actionType: MDCChipActionType, isSelected: boolean) => { + const action = this.actions.get(actionType); + if (action) { + action.setSelected(isSelected); + } + }, setStyleProperty: (prop: string, value: string) => { - this.rootHTML.style.setProperty(prop, value); + this.root.style.setProperty(prop, value); }, }; @@ -176,8 +180,8 @@ export class MDCChip extends MDCComponent { } } - /** Returns the ActionTypes for the encapsulated actions. */ - getActions(): ActionType[] { + /** Returns the MDCChipActionTypes for the encapsulated actions. */ + getActions(): MDCChipActionType[] { return this.foundation.getActions(); } @@ -195,32 +199,32 @@ export class MDCChip extends MDCComponent { } /** Returns the focusability of the action. */ - isActionFocusable(action: ActionType): boolean { + isActionFocusable(action: MDCChipActionType): boolean { return this.foundation.isActionFocusable(action); } /** Returns the selectability of the action. */ - isActionSelectable(action: ActionType): boolean { + isActionSelectable(action: MDCChipActionType): boolean { return this.foundation.isActionSelectable(action); } /** Returns the selected state of the action. */ - isActionSelected(action: ActionType): boolean { + isActionSelected(action: MDCChipActionType): boolean { return this.foundation.isActionSelected(action); } /** Sets the focus behavior of the action. */ - setActionFocus(action: ActionType, focus: FocusBehavior) { + setActionFocus(action: MDCChipActionType, focus: MDCChipActionFocusBehavior) { this.foundation.setActionFocus(action, focus); } /** Sets the selected state of the action. */ - setActionSelected(action: ActionType, isSelected: boolean) { + setActionSelected(action: MDCChipActionType, isSelected: boolean) { this.foundation.setActionSelected(action, isSelected); } /** Starts the animation on the chip. */ - startAnimation(animation: Animation) { + startAnimation(animation: MDCChipAnimation) { this.foundation.startAnimation(animation); } } diff --git a/packages/mdc-chips/chip/constants.ts b/packages/mdc-chips/chip/constants.ts index 35c5501639f..8d9fc562ea1 100644 --- a/packages/mdc-chips/chip/constants.ts +++ b/packages/mdc-chips/chip/constants.ts @@ -22,9 +22,9 @@ */ /** - * CssClasses provides the named constants for class names. + * MDCChipCssClasses provides the named constants for class names. */ -export enum CssClasses { +export enum MDCChipCssClasses { SELECTING = 'mdc-evolution-chip--selecting', DESELECTING = 'mdc-evolution-chip--deselecting', SELECTING_WITH_PRIMARY_ICON = @@ -40,26 +40,27 @@ export enum CssClasses { } /** - * Events provides the named constants for emitted events. + * MDCChipEvents provides the named constants for emitted events. */ -export enum Events { +export enum MDCChipEvents { INTERACTION = 'MDCChip:interaction', NAVIGATION = 'MDCChip:navigation', ANIMATION = 'MDCChip:animation', } /** - * Events provides the named constants for strings used by the foundation. + * MDCChipAttributes provides the named constants for strings used by the + * foundation. */ -export enum Attributes { +export enum MDCChipAttributes { DATA_REMOVED_ANNOUNCEMENT = 'data-mdc-removed-announcement', DATA_ADDED_ANNOUNCEMENT = 'data-mdc-added-announcement', } /** - * Animation provides the names of runnable animations. + * MDCChipAnimation provides the names of runnable animations. */ -export enum Animation { +export enum MDCChipAnimation { ENTER = 'mdc-evolution-chip-enter', EXIT = 'mdc-evolution-chip-exit', } diff --git a/packages/mdc-chips/chip/foundation.ts b/packages/mdc-chips/chip/foundation.ts index 78996de4b9a..5833ca07423 100644 --- a/packages/mdc-chips/chip/foundation.ts +++ b/packages/mdc-chips/chip/foundation.ts @@ -25,16 +25,16 @@ import {AnimationFrame} from '@material/animation/animationframe'; import {MDCFoundation} from '@material/base/foundation'; import {KEY} from '@material/dom/keyboard'; -import {ActionType, FocusBehavior, InteractionTrigger} from '../action/constants'; +import {MDCChipActionFocusBehavior, MDCChipActionInteractionTrigger, MDCChipActionType} from '../action/constants'; import {MDCChipActionInteractionEventDetail} from '../action/types'; import {MDCChipAdapter} from './adapter'; -import {Animation, Attributes, CssClasses, Events} from './constants'; +import {MDCChipAnimation, MDCChipAttributes, MDCChipCssClasses, MDCChipEvents} from './constants'; import {ActionInteractionEvent, ActionNavigationEvent, MDCChipAnimationEventDetail, MDCChipInteractionEventDetail, MDCChipNavigationEventDetail} from './types'; interface Navigation { - from: ActionType; - to: ActionType; + from: MDCChipActionType; + to: MDCChipActionType; } enum Direction { @@ -52,7 +52,7 @@ enum AnimationKeys { * MDCChipFoundation provides a foundation for all chips. */ export class MDCChipFoundation extends MDCFoundation { - static get defaultAdapter(): MDCChipAdapter { + static override get defaultAdapter(): MDCChipAdapter { return { addClass: () => undefined, emitEvent: () => undefined, @@ -81,7 +81,7 @@ export class MDCChipFoundation extends MDCFoundation { this.animFrame = new AnimationFrame(); } - destroy() { + override destroy() { this.animFrame.cancelAll(); } @@ -96,9 +96,9 @@ export class MDCChipFoundation extends MDCFoundation { } if (isDisabled) { - this.adapter.addClass(CssClasses.DISABLED); + this.adapter.addClass(MDCChipCssClasses.DISABLED); } else { - this.adapter.removeClass(CssClasses.DISABLED); + this.adapter.removeClass(MDCChipCssClasses.DISABLED); } } @@ -112,59 +112,60 @@ export class MDCChipFoundation extends MDCFoundation { return false; } - getActions(): ActionType[] { + getActions(): MDCChipActionType[] { return this.adapter.getActions(); } - isActionFocusable(action: ActionType): boolean { + isActionFocusable(action: MDCChipActionType): boolean { return this.adapter.isActionFocusable(action); } - isActionSelectable(action: ActionType): boolean { + isActionSelectable(action: MDCChipActionType): boolean { return this.adapter.isActionSelectable(action); } - isActionSelected(action: ActionType): boolean { + isActionSelected(action: MDCChipActionType): boolean { return this.adapter.isActionSelected(action); } - setActionFocus(action: ActionType, focus: FocusBehavior) { + setActionFocus(action: MDCChipActionType, focus: MDCChipActionFocusBehavior) { this.adapter.setActionFocus(action, focus); } - setActionSelected(action: ActionType, isSelected: boolean) { + setActionSelected(action: MDCChipActionType, isSelected: boolean) { this.adapter.setActionSelected(action, isSelected); this.animateSelection(isSelected); } - startAnimation(animation: Animation) { - if (animation === Animation.ENTER) { - this.adapter.addClass(CssClasses.ENTER); + startAnimation(animation: MDCChipAnimation) { + if (animation === MDCChipAnimation.ENTER) { + this.adapter.addClass(MDCChipCssClasses.ENTER); return; } - if (animation === Animation.EXIT) { - this.adapter.addClass(CssClasses.EXIT); + if (animation === MDCChipAnimation.EXIT) { + this.adapter.addClass(MDCChipCssClasses.EXIT); return; } } handleAnimationEnd(event: AnimationEvent) { const {animationName} = event; - if (animationName === Animation.ENTER) { - this.adapter.removeClass(CssClasses.ENTER); - this.adapter.emitEvent(Events.ANIMATION, { - chipID: this.getElementID(), - animation: Animation.ENTER, - addedAnnouncement: this.getAddedAnnouncement(), - isComplete: true, - }); + if (animationName === MDCChipAnimation.ENTER) { + this.adapter.removeClass(MDCChipCssClasses.ENTER); + this.adapter.emitEvent( + MDCChipEvents.ANIMATION, { + chipID: this.getElementID(), + animation: MDCChipAnimation.ENTER, + addedAnnouncement: this.getAddedAnnouncement(), + isComplete: true, + }); return; } - if (animationName === Animation.EXIT) { - this.adapter.removeClass(CssClasses.EXIT); - this.adapter.addClass(CssClasses.HIDDEN); + if (animationName === MDCChipAnimation.EXIT) { + this.adapter.removeClass(MDCChipCssClasses.EXIT); + this.adapter.addClass(MDCChipCssClasses.HIDDEN); const width = this.adapter.getOffsetWidth(); this.adapter.setStyleProperty('width', `${width}px`); // Wait two frames so the width gets applied correctly. @@ -177,14 +178,15 @@ export class MDCChipFoundation extends MDCFoundation { } handleTransitionEnd() { - if (!this.adapter.hasClass(CssClasses.HIDDEN)) return; - - this.adapter.emitEvent(Events.ANIMATION, { - chipID: this.getElementID(), - animation: Animation.EXIT, - removedAnnouncement: this.getRemovedAnnouncement(), - isComplete: true, - }); + if (!this.adapter.hasClass(MDCChipCssClasses.HIDDEN)) return; + + this.adapter.emitEvent( + MDCChipEvents.ANIMATION, { + chipID: this.getElementID(), + animation: MDCChipAnimation.EXIT, + removedAnnouncement: this.getRemovedAnnouncement(), + isComplete: true, + }); } handleActionInteraction({detail}: ActionInteractionEvent) { @@ -192,47 +194,49 @@ export class MDCChipFoundation extends MDCFoundation { const isSelectable = this.adapter.isActionSelectable(source); const isSelected = this.adapter.isActionSelected(source); - this.adapter.emitEvent(Events.INTERACTION, { - chipID: this.getElementID(), - shouldRemove: this.shouldRemove(detail), - actionID, - isSelectable, - isSelected, - source, - }); + this.adapter.emitEvent( + MDCChipEvents.INTERACTION, { + chipID: this.getElementID(), + shouldRemove: this.shouldRemove(detail), + actionID, + isSelectable, + isSelected, + source, + }); } handleActionNavigation({detail}: ActionNavigationEvent) { const {source, key} = detail; const isRTL = this.adapter.isRTL(); const isTrailingActionFocusable = - this.adapter.isActionFocusable(ActionType.TRAILING); + this.adapter.isActionFocusable(MDCChipActionType.TRAILING); const isPrimaryActionFocusable = - this.adapter.isActionFocusable(ActionType.PRIMARY); + this.adapter.isActionFocusable(MDCChipActionType.PRIMARY); const dir = this.directionFromKey(key, isRTL); - const shouldNavigateToTrailing = source === ActionType.PRIMARY && + const shouldNavigateToTrailing = source === MDCChipActionType.PRIMARY && dir === Direction.RIGHT && isTrailingActionFocusable; - const shouldNavigateToPrimary = source === ActionType.TRAILING && + const shouldNavigateToPrimary = source === MDCChipActionType.TRAILING && dir === Direction.LEFT && isPrimaryActionFocusable; if (shouldNavigateToTrailing) { - this.navigateActions({from: source, to: ActionType.TRAILING}); + this.navigateActions({from: source, to: MDCChipActionType.TRAILING}); return; } if (shouldNavigateToPrimary) { - this.navigateActions({from: source, to: ActionType.PRIMARY}); + this.navigateActions({from: source, to: MDCChipActionType.PRIMARY}); return; } - this.adapter.emitEvent(Events.NAVIGATION, { - chipID: this.getElementID(), - isRTL, - source, - key, - }); + this.adapter.emitEvent( + MDCChipEvents.NAVIGATION, { + chipID: this.getElementID(), + isRTL, + source, + key, + }); } private directionFromKey(key: string, isRTL: boolean): Direction { @@ -250,27 +254,31 @@ export class MDCChipFoundation extends MDCFoundation { } private navigateActions(nav: Navigation) { - this.adapter.setActionFocus(nav.from, FocusBehavior.NOT_FOCUSABLE); - this.adapter.setActionFocus(nav.to, FocusBehavior.FOCUSABLE_AND_FOCUSED); + this.adapter.setActionFocus( + nav.from, MDCChipActionFocusBehavior.NOT_FOCUSABLE); + this.adapter.setActionFocus( + nav.to, MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); } private shouldRemove({source, trigger}: MDCChipActionInteractionEventDetail): boolean { - if (trigger === InteractionTrigger.BACKSPACE_KEY || - trigger === InteractionTrigger.DELETE_KEY) { + if (trigger === MDCChipActionInteractionTrigger.BACKSPACE_KEY || + trigger === MDCChipActionInteractionTrigger.DELETE_KEY) { return true; } - return source === ActionType.TRAILING; + return source === MDCChipActionType.TRAILING; } private getRemovedAnnouncement(): string|undefined { - const msg = this.adapter.getAttribute(Attributes.DATA_REMOVED_ANNOUNCEMENT); + const msg = + this.adapter.getAttribute(MDCChipAttributes.DATA_REMOVED_ANNOUNCEMENT); return msg || undefined; } private getAddedAnnouncement(): string|undefined { - const msg = this.adapter.getAttribute(Attributes.DATA_ADDED_ANNOUNCEMENT); + const msg = + this.adapter.getAttribute(MDCChipAttributes.DATA_ADDED_ANNOUNCEMENT); return msg || undefined; } @@ -285,42 +293,42 @@ export class MDCChipFoundation extends MDCFoundation { } private resetAnimationStyles() { - this.adapter.removeClass(CssClasses.SELECTING); - this.adapter.removeClass(CssClasses.DESELECTING); - this.adapter.removeClass(CssClasses.SELECTING_WITH_PRIMARY_ICON); - this.adapter.removeClass(CssClasses.DESELECTING_WITH_PRIMARY_ICON); + this.adapter.removeClass(MDCChipCssClasses.SELECTING); + this.adapter.removeClass(MDCChipCssClasses.DESELECTING); + this.adapter.removeClass(MDCChipCssClasses.SELECTING_WITH_PRIMARY_ICON); + this.adapter.removeClass(MDCChipCssClasses.DESELECTING_WITH_PRIMARY_ICON); } private updateSelectionStyles(isSelected: boolean) { - const hasIcon = this.adapter.hasClass(CssClasses.WITH_PRIMARY_ICON); + const hasIcon = this.adapter.hasClass(MDCChipCssClasses.WITH_PRIMARY_ICON); if (hasIcon && isSelected) { - this.adapter.addClass(CssClasses.SELECTING_WITH_PRIMARY_ICON); + this.adapter.addClass(MDCChipCssClasses.SELECTING_WITH_PRIMARY_ICON); this.animFrame.request(AnimationKeys.SELECTION, () => { - this.adapter.addClass(CssClasses.SELECTED); + this.adapter.addClass(MDCChipCssClasses.SELECTED); }); return; } if (hasIcon && !isSelected) { - this.adapter.addClass(CssClasses.DESELECTING_WITH_PRIMARY_ICON); + this.adapter.addClass(MDCChipCssClasses.DESELECTING_WITH_PRIMARY_ICON); this.animFrame.request(AnimationKeys.SELECTION, () => { - this.adapter.removeClass(CssClasses.SELECTED); + this.adapter.removeClass(MDCChipCssClasses.SELECTED); }); return; } if (isSelected) { - this.adapter.addClass(CssClasses.SELECTING); + this.adapter.addClass(MDCChipCssClasses.SELECTING); this.animFrame.request(AnimationKeys.SELECTION, () => { - this.adapter.addClass(CssClasses.SELECTED); + this.adapter.addClass(MDCChipCssClasses.SELECTED); }); return; } if (!isSelected) { - this.adapter.addClass(CssClasses.DESELECTING); + this.adapter.addClass(MDCChipCssClasses.DESELECTING); this.animFrame.request(AnimationKeys.SELECTION, () => { - this.adapter.removeClass(CssClasses.SELECTED); + this.adapter.removeClass(MDCChipCssClasses.SELECTED); }); return; } diff --git a/packages/mdc-chips/chip/index.ts b/packages/mdc-chips/chip/index.ts index ac92b9a0b32..605cac4a233 100644 --- a/packages/mdc-chips/chip/index.ts +++ b/packages/mdc-chips/chip/index.ts @@ -25,3 +25,4 @@ export * from './adapter'; export * from './component'; export * from './constants'; export * from './foundation'; +export * from './types'; diff --git a/packages/mdc-chips/chip/test/component.test.ts b/packages/mdc-chips/chip/test/component.test.ts index 05e5e7cb5fd..05af7cdc7e2 100644 --- a/packages/mdc-chips/chip/test/component.test.ts +++ b/packages/mdc-chips/chip/test/component.test.ts @@ -21,10 +21,11 @@ * THE SOFTWARE. */ +import {createFixture, html} from '../../../../testing/dom'; import {createKeyboardEvent, emitEvent} from '../../../../testing/dom/events'; import {setUpMdcTestEnvironment} from '../../../../testing/helpers/setup'; -import {ActionType, FocusBehavior} from '../../action/constants'; -import {Animation, CssClasses, Events, MDCChip} from '../index'; +import {MDCChipActionFocusBehavior, MDCChipActionType} from '../../action/constants'; +import {MDCChip, MDCChipAnimation, MDCChipCssClasses, MDCChipEvents} from '../index'; interface ActionOptions { readonly isFocusable: boolean; @@ -51,16 +52,11 @@ function actionFixture( } function getFixture({primary, trailing, id}: TestOptions): HTMLElement { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` -
+ return createFixture(html` +
${actionFixture(primary)} ${trailing === undefined ? '' : actionFixture(trailing, true)} -
`; - const el = wrapper.firstElementChild as HTMLElement; - el.id = id; - wrapper.removeChild(el); - return el; +
`); } function setupTest(options: TestOptions) { @@ -88,21 +84,22 @@ describe('MDCChipAction', () => { id: 'c0', }); - const primaryActionEl = root.querySelector('.mdc-evolution-chip__action')!; + const primaryActionEl = + root.querySelector('.mdc-evolution-chip__action')!; const interactionHandler = jasmine.createSpy('emitInteractionHandler'); - component.listen(Events.INTERACTION, interactionHandler); + component.listen(MDCChipEvents.INTERACTION, interactionHandler); emitEvent(primaryActionEl, 'click', { bubbles: true, }); - component.unlisten(Events.INTERACTION, interactionHandler); + component.unlisten(MDCChipEvents.INTERACTION, interactionHandler); expect(interactionHandler).toHaveBeenCalled(); const navigationHandler = jasmine.createSpy('emitNavigationHandler'); - component.listen(Events.NAVIGATION, navigationHandler); + component.listen(MDCChipEvents.NAVIGATION, navigationHandler); primaryActionEl.dispatchEvent(createKeyboardEvent('keydown', { key: 'ArrowLeft', })); - component.unlisten(Events.NAVIGATION, navigationHandler); + component.unlisten(MDCChipEvents.NAVIGATION, navigationHandler); expect(navigationHandler).toHaveBeenCalled(); }); @@ -114,21 +111,22 @@ describe('MDCChipAction', () => { }); component.destroy(); - const primaryActionEl = root.querySelector('.mdc-evolution-chip__action')!; + const primaryActionEl = + root.querySelector('.mdc-evolution-chip__action')!; const interactionHandler = jasmine.createSpy('emitInteractionHandler'); - component.listen(Events.INTERACTION, interactionHandler); + component.listen(MDCChipEvents.INTERACTION, interactionHandler); emitEvent(primaryActionEl, 'click', { bubbles: true, }); - component.unlisten(Events.INTERACTION, interactionHandler); + component.unlisten(MDCChipEvents.INTERACTION, interactionHandler); expect(interactionHandler).not.toHaveBeenCalled(); const navigationHandler = jasmine.createSpy('emitNavigationHandler'); - component.listen(Events.NAVIGATION, navigationHandler); + component.listen(MDCChipEvents.NAVIGATION, navigationHandler); primaryActionEl.dispatchEvent(createKeyboardEvent('keydown', { key: 'ArrowLeft', })); - component.unlisten(Events.NAVIGATION, navigationHandler); + component.unlisten(MDCChipEvents.NAVIGATION, navigationHandler); expect(navigationHandler).not.toHaveBeenCalled(); }); @@ -140,7 +138,7 @@ describe('MDCChipAction', () => { }); expect(component.getActions()).toEqual([ - ActionType.PRIMARY, ActionType.TRAILING + MDCChipActionType.PRIMARY, MDCChipActionType.TRAILING ]); }); @@ -161,7 +159,7 @@ describe('MDCChipAction', () => { id: 'c0', }); - expect(component.isActionFocusable(ActionType.PRIMARY)).toBe(true); + expect(component.isActionFocusable(MDCChipActionType.PRIMARY)).toBe(true); }); it(`#isActionSelectable() returns true when configured`, () => { @@ -171,7 +169,7 @@ describe('MDCChipAction', () => { id: 'c0', }); - expect(component.isActionSelectable(ActionType.PRIMARY)).toBe(true); + expect(component.isActionSelectable(MDCChipActionType.PRIMARY)).toBe(true); }); it(`#isActionSelected() returns true when configured`, () => { @@ -181,8 +179,8 @@ describe('MDCChipAction', () => { id: 'c0', }); - component.setActionSelected(ActionType.PRIMARY, true); - expect(component.isActionSelected(ActionType.PRIMARY)).toBe(true); + component.setActionSelected(MDCChipActionType.PRIMARY, true); + expect(component.isActionSelected(MDCChipActionType.PRIMARY)).toBe(true); }); it(`#setActionFocus() updates the focus when configured`, () => { @@ -191,9 +189,10 @@ describe('MDCChipAction', () => { id: 'c0', }); - component.setActionFocus(ActionType.PRIMARY, FocusBehavior.FOCUSABLE); - expect(root.querySelector('.mdc-evolution-chip__action')!.getAttribute( - 'tabindex')) + component.setActionFocus( + MDCChipActionType.PRIMARY, MDCChipActionFocusBehavior.FOCUSABLE); + expect(root.querySelector( + '.mdc-evolution-chip__action')!.getAttribute('tabindex')) .toBe('0'); }); @@ -203,9 +202,10 @@ describe('MDCChipAction', () => { id: 'c0', }); - component.setActionSelected(ActionType.PRIMARY, true); - expect(root.querySelector('.mdc-evolution-chip__action')!.getAttribute( - 'aria-selected')) + component.setActionSelected(MDCChipActionType.PRIMARY, true); + expect( + root.querySelector( + '.mdc-evolution-chip__action')!.getAttribute('aria-selected')) .toBe('true'); }); @@ -260,18 +260,18 @@ describe('MDCChipAction', () => { const width = root.offsetWidth; // tslint:disable-next-line:no-any (component as any).foundation.handleAnimationEnd({ - animationName: Animation.EXIT, + animationName: MDCChipAnimation.EXIT, }); expect(root.style.getPropertyValue('width')).toBe(`${width}px`); }); - it(`#startAnimation() starts the animation`, () => { + it(`#startMDCChipAnimation() starts the animation`, () => { const {component, root} = setupTest({ primary: {isFocusable: true, isSelectable: true, isDisabled: false}, id: 'c0', }); - component.startAnimation(Animation.ENTER); - expect(root.classList.contains(CssClasses.ENTER)).toBeTrue(); + component.startAnimation(MDCChipAnimation.ENTER); + expect(root).toHaveClass(MDCChipCssClasses.ENTER); }); }); diff --git a/packages/mdc-chips/chip/test/foundation.test.ts b/packages/mdc-chips/chip/test/foundation.test.ts index 8b13ab484af..984a5b3e3fd 100644 --- a/packages/mdc-chips/chip/test/foundation.test.ts +++ b/packages/mdc-chips/chip/test/foundation.test.ts @@ -23,8 +23,8 @@ import {KEY} from '../../../mdc-dom/keyboard'; import {setUpFoundationTest, setUpMdcTestEnvironment} from '../../../../testing/helpers/setup'; -import {ActionType, FocusBehavior, InteractionTrigger} from '../../action/constants'; -import {Animation, Attributes, CssClasses, Events} from '../constants'; +import {MDCChipActionFocusBehavior, MDCChipActionInteractionTrigger, MDCChipActionType} from '../../action/constants'; +import {MDCChipAnimation, MDCChipAttributes, MDCChipCssClasses, MDCChipEvents} from '../constants'; import {MDCChipFoundation} from '../foundation'; import {ActionInteractionEvent, ActionNavigationEvent} from '../types'; @@ -44,252 +44,274 @@ describe('MDCChipFoundation', () => { it(`#getActions() returns the adapter's return value`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getActions.and.returnValue([ActionType.UNSPECIFIED]); - expect(foundation.getActions()).toEqual([ActionType.UNSPECIFIED]); + mockAdapter.getActions.and.returnValue([MDCChipActionType.UNSPECIFIED]); + expect(foundation.getActions()).toEqual([MDCChipActionType.UNSPECIFIED]); }); it(`#isActionFocusable() returns the adapter's return value`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.isActionFocusable.and.returnValue(true); - expect(foundation.isActionFocusable(ActionType.UNSPECIFIED)).toBe(true); + expect(foundation.isActionFocusable(MDCChipActionType.UNSPECIFIED)) + .toBe(true); }); it(`#isActionSelectable() returns the adapter's return value`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.isActionSelectable.and.returnValue(true); - expect(foundation.isActionSelectable(ActionType.UNSPECIFIED)).toBe(true); + expect(foundation.isActionSelectable(MDCChipActionType.UNSPECIFIED)) + .toBe(true); }); it(`#isActionSelected() returns the adapter's return value`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.isActionSelected.and.returnValue(true); - expect(foundation.isActionSelected(ActionType.UNSPECIFIED)).toBe(true); + expect(foundation.isActionSelected(MDCChipActionType.UNSPECIFIED)) + .toBe(true); }); it(`#setActionFocus(` + - `${ActionType.UNSPECIFIED}, ${FocusBehavior.FOCUSABLE_AND_FOCUSED})` + + `${MDCChipActionType.UNSPECIFIED}, ${ + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED})` + ` updates the action focus`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionFocus(ActionType.UNSPECIFIED, FocusBehavior.FOCUSABLE_AND_FOCUSED); + foundation.setActionFocus( + MDCChipActionType.UNSPECIFIED, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); expect(mockAdapter.setActionFocus) - .toHaveBeenCalledWith(ActionType.UNSPECIFIED, FocusBehavior.FOCUSABLE_AND_FOCUSED); + .toHaveBeenCalledWith( + MDCChipActionType.UNSPECIFIED, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); }); - it(`#setActionSelected(${ActionType.UNSPECIFIED}, true) updates` + + it(`#setActionSelected(${MDCChipActionType.UNSPECIFIED}, true) updates` + ` the action selection`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); expect(mockAdapter.setActionSelected) - .toHaveBeenCalledWith(ActionType.UNSPECIFIED, true); + .toHaveBeenCalledWith(MDCChipActionType.UNSPECIFIED, true); }); it(`sequential calls to #setActionSelected() only modify the DOM once`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); - foundation.setActionSelected(ActionType.UNSPECIFIED, false); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, false); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); jasmine.clock().tick(3); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.SELECTING); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.SELECTED); + expect(mockAdapter.addClass) + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTING); + expect(mockAdapter.addClass) + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTED); expect(mockAdapter.addClass).toHaveBeenCalledTimes(2); }); it('#destroy() cancels selection animation frames', () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); foundation.destroy(); jasmine.clock().tick(3); - expect(mockAdapter.addClass).not.toHaveBeenCalledWith(CssClasses.SELECTING); - expect(mockAdapter.addClass).not.toHaveBeenCalledWith(CssClasses.SELECTED); + expect(mockAdapter.addClass) + .not.toHaveBeenCalledWith(MDCChipCssClasses.SELECTING); + expect(mockAdapter.addClass) + .not.toHaveBeenCalledWith(MDCChipCssClasses.SELECTED); }); it(`#setActionSelected(${ - ActionType.UNSPECIFIED}, true) adds the selected class`, + MDCChipActionType.UNSPECIFIED}, true) adds the selected class`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); jasmine.clock().tick(3); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.SELECTED); + expect(mockAdapter.addClass) + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTED); }); it(`#setActionSelected(${ - ActionType.UNSPECIFIED}, false) removes the selected class`, + MDCChipActionType.UNSPECIFIED}, false) removes the selected class`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionSelected(ActionType.UNSPECIFIED, false); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, false); jasmine.clock().tick(3); expect(mockAdapter.removeClass) - .toHaveBeenCalledWith(CssClasses.SELECTED); + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTED); }); it(`#setActionSelected(${ - ActionType.UNSPECIFIED}, true) removes all animating classes`, + MDCChipActionType.UNSPECIFIED}, true) removes all animating classes`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); expect(mockAdapter.removeClass) - .toHaveBeenCalledWith(CssClasses.SELECTING); + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTING); expect(mockAdapter.removeClass) - .toHaveBeenCalledWith(CssClasses.DESELECTING); + .toHaveBeenCalledWith(MDCChipCssClasses.DESELECTING); expect(mockAdapter.removeClass) - .toHaveBeenCalledWith(CssClasses.SELECTING_WITH_PRIMARY_ICON); + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTING_WITH_PRIMARY_ICON); expect(mockAdapter.removeClass) - .toHaveBeenCalledWith(CssClasses.DESELECTING_WITH_PRIMARY_ICON); + .toHaveBeenCalledWith( + MDCChipCssClasses.DESELECTING_WITH_PRIMARY_ICON); }); it(`#setActionSelected(${ - ActionType + MDCChipActionType .UNSPECIFIED}, true) adds the selecting class when no primary icon is present`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.hasClass.withArgs(CssClasses.WITH_PRIMARY_ICON) + mockAdapter.hasClass.withArgs(MDCChipCssClasses.WITH_PRIMARY_ICON) .and.returnValue(false); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); jasmine.clock().tick(2); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.SELECTING); + expect(mockAdapter.addClass) + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTING); }); it(`#setActionSelected(${ - ActionType + MDCChipActionType .UNSPECIFIED}, true) adds the selecting with icon class when the primary icon is present`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.hasClass.withArgs(CssClasses.WITH_PRIMARY_ICON) + mockAdapter.hasClass.withArgs(MDCChipCssClasses.WITH_PRIMARY_ICON) .and.returnValue(true); - foundation.setActionSelected(ActionType.UNSPECIFIED, true); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, true); jasmine.clock().tick(2); expect(mockAdapter.addClass) - .toHaveBeenCalledWith(CssClasses.SELECTING_WITH_PRIMARY_ICON); + .toHaveBeenCalledWith(MDCChipCssClasses.SELECTING_WITH_PRIMARY_ICON); }); it(`#setActionSelected(${ - ActionType + MDCChipActionType .UNSPECIFIED}, false) adds the deselecting class when no primary icon is present`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.hasClass.withArgs(CssClasses.WITH_PRIMARY_ICON) + mockAdapter.hasClass.withArgs(MDCChipCssClasses.WITH_PRIMARY_ICON) .and.returnValue(false); - foundation.setActionSelected(ActionType.UNSPECIFIED, false); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, false); jasmine.clock().tick(2); expect(mockAdapter.addClass) - .toHaveBeenCalledWith(CssClasses.DESELECTING); + .toHaveBeenCalledWith(MDCChipCssClasses.DESELECTING); }); it(`#setActionSelected(${ - ActionType + MDCChipActionType .UNSPECIFIED}, false) adds the deelecting with icon class when the primary icon is present`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.hasClass.withArgs(CssClasses.WITH_PRIMARY_ICON) + mockAdapter.hasClass.withArgs(MDCChipCssClasses.WITH_PRIMARY_ICON) .and.returnValue(true); - foundation.setActionSelected(ActionType.UNSPECIFIED, false); + foundation.setActionSelected(MDCChipActionType.UNSPECIFIED, false); jasmine.clock().tick(2); expect(mockAdapter.addClass) - .toHaveBeenCalledWith(CssClasses.DESELECTING_WITH_PRIMARY_ICON); + .toHaveBeenCalledWith( + MDCChipCssClasses.DESELECTING_WITH_PRIMARY_ICON); }); it('#setDisabled(true) makes each action disabled', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getActions.and.returnValue([ActionType.UNSPECIFIED]); + mockAdapter.getActions.and.returnValue([MDCChipActionType.UNSPECIFIED]); foundation.setDisabled(true); expect(mockAdapter.setActionDisabled) - .toHaveBeenCalledWith(ActionType.UNSPECIFIED, true); + .toHaveBeenCalledWith(MDCChipActionType.UNSPECIFIED, true); }); it('#setDisabled(true) adds the disabled class', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getActions.and.returnValue([ActionType.UNSPECIFIED]); + mockAdapter.getActions.and.returnValue([MDCChipActionType.UNSPECIFIED]); foundation.setDisabled(true); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.DISABLED); + expect(mockAdapter.addClass) + .toHaveBeenCalledWith(MDCChipCssClasses.DISABLED); }); it('#setDisabled(false) makes each action enabled', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getActions.and.returnValue([ActionType.UNSPECIFIED]); + mockAdapter.getActions.and.returnValue([MDCChipActionType.UNSPECIFIED]); foundation.setDisabled(false); expect(mockAdapter.setActionDisabled) - .toHaveBeenCalledWith(ActionType.UNSPECIFIED, false); + .toHaveBeenCalledWith(MDCChipActionType.UNSPECIFIED, false); }); it('#setDisabled(false) removes the disabled class', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getActions.and.returnValue([ActionType.UNSPECIFIED]); + mockAdapter.getActions.and.returnValue([MDCChipActionType.UNSPECIFIED]); foundation.setDisabled(false); - expect(mockAdapter.removeClass).toHaveBeenCalledWith(CssClasses.DISABLED); + expect(mockAdapter.removeClass) + .toHaveBeenCalledWith(MDCChipCssClasses.DISABLED); }); - it(`#handleActionInteraction() emits ${Events.INTERACTION}`, () => { + it(`#handleActionInteraction() emits ${MDCChipEvents.INTERACTION}`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getElementID.and.returnValue('foo'); - mockAdapter.isActionSelected.withArgs(ActionType.UNSPECIFIED) + mockAdapter.isActionSelected.withArgs(MDCChipActionType.UNSPECIFIED) .and.returnValue(true); - mockAdapter.isActionSelectable.withArgs(ActionType.UNSPECIFIED) + mockAdapter.isActionSelectable.withArgs(MDCChipActionType.UNSPECIFIED) .and.returnValue(true); foundation.handleActionInteraction({ detail: { actionID: 'bar', - source: ActionType.UNSPECIFIED, - trigger: InteractionTrigger.CLICK, + source: MDCChipActionType.UNSPECIFIED, + trigger: MDCChipActionInteractionTrigger.CLICK, }, } as ActionInteractionEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.INTERACTION, { - actionID: 'bar', - chipID: 'foo', - shouldRemove: false, - isSelectable: true, - isSelected: true, - source: ActionType.UNSPECIFIED, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.INTERACTION, { + actionID: 'bar', + chipID: 'foo', + shouldRemove: false, + isSelectable: true, + isSelected: true, + source: MDCChipActionType.UNSPECIFIED, + }); }); - it(`#handleActionInteraction() emits ${Events.INTERACTION} with` + - ` {shouldRemove: true} when from action "${ActionType.TRAILING}"`, + it(`#handleActionInteraction() emits ${MDCChipEvents.INTERACTION} with` + + ` {shouldRemove: true} when from action "${ + MDCChipActionType.TRAILING}"`, () => { const {foundation, mockAdapter} = setupTest(); foundation.handleActionInteraction({ detail: { actionID: 'bar', - source: ActionType.TRAILING, - trigger: InteractionTrigger.CLICK, + source: MDCChipActionType.TRAILING, + trigger: MDCChipActionInteractionTrigger.CLICK, }, } as ActionInteractionEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.INTERACTION, { - actionID: 'bar', - chipID: '', - shouldRemove: true, - isSelectable: false, - isSelected: false, - source: ActionType.TRAILING, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.INTERACTION, { + actionID: 'bar', + chipID: '', + shouldRemove: true, + isSelectable: false, + isSelected: false, + source: MDCChipActionType.TRAILING, + }); }); - it(`#handleActionInteraction() emits ${Events.INTERACTION} with` + + it(`#handleActionInteraction() emits ${MDCChipEvents.INTERACTION} with` + ` {shouldRemove: true} when from` + - ` trigger "${InteractionTrigger.BACKSPACE_KEY}"`, + ` trigger "${MDCChipActionInteractionTrigger.BACKSPACE_KEY}"`, () => { const {foundation, mockAdapter} = setupTest(); foundation.handleActionInteraction({ detail: { actionID: 'bar', - source: ActionType.UNSPECIFIED, - trigger: InteractionTrigger.BACKSPACE_KEY, + source: MDCChipActionType.UNSPECIFIED, + trigger: MDCChipActionInteractionTrigger.BACKSPACE_KEY, }, } as ActionInteractionEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.INTERACTION, { - actionID: 'bar', - chipID: '', - shouldRemove: true, - isSelectable: false, - isSelected: false, - source: ActionType.UNSPECIFIED, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.INTERACTION, { + actionID: 'bar', + chipID: '', + shouldRemove: true, + isSelectable: false, + isSelected: false, + source: MDCChipActionType.UNSPECIFIED, + }); }); describe('#handleActionNavigation', () => { @@ -299,66 +321,69 @@ describe('MDCChipFoundation', () => { it(`from primary action focuses trailing action if focusable`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.isActionFocusable.withArgs(ActionType.TRAILING) + mockAdapter.isActionFocusable.withArgs(MDCChipActionType.TRAILING) .and.returnValue(true); foundation.handleActionNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, key, }, } as ActionNavigationEvent); expect(mockAdapter.setActionFocus) .toHaveBeenCalledWith( - ActionType.TRAILING, FocusBehavior.FOCUSABLE_AND_FOCUSED); + MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); expect(mockAdapter.setActionFocus) .toHaveBeenCalledWith( - ActionType.PRIMARY, FocusBehavior.NOT_FOCUSABLE); + MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); - it(`from primary action emits ${Events.NAVIGATION}` + + it(`from primary action emits ${MDCChipEvents.NAVIGATION}` + ` if trailing action is not focusable`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getElementID.and.returnValue('foo'); - mockAdapter.isActionFocusable.withArgs(ActionType.TRAILING) + mockAdapter.isActionFocusable.withArgs(MDCChipActionType.TRAILING) .and.returnValue(false); foundation.handleActionNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, key, }, } as ActionNavigationEvent); expect(mockAdapter.emitEvent) - .toHaveBeenCalledWith(Events.NAVIGATION, { + .toHaveBeenCalledWith(MDCChipEvents.NAVIGATION, { chipID: 'foo', - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, isRTL: false, key, }); }); - it(`from primary action in RTL emits ${Events.NAVIGATION}`, () => { + it(`from primary action in RTL emits ${MDCChipEvents.NAVIGATION}`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.isRTL.and.returnValue(true); mockAdapter.getElementID.and.returnValue('foo'); foundation.handleActionNavigation({ detail: { - source: ActionType.PRIMARY, + source: MDCChipActionType.PRIMARY, key, }, } as ActionNavigationEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.NAVIGATION, { - chipID: 'foo', - source: ActionType.PRIMARY, - isRTL: true, - key, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.NAVIGATION, { + chipID: 'foo', + source: MDCChipActionType.PRIMARY, + isRTL: true, + key, + }); }); }); @@ -368,67 +393,71 @@ describe('MDCChipFoundation', () => { it(`from trailing action focuses primary action if focusable`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.isActionFocusable.withArgs(ActionType.PRIMARY) + mockAdapter.isActionFocusable.withArgs(MDCChipActionType.PRIMARY) .and.returnValue(true); foundation.handleActionNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, key, }, } as ActionNavigationEvent); expect(mockAdapter.setActionFocus) .toHaveBeenCalledWith( - ActionType.PRIMARY, FocusBehavior.FOCUSABLE_AND_FOCUSED); + MDCChipActionType.PRIMARY, + MDCChipActionFocusBehavior.FOCUSABLE_AND_FOCUSED); expect(mockAdapter.setActionFocus) .toHaveBeenCalledWith( - ActionType.TRAILING, FocusBehavior.NOT_FOCUSABLE); + MDCChipActionType.TRAILING, + MDCChipActionFocusBehavior.NOT_FOCUSABLE); }); - it(`from trailing action emits ${Events.NAVIGATION}` + + it(`from trailing action emits ${MDCChipEvents.NAVIGATION}` + ` if primary action is not focusable`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getElementID.and.returnValue('foo'); - mockAdapter.isActionFocusable.withArgs(ActionType.PRIMARY) + mockAdapter.isActionFocusable.withArgs(MDCChipActionType.PRIMARY) .and.returnValue(false); foundation.handleActionNavigation({ detail: { - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, key, }, } as ActionNavigationEvent); expect(mockAdapter.emitEvent) - .toHaveBeenCalledWith(Events.NAVIGATION, { + .toHaveBeenCalledWith(MDCChipEvents.NAVIGATION, { chipID: 'foo', - source: ActionType.TRAILING, + source: MDCChipActionType.TRAILING, isRTL: false, key, }); }); - it(`from trailing action in RTL emits ${Events.NAVIGATION}`, () => { - const {foundation, mockAdapter} = setupTest(); - mockAdapter.isRTL.and.returnValue(true); - mockAdapter.getElementID.and.returnValue('foo'); + it(`from trailing action in RTL emits ${MDCChipEvents.NAVIGATION}`, + () => { + const {foundation, mockAdapter} = setupTest(); + mockAdapter.isRTL.and.returnValue(true); + mockAdapter.getElementID.and.returnValue('foo'); - foundation.handleActionNavigation({ - detail: { - source: ActionType.TRAILING, - key, - }, - } as ActionNavigationEvent); + foundation.handleActionNavigation({ + detail: { + source: MDCChipActionType.TRAILING, + key, + }, + } as ActionNavigationEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.NAVIGATION, { - chipID: 'foo', - source: ActionType.TRAILING, - isRTL: true, - key, - }); - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.NAVIGATION, { + chipID: 'foo', + source: MDCChipActionType.TRAILING, + isRTL: true, + key, + }); + }); }); const emittingKeys = [ @@ -439,73 +468,77 @@ describe('MDCChipFoundation', () => { ]; for (const key of emittingKeys) { - it(`${key} emits ${Events.NAVIGATION}`, () => { + it(`${key} emits ${MDCChipEvents.NAVIGATION}`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getElementID.and.returnValue('foo'); foundation.handleActionNavigation({ detail: { - source: ActionType.UNSPECIFIED, + source: MDCChipActionType.UNSPECIFIED, key, }, } as ActionNavigationEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.NAVIGATION, { - chipID: 'foo', - source: ActionType.UNSPECIFIED, - isRTL: false, - key, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.NAVIGATION, { + chipID: 'foo', + source: MDCChipActionType.UNSPECIFIED, + isRTL: false, + key, + }); }); } }); - it(`#startAnimation(${Animation.ENTER}) adds the enter class`, () => { + it(`#startAnimation(${MDCChipAnimation.ENTER}) adds the enter class`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.startAnimation(Animation.ENTER); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.ENTER); + foundation.startAnimation(MDCChipAnimation.ENTER); + expect(mockAdapter.addClass).toHaveBeenCalledWith(MDCChipCssClasses.ENTER); }); - it(`#startAnimation(${Animation.EXIT}) adds the exit class`, () => { + it(`#startAnimation(${MDCChipAnimation.EXIT}) adds the exit class`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.startAnimation(Animation.EXIT); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.EXIT); + foundation.startAnimation(MDCChipAnimation.EXIT); + expect(mockAdapter.addClass).toHaveBeenCalledWith(MDCChipCssClasses.EXIT); }); it(`#handleAnimationEnd() for enter removes the enter class`, () => { const {foundation, mockAdapter} = setupTest(); foundation.handleAnimationEnd( {animationName: 'mdc-evolution-chip-enter'} as AnimationEvent); - expect(mockAdapter.removeClass).toHaveBeenCalledWith(CssClasses.ENTER); + expect(mockAdapter.removeClass) + .toHaveBeenCalledWith(MDCChipCssClasses.ENTER); }); it(`#handleAnimationEnd() for enter emits an event`, () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getElementID.and.returnValue('foo'); - mockAdapter.getAttribute.withArgs(Attributes.DATA_ADDED_ANNOUNCEMENT) + mockAdapter.getAttribute.withArgs(MDCChipAttributes.DATA_ADDED_ANNOUNCEMENT) .and.returnValue('Added foo'); foundation.handleAnimationEnd( {animationName: 'mdc-evolution-chip-enter'} as AnimationEvent); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.ANIMATION, { - chipID: 'foo', - addedAnnouncement: 'Added foo', - animation: Animation.ENTER, - isComplete: true, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.ANIMATION, { + chipID: 'foo', + addedAnnouncement: 'Added foo', + animation: MDCChipAnimation.ENTER, + isComplete: true, + }); }); it(`#handleAnimationEnd() for exit removes the exit class`, () => { const {foundation, mockAdapter} = setupTest(); foundation.handleAnimationEnd( {animationName: 'mdc-evolution-chip-exit'} as AnimationEvent); - expect(mockAdapter.removeClass).toHaveBeenCalledWith(CssClasses.EXIT); + expect(mockAdapter.removeClass) + .toHaveBeenCalledWith(MDCChipCssClasses.EXIT); }); it(`#handleAnimationEnd() for exit adds the hidden class`, () => { const {foundation, mockAdapter} = setupTest(); foundation.handleAnimationEnd( {animationName: 'mdc-evolution-chip-exit'} as AnimationEvent); - expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.HIDDEN); + expect(mockAdapter.addClass).toHaveBeenCalledWith(MDCChipCssClasses.HIDDEN); }); it(`#handleAnimationEnd() sets the computed width on the root`, () => { @@ -528,22 +561,26 @@ describe('MDCChipFoundation', () => { () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getElementID.and.returnValue('foo'); - mockAdapter.getAttribute.withArgs(Attributes.DATA_REMOVED_ANNOUNCEMENT) + mockAdapter.getAttribute + .withArgs(MDCChipAttributes.DATA_REMOVED_ANNOUNCEMENT) .and.returnValue('Removed foo'); - mockAdapter.hasClass.withArgs(CssClasses.HIDDEN).and.returnValue(true); + mockAdapter.hasClass.withArgs(MDCChipCssClasses.HIDDEN) + .and.returnValue(true); foundation.handleTransitionEnd(); - expect(mockAdapter.emitEvent).toHaveBeenCalledWith(Events.ANIMATION, { - chipID: 'foo', - removedAnnouncement: 'Removed foo', - animation: Animation.EXIT, - isComplete: true, - }); + expect(mockAdapter.emitEvent) + .toHaveBeenCalledWith(MDCChipEvents.ANIMATION, { + chipID: 'foo', + removedAnnouncement: 'Removed foo', + animation: MDCChipAnimation.EXIT, + isComplete: true, + }); }); it(`#handleTransitionEnd() does not emit an event when the root does not have the hidden class`, () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.hasClass.withArgs(CssClasses.HIDDEN).and.returnValue(false); + mockAdapter.hasClass.withArgs(MDCChipCssClasses.HIDDEN) + .and.returnValue(false); foundation.handleTransitionEnd(); expect(mockAdapter.emitEvent).not.toHaveBeenCalled(); }); diff --git a/packages/mdc-chips/chip/types.ts b/packages/mdc-chips/chip/types.ts index 92c8f230bee..ce0c078f63b 100644 --- a/packages/mdc-chips/chip/types.ts +++ b/packages/mdc-chips/chip/types.ts @@ -21,15 +21,16 @@ * THE SOFTWARE. */ -import {ActionType} from '../action/constants'; +import {MDCChipActionType} from '../action/constants'; import {MDCChipActionInteractionEventDetail, MDCChipActionNavigationEventDetail} from '../action/types'; -import {Animation} from './constants'; + +import {MDCChipAnimation} from './constants'; /** MDCChipInteractionEventDetail provides details for the interaction event. */ export interface MDCChipInteractionEventDetail { actionID: string; chipID: string; - source: ActionType; + source: MDCChipActionType; shouldRemove: boolean; isSelectable: boolean; isSelected: boolean; @@ -38,15 +39,17 @@ export interface MDCChipInteractionEventDetail { /** MDCChipNavigationEventDetail provides details for the navigation event. */ export interface MDCChipNavigationEventDetail { chipID: string; - source: ActionType; + source: MDCChipActionType; key: string; isRTL: boolean; } -/** MDCChipAnimationEventDetail provides details for the animation event. */ +/** + * MDCChipAnimationEventDetail provides details for the animation event. + */ export interface MDCChipAnimationEventDetail { chipID: string; - animation: Animation; + animation: MDCChipAnimation; isComplete: boolean; addedAnnouncement?: string; removedAnnouncement?: string; diff --git a/packages/mdc-chips/deprecated/README.md b/packages/mdc-chips/deprecated/README.md index dc1c4f22365..f50717b8ccc 100644 --- a/packages/mdc-chips/deprecated/README.md +++ b/packages/mdc-chips/deprecated/README.md @@ -458,10 +458,10 @@ Method Signature | Description `setShouldRemoveOnTrailingIconClick(shouldRemove: boolean) => void` | Sets whether a trailing icon click should trigger exit/removal of the chip `getDimensions() => ClientRect` | Returns the dimensions of the chip. This is used for applying ripple to the chip. `beginExit() => void` | Begins the exit animation which leads to removal of the chip -`handleInteraction(evt: Event) => void` | Handles an interaction event on the root element -`handleTransitionEnd(evt: Event) => void` | Handles a transition end event on the root element -`handleTrailingIconInteraction(evt: Event) => void` | Handles an interaction event on the trailing icon element -`handleKeydown(evt: Event) => void` | Handles a keydown event on the root element +`handleInteraction(event: Event) => void` | Handles an interaction event on the root element +`handleTransitionEnd(event: Event) => void` | Handles a transition end event on the root element +`handleTrailingIconInteraction(event: Event) => void` | Handles an interaction event on the trailing icon element +`handleKeydown(event: Event) => void` | Handles a keydown event on the root element `removeFocus() => void` | Removes focusability from the chip #### `MDCChipFoundation` Event Handlers diff --git a/packages/mdc-chips/deprecated/_mixins.scss b/packages/mdc-chips/deprecated/_mixins.scss index 559b0c64adc..a10225424b0 100644 --- a/packages/mdc-chips/deprecated/_mixins.scss +++ b/packages/mdc-chips/deprecated/_mixins.scss @@ -20,8 +20,8 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern @use 'sass:math'; @use '@material/animation/functions'; @@ -31,9 +31,10 @@ @use '@material/elevation/mixins' as elevation-mixins; @use '@material/density/functions' as density-functions; @use '@material/feature-targeting/feature-targeting'; +@use '@material/focus-ring/focus-ring'; @use '@material/ripple/ripple'; @use '@material/ripple/ripple-theme'; -@use '@material/rtl/mixins' as rtl-mixins; +@use '@material/rtl/rtl'; @use '@material/theme/theme'; @use '@material/touch-target/mixins' as touch-target-mixins; @use '@material/typography/typography'; @@ -241,13 +242,9 @@ $ripple-target: '.mdc-chip__ripple'; } .mdc-chip__checkmark-svg { - // stylelint-disable max-nesting-depth - @include feature-targeting.targets($feat-animation) { transition: width 0ms; } - - // stylelint-enable max-nesting-depth } } } @@ -646,11 +643,7 @@ $ripple-target: '.mdc-chip__ripple'; &.mdc-chip--selected .mdc-chip__checkmark, .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property( - margin, - $left-margin, - $right-margin - ); + @include rtl.reflexive-property(margin, $left-margin, $right-margin); } } } @@ -671,11 +664,7 @@ $ripple-target: '.mdc-chip__ripple'; // TODO(b/151980552): Remove the following block .mdc-chip__icon--trailing { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property( - margin, - $left-margin, - $right-margin - ); + @include rtl.reflexive-property(margin, $left-margin, $right-margin); } } } @@ -738,3 +727,18 @@ $ripple-target: '.mdc-chip__ripple'; } } } + +@mixin high-contrast-focus { + // High contrast mode focus for chips. + .mdc-chip__focus-ring { + display: none; + } + + @include ripple-theme.focus() { + .mdc-chip__focus-ring { + z-index: 1; + display: block; + @include focus-ring.focus-ring(); + } + } +} diff --git a/packages/mdc-chips/deprecated/chip-set/adapter.ts b/packages/mdc-chips/deprecated/chip-set/adapter.ts index 62121ce0299..f8b94a033d2 100644 --- a/packages/mdc-chips/deprecated/chip-set/adapter.ts +++ b/packages/mdc-chips/deprecated/chip-set/adapter.ts @@ -43,7 +43,8 @@ export interface MDCChipSetAdapter { /** * Sets the selected state of the chip at the given index. */ - selectChipAtIndex(index: number, isSelected: boolean, shouldNotifyClients: boolean): void; + selectChipAtIndex( + index: number, isSelected: boolean, shouldNotifyClients: boolean): void; /** * Returns the index of the chip at the given ID. diff --git a/packages/mdc-chips/deprecated/chip-set/component.ts b/packages/mdc-chips/deprecated/chip-set/component.ts index 01b0075aba7..fc8033c11da 100644 --- a/packages/mdc-chips/deprecated/chip-set/component.ts +++ b/packages/mdc-chips/deprecated/chip-set/component.ts @@ -23,75 +23,83 @@ import {MDCComponent} from '@material/base/component'; import {announce} from '@material/dom/announce'; + import {MDCChip, MDCChipFactory} from '../chip/component'; import {MDCChipFoundation} from '../chip/foundation'; -import {MDCChipInteractionEvent, MDCChipNavigationEvent, MDCChipRemovalEvent, - MDCChipSelectionEvent} from '../chip/types'; +import {MDCChipInteractionEvent, MDCChipNavigationEvent, MDCChipRemovalEvent, MDCChipSelectionEvent} from '../chip/types'; + import {MDCChipSetAdapter} from './adapter'; import {MDCChipSetFoundation} from './foundation'; -const {INTERACTION_EVENT, SELECTION_EVENT, REMOVAL_EVENT, NAVIGATION_EVENT} = MDCChipFoundation.strings; +const {INTERACTION_EVENT, SELECTION_EVENT, REMOVAL_EVENT, NAVIGATION_EVENT} = + MDCChipFoundation.strings; const {CHIP_SELECTOR} = MDCChipSetFoundation.strings; let idCounter = 0; +/** MDC Chip Set */ export class MDCChipSet extends MDCComponent { - static attachTo(root: Element) { + static override attachTo(root: HTMLElement) { return new MDCChipSet(root); } - get chips(): ReadonlyArray { + get chips(): readonly MDCChip[] { return this.chipsList.slice(); } /** * @return An array of the IDs of all selected chips. */ - get selectedChipIds(): ReadonlyArray { + get selectedChipIds(): readonly string[] { return this.foundation.getSelectedChipIds(); } - private chipsList!: MDCChip[]; // assigned in initialize() - private chipFactory!: (el: Element) => MDCChip; // assigned in initialize() - private handleChipInteraction!: (evt: MDCChipInteractionEvent) => + private chipsList!: MDCChip[]; // assigned in initialize() + private chipFactory!: + (el: HTMLElement) => MDCChip; // assigned in initialize() + private handleChipInteraction!: (event: MDCChipInteractionEvent) => void; // assigned in initialSyncWithDOM() private handleChipSelection!: - (evt: MDCChipSelectionEvent) => void; // assigned in initialSyncWithDOM() + (event: MDCChipSelectionEvent) => void; // assigned in initialSyncWithDOM() private handleChipRemoval!: - (evt: MDCChipRemovalEvent) => void; // assigned in initialSyncWithDOM() - private handleChipNavigation!: (evt: MDCChipNavigationEvent) => + (event: MDCChipRemovalEvent) => void; // assigned in initialSyncWithDOM() + private handleChipNavigation!: (event: MDCChipNavigationEvent) => void; // assigned in initialSyncWithDOM() /** * @param chipFactory A function which creates a new MDCChip. */ - initialize(chipFactory: MDCChipFactory = (el) => new MDCChip(el)) { + override initialize(chipFactory: MDCChipFactory = (el) => new MDCChip(el)) { this.chipFactory = chipFactory; this.chipsList = this.instantiateChips(this.chipFactory); } - initialSyncWithDOM() { + override initialSyncWithDOM() { for (const chip of this.chipsList) { if (chip.id && chip.selected) { this.foundation.select(chip.id); } } - this.handleChipInteraction = (evt) => - this.foundation.handleChipInteraction(evt.detail); - this.handleChipSelection = (evt) => - this.foundation.handleChipSelection(evt.detail); - this.handleChipRemoval = (evt) => - this.foundation.handleChipRemoval(evt.detail); - this.handleChipNavigation = (evt) => - this.foundation.handleChipNavigation(evt.detail); + this.handleChipInteraction = (event) => { + this.foundation.handleChipInteraction(event.detail); + }; + this.handleChipSelection = (event) => { + this.foundation.handleChipSelection(event.detail); + }; + this.handleChipRemoval = (event) => { + this.foundation.handleChipRemoval(event.detail); + }; + this.handleChipNavigation = (event) => { + this.foundation.handleChipNavigation(event.detail); + }; this.listen(INTERACTION_EVENT, this.handleChipInteraction); this.listen(SELECTION_EVENT, this.handleChipSelection); this.listen(REMOVAL_EVENT, this.handleChipRemoval); this.listen(NAVIGATION_EVENT, this.handleChipNavigation); } - destroy() { + override destroy() { for (const chip of this.chipsList) { chip.destroy(); } @@ -107,14 +115,15 @@ export class MDCChipSet extends MDCComponent { /** * Adds a new chip object to the chip set from the given chip element. */ - addChip(chipEl: Element) { + addChip(chipEl: HTMLElement) { chipEl.id = chipEl.id || `mdc-chip-${++idCounter}`; this.chipsList.push(this.chipFactory(chipEl)); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCChipSetAdapter = { announceMessage: (message) => { announce(message); @@ -156,8 +165,8 @@ export class MDCChipSet extends MDCComponent { * Instantiates chip components on all of the chip set's child chip elements. */ private instantiateChips(chipFactory: MDCChipFactory): MDCChip[] { - const chipElements: Element[] = - [].slice.call(this.root.querySelectorAll(CHIP_SELECTOR)); + const chipElements = + Array.from(this.root.querySelectorAll(CHIP_SELECTOR)); return chipElements.map((el) => { el.id = el.id || `mdc-chip-${++idCounter}`; return chipFactory(el); @@ -165,7 +174,8 @@ export class MDCChipSet extends MDCComponent { } /** - * Returns the index of the chip with the given id, or -1 if the chip does not exist. + * Returns the index of the chip with the given id, or -1 if the chip does not + * exist. */ private findChipIndex(chipId: string): number { for (let i = 0; i < this.chips.length; i++) { diff --git a/packages/mdc-chips/deprecated/chip-set/foundation.ts b/packages/mdc-chips/deprecated/chip-set/foundation.ts index 1de7420af11..e348c80a1b5 100644 --- a/packages/mdc-chips/deprecated/chip-set/foundation.ts +++ b/packages/mdc-chips/deprecated/chip-set/foundation.ts @@ -29,16 +29,17 @@ import {MDCChipInteractionEventDetail, MDCChipNavigationEventDetail, MDCChipRemo import {MDCChipSetAdapter} from './adapter'; import {cssClasses, strings} from './constants'; +/** MDC Chip Set Foundation */ export class MDCChipSetFoundation extends MDCFoundation { - static get strings() { + static override get strings() { return strings; } - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get defaultAdapter(): MDCChipSetAdapter { + static override get defaultAdapter(): MDCChipSetAdapter { return { announceMessage: () => undefined, focusChipPrimaryActionAtIndex: () => undefined, @@ -54,7 +55,8 @@ export class MDCChipSetFoundation extends MDCFoundation { } /** - * The ids of the selected chips in the set. Only used for choice chip set or filter chip set. + * The ids of the selected chips in the set. Only used for choice chip set or + * filter chip set. */ private selectedChipIds: string[] = []; @@ -65,13 +67,14 @@ export class MDCChipSetFoundation extends MDCFoundation { /** * Returns an array of the IDs of all selected chips. */ - getSelectedChipIds(): ReadonlyArray { + getSelectedChipIds(): readonly string[] { return this.selectedChipIds.slice(); } /** - * Selects the chip with the given id. Deselects all other chips if the chip set is of the choice variant. - * Does not notify clients of the updated selection state. + * Selects the chip with the given id. Deselects all other chips if the chip + * set is of the choice variant. Does not notify clients of the updated + * selection state. */ select(chipId: string) { this.selectImpl(chipId, false); @@ -90,7 +93,8 @@ export class MDCChipSetFoundation extends MDCFoundation { } /** - * Handles a chip selection event, used to handle discrepancy when selection state is set directly on the Chip. + * Handles a chip selection event, used to handle discrepancy when selection + * state is set directly on the Chip. */ handleChipSelection({chipId, selected, shouldIgnore}: MDCChipSelectionEventDetail) { @@ -124,7 +128,8 @@ export class MDCChipSetFoundation extends MDCFoundation { } const nextIndex = Math.min(index, maxIndex); this.removeFocusFromChipsExcept(nextIndex); - // After removing a chip, we should focus the trailing action for the next chip. + // After removing a chip, we should focus the trailing action for the next + // chip. this.adapter.focusChipTrailingActionAtIndex(nextIndex); } @@ -172,20 +177,24 @@ export class MDCChipSetFoundation extends MDCFoundation { private focusChipAction(index: number, key: string, source: EventSource) { const shouldJumpChips = jumpChipKeys.has(key); if (shouldJumpChips && source === EventSource.PRIMARY) { - return this.adapter.focusChipPrimaryActionAtIndex(index); + this.adapter.focusChipPrimaryActionAtIndex(index); + return; } if (shouldJumpChips && source === EventSource.TRAILING) { - return this.adapter.focusChipTrailingActionAtIndex(index); + this.adapter.focusChipTrailingActionAtIndex(index); + return; } const dir = this.getDirection(key); if (dir === Direction.LEFT) { - return this.adapter.focusChipTrailingActionAtIndex(index); + this.adapter.focusChipTrailingActionAtIndex(index); + return; } if (dir === Direction.RIGHT) { - return this.adapter.focusChipPrimaryActionAtIndex(index); + this.adapter.focusChipPrimaryActionAtIndex(index); + return; } } diff --git a/packages/mdc-chips/deprecated/chip-set/mdc-chip-set.import.scss b/packages/mdc-chips/deprecated/chip-set/mdc-chip-set.import.scss deleted file mode 100644 index 52b0ea2342b..00000000000 --- a/packages/mdc-chips/deprecated/chip-set/mdc-chip-set.import.scss +++ /dev/null @@ -1,39 +0,0 @@ -@forward "../../animation/variables" as mdc-animation-*; -@forward "../../ripple/variables" as mdc-* hide $mdc-dark-ink-opacities, $mdc-fade-in-duration, $mdc-fade-out-duration, $mdc-light-ink-opacities, $mdc-pressed-dark-ink-opacity, $mdc-pressed-light-ink-opacity, $mdc-translate-duration; -@forward "../../ripple/variables" as mdc-ripple-* hide $mdc-ripple-states-wash-duration; -@forward "../../theme/variables" as mdc-theme-*; -@forward "../../density/variables" as mdc-density-*; -@forward "../../checkbox/variables" as mdc-checkbox-*; -@forward "../../base/mixins" as mdc-base-*; -@forward "../../feature-targeting/variables" as mdc-feature-*; -@forward "../../feature-targeting/mixins" as mdc-feature-*; -@forward "../../elevation/variables" as mdc-elevation-*; -@forward "../../rtl/variables" as mdc-rtl-*; -@forward "../../touch-target/variables" as mdc-touch-target-*; -@forward "../../typography/variables" as mdc-typography-*; -@forward "../../shape/variables" as mdc-shape-*; -@forward "../variables" as mdc-chip-*; -@forward "../mixins" as mdc-chip-*; -@forward "../../theme/mixins" as mdc-theme-*; -@forward "../../elevation/mixins" as mdc-* hide mdc-core-styles, mdc-overlay-common, mdc-overlay-dimensions, mdc-overlay-fill-color, mdc-overlay-opacity, mdc-overlay-selector-, mdc-overlay-surface-position, mdc-shadow; -@forward "../../elevation/mixins" as mdc-elevation-* hide mdc-elevation-elevation; -@forward "../../ripple/keyframes" as mdc-ripple-*; -@forward "../../ripple/mixins" as mdc-* hide mdc-common, mdc-core-styles, mdc-radius-bounded, mdc-radius-unbounded, mdc-surface, mdc-target-common, mdc-target-selector; -@forward "../../ripple/mixins" as mdc-ripple-* hide mdc-ripple-states, mdc-ripple-states-activated, mdc-ripple-states-base-color, mdc-ripple-states-focus-opacity, mdc-ripple-states-focus-opacity-properties-, mdc-ripple-states-hover-opacity, mdc-ripple-states-interactions-, mdc-ripple-states-opacities, mdc-ripple-states-press-opacity, mdc-ripple-states-selected; -@forward "../../rtl/mixins" as mdc-* hide mdc-property-, mdc-reflexive, mdc-reflexive-box, mdc-reflexive-position, mdc-reflexive-property; -@forward "../../rtl/mixins" as mdc-rtl-* hide mdc-rtl-rtl; -@forward "../../touch-target/mixins" as mdc-* hide mdc-margin, mdc-wrapper; -@forward "../../touch-target/mixins" as mdc-touch-target-* hide mdc-touch-target-touch-target; -@forward "../../typography/mixins" as mdc-* hide mdc-base, mdc-baseline-bottom, mdc-baseline-strut-, mdc-baseline-top, mdc-core-styles, mdc-overflow-ellipsis; -@forward "../../typography/mixins" as mdc-typography-* hide mdc-typography-typography; -@forward "../../shape/mixins" as mdc-shape-*; -@forward "../../animation/functions" as mdc-animation-*; -@forward "../../theme/functions" as mdc-theme-*; -@forward "../../checkbox/functions" as mdc-checkbox-*; -@forward "../../feature-targeting/functions" as mdc-feature-*; -@forward "../../elevation/functions" as mdc-elevation-*; -@forward "../../density/functions" as mdc-density-*; -@forward "../../ripple/functions" as mdc-*; -@forward "../../typography/functions" as mdc-typography-*; -@forward "../../shape/functions" as mdc-shape-*; -@forward "mdc-chip-set"; diff --git a/packages/mdc-chips/deprecated/chip-set/test/component.test.ts b/packages/mdc-chips/deprecated/chip-set/test/component.test.ts index 1b58872aa95..eb1171ede59 100644 --- a/packages/mdc-chips/deprecated/chip-set/test/component.test.ts +++ b/packages/mdc-chips/deprecated/chip-set/test/component.test.ts @@ -21,14 +21,14 @@ * THE SOFTWARE. */ +import {createFixture, html} from '../../../../../testing/dom'; import {emitEvent} from '../../../../../testing/dom/events'; import {createMockFoundation} from '../../../../../testing/helpers/foundation'; import {MDCChipFoundation} from '../../chip/index'; import {MDCChipSet, MDCChipSetFoundation} from '../index'; const getFixture = () => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html`
Chip content
@@ -39,11 +39,7 @@ const getFixture = () => {
Chip content
-
`; - - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; +
`); }; describe('MDCChipSet', () => { @@ -178,6 +174,10 @@ describe('MDCChipSet', () => { expect(mockFoundation.handleChipNavigation).toHaveBeenCalledWith({ chipId: 'chipA', key: ARROW_LEFT_KEY, + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Type 'number' is not assignable + // to type 'ExpectedRecursive'. source: 1, }); expect(mockFoundation.handleChipNavigation).toHaveBeenCalledTimes(1); @@ -218,14 +218,10 @@ describe('MDCChipSet', () => { const {component} = setupTest(); // component.initialSyncWithDOM(); // TODO: why is this here? - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + const chipEl = createFixture(html`
Hello world
-
`; - - const chipEl = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(chipEl); + `); component.addChip(chipEl); expect(component.chips.length).toEqual(4); @@ -236,8 +232,7 @@ describe('MDCChipSet', () => { () => { const {root, component} = setupTest(); root.classList.add('foo'); - expect( - (component.getDefaultFoundation() as any).adapter.hasClass('foo')) + expect((component.getDefaultFoundation() as any).adapter.hasClass('foo')) .toBe(true); }); @@ -269,8 +264,7 @@ describe('MDCChipSet', () => { it('#adapter.getChipListCount returns the number of chips', () => { const {component} = setupTest(); - expect( - (component.getDefaultFoundation() as any).adapter.getChipListCount()) + expect((component.getDefaultFoundation() as any).adapter.getChipListCount()) .toEqual(3); }); diff --git a/packages/mdc-chips/deprecated/chip-set/test/foundation.test.ts b/packages/mdc-chips/deprecated/chip-set/test/foundation.test.ts index cedb97f012c..6fb9667d789 100644 --- a/packages/mdc-chips/deprecated/chip-set/test/foundation.test.ts +++ b/packages/mdc-chips/deprecated/chip-set/test/foundation.test.ts @@ -188,6 +188,10 @@ describe('MDCChipSetFoundation', () => { setupChipNavigationTest(['chipA', 'chipB', 'chipC']); foundation.handleChipInteraction({chipId: 'chipA'}); + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Expected 3 arguments, but + // got 2. expect(mockAdapter.selectChipAtIndex).not.toHaveBeenCalledWith(0, true); }); diff --git a/packages/mdc-chips/deprecated/chip/adapter.ts b/packages/mdc-chips/deprecated/chip/adapter.ts index 43b7cd45793..9bb10f680f3 100644 --- a/packages/mdc-chips/deprecated/chip/adapter.ts +++ b/packages/mdc-chips/deprecated/chip/adapter.ts @@ -59,7 +59,7 @@ export interface MDCChipAdapter { /** * @return true if target has className, false otherwise. */ - eventTargetHasClass(target: EventTarget | null, className: string): boolean; + eventTargetHasClass(target: EventTarget|null, className: string): boolean; /** * @return the attribute string value if present, otherwise null @@ -73,13 +73,14 @@ export interface MDCChipAdapter { notifyInteraction(): void; /** - * Emits a custom "MDCChip:selection" event denoting the chip has been selected or deselected. + * Emits a custom "MDCChip:selection" event denoting the chip has been + * selected or deselected. */ notifySelection(selected: boolean, chipSetShouldIgnore: boolean): void; /** - * Emits a custom "MDCChip:trailingIconInteraction" event denoting the trailing icon has been - * interacted with (typically on click or keydown). + * Emits a custom "MDCChip:trailingIconInteraction" event denoting the + * trailing icon has been interacted with (typically on click or keydown). */ notifyTrailingIconInteraction(): void; @@ -89,7 +90,8 @@ export interface MDCChipAdapter { notifyRemoval(removedAnnouncement: string|null): void; /** - * Emits a custom event "MDCChip:navigation" denoting a focus navigation event. + * Emits a custom event "MDCChip:navigation" denoting a focus navigation + * event. */ notifyNavigation(key: string, source: EventSource): void; @@ -104,7 +106,8 @@ export interface MDCChipAdapter { notifyEditFinish(): void; /** - * @return The computed property value of the given style property on the root element. + * @return The computed property value of the given style property on the root + * element. */ getComputedStyleValue(propertyName: string): string; @@ -121,12 +124,13 @@ export interface MDCChipAdapter { /** * @return The bounding client rect of the root element. */ - getRootBoundingClientRect(): ClientRect; + getRootBoundingClientRect(): DOMRect; /** - * @return The bounding client rect of the checkmark element or null if it doesn't exist. + * @return The bounding client rect of the checkmark element or null if it + * doesn't exist. */ - getCheckmarkBoundingClientRect(): ClientRect | null; + getCheckmarkBoundingClientRect(): DOMRect|null; /** * Sets the value of the attribute on the primary action content. diff --git a/packages/mdc-chips/deprecated/chip/component.ts b/packages/mdc-chips/deprecated/chip/component.ts index f8113101a79..913248b044e 100644 --- a/packages/mdc-chips/deprecated/chip/component.ts +++ b/packages/mdc-chips/deprecated/chip/component.ts @@ -37,9 +37,13 @@ import {strings} from './constants'; import {MDCChipFoundation} from './foundation'; import {MDCChipInteractionEventDetail, MDCChipNavigationEventDetail, MDCChipRemovalEventDetail, MDCChipSelectionEventDetail} from './types'; -export type MDCChipFactory = (el: Element, foundation?: MDCChipFoundation) => MDCChip; +/** MDC Chip Factory */ +export type MDCChipFactory = + (el: HTMLElement, foundation?: MDCChipFoundation) => MDCChip; -export class MDCChip extends MDCComponent implements MDCRippleCapableSurface { +/** MDC Chip */ +export class MDCChip extends MDCComponent implements + MDCRippleCapableSurface { /** * @return Whether the chip is selected. */ @@ -55,7 +59,8 @@ export class MDCChip extends MDCComponent implements MDCRippl } /** - * @return Whether a trailing icon click should trigger exit/removal of the chip. + * @return Whether a trailing icon click should trigger exit/removal of the + * chip. */ get shouldRemoveOnTrailingIconClick(): boolean { return this.foundation.getShouldRemoveOnTrailingIconClick(); @@ -83,13 +88,13 @@ export class MDCChip extends MDCComponent implements MDCRippl return this.root.id; } - static attachTo(root: Element) { + static override attachTo(root: HTMLElement) { return new MDCChip(root); } - private leadingIcon!: Element|null; // assigned in initialize() - private checkmark!: Element|null; // assigned in initialize() - private primaryAction!: Element|null; // assigned in initialize() + private leadingIcon!: HTMLElement|null; // assigned in initialize() + private checkmark!: HTMLElement|null; // assigned in initialize() + private primaryAction!: HTMLElement|null; // assigned in initialize() private trailingAction!: MDCChipTrailingAction| null; // assigned in initialize() private rippleSurface!: MDCRipple; // assigned in initialize() @@ -112,26 +117,29 @@ export class MDCChip extends MDCComponent implements MDCRippl private handleFocusOut!: SpecificEventListener<'focusout'>; // assigned in initialSyncWIthDOM() - initialize( + override initialize( rippleFactory: MDCRippleFactory = (el, foundation) => new MDCRipple(el, foundation), trailingActionFactory: MDCChipTrailingActionFactory = (el) => new MDCChipTrailingAction(el), ) { - this.leadingIcon = this.root.querySelector(strings.LEADING_ICON_SELECTOR); - this.checkmark = this.root.querySelector(strings.CHECKMARK_SELECTOR); + this.leadingIcon = + this.root.querySelector(strings.LEADING_ICON_SELECTOR); + this.checkmark = + this.root.querySelector(strings.CHECKMARK_SELECTOR); this.primaryAction = - this.root.querySelector(strings.PRIMARY_ACTION_SELECTOR); + this.root.querySelector(strings.PRIMARY_ACTION_SELECTOR); const trailingActionEl = - this.root.querySelector(strings.TRAILING_ACTION_SELECTOR); + this.root.querySelector(strings.TRAILING_ACTION_SELECTOR); if (trailingActionEl) { this.trailingAction = trailingActionFactory(trailingActionEl); } - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const rippleAdapter: MDCRippleAdapter = { ...MDCRipple.createAdapter(this), computeBoundingRect: () => this.foundation.getDimensions(), @@ -140,30 +148,30 @@ export class MDCChip extends MDCComponent implements MDCRippl rippleFactory(this.root, new MDCRippleFoundation(rippleAdapter)); } - initialSyncWithDOM() { + override initialSyncWithDOM() { // Custom events this.handleTrailingActionInteraction = () => { this.foundation.handleTrailingActionInteraction(); }; this.handleTrailingActionNavigation = - (evt: MDCChipTrailingActionNavigationEvent) => { - this.foundation.handleTrailingActionNavigation(evt); + (event: MDCChipTrailingActionNavigationEvent) => { + this.foundation.handleTrailingActionNavigation(event); }; // Native events this.handleClick = () => { this.foundation.handleClick(); }; - this.handleKeydown = (evt: KeyboardEvent) => { - this.foundation.handleKeydown(evt); + this.handleKeydown = (event: KeyboardEvent) => { + this.foundation.handleKeydown(event); }; - this.handleTransitionEnd = (evt: TransitionEvent) => { - this.foundation.handleTransitionEnd(evt); + this.handleTransitionEnd = (event: TransitionEvent) => { + this.foundation.handleTransitionEnd(event); }; - this.handleFocusIn = (evt: FocusEvent) => { - this.foundation.handleFocusIn(evt); + this.handleFocusIn = (event: FocusEvent) => { + this.foundation.handleFocusIn(event); }; - this.handleFocusOut = (evt: FocusEvent) => { - this.foundation.handleFocusOut(evt); + this.handleFocusOut = (event: FocusEvent) => { + this.foundation.handleFocusOut(event); }; @@ -183,7 +191,7 @@ export class MDCChip extends MDCComponent implements MDCRippl } } - destroy() { + override destroy() { this.rippleSurface.destroy(); this.unlisten('transitionend', this.handleTransitionEnd); @@ -211,11 +219,14 @@ export class MDCChip extends MDCComponent implements MDCRippl this.foundation.beginExit(); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCChipAdapter = { - addClass: (className) => this.root.classList.add(className), + addClass: (className) => { + this.root.classList.add(className); + }, addClassToLeadingIcon: (className) => { if (this.leadingIcon) { this.leadingIcon.classList.add(className); @@ -225,7 +236,7 @@ export class MDCChip extends MDCComponent implements MDCRippl target ? (target as Element).classList.contains(className) : false, focusPrimaryAction: () => { if (this.primaryAction) { - (this.primaryAction as HTMLElement).focus(); + this.primaryAction.focus(); } }, focusTrailingAction: () => { @@ -249,30 +260,36 @@ export class MDCChip extends MDCComponent implements MDCRippl } return false; }, - notifyInteraction: () => this.emit( - strings.INTERACTION_EVENT, {chipId: this.id}, - true /* shouldBubble */), - notifyNavigation: (key, source) => - this.emit( - strings.NAVIGATION_EVENT, {chipId: this.id, key, source}, - true /* shouldBubble */), + notifyInteraction: () => { + this.emit( + strings.INTERACTION_EVENT, {chipId: this.id}, + true /* shouldBubble */); + }, + notifyNavigation: (key, source) => { + this.emit( + strings.NAVIGATION_EVENT, {chipId: this.id, key, source}, + true /* shouldBubble */); + }, notifyRemoval: (removedAnnouncement) => { this.emit( strings.REMOVAL_EVENT, {chipId: this.id, removedAnnouncement}, true /* shouldBubble */); }, - notifySelection: (selected, shouldIgnore) => - this.emit( - strings.SELECTION_EVENT, - {chipId: this.id, selected, shouldIgnore}, - true /* shouldBubble */), - notifyTrailingIconInteraction: () => - this.emit( - strings.TRAILING_ICON_INTERACTION_EVENT, {chipId: this.id}, - true /* shouldBubble */), - notifyEditStart: () => { /* Not Implemented. */ }, - notifyEditFinish: () => { /* Not Implemented. */ }, - removeClass: (className) => this.root.classList.remove(className), + notifySelection: (selected, shouldIgnore) => { + this.emit( + strings.SELECTION_EVENT, {chipId: this.id, selected, shouldIgnore}, + true /* shouldBubble */); + }, + notifyTrailingIconInteraction: () => { + this.emit( + strings.TRAILING_ICON_INTERACTION_EVENT, {chipId: this.id}, + true /* shouldBubble */); + }, + notifyEditStart: () => {/* Not Implemented. */}, + notifyEditFinish: () => {/* Not Implemented. */}, + removeClass: (className) => { + this.root.classList.remove(className); + }, removeClassFromLeadingIcon: (className) => { if (this.leadingIcon) { this.leadingIcon.classList.remove(className); @@ -285,11 +302,12 @@ export class MDCChip extends MDCComponent implements MDCRippl }, setPrimaryActionAttr: (attr, value) => { if (this.primaryAction) { - this.primaryAction.setAttribute(attr, value); + this.safeSetAttribute(this.primaryAction, attr, value); } }, - setStyleProperty: (propertyName, value) => - (this.root as HTMLElement).style.setProperty(propertyName, value), + setStyleProperty: (propertyName, value) => { + this.root.style.setProperty(propertyName, value); + }, }; return new MDCChipFoundation(adapter); } diff --git a/packages/mdc-chips/deprecated/chip/constants.ts b/packages/mdc-chips/deprecated/chip/constants.ts index 0184511c59e..f4e498f921e 100644 --- a/packages/mdc-chips/deprecated/chip/constants.ts +++ b/packages/mdc-chips/deprecated/chip/constants.ts @@ -82,7 +82,8 @@ export const cssClasses = { }; export const navigationKeys = new Set(); -// IE11 has no support for new Set with iterable so we need to initialize this by hand +// IE11 has no support for new Set with iterable so we need to initialize this +// by hand navigationKeys.add(strings.ARROW_LEFT_KEY); navigationKeys.add(strings.ARROW_RIGHT_KEY); navigationKeys.add(strings.ARROW_DOWN_KEY); @@ -95,7 +96,8 @@ navigationKeys.add(strings.IE_ARROW_DOWN_KEY); navigationKeys.add(strings.IE_ARROW_UP_KEY); export const jumpChipKeys = new Set(); -// IE11 has no support for new Set with iterable so we need to initialize this by hand +// IE11 has no support for new Set with iterable so we need to initialize this +// by hand jumpChipKeys.add(strings.ARROW_UP_KEY); jumpChipKeys.add(strings.ARROW_DOWN_KEY); jumpChipKeys.add(strings.HOME_KEY); diff --git a/packages/mdc-chips/deprecated/chip/foundation.ts b/packages/mdc-chips/deprecated/chip/foundation.ts index 55b6fdc1c62..2d6966ba832 100644 --- a/packages/mdc-chips/deprecated/chip/foundation.ts +++ b/packages/mdc-chips/deprecated/chip/foundation.ts @@ -35,23 +35,24 @@ const emptyClientRect = { right: 0, top: 0, width: 0, -}; +} as any; enum FocusBehavior { SHOULD_FOCUS, SHOULD_NOT_FOCUS, } +/** MDC Chip Foundation */ export class MDCChipFoundation extends MDCFoundation { - static get strings() { + static override get strings() { return strings; } - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get defaultAdapter(): MDCChipAdapter { + static override get defaultAdapter(): MDCChipAdapter { return { addClass: () => undefined, addClassToLeadingIcon: () => undefined, @@ -81,7 +82,10 @@ export class MDCChipFoundation extends MDCFoundation { }; } - /** Whether a trailing icon click should immediately trigger exit/removal of the chip. */ + /** + * Whether a trailing icon click should immediately trigger exit/removal of + * the chip. + */ private shouldRemoveOnTrailingIconClick = true; /** @@ -131,21 +135,22 @@ export class MDCChipFoundation extends MDCFoundation { this.shouldFocusPrimaryActionOnClick = shouldFocus; } - getDimensions(): ClientRect { + getDimensions(): DOMRect { const getRootRect = () => this.adapter.getRootBoundingClientRect(); const getCheckmarkRect = () => this.adapter.getCheckmarkBoundingClientRect(); - // When a chip has a checkmark and not a leading icon, the bounding rect changes in size depending on the current - // size of the checkmark. + // When a chip has a checkmark and not a leading icon, the bounding rect + // changes in size depending on the current size of the checkmark. if (!this.adapter.hasLeadingIcon()) { const checkmarkRect = getCheckmarkRect(); if (checkmarkRect) { const rootRect = getRootRect(); - // Checkmark is a square, meaning the client rect's width and height are identical once the animation completes. - // However, the checkbox is initially hidden by setting the width to 0. - // To account for an initial width of 0, we use the checkbox's height instead (which equals the end-state width) - // when adding it to the root client rect's width. + // Checkmark is a square, meaning the client rect's width and height are + // identical once the animation completes. However, the checkbox is + // initially hidden by setting the width to 0. To account for an initial + // width of 0, we use the checkbox's height instead (which equals the + // end-state width) when adding it to the root client rect's width. return { bottom: rootRect.bottom, height: rootRect.height, @@ -153,7 +158,7 @@ export class MDCChipFoundation extends MDCFoundation { right: rootRect.right, top: rootRect.top, width: rootRect.width + checkmarkRect.height, - }; + } as any; } } @@ -181,27 +186,31 @@ export class MDCChipFoundation extends MDCFoundation { /** * Handles a transition end event on the root element. */ - handleTransitionEnd(evt: TransitionEvent) { + handleTransitionEnd(event: TransitionEvent) { // Handle transition end event on the chip when it is about to be removed. const shouldHandle = - this.adapter.eventTargetHasClass(evt.target, cssClasses.CHIP_EXIT); - const widthIsAnimating = evt.propertyName === 'width'; - const opacityIsAnimating = evt.propertyName === 'opacity'; + this.adapter.eventTargetHasClass(event.target, cssClasses.CHIP_EXIT); + const widthIsAnimating = event.propertyName === 'width'; + const opacityIsAnimating = event.propertyName === 'opacity'; if (shouldHandle && opacityIsAnimating) { - // See: https://css-tricks.com/using-css-transitions-auto-dimensions/#article-header-id-5 + // See: + // https://css-tricks.com/using-css-transitions-auto-dimensions/#article-header-id-5 const chipWidth = this.adapter.getComputedStyleValue('width'); - // On the next frame (once we get the computed width), explicitly set the chip's width - // to its current pixel width, so we aren't transitioning out of 'auto'. + // On the next frame (once we get the computed width), explicitly set the + // chip's width to its current pixel width, so we aren't transitioning out + // of 'auto'. requestAnimationFrame(() => { this.adapter.setStyleProperty('width', chipWidth); - // To mitigate jitter, start transitioning padding and margin before width. + // To mitigate jitter, start transitioning padding and margin before + // width. this.adapter.setStyleProperty('padding', '0'); this.adapter.setStyleProperty('margin', '0'); - // On the next frame (once width is explicitly set), transition width to 0. + // On the next frame (once width is explicitly set), transition width to + // 0. requestAnimationFrame(() => { this.adapter.setStyleProperty('width', '0'); }); @@ -217,16 +226,17 @@ export class MDCChipFoundation extends MDCFoundation { this.adapter.notifyRemoval(removedAnnouncement); } - // Handle a transition end event on the leading icon or checkmark, since the transition end event bubbles. + // Handle a transition end event on the leading icon or checkmark, since the + // transition end event bubbles. if (!opacityIsAnimating) { return; } const shouldHideLeadingIcon = - this.adapter.eventTargetHasClass(evt.target, cssClasses.LEADING_ICON) && + this.adapter.eventTargetHasClass(event.target, cssClasses.LEADING_ICON) && this.adapter.hasClass(cssClasses.SELECTED); const shouldShowLeadingIcon = - this.adapter.eventTargetHasClass(evt.target, cssClasses.CHECKMARK) && + this.adapter.eventTargetHasClass(event.target, cssClasses.CHECKMARK) && !this.adapter.hasClass(cssClasses.SELECTED); if (shouldHideLeadingIcon) { @@ -240,18 +250,18 @@ export class MDCChipFoundation extends MDCFoundation { } } - handleFocusIn(evt: FocusEvent) { + handleFocusIn(event: FocusEvent) { // Early exit if the event doesn't come from the primary action - if (!this.eventFromPrimaryAction(evt)) { + if (!this.eventFromPrimaryAction(event)) { return; } this.adapter.addClass(cssClasses.PRIMARY_ACTION_FOCUSED); } - handleFocusOut(evt: FocusEvent) { + handleFocusOut(event: FocusEvent) { // Early exit if the event doesn't come from the primary action - if (!this.eventFromPrimaryAction(evt)) { + if (!this.eventFromPrimaryAction(event)) { return; } @@ -274,10 +284,10 @@ export class MDCChipFoundation extends MDCFoundation { /** * Handles a keydown event from the root element. */ - handleKeydown(evt: KeyboardEvent) { + handleKeydown(event: KeyboardEvent) { if (this.isEditing()) { - if (this.shouldFinishEditing(evt)) { - evt.preventDefault(); + if (this.shouldFinishEditing(event)) { + event.preventDefault(); this.finishEditing(); } // When editing, the foundation should only handle key events that finish @@ -286,36 +296,36 @@ export class MDCChipFoundation extends MDCFoundation { } if (this.isEditable()) { - if (this.shouldStartEditing(evt)) { - evt.preventDefault(); + if (this.shouldStartEditing(event)) { + event.preventDefault(); this.startEditing(); } } - if (this.shouldNotifyInteraction(evt)) { + if (this.shouldNotifyInteraction(event)) { this.adapter.notifyInteraction(); this.setPrimaryActionFocusable(this.getFocusBehavior()); return; } - if (this.isDeleteAction(evt)) { - evt.preventDefault(); + if (this.isDeleteAction(event)) { + event.preventDefault(); this.removeChip(); return; } // Early exit if the key is not usable - if (!navigationKeys.has(evt.key)) { + if (!navigationKeys.has(event.key)) { return; } // Prevent default behavior for movement keys which could include scrolling - evt.preventDefault(); - this.focusNextAction(evt.key, EventSource.PRIMARY); + event.preventDefault(); + this.focusNextAction(event.key, EventSource.PRIMARY); } - handleTrailingActionNavigation(evt: MDCChipTrailingActionNavigationEvent) { - this.focusNextAction(evt.detail.key, EventSource.TRAILING); + handleTrailingActionNavigation(event: MDCChipTrailingActionNavigationEvent) { + this.focusNextAction(event.detail.key, EventSource.TRAILING); } /** @@ -406,23 +416,23 @@ export class MDCChipFoundation extends MDCFoundation { } } - private shouldStartEditing(evt: KeyboardEvent): boolean { - return this.eventFromPrimaryAction(evt) && evt.key === strings.ENTER_KEY; + private shouldStartEditing(event: KeyboardEvent): boolean { + return this.eventFromPrimaryAction(event) && event.key === strings.ENTER_KEY; } - private shouldFinishEditing(evt: KeyboardEvent): boolean { - return evt.key === strings.ENTER_KEY; + private shouldFinishEditing(event: KeyboardEvent): boolean { + return event.key === strings.ENTER_KEY; } - private shouldNotifyInteraction(evt: KeyboardEvent): boolean { - return evt.key === strings.ENTER_KEY || evt.key === strings.SPACEBAR_KEY; + private shouldNotifyInteraction(event: KeyboardEvent): boolean { + return event.key === strings.ENTER_KEY || event.key === strings.SPACEBAR_KEY; } - private isDeleteAction(evt: KeyboardEvent): boolean { + private isDeleteAction(event: KeyboardEvent): boolean { const isDeletable = this.adapter.hasClass(cssClasses.DELETABLE); return isDeletable && - (evt.key === strings.BACKSPACE_KEY || evt.key === strings.DELETE_KEY || - evt.key === strings.IE_DELETE_KEY); + (event.key === strings.BACKSPACE_KEY || event.key === strings.DELETE_KEY || + event.key === strings.IE_DELETE_KEY); } private setSelectedImpl(selected: boolean) { @@ -443,9 +453,9 @@ export class MDCChipFoundation extends MDCFoundation { this.adapter.notifySelection(selected, true); } - private eventFromPrimaryAction(evt: Event) { + private eventFromPrimaryAction(event: Event) { return this.adapter.eventTargetHasClass( - evt.target, cssClasses.PRIMARY_ACTION); + event.target, cssClasses.PRIMARY_ACTION); } private startEditing() { diff --git a/packages/mdc-chips/deprecated/chip/mdc-chip.import.scss b/packages/mdc-chips/deprecated/chip/mdc-chip.import.scss deleted file mode 100644 index da6e1a46eaa..00000000000 --- a/packages/mdc-chips/deprecated/chip/mdc-chip.import.scss +++ /dev/null @@ -1,39 +0,0 @@ -@forward "@material/animation/variables" as mdc-animation-*; -@forward "@material/ripple/variables" as mdc-* hide $mdc-dark-ink-opacities, $mdc-fade-in-duration, $mdc-fade-out-duration, $mdc-light-ink-opacities, $mdc-pressed-dark-ink-opacity, $mdc-pressed-light-ink-opacity, $mdc-translate-duration; -@forward "@material/ripple/variables" as mdc-ripple-* hide $mdc-ripple-states-wash-duration; -@forward "@material/theme/variables" as mdc-theme-*; -@forward "@material/density/variables" as mdc-density-*; -@forward "@material/checkbox/variables" as mdc-checkbox-*; -@forward "@material/base/mixins" as mdc-base-*; -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; -@forward "@material/elevation/variables" as mdc-elevation-*; -@forward "@material/rtl/variables" as mdc-rtl-*; -@forward "@material/touch-target/variables" as mdc-touch-target-*; -@forward "@material/typography/variables" as mdc-typography-*; -@forward "@material/shape/variables" as mdc-shape-*; -@forward "../variables" as mdc-chip-*; -@forward "../mixins" as mdc-chip-*; -@forward "@material/theme/mixins" as mdc-theme-*; -@forward "@material/elevation/mixins" as mdc-* hide mdc-core-styles, mdc-overlay-common, mdc-overlay-dimensions, mdc-overlay-fill-color, mdc-overlay-opacity, mdc-overlay-selector-, mdc-overlay-surface-position, mdc-shadow; -@forward "@material/elevation/mixins" as mdc-elevation-* hide mdc-elevation-elevation; -@forward "@material/ripple/keyframes" as mdc-ripple-*; -@forward "@material/ripple/mixins" as mdc-* hide mdc-common, mdc-core-styles, mdc-radius-bounded, mdc-radius-unbounded, mdc-surface, mdc-target-common, mdc-target-selector; -@forward "@material/ripple/mixins" as mdc-ripple-* hide mdc-ripple-states, mdc-ripple-states-activated, mdc-ripple-states-base-color, mdc-ripple-states-focus-opacity, mdc-ripple-states-focus-opacity-properties-, mdc-ripple-states-hover-opacity, mdc-ripple-states-interactions-, mdc-ripple-states-opacities, mdc-ripple-states-press-opacity, mdc-ripple-states-selected; -@forward "@material/rtl/mixins" as mdc-* hide mdc-property-, mdc-reflexive, mdc-reflexive-box, mdc-reflexive-position, mdc-reflexive-property; -@forward "@material/rtl/mixins" as mdc-rtl-* hide mdc-rtl-rtl; -@forward "@material/touch-target/mixins" as mdc-* hide mdc-margin, mdc-wrapper; -@forward "@material/touch-target/mixins" as mdc-touch-target-* hide mdc-touch-target-touch-target; -@forward "@material/typography/mixins" as mdc-* hide mdc-base, mdc-baseline-bottom, mdc-baseline-strut-, mdc-baseline-top, mdc-core-styles, mdc-overflow-ellipsis; -@forward "@material/typography/mixins" as mdc-typography-* hide mdc-typography-typography; -@forward "@material/shape/mixins" as mdc-shape-*; -@forward "@material/animation/functions" as mdc-animation-*; -@forward "@material/theme/functions" as mdc-theme-*; -@forward "@material/checkbox/functions" as mdc-checkbox-*; -@forward "@material/feature-targeting/functions" as mdc-feature-*; -@forward "@material/elevation/functions" as mdc-elevation-*; -@forward "@material/density/functions" as mdc-density-*; -@forward "@material/ripple/functions" as mdc-*; -@forward "@material/typography/functions" as mdc-typography-*; -@forward "@material/shape/functions" as mdc-shape-*; -@forward "mdc-chip"; diff --git a/packages/mdc-chips/deprecated/chip/test/component.test.ts b/packages/mdc-chips/deprecated/chip/test/component.test.ts index 09e592776c1..d6cd2bc449f 100644 --- a/packages/mdc-chips/deprecated/chip/test/component.test.ts +++ b/packages/mdc-chips/deprecated/chip/test/component.test.ts @@ -22,6 +22,7 @@ */ import {MDCRipple} from '../../../../mdc-ripple/index'; +import {createFixture, html} from '../../../../../testing/dom'; import {emitEvent} from '../../../../../testing/dom/events'; import {createMockFoundation} from '../../../../../testing/helpers/foundation'; import {strings as trailingActionStrings} from '../../trailingaction/constants'; @@ -29,25 +30,19 @@ import {chipStrings, MDCChip, MDCChipFoundation} from '../index'; const {CHECKMARK_SELECTOR} = MDCChipFoundation.strings; -const getFixture = () => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` +function getFixture() { + return createFixture(html`
Chip content -
`; - - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; -}; + `); +} -const getFixtureWithCheckmark = () => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` +function getFixtureWithCheckmark() { + return createFixture(html`
@@ -60,57 +55,45 @@ const getFixtureWithCheckmark = () => { Chip content -
`; - - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; -}; - -const addLeadingIcon = (root: HTMLElement) => { - const wrapper = document.createElement('div'); +
`); +} - wrapper.innerHTML = - `face`; - const icon = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(icon); +function addLeadingIcon(root: HTMLElement) { + const icon = createFixture( + html`face`); root.insertBefore(icon, root.firstChild); return icon; -}; - -const addTrailingAction = (root: HTMLElement, isFocusable?: boolean) => { - const wrapper = document.createElement('div'); +} - wrapper.innerHTML = ``; - const parent = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(parent); +function addTrailingAction(root: HTMLElement, isFocusable?: boolean) { + const parent = createFixture(html``); - let innerHTML: string; + let innerHTML: ReturnType; if (isFocusable) { innerHTML = - ``; } else { - innerHTML = ``; } - wrapper.innerHTML = innerHTML; - const trailingAction = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(trailingAction); + const trailingAction = createFixture(innerHTML); parent.appendChild(trailingAction); root.appendChild(parent); return trailingAction; -}; +} -const addFocusableTrailingAction = (root: HTMLElement) => - addTrailingAction(root, true); +function addFocusableTrailingAction(root: HTMLElement) { + return addTrailingAction(root, true); +} class FakeRipple { destroy: jasmine.Spy; @@ -216,6 +199,10 @@ describe('MDCChip', () => { component.destroy(); emitEvent(root, trailingActionStrings.INTERACTION_EVENT); expect(mockFoundation.handleTrailingActionInteraction) + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Expected 0 arguments, but + // got 1. .not.toHaveBeenCalledWith(jasmine.anything()); emitEvent(root, trailingActionStrings.NAVIGATION_EVENT); expect(mockFoundation.handleTrailingActionNavigation) @@ -234,15 +221,11 @@ describe('MDCChip', () => { }); it('sets id on chip if attribute exists', () => { - const wrapper = document.createElement('div'); - - wrapper.innerHTML = ` + const root = createFixture(html`
Hello
- `; - const root = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(root); + `); const component = new MDCChip(root); expect(component.id).toEqual('hello-chip'); @@ -252,22 +235,21 @@ describe('MDCChip', () => { () => { const {root, component} = setupTest(); root.classList.add('foo'); - expect( - (component.getDefaultFoundation() as any).adapter.hasClass('foo')) + expect((component.getDefaultFoundation() as any).adapter.hasClass('foo')) .toBe(true); }); it('adapter#addClass adds a class to the root element', () => { const {root, component} = setupTest(); (component.getDefaultFoundation() as any).adapter.addClass('foo'); - expect(root.classList.contains('foo')).toBe(true); + expect(root).toHaveClass('foo'); }); it('adapter#removeClass removes a class from the root element', () => { const {root, component} = setupTest(); root.classList.add('foo'); (component.getDefaultFoundation() as any).adapter.removeClass('foo'); - expect(root.classList.contains('foo')).toBe(false); + expect(root).not.toHaveClass('foo'); }); it('adapter#addClassToLeadingIcon adds a class to the leading icon element', @@ -278,7 +260,7 @@ describe('MDCChip', () => { (component.getDefaultFoundation() as any) .adapter.addClassToLeadingIcon('foo'); - expect(leadingIcon.classList.contains('foo')).toBe(true); + expect(leadingIcon).toHaveClass('foo'); }); it('adapter#addClassToLeadingIcon does nothing if no leading icon element is present', @@ -299,7 +281,7 @@ describe('MDCChip', () => { leadingIcon.classList.add('foo'); (component.getDefaultFoundation() as any) .adapter.removeClassFromLeadingIcon('foo'); - expect(leadingIcon.classList.contains('foo')).toBe(false); + expect(leadingIcon).not.toHaveClass('foo'); }); it('adapter#removeClassFromLeadingIcon does nothing if no leading icon element is present', @@ -314,12 +296,7 @@ describe('MDCChip', () => { it('adapter#eventTargetHasClass returns true if given element has class', () => { const {component} = setupTest(); - - const wrapper = document.createElement('div'); - - wrapper.innerHTML = `
bar
`; - const mockEventTarget = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(mockEventTarget); + const mockEventTarget = createFixture(html`
bar
`); expect((component.getDefaultFoundation() as any) .adapter.eventTargetHasClass(mockEventTarget, 'foo')) @@ -437,7 +414,7 @@ describe('MDCChip', () => { () => { const root = getFixtureWithCheckmark(); const component = new MDCChip(root); - const checkmark = root.querySelector(CHECKMARK_SELECTOR); + const checkmark = root.querySelector(CHECKMARK_SELECTOR); checkmark!.getBoundingClientRect = jasmine.createSpy(''); (component.getDefaultFoundation() as any) @@ -477,7 +454,8 @@ describe('MDCChip', () => { document.documentElement.appendChild(root); (component.getDefaultFoundation() as any).adapter.focusPrimaryAction(); expect(document.activeElement) - .toEqual(root.querySelector(chipStrings.PRIMARY_ACTION_SELECTOR)); + .toEqual(root.querySelector( + chipStrings.PRIMARY_ACTION_SELECTOR)); document.documentElement.removeChild(root); }); @@ -496,7 +474,7 @@ describe('MDCChip', () => { () => { const {root, component} = setupTest(); const primaryAction = - root.querySelector(chipStrings.PRIMARY_ACTION_SELECTOR); + root.querySelector(chipStrings.PRIMARY_ACTION_SELECTOR); (component.getDefaultFoundation() as any) .adapter.setPrimaryActionAttr('tabindex', '-1'); expect(primaryAction!.getAttribute('tabindex')).toEqual('-1'); @@ -607,6 +585,6 @@ describe('MDCChip', () => { const {component, root} = setupTest(); document.documentElement.appendChild(root); component.remove(); - expect(document.querySelector('.mdc-chip')).toEqual(null); + expect(document.querySelector('.mdc-chip')).toEqual(null); }); }); diff --git a/packages/mdc-chips/deprecated/chip/test/foundation.test.ts b/packages/mdc-chips/deprecated/chip/test/foundation.test.ts index 7b4bc6128b0..928e6be9d47 100644 --- a/packages/mdc-chips/deprecated/chip/test/foundation.test.ts +++ b/packages/mdc-chips/deprecated/chip/test/foundation.test.ts @@ -171,6 +171,11 @@ describe('MDCChipFoundation', () => { const {foundation, mockAdapter} = setupTest(); mockAdapter.getCheckmarkBoundingClientRect.and.returnValue(null); const boundingRect = {width: 10, height: 10}; + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type '{ width: + // number; height: number; }' is not assignable to parameter of type + // 'DOMRect'. mockAdapter.getRootBoundingClientRect.and.returnValue(boundingRect); expect(foundation.getDimensions() === boundingRect).toBe(true); @@ -182,7 +187,17 @@ describe('MDCChipFoundation', () => { const boundingRect = {width: 10, height: 10}; const checkmarkBoundingRect = {width: 5, height: 5}; mockAdapter.getCheckmarkBoundingClientRect.and.returnValue( + // TODO: Wait until b/208710526 is fixed, then remove this + // autogenerated error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type '{ width: + // number; height: number; }' is not assignable to parameter of type + // 'DOMRect'. checkmarkBoundingRect); + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type '{ width: + // number; height: number; }' is not assignable to parameter of type + // 'DOMRect'. mockAdapter.getRootBoundingClientRect.and.returnValue(boundingRect); mockAdapter.hasLeadingIcon.and.returnValue(false); @@ -197,8 +212,18 @@ describe('MDCChipFoundation', () => { const {foundation, mockAdapter} = setupTest(); const checkmarkBoundingRect = {width: 5, height: 5}; mockAdapter.getCheckmarkBoundingClientRect.and.returnValue( + // TODO: Wait until b/208710526 is fixed, then remove this + // autogenerated error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type '{ width: + // number; height: number; }' is not assignable to parameter of type + // 'DOMRect'. checkmarkBoundingRect); const boundingRect = {width: 10, height: 10}; + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type '{ width: + // number; height: number; }' is not assignable to parameter of type + // 'DOMRect'. mockAdapter.getRootBoundingClientRect.and.returnValue(boundingRect); mockAdapter.hasLeadingIcon.and.returnValue(true); @@ -213,12 +238,12 @@ describe('MDCChipFoundation', () => { it('#handleKeydown does not emit event on invalid key', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'keydown', key: 'Shift', } as KeyboardEvent; - foundation.handleKeydown(mockEvt); + foundation.handleKeydown(mockevent); expect(mockAdapter.notifyInteraction).not.toHaveBeenCalled(); expect(mockAdapter.notifyNavigation).not.toHaveBeenCalled(); }); @@ -275,18 +300,18 @@ describe('MDCChipFoundation', () => { } as KeyboardEvent, ]; - for (const evt of validKeyDownTable) { - it(`#handleKeydown(${evt.key}) notifies interaction`, () => { + for (const event of validKeyDownTable) { + it(`#handleKeydown(${event.key}) notifies interaction`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.handleKeydown(evt); + foundation.handleKeydown(event); expect(mockAdapter.notifyInteraction).toHaveBeenCalled(); }); - it(`#handleKeydown(${evt.key}) focuses the primary action`, () => { + it(`#handleKeydown(${event.key}) focuses the primary action`, () => { const {foundation, mockAdapter} = setupTest(); - foundation.handleKeydown(evt); + foundation.handleKeydown(event); expect(mockAdapter.setPrimaryActionAttr) .toHaveBeenCalledWith(strings.TAB_INDEX, '0'); expect(mockAdapter.removeTrailingActionFocus).toHaveBeenCalled(); @@ -294,12 +319,12 @@ describe('MDCChipFoundation', () => { }); it(`#handleKeydown(${ - evt.key}) does not focus the primary action when configured`, + event.key}) does not focus the primary action when configured`, () => { const {foundation, mockAdapter} = setupTest(); foundation.setShouldFocusPrimaryActionOnClick(false); - foundation.handleKeydown(evt); + foundation.handleKeydown(event); expect(mockAdapter.focusPrimaryAction).not.toHaveBeenCalled(); }); } @@ -307,14 +332,14 @@ describe('MDCChipFoundation', () => { it('#handleTransitionEnd notifies removal of chip on width transition end', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'width', } as TransitionEvent; mockAdapter.eventTargetHasClass.and.returnValue(true); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.notifyRemoval).toHaveBeenCalled(); }); @@ -322,7 +347,7 @@ describe('MDCChipFoundation', () => { it('#handleTransitionEnd notifies removal of chip with removal announcement if present', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'width', @@ -330,7 +355,7 @@ describe('MDCChipFoundation', () => { mockAdapter.eventTargetHasClass.and.returnValue(true); mockAdapter.getAttribute.and.returnValue('Removed foo'); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.notifyRemoval).toHaveBeenCalledWith('Removed foo'); }); @@ -338,7 +363,7 @@ describe('MDCChipFoundation', () => { it('#handleTransitionEnd animates width if chip is exiting on chip opacity transition end', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'opacity', @@ -346,7 +371,7 @@ describe('MDCChipFoundation', () => { mockAdapter.eventTargetHasClass.and.returnValue(true); mockAdapter.getComputedStyleValue.and.returnValue('100px'); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); jasmine.clock().tick(1); expect(mockAdapter.setStyleProperty) @@ -364,20 +389,20 @@ describe('MDCChipFoundation', () => { 'on leading icon opacity transition end, if chip is selected', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'opacity', } as TransitionEvent; mockAdapter.eventTargetHasClass - .withArgs(mockEvt.target, cssClasses.CHIP_EXIT) + .withArgs(mockevent.target, cssClasses.CHIP_EXIT) .and.returnValue(false); mockAdapter.eventTargetHasClass - .withArgs(mockEvt.target, cssClasses.LEADING_ICON) + .withArgs(mockevent.target, cssClasses.LEADING_ICON) .and.returnValue(true); mockAdapter.hasClass.withArgs(cssClasses.SELECTED).and.returnValue(true); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.addClassToLeadingIcon) .toHaveBeenCalledWith(cssClasses.HIDDEN_LEADING_ICON); @@ -387,14 +412,14 @@ describe('MDCChipFoundation', () => { 'if chip is not selected', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'opacity', } as TransitionEvent; mockAdapter.eventTargetHasClass.and.returnValue(true); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.addClassToLeadingIcon) .not.toHaveBeenCalledWith(cssClasses.HIDDEN_LEADING_ICON); @@ -405,16 +430,16 @@ describe('MDCChipFoundation', () => { 'on checkmark opacity transition end, if chip is not selected', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'opacity', } as TransitionEvent; mockAdapter.eventTargetHasClass - .withArgs(mockEvt.target, cssClasses.CHECKMARK) + .withArgs(mockevent.target, cssClasses.CHECKMARK) .and.returnValue(true); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.removeClassFromLeadingIcon) .toHaveBeenCalledWith(cssClasses.HIDDEN_LEADING_ICON); @@ -423,7 +448,7 @@ describe('MDCChipFoundation', () => { it('#handleTransitionEnd does nothing on checkmark opacity transition end, if chip is selected', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'opacity', @@ -431,7 +456,7 @@ describe('MDCChipFoundation', () => { mockAdapter.eventTargetHasClass.and.returnValue(true); mockAdapter.hasClass.and.returnValue(true); - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.removeClassFromLeadingIcon) .not.toHaveBeenCalledWith(cssClasses.HIDDEN_LEADING_ICON); @@ -440,13 +465,13 @@ describe('MDCChipFoundation', () => { it('#handleTransitionEnd does nothing for width property when not exiting', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'transitionend', target: {}, propertyName: 'width', } as TransitionEvent; - foundation.handleTransitionEnd(mockEvt); + foundation.handleTransitionEnd(mockevent); expect(mockAdapter.notifyRemoval).not.toHaveBeenCalled(); expect(mockAdapter.addClassToLeadingIcon) @@ -497,12 +522,12 @@ describe('MDCChipFoundation', () => { for (const key of navigationKeyTable) { it(`#handleKeydown emits custom event for key ${key}`, () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'keydown', preventDefault: jasmine.createSpy('.preventDefault') as Function, key, } as KeyboardEvent; - foundation.handleKeydown(mockEvt); + foundation.handleKeydown(mockevent); expect(mockAdapter.notifyNavigation) .toHaveBeenCalledWith(key, EventSource.PRIMARY); }); @@ -510,26 +535,30 @@ describe('MDCChipFoundation', () => { it('#handleKeydown calls preventDefault on navigation events', () => { const {foundation} = setupTest(); - const mockEvt = { + const mockevent = { type: 'keydown', key: strings.ARROW_LEFT_KEY, preventDefault: jasmine.createSpy('.preventDefault') as Function, } as KeyboardEvent; - foundation.handleKeydown(mockEvt); - expect(mockEvt.preventDefault).toHaveBeenCalledTimes(1); + foundation.handleKeydown(mockevent); + expect(mockevent.preventDefault).toHaveBeenCalledTimes(1); }); it('#handleKeydown does not emit a custom event for inappropriate keys', () => { const {foundation, mockAdapter} = setupTest(); - const mockEvt = { + const mockevent = { type: 'keydown', key: ' ', } as KeyboardEvent; - foundation.handleKeydown(mockEvt); + foundation.handleKeydown(mockevent); expect(mockAdapter.notifyNavigation) + // TODO: Wait until b/208710526 is fixed, then remove this + // autogenerated error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Expected 2 arguments, but + // got 1. .not.toHaveBeenCalledWith(jasmine.any(String)); }); diff --git a/packages/mdc-chips/deprecated/chip/types.ts b/packages/mdc-chips/deprecated/chip/types.ts index 85a23f0d83c..0eb79957a5a 100644 --- a/packages/mdc-chips/deprecated/chip/types.ts +++ b/packages/mdc-chips/deprecated/chip/types.ts @@ -27,16 +27,19 @@ export interface MDCChipInteractionEventDetail { chipId: string; } -export interface MDCChipSelectionEventDetail extends MDCChipInteractionEventDetail { +export interface MDCChipSelectionEventDetail extends + MDCChipInteractionEventDetail { selected: boolean; shouldIgnore: boolean; } -export interface MDCChipRemovalEventDetail extends MDCChipInteractionEventDetail { +export interface MDCChipRemovalEventDetail extends + MDCChipInteractionEventDetail { removedAnnouncement: string|null; } -export interface MDCChipNavigationEventDetail extends MDCChipInteractionEventDetail { +export interface MDCChipNavigationEventDetail extends + MDCChipInteractionEventDetail { key: string; source: EventSource; } diff --git a/packages/mdc-chips/deprecated/mdc-chips.import.scss b/packages/mdc-chips/deprecated/mdc-chips.import.scss index e7436a846ea..3fffa6c30db 100644 --- a/packages/mdc-chips/deprecated/mdc-chips.import.scss +++ b/packages/mdc-chips/deprecated/mdc-chips.import.scss @@ -5,8 +5,6 @@ @forward "@material/density/variables" as mdc-density-*; @forward "@material/checkbox/variables" as mdc-checkbox-*; @forward "@material/base/mixins" as mdc-base-*; -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; @forward "@material/elevation/variables" as mdc-elevation-*; @forward "@material/rtl/variables" as mdc-rtl-*; @forward "@material/touch-target/variables" as mdc-touch-target-*; diff --git a/packages/mdc-chips/deprecated/test/mdc-chips.scss.test.ts b/packages/mdc-chips/deprecated/test/mdc-chips.scss.test.ts index 4a8680c9a85..dc6bbd05a7c 100644 --- a/packages/mdc-chips/deprecated/test/mdc-chips.scss.test.ts +++ b/packages/mdc-chips/deprecated/test/mdc-chips.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../../testing/featuretargeting'; describe('mdc-chips.scss', () => { diff --git a/packages/mdc-chips/deprecated/trailingaction/README.md b/packages/mdc-chips/deprecated/trailingaction/README.md index d6eb9e26125..bade73831c8 100644 --- a/packages/mdc-chips/deprecated/trailingaction/README.md +++ b/packages/mdc-chips/deprecated/trailingaction/README.md @@ -111,8 +111,8 @@ Method Signature | Description `removeFocus() => void` | Removes focus from the trailing action `focus() => void` | Gives focus to the trailing action `isNavigable() => boolean` | Returns the navigability of the trailing action -`handleClick(evt: MouseEvent) => void` | Handles a click event on the root element -`handleKeydown(evt: KeyboardEvent) => void` | Handles a keydown event on the root element +`handleClick(event: MouseEvent) => void` | Handles a click event on the root element +`handleKeydown(event: KeyboardEvent) => void` | Handles a keydown event on the root element #### `MDCChipTrailingActionFoundation` Event Handlers diff --git a/packages/mdc-chips/deprecated/trailingaction/_mixins.scss b/packages/mdc-chips/deprecated/trailingaction/_mixins.scss index 074d6c5f86e..b814ddfe0ca 100644 --- a/packages/mdc-chips/deprecated/trailingaction/_mixins.scss +++ b/packages/mdc-chips/deprecated/trailingaction/_mixins.scss @@ -20,8 +20,8 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern @use '@material/feature-targeting/feature-targeting'; @use '@material/ripple/ripple'; diff --git a/packages/mdc-chips/deprecated/trailingaction/component.ts b/packages/mdc-chips/deprecated/trailingaction/component.ts index 53db4eb2a06..6fec1a14362 100644 --- a/packages/mdc-chips/deprecated/trailingaction/component.ts +++ b/packages/mdc-chips/deprecated/trailingaction/component.ts @@ -27,6 +27,7 @@ import {MDCRippleAdapter} from '@material/ripple/adapter'; import {MDCRipple, MDCRippleFactory} from '@material/ripple/component'; import {MDCRippleFoundation} from '@material/ripple/foundation'; import {MDCRippleCapableSurface} from '@material/ripple/types'; + import {MDCChipTrailingActionAdapter} from './adapter'; import {strings} from './constants'; import {MDCChipTrailingActionFoundation} from './foundation'; @@ -36,9 +37,10 @@ import {MDCChipTrailingActionInteractionEventDetail, MDCChipTrailingActionNaviga * Creates a trailing action component on the given element. */ export type MDCChipTrailingActionFactory = - (el: Element, foundation?: MDCChipTrailingActionFoundation) => + (el: HTMLElement, foundation?: MDCChipTrailingActionFoundation) => MDCChipTrailingAction; +/** MDC Chip Trailing Action */ export class MDCChipTrailingAction extends MDCComponent implements MDCRippleCapableSurface { @@ -46,7 +48,7 @@ export class MDCChipTrailingAction extends return this.rippleSurface; } - static attachTo(root: Element) { + static override attachTo(root: HTMLElement) { return new MDCChipTrailingAction(root); } @@ -56,7 +58,7 @@ export class MDCChipTrailingAction extends private handleKeydown!: SpecificEventListener<'keydown'>; // assigned in initialSyncWithDOM() - initialize( + override initialize( rippleFactory: MDCRippleFactory = (el, foundation) => new MDCRipple(el, foundation)) { // DO NOT INLINE this variable. For backward compatibility, foundations take @@ -67,44 +69,44 @@ export class MDCChipTrailingAction extends rippleFactory(this.root, new MDCRippleFoundation(rippleAdapter)); } - initialSyncWithDOM() { - this.handleClick = (evt: MouseEvent) => { - this.foundation.handleClick(evt); + override initialSyncWithDOM() { + this.handleClick = (event: MouseEvent) => { + this.foundation.handleClick(event); }; - this.handleKeydown = (evt: KeyboardEvent) => { - this.foundation.handleKeydown(evt); + this.handleKeydown = (event: KeyboardEvent) => { + this.foundation.handleKeydown(event); }; this.listen('click', this.handleClick); this.listen('keydown', this.handleKeydown); } - destroy() { + override destroy() { this.rippleSurface.destroy(); this.unlisten('click', this.handleClick); this.unlisten('keydown', this.handleKeydown); super.destroy(); } - getDefaultFoundation() { + override getDefaultFoundation() { // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. const adapter: MDCChipTrailingActionAdapter = { focus: () => { - // TODO(b/157231863): Migate MDCComponent#root to HTMLElement - (this.root as HTMLElement).focus(); + this.root.focus(); }, getAttribute: (attr) => this.root.getAttribute(attr), - notifyInteraction: (trigger) => - this.emit( - strings.INTERACTION_EVENT, {trigger}, true /* shouldBubble */), + notifyInteraction: (trigger) => { + this.emit( + strings.INTERACTION_EVENT, {trigger}, true /* shouldBubble */); + }, notifyNavigation: (key) => { this.emit( strings.NAVIGATION_EVENT, {key}, true /* shouldBubble */); }, setAttribute: (attr, value) => { - this.root.setAttribute(attr, value); + this.safeSetAttribute(this.root, attr, value); }, }; return new MDCChipTrailingActionFoundation(adapter); diff --git a/packages/mdc-chips/deprecated/trailingaction/foundation.ts b/packages/mdc-chips/deprecated/trailingaction/foundation.ts index 5380a33702d..e341da0dd78 100644 --- a/packages/mdc-chips/deprecated/trailingaction/foundation.ts +++ b/packages/mdc-chips/deprecated/trailingaction/foundation.ts @@ -27,13 +27,14 @@ import {isNavigationEvent, KEY, normalizeKey} from '@material/dom/keyboard'; import {MDCChipTrailingActionAdapter} from './adapter'; import {InteractionTrigger, strings} from './constants'; +/** MDC Chip Trailing Action Foundation */ export class MDCChipTrailingActionFoundation extends MDCFoundation { - static get strings() { + static override get strings() { return strings; } - static get defaultAdapter(): MDCChipTrailingActionAdapter { + static override get defaultAdapter(): MDCChipTrailingActionAdapter { return { focus: () => undefined, getAttribute: () => null, @@ -47,21 +48,21 @@ export class MDCChipTrailingActionFoundation extends super({...MDCChipTrailingActionFoundation.defaultAdapter, ...adapter}); } - handleClick(evt: MouseEvent) { - evt.stopPropagation(); + handleClick(event: MouseEvent) { + event.stopPropagation(); this.adapter.notifyInteraction(InteractionTrigger.CLICK); } - handleKeydown(evt: KeyboardEvent) { - evt.stopPropagation(); - const key = normalizeKey(evt); + handleKeydown(event: KeyboardEvent) { + event.stopPropagation(); + const key = normalizeKey(event); if (this.shouldNotifyInteractionFromKey(key)) { const trigger = this.getTriggerFromKey(key); this.adapter.notifyInteraction(trigger); return; } - if (isNavigationEvent(evt)) { + if (isNavigationEvent(event)) { this.adapter.notifyNavigation(key); return; } diff --git a/packages/mdc-chips/deprecated/trailingaction/test/component.test.ts b/packages/mdc-chips/deprecated/trailingaction/test/component.test.ts index 70ecaa813bf..e7a84640f7b 100644 --- a/packages/mdc-chips/deprecated/trailingaction/test/component.test.ts +++ b/packages/mdc-chips/deprecated/trailingaction/test/component.test.ts @@ -22,22 +22,18 @@ */ import {MDCRipple} from '../../../../mdc-ripple/index'; +import {createFixture, html} from '../../../../../testing/dom'; import {emitEvent} from '../../../../../testing/dom/events'; import {createMockFoundation} from '../../../../../testing/helpers/foundation'; import {MDCChipTrailingAction, MDCChipTrailingActionFoundation, trailingActionStrings as strings} from '../index'; const getFixture = () => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html` +
+ +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -285,6 +290,41 @@ Note: Full-screen dialogs are intended for mobile/small-screen devices. The dialog's size will adapt to the screen size, and so becomes modal if used on larger screen sizes. +## Floating sheet + +Floating sheets are dialogs with a close icon button. Clicking the close icon +button closes the sheet. Having the close icon button is mutually exclusive with +having action bar buttons (e.g. cancel and OK buttons). The icon button is +absolutely positioned. Sheet content can have no default padding by using the +`mdc-dialog--no-content-padding` class. + +### Floating sheet example + +```html +
+
+
+
+
+ +
+
+
+

Sheets

+ There are no action buttons. Any HTML content can go here. Title is also defined through content. +
+
+
+
+
+``` + ## Additional Information ### Dialog actions @@ -365,6 +405,7 @@ CSS Class | Description --- | --- `mdc-dialog` | Mandatory. The root DOM element containing the surface and the container. `mdc-dialog__scrim` | Mandatory. Semitransparent backdrop that displays behind a dialog. +`mdc-dialog__scrim--removed` | Optional. Remove semitransparent backdrop behind dialog and allow pointer on a content behind a dialog. `mdc-dialog__container` | Mandatory. Wrapper element needed to ensure flexbox behavior in IE 11. `mdc-dialog__surface` | Mandatory. The bounding box for the dialog's content. `mdc-dialog__title` | Optional. Brief summary of the dialog's purpose. @@ -385,6 +426,7 @@ Mixin | Description `scrim-color($color, $opacity)` | Sets the color of the scrim behind the dialog. `title-ink-color($color, $opacity)` | Sets the color of the dialog's title text. `content-ink-color($color, $opacity)` | Sets the color of the dialog's content text. +`content-display($display)` | Sets the display property of the dialog's content. `content-padding($padding-top, $padding-right, $padding-bottom, $padding-left)` | Sets the padding of the dialog's content. `scroll-divider-color($color, $opacity)` | Sets the color of the dividers which display around scrollable content. `shape-radius($radius, $rtl-reflexive)` | Sets the rounded shape to dialog surface with given radius size. Set `$rtl-reflexive` to true to flip radius values in RTL context, defaults to false. diff --git a/packages/mdc-dialog/_dialog-theme.scss b/packages/mdc-dialog/_dialog-theme.scss new file mode 100644 index 00000000000..e02c469cec8 --- /dev/null +++ b/packages/mdc-dialog/_dialog-theme.scss @@ -0,0 +1,272 @@ +// +// Copyright 2021 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software- +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT- IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE- +// + +// Selector '-mdc-*' should only be used in this project- +// stylelint-disable selector-class-pattern -- +// Internal styling for Dialog MDC component- + +@use 'sass:map'; +@use 'sass:meta'; +@use '@material/elevation/elevation-theme'; +@use '@material/shape/shape'; +@use '@material/theme/keys'; +@use '@material/theme/theme'; +@use '@material/tokens/resolvers'; +@use '@material/button/button-text-theme'; + +$light-theme: ( + container-color: null, + container-elevation: null, + container-shadow-color: null, + container-shape: null, + container-surface-tint-layer-color: null, + with-divider-divider-color: null, + with-divider-divider-height: null, + with-icon-icon-size: null, + with-icon-icon-color: null, + subhead-font: null, + subhead-line-height: null, + subhead-size: null, + subhead-weight: null, + subhead-tracking: null, + subhead-color: null, + supporting-text-font: null, + supporting-text-line-height: null, + supporting-text-size: null, + supporting-text-weight: null, + supporting-text-tracking: null, + supporting-text-color: null, + action-label-text-font: null, + action-label-text-line-height: null, + action-label-text-size: null, + action-label-text-weight: null, + action-label-text-tracking: null, + action-label-text-color: null, + action-hover-state-layer-color: null, + action-hover-state-layer-opacity: null, + action-hover-label-text-color: null, + action-focus-state-layer-color: null, + action-focus-state-layer-opacity: null, + action-focus-label-text-color: null, + action-pressed-state-layer-color: null, + action-pressed-state-layer-opacity: null, + action-pressed-label-text-color: null, + // TODO(b/229523517): add headline token support to dialog + headline-color: null, + headline-font: null, + headline-line-height: null, + headline-size: null, + headline-tracking: null, + headline-weight: null, + z-index: null, +); + +$custom-property-prefix: 'dialog'; + +@mixin theme($theme, $resolvers: resolvers.$material) { + // TODO(b/251881053): Replace with `validate-theme`. + @include theme.validate-theme-styles($light-theme, $theme); + $theme: _resolve-elevation($theme, map.get($resolvers, elevation)); + @include keys.declare-custom-properties( + $theme, + $prefix: $custom-property-prefix + ); +} + +@mixin theme-styles($theme, $resolvers: resolvers.$material) { + @include theme.validate-theme-styles($light-theme, $theme); + $theme: keys.create-theme-properties( + $theme, + $prefix: $custom-property-prefix + ); + + @include _container-color(map.get($theme, container-color)); + @include _container-elevation( + map.get($resolvers, elevation), + map.get($theme, container-elevation), + map.get($theme, container-shadow-color) + ); + @include container-shape(map.get($theme, container-shape)); + @include _divider-color(map.get($theme, with-divider-divider-color)); + @include _divider-height(map.get($theme, with-divider-divider-height)); + @include _icon-size(map.get($theme, with-icon-icon-size)); + @include _icon-color(map.get($theme, with-icon-icon-color)); + @include subhead-typography( + ( + font: map.get($theme, subhead-font), + line-height: map.get($theme, subhead-line-height), + size: map.get($theme, subhead-size), + weight: map.get($theme, subhead-weight), + tracking: map.get($theme, subhead-tracking), + ) + ); + @include _subhead-color(map.get($theme, subhead-color)); + @include _supporting-text-typography( + ( + font: map.get($theme, supporting-text-font), + line-height: map.get($theme, supporting-text-line-height), + size: map.get($theme, supporting-text-size), + weight: map.get($theme, supporting-text-weight), + tracking: map.get($theme, supporting-text-tracking), + ) + ); + @include _supporting-text-color(map.get($theme, supporting-text-color)); + @include _z-index(map.get($theme, z-index)); + + .mdc-dialog__actions .mdc-button { + @include button-text-theme.theme-styles-internal( + ( + focus-label-text-color: map.get($theme, action-focus-label-text-color), + focus-state-layer-color: map.get($theme, action-focus-state-layer-color), + focus-state-layer-opacity: + map.get($theme, action-focus-state-layer-opacity), + hover-label-text-color: map.get($theme, action-hover-text-color), + hover-state-layer-color: map.get($theme, action-hover-state-layer-color), + hover-state-layer-opacity: + map.get($theme, action-hover-state-layer-opacity), + label-text-color: map.get($theme, action-label-text-color), + label-text-font: map.get($theme, action-label-text-font), + label-text-size: map.get($theme, action-label-text-size), + label-text-tracking: map.get($theme, action-label-text-tracking), + label-text-weight: map.get($theme, action-label-text-weight), + pressed-label-text-color: + map.get($theme, action-pressed-label-text-color), + pressed-state-layer-color: + map.get($theme, action-pressed-state-layer-color), + pressed-state-layer-opacity: + map.get($theme, action-pressed-state-layer-opacity), + ) + ); + } +} + +@mixin _container-color($color) { + .mdc-dialog__surface { + @include theme.property(background-color, $color); + } +} + +@mixin _container-elevation($elevation-resolver, $elevation, $shadow-color) { + .mdc-dialog__surface { + @include elevation-theme.with-resolver( + $elevation-resolver, + $elevation: $elevation, + $shadow-color: $shadow-color + ); + } +} + +@mixin container-shape($radius) { + .mdc-dialog__surface { + @include shape.radius($radius); + } +} + +@mixin _divider-color($color) { + &.mdc-dialog--scrollable .mdc-dialog__title, + &.mdc-dialog--scrollable .mdc-dialog__actions, + &.mdc-dialog--scrollable.mdc-dialog-scroll-divider-footer + .mdc-dialog__actions { + @include theme.property(border-color, $color); + } + + &.mdc-dialog--scrollable .mdc-dialog__title { + @include theme.property(border-bottom-color, $color); + } +} + +@mixin _divider-height($height) { + .mdc-dialog__actions { + @include theme.property(border-top-width, $height); + } + + &.mdc-dialog--scrollable .mdc-dialog__title { + @include theme.property(border-bottom-width, $height); + } +} + +@mixin _icon-size($size) { + .mdc-dialog__title-icon { + @include theme.property(height, $size); + @include theme.property(width, $size); + @include theme.property(font-size, $size); + } +} + +@mixin _icon-color($color) { + .mdc-dialog__title-icon { + @include theme.property(color, $color); + } +} + +@mixin subhead-typography($typography) { + .mdc-dialog__title { + @include theme.property(font-family, map.get($typography, font)); + @include theme.property(line-height, map.get($typography, line-height)); + @include theme.property(font-size, map.get($typography, size)); + @include theme.property(font-weight, map.get($typography, weight)); + @include theme.property(letter-spacing, map.get($typography, tracking)); + } +} + +@mixin _subhead-color($color) { + .mdc-dialog__title { + @include theme.property(color, $color); + } +} + +@mixin _supporting-text-typography($typography) { + .mdc-dialog__content { + @include theme.property(font-family, map.get($typography, font)); + @include theme.property(line-height, map.get($typography, line-height)); + @include theme.property(font-size, map.get($typography, size)); + @include theme.property(font-weight, map.get($typography, weight)); + @include theme.property(letter-spacing, map.get($typography, tracking)); + } +} + +@mixin _supporting-text-color($color) { + .mdc-dialog__content { + @include theme.property(color, $color); + } +} + +@mixin _z-index($z-index) { + &.mdc-dialog { + @include theme.property(z-index, $z-index); + } +} + +@function _resolve-elevation($theme, $elevation-resolver) { + @if map.get($theme, container-elevation) and + map.get($theme, container-shadow-color) + { + $resolved-value: meta.call( + $elevation-resolver, + $elevation: map.get($theme, container-elevation), + $shadow-color: map.get($theme, container-shadow-color) + ); + $theme: map.set($theme, container-elevation, $resolved-value); + } + + @return $theme; +} diff --git a/packages/mdc-dialog/_mixins.scss b/packages/mdc-dialog/_mixins.scss index 7143040c621..4223688426b 100644 --- a/packages/mdc-dialog/_mixins.scss +++ b/packages/mdc-dialog/_mixins.scss @@ -20,17 +20,18 @@ // THE SOFTWARE. // -// Selector '.mdc-*' should only be used in this project. // stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. // Internal styling for Dialog MDC component. +@use 'sass:map'; @use 'sass:math'; @use '@material/animation/functions' as animation-functions; @use '@material/button/button-theme'; @use '@material/dom/dom'; @use '@material/elevation/mixins' as elevation-mixins; @use '@material/feature-targeting/feature-targeting'; -@use '@material/rtl/mixins' as rtl-mixins; +@use '@material/rtl/rtl'; @use '@material/shape/mixins' as shape-mixins; @use '@material/theme/custom-properties'; @use '@material/theme/theme'; @@ -38,10 +39,10 @@ @use '@material/touch-target/variables' as touch-target-variables; @use '@material/typography/typography'; @use './dialog-custom-properties'; -@use '@material/icon-button/mixins' as iconbutton-mixins; +@use '@material/icon-button/icon-button-theme'; @use './variables'; -@mixin core-styles($query: feature-targeting.all()) { +@mixin static-styles($query: feature-targeting.all()) { $feat-animation: feature-targeting.create-target($query, animation); $feat-structure: feature-targeting.create-target($query, structure); @@ -65,20 +66,11 @@ } .mdc-dialog { - @include container-fill-color(surface, $query: $query); - @include scrim-color(variables.$scrim-color, $query: $query); - @include title-ink-color(variables.$title-ink-color, $query: $query); - @include content-ink-color(variables.$content-ink-color, $query: $query); - @include scroll-divider-color( - variables.$scroll-divider-color, - $query: $query - ); // Note: the top padding is only 20px for dialogs without titles; see below for override. @include content-padding(20px, 24px, 20px, 24px, $query: $query); @include min-width(variables.$min-width, $query: $query); @include max-width(variables.$max-width, variables.$margin, $query: $query); @include max-height(null, variables.$margin, $query: $query); - @include shape-radius(variables.$shape-radius, $query: $query); @include feature-targeting.targets($feat-structure) { // Use `display: none` instead of `visibility: hidden` to avoid recalculating layout when the dialog is closed. @@ -120,7 +112,6 @@ justify-content: space-around; // Ensure Safari centers the dialog (because it treats the container's width oddly) box-sizing: border-box; height: 100%; - transform: scale(0.8); opacity: 0; // This element is necessary for IE 11 and needs to have `height: 100%`. // Let clicks on element fall through to scrim element underneath. @@ -131,7 +122,6 @@ .mdc-dialog__surface { @include elevation-mixins.overlay-surface-position($query: $query); @include elevation-mixins.overlay-dimensions(100%, $query: $query); - @include elevation-mixins.elevation(24, $query: $query); @include feature-targeting.targets($feat-structure) { display: flex; @@ -144,9 +134,12 @@ pointer-events: auto; // Override from `.mdc-dialog__container`. // IE 11: Otherwise, scrolling content in `mdc-dialog__content` overflows. overflow-y: auto; + outline: 0; + // Dialog scales up as it opens. + transform: scale(0.8); - @include rtl-mixins.rtl { - /* @noflip */ + @include rtl.rtl { + @include rtl.ignore-next-line(); text-align: right; } @@ -174,10 +167,9 @@ @include typography.text-baseline( $top: 40px, $display: block, + $lineHeight: null, $query: $query ); - @include typography.typography(headline6, $query: $query); - @include feature-targeting.targets($feat-structure) { position: relative; flex-shrink: 0; @@ -185,14 +177,13 @@ margin: 0 0 1px; padding: 0 24px variables.$title-bottom-padding; - @include rtl-mixins.rtl { - /* @noflip */ + @include rtl.rtl { + @include rtl.ignore-next-line(); text-align: right; } } } - // stylelint-disable-next-line plugin/selector-bem-pattern .mdc-dialog--scrollable .mdc-dialog__title { @include feature-targeting.targets($feat-structure) { margin-bottom: 1px; @@ -213,6 +204,10 @@ variables.$title-bottom-padding; z-index: 1; + @include dom.forced-colors-mode($exclude-ie11: true) { + border-bottom-color: CanvasText; + } + @include _modal-header( $close-icon-padding: variables.$close-icon-padding ); @@ -234,7 +229,7 @@ } } - .mdc-dialog__close { + .mdc-dialog__close-tooltip-wrapper { @include feature-targeting.targets($feat-structure) { top: 5px; } @@ -249,19 +244,34 @@ // border-top should be visible. @include feature-targeting.targets($feat-structure) { border-top: 1px solid transparent; + @include dom.forced-colors-mode($exclude-ie11: true) { + border-top-color: CanvasText; + } } } } - .mdc-dialog__content { - @include typography.typography(body1, $query: $query); + // Needed so that close affordance is aligned as if there was a title. + .mdc-dialog--fullscreen--titleless { + .mdc-dialog__close-tooltip-wrapper { + @include feature-targeting.targets($feat-structure) { + margin-top: 4px; + } + } + + &.mdc-dialog--scrollable .mdc-dialog__close-tooltip-wrapper { + @include feature-targeting.targets($feat-structure) { + margin-top: 0; + } + } + } + .mdc-dialog__content { @include feature-targeting.targets($feat-structure) { flex-grow: 1; box-sizing: border-box; margin: 0; overflow: auto; - -webkit-overflow-scrolling: touch; } // The content element already has top/bottom padding, so we need to suppress margins on its first/last children. @@ -279,7 +289,6 @@ } } - // stylelint-disable-next-line plugin/selector-bem-pattern .mdc-dialog__title + .mdc-dialog__content, .mdc-dialog__header + .mdc-dialog__content { @include feature-targeting.targets($feat-structure) { @@ -289,7 +298,6 @@ } } - // stylelint-disable-next-line plugin/selector-bem-pattern .mdc-dialog--scrollable .mdc-dialog__title + .mdc-dialog__content { @include feature-targeting.targets($feat-structure) { // Reduce and equalize vertical paddings when scrollable dividers are present @@ -299,7 +307,6 @@ } } - // stylelint-disable-next-line plugin/selector-bem-pattern .mdc-dialog__content .mdc-deprecated-list:first-child:last-child { @include feature-targeting.targets($feat-structure) { // Override default .mdc-deprecated-list padding for content consisting exclusively of a MDC List @@ -307,7 +314,6 @@ } } - // stylelint-disable-next-line plugin/selector-bem-pattern, selector-max-specificity .mdc-dialog--scrollable .mdc-dialog__content .mdc-deprecated-list:first-child:last-child { @@ -330,6 +336,9 @@ margin: 0; padding: variables.$actions-padding; border-top: 1px solid transparent; + @include dom.forced-colors-mode($exclude-ie11: true) { + border-top-color: CanvasText; + } } .mdc-dialog--stacked & { @@ -342,22 +351,22 @@ .mdc-dialog__button { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-box(margin, left, 8px); + @include rtl.reflexive-box(margin, left, 8px); } &:first-child { @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-box(margin, left, 0); + @include rtl.reflexive-box(margin, left, 0); } } @include feature-targeting.targets($feat-structure) { max-width: 100%; // Prevent long text from overflowing parent element in IE 11 - /* @noflip */ + @include rtl.ignore-next-line(); text-align: right; - @include rtl-mixins.rtl { - /* @noflip */ + @include rtl.rtl { + @include rtl.ignore-next-line(); text-align: left; } } @@ -386,7 +395,8 @@ .mdc-dialog__container { @include feature-targeting.targets($feat-animation) { - transition: opacity 75ms linear, + transition: + opacity 75ms linear, animation-functions.enter(transform, 150ms); } } @@ -401,6 +411,12 @@ } .mdc-dialog__container { + @include feature-targeting.targets($feat-structure) { + transform: none; + } + } + + .mdc-dialog__surface { @include feature-targeting.targets($feat-structure) { // Dialog container scales up while opening, but should remain scaled up while closing transform: none; @@ -408,6 +424,16 @@ } } + // Override the above transitions when chaining dialogs. + .mdc-dialog--chaining .mdc-dialog__scrim { + @include feature-targeting.targets($feat-animation) { + transition: none; + } + @include feature-targeting.targets($feat-structure) { + opacity: 1; + } + } + .mdc-dialog--open { .mdc-dialog__scrim { @include feature-targeting.targets($feat-structure) { @@ -417,16 +443,20 @@ .mdc-dialog__container { @include feature-targeting.targets($feat-structure) { - transform: none; opacity: 1; } } + .mdc-dialog__surface { + @include feature-targeting.targets($feat-structure) { + transform: none; + } + } + &.mdc-dialog__surface-scrim--shown { .mdc-dialog__surface-scrim { @include feature-targeting.targets($feat-structure) { opacity: 1; - z-index: 1; } } } @@ -455,6 +485,7 @@ position: absolute; width: 100%; height: 100%; + z-index: 1; } .mdc-dialog__surface-scrim--shown &, @@ -474,6 +505,79 @@ overflow: hidden; } } + + .mdc-dialog--no-content-padding { + .mdc-dialog__content { + @include feature-targeting.targets($feat-structure) { + padding: 0; + } + } + } + + .mdc-dialog--sheet { + .mdc-dialog__container .mdc-dialog__close-tooltip-wrapper { + @include feature-targeting.targets($feat-structure) { + right: variables.$sheet-close-icon-right; + top: variables.$sheet-close-icon-top; + position: absolute; + // Customers can create their stacking context in dialog content using + // any way of createing stacking context. For example with + // position: relative; + // z-index: 0; + z-index: 1; + } + } + } + + .mdc-dialog__scrim--removed { + @include feature-targeting.targets($feat-structure) { + pointer-events: none; + } + + .mdc-dialog__scrim, + .mdc-dialog__surface-scrim { + @include feature-targeting.targets($feat-structure) { + display: none; + } + } + } +} + +@mixin core-styles($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + .mdc-dialog { + @include container-fill-color(surface, $query: $query); + @include scrim-color(variables.$scrim-color, $query: $query); + @include title-ink-color(variables.$title-ink-color, $query: $query); + @include content-ink-color(variables.$content-ink-color, $query: $query); + @include scroll-divider-color( + variables.$scroll-divider-color, + $query: $query + ); + @include shape-radius(variables.$shape-radius, $query: $query); + } + + .mdc-dialog__surface { + @include elevation-mixins.elevation(24, $query: $query); + } + + .mdc-dialog__title { + @include typography.typography(headline6, $query: $query); + } + + .mdc-dialog__content { + @include typography.typography(body1, $query: $query); + } + + // For go/soy-checks/rewrite-css + .mdc-dialog__title-icon { + @include feature-targeting.targets($feat-structure) { + /** Hook for theming API. */ + } + } + + @include static-styles($query: $query); } @mixin container-fill-color($color, $query: feature-targeting.all()) { @@ -481,7 +585,7 @@ .mdc-dialog__surface { @include feature-targeting.targets($feat-color) { - @include theme.prop(background-color, $color); + @include theme.property(background-color, $color); } } } @@ -534,7 +638,24 @@ } .mdc-dialog__close { - @include iconbutton-mixins.ink_color($color: $color, $query: $query); + @include icon-button-theme.ink_color($color: $color, $query: $query); + } +} + +/// Defines the `display` property of the content element using a given query. +/// This is useful if the content of the dialog needs to use a custom layout, +/// for example one that consists of a fixed header and a fixed footer, with a +/// scrollable list area in-between, which is sized to fill available space +/// (see b/231813016). +/// @param {string} $display - The `display` type to set. +/// @param {string} $query Query. +@mixin content-display($display, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + .mdc-dialog__content { + @include feature-targeting.targets($feat-structure) { + display: $display; + } } } @@ -605,26 +726,53 @@ } } -@mixin max-width($max-width, $margin, $query: feature-targeting.all()) { +@mixin min-height($min-height, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); - $max-size-calc-expr: calc(100vw - #{$margin * 2}); .mdc-dialog__surface { @include feature-targeting.targets($feat-structure) { - @if $max-width { - $max-width-breakpoint: $max-width + ($margin * 2); + min-height: $min-height; + } + } +} - // Fit snugly within the viewport at smaller screen sizes. - @media (max-width: $max-width-breakpoint) { - max-width: $max-size-calc-expr; - } +@mixin max-width-with-breakpoint( + $above-breakpoint-max-width, + $below-breakpoint-max-width, + $max-width-breakpoint, + $query: feature-targeting.all() +) { + $feat-structure: feature-targeting.create-target($query, structure); - // Once the screen gets big enough, apply a fixed maximum width. - @media (min-width: $max-width-breakpoint) { - max-width: $max-width; - } - } @else { - max-width: $max-size-calc-expr; + .mdc-dialog__surface { + @include feature-targeting.targets($feat-structure) { + @media (max-width: $max-width-breakpoint) { + max-width: $below-breakpoint-max-width; + } + @media (min-width: $max-width-breakpoint) { + max-width: $above-breakpoint-max-width; + } + } + } +} + +@mixin max-width($max-width, $margin, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + $max-width-breakpoint: $max-width + ($margin * 2); + // Fit snugly within the viewport at smaller screen sizes. + $below-breakpoint-max-width: calc(100vw - #{$margin * 2}); + + @if $max-width { + @include max-width-with-breakpoint( + $max-width, + $below-breakpoint-max-width, + $max-width-breakpoint, + $query + ); + } @else { + .mdc-dialog__surface { + @include feature-targeting.targets($feat-structure) { + max-width: $below-breakpoint-max-width; } } } @@ -661,9 +809,6 @@ // is fixed. .mdc-dialog__container { @include feature-targeting.targets($feat-structure) { - /* stylelint-disable */ - // Disable stylelint here, as nesting depth > 3 is required to - // work around IE 11. @if $max-height { $max-height-breakpoint: $max-height + ($margin * 2); @@ -672,7 +817,6 @@ height: auto; } } - /* stylelint-enable*/ } } } @@ -708,14 +852,110 @@ } } +/// Defines dialog position on the screen. +/// Dialog position can be customised across both vertical and horizontal axis. +/// If only one axis is specified dialog is centered across the second one. +/// Only one value can be specified for each axis at the time. For example +/// dialog can not have both $top and $bottom specified because it will conflict +/// with size related mixins. Use `min-width`, `max-width` and `max-height` +/// to control the dialog size. +/// @param {Map} $position-map - Dialog position specified as map with keys +/// {top, bottom, left, right} +@mixin position($position-map, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + $top: map.get($position-map, top); + $right: map.get($position-map, right); + $bottom: map.get($position-map, bottom); + $left: map.get($position-map, left); + + @if ($top != null and $bottom != null) { + @error "Top and Botton properties can not be used simultaneously. Use " + + "`min-width`, `max-width` and `max-height` to control dialog size."; + } + @if ($right != null and $left != null) { + @error "Right and Left properties can not be used simultaneously. Use " + + "`min-width`, `max-width` and `max-height` to control dialog size."; + } + + @include feature-targeting.targets($feat-structure) { + @if ($top != null) { + .mdc-dialog__container { + align-items: flex-start; + padding-top: $top; + } + } + @if ($bottom != null) { + .mdc-dialog__container { + align-items: flex-end; + padding-bottom: $bottom; + } + } + @if ($right != null) { + &.mdc-dialog { + justify-content: flex-end; + padding-right: $right; + } + } + @if ($left != null) { + &.mdc-dialog { + justify-content: flex-start; + padding-left: $left; + } + } + } +} + +/// Defines dialog base z-index. +/// This mixin can be used to specify dialog base z-index to make it compatible +/// with other frameworks used on the same page. +/// @param {Number} $z-index - Dialog z-index value. +@mixin z-index($z-index, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + @include feature-targeting.targets($feat-structure) { + &.mdc-dialog { + z-index: $z-index; + } + } +} + +/// This mixin can be used to hide the fullscreen dialog header when the dialog +/// is a standard modal and not yet fullscreen in X-small sizes. +@mixin fullscreen-dialog-hide-modal-header($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + // Non-fullscreen-dialog sizes + @media (min-width: 600px) { + &.mdc-dialog--fullscreen { + .mdc-dialog__header { + @include feature-targeting.targets($feat-structure) { + display: none; + } + } + + .mdc-dialog__content { + @include feature-targeting.targets($feat-structure) { + padding-top: 24px; + } + } + } + } +} + @mixin _fullscreen-dialog-size($query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); .mdc-dialog__surface { + @include feature-targeting.targets($feat-structure) { + // Reset the max-width so the default dialog sizing doesn't interfere with + // the full-screen dialog sizing. + max-width: none; + } + // Medium screens - @media (max-width: 960px) and (max-height: 1440px) { + @media (max-width: 960px) { @include feature-targeting.targets($feat-structure) { max-height: 560px; - max-width: 560px; + width: 560px; @include _modal-header( $close-icon-padding: variables.$close-icon-padding ); @@ -723,7 +963,7 @@ } // Small screens - @media (max-width: 720px) and (max-height: 1023px) { + @media (max-width: 720px) { @include feature-targeting.targets($feat-structure) { $max-small-height: 560px; $max-small-width: 560px; @@ -741,25 +981,10 @@ } } - // X-Small Screens (horizontal) - @media (max-width: 720px) and (max-height: 400px) { - @include feature-targeting.targets($feat-structure) { - // Use 100% instead of vw/vh so the url bar is taken into account on - // mobile. - height: 100%; - max-height: 100vh; - max-width: 100vw; - width: 100%; - @include _fullscreen-header( - $close-icon-padding: variables.$close-icon-padding, - $title-side-padding: variables.$title-side-padding - ); - } - @include shape-mixins.radius(0, $query: $query); - } - - // X-Small Screens (vertical) - @media (max-width: 600px) and (max-height: 960px) { + // X-Small Screens + @media (max-width: 720px) and (max-height: 400px), + (max-width: 600px), + (min-width: 720px) and (max-height: 400px) { @include feature-targeting.targets($feat-structure) { // Use 100% instead of vw/vh so the url bar is taken into account on // mobile. @@ -776,10 +1001,10 @@ } // Large to X-Large screens - @media (min-width: 960px) and (min-height: 1440px) { + @media (min-width: 960px) { @include feature-targeting.targets($feat-structure) { $min-horizontal-margin: 200px; - max-width: calc(100vw - #{$min-horizontal-margin * 2}); + width: calc(100vw - #{$min-horizontal-margin * 2}); @include _modal-header( $close-icon-padding: variables.$close-icon-padding ); @@ -804,10 +1029,10 @@ $max-width-breakpoint: $max-width + ($horizontal-margin * 2); @media (max-width: $max-width-breakpoint) { - max-width: $max-width-calc-expr; + width: $max-width-calc-expr; } @media (min-width: $max-width-breakpoint) { - max-width: $max-width; + width: $max-width; } $max-height-calc-expr: calc(100vh - #{$vertical-margin * 2}); @@ -826,7 +1051,8 @@ /// @param {Number} $title-side-padding - Space between the edge of the close /// icon button and edge of the title. @mixin _fullscreen-header($close-icon-padding, $title-side-padding) { - .mdc-dialog__close { + .mdc-dialog__close-tooltip-wrapper { + position: relative; order: -1; @include theme.property(left, -#{$close-icon-padding}); } @@ -838,7 +1064,7 @@ .mdc-dialog__title { @include theme.property( margin-left, - calc(title - 2 * close), + 'calc(title - 2 * close)', $replace: (title: $title-side-padding, close: $close-icon-padding) ); } @@ -847,7 +1073,8 @@ /// Defines styles for the header bar when a dialog is modal. /// @param {Number} $close-icon-padding - Padding on close icon button. @mixin _modal-header($close-icon-padding) { - .mdc-dialog__close { + .mdc-dialog__close-tooltip-wrapper { + position: relative; @include theme.property(right, -#{$close-icon-padding}); } } diff --git a/packages/mdc-dialog/_variables.scss b/packages/mdc-dialog/_variables.scss index 0603dff9ad4..229165f6084 100644 --- a/packages/mdc-dialog/_variables.scss +++ b/packages/mdc-dialog/_variables.scss @@ -42,4 +42,7 @@ $close-icon-padding: 12px !default; $title-side-padding: 16px !default; $fullscreen-header-side-padding: 16px !default; +$sheet-close-icon-right: 12px !default; +$sheet-close-icon-top: 9px !default; + $z-index: 7 !default; diff --git a/packages/mdc-dialog/adapter.ts b/packages/mdc-dialog/adapter.ts index 385d8cc1994..822bed32c68 100644 --- a/packages/mdc-dialog/adapter.ts +++ b/packages/mdc-dialog/adapter.ts @@ -40,7 +40,7 @@ export interface MDCDialogAdapter { isContentScrollable(): boolean; areButtonsStacked(): boolean; - getActionFromEvent(evt: Event): string|null; + getActionFromEvent(event: Event): string|null; trapFocus(focusElement: HTMLElement|null): void; releaseFocus(): void; @@ -59,13 +59,13 @@ export interface MDCDialogAdapter { * with the 'mdc-dialog__content' class). */ registerContentEventHandler( - evtType: K, handler: SpecificEventListener): void; + eventType: K, handler: SpecificEventListener): void; /** * Deregisters an event listener on the dialog's content element. */ deregisterContentEventHandler( - evtType: K, handler: SpecificEventListener): void; + eventType: K, handler: SpecificEventListener): void; /** * @return true if the content has been scrolled (that is, for @@ -87,11 +87,11 @@ export interface MDCDialogAdapter { * Registers an event listener to the window. */ registerWindowEventHandler( - evtType: K, handler: SpecificWindowEventListener): void; + eventType: K, handler: SpecificWindowEventListener): void; /** * Deregisters an event listener to the window. */ deregisterWindowEventHandler( - evtType: K, handler: SpecificWindowEventListener): void; + eventType: K, handler: SpecificWindowEventListener): void; } diff --git a/packages/mdc-dialog/component.ts b/packages/mdc-dialog/component.ts index d576df8c7bc..36a54ef0a6e 100644 --- a/packages/mdc-dialog/component.ts +++ b/packages/mdc-dialog/component.ts @@ -26,6 +26,7 @@ import {SpecificEventListener} from '@material/base/types'; import {FocusTrap} from '@material/dom/focus-trap'; import {closest, matches} from '@material/dom/ponyfill'; import {MDCRipple} from '@material/ripple/component'; + import {MDCDialogAdapter} from './adapter'; import {MDCDialogFoundation} from './foundation'; import {MDCDialogCloseEventDetail} from './types'; @@ -34,6 +35,7 @@ import {MDCDialogFocusTrapFactory} from './util'; const {strings} = MDCDialogFoundation; +/** MDC Dialog */ export class MDCDialog extends MDCComponent { get isOpen() { return this.foundation.isOpen(); @@ -63,14 +65,14 @@ export class MDCDialog extends MDCComponent { this.foundation.setAutoStackButtons(autoStack); } - static attachTo(root: Element) { + static override attachTo(root: HTMLElement) { return new MDCDialog(root); } - private buttonRipples!: MDCRipple[]; // assigned in initialize() - private buttons!: HTMLButtonElement[]; // assigned in initialize() - private container!: HTMLElement; // assigned in initialize() - private content!: HTMLElement|null; // assigned in initialize() + private buttonRipples!: MDCRipple[]; // assigned in initialize() + private buttons!: HTMLButtonElement[]; // assigned in initialize() + private container!: HTMLElement; // assigned in initialize() + private content!: HTMLElement|null; // assigned in initialize() private defaultButton!: HTMLButtonElement|null; // assigned in initialize() private focusTrap!: FocusTrap; // assigned in initialSyncWithDOM() @@ -86,18 +88,20 @@ export class MDCDialog extends MDCComponent { private handleOpening!: EventListener; // assigned in initialSyncWithDOM() private handleClosing!: () => void; // assigned in initialSyncWithDOM() - initialize( - focusTrapFactory: MDCDialogFocusTrapFactory = (el, focusOptions) => new FocusTrap(el, focusOptions), + override initialize( + focusTrapFactory: MDCDialogFocusTrapFactory = (el, focusOptions) => + new FocusTrap(el, focusOptions), ) { const container = this.root.querySelector(strings.CONTAINER_SELECTOR); if (!container) { - throw new Error(`Dialog component requires a ${strings.CONTAINER_SELECTOR} container element`); + throw new Error(`Dialog component requires a ${ + strings.CONTAINER_SELECTOR} container element`); } this.container = container; this.content = this.root.querySelector(strings.CONTENT_SELECTOR); - this.buttons = [].slice.call( + this.buttons = Array.from( this.root.querySelectorAll(strings.BUTTON_SELECTOR)); this.defaultButton = this.root.querySelector( `[${strings.BUTTON_DEFAULT_ATTRIBUTE}]`); @@ -109,7 +113,7 @@ export class MDCDialog extends MDCComponent { } } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.focusTrap = util.createFocusTrapInstance( this.container, this.focusTrapFactory, this.getInitialFocusEl() || undefined); @@ -133,7 +137,7 @@ export class MDCDialog extends MDCComponent { this.listen(strings.CLOSING_EVENT, this.handleClosing); } - destroy() { + override destroy() { this.unlisten('click', this.handleClick); this.unlisten('keydown', this.handleKeydown); this.unlisten(strings.OPENING_EVENT, this.handleOpening); @@ -158,12 +162,17 @@ export class MDCDialog extends MDCComponent { this.foundation.close(action); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCDialogAdapter = { - addBodyClass: (className) => document.body.classList.add(className), - addClass: (className) => this.root.classList.add(className), + addBodyClass: (className) => { + document.body.classList.add(className); + }, + addClass: (className) => { + this.root.classList.add(className); + }, areButtonsStacked: () => util.areTopsMisaligned(this.buttons), clickDefaultButton: () => { if (this.defaultButton && !this.defaultButton.disabled) { @@ -172,27 +181,40 @@ export class MDCDialog extends MDCComponent { }, eventTargetMatches: (target, selector) => target ? matches(target as Element, selector) : false, - getActionFromEvent: (evt: Event) => { - if (!evt.target) { + getActionFromEvent: (event: Event) => { + if (!event.target) { return ''; } - const element = closest(evt.target as Element, `[${strings.ACTION_ATTRIBUTE}]`); + const element = + closest(event.target as Element, `[${strings.ACTION_ATTRIBUTE}]`); return element && element.getAttribute(strings.ACTION_ATTRIBUTE); }, getInitialFocusEl: () => this.getInitialFocusEl(), hasClass: (className) => this.root.classList.contains(className), isContentScrollable: () => util.isScrollable(this.content), - notifyClosed: (action) => this.emit( - strings.CLOSED_EVENT, action ? {action} : {}), - notifyClosing: (action) => this.emit( - strings.CLOSING_EVENT, action ? {action} : {}), - notifyOpened: () => this.emit(strings.OPENED_EVENT, {}), - notifyOpening: () => this.emit(strings.OPENING_EVENT, {}), + notifyClosed: (action) => { + this.emit( + strings.CLOSED_EVENT, action ? {action} : {}); + }, + notifyClosing: (action) => { + this.emit( + strings.CLOSING_EVENT, action ? {action} : {}); + }, + notifyOpened: () => { + this.emit(strings.OPENED_EVENT, {}); + }, + notifyOpening: () => { + this.emit(strings.OPENING_EVENT, {}); + }, releaseFocus: () => { this.focusTrap.releaseFocus(); }, - removeBodyClass: (className) => document.body.classList.remove(className), - removeClass: (className) => this.root.classList.remove(className), + removeBodyClass: (className) => { + document.body.classList.remove(className); + }, + removeClass: (className) => { + this.root.classList.remove(className); + }, reverseButtons: () => { this.buttons.reverse(); this.buttons.forEach((button) => { @@ -202,14 +224,14 @@ export class MDCDialog extends MDCComponent { trapFocus: () => { this.focusTrap.trapFocus(); }, - registerContentEventHandler: (evt, handler) => { + registerContentEventHandler: (event, handler) => { if (this.content instanceof HTMLElement) { - this.content.addEventListener(evt, handler); + this.content.addEventListener(event, handler); } }, - deregisterContentEventHandler: (evt, handler) => { + deregisterContentEventHandler: (event, handler) => { if (this.content instanceof HTMLElement) { - this.content.removeEventListener(evt, handler); + this.content.removeEventListener(event, handler); } }, isScrollableContentAtTop: () => { @@ -218,17 +240,18 @@ export class MDCDialog extends MDCComponent { isScrollableContentAtBottom: () => { return util.isScrollAtBottom(this.content); }, - registerWindowEventHandler: (evt, handler) => { - window.addEventListener(evt, handler); + registerWindowEventHandler: (event, handler) => { + window.addEventListener(event, handler); }, - deregisterWindowEventHandler: (evt, handler) => { - window.removeEventListener(evt, handler); + deregisterWindowEventHandler: (event, handler) => { + window.removeEventListener(event, handler); }, }; return new MDCDialogFoundation(adapter); } private getInitialFocusEl(): HTMLElement|null { - return this.root.querySelector(`[${strings.INITIAL_FOCUS_ATTRIBUTE}]`); + return this.root.querySelector( + `[${strings.INITIAL_FOCUS_ATTRIBUTE}]`); } } diff --git a/packages/mdc-dialog/foundation.ts b/packages/mdc-dialog/foundation.ts index 80e993e32a3..4ca57058f10 100644 --- a/packages/mdc-dialog/foundation.ts +++ b/packages/mdc-dialog/foundation.ts @@ -34,20 +34,21 @@ enum AnimationKeys { POLL_LAYOUT_CHANGE = 'poll_layout_change' } +/** MDC Dialog Foundation */ export class MDCDialogFoundation extends MDCFoundation { - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get strings() { + static override get strings() { return strings; } - static get numbers() { + static override get numbers() { return numbers; } - static get defaultAdapter(): MDCDialogAdapter { + static override get defaultAdapter(): MDCDialogAdapter { return { addBodyClass: () => undefined, addClass: () => undefined, @@ -109,14 +110,14 @@ export class MDCDialogFoundation extends MDCFoundation { }; } - init() { + override init() { if (this.adapter.hasClass(cssClasses.STACKED)) { this.setAutoStackButtons(false); } this.isFullscreen = this.adapter.hasClass(cssClasses.FULLSCREEN); } - destroy() { + override destroy() { if (this.animationTimer) { clearTimeout(this.animationTimer); this.handleAnimationTimerEnd(); @@ -157,7 +158,9 @@ export class MDCDialogFoundation extends MDCFoundation { // animation this.runNextAnimationFrame(() => { this.adapter.addClass(cssClasses.OPEN); - this.adapter.addBodyClass(cssClasses.SCROLL_LOCK); + if (!dialogOptions || !dialogOptions.isScrimless) { + this.adapter.addBodyClass(cssClasses.SCROLL_LOCK); + } this.layout(); @@ -273,14 +276,14 @@ export class MDCDialogFoundation extends MDCFoundation { } /** Handles click on the dialog root element. */ - handleClick(evt: MouseEvent) { + handleClick(event: MouseEvent) { const isScrim = - this.adapter.eventTargetMatches(evt.target, strings.SCRIM_SELECTOR); + this.adapter.eventTargetMatches(event.target, strings.SCRIM_SELECTOR); // Check for scrim click first since it doesn't require querying ancestors. if (isScrim && this.scrimClickAction !== '') { this.close(this.scrimClickAction); } else { - const action = this.adapter.getActionFromEvent(evt); + const action = this.adapter.getActionFromEvent(event); if (action) { this.close(action); } @@ -288,12 +291,12 @@ export class MDCDialogFoundation extends MDCFoundation { } /** Handles keydown on the dialog root element. */ - handleKeydown(evt: KeyboardEvent) { - const isEnter = evt.key === 'Enter' || evt.keyCode === 13; + handleKeydown(event: KeyboardEvent) { + const isEnter = event.key === 'Enter' || event.keyCode === 13; if (!isEnter) { return; } - const action = this.adapter.getActionFromEvent(evt); + const action = this.adapter.getActionFromEvent(event); if (action) { // Action button callback is handled in `handleClick`, // since space/enter keydowns on buttons trigger click events. @@ -313,7 +316,7 @@ export class MDCDialogFoundation extends MDCFoundation { // // // - const target = evt.composedPath ? evt.composedPath()[0] : evt.target; + const target = event.composedPath ? event.composedPath()[0] : event.target; const isDefault = this.suppressDefaultPressSelector ? !this.adapter.eventTargetMatches( target, this.suppressDefaultPressSelector) : @@ -324,8 +327,8 @@ export class MDCDialogFoundation extends MDCFoundation { } /** Handles keydown on the document. */ - handleDocumentKeydown(evt: KeyboardEvent) { - const isEscape = evt.key === 'Escape' || evt.keyCode === 27; + handleDocumentKeydown(event: KeyboardEvent) { + const isEscape = event.key === 'Escape' || event.keyCode === 27; if (isEscape && this.escapeKeyAction !== '') { this.close(this.escapeKeyAction); } diff --git a/packages/mdc-dialog/mdc-dialog.import.scss b/packages/mdc-dialog/mdc-dialog.import.scss index 7d5ad7143ef..9c1a8e4b9ad 100644 --- a/packages/mdc-dialog/mdc-dialog.import.scss +++ b/packages/mdc-dialog/mdc-dialog.import.scss @@ -3,8 +3,6 @@ @forward "@material/theme/variables" as mdc-theme-*; @forward "@material/button/variables" as mdc-button-*; @forward "@material/base/mixins" as mdc-base-*; -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; @forward "@material/elevation/variables" as mdc-elevation-*; @forward "@material/rtl/variables" as mdc-rtl-*; @forward "@material/shape/variables" as mdc-shape-*; diff --git a/packages/mdc-dialog/package.json b/packages/mdc-dialog/package.json index a4fb39de9e2..03b973c6084 100644 --- a/packages/mdc-dialog/package.json +++ b/packages/mdc-dialog/package.json @@ -1,6 +1,6 @@ { "name": "@material/dialog", - "version": "12.0.0", + "version": "14.0.0", "description": "The Material Components Web dialog component", "license": "MIT", "keywords": [ @@ -18,19 +18,20 @@ "directory": "packages/mdc-dialog" }, "dependencies": { - "@material/animation": "^12.0.0", - "@material/base": "^12.0.0", - "@material/button": "^12.0.0", - "@material/dom": "^12.0.0", - "@material/elevation": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/icon-button": "^12.0.0", - "@material/ripple": "^12.0.0", - "@material/rtl": "^12.0.0", - "@material/shape": "^12.0.0", - "@material/theme": "^12.0.0", - "@material/touch-target": "^12.0.0", - "@material/typography": "^12.0.0", + "@material/animation": "^14.0.0", + "@material/base": "^14.0.0", + "@material/button": "^14.0.0", + "@material/dom": "^14.0.0", + "@material/elevation": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/icon-button": "^14.0.0", + "@material/ripple": "^14.0.0", + "@material/rtl": "^14.0.0", + "@material/shape": "^14.0.0", + "@material/theme": "^14.0.0", + "@material/tokens": "^14.0.0", + "@material/touch-target": "^14.0.0", + "@material/typography": "^14.0.0", "tslib": "^2.1.0" }, "publishConfig": { diff --git a/packages/mdc-dialog/test/component.test.ts b/packages/mdc-dialog/test/component.test.ts index 6a2572bfe06..530ec528942 100644 --- a/packages/mdc-dialog/test/component.test.ts +++ b/packages/mdc-dialog/test/component.test.ts @@ -22,15 +22,17 @@ */ import {supportsCssVariables} from '../../mdc-ripple/util'; +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; import {createMockFoundation} from '../../../testing/helpers/foundation'; import {setUpMdcTestEnvironment} from '../../../testing/helpers/setup'; -import {strings, numbers} from '../constants'; +import {numbers, strings} from '../constants'; import {MDCDialog, MDCDialogFoundation, util} from '../index'; -function getFixture() { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` +const DEFAULT_CONTENT = html`Let Google help apps determine location.`; + +function getFixture(content: ReturnType = DEFAULT_CONTENT) { + return createFixture(html`
- Let Google help apps determine location. + ${content}
-
@@ -61,24 +64,21 @@ function getFixture() {
-
`; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; + `); } function setupTest(fixture = getFixture()) { - const root = fixture.querySelector('.mdc-dialog') as HTMLElement; + const root = fixture.querySelector('.mdc-dialog')!; const component = new MDCDialog(root); - const title = fixture.querySelector('.mdc-dialog__title') as HTMLElement; - const content = fixture.querySelector('.mdc-dialog__content') as HTMLElement; - const actions = fixture.querySelector('.mdc-dialog__actions') as HTMLElement; + const title = fixture.querySelector('.mdc-dialog__title')!; + const content = fixture.querySelector('.mdc-dialog__content')!; + const actions = fixture.querySelector('.mdc-dialog__actions')!; const yesButton = - fixture.querySelector('[data-mdc-dialog-action="yes"]') as HTMLElement; + fixture.querySelector('[data-mdc-dialog-action="yes"]')!; const noButton = - fixture.querySelector('[data-mdc-dialog-action="no"]') as HTMLElement; + fixture.querySelector('[data-mdc-dialog-action="no"]')!; const cancelButton = - fixture.querySelector('[data-mdc-dialog-action="cancel"]') as HTMLElement; + fixture.querySelector('[data-mdc-dialog-action="cancel"]')!; return { root, component, @@ -108,18 +108,18 @@ describe('MDCDialog', () => { it('attachTo returns a component instance', () => { expect(MDCDialog.attachTo( - getFixture().querySelector('.mdc-dialog') as HTMLElement)) + getFixture().querySelector('.mdc-dialog')!)) .toEqual(jasmine.any(MDCDialog)); }); it('attachTo throws an error when container element is missing', () => { const fixture = getFixture(); const container = - fixture.querySelector('.mdc-dialog__container') as HTMLElement; + fixture.querySelector('.mdc-dialog__container')!; container.parentElement!.removeChild(container); expect( () => MDCDialog.attachTo( - fixture.querySelector('.mdc-dialog') as HTMLElement)) + fixture.querySelector('.mdc-dialog')!)) .toThrow(); }); @@ -174,7 +174,7 @@ describe('MDCDialog', () => { expect(mockFoundation.handleDocumentKeydown).toHaveBeenCalledTimes(1); }); - it('#initialize attaches ripple elements to all footer buttons', function() { + it('#initialize attaches ripple elements to all footer buttons', () => { if (!supportsCssVariables(window, true)) { return; } @@ -182,12 +182,12 @@ describe('MDCDialog', () => { const {yesButton, noButton, cancelButton} = setupTest(); jasmine.clock().tick(1); - expect(yesButton.classList.contains('mdc-ripple-upgraded')).toBe(true); - expect(noButton.classList.contains('mdc-ripple-upgraded')).toBe(true); - expect(cancelButton.classList.contains('mdc-ripple-upgraded')).toBe(true); + expect(yesButton).toHaveClass('mdc-ripple-upgraded'); + expect(noButton).toHaveClass('mdc-ripple-upgraded'); + expect(cancelButton).toHaveClass('mdc-ripple-upgraded'); }); - it('#destroy cleans up all ripples on footer buttons', function() { + it('#destroy cleans up all ripples on footer buttons', () => { if (!supportsCssVariables(window, true)) { return; } @@ -198,9 +198,9 @@ describe('MDCDialog', () => { component.destroy(); jasmine.clock().tick(1); - expect(yesButton.classList.contains('mdc-ripple-upgraded')).toBe(false); - expect(noButton.classList.contains('mdc-ripple-upgraded')).toBe(false); - expect(cancelButton.classList.contains('mdc-ripple-upgraded')).toBe(false); + expect(yesButton).not.toHaveClass('mdc-ripple-upgraded'); + expect(noButton).not.toHaveClass('mdc-ripple-upgraded'); + expect(cancelButton).not.toHaveClass('mdc-ripple-upgraded'); }); it('#open forwards to MDCDialogFoundation#open', () => { @@ -278,12 +278,13 @@ describe('MDCDialog', () => { }); it('autoStackButtons adds scrollable class', () => { - const fixture = getFixture(); - const root = fixture.querySelector('.mdc-dialog') as HTMLElement; - const content = root.querySelector('.mdc-dialog__content') as HTMLElement; - // Simulate a scrollable content area - content.innerHTML = new Array(100).join(`

${content.textContent}

`); + const contentChildren = + new Array(100).join(html`

${DEFAULT_CONTENT}

`); + const fixture = getFixture(contentChildren); + const root = fixture.querySelector('.mdc-dialog')!; + const content = root.querySelector('.mdc-dialog__content')!; + content.style.height = '50px'; content.style.overflow = 'auto'; @@ -296,7 +297,7 @@ describe('MDCDialog', () => { jasmine.clock().tick(1); jasmine.clock().tick(1); - expect(root.classList.contains('mdc-dialog--scrollable')).toBe(true); + expect(root).toHaveClass('mdc-dialog--scrollable'); } finally { document.body.removeChild(fixture); } @@ -305,22 +306,21 @@ describe('MDCDialog', () => { it('adapter#addClass adds a class to the root element', () => { const {root, component} = setupTest(); (component.getDefaultFoundation() as any).adapter.addClass('foo'); - expect(root.classList.contains('foo')).toBe(true); + expect(root).toHaveClass('foo'); }); it('adapter#removeClass removes a class from the root element', () => { const {root, component} = setupTest(); root.classList.add('foo'); (component.getDefaultFoundation() as any).adapter.removeClass('foo'); - expect(root.classList.contains('foo')).toBe(false); + expect(root).not.toHaveClass('foo'); }); it('adapter#hasClass returns whether a class exists on the root element', () => { const {root, component} = setupTest(); root.classList.add('foo'); - expect( - (component.getDefaultFoundation() as any).adapter.hasClass('foo')) + expect((component.getDefaultFoundation() as any).adapter.hasClass('foo')) .toBe(true); expect((component.getDefaultFoundation() as any) .adapter.hasClass('does-not-exist')) @@ -331,19 +331,18 @@ describe('MDCDialog', () => { const {component} = setupTest(); (component.getDefaultFoundation() as any) .adapter.addBodyClass('mdc-dialog--scroll-lock'); - expect((document.querySelector('body') as HTMLElement) - .classList.contains('mdc-dialog--scroll-lock')) - .toBe(true); + expect((document.querySelector('body')!)) + .toHaveClass('mdc-dialog--scroll-lock'); }); it('adapter#removeBodyClass removes a class from the body', () => { const {component} = setupTest(); - const body = document.querySelector('body') as HTMLElement; + const body = document.querySelector('body')!; body.classList.add('mdc-dialog--scroll-lock'); (component.getDefaultFoundation() as any) .adapter.removeBodyClass('mdc-dialog--scroll-lock'); - expect(body.classList.contains('mdc-dialog--scroll-lock')).toBe(false); + expect(body).not.toHaveClass('mdc-dialog--scroll-lock'); }); it('adapter#eventTargetMatches returns whether or not the target matches the selector', @@ -471,8 +470,8 @@ describe('MDCDialog', () => { it('adapter#isContentScrollable returns result of util.isScrollable', () => { const {component, content} = setupTest(); - expect((component.getDefaultFoundation() as any) - .adapter.isContentScrollable()) + expect( + (component.getDefaultFoundation() as any).adapter.isContentScrollable()) .toBe(util.isScrollable(content)); }); @@ -522,8 +521,8 @@ describe('MDCDialog', () => { strings.BUTTON_DEFAULT_ATTRIBUTE}`, () => { const fixture = getFixture(); - const yesButton = fixture.querySelector( - '[data-mdc-dialog-action="yes"]') as HTMLElement; + const yesButton = fixture.querySelector( + '[data-mdc-dialog-action="yes"]')!; yesButton.setAttribute(strings.BUTTON_DEFAULT_ATTRIBUTE, 'true'); const {component} = setupTest(fixture); @@ -553,9 +552,9 @@ describe('MDCDialog', () => { const {component, actions, yesButton, noButton, cancelButton} = setupTest(); (component.getDefaultFoundation() as any).adapter.reverseButtons(); - expect([ - yesButton, noButton, cancelButton - ]).toEqual([].slice.call(actions.children)); + expect(actions.children[0]).toEqual(yesButton); + expect(actions.children[1]).toEqual(noButton); + expect(actions.children[2]).toEqual(cancelButton); }); it('#layout proxies to foundation', () => { @@ -566,31 +565,38 @@ describe('MDCDialog', () => { expect((component as any).foundation.layout).toHaveBeenCalled(); }); - it(`Button with ${strings.INITIAL_FOCUS_ATTRIBUTE} will be focused when the dialog is opened, with multiple initial focus buttons in DOM`, () => { - const {root: root1, component: component1, yesButton: yesButton1} = setupTest(); - const {root: root2, component: component2, yesButton: yesButton2} = setupTest(); + it(`Button with ${ + strings + .INITIAL_FOCUS_ATTRIBUTE} will be focused when the dialog is opened, with multiple initial focus buttons in DOM`, + () => { + const {root: root1, component: component1, yesButton: yesButton1} = + setupTest(); + const {root: root2, component: component2, yesButton: yesButton2} = + setupTest(); - expect(yesButton1.hasAttribute(strings.INITIAL_FOCUS_ATTRIBUTE)).toBe(true); - expect(yesButton2.hasAttribute(strings.INITIAL_FOCUS_ATTRIBUTE)).toBe(true); + expect(yesButton1.hasAttribute(strings.INITIAL_FOCUS_ATTRIBUTE)) + .toBe(true); + expect(yesButton2.hasAttribute(strings.INITIAL_FOCUS_ATTRIBUTE)) + .toBe(true); - try { - document.body.appendChild(root1) - document.body.appendChild(root2) - - component1.open() - jasmine.clock().tick(numbers.DIALOG_ANIMATION_OPEN_TIME_MS + 10); - expect(document.activeElement).toEqual(yesButton1); - component1.close() - jasmine.clock().tick(numbers.DIALOG_ANIMATION_CLOSE_TIME_MS); - - component2.open() - jasmine.clock().tick(numbers.DIALOG_ANIMATION_OPEN_TIME_MS + 10); - expect(document.activeElement).toEqual(yesButton2); - component2.close() - jasmine.clock().tick(numbers.DIALOG_ANIMATION_CLOSE_TIME_MS); - } finally { - document.body.removeChild(root1) - document.body.removeChild(root2) - } - }); + try { + document.body.appendChild(root1); + document.body.appendChild(root2); + + component1.open(); + jasmine.clock().tick(numbers.DIALOG_ANIMATION_OPEN_TIME_MS + 10); + expect(document.activeElement).toEqual(yesButton1); + component1.close(); + jasmine.clock().tick(numbers.DIALOG_ANIMATION_CLOSE_TIME_MS); + + component2.open(); + jasmine.clock().tick(numbers.DIALOG_ANIMATION_OPEN_TIME_MS + 10); + expect(document.activeElement).toEqual(yesButton2); + component2.close(); + jasmine.clock().tick(numbers.DIALOG_ANIMATION_CLOSE_TIME_MS); + } finally { + document.body.removeChild(root1); + document.body.removeChild(root2); + } + }); }); diff --git a/packages/mdc-dialog/test/foundation.test.ts b/packages/mdc-dialog/test/foundation.test.ts index b01e87d8937..e01b5b44ba5 100644 --- a/packages/mdc-dialog/test/foundation.test.ts +++ b/packages/mdc-dialog/test/foundation.test.ts @@ -379,7 +379,7 @@ describe('MDCDialogFoundation', () => { .toHaveBeenCalledWith('scroll', jasmine.any(Function)); }); - it('#open hides the scrim if \"isAboveFullscreenDialog" is true', () => { + it('#open hides the scrim if "isAboveFullscreenDialog" is true', () => { const {foundation, mockAdapter} = setupTest(); foundation.open({isAboveFullscreenDialog: true}); @@ -571,14 +571,14 @@ describe('MDCDialogFoundation', () => { strings.SCRIM_SELECTOR} selector matches`, () => { const {foundation, mockAdapter} = setupTest(); - const evt = {type: 'click', target: {}} as MouseEvent; + const event = {type: 'click', target: {}} as MouseEvent; foundation.close = jasmine.createSpy('close'); mockAdapter.eventTargetMatches - .withArgs(evt.target, strings.SCRIM_SELECTOR) + .withArgs(event.target, strings.SCRIM_SELECTOR) .and.returnValue(true); foundation.open(); - foundation.handleClick(evt); + foundation.handleClick(event); expect(foundation.close) .toHaveBeenCalledWith(foundation.getScrimClickAction()); @@ -589,15 +589,15 @@ describe('MDCDialogFoundation', () => { empty string`, () => { const {foundation, mockAdapter} = setupTest(); - const evt = {type: 'click', target: {}} as MouseEvent; + const event = {type: 'click', target: {}} as MouseEvent; foundation.close = jasmine.createSpy('close'); mockAdapter.eventTargetMatches - .withArgs(evt.target, strings.SCRIM_SELECTOR) + .withArgs(event.target, strings.SCRIM_SELECTOR) .and.returnValue(true); foundation.setScrimClickAction(''); foundation.open(); - foundation.handleClick(evt); + foundation.handleClick(event); expect(foundation.close).not.toHaveBeenCalledWith(jasmine.any(String)); }); diff --git a/packages/mdc-dialog/test/mdc-dialog.scss.test.ts b/packages/mdc-dialog/test/mdc-dialog.scss.test.ts index 782cca6e060..69645c1e7b2 100644 --- a/packages/mdc-dialog/test/mdc-dialog.scss.test.ts +++ b/packages/mdc-dialog/test/mdc-dialog.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-dialog.scss', () => { diff --git a/packages/mdc-dialog/test/util.test.ts b/packages/mdc-dialog/test/util.test.ts index 5f4cb0f5531..ac431d38330 100644 --- a/packages/mdc-dialog/test/util.test.ts +++ b/packages/mdc-dialog/test/util.test.ts @@ -22,6 +22,7 @@ */ import {FocusTrap} from '../../mdc-dom/focus-trap'; +import {createFixture, html} from '../../../testing/dom'; import * as util from '../util'; describe('MDCDialog - util', () => { @@ -30,7 +31,7 @@ describe('MDCDialog - util', () => { const surface = document.createElement('div'); const yesBtn = document.createElement('button'); const focusTrapFactory = jasmine.createSpy('focusTrapFactory'); - const properlyConfiguredFocusTrapInstance = {} as FocusTrap; + const properlyConfiguredFocusTrapInstance: FocusTrap = {} as FocusTrap; focusTrapFactory .withArgs(surface, { initialFocusEl: yesBtn, @@ -61,7 +62,7 @@ describe('MDCDialog - util', () => { it('isScrollable returns false when element content does not overflow its bounding box', () => { - const parent = getElement(` + const parent = createFixture(html`
`); @@ -78,7 +79,7 @@ describe('MDCDialog - util', () => { it('isScrollable returns true when element content overflows its bounding box', () => { - const parent = getElement(` + const parent = createFixture(html`
`); @@ -95,7 +96,7 @@ describe('MDCDialog - util', () => { it('isScrollAtTop returns true when scrollable content has not been scrolled', () => { - const parent = getElement(` + const parent = createFixture(html`
`); @@ -113,7 +114,7 @@ describe('MDCDialog - util', () => { it('isScrollAtTop returns false when scrollable content has been scrolled', () => { - const parent = getElement(` + const parent = createFixture(html`
`); @@ -132,7 +133,7 @@ describe('MDCDialog - util', () => { it('isScrollAtBottom returns false when scrollable content is not scrolled to the bottom', () => { - const parent = getElement(` + const parent = createFixture(html`
`); @@ -150,7 +151,7 @@ describe('MDCDialog - util', () => { it('isScrollAtBottom returns true when scrollable content has been scrolled to the bottom', () => { - const parent = getElement(` + const parent = createFixture(html`
`); @@ -173,7 +174,7 @@ describe('MDCDialog - util', () => { it('areTopsMisaligned returns false when array only contains one element', () => { - const parent = getElement(` + const parent = createFixture(html`
`); - const buttons = - [].slice.call(parent.querySelectorAll('button')) as HTMLElement[]; + const buttons = Array.from(parent.querySelectorAll('button')); // HTMLElement.offsetTop only returns the correct value when the element // is attached to the DOM. @@ -197,7 +197,7 @@ describe('MDCDialog - util', () => { it('areTopsMisaligned returns false when elements have same offsetTop', () => { - const parent = getElement(` + const parent = createFixture(html`
{ - const parent = getElement(` + const parent = createFixture(html`
; + private readonly liveRegions: Map>; static getInstance(): Announcer { if (!Announcer.instance) { @@ -58,37 +66,46 @@ class Announcer { this.liveRegions = new Map(); } - say(message: string, priority: AnnouncerPriority = AnnouncerPriority.POLITE) { - const liveRegion = this.getLiveRegion(priority); + say(message: string, options?: AnnouncerMessageOptions) { + const priority = options?.priority ?? AnnouncerPriority.POLITE; + const ownerDocument = options?.ownerDocument ?? document; + const liveRegion = this.getLiveRegion(priority, ownerDocument); // Reset the region to pick up the message, even if the message is the // exact same as before. liveRegion.textContent = ''; // Timeout is necessary for screen readers like NVDA and VoiceOver. setTimeout(() => { liveRegion.textContent = message; - document.addEventListener('click', clearLiveRegion); + ownerDocument.addEventListener('click', clearLiveRegion); }, 1); function clearLiveRegion() { liveRegion.textContent = ''; - document.removeEventListener('click', clearLiveRegion); + ownerDocument.removeEventListener('click', clearLiveRegion); } } - private getLiveRegion(priority: AnnouncerPriority): Element { - const existingLiveRegion = this.liveRegions.get(priority); - if (existingLiveRegion && - document.body.contains(existingLiveRegion as Node)) { + private getLiveRegion(priority: AnnouncerPriority, ownerDocument: Document): + Element { + let documentLiveRegions = this.liveRegions.get(ownerDocument); + if (!documentLiveRegions) { + documentLiveRegions = new Map(); + this.liveRegions.set(ownerDocument, documentLiveRegions); + } + + const existingLiveRegion = documentLiveRegions.get(priority); + if (existingLiveRegion && ownerDocument.body.contains(existingLiveRegion)) { return existingLiveRegion; } - const liveRegion = this.createLiveRegion(priority); - this.liveRegions.set(priority, liveRegion); + const liveRegion = this.createLiveRegion(priority, ownerDocument); + documentLiveRegions.set(priority, liveRegion); return liveRegion; } - private createLiveRegion(priority: AnnouncerPriority): Element { - const el = document.createElement('div'); + private createLiveRegion( + priority: AnnouncerPriority, ownerDocument: Document): HTMLDivElement { + const el = ownerDocument.createElement('div'); el.style.position = 'absolute'; el.style.top = '-9999px'; el.style.left = '-9999px'; @@ -97,7 +114,7 @@ class Announcer { el.setAttribute('aria-atomic', 'true'); el.setAttribute('aria-live', priority); el.setAttribute(DATA_MDC_DOM_ANNOUNCE, 'true'); - document.body.appendChild(el); + ownerDocument.body.appendChild(el); return el; } } diff --git a/packages/mdc-dom/events.ts b/packages/mdc-dom/events.ts index 7d16a4b518b..99d5e1ebaa4 100644 --- a/packages/mdc-dom/events.ts +++ b/packages/mdc-dom/events.ts @@ -25,8 +25,8 @@ * Determine whether the current browser supports passive event listeners, and * if so, use them. */ -export function applyPassive(globalObj: Window = window): - boolean | EventListenerOptions { +export function applyPassive(globalObj: Window = window): boolean| + EventListenerOptions { return supportsPassiveOption(globalObj) ? {passive: true} as AddEventListenerOptions : false; diff --git a/packages/mdc-dom/focus-trap.ts b/packages/mdc-dom/focus-trap.ts index 85945c0616b..5f7f5460d97 100644 --- a/packages/mdc-dom/focus-trap.ts +++ b/packages/mdc-dom/focus-trap.ts @@ -64,7 +64,9 @@ export class FocusTrap { * element. */ releaseFocus() { - [].slice.call(this.root.querySelectorAll(`.${FOCUS_SENTINEL_CLASS}`)) + Array + .from( + this.root.querySelectorAll(`.${FOCUS_SENTINEL_CLASS}`)) .forEach((sentinelEl: HTMLElement) => { sentinelEl.parentElement!.removeChild(sentinelEl); }); @@ -116,10 +118,8 @@ export class FocusTrap { } private getFocusableElements(root: HTMLElement): HTMLElement[] { - const focusableEls = - [].slice.call(root.querySelectorAll( - '[autofocus], [tabindex], a, input, textarea, select, button')) as - HTMLElement[]; + const focusableEls = Array.from(root.querySelectorAll( + '[autofocus], [tabindex], a, input, textarea, select, button')); return focusableEls.filter((el) => { const isDisabledOrHidden = el.getAttribute('aria-disabled') === 'true' || el.getAttribute('disabled') != null || diff --git a/packages/mdc-dom/keyboard.ts b/packages/mdc-dom/keyboard.ts index b80ab212e78..cdc86ae4b02 100644 --- a/packages/mdc-dom/keyboard.ts +++ b/packages/mdc-dom/keyboard.ts @@ -110,15 +110,15 @@ navigationKeys.add(KEY.ARROW_DOWN); /** * normalizeKey returns the normalized string for a navigational action. */ -export function normalizeKey(evt: KeyboardEvent): string { - const {key} = evt; +export function normalizeKey(event: KeyboardEvent): string { + const {key} = event; // If the event already has a normalized key, return it if (normalizedKeys.has(key)) { return key; } // tslint:disable-next-line:deprecation - const mappedKey = mappedKeyCodes.get(evt.keyCode); + const mappedKey = mappedKeyCodes.get(event.keyCode); if (mappedKey) { return mappedKey; } @@ -128,6 +128,6 @@ export function normalizeKey(evt: KeyboardEvent): string { /** * isNavigationEvent returns whether the event is a navigation event */ -export function isNavigationEvent(evt: KeyboardEvent): boolean { - return navigationKeys.has(normalizeKey(evt)); +export function isNavigationEvent(event: KeyboardEvent): boolean { + return navigationKeys.has(normalizeKey(event)); } diff --git a/packages/mdc-dom/package.json b/packages/mdc-dom/package.json index c12f526548e..3151c87a41a 100644 --- a/packages/mdc-dom/package.json +++ b/packages/mdc-dom/package.json @@ -1,7 +1,7 @@ { "name": "@material/dom", "description": "DOM manipulation utilities for Material Components for the web", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "main": "dist/mdc.dom.js", "module": "index.js", @@ -15,7 +15,8 @@ "access": "public" }, "dependencies": { - "@material/feature-targeting": "^12.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/rtl": "^14.0.0", "tslib": "^2.1.0" } } diff --git a/packages/mdc-dom/ponyfill.ts b/packages/mdc-dom/ponyfill.ts index ffb773e6eed..4007cc15898 100644 --- a/packages/mdc-dom/ponyfill.ts +++ b/packages/mdc-dom/ponyfill.ts @@ -22,16 +22,17 @@ */ /** - * @fileoverview A "ponyfill" is a polyfill that doesn't modify the global prototype chain. - * This makes ponyfills safer than traditional polyfills, especially for libraries like MDC. + * @fileoverview A "ponyfill" is a polyfill that doesn't modify the global + * prototype chain. This makes ponyfills safer than traditional polyfills, + * especially for libraries like MDC. */ -export function closest(element: Element, selector: string): Element | null { +export function closest(element: Element, selector: string): Element|null { if (element.closest) { return element.closest(selector); } - let el: Element | null = element; + let el: Element|null = element; while (el) { if (matches(el, selector)) { return el; @@ -41,10 +42,10 @@ export function closest(element: Element, selector: string): Element | null { return null; } +/** Element.matches with support for webkit and IE. */ export function matches(element: Element, selector: string): boolean { - const nativeMatches = element.matches - || element.webkitMatchesSelector - || (element as any).msMatchesSelector; + const nativeMatches = element.matches || element.webkitMatchesSelector || + (element as any).msMatchesSelector; return nativeMatches.call(element, selector); } diff --git a/packages/mdc-dom/test/announce.test.ts b/packages/mdc-dom/test/announce.test.ts index 4b76da1dc49..8aa52a7c46a 100644 --- a/packages/mdc-dom/test/announce.test.ts +++ b/packages/mdc-dom/test/announce.test.ts @@ -46,12 +46,23 @@ describe('announce', () => { }); it('creates an aria-live="assertive" region if specified', () => { - announce('Bar', AnnouncerPriority.ASSERTIVE); + announce('Bar', {priority: AnnouncerPriority.ASSERTIVE}); jasmine.clock().tick(1); const liveRegion = document.querySelector(LIVE_REGION_SELECTOR); expect(liveRegion!.textContent).toEqual('Bar'); }); + it('uses the provided ownerDocument for announcements', () => { + const ownerDocument = document.implementation.createHTMLDocument('Title'); + announce('custom ownerDocument', {ownerDocument}); + const globalDocumentLiveRegion = + document.querySelector(LIVE_REGION_SELECTOR); + expect(globalDocumentLiveRegion).toBeNull(); + const ownerDocumentLiveRegion = + ownerDocument.querySelector(LIVE_REGION_SELECTOR); + expect(ownerDocumentLiveRegion).toBeDefined(); + }); + it('sets live region content after a timeout', () => { announce('Baz'); const liveRegion = document.querySelector(LIVE_REGION_SELECTOR); @@ -60,20 +71,21 @@ describe('announce', () => { expect(liveRegion!.textContent).toEqual('Baz'); }); - it('reuses same polite live region on successive calls', () => { + it('reuses same live region on successive calls per document', () => { + const secondDocument = document.implementation.createHTMLDocument('Title'); announce('aaa'); + announce('aaa', {ownerDocument: secondDocument}); announce('bbb'); + announce('bbb', {ownerDocument: secondDocument}); announce('ccc'); - const liveRegions = document.querySelectorAll(LIVE_REGION_SELECTOR); - expect(liveRegions.length).toEqual(1); - }); + announce('ccc', {ownerDocument: secondDocument}); - it('reuses same assertive live region on successive calls', () => { - announce('aaa', AnnouncerPriority.ASSERTIVE); - announce('bbb', AnnouncerPriority.ASSERTIVE); - announce('ccc', AnnouncerPriority.ASSERTIVE); - const liveRegions = document.querySelectorAll(LIVE_REGION_SELECTOR); - expect(liveRegions.length).toEqual(1); + const globalDocumentLiveRegions = + document.querySelectorAll(LIVE_REGION_SELECTOR); + expect(globalDocumentLiveRegions.length).toEqual(1); + const secondDocumentLiveRegions = + secondDocument.querySelectorAll(LIVE_REGION_SELECTOR); + expect(secondDocumentLiveRegions.length).toEqual(1); }); it('sets the latest message during immediate successive', () => { diff --git a/packages/mdc-dom/test/focus-trap.test.ts b/packages/mdc-dom/test/focus-trap.test.ts index ee180042ad0..1a9138a018f 100644 --- a/packages/mdc-dom/test/focus-trap.test.ts +++ b/packages/mdc-dom/test/focus-trap.test.ts @@ -21,14 +21,14 @@ * THE SOFTWARE. */ +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; import {FocusTrap} from '../focus-trap'; const FOCUS_SENTINEL_CLASS = 'mdc-dom-focus-sentinel'; function getFixture() { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html`
@@ -57,30 +57,27 @@ function getFixture() {
5a
-
`; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; +
`); } function setUp() { const root = getFixture(); document.body.appendChild(root); - const button = root.querySelector('button') as HTMLElement; - const container1 = root.querySelector('#container1_innerDiv') as HTMLElement; - const container2 = root.querySelector('#container2_standard') as HTMLElement; + const button = root.querySelector('button')!; + const container1 = root.querySelector('#container1_innerDiv')!; + const container2 = root.querySelector('#container2_standard')!; const container3 = - root.querySelector('#container3_notVisibleElements') as HTMLElement; + root.querySelector('#container3_notVisibleElements')!; const container4 = - root.querySelector('#container4_disabledOrHiddenElements') as HTMLElement; + root.querySelector('#container4_disabledOrHiddenElements')!; const container5 = - root.querySelector('#container5_noFocusableChild') as HTMLElement; + root.querySelector('#container5_noFocusableChild')!; return {button, container1, container2, container3, container4, container5}; } describe('FocusTrap', () => { afterEach(() => { - [].slice.call(document.querySelectorAll('#root')).forEach((el) => { + Array.from(document.querySelectorAll('#root')).forEach((el) => { document.body.removeChild(el); }); }); @@ -139,7 +136,7 @@ describe('FocusTrap', () => { it('sets initial focus to initialFocusEl', () => { const {container1} = setUp(); - const initialFocusEl = container1.querySelector('#con1b') as HTMLElement; + const initialFocusEl = container1.querySelector('#con1b')!; const focusTrap = new FocusTrap(container1, {initialFocusEl}); focusTrap.trapFocus(); expect(document.activeElement!.id).toBe('con1b'); diff --git a/packages/mdc-dom/test/mdc-dom.scss.test.ts b/packages/mdc-dom/test/mdc-dom.scss.test.ts index 915d20c6c50..e981c0c136e 100644 --- a/packages/mdc-dom/test/mdc-dom.scss.test.ts +++ b/packages/mdc-dom/test/mdc-dom.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-dom.scss', () => { diff --git a/packages/mdc-dom/test/ponyfill.test.ts b/packages/mdc-dom/test/ponyfill.test.ts index 85333f6b712..6d9a4cb0454 100644 --- a/packages/mdc-dom/test/ponyfill.test.ts +++ b/packages/mdc-dom/test/ponyfill.test.ts @@ -21,15 +21,9 @@ * THE SOFTWARE. */ -import {closest, estimateScrollWidth, matches} from '../ponyfill'; -function getFixture(content: string) { - const wrapper = document.createElement('div'); - wrapper.innerHTML = content; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; -} +import {createFixture, html} from '../../../testing/dom'; +import {closest, estimateScrollWidth, matches} from '../ponyfill'; describe('MDCDom - ponyfill', () => { it('#closest returns result from native method if available', () => { @@ -85,13 +79,13 @@ describe('MDCDom - ponyfill', () => { }); it('#matches returns true when the selector matches the element', () => { - const element = getFixture(`
`); + const element = createFixture(html`
`); expect(matches(element, '.foo')).toBe(true); }); it('#matches returns false when the selector does not match the element', () => { - const element = getFixture(`
`); + const element = createFixture(html`
`); expect(matches(element, '.bar')).toBe(false); }); @@ -106,19 +100,21 @@ describe('MDCDom - ponyfill', () => { it('#estimateScrollWidth returns the default width when the element is not hidden', () => { - const root = getFixture(` - - `); - const el = root.querySelector('#i0') as HTMLElement; + const root = createFixture(html` + + + `); + const el = root.querySelector('#i0')!; expect(estimateScrollWidth(el)).toBe(10); }); it('#estimateScrollWidth returns the estimated width when the element is hidden', () => { - const root = getFixture(` - - `); - const el = root.querySelector('#i0') as HTMLElement; + const root = createFixture(html` + + + `); + const el = root.querySelector('#i0')!; expect(estimateScrollWidth(el)).toBe(10); }); }); diff --git a/packages/mdc-drawer/README.md b/packages/mdc-drawer/README.md index a73ef290775..cd3c4db2836 100644 --- a/packages/mdc-drawer/README.md +++ b/packages/mdc-drawer/README.md @@ -31,6 +31,14 @@ A navigation drawer is recommended for: * Apps with two or more levels of navigation hierarchy * Quick navigation between unrelated destinations +### Important Changes + +Drawer is currently being updated to use the new List implementation. For now, +please continue to use the old implementation (`mdc-deprecated-list` and +associated DOM/classes) instead of the new one (`mdc-list`). + +See the [List documentation](../mdc-list/README.md) for more information. + ### Installation ``` @@ -46,7 +54,7 @@ npm install @material/drawer @include drawer.core-styles; @include drawer.dismissible-core-styles; @include drawer.modal-core-styles; -@include list.core-styles; +@include list.deprecated-core-styles; ``` ### JavaScript instantiation @@ -55,7 +63,7 @@ For permanently visible drawer, the list must be instantiated for appropriate ke ```js import {MDCList} from "@material/list"; -const list = MDCList.attachTo(document.querySelector('.mdc-list')); +const list = MDCList.attachTo(document.querySelector('.mdc-deprecated-list')); list.wrapFocus = true; ``` @@ -63,7 +71,7 @@ Other variants use the `MDCDrawer` component, which will instantiate `MDCList` a ```js import {MDCDrawer} from "@material/drawer"; -const drawer = MDCDrawer.attachTo(document.querySelector('.mdc-drawer')); +const drawer = MDCDrawer.attachTo(document.querySelector('.mdc-drawer')); ``` ### Icons @@ -89,15 +97,15 @@ It is recommended to shift focus to the first focusable element in the main cont Restore focus to the first focusable element when a list item is activated or after the drawer closes. Do not close the drawer upon item activation, since it should be up to the user when to show/hide the dismissible drawer. ```js -const listEl = document.querySelector('.mdc-drawer .mdc-list'); -const mainContentEl = document.querySelector('.main-content'); +const listEl = document.querySelector('.mdc-drawer .mdc-deprecated-list'); +const mainContentEl = document.querySelector('.main-content'); listEl.addEventListener('click', (event) => { - mainContentEl.querySelector('input, button').focus(); + mainContentEl.querySelector('input, button').focus(); }); document.body.addEventListener('MDCDrawer:closed', () => { - mainContentEl.querySelector('input, button').focus(); + mainContentEl.querySelector('input, button').focus(); }); ``` @@ -106,15 +114,15 @@ document.body.addEventListener('MDCDrawer:closed', () => { Close the drawer when an item is activated in order to dismiss the modal as soon as the user performs an action. Only restore focus to the first focusable element in the main content after the drawer is closed, since it's being closed automatically. ```js -const listEl = document.querySelector('.mdc-drawer .mdc-list'); -const mainContentEl = document.querySelector('.main-content'); +const listEl = document.querySelector('.mdc-drawer .mdc-deprecated-list'); +const mainContentEl = document.querySelector('.main-content'); listEl.addEventListener('click', (event) => { drawer.open = false; }); document.body.addEventListener('MDCDrawer:closed', () => { - mainContentEl.querySelector('input, button').focus(); + mainContentEl.querySelector('input, button').focus(); }); ``` @@ -131,21 +139,21 @@ There are three types of navigation drawers: [standard (1)](#standard-navigation ```html
@@ -285,21 +293,21 @@ Dismissible drawers are by default hidden off screen, and can slide into view. D
- + ``` @@ -88,7 +88,7 @@ If the label text is too long for a single line, it will wrap the text by defaul ... - + ``` @@ -96,7 +96,7 @@ If the label text is too long for a single line, it will wrap the text by defaul Property | Value Type | Description --- | --- | --- -`input` | String | Gets and sets the form field input. +`input` | String | Gets and sets the form field input. In order for the label ripple integration to work correctly, the `input` property needs to be set to a valid instance of an MDC Web input element which exposes a `ripple` getter. No action is taken if the `input` property is not set or the input instance doesn't expose a `ripple` getter. diff --git a/packages/mdc-form-field/_form-field-theme.scss b/packages/mdc-form-field/_form-field-theme.scss new file mode 100644 index 00000000000..d53bd6ae66a --- /dev/null +++ b/packages/mdc-form-field/_form-field-theme.scss @@ -0,0 +1,79 @@ +// +// Copyright 2023 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use 'sass:map'; +@use 'sass:string'; +@use '@material/feature-targeting/feature-targeting'; +@use '@material/theme/keys'; +@use '@material/theme/theme'; +@use '@material/theme/validate'; +@use '@material/typography/typography'; + +$item-spacing: 4px !default; + +$_custom-property-prefix: 'form-field'; + +$light-theme: ( + 'label-text-color': 'text-primary-on-background', + 'label-text-font': string.unquote('Roboto, sans-serif'), + 'label-text-line-height': typography.px-to-rem(20px), + 'label-text-size': typography.px-to-rem(14px), + 'label-text-tracking': typography.get-letter-spacing_(0.25, 0.875), + 'label-text-weight': 400, +); + +@mixin theme($theme) { + $theme: validate.theme($light-theme, $theme); + + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +@mixin theme-styles($theme, $query: feature-targeting.all()) { + $theme: validate.theme-styles($light-theme, $theme); + $theme: keys.create-theme-properties($theme, $_custom-property-prefix); + + $feat-color: feature-targeting.create-target($query, color); + $feat-typography: feature-targeting.create-target($query, typography); + + .mdc-form-field { + @include feature-targeting.targets($feat-typography) { + @include typography.theme-styles( + ( + 'font': map.get($theme, 'label-text-font'), + 'line-height': map.get($theme, 'label-text-line-height'), + 'size': map.get($theme, 'label-text-size'), + 'tracking': map.get($theme, 'label-text-tracking'), + 'weight': map.get($theme, 'label-text-weight'), + ) + ); + } + @include feature-targeting.targets($feat-color) { + @include theme.property(color, map.get($theme, 'label-text-color')); + } + } +} diff --git a/packages/mdc-form-field/_form-field.scss b/packages/mdc-form-field/_form-field.scss new file mode 100644 index 00000000000..d9d09f34787 --- /dev/null +++ b/packages/mdc-form-field/_form-field.scss @@ -0,0 +1,108 @@ +// +// Copyright 2023 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use '@material/feature-targeting/feature-targeting'; +@use '@material/rtl/rtl'; +@use '@material/theme/theme'; +@use '@material/typography/typography'; +@use './form-field-theme'; +@use './variables'; + +@mixin core-styles($query: feature-targeting.all()) { + $feat-color: feature-targeting.create-target($query, color); + + @include static-styles($query); + + // TODO: Replace with call to theme-styles after Angular moves to Theming API + .mdc-form-field { + @include typography.typography(body2, $query); + + @include feature-targeting.targets($feat-color) { + @include theme.property(color, text-primary-on-background); + } + } +} + +@mixin static-styles($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + .mdc-form-field { + @include feature-targeting.targets($feat-structure) { + display: inline-flex; + &[hidden] { + display: none; + } + align-items: center; + vertical-align: middle; + } + + > label { + @include feature-targeting.targets($feat-structure) { + @include rtl.reflexive-property(margin, 0, auto); + @include rtl.reflexive-property(padding, variables.$item-spacing, 0); + + order: 0; + } + } + } + + .mdc-form-field--nowrap { + > label { + @include feature-targeting.targets($feat-structure) { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + + .mdc-form-field--align-end { + > label { + @include feature-targeting.targets($feat-structure) { + @include rtl.reflexive-property(margin, auto, 0); + @include rtl.reflexive-property(padding, 0, variables.$item-spacing); + + order: -1; + } + } + } + + .mdc-form-field--space-between { + @include feature-targeting.targets($feat-structure) { + justify-content: space-between; + } + + > label { + @include feature-targeting.targets($feat-structure) { + margin: 0; + + @include rtl.rtl { + // RTL needed for specificity + margin: 0; + } + } + } + } +} diff --git a/packages/mdc-form-field/_mixins.scss b/packages/mdc-form-field/_mixins.scss index e20284bc291..e19f6216040 100644 --- a/packages/mdc-form-field/_mixins.scss +++ b/packages/mdc-form-field/_mixins.scss @@ -20,88 +20,5 @@ // THE SOFTWARE. // -// Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern - -@use '@material/feature-targeting/feature-targeting'; -@use '@material/rtl/mixins' as rtl-mixins; -@use '@material/theme/theme'; -@use '@material/typography/typography'; -@use './variables'; - -@mixin core-styles($query: feature-targeting.all()) { - $feat-color: feature-targeting.create-target($query, color); - $feat-structure: feature-targeting.create-target($query, structure); - - .mdc-form-field { - @include typography.typography(body2, $query); - - @include feature-targeting.targets($feat-color) { - @include theme.property(color, text-primary-on-background); - } - - @include feature-targeting.targets($feat-structure) { - display: inline-flex; - align-items: center; - vertical-align: middle; - } - - // stylelint-disable-next-line selector-max-type - > label { - @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(margin, 0, auto); - @include rtl-mixins.reflexive-property( - padding, - variables.$item-spacing, - 0 - ); - - order: 0; - } - } - } - - .mdc-form-field--nowrap { - > label { - @include feature-targeting.targets($feat-structure) { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - } - } - - .mdc-form-field--align-end { - // stylelint-disable-next-line selector-max-type - > label { - @include feature-targeting.targets($feat-structure) { - @include rtl-mixins.reflexive-property(margin, auto, 0); - @include rtl-mixins.reflexive-property( - padding, - 0, - variables.$item-spacing - ); - - order: -1; - } - } - } - - .mdc-form-field--space-between { - @include feature-targeting.targets($feat-structure) { - justify-content: space-between; - } - - // stylelint-disable-next-line selector-max-type - > label { - @include feature-targeting.targets($feat-structure) { - margin: 0; - - @include rtl-mixins.rtl { - // RTL needed for specificity - margin: 0; - } - } - } - } -} +/// @deprecated Import `_form-field.scss` instead. +@forward './form-field' show core-styles; diff --git a/packages/mdc-form-field/_variables.scss b/packages/mdc-form-field/_variables.scss index 3a94c6f0836..c167edbad66 100644 --- a/packages/mdc-form-field/_variables.scss +++ b/packages/mdc-form-field/_variables.scss @@ -20,4 +20,5 @@ // THE SOFTWARE. // -$item-spacing: 4px !default; +/// @deprecated Import `_form-field-theme.scss` instead. +@forward './form-field-theme' show $item-spacing; diff --git a/packages/mdc-form-field/adapter.ts b/packages/mdc-form-field/adapter.ts index 51f4aedb047..4bdd2bfcebc 100644 --- a/packages/mdc-form-field/adapter.ts +++ b/packages/mdc-form-field/adapter.ts @@ -33,6 +33,8 @@ import {EventType, SpecificEventListener} from '@material/base/types'; export interface MDCFormFieldAdapter { activateInputRipple(): void; deactivateInputRipple(): void; - deregisterInteractionHandler(evtType: K, handler: SpecificEventListener): void; - registerInteractionHandler(evtType: K, handler: SpecificEventListener): void; + deregisterInteractionHandler( + eventType: K, handler: SpecificEventListener): void; + registerInteractionHandler( + eventType: K, handler: SpecificEventListener): void; } diff --git a/packages/mdc-form-field/component.ts b/packages/mdc-form-field/component.ts index 981e50546a1..02e0f0d0125 100644 --- a/packages/mdc-form-field/component.ts +++ b/packages/mdc-form-field/component.ts @@ -23,28 +23,32 @@ import {MDCComponent} from '@material/base/component'; import {MDCRipple} from '@material/ripple/component'; + import {MDCFormFieldAdapter} from './adapter'; import {MDCFormFieldFoundation} from './foundation'; +/** MDC Form Field Input */ export interface MDCFormFieldInput { - readonly ripple: MDCRipple | undefined; + readonly ripple: MDCRipple|undefined; } +/** MDC Form Field */ export class MDCFormField extends MDCComponent { - static attachTo(root: HTMLElement) { + static override attachTo(root: HTMLElement) { return new MDCFormField(root); } input?: MDCFormFieldInput; - private labelEl(): Element|null { + private labelEl() { const {LABEL_SELECTOR} = MDCFormFieldFoundation.strings; - return this.root.querySelector(LABEL_SELECTOR); + return this.root.querySelector(LABEL_SELECTOR); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCFormFieldAdapter = { activateInputRipple: () => { if (this.input && this.input.ripple) { @@ -56,17 +60,11 @@ export class MDCFormField extends MDCComponent { this.input.ripple.deactivate(); } }, - deregisterInteractionHandler: (evtType, handler) => { - const labelEl = this.labelEl(); - if (labelEl) { - (labelEl as HTMLElement).removeEventListener(evtType, handler); - } + deregisterInteractionHandler: (eventType, handler) => { + this.labelEl()?.removeEventListener(eventType, handler); }, - registerInteractionHandler: (evtType, handler) => { - const labelEl = this.labelEl(); - if (labelEl) { - (labelEl as HTMLElement).addEventListener(evtType, handler); - } + registerInteractionHandler: (eventType, handler) => { + this.labelEl()?.addEventListener(eventType, handler); }, }; return new MDCFormFieldFoundation(adapter); diff --git a/packages/mdc-form-field/foundation.ts b/packages/mdc-form-field/foundation.ts index ec102238452..00ce7b95619 100644 --- a/packages/mdc-form-field/foundation.ts +++ b/packages/mdc-form-field/foundation.ts @@ -22,19 +22,21 @@ */ import {MDCFoundation} from '@material/base/foundation'; + import {MDCFormFieldAdapter} from './adapter'; import {cssClasses, strings} from './constants'; +/** MDC Form Field Foundation */ export class MDCFormFieldFoundation extends MDCFoundation { - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get strings() { + static override get strings() { return strings; } - static get defaultAdapter(): MDCFormFieldAdapter { + static override get defaultAdapter(): MDCFormFieldAdapter { return { activateInputRipple: () => undefined, deactivateInputRipple: () => undefined, @@ -53,11 +55,11 @@ export class MDCFormFieldFoundation extends MDCFoundation { }; } - init() { + override init() { this.adapter.registerInteractionHandler('click', this.click); } - destroy() { + override destroy() { this.adapter.deregisterInteractionHandler('click', this.click); } diff --git a/packages/mdc-form-field/mdc-form-field.import.scss b/packages/mdc-form-field/mdc-form-field.import.scss index 1c856849f44..eaaf352dbb6 100644 --- a/packages/mdc-form-field/mdc-form-field.import.scss +++ b/packages/mdc-form-field/mdc-form-field.import.scss @@ -1,5 +1,3 @@ -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; @forward "@material/rtl/variables" as mdc-rtl-*; @forward "@material/theme/variables" as mdc-theme-*; @forward "@material/typography/variables" as mdc-typography-*; diff --git a/packages/mdc-form-field/mdc-form-field.scss b/packages/mdc-form-field/mdc-form-field.scss index d645b5879d4..7e720769e08 100644 --- a/packages/mdc-form-field/mdc-form-field.scss +++ b/packages/mdc-form-field/mdc-form-field.scss @@ -20,5 +20,8 @@ // THE SOFTWARE. // -@use './mixins'; -@include mixins.core-styles; +@use './form-field'; +@use './form-field-theme'; + +@include form-field.static-styles(); +@include form-field-theme.theme-styles(form-field-theme.$light-theme); diff --git a/packages/mdc-form-field/package.json b/packages/mdc-form-field/package.json index c5d5f1f8ad0..1aeb5291153 100644 --- a/packages/mdc-form-field/package.json +++ b/packages/mdc-form-field/package.json @@ -1,7 +1,7 @@ { "name": "@material/form-field", "description": "Material Components for the web wrapper for laying out form fields and labels next to one another", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "keywords": [ "material components", @@ -17,12 +17,12 @@ "directory": "packages/mdc-form-field" }, "dependencies": { - "@material/base": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/ripple": "^12.0.0", - "@material/rtl": "^12.0.0", - "@material/theme": "^12.0.0", - "@material/typography": "^12.0.0", + "@material/base": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/ripple": "^14.0.0", + "@material/rtl": "^14.0.0", + "@material/theme": "^14.0.0", + "@material/typography": "^14.0.0", "tslib": "^2.1.0" } } diff --git a/packages/mdc-form-field/test/component.test.ts b/packages/mdc-form-field/test/component.test.ts index bc98c96fe9c..13f00663c9f 100644 --- a/packages/mdc-form-field/test/component.test.ts +++ b/packages/mdc-form-field/test/component.test.ts @@ -22,19 +22,16 @@ */ import {MDCFormField} from '../../mdc-form-field/index'; +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; function getFixture() { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html`
- +
- `; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; + `); } function setupTest() { @@ -64,7 +61,7 @@ describe('MDCFormField', () => { () => { const {root, component} = setupTest(); const handler = jasmine.createSpy('eventHandler'); - const label = root.querySelector('label') as HTMLElement; + const label = root.querySelector('label')!; (component.getDefaultFoundation() as any) .adapter.registerInteractionHandler('click', handler); @@ -77,7 +74,7 @@ describe('MDCFormField', () => { () => { const {root, component} = setupTest(); const handler = jasmine.createSpy('eventHandler'); - const label = root.querySelector('label') as HTMLElement; + const label = root.querySelector('label')!; label.addEventListener('click', handler); (component.getDefaultFoundation() as any) @@ -89,7 +86,7 @@ describe('MDCFormField', () => { it('adapter#activateInputRipple calls activate on the input ripple', () => { const {component} = setupTest(); - const ripple = {activate: jasmine.createSpy('activate')} as any; + const ripple: any = {activate: jasmine.createSpy('activate')}; const input = {ripple}; component.input = input; @@ -123,7 +120,7 @@ describe('MDCFormField', () => { it('adapter#deactivateInputRipple calls deactivate on the input ripple', () => { const {component} = setupTest(); - const ripple = {deactivate: jasmine.createSpy('deactivate')} as any; + const ripple: any = {deactivate: jasmine.createSpy('deactivate')}; const input = {ripple}; component.input = input; diff --git a/packages/mdc-form-field/test/feature-targeting-any.test.scss b/packages/mdc-form-field/test/feature-targeting-any.test.scss index c992af3c4a5..247c349c7f4 100644 --- a/packages/mdc-form-field/test/feature-targeting-any.test.scss +++ b/packages/mdc-form-field/test/feature-targeting-any.test.scss @@ -1,9 +1,14 @@ -@use '../mixins' as form-field; +@use '../form-field'; +@use '../form-field-theme'; @use '@material/feature-targeting/feature-targeting'; @mixin test($query) { .test { - @include form-field.core-styles($query: $query); + @include form-field.static-styles($query: $query); + @include form-field-theme.theme-styles( + form-field-theme.$light-theme, + $query + ); } } diff --git a/packages/mdc-form-field/test/mdc-form-field.scss.test.ts b/packages/mdc-form-field/test/mdc-form-field.scss.test.ts index 661a92c4006..c57f668cc31 100644 --- a/packages/mdc-form-field/test/mdc-form-field.scss.test.ts +++ b/packages/mdc-form-field/test/mdc-form-field.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-form-field.scss', () => { diff --git a/packages/mdc-icon-button/README.md b/packages/mdc-icon-button/README.md index 28b9a0a56da..ea4187e92ba 100644 --- a/packages/mdc-icon-button/README.md +++ b/packages/mdc-icon-button/README.md @@ -24,9 +24,7 @@ npm install @material/icon-button ### Styles ```scss -@use "@material/icon-button"; - -@include icon-button.core-styles; +@use "@material/icon-button/styles"; ``` ### JavaScript instantiation @@ -58,9 +56,10 @@ However, you can also use SVG, [Font Awesome](https://fontawesome.com/), or any ## Icon button ```html - ``` @@ -75,15 +74,18 @@ To meet this requirement, add the following to your button: ```html
-
``` **Note: The outer `mdc-touch-target-wrapper` element is only necessary if you want to avoid potentially overlapping touch targets on adjacent elements (due to collapsing margins).** +The `mdc-icon-button__focus-ring` element ensures that a focus indicator is displayed in high contrast mode around the active/focused icon button. + ## Icon button toggle The icon button can be used to toggle between an on and off icon. @@ -98,6 +100,7 @@ If the button should be initialized in the "on" state, then add the `mdc-icon-bu aria-label="Add to favorites" aria-pressed="false">
+ favorite favorite_border @@ -120,6 +123,7 @@ The icon button toggle can be used with SVGs. aria-label="Unstar this item" aria-pressed="true">
+ ... @@ -139,6 +143,7 @@ The icon button toggle can be used with `img` tags. aria-label="Unstar this item" aria-pressed="true">
+ @@ -158,6 +163,7 @@ and `aria-data-label-off` (aria label in off state) attributes, and omit the data-aria-label-on="Remove from favorites" data-aria-label-off="Add to favorites">
+ favorite favorite_border @@ -174,6 +180,7 @@ CSS Class | Description `mdc-icon-button--on` | This class is applied to the root element and is used to indicate if the icon button toggle is in the "on" state. `mdc-icon-button__icon` | This class is applied to each icon element for the icon button toggle. `mdc-icon-button__icon--on` | This class is applied to a icon element and is used to indicate the toggle button icon that is represents the "on" icon. +`mdc-icon-button__focus-ring` | Recommended. Indicates the element which shows the high contrast mode focus ring styling. ### Sass mixins @@ -207,7 +214,7 @@ Method Signature | Description `removeClass(className: string) => void` | Removes a class from the root element. `hasClass(className: string) => boolean` | Determines whether the root element has the given CSS class name. `setAttr(name: string, value: string) => void` | Sets the attribute `name` to `value` on the root element. -`notifyChange(evtData: {isOn: boolean}) => void` | Broadcasts a change notification, passing along the `evtData` to the environment's event handling system. In our vanilla implementation, Custom Events are used for this. +`notifyChange(eventData: {isOn: boolean}) => void` | Broadcasts a change notification, passing along the `eventData` to the environment's event handling system. In our vanilla implementation, Custom Events are used for this. ### `MDCIconButtonToggleFoundation` diff --git a/packages/mdc-icon-button/_icon-button-all-deprecated.scss b/packages/mdc-icon-button/_icon-button-all-deprecated.scss new file mode 100644 index 00000000000..bf188f9c3a3 --- /dev/null +++ b/packages/mdc-icon-button/_icon-button-all-deprecated.scss @@ -0,0 +1,2 @@ +@forward "./variables"; +@forward "./mixins"; diff --git a/packages/mdc-icon-button/_icon-button-theme.scss b/packages/mdc-icon-button/_icon-button-theme.scss index 7af220b6294..557fc722918 100644 --- a/packages/mdc-icon-button/_icon-button-theme.scss +++ b/packages/mdc-icon-button/_icon-button-theme.scss @@ -20,16 +20,26 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern @use 'sass:math'; +@use 'sass:map'; +@use 'sass:meta'; @use '@material/density/functions' as density-functions; @use '@material/density/variables' as density-variables; +@use '@material/elevation/elevation-theme'; @use '@material/feature-targeting/feature-targeting'; +@use '@material/focus-ring/focus-ring'; @use '@material/ripple/ripple-theme'; -@use '@material/rtl/mixins' as rtl; +@use '@material/rtl/rtl'; +@use '@material/dom/dom'; +@use '@material/theme/custom-properties'; +@use '@material/theme/keys'; +@use '@material/theme/state'; @use '@material/theme/theme'; +@use '@material/theme/theme-color'; +@use '@material/touch-target/mixins' as touch-target-mixins; $ripple-target: '.mdc-icon-button__ripple'; @@ -37,6 +47,7 @@ $icon-size: 24px !default; $size: 48px !default; $minimum-height: 28px !default; $maximum-height: $size !default; +$container-shape: 50%; $density-scale: density-variables.$default-scale !default; $density-config: ( size: ( @@ -46,6 +57,82 @@ $density-config: ( ), ) !default; +$_custom-property-prefix: 'icon-button'; + +$light-theme: ( + disabled-icon-color: theme-color.$on-surface, + disabled-icon-opacity: 0.38, + icon-color: theme-color.$primary, + icon-size: $icon-size, + focus-icon-color: theme-color.$primary, + focus-state-layer-color: theme-color.$primary, + focus-state-layer-opacity: 0.12, + hover-icon-color: theme-color.$primary, + hover-state-layer-color: theme-color.$primary, + hover-state-layer-opacity: 0.08, + pressed-icon-color: theme-color.$primary, + pressed-state-layer-color: theme-color.$primary, + pressed-state-layer-opacity: 0.12, + state-layer-size: $size, + focus-ring-color: null, + focus-ring-offset: 0, +); + +@mixin theme($theme) { + @include theme.validate-theme($light-theme, $theme); + + @include keys.declare-custom-properties( + $theme, + $prefix: $_custom-property-prefix + ); +} + +@mixin theme-styles($theme) { + @include theme.validate-theme-styles($light-theme, $theme); + + $theme: keys.create-theme-properties( + $theme, + $prefix: $_custom-property-prefix + ); + + @include _state-layer-size($size: map.get($theme, state-layer-size)); + @include _icon-size(map.get($theme, icon-size)); + @include _disabled-icon-opacity(map.get($theme, disabled-icon-opacity)); + @include _icon-color-with-map( + ( + default: map.get($theme, icon-color), + disabled: map.get($theme, disabled-icon-color), + focus: map.get($theme, focus-icon-color), + hover: map.get($theme, hover-icon-color), + pressed: map.get($theme, pressed-icon-color), + ) + ); + + // States styles + @include ripple-theme.theme-styles( + ( + focus-state-layer-color: map.get($theme, focus-state-layer-color), + focus-state-layer-opacity: map.get($theme, focus-state-layer-opacity), + hover-state-layer-color: map.get($theme, hover-state-layer-color), + hover-state-layer-opacity: map.get($theme, hover-state-layer-opacity), + pressed-state-layer-color: map.get($theme, pressed-state-layer-color), + pressed-state-layer-opacity: map.get($theme, pressed-state-layer-opacity), + ), + $ripple-target: $ripple-target + ); + + @include ripple-theme.states-colors( + ( + hover: map.get($theme, hover-state-layer-color), + press: map.get($theme, pressed-state-layer-color), + ), + $ripple-target: $ripple-target + ); + + @include _focus-ring-color(map.get($theme, 'focus-ring-color')); + @include _focus-ring-offset(map.get($theme, 'focus-ring-offset')); +} + /// /// Sets the density scale for icon button. /// @@ -60,12 +147,6 @@ $density-config: ( ); @include size($size, $query: $query); - - &.mdc-icon-button--touch { - @if $density-scale != 0 { - @include _touch-target-reset($query: $query); - } - } } /// @@ -80,28 +161,48 @@ $density-config: ( @include feature-targeting.targets($feat-structure) { width: $size; height: $size; - padding: math.div($size - $icon-size, 2); + padding: calc(($size - $icon-size) / 2); } -} -/// -/// Resets touch target-related styles. This is called from the density mixin to -/// automatically remove the increased touch target, since dense components -/// don't have the same default a11y requirements. -/// @access private -/// -@mixin _touch-target-reset($query: feature-targeting.all()) { - $feat-structure: feature-targeting.create-target($query, structure); + &.mdc-icon-button--reduced-size { + $component-size: $size; + // Icon button ripple size is capped at 40px for icon buttons with + // densities -1 and 0 (icon buttons with sizes 44x44 and 48x48px). + // See http://b/192353968 for more info. + @if math.unit($size) == 'px' and ($size >= 40px and $size <= 48px) { + $component-size: 40px; + } - @include feature-targeting.targets($feat-structure) { - margin-top: 0; - margin-bottom: 0; + .mdc-icon-button__ripple { + @include feature-targeting.targets($feat-structure) { + width: $component-size; + height: $component-size; + } + + @include touch-target-mixins.margin( + $component-height: $component-size, + $component-width: $component-size, + $touch-target-height: $size, + $touch-target-width: $size, + $query: $query + ); + } + + .mdc-icon-button__focus-ring { + @include feature-targeting.targets($feat-structure) { + max-height: $component-size; + max-width: $component-size; + } + } } .mdc-icon-button__touch { - @include feature-targeting.targets($feat-structure) { - display: none; - } + @include touch-target-mixins.touch-target( + $set-width: true, + $query: $query, + $height: $size, + $width: $size + ); } } @@ -124,14 +225,16 @@ $density-config: ( ) { $feat-structure: feature-targeting.create-target($query, structure); + $component-width: $width + $padding * 2; + $component-height: $height + $padding * 2; + @include feature-targeting.targets($feat-structure) { - width: $width + $padding * 2; - height: $height + $padding * 2; + width: $component-width; + height: $component-height; padding: $padding; font-size: math.max($width, $height); } - // stylelint-disable-next-line selector-max-type svg, img { @include feature-targeting.targets($feat-structure) { @@ -146,12 +249,14 @@ $density-config: ( /// @param {Color} $color - The desired font and ripple color. /// @mixin ink-color($color, $query: feature-targeting.all()) { - @include ink-color_($color, $query: $query); - @include ripple-theme.states( - $color, - $query: $query, - $ripple-target: $ripple-target - ); + @if $color { + @include ink-color_($color, $query: $query); + @include ripple-theme.states( + $color, + $query: $query, + $ripple-target: $ripple-target + ); + } } /// @@ -163,7 +268,7 @@ $density-config: ( .mdc-button__icon { @include rtl.rtl { @include feature-targeting.targets($feat-structure) { - /* @noflip */ + @include rtl.ignore-next-line(); transform: rotate(180deg); } } @@ -210,6 +315,123 @@ $density-config: ( } } +@mixin _state-layer-size($size) { + @include theme.property(height, $size); + @include theme.property(width, $size); + + @if $size { + $value: custom-properties.get-declaration-value($size); + @if type-of($value) == 'number' { + @include size($value); + } + } +} + +@mixin _icon-size($size) { + .mdc-button__icon { + @include theme.property(font-size, $size); + } + svg, + img { + @include theme.property(width, $size); + @include theme.property(height, $size); + } +} + +/// +/// Sets the icon opacity to the given opacity. +/// @access private +/// +@mixin _disabled-icon-opacity($opacity) { + &:disabled { + @include theme.property(opacity, $opacity); + } +} + +/// +/// Sets the icon color to the given color. +/// @param {map} $color-map - The desired icon color, specified as a map of +/// colors with states {default, disabled, focus, hover, pressed} as keys. +/// @access private +/// +@mixin _icon-color-with-map($color-map) { + @include ink-color_(state.get-default-state($color-map)); + + $focus: state.get-focus-state($color-map); + @if $focus { + @include ripple-theme.focus { + @include ink-color_($focus); + } + } + + $hover: state.get-hover-state($color-map); + @if $hover { + &:hover { + @include ink-color_($hover); + } + } + + $pressed: state.get-pressed-state($color-map); + @if $pressed { + @include ripple-theme.active { + @include ink-color_($pressed); + } + } + + $disabled: state.get-disabled-state($color-map); + @if $disabled { + &:disabled { + @include ink-color_($disabled); + } + } +} + +@mixin _states-colors($color-map) { + // TODO(b/191298796): support focused & pressed key colors. + + $hover: map.get($color-map, hover); + @if $hover { + @include ripple-theme.states-base-color( + $color: $hover, + $ripple-target: $ripple-target + ); + } +} + +@mixin _focus-ring-color($color, $query: feature-targeting.all()) { + $color-value: if( + custom-properties.is-custom-prop($color), + custom-properties.get-declaration-value($color), + $color + ); + + @if $color != null { + .mdc-icon-button__focus-ring { + @include focus-ring.focus-ring-color( + $inner-ring-color: $color-value, + $query: $query + ); + } + } +} + +@mixin _focus-ring-offset($offset, $query: feature-targeting.all()) { + $offset-value: if( + custom-properties.is-custom-prop($offset), + custom-properties.get-declaration-value($offset), + $offset + ); + + @if meta.type-of($offset-value) == 'number' { + .mdc-icon-button__focus-ring { + @include focus-ring.focus-ring-offset( + $offset: $offset-value, + $query: $query + ); + } + } +} + /// /// Helps style the icon button in its disabled state. /// @access private diff --git a/packages/mdc-icon-button/_icon-button.scss b/packages/mdc-icon-button/_icon-button.scss index 5dc54e2e123..9c7002cb43b 100644 --- a/packages/mdc-icon-button/_icon-button.scss +++ b/packages/mdc-icon-button/_icon-button.scss @@ -20,10 +20,12 @@ // THE SOFTWARE. // +// stylelint-disable selector-class-pattern -- // Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern +@use '@material/dom/dom'; @use '@material/feature-targeting/feature-targeting'; +@use '@material/focus-ring/focus-ring'; @use '@material/ripple/ripple'; @use '@material/ripple/ripple-theme'; @use '@material/touch-target/mixins' as touch-target-mixins; @@ -34,13 +36,26 @@ @include ripple($query); } -@mixin without-ripple($query: feature-targeting.all()) { +@mixin static-styles($query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); // postcss-bem-linter: define icon-button .mdc-icon-button { - @include base_($query: $query); - @include icon-button-theme.density(0, $query: $query); + @include feature-targeting.targets($feat-structure) { + display: inline-block; + position: relative; + box-sizing: border-box; + border: none; + outline: none; + background-color: transparent; + fill: currentColor; + color: inherit; + text-decoration: none; + cursor: pointer; + user-select: none; + z-index: 0; // Added to render above the container in IE11 tests. + overflow: visible; // Added to fix IE11 touch target tests. + } .mdc-icon-button__touch { @include touch-target-mixins.touch-target( @@ -48,6 +63,49 @@ $query: $query ); } + + @include ripple-theme.focus { + .mdc-icon-button__focus-ring { + @include dom.forced-colors-mode($exclude-ie11: true) { + @include feature-targeting.targets($feat-structure) { + display: block; + } + } + } + } + + @include if-disabled_ { + @include feature-targeting.targets($feat-structure) { + cursor: default; + pointer-events: none; + } + } + + &[hidden] { + @include feature-targeting.targets($feat-structure) { + display: none; + } + } + } + + .mdc-icon-button--display-flex { + @include feature-targeting.targets($feat-structure) { + align-items: center; + display: inline-flex; + justify-content: center; + } + } + + .mdc-icon-button__focus-ring { + @include focus-ring.focus-ring( + $query: $query, + $container-outer-padding-vertical: 0, + $container-outer-padding-horizontal: 0 + ); + + @include feature-targeting.targets($feat-structure) { + display: none; + } } .mdc-icon-button__icon { @@ -55,7 +113,6 @@ display: inline-block; } - // stylelint-disable-next-line plugin/selector-bem-pattern &.mdc-icon-button__icon--on { @include feature-targeting.targets($feat-structure) { display: none; @@ -69,7 +126,6 @@ display: none; } - // stylelint-disable-next-line plugin/selector-bem-pattern &.mdc-icon-button__icon--on { @include feature-targeting.targets($feat-structure) { display: inline-block; @@ -77,14 +133,45 @@ } } } + // postcss-bem-linter: end - .mdc-icon-button--touch { - @include touch-target-mixins.margin( - $component-height: icon-button-theme.$size, + .mdc-icon-button__link { + @include feature-targeting.targets($feat-structure) { + height: 100%; + left: 0; + outline: none; + position: absolute; + top: 0; + width: 100%; + } + } +} + +@mixin without-ripple($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + .mdc-icon-button { + @include feature-targeting.targets($feat-structure) { + font-size: icon-button-theme.$icon-size; + } + + @include icon-button-theme.density(0, $query: $query); + + @include icon-button-theme.disabled-ink-color( + text-disabled-on-light, $query: $query ); + + svg, + img { + @include feature-targeting.targets($feat-structure) { + width: icon-button-theme.$icon-size; + height: icon-button-theme.$icon-size; + } + } } - // postcss-bem-linter: end + + @include static-styles($query: $query); } @mixin ripple($query: feature-targeting.all()) { @@ -104,56 +191,33 @@ $ripple-target: icon-button-theme.$ripple-target ); + &:disabled { + @include ripple-theme.states-opacities( + ( + hover: 0, + focus: 0, + press: 0, + ), + $ripple-target: icon-button-theme.$ripple-target, + $query: $query + ); + } + .mdc-icon-button__ripple { $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { + height: 100%; + left: 0px; pointer-events: none; - z-index: 1; + position: absolute; + top: 0px; + width: 100%; + z-index: -1; } } } } -@mixin base_($query: feature-targeting.all()) { - $feat-structure: feature-targeting.create-target($query, structure); - - @include feature-targeting.targets($feat-structure) { - display: inline-block; - position: relative; - box-sizing: border-box; - border: none; - outline: none; - background-color: transparent; - fill: currentColor; - color: inherit; - font-size: icon-button-theme.$icon-size; - text-decoration: none; - cursor: pointer; - user-select: none; - } - - // stylelint-disable-next-line selector-max-type - svg, - img { - @include feature-targeting.targets($feat-structure) { - width: icon-button-theme.$icon-size; - height: icon-button-theme.$icon-size; - } - } - - @include icon-button-theme.disabled-ink-color( - text-disabled-on-light, - $query: $query - ); - - @include if-disabled_ { - @include feature-targeting.targets($feat-structure) { - cursor: default; - pointer-events: none; - } - } -} - /// /// Helps style the icon button in its disabled state. /// @access private diff --git a/packages/mdc-icon-button/_index.scss b/packages/mdc-icon-button/_index.scss index bf188f9c3a3..bd1ec25d440 100644 --- a/packages/mdc-icon-button/_index.scss +++ b/packages/mdc-icon-button/_index.scss @@ -1,2 +1 @@ -@forward "./variables"; -@forward "./mixins"; +@forward "./icon-button-theme"; diff --git a/packages/mdc-icon-button/_mixins.import.scss b/packages/mdc-icon-button/_mixins.import.scss index 090e519af52..086492bc016 100644 --- a/packages/mdc-icon-button/_mixins.import.scss +++ b/packages/mdc-icon-button/_mixins.import.scss @@ -5,4 +5,4 @@ @forward "@material/theme/all-theme-deprecated" as mdc-theme-* hide $mdc-theme-red-50, $mdc-theme-red-100, $mdc-theme-red-200, $mdc-theme-red-300, $mdc-theme-red-400, $mdc-theme-red-500, $mdc-theme-red-600, $mdc-theme-red-700, $mdc-theme-red-800, $mdc-theme-red-900, $mdc-theme-red-a100, $mdc-theme-red-a200, $mdc-theme-red-a400, $mdc-theme-red-a700, $mdc-theme-pink-50, $mdc-theme-pink-100, $mdc-theme-pink-200, $mdc-theme-pink-300, $mdc-theme-pink-400, $mdc-theme-pink-500, $mdc-theme-pink-600, $mdc-theme-pink-700, $mdc-theme-pink-800, $mdc-theme-pink-900, $mdc-theme-pink-a100, $mdc-theme-pink-a200, $mdc-theme-pink-a400, $mdc-theme-pink-a700, $mdc-theme-purple-50, $mdc-theme-purple-100, $mdc-theme-purple-200, $mdc-theme-purple-300, $mdc-theme-purple-400, $mdc-theme-purple-500, $mdc-theme-purple-600, $mdc-theme-purple-700, $mdc-theme-purple-800, $mdc-theme-purple-900, $mdc-theme-purple-a100, $mdc-theme-purple-a200, $mdc-theme-purple-a400, $mdc-theme-purple-a700, $mdc-theme-deep-purple-50, $mdc-theme-deep-purple-100, $mdc-theme-deep-purple-200, $mdc-theme-deep-purple-300, $mdc-theme-deep-purple-400, $mdc-theme-deep-purple-500, $mdc-theme-deep-purple-600, $mdc-theme-deep-purple-700, $mdc-theme-deep-purple-800, $mdc-theme-deep-purple-900, $mdc-theme-deep-purple-a100, $mdc-theme-deep-purple-a200, $mdc-theme-deep-purple-a400, $mdc-theme-deep-purple-a700, $mdc-theme-indigo-50, $mdc-theme-indigo-100, $mdc-theme-indigo-200, $mdc-theme-indigo-300, $mdc-theme-indigo-400, $mdc-theme-indigo-500, $mdc-theme-indigo-600, $mdc-theme-indigo-700, $mdc-theme-indigo-800, $mdc-theme-indigo-900, $mdc-theme-indigo-a100, $mdc-theme-indigo-a200, $mdc-theme-indigo-a400, $mdc-theme-indigo-a700, $mdc-theme-blue-50, $mdc-theme-blue-100, $mdc-theme-blue-200, $mdc-theme-blue-300, $mdc-theme-blue-400, $mdc-theme-blue-500, $mdc-theme-blue-600, $mdc-theme-blue-700, $mdc-theme-blue-800, $mdc-theme-blue-900, $mdc-theme-blue-a100, $mdc-theme-blue-a200, $mdc-theme-blue-a400, $mdc-theme-blue-a700, $mdc-theme-light-blue-50, $mdc-theme-light-blue-100, $mdc-theme-light-blue-200, $mdc-theme-light-blue-300, $mdc-theme-light-blue-400, $mdc-theme-light-blue-500, $mdc-theme-light-blue-600, $mdc-theme-light-blue-700, $mdc-theme-light-blue-800, $mdc-theme-light-blue-900, $mdc-theme-light-blue-a100, $mdc-theme-light-blue-a200, $mdc-theme-light-blue-a400, $mdc-theme-light-blue-a700, $mdc-theme-cyan-50, $mdc-theme-cyan-100, $mdc-theme-cyan-200, $mdc-theme-cyan-300, $mdc-theme-cyan-400, $mdc-theme-cyan-500, $mdc-theme-cyan-600, $mdc-theme-cyan-700, $mdc-theme-cyan-800, $mdc-theme-cyan-900, $mdc-theme-cyan-a100, $mdc-theme-cyan-a200, $mdc-theme-cyan-a400, $mdc-theme-cyan-a700, $mdc-theme-teal-50, $mdc-theme-teal-100, $mdc-theme-teal-200, $mdc-theme-teal-300, $mdc-theme-teal-400, $mdc-theme-teal-500, $mdc-theme-teal-600, $mdc-theme-teal-700, $mdc-theme-teal-800, $mdc-theme-teal-900, $mdc-theme-teal-a100, $mdc-theme-teal-a200, $mdc-theme-teal-a400, $mdc-theme-teal-a700, $mdc-theme-green-50, $mdc-theme-green-100, $mdc-theme-green-200, $mdc-theme-green-300, $mdc-theme-green-400, $mdc-theme-green-500, $mdc-theme-green-600, $mdc-theme-green-700, $mdc-theme-green-800, $mdc-theme-green-900, $mdc-theme-green-a100, $mdc-theme-green-a200, $mdc-theme-green-a400, $mdc-theme-green-a700, $mdc-theme-light-green-50, $mdc-theme-light-green-100, $mdc-theme-light-green-200, $mdc-theme-light-green-300, $mdc-theme-light-green-400, $mdc-theme-light-green-500, $mdc-theme-light-green-600, $mdc-theme-light-green-700, $mdc-theme-light-green-800, $mdc-theme-light-green-900, $mdc-theme-light-green-a100, $mdc-theme-light-green-a200, $mdc-theme-light-green-a400, $mdc-theme-light-green-a700, $mdc-theme-lime-50, $mdc-theme-lime-100, $mdc-theme-lime-200, $mdc-theme-lime-300, $mdc-theme-lime-400, $mdc-theme-lime-500, $mdc-theme-lime-600, $mdc-theme-lime-700, $mdc-theme-lime-800, $mdc-theme-lime-900, $mdc-theme-lime-a100, $mdc-theme-lime-a200, $mdc-theme-lime-a400, $mdc-theme-lime-a700, $mdc-theme-yellow-50, $mdc-theme-yellow-100, $mdc-theme-yellow-200, $mdc-theme-yellow-300, $mdc-theme-yellow-400, $mdc-theme-yellow-500, $mdc-theme-yellow-600, $mdc-theme-yellow-700, $mdc-theme-yellow-800, $mdc-theme-yellow-900, $mdc-theme-yellow-a100, $mdc-theme-yellow-a200, $mdc-theme-yellow-a400, $mdc-theme-yellow-a700, $mdc-theme-amber-50, $mdc-theme-amber-100, $mdc-theme-amber-200, $mdc-theme-amber-300, $mdc-theme-amber-400, $mdc-theme-amber-500, $mdc-theme-amber-600, $mdc-theme-amber-700, $mdc-theme-amber-800, $mdc-theme-amber-900, $mdc-theme-amber-a100, $mdc-theme-amber-a200, $mdc-theme-amber-a400, $mdc-theme-amber-a700, $mdc-theme-orange-50, $mdc-theme-orange-100, $mdc-theme-orange-200, $mdc-theme-orange-300, $mdc-theme-orange-400, $mdc-theme-orange-500, $mdc-theme-orange-600, $mdc-theme-orange-700, $mdc-theme-orange-800, $mdc-theme-orange-900, $mdc-theme-orange-a100, $mdc-theme-orange-a200, $mdc-theme-orange-a400, $mdc-theme-orange-a700, $mdc-theme-deep-orange-50, $mdc-theme-deep-orange-100, $mdc-theme-deep-orange-200, $mdc-theme-deep-orange-300, $mdc-theme-deep-orange-400, $mdc-theme-deep-orange-500, $mdc-theme-deep-orange-600, $mdc-theme-deep-orange-700, $mdc-theme-deep-orange-800, $mdc-theme-deep-orange-900, $mdc-theme-deep-orange-a100, $mdc-theme-deep-orange-a200, $mdc-theme-deep-orange-a400, $mdc-theme-deep-orange-a700, $mdc-theme-brown-50, $mdc-theme-brown-100, $mdc-theme-brown-200, $mdc-theme-brown-300, $mdc-theme-brown-400, $mdc-theme-brown-500, $mdc-theme-brown-600, $mdc-theme-brown-700, $mdc-theme-brown-800, $mdc-theme-brown-900, $mdc-theme-grey-50, $mdc-theme-grey-100, $mdc-theme-grey-200, $mdc-theme-grey-300, $mdc-theme-grey-400, $mdc-theme-grey-500, $mdc-theme-grey-600, $mdc-theme-grey-700, $mdc-theme-grey-800, $mdc-theme-grey-900, $mdc-theme-blue-grey-50, $mdc-theme-blue-grey-100, $mdc-theme-blue-grey-200, $mdc-theme-blue-grey-300, $mdc-theme-blue-grey-400, $mdc-theme-blue-grey-500, $mdc-theme-blue-grey-600, $mdc-theme-blue-grey-700, $mdc-theme-blue-grey-800, $mdc-theme-blue-grey-900; @forward "@material/ripple" as mdc-* hide $mdc-dark-ink-opacities, $mdc-fade-in-duration, $mdc-fade-out-duration, $mdc-light-ink-opacities, $mdc-pressed-dark-ink-opacity, $mdc-pressed-light-ink-opacity, $mdc-translate-duration, mdc-core-styles, mdc-common, mdc-surface, mdc-radius-bounded, mdc-radius-unbounded, mdc-target-selector, mdc-target-common, mdc-keyframes-; @forward "@material/ripple" as mdc-ripple-* hide $mdc-ripple-states-wash-duration, mdc-ripple-states-base-color, mdc-ripple-states-opacities, mdc-ripple-states-hover-opacity, mdc-ripple-states-focus-opacity, mdc-ripple-states-focus-opacity-properties-, mdc-ripple-states-press-opacity, mdc-ripple-states, mdc-ripple-states-activated, mdc-ripple-states-selected, mdc-ripple-states-interactions-, mdc-ripple-states-opacity, mdc-ripple-states-opacities-; -@forward "./index" as mdc-icon-button-*; +@forward "./icon-button-all-deprecated" as mdc-icon-button-*; diff --git a/packages/mdc-icon-button/_variables.import.scss b/packages/mdc-icon-button/_variables.import.scss index a5853d710fe..db09700211a 100644 --- a/packages/mdc-icon-button/_variables.import.scss +++ b/packages/mdc-icon-button/_variables.import.scss @@ -1,2 +1,2 @@ @forward "@material/density" as mdc-density-* hide mdc-density-prop-value; -@forward "./index" as mdc-icon-button-* hide mdc-icon-button-core-styles, mdc-icon-button-without-ripple, mdc-icon-button-ripple, mdc-icon-button-density, mdc-icon-button-size, mdc-icon-button-icon-size, mdc-icon-button-ink-color, mdc-icon-button-disabled-ink-color, mdc-icon-button-base-, mdc-icon-button-ink-color-, mdc-icon-button-if-disabled-; +@forward "./icon-button-all-deprecated" as mdc-icon-button-* hide mdc-icon-button-core-styles, mdc-icon-button-without-ripple, mdc-icon-button-ripple, mdc-icon-button-density, mdc-icon-button-size, mdc-icon-button-icon-size, mdc-icon-button-ink-color, mdc-icon-button-disabled-ink-color, mdc-icon-button-base-, mdc-icon-button-ink-color-, mdc-icon-button-if-disabled-; diff --git a/packages/mdc-icon-button/adapter.ts b/packages/mdc-icon-button/adapter.ts index 1487134d72e..53c81e6cd85 100644 --- a/packages/mdc-icon-button/adapter.ts +++ b/packages/mdc-icon-button/adapter.ts @@ -42,5 +42,5 @@ export interface MDCIconButtonToggleAdapter { setAttr(attrName: string, attrValue: string): void; - notifyChange(evtData: MDCIconButtonToggleEventDetail): void; + notifyChange(eventData: MDCIconButtonToggleEventDetail): void; } diff --git a/packages/mdc-icon-button/component.ts b/packages/mdc-icon-button/component.ts index 1718fe841d5..4bb0e7d4632 100644 --- a/packages/mdc-icon-button/component.ts +++ b/packages/mdc-icon-button/component.ts @@ -24,14 +24,17 @@ import {MDCComponent} from '@material/base/component'; import {SpecificEventListener} from '@material/base/types'; import {MDCRipple} from '@material/ripple/component'; + import {MDCIconButtonToggleAdapter} from './adapter'; import {MDCIconButtonToggleFoundation} from './foundation'; import {MDCIconButtonToggleEventDetail} from './types'; const {strings} = MDCIconButtonToggleFoundation; -export class MDCIconButtonToggle extends MDCComponent { - static attachTo(root: HTMLElement) { +/** MDC Icon Button Toggle */ +export class MDCIconButtonToggle extends + MDCComponent { + static override attachTo(root: HTMLElement) { return new MDCIconButtonToggle(root); } @@ -39,33 +42,39 @@ export class MDCIconButtonToggle extends MDCComponent; // assigned in initialSyncWithDOM() - initialSyncWithDOM() { + override initialSyncWithDOM() { this.handleClick = () => { this.foundation.handleClick(); }; this.listen('click', this.handleClick); } - destroy() { + override destroy() { this.unlisten('click', this.handleClick); this.ripple.destroy(); super.destroy(); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. const adapter: MDCIconButtonToggleAdapter = { - addClass: (className) => this.root.classList.add(className), + addClass: (className) => { + this.root.classList.add(className); + }, hasClass: (className) => this.root.classList.contains(className), - notifyChange: (evtData) => { + notifyChange: (eventData) => { this.emit( - strings.CHANGE_EVENT, evtData); + strings.CHANGE_EVENT, eventData); + }, + removeClass: (className) => { + this.root.classList.remove(className); }, - removeClass: (className) => this.root.classList.remove(className), getAttr: (attrName) => this.root.getAttribute(attrName), - setAttr: (attrName, attrValue) => - this.root.setAttribute(attrName, attrValue), + setAttr: (attrName, attrValue) => { + this.safeSetAttribute(this.root, attrName, attrValue); + }, }; return new MDCIconButtonToggleFoundation(adapter); } diff --git a/packages/mdc-icon-button/foundation.ts b/packages/mdc-icon-button/foundation.ts index 1a9b8bd408b..d4e9524cc5f 100644 --- a/packages/mdc-icon-button/foundation.ts +++ b/packages/mdc-icon-button/foundation.ts @@ -22,25 +22,28 @@ */ import {MDCFoundation} from '@material/base/foundation'; + import {MDCIconButtonToggleAdapter} from './adapter'; import {cssClasses, strings} from './constants'; -export class MDCIconButtonToggleFoundation extends MDCFoundation { +/** MDC Icon Button Toggle Foundation */ +export class MDCIconButtonToggleFoundation extends + MDCFoundation { /** - * Whether the icon button has an aria label that changes depending on + * Whether the icon button has an aria-label that changes depending on * toggled state. */ - private hasToggledAriaLabel: boolean = false; + private hasToggledAriaLabel = false; - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get strings() { + static override get strings() { return strings; } - static get defaultAdapter(): MDCIconButtonToggleAdapter { + static override get defaultAdapter(): MDCIconButtonToggleAdapter { return { addClass: () => undefined, hasClass: () => false, @@ -55,14 +58,14 @@ export class MDCIconButtonToggleFoundation extends MDCFoundation - `; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; + `); } function setupTest({createMockFoundation = false} = {}) { @@ -64,7 +61,7 @@ describe('MDCIconButtonToggle', () => { it('#constructor initializes the ripple on the root element', () => { const {root} = setupTest(); jasmine.clock().tick(1); - expect(root.classList.contains('mdc-ripple-upgraded')).toBe(true); + expect(root).toHaveClass('mdc-ripple-upgraded'); }); it('#destroy removes the ripple', () => { @@ -72,7 +69,7 @@ describe('MDCIconButtonToggle', () => { jasmine.clock().tick(1); component.destroy(); jasmine.clock().tick(1); - expect(root.classList.contains('mdc-ripple-upgraded')).toBe(false); + expect(root).not.toHaveClass('mdc-ripple-upgraded'); }); } @@ -95,14 +92,14 @@ describe('MDCIconButtonToggle', () => { it('#adapter.addClass adds a class to the root element', () => { const {root, component} = setupTest(); (component.getDefaultFoundation() as any).adapter.addClass('foo'); - expect(root.classList.contains('foo')).toBe(true); + expect(root).toHaveClass('foo'); }); it('#adapter.removeClass removes a class from the root element', () => { const {root, component} = setupTest(); root.classList.add('foo'); (component.getDefaultFoundation() as any).adapter.removeClass('foo'); - expect(root.classList.contains('foo')).toBe(false); + expect(root).not.toHaveClass('foo'); }); it('#adapter.setAttr sets an attribute on the root element', () => { @@ -126,13 +123,13 @@ describe('MDCIconButtonToggle', () => { it('assert keydown does not trigger ripple', () => { const {root} = setupTest(); emitEvent(root, 'keydown'); - expect(root.classList.contains(cssClasses.FG_ACTIVATION)).toBe(false); + expect(root).not.toHaveClass(cssClasses.FG_ACTIVATION); }); it('assert keyup does not trigger ripple', () => { const {root} = setupTest(); emitEvent(root, 'keyup'); - expect(root.classList.contains(cssClasses.FG_ACTIVATION)).toBe(false); + expect(root).not.toHaveClass(cssClasses.FG_ACTIVATION); }); it('click handler is added to root element', () => { diff --git a/packages/mdc-icon-button/test/mdc-icon-button.scss.test.ts b/packages/mdc-icon-button/test/mdc-icon-button.scss.test.ts index 842d5cdabd7..7ba55fafa12 100644 --- a/packages/mdc-icon-button/test/mdc-icon-button.scss.test.ts +++ b/packages/mdc-icon-button/test/mdc-icon-button.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-icon-button.scss', () => { diff --git a/packages/mdc-image-list/_mixins.scss b/packages/mdc-image-list/_mixins.scss index b6a0f68c2a9..5de6dc0aca1 100644 --- a/packages/mdc-image-list/_mixins.scss +++ b/packages/mdc-image-list/_mixins.scss @@ -18,6 +18,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + @use 'sass:math'; @use '@material/feature-targeting/feature-targeting'; @use '@material/shape/mixins' as shape-mixins; diff --git a/packages/mdc-image-list/mdc-image-list.import.scss b/packages/mdc-image-list/mdc-image-list.import.scss deleted file mode 100644 index ba4839aa69d..00000000000 --- a/packages/mdc-image-list/mdc-image-list.import.scss +++ /dev/null @@ -1,19 +0,0 @@ -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; -@forward "@material/rtl/variables" as mdc-rtl-*; -@forward "@material/shape/variables" as mdc-shape-*; -@forward "@material/theme/variables" as mdc-theme-*; -@forward "@material/typography/variables" as mdc-typography-*; -@forward "variables" as mdc-image-list-*; -@forward "@material/rtl/mixins" as mdc-* hide mdc-property-, mdc-reflexive, mdc-reflexive-box, mdc-reflexive-position, mdc-reflexive-property; -@forward "@material/rtl/mixins" as mdc-rtl-* hide mdc-rtl-rtl; -@forward "@material/shape/mixins" as mdc-shape-*; -@forward "@material/theme/mixins" as mdc-theme-*; -@forward "@material/typography/mixins" as mdc-* hide mdc-base, mdc-baseline-bottom, mdc-baseline-strut-, mdc-baseline-top, mdc-core-styles, mdc-overflow-ellipsis; -@forward "@material/typography/mixins" as mdc-typography-* hide mdc-typography-typography; -@forward "mixins" as mdc-image-list-*; -@forward "@material/feature-targeting/functions" as mdc-feature-*; -@forward "@material/shape/functions" as mdc-shape-*; -@forward "@material/theme/functions" as mdc-theme-*; -@forward "@material/typography/functions" as mdc-typography-*; -@forward "mdc-image-list"; diff --git a/packages/mdc-image-list/package.json b/packages/mdc-image-list/package.json index 322453fc495..af71c927074 100644 --- a/packages/mdc-image-list/package.json +++ b/packages/mdc-image-list/package.json @@ -1,6 +1,6 @@ { "name": "@material/image-list", - "version": "12.0.0", + "version": "14.0.0", "description": "The Material Components for the web image list component", "license": "MIT", "repository": { @@ -15,10 +15,10 @@ ], "sideEffects": false, "dependencies": { - "@material/feature-targeting": "^12.0.0", - "@material/shape": "^12.0.0", - "@material/theme": "^12.0.0", - "@material/typography": "^12.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/shape": "^14.0.0", + "@material/theme": "^14.0.0", + "@material/typography": "^14.0.0", "tslib": "^2.1.0" }, "publishConfig": { diff --git a/packages/mdc-image-list/test/mdc-image-list.scss.test.ts b/packages/mdc-image-list/test/mdc-image-list.scss.test.ts index 5ca8b95b0bc..d44ad330b60 100644 --- a/packages/mdc-image-list/test/mdc-image-list.scss.test.ts +++ b/packages/mdc-image-list/test/mdc-image-list.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-image-list.scss', () => { diff --git a/packages/mdc-layout-grid/_mixins.scss b/packages/mdc-layout-grid/_mixins.scss index 794feadf8fc..7a492c68b40 100644 --- a/packages/mdc-layout-grid/_mixins.scss +++ b/packages/mdc-layout-grid/_mixins.scss @@ -89,7 +89,6 @@ } width: calc(#{$percent} - #{$gutter}); - // stylelint-disable-next-line declaration-block-no-duplicate-properties width: calc(#{$percent} - var(--mdc-layout-grid-gutter-#{$size}, #{$gutter})); @supports (display: grid) { @@ -107,7 +106,6 @@ box-sizing: border-box; margin: 0 auto; padding: $margin; - // stylelint-disable-next-line declaration-block-no-duplicate-properties padding: var(--mdc-layout-grid-margin-#{$size}, #{$margin}); @if $max-width { @@ -124,14 +122,12 @@ flex-flow: row wrap; align-items: stretch; margin: math.div(-$gutter, 2); - // stylelint-disable-next-line declaration-block-no-duplicate-properties margin: calc(var(--mdc-layout-grid-gutter-#{$size}, #{$gutter}) / 2 * -1); @supports (display: grid) { display: grid; margin: 0; grid-gap: $gutter; - // stylelint-disable-next-line declaration-block-no-duplicate-properties grid-gap: var(--mdc-layout-grid-gutter-#{$size}, $gutter); grid-template-columns: repeat( map.get(variables.$columns, $size), @@ -149,7 +145,6 @@ box-sizing: border-box; margin: math.div($gutter, 2); - // stylelint-disable-next-line declaration-block-no-duplicate-properties margin: calc(var(--mdc-layout-grid-gutter-#{$size}, #{$gutter}) / 2); @supports (display: grid) { @@ -198,7 +193,6 @@ width: $column-width * $columnCount + $gutter * $gutter-number + $margin * $margin-number; - // stylelint-disable-next-line declaration-block-no-duplicate-properties width: calc( var(--mdc-layout-grid-column-width-#{$size}, #{$column-width}) * #{$columnCount} + var(--mdc-layout-grid-gutter-#{$size}, #{$gutter}) * #{$gutter-number} + diff --git a/packages/mdc-layout-grid/mdc-layout-grid.scss b/packages/mdc-layout-grid/mdc-layout-grid.scss index 69295812bf8..4403cc6371c 100644 --- a/packages/mdc-layout-grid/mdc-layout-grid.scss +++ b/packages/mdc-layout-grid/mdc-layout-grid.scss @@ -18,6 +18,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + @use 'sass:list'; @use 'sass:map'; @use './variables'; @@ -74,12 +77,10 @@ @for $span from 1 through map.get(variables.$columns, $upper-breakpoint) { // Span classes. - // stylelint-disable max-nesting-depth @at-root .mdc-layout-grid__cell--span-#{$span}, .mdc-layout-grid__cell--span-#{$span}-#{$size} { @include mixins.cell-span_($size, $span, $gutter); } - // stylelint-enable max-nesting-depth } } } diff --git a/packages/mdc-layout-grid/package.json b/packages/mdc-layout-grid/package.json index 69c9be1237f..583b5303248 100644 --- a/packages/mdc-layout-grid/package.json +++ b/packages/mdc-layout-grid/package.json @@ -1,6 +1,6 @@ { "name": "@material/layout-grid", - "version": "12.0.0", + "version": "14.0.0", "description": "The Material Components for the web layout grid component", "license": "MIT", "keywords": [ diff --git a/packages/mdc-line-ripple/README.md b/packages/mdc-line-ripple/README.md index 85bfb8b751b..365cc28aa13 100644 --- a/packages/mdc-line-ripple/README.md +++ b/packages/mdc-line-ripple/README.md @@ -86,8 +86,8 @@ Method Signature | Description `removeClass(className: string) => void` | Removes a class from the root element. `hasClass(className: string) => boolean` | Determines whether the root element has the given CSS class name. `setStyle(propertyName: string, value: string) => void` | Sets the style property with `propertyName` to `value` on the root element. -`registerEventHandler(evtType: EventType, handler: EventListener) => void` | Registers an event listener on the root element for a given event. -`deregisterEventHandler(evtType: EventType, handler: EventListener) => void` | Deregisters an event listener on the root element for a given event. +`registerEventHandler(eventType: EventType, handler: EventListener) => void` | Registers an event listener on the root element for a given event. +`deregisterEventHandler(eventType: EventType, handler: EventListener) => void` | Deregisters an event listener on the root element for a given event. ### `MDCLineRippleFoundation` @@ -96,4 +96,4 @@ Method Signature | Description `activate() => void` | Activates the line ripple. `deactivate() => void` | Deactivates the line ripple. `setRippleCenter(xCoordinate: number) => void` | Sets the center of the ripple to the `xCoordinate` given. -`handleTransitionEnd(evt: Event) => void` | Handles a `transitionend` event. +`handleTransitionEnd(event: Event) => void` | Handles a `transitionend` event. diff --git a/packages/mdc-line-ripple/_line-ripple-theme.scss b/packages/mdc-line-ripple/_line-ripple-theme.scss new file mode 100644 index 00000000000..c4597f169ed --- /dev/null +++ b/packages/mdc-line-ripple/_line-ripple-theme.scss @@ -0,0 +1,80 @@ +// +// Copyright 2023 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use '@material/feature-targeting/feature-targeting'; +@use '@material/theme/theme'; +@use './functions'; + +@mixin theme-styles($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + $feat-animation: feature-targeting.create-target($query, animation); + + // postcss-bem-linter: define line-ripple + .mdc-line-ripple { + @include feature-targeting.targets($feat-structure) { + @include height(1px); + @include active-height(2px); + } + + @include feature-targeting.targets($feat-animation) { + &::after { + transition: functions.transition-value(transform), + functions.transition-value(opacity); + } + } + } +} + +@mixin height($height) { + &::before { + @include theme.property(border-bottom-width, $height); + } +} + +@mixin active-height($height) { + &::after { + @include theme.property(border-bottom-width, $height); + } +} + +@mixin active-color($color, $query: feature-targeting.all()) { + $feat-color: feature-targeting.create-target($query, color); + + @include feature-targeting.targets($feat-color) { + &::after { + @include theme.property(border-bottom-color, $color); + } + } +} + +@mixin inactive-color($color, $query: feature-targeting.all()) { + $feat-color: feature-targeting.create-target($query, color); + + @include feature-targeting.targets($feat-color) { + &::before { + @include theme.property(border-bottom-color, $color); + } + } +} diff --git a/packages/mdc-line-ripple/_line-ripple.scss b/packages/mdc-line-ripple/_line-ripple.scss new file mode 100644 index 00000000000..b6f71bfc30e --- /dev/null +++ b/packages/mdc-line-ripple/_line-ripple.scss @@ -0,0 +1,78 @@ +// +// Copyright 2018 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use '@material/feature-targeting/feature-targeting'; +@use '@material/theme/theme'; +@use './functions'; +@use './line-ripple-theme'; + +// Public + +@mixin core-styles($query: feature-targeting.all()) { + @include static-styles($query); + @include line-ripple-theme.theme-styles($query); +} + +@mixin static-styles($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + // postcss-bem-linter: define line-ripple + .mdc-line-ripple { + @include feature-targeting.targets($feat-structure) { + &::before, + &::after { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + border-bottom-style: solid; + content: ''; + } + + &::before { + z-index: 1; + } + + &::after { + transform: scaleX(0); + opacity: 0; + z-index: 2; + } + } + } + + .mdc-line-ripple--active::after { + @include feature-targeting.targets($feat-structure) { + transform: scaleX(1); + opacity: 1; + } + } + + .mdc-line-ripple--deactivating::after { + @include feature-targeting.targets($feat-structure) { + opacity: 0; + } + } +} diff --git a/packages/mdc-line-ripple/_mixins.scss b/packages/mdc-line-ripple/_mixins.scss index 29f60d1b6a4..c431df73f61 100644 --- a/packages/mdc-line-ripple/_mixins.scss +++ b/packages/mdc-line-ripple/_mixins.scss @@ -20,83 +20,7 @@ // THE SOFTWARE. // -// Selector '.mdc-*' should only be used in this project. -// stylelint-disable selector-class-pattern - -@use '@material/feature-targeting/feature-targeting'; -@use '@material/theme/theme'; -@use './functions'; - -// Public - -@mixin core-styles($query: feature-targeting.all()) { - $feat-structure: feature-targeting.create-target($query, structure); - $feat-animation: feature-targeting.create-target($query, animation); - - // postcss-bem-linter: define line-ripple - .mdc-line-ripple { - @include feature-targeting.targets($feat-structure) { - &::before, - &::after { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - border-bottom-style: solid; - content: ''; - } - - &::before { - border-bottom-width: 1px; - z-index: 1; - } - - &::after { - transform: scaleX(0); - border-bottom-width: 2px; - opacity: 0; - z-index: 2; - } - } - - @include feature-targeting.targets($feat-animation) { - &::after { - transition: functions.transition-value(transform), - functions.transition-value(opacity); - } - } - } - - .mdc-line-ripple--active::after { - @include feature-targeting.targets($feat-structure) { - transform: scaleX(1); - opacity: 1; - } - } - - .mdc-line-ripple--deactivating::after { - @include feature-targeting.targets($feat-structure) { - opacity: 0; - } - } -} - -@mixin active-color($color, $query: feature-targeting.all()) { - $feat-color: feature-targeting.create-target($query, color); - - @include feature-targeting.targets($feat-color) { - &::after { - @include theme.property(border-bottom-color, $color); - } - } -} - -@mixin inactive-color($color, $query: feature-targeting.all()) { - $feat-color: feature-targeting.create-target($query, color); - - @include feature-targeting.targets($feat-color) { - &::before { - @include theme.property(border-bottom-color, $color); - } - } -} +/// @deprecated Import `_line-ripple.scss` or `_line-ripple-theme.scss` instead. +@forward './line-ripple' show core-styles; +@forward './line-ripple-theme' show height, active-height, active-color, + inactive-color; diff --git a/packages/mdc-line-ripple/adapter.ts b/packages/mdc-line-ripple/adapter.ts index 80677c6bda7..2aeaf635dc8 100644 --- a/packages/mdc-line-ripple/adapter.ts +++ b/packages/mdc-line-ripple/adapter.ts @@ -51,10 +51,12 @@ export interface MDCLineRippleAdapter { /** * Registers an event listener on the line ripple element for a given event. */ - registerEventHandler(evtType: K, handler: SpecificEventListener): void; + registerEventHandler( + eventType: K, handler: SpecificEventListener): void; /** * Deregisters an event listener on the line ripple element for a given event. */ - deregisterEventHandler(evtType: K, handler: SpecificEventListener): void; + deregisterEventHandler( + eventType: K, handler: SpecificEventListener): void; } diff --git a/packages/mdc-line-ripple/component.ts b/packages/mdc-line-ripple/component.ts index 1ba0248e003..dd85b68ed5f 100644 --- a/packages/mdc-line-ripple/component.ts +++ b/packages/mdc-line-ripple/component.ts @@ -22,13 +22,17 @@ */ import {MDCComponent} from '@material/base/component'; + import {MDCLineRippleAdapter} from './adapter'; import {MDCLineRippleFoundation} from './foundation'; -export type MDCLineRippleFactory = (el: Element, foundation?: MDCLineRippleFoundation) => MDCLineRipple; +/** MDC Line Ripple Factory */ +export type MDCLineRippleFactory = + (el: HTMLElement, foundation?: MDCLineRippleFoundation) => MDCLineRipple; +/** MDC Line Ripple */ export class MDCLineRipple extends MDCComponent { - static attachTo(root: Element): MDCLineRipple { + static override attachTo(root: HTMLElement): MDCLineRipple { return new MDCLineRipple(root); } @@ -54,17 +58,28 @@ export class MDCLineRipple extends MDCComponent { this.foundation.setRippleCenter(xCoordinate); } - getDefaultFoundation() { - // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. - // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + override getDefaultFoundation() { + // DO NOT INLINE this variable. For backward compatibility, foundations take + // a Partial. To ensure we don't accidentally omit any + // methods, we need a separate, strongly typed adapter variable. // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface. const adapter: MDCLineRippleAdapter = { - addClass: (className) => this.root.classList.add(className), - removeClass: (className) => this.root.classList.remove(className), + addClass: (className) => { + this.root.classList.add(className); + }, + removeClass: (className) => { + this.root.classList.remove(className); + }, hasClass: (className) => this.root.classList.contains(className), - setStyle: (propertyName, value) => (this.root as HTMLElement).style.setProperty(propertyName, value), - registerEventHandler: (evtType, handler) => this.listen(evtType, handler), - deregisterEventHandler: (evtType, handler) => this.unlisten(evtType, handler), + setStyle: (propertyName, value) => { + this.root.style.setProperty(propertyName, value); + }, + registerEventHandler: (eventType, handler) => { + this.listen(eventType, handler); + }, + deregisterEventHandler: (eventType, handler) => { + this.unlisten(eventType, handler); + }, }; // tslint:enable:object-literal-sort-keys return new MDCLineRippleFoundation(adapter); diff --git a/packages/mdc-line-ripple/foundation.ts b/packages/mdc-line-ripple/foundation.ts index 532659076d6..1e4c73d1043 100644 --- a/packages/mdc-line-ripple/foundation.ts +++ b/packages/mdc-line-ripple/foundation.ts @@ -23,18 +23,22 @@ import {MDCFoundation} from '@material/base/foundation'; import {SpecificEventListener} from '@material/base/types'; + import {MDCLineRippleAdapter} from './adapter'; import {cssClasses} from './constants'; -export class MDCLineRippleFoundation extends MDCFoundation { - static get cssClasses() { +/** MDC Line Ripple Foundation */ +export class MDCLineRippleFoundation extends + MDCFoundation { + static override get cssClasses() { return cssClasses; } /** - * See {@link MDCLineRippleAdapter} for typing information on parameters and return types. + * See {@link MDCLineRippleAdapter} for typing information on parameters and + * return types. */ - static get defaultAdapter(): MDCLineRippleAdapter { + static override get defaultAdapter(): MDCLineRippleAdapter { // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface. return { addClass: () => undefined, @@ -52,17 +56,17 @@ export class MDCLineRippleFoundation extends MDCFoundation constructor(adapter?: Partial) { super({...MDCLineRippleFoundation.defaultAdapter, ...adapter}); - this.transitionEndHandler = (evt) => { - this.handleTransitionEnd(evt); + this.transitionEndHandler = (event) => { + this.handleTransitionEnd(event); }; } - init() { + override init() { this.adapter.registerEventHandler( 'transitionend', this.transitionEndHandler); } - destroy() { + override destroy() { this.adapter.deregisterEventHandler( 'transitionend', this.transitionEndHandler); } @@ -80,13 +84,13 @@ export class MDCLineRippleFoundation extends MDCFoundation this.adapter.addClass(cssClasses.LINE_RIPPLE_DEACTIVATING); } - handleTransitionEnd(evt: TransitionEvent) { + handleTransitionEnd(event: TransitionEvent) { // Wait for the line ripple to be either transparent or opaque // before emitting the animation end event const isDeactivating = this.adapter.hasClass(cssClasses.LINE_RIPPLE_DEACTIVATING); - if (evt.propertyName === 'opacity') { + if (event.propertyName === 'opacity') { if (isDeactivating) { this.adapter.removeClass(cssClasses.LINE_RIPPLE_ACTIVE); this.adapter.removeClass(cssClasses.LINE_RIPPLE_DEACTIVATING); diff --git a/packages/mdc-line-ripple/mdc-line-ripple.import.scss b/packages/mdc-line-ripple/mdc-line-ripple.import.scss index 7259570c475..8e4008ae07c 100644 --- a/packages/mdc-line-ripple/mdc-line-ripple.import.scss +++ b/packages/mdc-line-ripple/mdc-line-ripple.import.scss @@ -1,6 +1,4 @@ @forward "@material/base/mixins" as mdc-base-*; -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; @forward "@material/theme/variables" as mdc-theme-*; @forward "@material/animation/variables" as mdc-animation-*; @forward "@material/theme/mixins" as mdc-theme-*; diff --git a/packages/mdc-line-ripple/package.json b/packages/mdc-line-ripple/package.json index f62367cd1ab..a2f08ae52ce 100644 --- a/packages/mdc-line-ripple/package.json +++ b/packages/mdc-line-ripple/package.json @@ -1,7 +1,7 @@ { "name": "@material/line-ripple", "description": "The Material Components for the web line-ripple component", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "keywords": [ "material components", @@ -21,10 +21,10 @@ "access": "public" }, "dependencies": { - "@material/animation": "^12.0.0", - "@material/base": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/theme": "^12.0.0", + "@material/animation": "^14.0.0", + "@material/base": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/theme": "^14.0.0", "tslib": "^2.1.0" } } diff --git a/packages/mdc-line-ripple/test/component.test.ts b/packages/mdc-line-ripple/test/component.test.ts index cd14b6f0132..f04cfbf072d 100644 --- a/packages/mdc-line-ripple/test/component.test.ts +++ b/packages/mdc-line-ripple/test/component.test.ts @@ -22,17 +22,14 @@ */ import {MDCLineRipple, MDCLineRippleFoundation} from '../../mdc-line-ripple/index'; +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; import {createMockFoundation} from '../../../testing/helpers/foundation'; const getFixture = () => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html` - `; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; + `); }; describe('MDCLineRipple', () => { @@ -50,7 +47,7 @@ describe('MDCLineRipple', () => { it('#adapter.addClass adds a class to the element', () => { const {root, component} = setupTest(); (component.getDefaultFoundation() as any).adapter.addClass('foo'); - expect(root.classList.contains('foo')).toBe(true); + expect(root).toHaveClass('foo'); }); it('#adapter.removeClass removes a class from the element', () => { @@ -58,7 +55,7 @@ describe('MDCLineRipple', () => { root.classList.add('foo'); (component.getDefaultFoundation() as any).adapter.removeClass('foo'); - expect(root.classList.contains('foo')).toBe(false); + expect(root).not.toHaveClass('foo'); }); it('#adapter.hasClass returns true if a class is on the element', () => { const {root, component} = setupTest(); @@ -71,8 +68,7 @@ describe('MDCLineRipple', () => { it('#adapter.setStyle adds a given style property to the element', () => { const {root, component} = setupTest(); - (component.getDefaultFoundation() as any) - .adapter.setStyle('color', 'blue'); + (component.getDefaultFoundation() as any).adapter.setStyle('color', 'blue'); expect(root.getAttribute('style')).toEqual('color: blue;'); }); diff --git a/packages/mdc-line-ripple/test/mdc-line-ripple.scss.test.ts b/packages/mdc-line-ripple/test/mdc-line-ripple.scss.test.ts index 8af3d74b510..81f8588c6b2 100644 --- a/packages/mdc-line-ripple/test/mdc-line-ripple.scss.test.ts +++ b/packages/mdc-line-ripple/test/mdc-line-ripple.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-line-ripple.scss', () => { diff --git a/packages/mdc-linear-progress/_keyframes.scss b/packages/mdc-linear-progress/_keyframes.scss index 102a2fed172..98d1c3f6f35 100644 --- a/packages/mdc-linear-progress/_keyframes.scss +++ b/packages/mdc-linear-progress/_keyframes.scss @@ -19,233 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -@use '@material/theme/theme'; -@use '@material/theme/custom-properties'; -@mixin primary-indeterminate-translate-keyframes_ { - @keyframes mdc-linear-progress-primary-indeterminate-translate { - 0% { - transform: translateX(0); - } - - 20% { - animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819); - transform: translateX(0); - } - - 59.15% { - animation-timing-function: cubic-bezier( - 0.302435, - 0.381352, - 0.55, - 0.956352 - ); - - $primary-half: custom-properties.create( - --mdc-linear-progress-primary-half, - 83.67142% - ); - @include apply-translate_($primary-half); - } - - 100% { - $primary-full: custom-properties.create( - --mdc-linear-progress-primary-full, - 200.611057% - ); - @include apply-translate_($primary-full); - } - } -} - -@mixin primary-indeterminate-scale-keyframes_ { - @keyframes mdc-linear-progress-primary-indeterminate-scale { - 0% { - transform: scaleX(0.08); - } - - 36.65% { - animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1); - transform: scaleX(0.08); - } - - 69.15% { - animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1); - transform: scaleX(0.661479); - } - - 100% { - transform: scaleX(0.08); - } - } -} - -@mixin secondary-indeterminate-translate-keyframes_ { - @keyframes mdc-linear-progress-secondary-indeterminate-translate { - 0% { - animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685); - transform: translateX(0); - } - - 25% { - animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712); - $secondary-quarter: custom-properties.create( - --mdc-linear-progress-secondary-quarter, - 37.651913% - ); - @include apply-translate_($secondary-quarter); - } - - 48.35% { - animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026); - $secondary-half: custom-properties.create( - --mdc-linear-progress-secondary-half, - 84.386165% - ); - @include apply-translate_($secondary-half); - } - - 100% { - $secondary-full: custom-properties.create( - --mdc-linear-progress-secondary-full, - 160.277782% - ); - @include apply-translate_($secondary-full); - } - } -} - -@mixin secondary-indeterminate-scale-keyframes_ { - @keyframes mdc-linear-progress-secondary-indeterminate-scale { - 0% { - animation-timing-function: cubic-bezier( - 0.205028, - 0.057051, - 0.57661, - 0.453971 - ); - transform: scaleX(0.08); - } - - 19.15% { - animation-timing-function: cubic-bezier( - 0.152313, - 0.196432, - 0.648374, - 1.004315 - ); - transform: scaleX(0.457104); - } - - 44.15% { - animation-timing-function: cubic-bezier( - 0.257759, - -0.003163, - 0.211762, - 1.38179 - ); - transform: scaleX(0.72796); - } - - 100% { - transform: scaleX(0.08); - } - } -} - -@mixin buffering-keyframes_ { - @keyframes mdc-linear-progress-buffering { - from { - // Normally the buffer dots start from the left and overflow to the right. - // We rotate by 180deg so that the buffer dots start on the right when - // in non-reversed mode and overflow to the left. - transform: rotate(180deg) translateX(-10px); - } - } -} - -@mixin primary-indeterminate-translate-reverse-keyframes_ { - @keyframes mdc-linear-progress-primary-indeterminate-translate-reverse { - 0% { - transform: translateX(0); - } - - 20% { - animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819); - transform: translateX(0); - } - - 59.15% { - animation-timing-function: cubic-bezier( - 0.302435, - 0.381352, - 0.55, - 0.956352 - ); - $primary-half: custom-properties.create( - --mdc-linear-progress-primary-half-neg, - -83.67142% - ); - @include apply-translate_($primary-half); - } - - 100% { - $primary-full: custom-properties.create( - --mdc-linear-progress-primary-full-neg, - -200.611057% - ); - @include apply-translate_($primary-full); - } - } -} - -@mixin secondary-indeterminate-translate-reverse-keyframes_ { - @keyframes mdc-linear-progress-secondary-indeterminate-translate-reverse { - 0% { - animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685); - transform: translateX(0); - } - - 25% { - animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712); - $secondary-quarter: custom-properties.create( - --mdc-linear-progress-secondary-quarter-neg, - -37.651913% - ); - @include apply-translate_($secondary-quarter); - } - - 48.35% { - animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026); - $secondary-half: custom-properties.create( - --mdc-linear-progress-secondary-half-neg, - -84.386165% - ); - @include apply-translate_($secondary-half); - } - - 100% { - $secondary-full: custom-properties.create( - --mdc-linear-progress-secondary-full-neg, - -160.277782% - ); - @include apply-translate_($secondary-full); - } - } -} - -@mixin buffering-reverse-keyframes_ { - @keyframes mdc-linear-progress-buffering-reverse { - from { - transform: translateX(-10px); - } - } -} - -@mixin apply-translate_($value) { - @include theme.property( - transform, - translateX(value), - $replace: (value: $value) - ); -} +/// @deprecated Import './linear-progress' module instead. +@forward './linear-progress' show exit; diff --git a/packages/mdc-linear-progress/_linear-progress-theme.scss b/packages/mdc-linear-progress/_linear-progress-theme.scss new file mode 100644 index 00000000000..7e021714638 --- /dev/null +++ b/packages/mdc-linear-progress/_linear-progress-theme.scss @@ -0,0 +1,265 @@ +// +// Copyright 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use 'sass:map'; +@use 'sass:math'; +@use 'sass:string'; +@use '@material/dom/dom'; +@use '@material/feature-targeting/feature-targeting'; +@use '@material/rtl/rtl'; +@use '@material/theme/custom-properties'; +@use '@material/theme/theme'; +@use '@material/theme/theme-color'; +@use '@material/theme/keys'; + +$baseline-buffer-color: #e6e6e6 !default; +$height: 4px; + +$custom-property-prefix: 'linear-progress'; + +// TODO(b/254889918): Support all linear progress tokens. +$light-theme: ( + active-indicator-color: theme-color.$primary, + active-indicator-height: 4px, + track-color: $baseline-buffer-color, + track-height: 4px, + track-shape: 0px, +); + +@mixin theme($theme) { + @include theme.validate-theme($light-theme, $theme); + @include keys.declare-custom-properties( + $theme, + $prefix: $custom-property-prefix + ); +} + +@mixin theme-styles($theme, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + @include theme.validate-theme-styles($light-theme, $theme); + + $theme: keys.create-theme-properties( + $theme, + $prefix: $custom-property-prefix + ); + + @include buffering-keyframes(map.get($theme, track-height), $query); + @include bar-color(map.get($theme, active-indicator-color), $query); + @include buffer-color(map.get($theme, track-color), $query); + + .mdc-linear-progress { + @include feature-targeting.targets($feat-structure) { + @include theme.property( + height, + 'max(track-height, bar-height)', + $replace: ( + track-height: map.get($theme, track-height), + bar-height: map.get($theme, active-indicator-height) + ) + ); + + @include dom.ie11-support() { + $track-height: map.get($theme, track-height); + $track-height: if( + custom-properties.is-custom-prop($track-height), + custom-properties.get-fallback($track-height), + $track-height + ); + + $bar-height: map.get($theme, active-indicator-height); + $bar-height: if( + custom-properties.is-custom-prop($bar-height), + custom-properties.get-fallback($bar-height), + $bar-height + ); + + @if ($track-height != null) and + ($bar-height != null) and + (not custom-properties.is-custom-prop-string($track-height)) and + (not custom-properties.is-custom-prop-string($bar-height)) + { + @include theme.property(height, math.max($track-height, $bar-height)); + } + } + } + } + + @include _bar-height(map.get($theme, active-indicator-height), $query); + @include _track-height(map.get($theme, track-height), $query); + @include _track-shape(map.get($theme, track-shape), $query); + + // TODO(b/155129310): Add styles for 4-color linear progress once this variant + // is supported. +} + +@mixin bar-color($color, $query: feature-targeting.all()) { + $feat-color: feature-targeting.create-target($query, color); + + .mdc-linear-progress__bar-inner { + @include feature-targeting.targets($feat-color) { + // Border is used rather than background-color to ensure that the + // bar is visible in Windows High Contrast Mode. + @include theme.property(border-color, $color); + } + } +} + +@mixin buffer-color($color, $query: feature-targeting.all()) { + // We need to escape the '#' character as "%23" for SVG because '#' is a reserved character in URIs. + $concrete-color: $color; + @if custom-properties.is-custom-prop($color) { + $concrete-color: custom-properties.get-fallback($color); + } + $concrete-color-for-svg: str-replace_( + string.unquote('#{$concrete-color}'), + '#', + '%23' + ); + $feat-color: feature-targeting.create-target($query, color); + + .mdc-linear-progress__buffer-dots { + @include feature-targeting.targets($feat-color) { + @include theme.property(background-color, $color); + + @media (forced-colors: active) { + background-color: ButtonBorder; + } + + @include dom.ie11-support() { + background-color: transparent; + // stylelint-disable function-url-quotes -- SVG data URI + // SVG is optimized for data URI (https://codepen.io/tigt/post/optimizing-svgs-in-data-uris) + @include theme.property( + background-image, + url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' preserveAspectRatio='none slice'%3E%3Ccircle cx='1' cy='1' r='1' fill='concrete-color-for-svg'/%3E%3C/svg%3E"), + $replace: (concrete-color-for-svg: $concrete-color-for-svg) + ); + // stylelint-enable function-url-quotes + } + } + } + + .mdc-linear-progress__buffer-bar { + @include feature-targeting.targets($feat-color) { + @include theme.property(background-color, $color); + } + } +} + +/// @deprecated Use Theming API instead. +@mixin bar-and-track-height($height, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + .mdc-linear-progress { + @include feature-targeting.targets($feat-structure) { + @include theme.property(height, $height); + } + } + + @include _bar-height($height, $query); + @include _track-height($height, $query); +} + +@mixin buffering-keyframes($track-height, $query: feature-targeting.all()) { + $feat-animation: feature-targeting.create-target($query, animation); + + @include feature-targeting.targets($feat-animation) { + @keyframes mdc-linear-progress-buffering { + from { + // Normally the buffer dots start from the left and overflow to the right. + // We rotate by 180deg so that the buffer dots start on the right when + // in non-reversed mode and overflow to the left. + @include rtl.ignore-next-line(); + @include theme.property( + transform, + 'rotate(180deg) translateX(calc(track-height * -2.5))', + $replace: (track-height: $track-height) + ); + } + } + } +} + +// Based on https://css-tricks.com/snippets/sass/str-replace-function/ +@function str-replace_($string, $search, $replace) { + $index: string.index($string, $search); + + @if $index { + $head: string.slice($string, 1, $index - 1); + $tail: str-replace_( + string.slice($string, $index + string.length($search)), + $search, + $replace + ); + + @return $head + $replace + $tail; + } + + @return $string; +} + +@mixin _bar-height($height, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + @include feature-targeting.targets($feat-structure) { + .mdc-linear-progress__bar { + @include theme.property(height, $height); + } + + .mdc-linear-progress__bar-inner { + @include theme.property(border-top-width, $height); + } + } +} + +@mixin _track-height($height, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + @include feature-targeting.targets($feat-structure) { + .mdc-linear-progress__buffer { + @include theme.property(height, $height); + } + + .mdc-linear-progress__buffer-dots { + @include dom.ie11-support() { + @include theme.property( + background-size, + 10px height, + $replace: (height: $height) + ); + } + } + } +} + +@mixin _track-shape($shape, $query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + + @include feature-targeting.targets($feat-structure) { + .mdc-linear-progress__buffer { + @include theme.property(border-radius, $shape); + } + } +} diff --git a/packages/mdc-linear-progress/_linear-progress.scss b/packages/mdc-linear-progress/_linear-progress.scss new file mode 100644 index 00000000000..b47fdc6e37a --- /dev/null +++ b/packages/mdc-linear-progress/_linear-progress.scss @@ -0,0 +1,540 @@ +// +// Copyright 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 +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// stylelint-disable selector-class-pattern -- +// Selector '.mdc-*' should only be used in this project. + +@use '@material/animation/functions' as animation-functions; +@use '@material/dom/dom'; +@use '@material/feature-targeting/feature-targeting'; +@use '@material/rtl/rtl'; +@use '@material/theme/custom-properties'; +@use '@material/theme/theme'; +@use './linear-progress-theme'; + +/// @deprecated Use static-styles() and theme-styles() instead. +@mixin core-styles($query: feature-targeting.all()) { + @include linear-progress-theme.bar-color(primary, $query: $query); + @include linear-progress-theme.buffer-color( + linear-progress-theme.$baseline-buffer-color, + $query: $query + ); + @include linear-progress-theme.bar-and-track-height( + linear-progress-theme.$height, + $query: $query + ); + @include linear-progress-theme.buffering-keyframes( + linear-progress-theme.$height, + $query: $query + ); + @include static-styles($query); +} + +@mixin static-styles($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + $feat-animation: feature-targeting.create-target($query, animation); + + @include feature-targeting.targets($feat-animation) { + @include primary-indeterminate-translate-keyframes_; + @include primary-indeterminate-scale-keyframes_; + @include secondary-indeterminate-translate-keyframes_; + @include secondary-indeterminate-scale-keyframes_; + @include primary-indeterminate-translate-reverse-keyframes_; + @include secondary-indeterminate-translate-reverse-keyframes_; + @include buffering-reverse-keyframes_; + } + + .mdc-linear-progress { + @include feature-targeting.targets($feat-structure) { + position: relative; + width: 100%; + transform: translateZ(0); + // Create a border around the bar in Windows High Contrast Mode. + outline: 1px solid transparent; + overflow-x: hidden; + + @include dom.forced-colors-mode($exclude-ie11: true) { + outline-color: CanvasText; + } + } + + @include feature-targeting.targets($feat-animation) { + transition: animation-functions.exit-temporary(opacity, 250ms); + } + + &__bar { + @include feature-targeting.targets($feat-structure) { + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; // Vertically center + width: 100%; + animation: none; + transform-origin: top left; + } + + @include feature-targeting.targets($feat-animation) { + transition: animation-functions.exit-temporary(transform, 250ms); + } + } + + &__bar-inner { + @include feature-targeting.targets($feat-structure) { + display: inline-block; + position: absolute; + width: 100%; + animation: none; + // border-top is used rather than background-color to ensure that the + // bar is visible in Windows High Contrast Mode. + border-top-style: solid; + } + } + + &__buffer { + @include feature-targeting.targets($feat-structure) { + display: flex; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; // Vertically center + width: 100%; + overflow: hidden; + } + } + + &__buffer-dots { + @include feature-targeting.targets($feat-structure) { + background-repeat: repeat-x; + flex: auto; + @include rtl.ignore-next-line(); + transform: rotate(180deg); + + // Use SVG mask for non-IE11 browsers + // SVG is optimized for data URI (https://codepen.io/tigt/post/optimizing-svgs-in-data-uris) + $svg: "data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' preserveAspectRatio='xMinYMin slice'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/svg%3E"; + -webkit-mask-image: url($svg); + mask-image: url($svg); + } + + @include feature-targeting.targets($feat-animation) { + // stylelint-disable-next-line no-unknown-animations -- + // animation is in _linear-progress-theme.scss + animation: mdc-linear-progress-buffering 250ms infinite linear; + } + } + + &__buffer-bar { + @include feature-targeting.targets($feat-structure) { + flex: 0 1 100%; + } + + @include feature-targeting.targets($feat-animation) { + transition: animation-functions.exit-temporary(flex-basis, 250ms); + } + } + + &__primary-bar { + @include feature-targeting.targets($feat-structure) { + transform: scaleX(0); + } + } + + &__secondary-bar { + @include feature-targeting.targets($feat-structure) { + display: none; + } + } + + @include indeterminate_($query: $query); + + @include rtl.rtl() { + // The rtl() mixin does not account for nested `dir` attributes. Set + // `dir` attribute on root to take highest priority. + &:not([dir='ltr']) { + @include _rtl-styles($query: $query); + } + } + + &--closed { + @include feature-targeting.targets($feat-structure) { + opacity: 0; + } + } + + &--closed-animation-off { + .mdc-linear-progress__buffer-dots { + @include feature-targeting.targets($feat-animation) { + animation: none; + } + } + + &.mdc-linear-progress--indeterminate { + .mdc-linear-progress__bar, + .mdc-linear-progress__bar .mdc-linear-progress__bar-inner { + @include feature-targeting.targets($feat-animation) { + animation: none; + } + } + } + } + } +} + +// Private mixins. + +@mixin indeterminate_($query: feature-targeting.all()) { + $feat-structure: feature-targeting.create-target($query, structure); + $feat-animation: feature-targeting.create-target($query, animation); + + &--indeterminate { + .mdc-linear-progress__bar { + @include feature-targeting.targets($feat-structure) { + transition: none; + } + } + + .mdc-linear-progress__primary-bar { + @include feature-targeting.targets($feat-structure) { + left: -145.166611%; + } + } + + .mdc-linear-progress__secondary-bar { + @include feature-targeting.targets($feat-structure) { + left: -54.888891%; + display: block; + } + } + + &.mdc-linear-progress--animation-ready { + .mdc-linear-progress__primary-bar { + @include feature-targeting.targets($feat-animation) { + animation: mdc-linear-progress-primary-indeterminate-translate 2s + infinite linear; + } + + > .mdc-linear-progress__bar-inner { + @include feature-targeting.targets($feat-animation) { + animation: mdc-linear-progress-primary-indeterminate-scale 2s + infinite linear; + } + } + } + + .mdc-linear-progress__secondary-bar { + @include feature-targeting.targets($feat-animation) { + animation: mdc-linear-progress-secondary-indeterminate-translate 2s + infinite linear; + } + + > .mdc-linear-progress__bar-inner { + @include feature-targeting.targets($feat-animation) { + animation: mdc-linear-progress-secondary-indeterminate-scale 2s + infinite linear; + } + } + } + } + } +} + +@mixin _rtl-styles($query: $query) { + $feat-structure: feature-targeting.create-target($query, structure); + $feat-animation: feature-targeting.create-target($query, animation); + + .mdc-linear-progress__bar { + @include feature-targeting.targets($feat-structure) { + @include rtl.ignore-next-line(); + right: 0; + @include rtl.ignore-next-line(); + -webkit-transform-origin: center right; + @include rtl.ignore-next-line(); + transform-origin: center right; + } + } + + &.mdc-linear-progress--animation-ready { + .mdc-linear-progress__primary-bar { + @include feature-targeting.targets($feat-animation) { + animation-name: mdc-linear-progress-primary-indeterminate-translate-reverse; + } + } + + .mdc-linear-progress__secondary-bar { + @include feature-targeting.targets($feat-animation) { + animation-name: mdc-linear-progress-secondary-indeterminate-translate-reverse; + } + } + } + + .mdc-linear-progress__buffer-dots { + @include feature-targeting.targets($feat-animation) { + animation: mdc-linear-progress-buffering-reverse 250ms infinite linear; + } + + @include feature-targeting.targets($feat-structure) { + @include rtl.ignore-next-line(); + transform: rotate(0); + } + } + + &.mdc-linear-progress--indeterminate { + .mdc-linear-progress__primary-bar { + @include feature-targeting.targets($feat-structure) { + @include rtl.ignore-next-line(); + right: -145.166611%; + @include rtl.ignore-next-line(); + left: auto; + } + } + + .mdc-linear-progress__secondary-bar { + @include feature-targeting.targets($feat-structure) { + @include rtl.ignore-next-line(); + right: -54.888891%; + @include rtl.ignore-next-line(); + left: auto; + } + } + } +} + +// Keyframes. + +@mixin primary-indeterminate-translate-keyframes_ { + @keyframes mdc-linear-progress-primary-indeterminate-translate { + 0% { + transform: translateX(0); + } + + 20% { + animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819); + transform: translateX(0); + } + + 59.15% { + animation-timing-function: cubic-bezier( + 0.302435, + 0.381352, + 0.55, + 0.956352 + ); + + $primary-half: custom-properties.create( + --mdc-linear-progress-primary-half, + 83.67142% + ); + @include apply-translate_($primary-half); + } + + 100% { + $primary-full: custom-properties.create( + --mdc-linear-progress-primary-full, + 200.611057% + ); + @include apply-translate_($primary-full); + } + } +} + +@mixin primary-indeterminate-scale-keyframes_ { + @keyframes mdc-linear-progress-primary-indeterminate-scale { + 0% { + transform: scaleX(0.08); + } + + 36.65% { + animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1); + transform: scaleX(0.08); + } + + 69.15% { + animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1); + transform: scaleX(0.661479); + } + + 100% { + transform: scaleX(0.08); + } + } +} + +@mixin secondary-indeterminate-translate-keyframes_ { + @keyframes mdc-linear-progress-secondary-indeterminate-translate { + 0% { + animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685); + transform: translateX(0); + } + + 25% { + animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712); + $secondary-quarter: custom-properties.create( + --mdc-linear-progress-secondary-quarter, + 37.651913% + ); + @include apply-translate_($secondary-quarter); + } + + 48.35% { + animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026); + $secondary-half: custom-properties.create( + --mdc-linear-progress-secondary-half, + 84.386165% + ); + @include apply-translate_($secondary-half); + } + + 100% { + $secondary-full: custom-properties.create( + --mdc-linear-progress-secondary-full, + 160.277782% + ); + @include apply-translate_($secondary-full); + } + } +} + +@mixin secondary-indeterminate-scale-keyframes_ { + @keyframes mdc-linear-progress-secondary-indeterminate-scale { + 0% { + animation-timing-function: cubic-bezier( + 0.205028, + 0.057051, + 0.57661, + 0.453971 + ); + transform: scaleX(0.08); + } + + 19.15% { + animation-timing-function: cubic-bezier( + 0.152313, + 0.196432, + 0.648374, + 1.004315 + ); + transform: scaleX(0.457104); + } + + 44.15% { + animation-timing-function: cubic-bezier( + 0.257759, + -0.003163, + 0.211762, + 1.38179 + ); + transform: scaleX(0.72796); + } + + 100% { + transform: scaleX(0.08); + } + } +} + +@mixin primary-indeterminate-translate-reverse-keyframes_ { + @keyframes mdc-linear-progress-primary-indeterminate-translate-reverse { + 0% { + transform: translateX(0); + } + + 20% { + animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819); + transform: translateX(0); + } + + 59.15% { + animation-timing-function: cubic-bezier( + 0.302435, + 0.381352, + 0.55, + 0.956352 + ); + $primary-half: custom-properties.create( + --mdc-linear-progress-primary-half-neg, + -83.67142% + ); + @include apply-translate_($primary-half); + } + + 100% { + $primary-full: custom-properties.create( + --mdc-linear-progress-primary-full-neg, + -200.611057% + ); + @include apply-translate_($primary-full); + } + } +} + +@mixin secondary-indeterminate-translate-reverse-keyframes_ { + @keyframes mdc-linear-progress-secondary-indeterminate-translate-reverse { + 0% { + animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685); + transform: translateX(0); + } + + 25% { + animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712); + $secondary-quarter: custom-properties.create( + --mdc-linear-progress-secondary-quarter-neg, + -37.651913% + ); + @include apply-translate_($secondary-quarter); + } + + 48.35% { + animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026); + $secondary-half: custom-properties.create( + --mdc-linear-progress-secondary-half-neg, + -84.386165% + ); + @include apply-translate_($secondary-half); + } + + 100% { + $secondary-full: custom-properties.create( + --mdc-linear-progress-secondary-full-neg, + -160.277782% + ); + @include apply-translate_($secondary-full); + } + } +} + +@mixin buffering-reverse-keyframes_ { + @keyframes mdc-linear-progress-buffering-reverse { + from { + @include rtl.ignore-next-line(); + transform: translateX(-10px); + } + } +} + +@mixin apply-translate_($value) { + @include theme.property( + transform, + translateX(value), + $replace: (value: $value), + $gss: (noflip: true) + ); +} diff --git a/packages/mdc-linear-progress/_mixins.scss b/packages/mdc-linear-progress/_mixins.scss index 20fa1c42f55..011ce083af3 100644 --- a/packages/mdc-linear-progress/_mixins.scss +++ b/packages/mdc-linear-progress/_mixins.scss @@ -1,3 +1,4 @@ +// // Copyright 2017 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -17,336 +18,8 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - -// stylelint-disable no-unknown-animations -- -// Animations keyframes are included in `keyframes.scss`. -// stylelint-disable selector-class-pattern -- -// Selector '.mdc-*' should only be used in this project. - -@use 'sass:string'; -@use '@material/animation/functions' as animation-functions; -@use '@material/feature-targeting/feature-targeting'; -@use '@material/rtl/rtl'; -@use '@material/theme/theme'; -@use '@material/theme/theme-color'; -@use './variables'; -@use './keyframes'; - -// -// Public -// - -@mixin core-styles($query: feature-targeting.all()) { - $feat-structure: feature-targeting.create-target($query, structure); - $feat-animation: feature-targeting.create-target($query, animation); - - @include feature-targeting.targets($feat-animation) { - @include keyframes.primary-indeterminate-translate-keyframes_; - @include keyframes.primary-indeterminate-scale-keyframes_; - @include keyframes.secondary-indeterminate-translate-keyframes_; - @include keyframes.secondary-indeterminate-scale-keyframes_; - @include keyframes.buffering-keyframes_; - @include keyframes.primary-indeterminate-translate-reverse-keyframes_; - @include keyframes.secondary-indeterminate-translate-reverse-keyframes_; - @include keyframes.buffering-reverse-keyframes_; - } - - .mdc-linear-progress { - @include feature-targeting.targets($feat-structure) { - position: relative; - width: 100%; - height: variables.$height; - transform: translateZ(0); - // Create a border around the bar in Windows High Contrast Mode. - outline: 1px solid transparent; - overflow: hidden; - } - - @include feature-targeting.targets($feat-animation) { - transition: animation-functions.exit-temporary(opacity, 250ms); - } - - &__bar { - @include feature-targeting.targets($feat-structure) { - position: absolute; - width: 100%; - height: 100%; - animation: none; - transform-origin: top left; - } - - @include feature-targeting.targets($feat-animation) { - transition: animation-functions.exit-temporary(transform, 250ms); - } - } - - &__bar-inner { - @include feature-targeting.targets($feat-structure) { - display: inline-block; - position: absolute; - width: 100%; - animation: none; - // border-top is used rather than background-color to ensure that the - // bar is visible in Windows High Contrast Mode. - border-top: variables.$height solid; - } - } - - &__buffer { - @include feature-targeting.targets($feat-structure) { - display: flex; - position: absolute; - width: 100%; - height: 100%; - } - } - - &__buffer-dots { - @include feature-targeting.targets($feat-structure) { - background-repeat: repeat-x; - background-size: 10px variables.$height; - flex: auto; - transform: rotate(180deg); - } - - @include feature-targeting.targets($feat-animation) { - animation: mdc-linear-progress-buffering 250ms infinite linear; - } - } - - &__buffer-bar { - @include feature-targeting.targets($feat-structure) { - flex: 0 1 100%; - } - - @include feature-targeting.targets($feat-animation) { - transition: animation-functions.exit-temporary(flex-basis, 250ms); - } - } - - &__primary-bar { - @include feature-targeting.targets($feat-structure) { - transform: scaleX(0); - } - } - - &__secondary-bar { - @include feature-targeting.targets($feat-structure) { - display: none; - } - } - - @include indeterminate_($query: $query); - - @include rtl.rtl() { - // The rtl() mixin does not account for nested `dir` attributes. Set - // `dir` attribute on root to take highest priority. - &:not([dir='ltr']) { - @include _rtl-styles($query: $query); - } - } - - &--closed { - @include feature-targeting.targets($feat-structure) { - opacity: 0; - } - } - - &--closed-animation-off { - .mdc-linear-progress__buffer-dots { - @include feature-targeting.targets($feat-animation) { - animation: none; - } - } - - &.mdc-linear-progress--indeterminate { - .mdc-linear-progress__bar, - .mdc-linear-progress__bar .mdc-linear-progress__bar-inner { - @include feature-targeting.targets($feat-animation) { - animation: none; - } - } - } - } - } - - @at-root { - @include bar-color(primary, $query: $query); - @include buffer-color(variables.$baseline-buffer-color, $query: $query); - } -} - -@mixin bar-color($color, $query: feature-targeting.all()) { - $feat-color: feature-targeting.create-target($query, color); - - .mdc-linear-progress__bar-inner { - @include feature-targeting.targets($feat-color) { - // Border is used rather than background-color to ensure that the - // bar is visible in Windows High Contrast Mode. - @include theme.property(border-color, $color); - } - } -} - -@mixin buffer-color($color, $query: feature-targeting.all()) { - // We need to escape the '#' character as "%23" for SVG because '#' is a reserved character in URIs. - $color-value-for-css: theme-color.prop-value($color); - $color-value-for-svg: str-replace_( - string.unquote('#{$color-value-for-css}'), - '#', - '%23' - ); - $feat-color: feature-targeting.create-target($query, color); - - .mdc-linear-progress__buffer-dots { - @include feature-targeting.targets($feat-color) { - // SVG is optimized for data URI (https://codepen.io/tigt/post/optimizing-svgs-in-data-uris) - // stylelint-disable-next-line function-url-quotes - background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' preserveAspectRatio='none slice'%3E%3Ccircle cx='1' cy='1' r='1' fill='#{$color-value-for-svg}'/%3E%3C/svg%3E"); - } - } - - .mdc-linear-progress__buffer-bar { - @include feature-targeting.targets($feat-color) { - background-color: $color-value-for-css; - } - } -} - -// -// Private // -@mixin indeterminate_($query: feature-targeting.all()) { - $feat-structure: feature-targeting.create-target($query, structure); - $feat-animation: feature-targeting.create-target($query, animation); - - &--indeterminate { - .mdc-linear-progress__bar { - @include feature-targeting.targets($feat-structure) { - transition: none; - } - } - - .mdc-linear-progress__primary-bar { - @include feature-targeting.targets($feat-structure) { - left: -145.166611%; - } - } - - .mdc-linear-progress__secondary-bar { - @include feature-targeting.targets($feat-structure) { - left: -54.888891%; - display: block; - } - } - - &.mdc-linear-progress--animation-ready { - .mdc-linear-progress__primary-bar { - @include feature-targeting.targets($feat-animation) { - animation: mdc-linear-progress-primary-indeterminate-translate 2s - infinite linear; - } - - > .mdc-linear-progress__bar-inner { - @include feature-targeting.targets($feat-animation) { - animation: mdc-linear-progress-primary-indeterminate-scale 2s - infinite linear; - } - } - } - - .mdc-linear-progress__secondary-bar { - @include feature-targeting.targets($feat-animation) { - animation: mdc-linear-progress-secondary-indeterminate-translate 2s - infinite linear; - } - - > .mdc-linear-progress__bar-inner { - @include feature-targeting.targets($feat-animation) { - animation: mdc-linear-progress-secondary-indeterminate-scale 2s - infinite linear; - } - } - } - } - } -} - -@mixin _rtl-styles($query: $query) { - $feat-structure: feature-targeting.create-target($query, structure); - $feat-animation: feature-targeting.create-target($query, animation); - - .mdc-linear-progress__bar { - @include feature-targeting.targets($feat-structure) { - /* @noflip */ - right: 0; - /* @noflip */ - -webkit-transform-origin: center right; - /* @noflip */ - transform-origin: center right; - } - } - - &.mdc-linear-progress--animation-ready { - .mdc-linear-progress__primary-bar { - @include feature-targeting.targets($feat-animation) { - animation-name: mdc-linear-progress-primary-indeterminate-translate-reverse; - } - } - - .mdc-linear-progress__secondary-bar { - @include feature-targeting.targets($feat-animation) { - animation-name: mdc-linear-progress-secondary-indeterminate-translate-reverse; - } - } - } - - .mdc-linear-progress__buffer-dots { - @include feature-targeting.targets($feat-animation) { - animation: mdc-linear-progress-buffering-reverse 250ms infinite linear; - } - - @include feature-targeting.targets($feat-structure) { - transform: rotate(0); - } - } - - &.mdc-linear-progress--indeterminate { - .mdc-linear-progress__primary-bar { - @include feature-targeting.targets($feat-structure) { - /* @noflip */ - right: -145.166611%; - /* @noflip */ - left: auto; - } - } - - .mdc-linear-progress__secondary-bar { - @include feature-targeting.targets($feat-structure) { - /* @noflip */ - right: -54.888891%; - /* @noflip */ - left: auto; - } - } - } -} - -// Based on https://css-tricks.com/snippets/sass/str-replace-function/ -@function str-replace_($string, $search, $replace) { - $index: string.index($string, $search); - - @if $index { - $head: string.slice($string, 1, $index - 1); - $tail: str-replace_( - string.slice($string, $index + string.length($search)), - $search, - $replace - ); - - @return $head + $replace + $tail; - } - - @return $string; -} +/// @deprecated Import './linear-progress' or './linear-progress-theme' instead. +@forward './linear-progress' show core-styles; +@forward './linear-progress-theme' show bar-color, buffer-color, track-height; diff --git a/packages/mdc-linear-progress/_variables.scss b/packages/mdc-linear-progress/_variables.scss index 6893916e15b..0d2588300c5 100644 --- a/packages/mdc-linear-progress/_variables.scss +++ b/packages/mdc-linear-progress/_variables.scss @@ -1,3 +1,4 @@ +// // Copyright 2017 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -17,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +// -$baseline-buffer-color: #e6e6e6 !default; -$height: 4px; +/// @deprecated Import './linear-progress-theme' module instead. +@forward './linear-progress-theme' show $baseline-buffer-color, $height; diff --git a/packages/mdc-linear-progress/component.ts b/packages/mdc-linear-progress/component.ts index ef887f07e8b..be8249a3a98 100644 --- a/packages/mdc-linear-progress/component.ts +++ b/packages/mdc-linear-progress/component.ts @@ -23,13 +23,15 @@ import {MDCComponent} from '@material/base/component'; import {MDCProgressIndicator} from '@material/progress-indicator/component'; + import {MDCLinearProgressAdapter} from './adapter'; import {MDCLinearProgressFoundation} from './foundation'; import {WithMDCResizeObserver} from './types'; +/** MDC Linear Progress */ export class MDCLinearProgress extends MDCComponent implements MDCProgressIndicator { - static attachTo(root: Element) { + static override attachTo(root: HTMLElement) { return new MDCLinearProgress(root); } @@ -53,13 +55,13 @@ export class MDCLinearProgress extends this.foundation.close(); } - initialSyncWithDOM() { + override initialSyncWithDOM() { this.root.addEventListener('transitionend', () => { this.foundation.handleTransitionEnd(); }); } - getDefaultFoundation() { + override getDefaultFoundation() { // DO NOT INLINE this variable. For backward compatibility, foundations take // a Partial. To ensure we don't accidentally omit any // methods, we need a separate, strongly typed adapter variable. @@ -92,10 +94,10 @@ export class MDCLinearProgress extends this.root.classList.remove(className); }, setAttribute: (attributeName: string, value: string) => { - this.root.setAttribute(attributeName, value); + this.safeSetAttribute(this.root, attributeName, value); }, setStyle: (name: string, value: string) => { - (this.root as HTMLElement).style.setProperty(name, value); + this.root.style.setProperty(name, value); }, attachResizeObserver: (callback) => { const RO = (window as unknown as WithMDCResizeObserver).ResizeObserver; @@ -107,7 +109,7 @@ export class MDCLinearProgress extends return null; }, - getWidth: () => (this.root as HTMLElement).offsetWidth, + getWidth: () => this.root.offsetWidth, }; return new MDCLinearProgressFoundation(adapter); } diff --git a/packages/mdc-linear-progress/foundation.ts b/packages/mdc-linear-progress/foundation.ts index 5a691c223de..82bc0e20663 100644 --- a/packages/mdc-linear-progress/foundation.ts +++ b/packages/mdc-linear-progress/foundation.ts @@ -32,15 +32,15 @@ import {MDCResizeObserver} from './types'; export class MDCLinearProgressFoundation extends MDCFoundation implements MDCProgressIndicatorFoundation { - static get cssClasses() { + static override get cssClasses() { return cssClasses; } - static get strings() { + static override get strings() { return strings; } - static get defaultAdapter(): MDCLinearProgressAdapter { + static override get defaultAdapter(): MDCLinearProgressAdapter { return { addClass: () => undefined, attachResizeObserver: () => null, @@ -65,7 +65,7 @@ export class MDCLinearProgressFoundation extends super({...MDCLinearProgressFoundation.defaultAdapter, ...adapter}); } - init() { + override init() { this.determinate = !this.adapter.hasClass(cssClasses.INDETERMINATE_CLASS); this.adapter.addClass(cssClasses.ANIMATION_READY_CLASS); this.progress = 0; @@ -168,7 +168,7 @@ export class MDCLinearProgressFoundation extends } } - destroy() { + override destroy() { super.destroy(); if (this.observer) { diff --git a/packages/mdc-linear-progress/mdc-linear-progress.import.scss b/packages/mdc-linear-progress/mdc-linear-progress.import.scss index a9a7c5be40b..6d09aa8aad4 100644 --- a/packages/mdc-linear-progress/mdc-linear-progress.import.scss +++ b/packages/mdc-linear-progress/mdc-linear-progress.import.scss @@ -1,6 +1,4 @@ @forward "@material/animation/variables" as mdc-animation-*; -@forward "@material/feature-targeting/variables" as mdc-feature-*; -@forward "@material/feature-targeting/mixins" as mdc-feature-*; @forward "@material/theme/variables" as mdc-theme-*; @forward "variables" as mdc-linear-progress-*; @forward "@material/theme/mixins" as mdc-theme-*; diff --git a/packages/mdc-linear-progress/mdc-linear-progress.scss b/packages/mdc-linear-progress/mdc-linear-progress.scss index 447f6c9f4bd..25d4384e34d 100644 --- a/packages/mdc-linear-progress/mdc-linear-progress.scss +++ b/packages/mdc-linear-progress/mdc-linear-progress.scss @@ -1,3 +1,4 @@ +// // Copyright 2017 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -17,6 +18,6 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +// -@use './mixins'; -@include mixins.core-styles; +@forward './styles'; diff --git a/packages/mdc-linear-progress/package.json b/packages/mdc-linear-progress/package.json index bbe5f0d555f..d2bbe32aec2 100644 --- a/packages/mdc-linear-progress/package.json +++ b/packages/mdc-linear-progress/package.json @@ -1,7 +1,7 @@ { "name": "@material/linear-progress", "description": "The Material Components for the web linear progress indicator component", - "version": "12.0.0", + "version": "14.0.0", "license": "MIT", "main": "dist/mdc.linearProgress.js", "module": "index.js", @@ -17,12 +17,13 @@ "directory": "packages/mdc-linear-progress" }, "dependencies": { - "@material/animation": "^12.0.0", - "@material/base": "^12.0.0", - "@material/feature-targeting": "^12.0.0", - "@material/progress-indicator": "^12.0.0", - "@material/rtl": "^12.0.0", - "@material/theme": "^12.0.0", + "@material/animation": "^14.0.0", + "@material/base": "^14.0.0", + "@material/dom": "^14.0.0", + "@material/feature-targeting": "^14.0.0", + "@material/progress-indicator": "^14.0.0", + "@material/rtl": "^14.0.0", + "@material/theme": "^14.0.0", "tslib": "^2.1.0" }, "publishConfig": { diff --git a/packages/mdc-feature-targeting/_mixins.scss b/packages/mdc-linear-progress/styles.scss similarity index 83% rename from packages/mdc-feature-targeting/_mixins.scss rename to packages/mdc-linear-progress/styles.scss index db218064412..2300efd3907 100644 --- a/packages/mdc-feature-targeting/_mixins.scss +++ b/packages/mdc-linear-progress/styles.scss @@ -1,5 +1,5 @@ // -// Copyright 2019 Google Inc. +// Copyright 2022 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 @@ -20,5 +20,8 @@ // THE SOFTWARE. // -/// @deprecated Import `_feature-targeting.scss` Sass module instead. -@forward 'feature-targeting' show targets; +@use './linear-progress'; +@use './linear-progress-theme'; + +@include linear-progress.static-styles(); +@include linear-progress-theme.theme-styles(linear-progress-theme.$light-theme); diff --git a/packages/mdc-linear-progress/test/component.test.ts b/packages/mdc-linear-progress/test/component.test.ts index 0ff9d663bed..f655699b51a 100644 --- a/packages/mdc-linear-progress/test/component.test.ts +++ b/packages/mdc-linear-progress/test/component.test.ts @@ -25,6 +25,7 @@ import {animationDimensionPercentages as percentages} from '../../mdc-linear-progress/constants'; import {MDCLinearProgress, MDCLinearProgressFoundation} from '../../mdc-linear-progress/index'; import {MDCResizeObserver, MDCResizeObserverCallback, MDCResizeObserverEntry, WithMDCResizeObserver} from '../../mdc-linear-progress/types'; +import {createFixture, html} from '../../../testing/dom'; import {emitEvent} from '../../../testing/dom/events'; import {createMockFoundation} from '../../../testing/helpers/foundation'; import {setUpMdcTestEnvironment} from '../../../testing/helpers/setup'; @@ -35,14 +36,13 @@ interface WithObserverFoundation { const RO = (window as unknown as WithMDCResizeObserver).ResizeObserver; -const roundPixelsToTwoDecimals = (val: string) => { +function roundPixelsToTwoDecimals(val: string) { const numberVal = Number(val.split('px')[0]); return Math.floor(numberVal * 100) / 100; -}; +} function getFixture() { - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` + return createFixture(html`
@@ -56,10 +56,7 @@ function getFixture() {
- `; - const el = wrapper.firstElementChild as HTMLElement; - wrapper.removeChild(el); - return el; + `); } const originalResizeObserver = RO; @@ -91,8 +88,7 @@ describe('MDCLinearProgress', () => { const {root, component} = setupTest(); component.determinate = false; - expect(root.classList.contains('mdc-linear-progress--indeterminate')) - .toBeTruthy(); + expect(root).toHaveClass('mdc-linear-progress--indeterminate'); expect(root.getAttribute(MDCLinearProgressFoundation.strings.ARIA_VALUENOW)) .toEqual(null); expect(root.getAttribute(MDCLinearProgressFoundation.strings.ARIA_VALUEMAX)) @@ -105,10 +101,8 @@ describe('MDCLinearProgress', () => { const {root, component} = setupTest(); component.progress = 0.5; - const primaryBar = - root.querySelector( - MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR) as - HTMLElement; + const primaryBar = root.querySelector( + MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR)!; // External GitHub TS compiler insists that `buffer.style.transform` could // be null // tslint:disable-next-line:no-unnecessary-type-assertion @@ -121,10 +115,8 @@ describe('MDCLinearProgress', () => { const {root, component} = setupTest(); component.buffer = 0.5; - const buffer = - root.querySelector( - MDCLinearProgressFoundation.strings.BUFFER_BAR_SELECTOR) as - HTMLElement; + const buffer = root.querySelector( + MDCLinearProgressFoundation.strings.BUFFER_BAR_SELECTOR)!; // External GitHub TS compiler insists that `buffer.style.transform` could // be null // tslint:disable-next-line:no-unnecessary-type-assertion @@ -135,15 +127,13 @@ describe('MDCLinearProgress', () => { const {root, component} = setupTest(); component.close(); - expect(root.classList.contains('mdc-linear-progress--closed')).toBeTrue(); + expect(root).toHaveClass('mdc-linear-progress--closed'); emitEvent(root, 'transitionend'); - expect(root.classList.contains('mdc-linear-progress--closed-animation-off')) - .toBeTrue(); + expect(root).toHaveClass('mdc-linear-progress--closed-animation-off'); component.open(); - expect(root.classList.contains('mdc-linear-progress--closed')).toBeFalse(); - expect(root.classList.contains('mdc-linear-progress--closed-animation-off')) - .toBeFalse(); + expect(root).not.toHaveClass('mdc-linear-progress--closed'); + expect(root).not.toHaveClass('mdc-linear-progress--closed-animation-off'); }); describe('attach to dom', () => { diff --git a/packages/mdc-linear-progress/test/foundation.test.ts b/packages/mdc-linear-progress/test/foundation.test.ts index af1ee462af6..2140bd21cbf 100644 --- a/packages/mdc-linear-progress/test/foundation.test.ts +++ b/packages/mdc-linear-progress/test/foundation.test.ts @@ -334,6 +334,11 @@ describe('MDCLinearProgressFoundation', () => { disconnected = true; } }; + // TODO: Wait until b/208710526 is fixed, then remove this autogenerated + // error suppression. + // @ts-ignore(go/unfork-jasmine-typings): Argument of type '{ disconnect: + // () => void; }' is not assignable to parameter of type + // 'MDCResizeObserver'. mockAdapter.attachResizeObserver.and.returnValue(mockedObserver); foundation.init(); const withObserver = diff --git a/packages/mdc-linear-progress/test/mdc-linear-progress.scss.test.ts b/packages/mdc-linear-progress/test/mdc-linear-progress.scss.test.ts index 955fab94ec9..20bbe6161cf 100644 --- a/packages/mdc-linear-progress/test/mdc-linear-progress.scss.test.ts +++ b/packages/mdc-linear-progress/test/mdc-linear-progress.scss.test.ts @@ -24,6 +24,7 @@ import 'jasmine'; import * as path from 'path'; + import {expectStylesWithNoFeaturesToBeEmpty} from '../../../testing/featuretargeting'; describe('mdc-linear-progress.scss', () => { diff --git a/packages/mdc-list/README.md b/packages/mdc-list/README.md index 2aeb4cce870..a4897c56c99 100644 --- a/packages/mdc-list/README.md +++ b/packages/mdc-list/README.md @@ -18,6 +18,22 @@ There are three list types: ![Composite image of the three list types](images/lists-types.png) +## List is being updated + +List is currently being updated to better match the Material spec. As a result, +there are two versions of List available: the deprecated old version +(documented below) and the new, future-proof version (documented [here](https://github.com/material-components/material-components-web/files/6955906/New.List.Public.Documentation.PUBLIC.DRAFT.pdf)). + +Components that require a List (e.g., Menu, Select, Navigation Drawer) still use +the deprecated version. Otherwise, we encourage you to use the new version in +your projects. + +Note that both new and old versions share the same JavaScript component and +imports. *However, there are differences in class names and DOM structure:* the +old version uses class names with a `mdc-deprecated-list` prefix (e.g., +`mdc-deprecated-list-item`) whereas the new version uses the typical `mdc-list` +prefix (e.g., `mdc-list-item`). + ## Using lists ### Installation @@ -31,7 +47,7 @@ npm install @material/list ```scss @use "@material/list"; -@include list.core-styles; +@include list.deprecated-core-styles; ``` ### JavaScript @@ -41,7 +57,7 @@ MDC List includes an optional JavaScript component which can be used for keyboar ```js import {MDCList} from '@material/list'; -const list = new MDCList(document.querySelector('.mdc-list')); +const list = new MDCList(document.querySelector('.mdc-deprecated-list')); ``` > See [Importing the JS component](../../docs/importing-js.md) for more information on how to import JavaScript. @@ -98,18 +114,18 @@ Single-line list items contain a maximum of one line of text. ### Single-line list example ```html -