From 4b779de6426bbe757564e2927f9ea0836a4ce394 Mon Sep 17 00:00:00 2001 From: lynnjepsen Date: Wed, 18 Oct 2017 12:32:34 -0700 Subject: [PATCH 1/4] feat(shape): Adding MDCShape, an experimental subsystem Also updated our webpack config and dependency test to allow demo-only JS. --- demos/shape/heart/foundation.js | 121 ++++++ demos/shape/heart/index.js | 33 ++ demos/shape/shape.html | 104 +++++ demos/shape/shape.scss | 22 + package.json | 1 + packages/mdc-shape/README.md | 100 +++++ packages/mdc-shape/_variables.scss | 18 + packages/mdc-shape/component.js | 57 +++ packages/mdc-shape/constants.js | 28 ++ packages/mdc-shape/foundation.js | 419 +++++++++++++++++++ packages/mdc-shape/index.js | 21 + packages/mdc-shape/mdc-shape.scss | 39 ++ packages/mdc-shape/package.json | 18 + packages/mdc-shape/util.js | 47 +++ scripts/check-pkg-for-release.js | 3 + test/unit/mdc-shape/foundation.test.js | 234 +++++++++++ test/unit/mdc-shape/mdc-shape.test.js | 60 +++ test/unit/mdc-shape/mock-shape-foundation.js | 27 ++ test/unit/mdc-shape/util.test.js | 32 ++ webpack.config.js | 31 ++ 20 files changed, 1415 insertions(+) create mode 100644 demos/shape/heart/foundation.js create mode 100644 demos/shape/heart/index.js create mode 100644 demos/shape/shape.html create mode 100644 demos/shape/shape.scss create mode 100644 packages/mdc-shape/README.md create mode 100644 packages/mdc-shape/_variables.scss create mode 100644 packages/mdc-shape/component.js create mode 100644 packages/mdc-shape/constants.js create mode 100644 packages/mdc-shape/foundation.js create mode 100644 packages/mdc-shape/index.js create mode 100644 packages/mdc-shape/mdc-shape.scss create mode 100644 packages/mdc-shape/package.json create mode 100644 packages/mdc-shape/util.js create mode 100644 test/unit/mdc-shape/foundation.test.js create mode 100644 test/unit/mdc-shape/mdc-shape.test.js create mode 100644 test/unit/mdc-shape/mock-shape-foundation.js create mode 100644 test/unit/mdc-shape/util.test.js diff --git a/demos/shape/heart/foundation.js b/demos/shape/heart/foundation.js new file mode 100644 index 00000000000..84e06e9f550 --- /dev/null +++ b/demos/shape/heart/foundation.js @@ -0,0 +1,121 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCShapeFoundation from '../../../packages/mdc-shape/foundation'; + +export default class DemoHeartFoundation extends MDCShapeFoundation { + generatePath_(width, height, padding) { + return 'm ' + (width / 2) + ' ' + (height - padding) + +this.generateLeftBase_(width, height, padding) + +this.generateTopLeftCurve_(width, height, padding) + +this.generateTopRightCurve_(width, height, padding) + +this.generateRightBase_(width, height, padding); + } + generateBaseWidth_(width, padding) { + return ((width - (padding * 2)) / 2); + } + generateBaseFlatWidth_(width, padding) { + return this.generateBaseWidth_(width, padding) * 0.14; + } + generateBaseCurveWidth_(width, padding) { + return this.generateBaseWidth_(width, padding) * 0.52; + } + generateBaseHeight_(height, padding) { + return (height - (padding * 2)) * 0.67; + } + generateBaseFlatHeight_(height, padding) { + return this.generateBaseHeight_(height, padding) * 0.11; + } + generateBaseCurveHeight_(height, padding) { + return this.generateBaseHeight_(height, padding) * 0.4; + } + generateBaseCurveHeight2_(height, padding) { + return this.generateBaseHeight_(height, padding) * 0.67; + } + generateLeftBase_(width, height, padding) { + const flatWidth = this.generateBaseFlatWidth_(width, padding); + const flatHeight = this.generateBaseFlatHeight_(height, padding); + const realWidth = this.generateBaseWidth_(width, padding) - flatWidth; + return 'l ' + (-1 * flatWidth) + ',' + (-1 * flatHeight) + +'c ' + (-1 * this.generateBaseCurveWidth_(width, padding)) + ',' + + (-1 * this.generateBaseCurveHeight_(height, padding)) + + ' ' + (-1 * realWidth) + ',' + (-1 * this.generateBaseCurveHeight2_(height, padding)) + + ' ' + (-1 * realWidth) + + ',' + (-1 * (this.generateBaseHeight_(height, padding) - flatHeight)); + } + generateRightBase_(width, height, padding) { + const flatWidth = this.generateBaseFlatWidth_(width, padding); + const flatHeight = this.generateBaseFlatHeight_(height, padding); + const curveHeight = this.generateBaseHeight_(height, padding) - flatHeight; + const realWidth = this.generateBaseWidth_(width, padding) - flatWidth; + return 'c 0,' + (curveHeight - this.generateBaseCurveHeight2_(height, padding)) + + (-1 * (realWidth - this.generateBaseCurveWidth_(width, padding))) + + ',' + (curveHeight - this.generateBaseCurveHeight_(height, padding)) + + (-1 * realWidth) + ',' + curveHeight + +'l ' + (-1 * flatWidth) + ',' + flatHeight; + } + generateOutsideWidth_(width, padding) { + return ((width - (padding * 2)) / 2) * 0.55; + } + generateOutsideCurveWidth_(width, padding) { + return this.generateOutsideWidth_(width, padding) * 0.44; + } + generateInsideWidth_(width, padding) { + return ((width - (padding * 2)) / 2) * 0.45; + } + generateInsideCurveWidth_(width, padding) { + return this.generateInsideWidth_(width, padding) * 0.37; + } + generateInsideCurveWidth2_(width, padding) { + return this.generateInsideWidth_(width, padding) * 0.76; + } + generateTopHeight_(height, padding) { + return (height - (padding * 2)) * (1 - 0.67); + } + generateTopCurveHeight_(height, padding) { + return this.generateTopHeight_(height, padding) * 0.56; + } + generateDipHeight_(height, padding) { + return (height - (padding * 2)) * 0.15; + } + generateDipCurveHeight_(height, padding) { + return this.generateDipHeight_(height, padding) * 0.38; + } + generateTopLeftCurve_(width, height, padding) { + const topHeight = this.generateTopHeight_(height, padding); + return 'c 0,' + (-1 * this.generateTopCurveHeight_(height, padding)) + ' ' + + this.generateOutsideCurveWidth_(width, padding) + ',' + (-1 * topHeight) + ' ' + + this.generateOutsideWidth_(width, padding) + ',' + (-1 * topHeight) + +'c ' + this.generateInsideCurveWidth_(width, padding) + ',0, ' + + this.generateInsideCurveWidth2_(width, padding) + + ',' + (this.generateDipCurveHeight_(height, padding)) + ', ' + + this.generateInsideWidth_(width, padding) + ',' + this.generateDipHeight_(height, padding); + } + generateTopRightCurve_(width, height, padding) { + const outsideWidth = this.generateOutsideWidth_(width, padding); + const insideWidth = this.generateInsideWidth_(width, padding); + const topHeight = this.generateTopHeight_(height, padding); + const dipHeight = this.generateDipHeight_(height, padding); + return 'c ' + (insideWidth - this.generateInsideCurveWidth2_(width, padding)) + + ',' + (-1 * (dipHeight - this.generateDipCurveHeight_(height, padding))) + ' ' + + (insideWidth - this.generateInsideCurveWidth_(width, padding)) + + ',' + (-1 * dipHeight) + ' ' + + insideWidth + ',' + (-1 * dipHeight) + +'c ' + (outsideWidth - this.generateOutsideCurveWidth_(width, padding)) + ',0 ' + + outsideWidth + ',' + (topHeight - this.generateTopCurveHeight_(height, padding)) + ' ' + + outsideWidth + ',' + topHeight; + } +} diff --git a/demos/shape/heart/index.js b/demos/shape/heart/index.js new file mode 100644 index 00000000000..0b944ed670f --- /dev/null +++ b/demos/shape/heart/index.js @@ -0,0 +1,33 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import DemoHeartFoundation from './foundation'; +import MDCShape from '../../../packages/mdc-shape/component'; + +export default class DemoHeart extends MDCShape { + static attachTo(root) { + return new DemoHeart(root); + } + + getDefaultFoundation() { + return new DemoHeartFoundation(this.createAdapter()); + } + + initialSyncWithDOM() { + super.initialSyncWithDOM(); + this.foundation_.redraw(); + } +} diff --git a/demos/shape/shape.html b/demos/shape/shape.html new file mode 100644 index 00000000000..fb5b812f47a --- /dev/null +++ b/demos/shape/shape.html @@ -0,0 +1,104 @@ + + + + + + Shape - Material Components Catalog + + + + + + + + +
+
+
+ + + + Shape +
+
+
+
+
+
+
+ + + + + + +
+
+
+
+ Change Elevation +
+
+ + + + + + +
+
+
+ Elevation + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/demos/shape/shape.scss b/demos/shape/shape.scss new file mode 100644 index 00000000000..ba6813f3ce0 --- /dev/null +++ b/demos/shape/shape.scss @@ -0,0 +1,22 @@ + +@import "../common"; +@import "../../packages/mdc-shape/mdc-shape"; + +fieldset { + margin: 24px; + margin-top: 0; + margin-bottom: 16px; +} +.mdc-shape { + --mdc-shape-elevation: 4; + --mdc-shape-background: #FF00FF; + width: 100px; + height: 100px; + margin: 16px auto; +} +.demo-input, .demo-button { + text-align: center; +} +#heart-ripple { + height: 100%; +} \ No newline at end of file diff --git a/package.json b/package.json index c5b7aa2181a..fccb95be70b 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "rtl", "select", "selection-control", + "shape", "slider", "snackbar", "switch", diff --git a/packages/mdc-shape/README.md b/packages/mdc-shape/README.md new file mode 100644 index 00000000000..2060fc25eed --- /dev/null +++ b/packages/mdc-shape/README.md @@ -0,0 +1,100 @@ +# Shape + +MDC Shape is a Sass / CSS / JavaScript library which draws shapes. + +## Design & API Documentation + +TODO + +## Installation + +TODO + +## Usage + +### DOM Structure + +```html +
+ +
Your Content
+ + + + + +
+``` + +We recommend you put any content for the shape inside the "clipped" element *after* the `mdc-shape__canvas` element. The "clipped" element must contain the style `clip-path:url(#FOO_ID)`, where `FOO_ID` corresponds to the value you assign to the `clipPath` element's `id` attribute. Using the same ID between the "clipped" element and the `clipPath` element effectively clips any content to the shape. It is important `mdc-shape__canvas` is before the "clipped" element, otherwise the clipping effect will clip the shadows drawn by `MDCShapeFoundation`. + +### CSS Classes + +CSS Class | Description +--- | --- +`mdc-shape` | Mandatory. Needs to be set on the root element of the component +`mdc-shape__canvas` | Mandatory. Needs to be set on the canvas node for drawing the shape +`mdc-shape__svg` | Mandatory. Needs to be set on the svg node for clipping content to the shape +`mdc-shape__path` | Mandatory. Needs to be set on the path node for clipping content to the shape + +### Using the Foundation Class + +MDC Shape ships with an `MDCShapeFoundation` class that external frameworks and libraries can use to integrate the component. As with all foundation classes, an adapter object must be provided. +The adapter for shape must provide the following functions, with correct signatures: + +| Method Signature | Description | +| --- | --- | +| `setCanvasWidth(value: number) => void` | Sets the width of the canvas element. | +| `setCanvasHeight(value: number) => void` | Sets the height of the canvas element. | +| `getCanvasWidth() => number` | Returns the width of the canvas element. | +| `getCanvasHeight() => number` | Returns the height of the canvas element. | +| `getDevicePixelRatio() => number` | Returns the device pixel ratio. | +| `create2dRenderingContext() => {shadowColor: string, shadowBlur: number, shadowOffsetY: number, fillStyle: string, scale: (number, number), clearRect: (number, number, number, number), fill: (Path2D)}` | Returns an object which has the shape of a CanvasRenderingContext2d instance. An easy way to achieve this is simply `this.root_.querySelector(mdc.shape.MDCShapeFoundation.SHAPE_SELECTOR).getContext('2d');`. | + +### Extending the Foundation Class + +`MDCShapeFoundation` is an abstract class. Developers should extend `MDCShapeFoundation` and implement the `generatePath_` method. GeneratePath_ takes width, height, and padding, and returns a string representation of the SVG path data. This allows developers to create *any* shape. + +TODO add more information about the other shapes we provide, which extend MDCShapeFoundation. + +### MDCShape API + +MDC Shape exposes the following methods: + +| Method Signature | Description | +| --- | --- | +| `set background(value: string) => void` | Sets the background of the shape | +| `set elevation(value: number) => void` | Sets the elevation of the shape | +| `redraw() => void` | Redraws the shape | + +### Shape Customization + +There are two ways to customize your shape's elevation and background color. The first way is to use the MDCShape's API. + +To modify the elevation of a shape, call the background setter + +``` +mdcShape.background = '#FOO'; +``` + +To modify the elevation of a shape, call the elevation setter + +``` +mdcShape.elevation = 4; +``` + +The second way is to use custom CSS properties. + +### CSS custom properties + +To modify the elevation of a shape, set custom CSS properties on the mdc-shape element + +``` +--mdc-shape-elevation: 4; +``` + +To modify the background of a shape, set custom CSS properties on the mdc-shape element + +``` +--mdc-shape-background: #FF0000; +``` \ No newline at end of file diff --git a/packages/mdc-shape/_variables.scss b/packages/mdc-shape/_variables.scss new file mode 100644 index 00000000000..ac64b8310a7 --- /dev/null +++ b/packages/mdc-shape/_variables.scss @@ -0,0 +1,18 @@ +// +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +$mdc-shape-shadow-padding: 56px; +$mdc-shape-shadow-negative-padding: -1 * $mdc-shape-shadow-padding; diff --git a/packages/mdc-shape/component.js b/packages/mdc-shape/component.js new file mode 100644 index 00000000000..f0bf49653ca --- /dev/null +++ b/packages/mdc-shape/component.js @@ -0,0 +1,57 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {MDCComponent} from '@material/base'; +import MDCShapeFoundation from './foundation'; +import {getElevationFromDOM, getBackgroundFromDOM} from './util'; + +export default class MDCShape extends MDCComponent { + set background(value) { + this.foundation_.setBackground(value); + } + + set elevation(value) { + this.foundation_.setElevation(value, true); + } + + redraw() { + this.foundation_.redraw(); + } + + createAdapter() { + return {setCanvasWidth: (value) => this.canvas_.width = value, + setCanvasHeight: (value) => this.canvas_.height = value, + getCanvasWidth: () => this.canvas_.offsetWidth, + getCanvasHeight: () => this.canvas_.offsetHeight, + getDevicePixelRatio: () => window.devicePixelRatio, + create2dRenderingContext: () => this.canvas_.getContext('2d'), + createPath2D: (path) => new Path2D(path), + setClipPathData: (value) => this.path_.setAttribute('d', value)}; + } + + initialSyncWithDOM() { + this.foundation_.setBackground(getBackgroundFromDOM(this.root_), false); + this.foundation_.setElevation(getElevationFromDOM(this.root_), false); + } + + get canvas_() { + return this.root_.querySelector(MDCShapeFoundation.strings.CANVAS_SELECTOR); + } + + get path_() { + return this.root_.querySelector(MDCShapeFoundation.strings.PATH_SELECTOR); + } +} diff --git a/packages/mdc-shape/constants.js b/packages/mdc-shape/constants.js new file mode 100644 index 00000000000..88d0aaf3f7e --- /dev/null +++ b/packages/mdc-shape/constants.js @@ -0,0 +1,28 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const cssClasses = { + ROOT: 'mdc-shape', +}; + +export const strings = { + CANVAS_SELECTOR: '.mdc-shape__canvas', + PATH_SELECTOR: '.mdc-shape__path', +}; + +export const numbers = { + SHADOW_PADDING: 56, + ELEVATION_ANIMATION_FRAME_COUNT: 10, +}; diff --git a/packages/mdc-shape/foundation.js b/packages/mdc-shape/foundation.js new file mode 100644 index 00000000000..195f9baf723 --- /dev/null +++ b/packages/mdc-shape/foundation.js @@ -0,0 +1,419 @@ +/** + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {MDCFoundation} from '@material/base'; + +import {cssClasses, strings, numbers} from './constants'; + +export default class MDCShapeFoundation extends MDCFoundation { + static get cssClasses() { + return cssClasses; + } + + static get strings() { + return strings; + } + + static get defaultAdapter() { + return { + setCanvasWidth: ( /* value: number */ ) => {}, + setCanvasHeight: ( /* value: number */ ) => {}, + getCanvasWidth: () => /* number */ 0, + getCanvasHeight: () => /* number */ 0, + getDevicePixelRatio: () => /* number */ 1, + create2dRenderingContext: () => + /* {shadowColor: string, + shadowBlur: number, + shadowOffsetY: number, + fillStyle: string, + scale: (number, number) => void, + clearRect: (number, number, number, number) => void, + fill: (Path2D) => void} */ ({ + shadowColor: '', + shadowBlur: 0, + shadowOffsetY: 0, + fillStyle: '', + scale: ( /* xScale: number, yScale: number */ ) => {}, + clearRect: ( /* x: number, y: number, width: number, height: number */ ) => {}, + fill: ( /* path2d: Path2D */ ) => {}, + }), + createPath2D: ( /* path: string */ ) => /* Path2D */ {}, + setClipPathData: ( /* value: string */ ) => {}, + }; + } + + constructor(adapter) { + super(Object.assign(MDCShapeFoundation.defaultAdapter, adapter)); + this.ctx_ = null; + this.elevation_ = 0; + this.ambientShadowBlur_ = 0; + this.ambientShadowOffsetY_ = 0; + this.penumbraShadowBlur_ = 0; + this.penumbraShadowOffsetY_ = 0; + this.umbraShadowBlur_ = 0; + this.umbraShadowOffsetY_ = 0; + this.umbraShadowSpread_ = 0; + this.elevating_ = false; + this.animationFrameId_ = null; + } + + init() { + const devicePixelRatio = this.adapter_.getDevicePixelRatio(); + this.adapter_.setCanvasWidth(this.adapter_.getCanvasWidth() * devicePixelRatio); + this.adapter_.setCanvasHeight(this.adapter_.getCanvasHeight() * devicePixelRatio); + this.ctx_ = this.adapter_.create2dRenderingContext(); + this.ctx_.scale(devicePixelRatio, devicePixelRatio); + } + + destroy() { + if (this.animationFrameId_) { + cancelAnimationFrame(this.animationFrameId_); + } + } + + setBackground(background) { + this.background_ = background; + } + + setElevation(value, animate) { + this.elevating_ = false; + this.elevation_ = value; + + if (!animate) { + this.ambientShadowBlur_ = this.finalAmbientShadowBlur_; + this.ambientShadowOffsetY_ = this.finalAmbientShadowOffsetY_; + this.penumbraShadowBlur_ = this.finalPenumbraShadowBlur_; + this.penumbraShadowOffsetY_ = this.finalPenumbraShadowOffsetY_; + this.umbraShadowBlur_ = this.finalUmbraShadowBlur_; + this.umbraShadowOffsetY_ = this.finalUmbraShadowOffsetY_; + this.umbraShadowSpread_ = this.finalUmbraShadowSpread_; + } else { + const factor = 1 / numbers.ELEVATION_ANIMATION_FRAME_COUNT; + this.ambientShadowBlurIncrement_ = + (this.finalAmbientShadowBlur_ - this.ambientShadowBlur_) * factor; + this.ambientShadowOffsetYIncrement_ = + (this.finalAmbientShadowOffsetY_ - this.ambientShadowOffsetY_) * factor; + this.penumbraShadowBlurIncrement_ = + (this.finalPenumbraShadowBlur_ - this.penumbraShadowBlur_) * factor; + this.penumbraShadowOffsetYIncrement_ = + (this.finalPenumbraShadowOffsetY_ - this.penumbraShadowOffsetY_) * factor; + this.umbraShadowBlurIncrement_ = + (this.finalUmbraShadowBlur_ - this.umbraShadowBlur_) * factor; + this.umbraShadowOffsetYIncrement_ = + (this.finalUmbraShadowOffsetY_ - this.umbraShadowOffsetY_) * factor; + this.umbraShadowSpreadIncrement_ = + (this.finalUmbraShadowSpread_ - this.umbraShadowSpread_) * factor; + this.elevating_ = true; + this.animationFrameId_ = requestAnimationFrame(() => this.elevate_()); + } + } + + redraw() { + this.clear_(); + this.init(); + this.draw_(); + } + + generateClipPath_() { + return this.generatePath_( + this.adapter_.getCanvasWidth() - (numbers.SHADOW_PADDING * 2), + this.adapter_.getCanvasHeight() - (numbers.SHADOW_PADDING * 2), + 0); + } + + generateShadowPath_(shadowSpread) { + return this.generatePath_( + this.adapter_.getCanvasWidth(), + this.adapter_.getCanvasHeight(), + numbers.SHADOW_PADDING - shadowSpread); + } + + generatePath_( /* width: number, height: number, padding: number */) { + // Subclasses should override this method to generate path data given a width, height, and padding + return 'm 0 0'; + } + + elevate_() { + this.ambientShadowBlur_ += this.ambientShadowBlurIncrement_; + this.ambientShadowOffsetY_ += this.ambientShadowOffsetYIncrement_; + this.penumbraShadowBlur_ += this.penumbraShadowBlurIncrement_; + this.penumbraShadowOffsetY_ += this.penumbraShadowOffsetYIncrement_; + this.umbraShadowBlur_ += this.umbraShadowBlurIncrement_; + this.umbraShadowOffsetY_ += this.umbraShadowOffsetYIncrement_; + this.umbraShadowSpread_ += this.umbraShadowSpreadIncrement_; + + let finished = false; + if (Math.abs(this.finalAmbientShadowBlur_ - this.ambientShadowBlur_) + <= Math.abs(this.ambientShadowBlurIncrement_)) { + this.ambientShadowBlur_ = this.finalAmbientShadowBlur_; + this.ambientShadowOffsetY_ = this.finalAmbientShadowOffsetY_; + this.penumbraShadowBlur_ = this.finalPenumbraShadowBlur_; + this.penumbraShadowOffsetY_ = this.finalPenumbraShadowOffsetY_; + this.umbraShadowBlur_ = this.finalUmbraShadowBlur_; + this.umbraShadowOffsetY_ = this.finalUmbraShadowOffsetY_; + this.umbraShadowSpread_ = this.finalUmbraShadowSpread_; + finished = true; + } + + this.redraw(); + + if (!finished && this.elevating_) { + this.animationFrameId_ = requestAnimationFrame(() => this.elevate_()); + } else { + this.elevating_ = false; + } + } + + clear_() { + this.ctx_.clearRect(0, 0, this.adapter_.getCanvasWidth(), this.adapter_.getCanvasHeight()); + } + + draw_() { + const devicePixelRatio = this.adapter_.getDevicePixelRatio(); + const noSpreadPath = this.generateShadowPath_(0); + this.drawAmbientShadow_(devicePixelRatio, noSpreadPath); + this.drawPenumbraShadow_(devicePixelRatio, noSpreadPath); + this.drawUmbraShadow_(devicePixelRatio); + this.drawColoredShape_(noSpreadPath); + this.adapter_.setClipPathData(this.generateClipPath_()); + } + + drawAmbientShadow_(devicePixelRatio, noSpreadPath) { + this.ctx_.fillStyle = '#FFF'; + this.ctx_.shadowColor = 'rgba(0, 0, 0, 0.12)'; + this.ctx_.shadowBlur = this.ambientShadowBlur_ * devicePixelRatio; + this.ctx_.shadowOffsetY = this.ambientShadowOffsetY_ * devicePixelRatio; + this.ctx_.fill(this.adapter_.createPath2D(noSpreadPath)); + } + + drawPenumbraShadow_(devicePixelRatio, noSpreadPath) { + this.ctx_.shadowColor = 'rgba(0, 0, 0, 0.14)'; + this.ctx_.shadowBlur = this.penumbraShadowBlur_ * devicePixelRatio; + this.ctx_.shadowOffsetY = this.penumbraShadowOffsetY_ * devicePixelRatio; + this.ctx_.fill(this.adapter_.createPath2D(noSpreadPath)); + } + + drawUmbraShadow_(devicePixelRatio) { + const umbraPath = this.generateShadowPath_(this.umbraShadowSpread_); + this.ctx_.shadowColor = 'rgba(0, 0, 0, 0.2)'; + this.ctx_.shadowBlur = this.umbraShadowBlur_ * devicePixelRatio; + this.ctx_.shadowOffsetY = this.umbraShadowOffsetY_ * devicePixelRatio; + this.ctx_.fill(this.adapter_.createPath2D(umbraPath)); + } + + drawColoredShape_(noSpreadPath) { + this.ctx_.fillStyle = this.background_; + this.ctx_.shadowColor = 'rgba(0, 0, 0, 0)'; + this.ctx_.shadowBlur = 0; + this.ctx_.shadowOffsetY = 0; + this.ctx_.fill(this.adapter_.createPath2D(noSpreadPath)); + } + + get finalAmbientShadowOffsetY_() { + return AMBIENT_OFFSET_Y[this.elevation_]; + } + + get finalAmbientShadowBlur_() { + return AMBIENT_BLUR[this.elevation_]; + } + + get finalPenumbraShadowOffsetY_() { + return this.elevation_; + } + + get finalPenumbraShadowBlur_() { + return PENUMBRA_BLUR[this.elevation_]; + } + + get finalUmbraShadowOffsetY_() { + return UMBRA_OFFSET_Y[this.elevation_]; + } + + get finalUmbraShadowBlur_() { + return UMBRA_BLUR[this.elevation_]; + } + + get finalUmbraShadowSpread_() { + return UMBRA_SPREAD[this.elevation_]; + } +} + +const UMBRA_OFFSET_Y = [ + 0, + 2, + 3, + 3, + 2, + 3, + 3, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 7, + 8, + 8, + 8, + 9, + 9, + 10, + 10, + 10, + 11, + 11, +]; + +const UMBRA_BLUR = [ + 0, + 1, + 1, + 3, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 7, + 8, + 8, + 9, + 9, + 10, + 11, + 11, + 12, + 13, + 13, + 14, + 14, + 15, +]; + +const UMBRA_SPREAD = [ + 0, + -1, + -2, + -2, + -1, + -1, + -1, + -2, + -3, + -3, + -3, + -4, + -4, + -4, + -4, + -5, + -5, + -5, + -5, + -6, + -6, + -6, + -6, + -7, + -7, +]; + +const PENUMBRA_BLUR = [ + 0, + 1, + 2, + 4, + 5, + 8, + 10, + 10, + 10, + 12, + 14, + 15, + 17, + 19, + 21, + 22, + 24, + 26, + 28, + 29, + 31, + 33, + 35, + 36, + 38, +]; + +const AMBIENT_OFFSET_Y = [ + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 5, + 6, + 6, + 6, + 7, + 7, + 8, + 8, + 8, + 9, + 9, +]; + +const AMBIENT_BLUR = [ + 0, + 3, + 5, + 8, + 10, + 14, + 18, + 16, + 14, + 16, + 18, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, +]; diff --git a/packages/mdc-shape/index.js b/packages/mdc-shape/index.js new file mode 100644 index 00000000000..cb224e9a425 --- /dev/null +++ b/packages/mdc-shape/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCShape from './component'; +import MDCShapeFoundation from './foundation'; +import {getElevationFromDOM, getBackgroundFromDOM} from './util'; + +export {MDCShape, MDCShapeFoundation, getElevationFromDOM, getBackgroundFromDOM}; diff --git a/packages/mdc-shape/mdc-shape.scss b/packages/mdc-shape/mdc-shape.scss new file mode 100644 index 00000000000..a808682f9e4 --- /dev/null +++ b/packages/mdc-shape/mdc-shape.scss @@ -0,0 +1,39 @@ +// +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@import "./variables"; + +$mdc-shape-shadow-double-padding: 2 * $mdc-shape-shadow-padding; + +.mdc-shape { + position: relative; + min-width: $mdc-shape-shadow-padding; + min-height: $mdc-shape-shadow-padding; + + &__canvas { + position: absolute; + top: $mdc-shape-shadow-negative-padding; + left: $mdc-shape-shadow-negative-padding; + width: calc(100% + #{$mdc-shape-shadow-double-padding}); + height: calc(100% + #{$mdc-shape-shadow-double-padding}); + pointer-events: none; + } + + &__svg { + position: absolute; + pointer-events: none; + } +} diff --git a/packages/mdc-shape/package.json b/packages/mdc-shape/package.json new file mode 100644 index 00000000000..135e7fb4405 --- /dev/null +++ b/packages/mdc-shape/package.json @@ -0,0 +1,18 @@ +{ + "name": "@material/shape", + "description": "The set of Material shape components", + "version": "0.0.0", + "license": "Apache-2.0", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/material-components/material-components-web.git" + }, + "dependencies": { + "@material/base": "^0.2.6" + }, + "private": "true", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/mdc-shape/util.js b/packages/mdc-shape/util.js new file mode 100644 index 00000000000..e6589023eff --- /dev/null +++ b/packages/mdc-shape/util.js @@ -0,0 +1,47 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function getElevationFromDOM(el) { + let elevation = 0; + let mdcShapeElevation = + getComputedStyle(el).getPropertyValue('--mdc-shape-elevation'); + + if (!mdcShapeElevation) { + mdcShapeElevation = el.style.getPropertyValue('--mdc-shape-elevation'); + } + + if (mdcShapeElevation) { + elevation = parseInt(mdcShapeElevation); + } + + return elevation; +} + +export function getBackgroundFromDOM(el) { + let background = '#FFF'; + let mdcShapeBackground = + getComputedStyle(el).getPropertyValue('--mdc-shape-background'); + + if (!mdcShapeBackground) { + mdcShapeBackground = el.style.getPropertyValue('--mdc-shape-background'); + } + + if (mdcShapeBackground) { + background = mdcShapeBackground.trim(); + } + + return background; +} diff --git a/scripts/check-pkg-for-release.js b/scripts/check-pkg-for-release.js index 8c287761ff9..f7a192c9a2f 100644 --- a/scripts/check-pkg-for-release.js +++ b/scripts/check-pkg-for-release.js @@ -44,6 +44,7 @@ const MASTER_PKG = require(path.join(process.env.PWD, MASTER_PKG_PATH)); // directly included in webpack or the material-component-web module. But they // are necessary since other MDC packages depend on them. const CSS_WHITELIST = ['base', 'animation', 'auto-init', 'rtl', 'selection-control']; +const NOT_PUBLISHABLE = ['shape']; main(); @@ -51,6 +52,8 @@ function main() { checkPublicConfigForNewComponent(); if (pkg.name !== MASTER_PKG.name) { checkNameIsPresentInAllowedScope(); + } + if (NOT_PUBLISHABLE.indexOf(pkg.name) !== -1) { checkDependencyAddedInWebpackConfig(); checkDependencyAddedInMDCPackage(); } diff --git a/test/unit/mdc-shape/foundation.test.js b/test/unit/mdc-shape/foundation.test.js new file mode 100644 index 00000000000..1ac299b147b --- /dev/null +++ b/test/unit/mdc-shape/foundation.test.js @@ -0,0 +1,234 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {assert} from 'chai'; +import td from 'testdouble'; + +import {setupFoundationTest} from '../helpers/setup'; +import {createMockRaf} from '../helpers/raf'; +import {verifyDefaultAdapter} from '../helpers/foundation'; + + +import MDCShapeFoundation from '../../../packages/mdc-shape/foundation'; +import MockShapeFoundation from './mock-shape-foundation'; + +suite('MDCShapeFoundation'); + +test('exports cssClasses', () => { + assert.isOk('cssClasses' in MDCShapeFoundation); +}); + +test('exports strings', () => { + assert.isOk('strings' in MDCShapeFoundation); +}); + +test('default adapter returns a complete adapter implementation', () => { + verifyDefaultAdapter(MDCShapeFoundation, [ + 'setCanvasWidth', 'setCanvasHeight', 'getCanvasWidth', 'getCanvasHeight', 'getDevicePixelRatio', + 'create2dRenderingContext', 'createPath2D', 'setClipPathData', + ]); + + const renderingContext = MDCShapeFoundation.defaultAdapter.create2dRenderingContext(); + const renderingContextMethods = + Object.keys(renderingContext).filter((k) => typeof renderingContext[k] === 'function'); + assert.deepEqual(renderingContextMethods.slice().sort(), ['scale', 'clearRect', 'fill'].slice().sort()); + renderingContextMethods.forEach((m) => assert.doesNotThrow(renderingContext[m])); +}); + +function setupTest() { + return setupFoundationTest(MockShapeFoundation); +} + +function testFoundation(desc, runTests) { + test(desc, () => { + const {mockAdapter, foundation} = setupTest(); + const mockRaf = createMockRaf(); + runTests({mockAdapter, foundation, mockRaf}); + mockRaf.restore(); + }); +} + +test('#init sets up canvas', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.getCanvasWidth()).thenReturn(100); + td.when(mockAdapter.getCanvasHeight()).thenReturn(200); + td.when(mockAdapter.getDevicePixelRatio()).thenReturn(2); + const mockContext = td.object(MDCShapeFoundation.defaultAdapter.create2dRenderingContext()); + td.when(mockAdapter.create2dRenderingContext()).thenReturn(mockContext); + + foundation.init(); + + td.verify(mockAdapter.setCanvasWidth(200)); + td.verify(mockAdapter.setCanvasHeight(400)); + td.verify(mockContext.scale(2, 2)); +}); + +test('#redraw draws ambient, penumbra, umbra shadow, and the final shape', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.getCanvasWidth()).thenReturn(200); + td.when(mockAdapter.getCanvasHeight()).thenReturn(300); + td.when(mockAdapter.getDevicePixelRatio()).thenReturn(2); + const mockContext = td.object(MDCShapeFoundation.defaultAdapter.create2dRenderingContext()); + td.when(mockAdapter.create2dRenderingContext()).thenReturn(mockContext); + let fillCount = 0; + const fillStyles = []; + const shadowColors = []; + const shadowBlurs = []; + const shadowOffsetYs = []; + mockContext.fill = () => { + fillCount++; + fillStyles.push(mockContext.fillStyle); + shadowColors.push(mockContext.shadowColor); + shadowBlurs.push(mockContext.shadowBlur); + shadowOffsetYs.push(mockContext.shadowOffsetY); + }; + + foundation.init(); + foundation.redraw(); + + td.verify(mockContext.clearRect(0, 0, 200, 300)); + assert.equal(4, fillCount); + assert.equal(fillStyles[0], '#FFF'); + assert.equal(fillStyles[1], '#FFF'); + assert.equal(fillStyles[2], '#FFF'); + assert.equal(shadowColors[0], 'rgba(0, 0, 0, 0.12)'); + assert.equal(shadowColors[1], 'rgba(0, 0, 0, 0.14)'); + assert.equal(shadowColors[2], 'rgba(0, 0, 0, 0.2)'); + assert.equal(shadowColors[3], 'rgba(0, 0, 0, 0)'); + assert.equal(shadowBlurs[0], 0); + assert.equal(shadowBlurs[1], 0); + assert.equal(shadowBlurs[2], 0); + assert.equal(shadowBlurs[3], 0); + assert.equal(shadowOffsetYs[0], 0); + assert.equal(shadowOffsetYs[1], 0); + assert.equal(shadowOffsetYs[2], 0); + assert.equal(shadowOffsetYs[3], 0); + td.verify(mockAdapter.createPath2D('m 56 56 h 200 v 300'), {times: 4}); + td.verify(mockAdapter.setClipPathData('m 0 0 h 88 v 188')); +}); + +test('#setBackground changes fillStyle when redrawing', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.getCanvasWidth()).thenReturn(100); + td.when(mockAdapter.getCanvasHeight()).thenReturn(200); + td.when(mockAdapter.getDevicePixelRatio()).thenReturn(2); + const mockContext = td.object(MDCShapeFoundation.defaultAdapter.create2dRenderingContext()); + td.when(mockAdapter.create2dRenderingContext()).thenReturn(mockContext); + const fillStyles = []; + mockContext.fill = () => { + fillStyles.push(mockContext.fillStyle); + }; + + foundation.init(); + foundation.setBackground('#FF0000'); + foundation.redraw(); + + assert.equal(fillStyles[3], '#FF0000'); +}); + +test('#setElevation changes shadows when redrawing', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.generatePath_ = (width, height, padding) => { + return 'm ' + padding + ' ' + padding + ' h ' + width + ' v ' + height; + }; + td.when(mockAdapter.getCanvasWidth()).thenReturn(100); + td.when(mockAdapter.getCanvasHeight()).thenReturn(200); + td.when(mockAdapter.getDevicePixelRatio()).thenReturn(2); + const mockContext = td.object(MDCShapeFoundation.defaultAdapter.create2dRenderingContext()); + td.when(mockAdapter.create2dRenderingContext()).thenReturn(mockContext); + const shadowBlurs = []; + const shadowOffsetYs = []; + mockContext.fill = () => { + shadowBlurs.push(mockContext.shadowBlur); + shadowOffsetYs.push(mockContext.shadowOffsetY); + }; + + foundation.init(); + foundation.setElevation(2, false); + foundation.redraw(); + + assert.equal(shadowBlurs[0], 10); + assert.equal(shadowBlurs[1], 4); + assert.equal(shadowBlurs[2], 2); + assert.equal(shadowOffsetYs[0], 2); + assert.equal(shadowOffsetYs[1], 4); + assert.equal(shadowOffsetYs[2], 6); + td.verify(mockAdapter.createPath2D('m 58 58 h 100 v 200')); +}); + +testFoundation('#setElevation changes shadows when animating', ({foundation, mockAdapter, mockRaf}) => { + foundation.generatePath_ = (width, height, padding) => { + return 'm ' + padding + ' ' + padding + ' h ' + width + ' v ' + height; + }; + td.when(mockAdapter.getCanvasWidth()).thenReturn(100); + td.when(mockAdapter.getCanvasHeight()).thenReturn(200); + td.when(mockAdapter.getDevicePixelRatio()).thenReturn(2); + const mockContext = td.object(MDCShapeFoundation.defaultAdapter.create2dRenderingContext()); + td.when(mockAdapter.create2dRenderingContext()).thenReturn(mockContext); + let fillCount = 0; + const shadowBlurs = []; + const shadowOffsetYs = []; + mockContext.fill = () => { + fillCount++; + shadowBlurs.push(mockContext.shadowBlur); + shadowOffsetYs.push(mockContext.shadowOffsetY); + }; + + foundation.init(); + foundation.setElevation(2, true); + mockRaf.flush(); + + assert.equal(fillCount, 4); + assert.equal(shadowBlurs[0], 1); + assert.equal(shadowBlurs[1], 0.4); + assert.equal(shadowBlurs[2], 0.2); + assert.equal(shadowOffsetYs[0], 0.2); + assert.equal(shadowOffsetYs[1], 0.4); + assert.equal(shadowOffsetYs[2], 0.6000000000000001); + td.verify(mockAdapter.createPath2D('m 56.2 56.2 h 100 v 200')); + + // requires 9 frames to finish animation + for (let i=0; i<9; i++) { + mockRaf.flush(); + } + + assert.equal(fillCount, 4 * 9); + assert.equal(shadowBlurs[8 * 4], 10); + assert.equal(shadowBlurs[8 * 4 + 1], 4); + assert.equal(shadowBlurs[8 * 4 + 2], 2); + assert.equal(shadowOffsetYs[8 * 4], 2); + assert.equal(shadowOffsetYs[8 * 4 + 1], 4); + assert.equal(shadowOffsetYs[8 * 4 + 2], 6); + td.verify(mockAdapter.createPath2D('m 58 58 h 100 v 200')); +}); + +testFoundation('#destroy cancels animation', ({foundation, mockAdapter, mockRaf}) => { + foundation.generatePath_ = (width, height, padding) => { + return 'm ' + padding + ' ' + padding + ' h ' + width + ' v ' + height; + }; + td.when(mockAdapter.getCanvasWidth()).thenReturn(100); + td.when(mockAdapter.getCanvasHeight()).thenReturn(200); + td.when(mockAdapter.getDevicePixelRatio()).thenReturn(2); + const mockContext = td.object(MDCShapeFoundation.defaultAdapter.create2dRenderingContext()); + td.when(mockAdapter.create2dRenderingContext()).thenReturn(mockContext); + + foundation.init(); + foundation.setElevation(2, true); + mockRaf.flush(); + foundation.destroy(); + + assert.equal(0, mockRaf.pendingFrames.length); +}); diff --git a/test/unit/mdc-shape/mdc-shape.test.js b/test/unit/mdc-shape/mdc-shape.test.js new file mode 100644 index 00000000000..8c90ca2367f --- /dev/null +++ b/test/unit/mdc-shape/mdc-shape.test.js @@ -0,0 +1,60 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import bel from 'bel'; +import td from 'testdouble'; + +import {MDCShape, MDCShapeFoundation} from '../../../packages/mdc-shape'; + +function getFixture() { + return bel` +
+ +
+ + + + + +
+ `; +} + +suite('MDCShape'); + +test('#set background proxies to foundation.setBackground()', () => { + const MockShapeFoundation = td.constructor(MDCShapeFoundation); + const foundation = new MockShapeFoundation(); + const component = new MDCShape(getFixture(), foundation); + component.background = '#FF0000'; + td.verify(foundation.setBackground('#FF0000')); +}); + +test('#set elevation proxies to foundation.setElevation()', () => { + const MockShapeFoundation = td.constructor(MDCShapeFoundation); + const foundation = new MockShapeFoundation(); + const component = new MDCShape(getFixture(), foundation); + component.elevation = 8; + td.verify(foundation.setElevation(8, true)); +}); + +test('#redraw proxies to foundation.redraw()', () => { + const MockShapeFoundation = td.constructor(MDCShapeFoundation); + const foundation = new MockShapeFoundation(); + const component = new MDCShape(getFixture(), foundation); + component.redraw(); + td.verify(foundation.redraw()); +}); diff --git a/test/unit/mdc-shape/mock-shape-foundation.js b/test/unit/mdc-shape/mock-shape-foundation.js new file mode 100644 index 00000000000..67d7042cbc8 --- /dev/null +++ b/test/unit/mdc-shape/mock-shape-foundation.js @@ -0,0 +1,27 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCShapeFoundation from '../../../packages/mdc-shape/foundation'; + +export default class MockShapeFoundation extends MDCShapeFoundation { + static get defaultAdapter() { + return MDCShapeFoundation.defaultAdapter; + } + + generatePath_(width, height, padding) { + return `m ${padding} ${padding} h ${width} v ${height}`; + } +} diff --git a/test/unit/mdc-shape/util.test.js b/test/unit/mdc-shape/util.test.js new file mode 100644 index 00000000000..141a29a3f9f --- /dev/null +++ b/test/unit/mdc-shape/util.test.js @@ -0,0 +1,32 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {assert} from 'chai'; +import bel from 'bel'; + +import {getElevationFromDOM, getBackgroundFromDOM} from '../../../packages/mdc-shape/util'; + +suite('MDCShape - util'); + +test('getElevationFromDOM', () => { + const el = bel`
`; + assert.equal(getElevationFromDOM(el), 4); +}); + +test('getBackgroundFromDOM', () => { + const el = bel`
`; + assert.equal(getBackgroundFromDOM(el), '#FF0000'); +}); diff --git a/webpack.config.js b/webpack.config.js index 5fe269cdf0d..a944e3c8aac 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -252,4 +252,35 @@ if (IS_DEV) { createBannerPlugin(), ], }); + + const demoJsEntry = {}; + glob.sync('demos/**/*.js').forEach((filename) => { + demoJsEntry[filename.slice(6, -3)] = path.resolve(filename); + }); + + module.exports.push({ + name: 'demo-js', + entry: demoJsEntry, + output: { + path: OUT_PATH, + publicPath: PUBLIC_PATH, + filename: '[name].js', + libraryTarget: 'umd', + library: ['mdc', '[name]'], + }, + devtool: DEVTOOL, + module: { + rules: [{ + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: { + cacheDirectory: true, + }, + }], + }, + plugins: [ + createBannerPlugin(), + ], + }); } From c1ae11d8f9d0ce7414d2f5e0ec27a088c0c9b5ff Mon Sep 17 00:00:00 2001 From: lynnjepsen Date: Mon, 6 Nov 2017 13:23:50 -0800 Subject: [PATCH 2/4] first pass at fixing format --- demos/shape/heart/foundation.js | 52 ++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/demos/shape/heart/foundation.js b/demos/shape/heart/foundation.js index 84e06e9f550..e2394384718 100644 --- a/demos/shape/heart/foundation.js +++ b/demos/shape/heart/foundation.js @@ -17,45 +17,60 @@ import MDCShapeFoundation from '../../../packages/mdc-shape/foundation'; export default class DemoHeartFoundation extends MDCShapeFoundation { + generatePath_(width, height, padding) { return 'm ' + (width / 2) + ' ' + (height - padding) - +this.generateLeftBase_(width, height, padding) - +this.generateTopLeftCurve_(width, height, padding) - +this.generateTopRightCurve_(width, height, padding) - +this.generateRightBase_(width, height, padding); + + this.generateLeftBase_(width, height, padding) + + this.generateTopLeftCurve_(width, height, padding) + + this.generateTopRightCurve_(width, height, padding) + + this.generateRightBase_(width, height, padding); } + generateBaseWidth_(width, padding) { - return ((width - (padding * 2)) / 2); + return ((width - padding * 2) / 2); } + generateBaseFlatWidth_(width, padding) { return this.generateBaseWidth_(width, padding) * 0.14; } + generateBaseCurveWidth_(width, padding) { return this.generateBaseWidth_(width, padding) * 0.52; } + generateBaseHeight_(height, padding) { - return (height - (padding * 2)) * 0.67; + return (height - padding * 2) * 0.67; } + generateBaseFlatHeight_(height, padding) { return this.generateBaseHeight_(height, padding) * 0.11; } + generateBaseCurveHeight_(height, padding) { return this.generateBaseHeight_(height, padding) * 0.4; } + generateBaseCurveHeight2_(height, padding) { return this.generateBaseHeight_(height, padding) * 0.67; } + generateLeftBase_(width, height, padding) { const flatWidth = this.generateBaseFlatWidth_(width, padding); const flatHeight = this.generateBaseFlatHeight_(height, padding); const realWidth = this.generateBaseWidth_(width, padding) - flatWidth; - return 'l ' + (-1 * flatWidth) + ',' + (-1 * flatHeight) - +'c ' + (-1 * this.generateBaseCurveWidth_(width, padding)) + ',' - + (-1 * this.generateBaseCurveHeight_(height, padding)) - + ' ' + (-1 * realWidth) + ',' + (-1 * this.generateBaseCurveHeight2_(height, padding)) - + ' ' + (-1 * realWidth) - + ',' + (-1 * (this.generateBaseHeight_(height, padding) - flatHeight)); + const negativeFlatWidth = -1 * flatWidth; + const negativeFlatHeight = -1 * flatHeight; + const baseCurveWidth = -1 * this.generateBaseCurveWidth_(width, padding); + const baseCurveHeight = -1 * this.generateBaseCurveHeight_(height, padding); + const negativeRealWidth = -1 * realWidth; + const baseCurveHeight2 = -1 * this.generateBaseCurveHeight2_(height, padding); + const baseHeight = -1 * (this.generateBaseHeight_(height, padding) - flatHeight); + return `l ${negativeFlatWidth},${negativeFlatHeight}` + + ` c ${baseCurveWidth},${baseCurveHeight}` + + ` ${negativeRealWidth},${baseCurveHeight2}` + + ` ${negativeRealWidth},${baseHeight}`; } + generateRightBase_(width, height, padding) { const flatWidth = this.generateBaseFlatWidth_(width, padding); const flatHeight = this.generateBaseFlatHeight_(height, padding); @@ -65,35 +80,45 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { + (-1 * (realWidth - this.generateBaseCurveWidth_(width, padding))) + ',' + (curveHeight - this.generateBaseCurveHeight_(height, padding)) + (-1 * realWidth) + ',' + curveHeight - +'l ' + (-1 * flatWidth) + ',' + flatHeight; + + 'l ' + (-1 * flatWidth) + ',' + flatHeight; } + generateOutsideWidth_(width, padding) { return ((width - (padding * 2)) / 2) * 0.55; } + generateOutsideCurveWidth_(width, padding) { return this.generateOutsideWidth_(width, padding) * 0.44; } + generateInsideWidth_(width, padding) { return ((width - (padding * 2)) / 2) * 0.45; } + generateInsideCurveWidth_(width, padding) { return this.generateInsideWidth_(width, padding) * 0.37; } + generateInsideCurveWidth2_(width, padding) { return this.generateInsideWidth_(width, padding) * 0.76; } + generateTopHeight_(height, padding) { return (height - (padding * 2)) * (1 - 0.67); } + generateTopCurveHeight_(height, padding) { return this.generateTopHeight_(height, padding) * 0.56; } + generateDipHeight_(height, padding) { return (height - (padding * 2)) * 0.15; } + generateDipCurveHeight_(height, padding) { return this.generateDipHeight_(height, padding) * 0.38; } + generateTopLeftCurve_(width, height, padding) { const topHeight = this.generateTopHeight_(height, padding); return 'c 0,' + (-1 * this.generateTopCurveHeight_(height, padding)) + ' ' @@ -104,6 +129,7 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { + ',' + (this.generateDipCurveHeight_(height, padding)) + ', ' + this.generateInsideWidth_(width, padding) + ',' + this.generateDipHeight_(height, padding); } + generateTopRightCurve_(width, height, padding) { const outsideWidth = this.generateOutsideWidth_(width, padding); const insideWidth = this.generateInsideWidth_(width, padding); From 1672f057dbb8a2cf832747095413a16c6b1ed848 Mon Sep 17 00:00:00 2001 From: lynnjepsen Date: Fri, 10 Nov 2017 09:09:27 -0800 Subject: [PATCH 3/4] more fixes, removed css custom properties, and fixed the dependency script --- demos/shape/heart/foundation.js | 68 ++++--- demos/shape/heart/index.js | 5 - demos/shape/{shape.html => index.html} | 5 +- demos/shape/shape.scss | 2 - packages/material-components-web/index.js | 2 + packages/mdc-elevation/constants.js | 183 ++++++++++++++++++ packages/mdc-elevation/index.js | 20 ++ packages/mdc-shape/README.md | 35 ++-- packages/mdc-shape/_variables.scss | 1 - packages/mdc-shape/component.js | 6 - packages/mdc-shape/foundation.js | 214 +++------------------- packages/mdc-shape/index.js | 3 +- packages/mdc-shape/mdc-shape.scss | 10 +- packages/mdc-shape/package.json | 3 +- packages/mdc-shape/util.js | 47 ----- scripts/check-pkg-for-release.js | 9 +- test/unit/mdc-shape/util.test.js | 32 ---- 17 files changed, 302 insertions(+), 343 deletions(-) rename demos/shape/{shape.html => index.html} (95%) create mode 100644 packages/mdc-elevation/constants.js create mode 100644 packages/mdc-elevation/index.js delete mode 100644 packages/mdc-shape/util.js delete mode 100644 test/unit/mdc-shape/util.test.js diff --git a/demos/shape/heart/foundation.js b/demos/shape/heart/foundation.js index e2394384718..4107e0ef602 100644 --- a/demos/shape/heart/foundation.js +++ b/demos/shape/heart/foundation.js @@ -65,10 +65,10 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { const negativeRealWidth = -1 * realWidth; const baseCurveHeight2 = -1 * this.generateBaseCurveHeight2_(height, padding); const baseHeight = -1 * (this.generateBaseHeight_(height, padding) - flatHeight); - return `l ${negativeFlatWidth},${negativeFlatHeight}` - + ` c ${baseCurveWidth},${baseCurveHeight}` - + ` ${negativeRealWidth},${baseCurveHeight2}` - + ` ${negativeRealWidth},${baseHeight}`; + return `l ${negativeFlatWidth},${negativeFlatHeight}` + + ` c ${baseCurveWidth},${baseCurveHeight}` + + ` ${negativeRealWidth},${baseCurveHeight2}` + + ` ${negativeRealWidth},${baseHeight}`; } generateRightBase_(width, height, padding) { @@ -76,15 +76,17 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { const flatHeight = this.generateBaseFlatHeight_(height, padding); const curveHeight = this.generateBaseHeight_(height, padding) - flatHeight; const realWidth = this.generateBaseWidth_(width, padding) - flatWidth; - return 'c 0,' + (curveHeight - this.generateBaseCurveHeight2_(height, padding)) - + (-1 * (realWidth - this.generateBaseCurveWidth_(width, padding))) - + ',' + (curveHeight - this.generateBaseCurveHeight_(height, padding)) - + (-1 * realWidth) + ',' + curveHeight - + 'l ' + (-1 * flatWidth) + ',' + flatHeight; + const controlY = curveHeight - this.generateBaseCurveHeight2_(height, padding); + const controlX2 = -1 * (realWidth - this.generateBaseCurveWidth_(width, padding)); + const controlY2 = curveHeight - this.generateBaseCurveHeight_(height, padding); + const negativeRealWidth = -1 * realWidth; + const negativeFlatWidth = -1 * flatWidth; + return `c 0,${controlY} ${controlX2},${controlY2} ${negativeRealWidth},${curveHeight}` + + ` l ${negativeFlatWidth},${flatHeight}`; } generateOutsideWidth_(width, padding) { - return ((width - (padding * 2)) / 2) * 0.55; + return ((width - padding * 2) / 2) * 0.55; } generateOutsideCurveWidth_(width, padding) { @@ -92,7 +94,7 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { } generateInsideWidth_(width, padding) { - return ((width - (padding * 2)) / 2) * 0.45; + return ((width - padding * 2) / 2) * 0.45; } generateInsideCurveWidth_(width, padding) { @@ -104,7 +106,7 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { } generateTopHeight_(height, padding) { - return (height - (padding * 2)) * (1 - 0.67); + return (height - padding * 2) * (1 - 0.67); } generateTopCurveHeight_(height, padding) { @@ -112,7 +114,7 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { } generateDipHeight_(height, padding) { - return (height - (padding * 2)) * 0.15; + return (height - padding * 2) * 0.15; } generateDipCurveHeight_(height, padding) { @@ -121,13 +123,20 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { generateTopLeftCurve_(width, height, padding) { const topHeight = this.generateTopHeight_(height, padding); - return 'c 0,' + (-1 * this.generateTopCurveHeight_(height, padding)) + ' ' - + this.generateOutsideCurveWidth_(width, padding) + ',' + (-1 * topHeight) + ' ' - + this.generateOutsideWidth_(width, padding) + ',' + (-1 * topHeight) - +'c ' + this.generateInsideCurveWidth_(width, padding) + ',0, ' - + this.generateInsideCurveWidth2_(width, padding) - + ',' + (this.generateDipCurveHeight_(height, padding)) + ', ' - + this.generateInsideWidth_(width, padding) + ',' + this.generateDipHeight_(height, padding); + const negativeTopCurveHeight = -1 * this.generateTopCurveHeight_(height, padding); + const outsideCurveWidth = this.generateOutsideCurveWidth_(width, padding); + const negativeTopHeight = -1 * topHeight; + const outsideWidth = this.generateOutsideWidth_(width, padding); + const insideCurveWidth = this.generateInsideCurveWidth_(width, padding); + const insideCurveWidth2 = this.generateInsideCurveWidth2_(width, padding); + const dipCurveHeight = this.generateDipCurveHeight_(height, padding); + const insideWidth = this.generateInsideWidth_(width, padding); + const dipHeight = this.generateDipHeight_(height, padding); + + return `c 0,${negativeTopCurveHeight} ${outsideCurveWidth},${negativeTopHeight} ` + + ` ${outsideWidth},${negativeTopHeight}` + + ` c ${insideCurveWidth},0 ${insideCurveWidth2},${dipCurveHeight}` + + ` ${insideWidth},${dipHeight}`; } generateTopRightCurve_(width, height, padding) { @@ -135,13 +144,16 @@ export default class DemoHeartFoundation extends MDCShapeFoundation { const insideWidth = this.generateInsideWidth_(width, padding); const topHeight = this.generateTopHeight_(height, padding); const dipHeight = this.generateDipHeight_(height, padding); - return 'c ' + (insideWidth - this.generateInsideCurveWidth2_(width, padding)) - + ',' + (-1 * (dipHeight - this.generateDipCurveHeight_(height, padding))) + ' ' - + (insideWidth - this.generateInsideCurveWidth_(width, padding)) - + ',' + (-1 * dipHeight) + ' ' - + insideWidth + ',' + (-1 * dipHeight) - +'c ' + (outsideWidth - this.generateOutsideCurveWidth_(width, padding)) + ',0 ' - + outsideWidth + ',' + (topHeight - this.generateTopCurveHeight_(height, padding)) + ' ' - + outsideWidth + ',' + topHeight; + const controlX = insideWidth - this.generateInsideCurveWidth2_(width, padding); + const controlY = -1 * (dipHeight - this.generateDipCurveHeight_(height, padding)); + const controlX2 = insideWidth - this.generateInsideCurveWidth_(width, padding); + const negativeDipHeight = -1 * dipHeight; + const controlX3 = outsideWidth - this.generateOutsideCurveWidth_(width, padding); + const controlY4 = topHeight - this.generateTopCurveHeight_(height, padding); + + return `c ${controlX},${controlY} ${controlX2},${negativeDipHeight}` + + ` ${insideWidth},${negativeDipHeight}` + + `c ${controlX3},0 ${outsideWidth},${controlY4}` + + ` ${outsideWidth},${topHeight}`; } } diff --git a/demos/shape/heart/index.js b/demos/shape/heart/index.js index 0b944ed670f..2b0f3b5ec48 100644 --- a/demos/shape/heart/index.js +++ b/demos/shape/heart/index.js @@ -25,9 +25,4 @@ export default class DemoHeart extends MDCShape { getDefaultFoundation() { return new DemoHeartFoundation(this.createAdapter()); } - - initialSyncWithDOM() { - super.initialSyncWithDOM(); - this.foundation_.redraw(); - } } diff --git a/demos/shape/shape.html b/demos/shape/index.html similarity index 95% rename from demos/shape/shape.html rename to demos/shape/index.html index fb5b812f47a..f3b11c26687 100644 --- a/demos/shape/shape.html +++ b/demos/shape/index.html @@ -89,13 +89,16 @@ let elevatableHeart; for (var i=0; i { - elevatableHeart.elevation = elevationInput.value; + elevatableHeart.elevation = parseInt(elevationInput.value); }); } })(); diff --git a/demos/shape/shape.scss b/demos/shape/shape.scss index ba6813f3ce0..d822e492f7d 100644 --- a/demos/shape/shape.scss +++ b/demos/shape/shape.scss @@ -8,8 +8,6 @@ fieldset { margin-bottom: 16px; } .mdc-shape { - --mdc-shape-elevation: 4; - --mdc-shape-background: #FF00FF; width: 100px; height: 100px; margin: 16px auto; diff --git a/packages/material-components-web/index.js b/packages/material-components-web/index.js index 1590898b91e..bbe26dc142e 100644 --- a/packages/material-components-web/index.js +++ b/packages/material-components-web/index.js @@ -19,6 +19,7 @@ import * as base from '@material/base'; import * as checkbox from '@material/checkbox'; import * as dialog from '@material/dialog'; import * as drawer from '@material/drawer'; +import * as elevation from '@material/elevation'; import * as formField from '@material/form-field'; import * as gridList from '@material/grid-list'; import * as iconToggle from '@material/icon-toggle'; @@ -61,6 +62,7 @@ export { checkbox, dialog, drawer, + elevation, formField, gridList, iconToggle, diff --git a/packages/mdc-elevation/constants.js b/packages/mdc-elevation/constants.js new file mode 100644 index 00000000000..fc7b77089dc --- /dev/null +++ b/packages/mdc-elevation/constants.js @@ -0,0 +1,183 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const UMBRA_OFFSET_Y = [ + 0, + 2, + 3, + 3, + 2, + 3, + 3, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 7, + 8, + 8, + 8, + 9, + 9, + 10, + 10, + 10, + 11, + 11, +]; + +export const UMBRA_BLUR = [ + 0, + 1, + 1, + 3, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 7, + 8, + 8, + 9, + 9, + 10, + 11, + 11, + 12, + 13, + 13, + 14, + 14, + 15, +]; + +export const UMBRA_SPREAD = [ + 0, + -1, + -2, + -2, + -1, + -1, + -1, + -2, + -3, + -3, + -3, + -4, + -4, + -4, + -4, + -5, + -5, + -5, + -5, + -6, + -6, + -6, + -6, + -7, + -7, +]; + +export const PENUMBRA_BLUR = [ + 0, + 1, + 2, + 4, + 5, + 8, + 10, + 10, + 10, + 12, + 14, + 15, + 17, + 19, + 21, + 22, + 24, + 26, + 28, + 29, + 31, + 33, + 35, + 36, + 38, +]; + +export const AMBIENT_OFFSET_Y = [ + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 5, + 6, + 6, + 6, + 7, + 7, + 8, + 8, + 8, + 9, + 9, +]; + +export const AMBIENT_BLUR = [ + 0, + 3, + 5, + 8, + 10, + 14, + 18, + 16, + 14, + 16, + 18, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, +]; diff --git a/packages/mdc-elevation/index.js b/packages/mdc-elevation/index.js new file mode 100644 index 00000000000..dda27a40aad --- /dev/null +++ b/packages/mdc-elevation/index.js @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as constants from './constants'; + +export {constants}; diff --git a/packages/mdc-shape/README.md b/packages/mdc-shape/README.md index 2060fc25eed..d4533df0f93 100644 --- a/packages/mdc-shape/README.md +++ b/packages/mdc-shape/README.md @@ -49,7 +49,19 @@ The adapter for shape must provide the following functions, with correct signatu | `getCanvasWidth() => number` | Returns the width of the canvas element. | | `getCanvasHeight() => number` | Returns the height of the canvas element. | | `getDevicePixelRatio() => number` | Returns the device pixel ratio. | -| `create2dRenderingContext() => {shadowColor: string, shadowBlur: number, shadowOffsetY: number, fillStyle: string, scale: (number, number), clearRect: (number, number, number, number), fill: (Path2D)}` | Returns an object which has the shape of a CanvasRenderingContext2d instance. An easy way to achieve this is simply `this.root_.querySelector(mdc.shape.MDCShapeFoundation.SHAPE_SELECTOR).getContext('2d');`. | + +#### MDCShapeAdapter.create2dRenderingContext + +Returns an object which has the shape of a CanvasRenderingContext2d instance. An easy way to achieve this is simply `this.root_.querySelector(mdc.shape.MDCShapeFoundation.SHAPE_SELECTOR).getContext('2d');` + +The full API must cover: +* shadowColor: string +* shadowBlur: number +* shadowOffsetY: number +* fillStyle: string +* scale: (number, number) +* clearRect: (number, number, number, number) +* fill: (Path2D) ### Extending the Foundation Class @@ -69,32 +81,17 @@ MDC Shape exposes the following methods: ### Shape Customization -There are two ways to customize your shape's elevation and background color. The first way is to use the MDCShape's API. +To customize your shape's elevation and background color, use the MDCShape's API. -To modify the elevation of a shape, call the background setter +To modify the elevation of a shape, set the background ``` mdcShape.background = '#FOO'; ``` -To modify the elevation of a shape, call the elevation setter +To modify the elevation of a shape, set the elevation ``` mdcShape.elevation = 4; ``` - -The second way is to use custom CSS properties. - -### CSS custom properties - -To modify the elevation of a shape, set custom CSS properties on the mdc-shape element - -``` ---mdc-shape-elevation: 4; -``` - -To modify the background of a shape, set custom CSS properties on the mdc-shape element - -``` ---mdc-shape-background: #FF0000; ``` \ No newline at end of file diff --git a/packages/mdc-shape/_variables.scss b/packages/mdc-shape/_variables.scss index ac64b8310a7..77c7dee70bd 100644 --- a/packages/mdc-shape/_variables.scss +++ b/packages/mdc-shape/_variables.scss @@ -15,4 +15,3 @@ // $mdc-shape-shadow-padding: 56px; -$mdc-shape-shadow-negative-padding: -1 * $mdc-shape-shadow-padding; diff --git a/packages/mdc-shape/component.js b/packages/mdc-shape/component.js index f0bf49653ca..1f0d0e7db92 100644 --- a/packages/mdc-shape/component.js +++ b/packages/mdc-shape/component.js @@ -16,7 +16,6 @@ import {MDCComponent} from '@material/base'; import MDCShapeFoundation from './foundation'; -import {getElevationFromDOM, getBackgroundFromDOM} from './util'; export default class MDCShape extends MDCComponent { set background(value) { @@ -42,11 +41,6 @@ export default class MDCShape extends MDCComponent { setClipPathData: (value) => this.path_.setAttribute('d', value)}; } - initialSyncWithDOM() { - this.foundation_.setBackground(getBackgroundFromDOM(this.root_), false); - this.foundation_.setElevation(getElevationFromDOM(this.root_), false); - } - get canvas_() { return this.root_.querySelector(MDCShapeFoundation.strings.CANVAS_SELECTOR); } diff --git a/packages/mdc-shape/foundation.js b/packages/mdc-shape/foundation.js index 195f9baf723..0ddb0bb0201 100644 --- a/packages/mdc-shape/foundation.js +++ b/packages/mdc-shape/foundation.js @@ -14,7 +14,7 @@ * limitations under the License. */ import {MDCFoundation} from '@material/base'; - +import {constants} from '@material/elevation'; import {cssClasses, strings, numbers} from './constants'; export default class MDCShapeFoundation extends MDCFoundation { @@ -70,11 +70,8 @@ export default class MDCShapeFoundation extends MDCFoundation { } init() { - const devicePixelRatio = this.adapter_.getDevicePixelRatio(); - this.adapter_.setCanvasWidth(this.adapter_.getCanvasWidth() * devicePixelRatio); - this.adapter_.setCanvasHeight(this.adapter_.getCanvasHeight() * devicePixelRatio); this.ctx_ = this.adapter_.create2dRenderingContext(); - this.ctx_.scale(devicePixelRatio, devicePixelRatio); + this.syncWithCanvas_(); } destroy() { @@ -91,15 +88,7 @@ export default class MDCShapeFoundation extends MDCFoundation { this.elevating_ = false; this.elevation_ = value; - if (!animate) { - this.ambientShadowBlur_ = this.finalAmbientShadowBlur_; - this.ambientShadowOffsetY_ = this.finalAmbientShadowOffsetY_; - this.penumbraShadowBlur_ = this.finalPenumbraShadowBlur_; - this.penumbraShadowOffsetY_ = this.finalPenumbraShadowOffsetY_; - this.umbraShadowBlur_ = this.finalUmbraShadowBlur_; - this.umbraShadowOffsetY_ = this.finalUmbraShadowOffsetY_; - this.umbraShadowSpread_ = this.finalUmbraShadowSpread_; - } else { + if (animate) { const factor = 1 / numbers.ELEVATION_ANIMATION_FRAME_COUNT; this.ambientShadowBlurIncrement_ = (this.finalAmbientShadowBlur_ - this.ambientShadowBlur_) * factor; @@ -117,15 +106,30 @@ export default class MDCShapeFoundation extends MDCFoundation { (this.finalUmbraShadowSpread_ - this.umbraShadowSpread_) * factor; this.elevating_ = true; this.animationFrameId_ = requestAnimationFrame(() => this.elevate_()); + } else { + this.ambientShadowBlur_ = this.finalAmbientShadowBlur_; + this.ambientShadowOffsetY_ = this.finalAmbientShadowOffsetY_; + this.penumbraShadowBlur_ = this.finalPenumbraShadowBlur_; + this.penumbraShadowOffsetY_ = this.finalPenumbraShadowOffsetY_; + this.umbraShadowBlur_ = this.finalUmbraShadowBlur_; + this.umbraShadowOffsetY_ = this.finalUmbraShadowOffsetY_; + this.umbraShadowSpread_ = this.finalUmbraShadowSpread_; } } redraw() { + this.syncWithCanvas_(); this.clear_(); - this.init(); this.draw_(); } + syncWithCanvas_() { + const devicePixelRatio = this.adapter_.getDevicePixelRatio(); + this.adapter_.setCanvasWidth(this.adapter_.getCanvasWidth() * devicePixelRatio); + this.adapter_.setCanvasHeight(this.adapter_.getCanvasHeight() * devicePixelRatio); + this.ctx_.scale(devicePixelRatio, devicePixelRatio); + } + generateClipPath_() { return this.generatePath_( this.adapter_.getCanvasWidth() - (numbers.SHADOW_PADDING * 2), @@ -222,11 +226,11 @@ export default class MDCShapeFoundation extends MDCFoundation { } get finalAmbientShadowOffsetY_() { - return AMBIENT_OFFSET_Y[this.elevation_]; + return constants.AMBIENT_OFFSET_Y[this.elevation_]; } get finalAmbientShadowBlur_() { - return AMBIENT_BLUR[this.elevation_]; + return constants.AMBIENT_BLUR[this.elevation_]; } get finalPenumbraShadowOffsetY_() { @@ -234,186 +238,18 @@ export default class MDCShapeFoundation extends MDCFoundation { } get finalPenumbraShadowBlur_() { - return PENUMBRA_BLUR[this.elevation_]; + return constants.PENUMBRA_BLUR[this.elevation_]; } get finalUmbraShadowOffsetY_() { - return UMBRA_OFFSET_Y[this.elevation_]; + return constants.UMBRA_OFFSET_Y[this.elevation_]; } get finalUmbraShadowBlur_() { - return UMBRA_BLUR[this.elevation_]; + return constants.UMBRA_BLUR[this.elevation_]; } get finalUmbraShadowSpread_() { - return UMBRA_SPREAD[this.elevation_]; + return constants.UMBRA_SPREAD[this.elevation_]; } } - -const UMBRA_OFFSET_Y = [ - 0, - 2, - 3, - 3, - 2, - 3, - 3, - 4, - 5, - 5, - 6, - 6, - 7, - 7, - 7, - 8, - 8, - 8, - 9, - 9, - 10, - 10, - 10, - 11, - 11, -]; - -const UMBRA_BLUR = [ - 0, - 1, - 1, - 3, - 4, - 5, - 5, - 5, - 5, - 6, - 6, - 7, - 8, - 8, - 9, - 9, - 10, - 11, - 11, - 12, - 13, - 13, - 14, - 14, - 15, -]; - -const UMBRA_SPREAD = [ - 0, - -1, - -2, - -2, - -1, - -1, - -1, - -2, - -3, - -3, - -3, - -4, - -4, - -4, - -4, - -5, - -5, - -5, - -5, - -6, - -6, - -6, - -6, - -7, - -7, -]; - -const PENUMBRA_BLUR = [ - 0, - 1, - 2, - 4, - 5, - 8, - 10, - 10, - 10, - 12, - 14, - 15, - 17, - 19, - 21, - 22, - 24, - 26, - 28, - 29, - 31, - 33, - 35, - 36, - 38, -]; - -const AMBIENT_OFFSET_Y = [ - 0, - 1, - 1, - 1, - 1, - 1, - 1, - 2, - 3, - 3, - 4, - 4, - 5, - 5, - 5, - 6, - 6, - 6, - 7, - 7, - 8, - 8, - 8, - 9, - 9, -]; - -const AMBIENT_BLUR = [ - 0, - 3, - 5, - 8, - 10, - 14, - 18, - 16, - 14, - 16, - 18, - 20, - 22, - 24, - 26, - 28, - 30, - 32, - 34, - 36, - 38, - 40, - 42, - 44, - 46, -]; diff --git a/packages/mdc-shape/index.js b/packages/mdc-shape/index.js index cb224e9a425..4d8a49cca66 100644 --- a/packages/mdc-shape/index.js +++ b/packages/mdc-shape/index.js @@ -16,6 +16,5 @@ import MDCShape from './component'; import MDCShapeFoundation from './foundation'; -import {getElevationFromDOM, getBackgroundFromDOM} from './util'; -export {MDCShape, MDCShapeFoundation, getElevationFromDOM, getBackgroundFromDOM}; +export {MDCShape, MDCShapeFoundation}; diff --git a/packages/mdc-shape/mdc-shape.scss b/packages/mdc-shape/mdc-shape.scss index a808682f9e4..9ea266e235d 100644 --- a/packages/mdc-shape/mdc-shape.scss +++ b/packages/mdc-shape/mdc-shape.scss @@ -16,8 +16,6 @@ @import "./variables"; -$mdc-shape-shadow-double-padding: 2 * $mdc-shape-shadow-padding; - .mdc-shape { position: relative; min-width: $mdc-shape-shadow-padding; @@ -25,10 +23,10 @@ $mdc-shape-shadow-double-padding: 2 * $mdc-shape-shadow-padding; &__canvas { position: absolute; - top: $mdc-shape-shadow-negative-padding; - left: $mdc-shape-shadow-negative-padding; - width: calc(100% + #{$mdc-shape-shadow-double-padding}); - height: calc(100% + #{$mdc-shape-shadow-double-padding}); + top: -1 * $mdc-shape-shadow-padding; + left: -1 * $mdc-shape-shadow-padding; + width: calc(100% + #{2 * $mdc-shape-shadow-padding}); + height: calc(100% + #{2 * $mdc-shape-shadow-padding}); pointer-events: none; } diff --git a/packages/mdc-shape/package.json b/packages/mdc-shape/package.json index 135e7fb4405..f98206beca0 100644 --- a/packages/mdc-shape/package.json +++ b/packages/mdc-shape/package.json @@ -9,7 +9,8 @@ "url": "https://github.com/material-components/material-components-web.git" }, "dependencies": { - "@material/base": "^0.2.6" + "@material/base": "^0.24.0", + "@material/elevation": "^0.1.13" }, "private": "true", "publishConfig": { diff --git a/packages/mdc-shape/util.js b/packages/mdc-shape/util.js deleted file mode 100644 index e6589023eff..00000000000 --- a/packages/mdc-shape/util.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export function getElevationFromDOM(el) { - let elevation = 0; - let mdcShapeElevation = - getComputedStyle(el).getPropertyValue('--mdc-shape-elevation'); - - if (!mdcShapeElevation) { - mdcShapeElevation = el.style.getPropertyValue('--mdc-shape-elevation'); - } - - if (mdcShapeElevation) { - elevation = parseInt(mdcShapeElevation); - } - - return elevation; -} - -export function getBackgroundFromDOM(el) { - let background = '#FFF'; - let mdcShapeBackground = - getComputedStyle(el).getPropertyValue('--mdc-shape-background'); - - if (!mdcShapeBackground) { - mdcShapeBackground = el.style.getPropertyValue('--mdc-shape-background'); - } - - if (mdcShapeBackground) { - background = mdcShapeBackground.trim(); - } - - return background; -} diff --git a/scripts/check-pkg-for-release.js b/scripts/check-pkg-for-release.js index f7a192c9a2f..83784899222 100644 --- a/scripts/check-pkg-for-release.js +++ b/scripts/check-pkg-for-release.js @@ -52,10 +52,11 @@ function main() { checkPublicConfigForNewComponent(); if (pkg.name !== MASTER_PKG.name) { checkNameIsPresentInAllowedScope(); - } - if (NOT_PUBLISHABLE.indexOf(pkg.name) !== -1) { - checkDependencyAddedInWebpackConfig(); - checkDependencyAddedInMDCPackage(); + const nameCamel = camelCase(pkg.name.replace('@material/', '')); + if (NOT_PUBLISHABLE.indexOf(nameCamel) === -1) { + checkDependencyAddedInWebpackConfig(); + checkDependencyAddedInMDCPackage(); + } } } diff --git a/test/unit/mdc-shape/util.test.js b/test/unit/mdc-shape/util.test.js deleted file mode 100644 index 141a29a3f9f..00000000000 --- a/test/unit/mdc-shape/util.test.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {assert} from 'chai'; -import bel from 'bel'; - -import {getElevationFromDOM, getBackgroundFromDOM} from '../../../packages/mdc-shape/util'; - -suite('MDCShape - util'); - -test('getElevationFromDOM', () => { - const el = bel`
`; - assert.equal(getElevationFromDOM(el), 4); -}); - -test('getBackgroundFromDOM', () => { - const el = bel`
`; - assert.equal(getBackgroundFromDOM(el), '#FF0000'); -}); From 83c022cc4c2bb2dee255edf480803ca762f2533a Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Fri, 23 Mar 2018 16:18:50 -0400 Subject: [PATCH 4/4] WIP Fix error preventing testing in IE11 (IE11 and Edge lack full Path2D support though) --- demos/shape/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/shape/index.html b/demos/shape/index.html index f3b11c26687..b888ba785a3 100644 --- a/demos/shape/index.html +++ b/demos/shape/index.html @@ -97,11 +97,11 @@ } } const elevationInput = document.querySelector("#elevation-input"); - elevationInput.addEventListener("change", () => { + elevationInput.addEventListener("change", function() { elevatableHeart.elevation = parseInt(elevationInput.value); }); } })(); - \ No newline at end of file +