diff --git a/package-lock.json b/package-lock.json
index 02eae2f96..17539454c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -939,7 +939,7 @@
"dependencies": {
"chalk": {
"version": "2.3.1",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"dev": true,
"requires": {
@@ -1239,7 +1239,7 @@
},
"@types/node": {
"version": "8.9.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
+ "resolved": "http://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
"integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==",
"dev": true
},
@@ -1555,7 +1555,7 @@
},
"acorn": {
"version": "2.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
+ "resolved": "http://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
"dev": true
},
@@ -1578,7 +1578,7 @@
},
"acorn-globals": {
"version": "1.0.9",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz",
+ "resolved": "http://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz",
"integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=",
"dev": true,
"requires": {
@@ -1593,7 +1593,7 @@
},
"adjust-sourcemap-loader": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz",
"integrity": "sha512-958oaHHVEXMvsY7v7cC5gEkNIcoaAVIhZ4mBReYVZJOTP9IgKmzLjIOhTtzpLMu+qriXvLsVjJ155EeInp45IQ==",
"dev": true,
"requires": {
@@ -2426,7 +2426,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -2750,7 +2750,7 @@
},
"browserify-aes": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"dev": true,
"requires": {
@@ -2787,7 +2787,7 @@
},
"browserify-rsa": {
"version": "4.0.1",
- "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
"dev": true,
"requires": {
@@ -3747,7 +3747,7 @@
"dependencies": {
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -4051,7 +4051,7 @@
},
"create-hash": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"dev": true,
"requires": {
@@ -4064,7 +4064,7 @@
},
"create-hmac": {
"version": "1.1.7",
- "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"dev": true,
"requires": {
@@ -4607,7 +4607,7 @@
},
"detective-amd": {
"version": "2.4.0",
- "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-2.4.0.tgz",
+ "resolved": "http://registry.npmjs.org/detective-amd/-/detective-amd-2.4.0.tgz",
"integrity": "sha1-XrDfTvXBipQDOwfa8TbbzV/HXNU=",
"dev": true,
"requires": {
@@ -4885,7 +4885,7 @@
},
"diffie-hellman": {
"version": "5.0.3",
- "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
"dev": true,
"requires": {
@@ -4955,7 +4955,7 @@
},
"dom-converter": {
"version": "0.1.4",
- "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz",
+ "resolved": "http://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz",
"integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=",
"dev": true,
"requires": {
@@ -5072,7 +5072,7 @@
},
"readable-stream": {
"version": "1.1.14",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
@@ -5618,7 +5618,7 @@
},
"express": {
"version": "4.16.3",
- "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
+ "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz",
"integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
"dev": true,
"requires": {
@@ -6834,7 +6834,6 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
"integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
"dev": true,
- "optional": true,
"requires": {
"nan": "2.11.0",
"node-pre-gyp": "0.10.0"
@@ -6843,8 +6842,7 @@
"abbrev": {
"version": "1.1.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ansi-regex": {
"version": "2.1.1",
@@ -6854,14 +6852,12 @@
"aproba": {
"version": "1.2.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"are-we-there-yet": {
"version": "1.1.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.6"
@@ -6884,8 +6880,7 @@
"chownr": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"code-point-at": {
"version": "1.1.0",
@@ -6905,8 +6900,7 @@
"core-util-is": {
"version": "1.0.2",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"debug": {
"version": "2.6.9",
@@ -6920,26 +6914,22 @@
"deep-extend": {
"version": "0.5.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"detect-libc": {
"version": "1.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"fs-minipass": {
"version": "1.2.5",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minipass": "2.2.4"
}
@@ -6947,14 +6937,12 @@
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"aproba": "1.2.0",
"console-control-strings": "1.1.0",
@@ -6970,7 +6958,6 @@
"version": "7.1.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
@@ -6983,14 +6970,12 @@
"has-unicode": {
"version": "2.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"iconv-lite": {
"version": "0.4.21",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safer-buffer": "2.1.2"
}
@@ -7008,7 +6993,6 @@
"version": "1.0.6",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
@@ -7022,8 +7006,7 @@
"ini": {
"version": "1.3.5",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
@@ -7036,8 +7019,7 @@
"isarray": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"minimatch": {
"version": "3.0.4",
@@ -7065,7 +7047,6 @@
"version": "1.1.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minipass": "2.2.4"
}
@@ -7088,7 +7069,6 @@
"version": "2.2.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"debug": "2.6.9",
"iconv-lite": "0.4.21",
@@ -7099,7 +7079,6 @@
"version": "0.10.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"detect-libc": "1.0.3",
"mkdirp": "0.5.1",
@@ -7143,7 +7122,6 @@
"version": "4.1.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
@@ -7208,7 +7186,6 @@
"version": "1.2.7",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"deep-extend": "0.5.1",
"ini": "1.3.5",
@@ -7228,7 +7205,6 @@
"version": "2.3.6",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
@@ -7243,7 +7219,6 @@
"version": "2.6.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"glob": "7.1.2"
}
@@ -7256,32 +7231,27 @@
"safer-buffer": {
"version": "2.1.2",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"sax": {
"version": "1.2.4",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"semver": {
"version": "5.5.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"string-width": {
"version": "1.0.2",
@@ -7297,7 +7267,6 @@
"version": "1.1.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "5.1.1"
}
@@ -7313,14 +7282,12 @@
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"tar": {
"version": "4.4.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"chownr": "1.0.1",
"fs-minipass": "1.2.5",
@@ -7334,14 +7301,12 @@
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"wide-align": {
"version": "1.1.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"string-width": "1.0.2"
}
@@ -7403,7 +7368,7 @@
},
"get-amd-module-type": {
"version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-2.0.5.tgz",
+ "resolved": "http://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-2.0.5.tgz",
"integrity": "sha1-5nHsWpatX79To6IqKJ6SOMdy3bA=",
"dev": true,
"requires": {
@@ -7479,7 +7444,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -7797,7 +7762,7 @@
},
"readable-stream": {
"version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
@@ -7960,7 +7925,7 @@
},
"lodash": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
+ "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
"integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=",
"dev": true
},
@@ -7996,7 +7961,7 @@
"dependencies": {
"minimist": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
"integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
"dev": true
}
@@ -8046,7 +8011,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -8200,7 +8165,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -8387,7 +8352,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -8461,7 +8426,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -8601,7 +8566,7 @@
},
"readable-stream": {
"version": "2.0.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"dev": true,
"requires": {
@@ -8799,7 +8764,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -9216,7 +9181,7 @@
},
"html-webpack-plugin": {
"version": "3.2.0",
- "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
"integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
"dev": true,
"requires": {
@@ -9251,7 +9216,7 @@
},
"http-errors": {
"version": "1.6.3",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"dev": true,
"requires": {
@@ -9280,7 +9245,7 @@
},
"http-proxy-middleware": {
"version": "0.18.0",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
+ "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
"integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==",
"dev": true,
"requires": {
@@ -10077,7 +10042,7 @@
},
"is-obj": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
@@ -11035,7 +11000,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -11312,7 +11277,7 @@
"dependencies": {
"ansi-regex": {
"version": "0.2.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz",
+ "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz",
"integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=",
"dev": true
},
@@ -11324,7 +11289,7 @@
},
"chalk": {
"version": "0.5.1",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
"integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=",
"dev": true,
"requires": {
@@ -11346,7 +11311,7 @@
},
"mkdirp": {
"version": "0.3.5",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=",
"dev": true
},
@@ -11827,7 +11792,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -11875,7 +11840,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -12321,7 +12286,7 @@
},
"magic-string": {
"version": "0.22.5",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
+ "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
"dev": true,
"requires": {
@@ -12878,7 +12843,7 @@
},
"minimist": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@@ -12962,7 +12927,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
@@ -12971,7 +12936,7 @@
"dependencies": {
"minimist": {
"version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
}
@@ -13003,7 +12968,7 @@
},
"module-definition": {
"version": "2.2.4",
- "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-2.2.4.tgz",
+ "resolved": "http://registry.npmjs.org/module-definition/-/module-definition-2.2.4.tgz",
"integrity": "sha1-wKN3HeWM9rzxKu0kdnBsWWrUsss=",
"dev": true,
"requires": {
@@ -13250,7 +13215,7 @@
"dependencies": {
"buffer": {
"version": "4.9.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+ "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true,
"requires": {
@@ -13339,7 +13304,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -13407,7 +13372,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -14298,7 +14263,7 @@
},
"onetime": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
@@ -14323,7 +14288,7 @@
"dependencies": {
"minimist": {
"version": "0.0.10",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
"dev": true
}
@@ -14450,7 +14415,7 @@
},
"os-locale": {
"version": "1.4.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true,
"requires": {
@@ -14549,7 +14514,7 @@
},
"parse-asn1": {
"version": "5.1.1",
- "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
+ "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
"integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
"dev": true,
"requires": {
@@ -14940,7 +14905,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -15093,7 +15058,7 @@
},
"minimist": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
"integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
"dev": true
}
@@ -15150,7 +15115,7 @@
},
"postcss-values-parser": {
"version": "1.5.0",
- "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz",
+ "resolved": "http://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz",
"integrity": "sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ==",
"dev": true,
"requires": {
@@ -15561,7 +15526,7 @@
},
"readable-stream": {
"version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -15957,7 +15922,7 @@
},
"readable-stream": {
"version": "2.0.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"dev": true,
"requires": {
@@ -16100,7 +16065,7 @@
},
"readable-stream": {
"version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
@@ -16385,7 +16350,7 @@
"dependencies": {
"convert-source-map": {
"version": "0.3.5",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
+ "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
"integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
"dev": true
}
@@ -16433,7 +16398,7 @@
},
"rollup": {
"version": "0.56.5",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.56.5.tgz",
+ "resolved": "http://registry.npmjs.org/rollup/-/rollup-0.56.5.tgz",
"integrity": "sha512-IGPk5vdWrsc4vkiW9XMeXr5QMtxmvATTttTi59w2jBQWe9G/MMQtn8teIBAj+DdK51TrpVT6P0aQUaQUlUYCJA==",
"dev": true
},
@@ -16493,7 +16458,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -16555,7 +16520,7 @@
"dependencies": {
"es6-promise": {
"version": "3.3.1",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
"dev": true
}
@@ -16591,7 +16556,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@@ -16912,7 +16877,7 @@
},
"yargs": {
"version": "11.1.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
"integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
"dev": true,
"requires": {
@@ -17125,7 +17090,7 @@
},
"sha.js": {
"version": "2.4.11",
- "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"dev": true,
"requires": {
@@ -17550,7 +17515,7 @@
},
"spdx-license-ids": {
"version": "1.2.2",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+ "resolved": "http://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
"integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
"dev": true
}
@@ -18421,7 +18386,7 @@
},
"through": {
"version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
@@ -19529,7 +19494,7 @@
},
"readable-stream": {
"version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
@@ -20087,7 +20052,7 @@
},
"request": {
"version": "2.85.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz",
+ "resolved": "http://registry.npmjs.org/request/-/request-2.85.0.tgz",
"integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==",
"dev": true,
"requires": {
@@ -21509,7 +21474,7 @@
},
"wrap-ansi": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
diff --git a/src/cdk/keycodes/keycodes.ts b/src/cdk/keycodes/keycodes.ts
index 156c0d1b9..04d8f70b2 100644
--- a/src/cdk/keycodes/keycodes.ts
+++ b/src/cdk/keycodes/keycodes.ts
@@ -109,6 +109,7 @@ export const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
export const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
export const COMMA = 188;
export const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
+export const PERIOD = 190;
export const SLASH = 191;
export const APOSTROPHE = 192;
export const TILDE = 192;
diff --git a/src/cdk/testing/dispatch-events.ts b/src/cdk/testing/dispatch-events.ts
index ebecde019..1548e0b02 100644
--- a/src/cdk/testing/dispatch-events.ts
+++ b/src/cdk/testing/dispatch-events.ts
@@ -14,24 +14,30 @@ export function dispatchEvent(node: Node | Window, event: Event): Event {
}
/** Shorthand to dispatch a fake event on a specified node. */
+// tslint:disable-next-line:no-reserved-keywords
export function dispatchFakeEvent(node: Node | Window, type: string, canBubble?: boolean): Event {
return dispatchEvent(node, createFakeEvent(type, canBubble));
}
/** Shorthand to dispatch a keyboard event with a specified key code. */
-export function dispatchKeyboardEvent(node: Node, type: string, keyCode: number, target?: Element):
+// tslint:disable-next-line:no-reserved-keywords
+export function dispatchKeyboardEvent(node: Node, type: string, keyCode: number, target?: Element,
+ shiftKey = false, ctrlKey = false, altKey = false):
KeyboardEvent {
- return dispatchEvent(node, createKeyboardEvent(type, keyCode, target)) as KeyboardEvent;
+ const event = createKeyboardEvent(type, keyCode, target, undefined, shiftKey, ctrlKey, altKey);
+
+ return dispatchEvent(node, event) as KeyboardEvent;
}
/** Shorthand to dispatch a mouse event on the specified coordinates. */
-// tslint:disable-next-line
+// tslint:disable-next-line:no-reserved-keywords
export function dispatchMouseEvent(node: Node, type: string, x = 0, y = 0,
event = createMouseEvent(type, x, y)): MouseEvent {
return dispatchEvent(node, event) as MouseEvent;
}
/** Shorthand to dispatch a touch event on the specified coordinates. */
+// tslint:disable-next-line:no-reserved-keywords
export function dispatchTouchEvent(node: Node, type: string, x = 0, y = 0) {
return dispatchEvent(node, createTouchEvent(type, x, y));
}
diff --git a/src/cdk/testing/event-objects.ts b/src/cdk/testing/event-objects.ts
index 108ebe67f..9d74e2be5 100644
--- a/src/cdk/testing/event-objects.ts
+++ b/src/cdk/testing/event-objects.ts
@@ -1,5 +1,5 @@
/** Creates a browser MouseEvent with the specified options. */
-// tslint:disable-next-line
+// tslint:disable-next-line:no-reserved-keywords
export function createMouseEvent(type: string, x = 0, y = 0) {
const event = document.createEvent('MouseEvent');
@@ -23,27 +23,28 @@ export function createMouseEvent(type: string, x = 0, y = 0) {
}
/** Creates a browser TouchEvent with the specified pointer coordinates. */
-// tslint:disable-next-line
+// tslint:disable-next-line:no-reserved-keywords
export function createTouchEvent(type: string, pageX = 0, pageY = 0) {
// In favor of creating events that work for most of the browsers, the event is created
// as a basic UI Event. The necessary details for the event will be set manually.
const event = document.createEvent('UIEvent');
- const touchDetails = {pageX, pageY};
+ const touchDetails = { pageX, pageY };
event.initUIEvent(type, true, true, window, 0);
// Most of the browsers don't have a "initTouchEvent" method that can be used to define
// the touch details.
Object.defineProperties(event, {
- touches: {value: [touchDetails]}
+ touches: { value: [touchDetails] }
});
return event;
}
/** Dispatches a keydown event from an element. */
-// tslint:disable-next-line
-export function createKeyboardEvent(type: string, keyCode: number, target?: Element, key?: string) {
+// tslint:disable-next-line:no-reserved-keywords
+export function createKeyboardEvent(type: string, keyCode: number, target?: Element, key?: string,
+ shiftKey = false, ctrlKey = false, altKey = false) {
const event = document.createEvent('KeyboardEvent') as any;
// Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`.
const initEventFn = (event.initKeyEvent || event.initKeyboardEvent).bind(event);
@@ -54,9 +55,12 @@ export function createKeyboardEvent(type: string, keyCode: number, target?: Elem
// Webkit Browsers don't set the keyCode when calling the init function.
// See related bug https://bugs.webkit.org/show_bug.cgi?id=16735
Object.defineProperties(event, {
- keyCode: {get: () => keyCode},
- key: {get: () => key},
- target: {get: () => target}
+ keyCode: { get: () => keyCode },
+ key: { get: () => key },
+ target: { get: () => target },
+ shiftKey: { get: () => shiftKey },
+ ctrlKey: { get: () => ctrlKey },
+ altKey: { get: () => altKey }
});
// IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
@@ -70,7 +74,7 @@ export function createKeyboardEvent(type: string, keyCode: number, target?: Elem
}
/** Creates a fake event object with any desired event type. */
-// tslint:disable-next-line
+// tslint:disable-next-line:no-reserved-keywords
export function createFakeEvent(type: string, canBubble = false, cancelable = true) {
const event = document.createEvent('Event');
event.initEvent(type, canBubble, cancelable);
diff --git a/src/lib-dev/input/module.ts b/src/lib-dev/input/module.ts
index 971d846ad..a44343479 100644
--- a/src/lib-dev/input/module.ts
+++ b/src/lib-dev/input/module.ts
@@ -16,6 +16,7 @@ import { McInputModule } from '../../lib/input/';
})
export class InputDemoComponent {
value: string = '';
+ numberValue: number | null = null;
}
@@ -36,6 +37,7 @@ export class InputDemoComponent {
})
export class InputDemoModule {}
+// tslint:disable:no-console
platformBrowserDynamic()
.bootstrapModule(InputDemoModule)
.catch((error) => console.error(error));
diff --git a/src/lib-dev/input/template.html b/src/lib-dev/input/template.html
index 369ab302e..96fedee58 100644
--- a/src/lib-dev/input/template.html
+++ b/src/lib-dev/input/template.html
@@ -1,6 +1,62 @@
+
+ Number Value: {{numberValue}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Min = -5 Max = 7 Step = 0.5 Big step = 1.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/core/utils/__test__/utils.spec.ts b/src/lib/core/utils/__test__/utils.spec.ts
index 9a277a6e4..b167f7c0a 100644
--- a/src/lib/core/utils/__test__/utils.spec.ts
+++ b/src/lib/core/utils/__test__/utils.spec.ts
@@ -1,5 +1,7 @@
+// tslint:disable:no-magic-numbers
import { toBoolean } from '../utils';
+
describe('[Core]::utils', () => {
it('should work for null values', () => {
expect(toBoolean(null)).toBe(false);
diff --git a/src/lib/form-field/_form-field-theme.scss b/src/lib/form-field/_form-field-theme.scss
index 8b082da00..2b1cec915 100644
--- a/src/lib/form-field/_form-field-theme.scss
+++ b/src/lib/form-field/_form-field-theme.scss
@@ -70,7 +70,7 @@
color: mc-color($second, 400);
}
- mc-cleaner {
+ mc-cleaner {
.mc-cleaner__icon {
color: mc-color($second, 200);
}
@@ -79,6 +79,16 @@
color: darken(mc-color($second, 200), $hover-darken);
}
}
+
+ mc-stepper {
+ .mc-stepper-step-up, .mc-stepper-step-down {
+ color: mc-color($second, 200);
+
+ &:hover {
+ color: darken(mc-color($second, 200), $hover-darken);
+ }
+ }
+ }
}
}
diff --git a/src/lib/form-field/form-field-number-control.ts b/src/lib/form-field/form-field-number-control.ts
new file mode 100644
index 000000000..6032239ba
--- /dev/null
+++ b/src/lib/form-field/form-field-number-control.ts
@@ -0,0 +1,28 @@
+import { Observable } from 'rxjs';
+
+
+/** An interface which allows a control to work inside of a `MсFormField`. */
+export abstract class McFormFieldNumberControl {
+ /** The value of the control. */
+ value: T | null;
+
+ /**
+ * Stream that emits whenever the state of the control changes such that the parent `MсFormField`
+ * needs to run change detection.
+ */
+ readonly stateChanges: Observable;
+
+ /** the number step */
+ step: number;
+
+ /** the number big step */
+ bigStep: number;
+
+ /** Whether the control is focused. */
+ readonly focused: boolean;
+
+ /** Handles step up and down */
+ abstract stepUp(step: number): void;
+
+ abstract stepDown(step: number): void;
+}
diff --git a/src/lib/form-field/form-field.html b/src/lib/form-field/form-field.html
index ced189617..9371c308c 100644
--- a/src/lib/form-field/form-field.html
+++ b/src/lib/form-field/form-field.html
@@ -17,6 +17,8 @@
(click)="clearValue($event)">
+
+
diff --git a/src/lib/form-field/form-field.module.ts b/src/lib/form-field/form-field.module.ts
index 65d6456b6..4c1b3f15d 100644
--- a/src/lib/form-field/form-field.module.ts
+++ b/src/lib/form-field/form-field.module.ts
@@ -7,6 +7,7 @@ import { McCleaner } from './cleaner';
import { McFormField, McFormFieldWithoutBorders } from './form-field';
import { McHint } from './hint';
import { McPrefix } from './prefix';
+import { McStepper } from './stepper';
import { McSuffix } from './suffix';
@@ -17,7 +18,8 @@ import { McSuffix } from './suffix';
McHint,
McPrefix,
McSuffix,
- McCleaner
+ McCleaner,
+ McStepper
],
imports: [CommonModule, McIconModule],
exports: [
@@ -26,7 +28,8 @@ import { McSuffix } from './suffix';
McHint,
McPrefix,
McSuffix,
- McCleaner
+ McCleaner,
+ McStepper
]
})
export class McFormFieldModule {
diff --git a/src/lib/form-field/form-field.scss b/src/lib/form-field/form-field.scss
index a774871f9..6ce4edf48 100644
--- a/src/lib/form-field/form-field.scss
+++ b/src/lib/form-field/form-field.scss
@@ -51,7 +51,8 @@ $mc-form-field-border-size: 1px;
}
.mc-form-field_has-suffix,
-.mc-form-field_has-cleaner {
+.mc-form-field_has-cleaner,
+.mc-form-field_has-stepper {
.mc-input {
padding-right: 32px;
}
@@ -78,5 +79,25 @@ mc-cleaner {
width: 32px;
cursor: pointer;
+}
+
+mc-stepper {
+ position: absolute;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ top: 0;
+ bottom: 0;
+ right: 0;
+
+ width: 32px;
+
+ .mc-stepper-step-up, .mc-stepper-step-down {
+ cursor: pointer;
+ width: 32px;
+ text-align: center;
+ }
}
diff --git a/src/lib/form-field/form-field.ts b/src/lib/form-field/form-field.ts
index 4e2877868..503e8eb2d 100644
--- a/src/lib/form-field/form-field.ts
+++ b/src/lib/form-field/form-field.ts
@@ -20,8 +20,10 @@ import { startWith } from 'rxjs/operators';
import { McCleaner } from './cleaner';
import { McFormFieldControl } from './form-field-control';
import { getMcFormFieldMissingControlError } from './form-field-errors';
+import { McFormFieldNumberControl } from './form-field-number-control';
import { McHint } from './hint';
import { McPrefix } from './prefix';
+import { McStepper } from './stepper';
import { McSuffix } from './suffix';
@@ -51,7 +53,8 @@ export const _McFormFieldMixinBase: CanColorCtor & typeof McFormFieldBase
'[class.mc-form-field_disabled]': '_control.disabled',
'[class.mc-form-field_has-prefix]': 'hasPrefix',
'[class.mc-form-field_has-suffix]': 'hasSuffix',
- '[class.mc-form-field_has-cleaner]': 'hasCleaner',
+ '[class.mc-form-field_has-cleaner]': 'canShowCleaner',
+ '[class.mc-form-field_has-stepper]': 'canShowStepper',
'[class.mc-focused]': '_control.focused',
'[class.ng-untouched]': '_shouldForward("untouched")',
'[class.ng-touched]': '_shouldForward("touched")',
@@ -60,7 +63,9 @@ export const _McFormFieldMixinBase: CanColorCtor & typeof McFormFieldBase
'[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")',
- '(keydown)': 'onKeyDown($event)'
+ '(keydown)': 'onKeyDown($event)',
+ '(mouseenter)': 'onHoverChanged(true)',
+ '(mouseleave)': 'onHoverChanged(false)'
},
inputs: ['color'],
encapsulation: ViewEncapsulation.None,
@@ -71,14 +76,18 @@ export class McFormField extends _McFormFieldMixinBase implements
AfterContentInit, AfterContentChecked, AfterViewInit, CanColor {
@ContentChild(McFormFieldControl) _control: McFormFieldControl
;
+ @ContentChild(McFormFieldNumberControl) _numberControl: McFormFieldNumberControl;
@ContentChildren(McHint) _hint: QueryList;
@ContentChildren(McSuffix) _suffix: QueryList;
@ContentChildren(McPrefix) _prefix: QueryList;
@ContentChildren(McCleaner) _cleaner: QueryList;
+ @ContentChild(McStepper) _stepper: McStepper;
// Unique id for the internal form field label.
_labelId = `mc-form-field-label-${nextUniqueId++}`;
+ hovered: boolean = false;
+
constructor(public _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef) {
super(_elementRef);
}
@@ -88,6 +97,11 @@ export class McFormField extends _McFormFieldMixinBase implements
if (this._control.controlType) {
this._elementRef.nativeElement.classList
.add(`mc-form-field-type-${this._control.controlType}`);
+
+ if (this._numberControl && this.hasStepper) {
+ this._stepper.stepUp.subscribe(this.onStepUp.bind(this));
+ this._stepper.stepDown.subscribe(this.onStepDown.bind(this));
+ }
}
// Subscribe to changes in the child control state in order to update the form field UI.
@@ -96,9 +110,15 @@ export class McFormField extends _McFormFieldMixinBase implements
this._changeDetectorRef.markForCheck();
});
+ if (this._numberControl) {
+ this._numberControl.stateChanges.pipe(startWith())
+ .subscribe(() => {
+ this._changeDetectorRef.markForCheck();
+ });
+ }
+
// Run change detection if the value changes.
const valueChanges = this._control.ngControl && this._control.ngControl.valueChanges || EMPTY;
-
merge(valueChanges)
.subscribe(() => this._changeDetectorRef.markForCheck());
}
@@ -121,10 +141,13 @@ export class McFormField extends _McFormFieldMixinBase implements
}
onContainerClick($event) {
- return this._control.onContainerClick && this._control.onContainerClick($event);
+ if (this._control.onContainerClick) {
+ this._control.onContainerClick($event);
+ }
}
onKeyDown(e: KeyboardEvent): void {
+ // tslint:disable-next-line:deprecation
if (e.keyCode === ESCAPE &&
this._control.focused &&
this.hasCleaner) {
@@ -137,6 +160,25 @@ export class McFormField extends _McFormFieldMixinBase implements
}
}
+ onHoverChanged(isHovered: boolean) {
+ if (isHovered !== this.hovered) {
+ this.hovered = isHovered;
+ this._changeDetectorRef.markForCheck();
+ }
+ }
+
+ onStepUp() {
+ if (this._numberControl) {
+ this._numberControl.stepUp(this._numberControl.step);
+ }
+ }
+
+ onStepDown() {
+ if (this._numberControl) {
+ this._numberControl.stepDown(this._numberControl.step);
+ }
+ }
+
/** Determines whether a class from the NgControl should be forwarded to the host element. */
_shouldForward(prop: string): boolean {
const ngControl = this._control ? this._control.ngControl : null;
@@ -151,27 +193,46 @@ export class McFormField extends _McFormFieldMixinBase implements
}
}
- get hasHint() {
+ get hasHint(): boolean {
return this._hint && this._hint.length > 0;
}
- get hasSuffix() {
+ get hasSuffix(): boolean {
return this._suffix && this._suffix.length > 0;
}
- get hasPrefix() {
+ get hasPrefix(): boolean {
return this._prefix && this._prefix.length > 0;
}
- get hasCleaner() {
+ get hasCleaner(): boolean {
return this._cleaner && this._cleaner.length > 0;
}
- get canShowCleaner() {
+ get hasStepper(): boolean {
+ return !!this._stepper;
+ }
+
+ get canShowCleaner(): boolean {
return this.hasCleaner &&
- this._control && this._control.ngControl
- ? this._control.ngControl.value && !this._control.disabled
- : false;
+ this._control &&
+ this._control.ngControl
+ ? this._control.ngControl.value && !this._control.disabled
+ : false;
+ }
+
+
+ get disabled(): boolean {
+ return this._control && this._control.disabled;
+ }
+
+ get canShowStepper(): boolean {
+ return this._numberControl &&
+ !this.disabled &&
+ (
+ this._numberControl.focused ||
+ this.hovered
+ );
}
}
diff --git a/src/lib/form-field/public-api.ts b/src/lib/form-field/public-api.ts
index b0c69da79..c2e4d224c 100644
--- a/src/lib/form-field/public-api.ts
+++ b/src/lib/form-field/public-api.ts
@@ -1,8 +1,10 @@
export * from './form-field.module';
export * from './form-field';
export * from './form-field-control';
+export * from './form-field-number-control';
export * from './form-field-errors';
export * from './hint';
export * from './suffix';
export * from './prefix';
export * from './cleaner';
+export * from './stepper';
diff --git a/src/lib/form-field/stepper.ts b/src/lib/form-field/stepper.ts
new file mode 100644
index 000000000..a190e3ba1
--- /dev/null
+++ b/src/lib/form-field/stepper.ts
@@ -0,0 +1,26 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+
+
+@Component({
+ selector: 'mc-stepper',
+ template: `
+
+
+ `
+})
+export class McStepper {
+ @Output()
+ readonly stepUp: EventEmitter = new EventEmitter();
+ @Output()
+ readonly stepDown: EventEmitter = new EventEmitter();
+
+ onStepUp($event: MouseEvent) {
+ this.stepUp.emit();
+ $event.preventDefault();
+ }
+
+ onStepDown($event: MouseEvent) {
+ this.stepDown.emit();
+ $event.preventDefault();
+ }
+}
diff --git a/src/lib/icon/icon.scss b/src/lib/icon/icon.scss
index d9db8d798..a70cb1599 100644
--- a/src/lib/icon/icon.scss
+++ b/src/lib/icon/icon.scss
@@ -1,2 +1,26 @@
@import 'icon-base';
@import 'icon-theme';
+
+.mc-icon-rotate_90 {
+ transform: rotate(90deg);
+}
+
+.mc-icon-rotate_180 {
+ transform: rotate(180deg);
+}
+
+.mc-icon-rotate_270 {
+ transform: rotate(270deg);
+}
+
+.mc-icon-flip-h {
+ transform: scaleY(-1);
+}
+
+.mc-icon-flip-v {
+ transform: scaleX(-1);
+}
+
+.mc-icon-flip-vh {
+ transform: scale(-1);
+}
diff --git a/src/lib/input/input-number-validators.ts b/src/lib/input/input-number-validators.ts
new file mode 100644
index 000000000..5e655664e
--- /dev/null
+++ b/src/lib/input/input-number-validators.ts
@@ -0,0 +1,79 @@
+import { Directive, forwardRef, Input, OnChanges, Provider, SimpleChanges } from '@angular/core';
+import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn, Validators } from '@angular/forms';
+
+
+export const MIN_VALIDATOR: Provider = {
+ provide: NG_VALIDATORS,
+ useExisting: forwardRef(() => MinValidator),
+ multi: true
+};
+
+/**
+ * A directive which installs the {@link MinValidator} for any `formControlName`,
+ * `formControl`, or control with `ngModel` that also has a `min` attribute.
+ *
+ * @experimental
+ */
+@Directive({
+ selector: '[min][formControlName],[min][formControl],[min][ngModel]',
+ providers: [MIN_VALIDATOR],
+ host: {'[attr.min]': 'min ? min : null'}
+})
+export class MinValidator implements Validator, OnChanges {
+
+ @Input() min: string;
+ private _validator: ValidatorFn;
+ private _onChange: () => void;
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if ('min' in changes) {
+ this._createValidator();
+ if (this._onChange) { this._onChange(); }
+ }
+ }
+
+ validate(c: AbstractControl): ValidationErrors | null { return this._validator(c); }
+
+ registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
+
+ private _createValidator(): void { this._validator = Validators.min(parseInt(this.min, 10)); }
+}
+
+
+export const MAX_VALIDATOR: Provider = {
+ provide: NG_VALIDATORS,
+ useExisting: forwardRef(() => MaxValidator),
+ multi: true
+};
+
+/**
+ * A directive which installs the {@link MaxValidator} for any `formControlName`,
+ * `formControl`, or control with `ngModel` that also has a `min` attribute.
+ *
+ * @experimental
+ */
+@Directive({
+ selector: '[max][formControlName],[max][formControl],[max][ngModel]',
+ providers: [MAX_VALIDATOR],
+ host: {'[attr.max]': 'max ? max : null'}
+})
+export class MaxValidator implements Validator,
+ OnChanges {
+
+ @Input() max: string;
+ private _validator: ValidatorFn;
+ private _onChange: () => void;
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if ('max' in changes) {
+ this._createValidator();
+ if (this._onChange) { this._onChange(); }
+ }
+ }
+
+ validate(c: AbstractControl): ValidationErrors | null { return this._validator(c); }
+
+ registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
+
+ private _createValidator(): void { this._validator = Validators.max(parseInt(this.max, 10)); }
+}
diff --git a/src/lib/input/input.module.ts b/src/lib/input/input.module.ts
index 88928e538..f46326aa1 100644
--- a/src/lib/input/input.module.ts
+++ b/src/lib/input/input.module.ts
@@ -4,13 +4,14 @@ import { FormsModule } from '@angular/forms';
import { A11yModule } from '@ptsecurity/cdk/a11y';
import { McCommonModule } from '@ptsecurity/mosaic/core';
-import { McInput, McInputMono } from './input';
+import { McInput, McInputMono, McNumberInput } from './input';
+import { MaxValidator, MinValidator } from './input-number-validators';
@NgModule({
imports: [CommonModule, A11yModule, McCommonModule, FormsModule],
- exports: [McInput, McInputMono],
- declarations: [McInput, McInputMono]
+ exports: [McInput, McNumberInput, McInputMono, MinValidator, MaxValidator],
+ declarations: [McInput, McNumberInput, McInputMono, MinValidator, MaxValidator ]
})
export class McInputModule {
}
diff --git a/src/lib/input/input.scss b/src/lib/input/input.scss
index 29e107977..bc363fe94 100644
--- a/src/lib/input/input.scss
+++ b/src/lib/input/input.scss
@@ -6,3 +6,17 @@
display: inline-block;
}
+
+input.mc-input[type="number"] {
+ -moz-appearance: textfield;
+}
+
+input.mc-input[type="number"]::-webkit-inner-spin-button,
+input.mc-input[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+}
+
+/* remove default red border for HTML5 validation invalid fields in Firefox */
+input.mc-input:invalid {
+ box-shadow: unset;
+}
diff --git a/src/lib/input/input.spec.ts b/src/lib/input/input.spec.ts
index 3d49df5d7..673c81446 100644
--- a/src/lib/input/input.spec.ts
+++ b/src/lib/input/input.spec.ts
@@ -2,7 +2,7 @@ import { Component, Provider, Type } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, ComponentFixtureAutoDetect } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
-import { ESCAPE } from '@ptsecurity/cdk/keycodes';
+import { DOWN_ARROW, ESCAPE, UP_ARROW } from '@ptsecurity/cdk/keycodes';
import {
dispatchFakeEvent,
dispatchKeyboardEvent
@@ -335,3 +335,371 @@ describe('McInput', () => {
});
});
});
+
+
+// tslint:disable no-unnecessary-class
+@Component({
+ template: `
+
+
+
+
+ `
+})
+class McNumberInput {
+ value: number | null = null;
+}
+
+@Component({
+ template: `
+
+
+
+
+ `
+})
+class McNumberInputMaxMinStep {
+ value: number | null = null;
+}
+// tslint:enable no-unnecessary-class
+
+// tslint:disable no-magic-numbers
+describe('McNumberInput', () => {
+ it('should have stepper on focus', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ dispatchFakeEvent(inputElement, 'focus');
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+
+ expect(mcStepper).not.toBeNull();
+ expect(icons.length).toBe(2);
+ }));
+
+ it('should not have stepper initially', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+
+ expect(mcStepper).toBeNull();
+ }));
+
+ describe('empty value', () => {
+ it('should not step up when no max', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ dispatchFakeEvent(inputElement, 'focus');
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconUp = icons[0];
+
+ dispatchFakeEvent(iconUp.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBeNull();
+ }));
+
+ it('should not step down when no min', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ dispatchFakeEvent(inputElement, 'focus');
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconDown = icons[0];
+
+ dispatchFakeEvent(iconDown.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBeNull();
+ }));
+
+
+ it('should step up when max is set', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ dispatchFakeEvent(inputElement, 'focus');
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconUp = icons[0];
+
+ dispatchFakeEvent(iconUp.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(3.5);
+ }));
+
+ it('should step down when min is set', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ dispatchFakeEvent(inputElement, 'focus');
+
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconDown = icons[1];
+
+ dispatchFakeEvent(iconDown.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(9.5);
+ }));
+ });
+
+ describe('not empty value', () => {
+ it('should step up when no min', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 1;
+ dispatchFakeEvent(inputElement, 'input');
+ dispatchFakeEvent(inputElement, 'focus');
+
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconUp = icons[0];
+
+ dispatchFakeEvent(iconUp.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(2);
+ }));
+
+ it('should step down when no max', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 1;
+ dispatchFakeEvent(inputElement, 'input');
+ dispatchFakeEvent(inputElement, 'focus');
+
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconDown = icons[1];
+
+ dispatchFakeEvent(iconDown.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(0);
+ }));
+ });
+
+ describe('keys', () => {
+ it('should step up on up arrow key', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 1;
+ dispatchFakeEvent(inputElement, 'input');
+
+ dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', UP_ARROW);
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(2);
+ }));
+
+ it('should step down on down arrow key', fakeAsync(() => {
+ const fixture = createComponent(McNumberInput);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 1;
+ dispatchFakeEvent(inputElement, 'input');
+
+ dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', DOWN_ARROW);
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(0);
+ }));
+
+ it('should step up with bug step on shift and up arrow key', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 5;
+ dispatchFakeEvent(inputElement, 'input');
+
+ dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', UP_ARROW, undefined, true);
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(7);
+ }));
+
+ it('should step down with bug step on shift and down arrow key', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 6;
+ dispatchFakeEvent(inputElement, 'input');
+
+ dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', DOWN_ARROW, undefined, true);
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(4);
+ }));
+
+ it('should ignore wrong chars', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 123;
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBe(123);
+
+ inputElement.value = 'blahblah';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+
+ inputElement.value = '1.2';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBe(1.2);
+
+ inputElement.value = '1..2';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+
+ inputElement.value = '1..';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+
+ inputElement.value = '--1';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+
+ inputElement.value = '-1-';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+
+ inputElement.value = '.';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+
+ inputElement.value = '-';
+ dispatchFakeEvent(inputElement, 'input');
+ fixture.detectChanges();
+ expect(fixture.componentInstance.value).toBeNull();
+ }));
+ });
+
+ describe('truncate to bounds', () => {
+ it('should set max when value > max on step up', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 20;
+ dispatchFakeEvent(inputElement, 'input');
+ dispatchFakeEvent(inputElement, 'focus');
+
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconUp = icons[0];
+
+ dispatchFakeEvent(iconUp.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(10);
+ }));
+
+ it('should set min when value < min on step down', fakeAsync(() => {
+ const fixture = createComponent(McNumberInputMaxMinStep);
+ fixture.detectChanges();
+
+ const inputElementDebug = fixture.debugElement.query(By.directive(McInput));
+ const inputElement = inputElementDebug.nativeElement;
+
+ inputElement.value = 1;
+ dispatchFakeEvent(inputElement, 'input');
+ dispatchFakeEvent(inputElement, 'focus');
+
+ fixture.detectChanges();
+
+ const mcStepper = fixture.debugElement.query(By.css('mc-stepper'));
+ const icons = mcStepper.queryAll(By.css('.mc-icon'));
+ const iconDown = icons[1];
+
+ dispatchFakeEvent(iconDown.nativeElement, 'mousedown');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.value).toBe(3);
+ }));
+ });
+});
+// tslint:enable no-magic-numbers
diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts
index a2631c95d..7d2731ba8 100644
--- a/src/lib/input/input.ts
+++ b/src/lib/input/input.ts
@@ -1,11 +1,19 @@
import {
+ Attribute,
Directive, DoCheck, ElementRef, Inject, Input, OnChanges,
- OnDestroy, OnInit, Optional, Self
+ OnDestroy, Optional, Self
} from '@angular/core';
-import { Subject } from 'rxjs';
-
-import { FormGroupDirective, NgControl, NgForm } from '@angular/forms';
+import {
+ FormGroupDirective,
+ NgControl,
+ NgForm, NgModel
+} from '@angular/forms';
import { coerceBooleanProperty } from '@ptsecurity/cdk/coercion';
+import {
+ END, C, V, X, A, DELETE, BACKSPACE, TAB, ENTER,
+ ESCAPE, ZERO, NINE, NUMPAD_ZERO, NUMPAD_NINE, NUMPAD_MINUS, DASH,
+ FF_MINUS, LEFT_ARROW, RIGHT_ARROW, HOME, UP_ARROW, DOWN_ARROW, F1, F12
+} from '@ptsecurity/cdk/keycodes';
import { getSupportedInputTypes, Platform } from '@ptsecurity/cdk/platform';
import {
CanUpdateErrorState,
@@ -13,10 +21,12 @@ import {
ErrorStateMatcher,
mixinErrorState
} from '@ptsecurity/mosaic/core';
-import { McFormFieldControl } from '@ptsecurity/mosaic/form-field';
+import { McFormFieldControl, McFormFieldNumberControl } from '@ptsecurity/mosaic/form-field';
+import { Subject } from 'rxjs';
import { getMcInputUnsupportedTypeError } from './input-errors';
import { MC_INPUT_VALUE_ACCESSOR } from './input-value-accessor';
+import { stepDown, stepUp } from './stepperUtils';
const MC_INPUT_INVALID_TYPES = [
@@ -31,6 +41,9 @@ const MC_INPUT_INVALID_TYPES = [
'submit'
];
+export const BIG_STEP = 10;
+export const SMALL_STEP = 1;
+
let nextUniqueId = 0;
export class McInputBase {
@@ -45,11 +58,196 @@ export const _McInputMixinBase: CanUpdateErrorStateCtor & typeof McInputBase =
mixinErrorState(McInputBase);
+@Directive({
+ selector: `input[mcInput][type="number"]`,
+ exportAs: 'mcNumericalInput',
+ providers: [NgModel, { provide: McFormFieldNumberControl, useExisting: McNumberInput }],
+ host: {
+ '(blur)': '_focusChanged(false)',
+ '(focus)': '_focusChanged(true)',
+ '(paste)': 'onPaste($event)',
+ '(keydown)': 'onKeyDown($event)'
+ }
+})
+export class McNumberInput implements McFormFieldNumberControl {
+ /**
+ * Implemented as part of McFormFieldNumberControl.
+ * @docs-private
+ */
+ value: any;
+
+ /**
+ * Implemented as part of McFormFieldNumberControl.
+ * @docs-private
+ */
+ focused: boolean = false;
+
+ /**
+ * Implemented as part of McFormFieldNumberControl.
+ * @docs-private
+ */
+ readonly stateChanges: Subject = new Subject();
+
+ private readonly _host: HTMLInputElement;
+
+ /**
+ * Implemented as part of McFormFieldNumberControl.
+ * @docs-private
+ */
+ private readonly _step: number;
+ get step() {
+ return this._step;
+ }
+
+ /**
+ * Implemented as part of McFormFieldNumberControl.
+ * @docs-private
+ */
+ private readonly _bigStep: number;
+ get bigStep() {
+ return this._bigStep;
+ }
+
+ private readonly _min: number;
+ private readonly _max: number;
+
+ constructor(
+ private _platform: Platform ,
+ private _elementRef: ElementRef,
+ private _model: NgModel,
+ @Attribute('step') step: string,
+ @Attribute('big-step') bigStep: string,
+ @Attribute('min') min: string,
+ @Attribute('max') max: string
+ ) {
+ this._step = this.isDigit(step) ? parseFloat(step) : SMALL_STEP;
+ this._bigStep = this.isDigit(bigStep) ? parseFloat(bigStep) : BIG_STEP;
+ this._min = this.isDigit(min) ? parseFloat(min) : -Infinity;
+ this._max = this.isDigit(max) ? parseFloat(max) : Infinity;
+
+ this._host = this._elementRef.nativeElement;
+
+ const self = this;
+
+ if ('valueAsNumber' in this._host) {
+ Object.defineProperty(Object.getPrototypeOf(this._host), 'valueAsNumber', {
+ // tslint:disable-next-line:no-reserved-keywords
+ get() {
+ const res = parseFloat(self.normalizeSplitter(this.value));
+
+ return isNaN(res) ? null : res;
+ }
+ });
+ }
+ }
+
+ _focusChanged(isFocused: boolean) {
+ if (isFocused !== this.focused) {
+ this.focused = isFocused;
+ this.stateChanges.next();
+ }
+ }
+
+ onKeyDown(event: KeyboardEvent) {
+ // tslint:disable-next-line:deprecation
+ const keyCode = event.keyCode;
+
+ const isCtrlA = (e) => e.keyCode === A && (e.ctrlKey || e.metaKey);
+ const isCtrlC = (e) => e.keyCode === C && (e.ctrlKey || e.metaKey);
+ const isCtrlV = (e) => e.keyCode === V && (e.ctrlKey || e.metaKey);
+ const isCtrlX = (e) => e.keyCode === X && (e.ctrlKey || e.metaKey);
+
+ const isFKey = (e) => e.keyCode >= F1 && e.keyCode <= F12;
+
+ const isNumber = (e) => (e.keyCode >= ZERO && e.keyCode <= NINE) ||
+ (e.keyCode >= NUMPAD_ZERO && e.keyCode <= NUMPAD_NINE);
+
+ const minuses = [NUMPAD_MINUS, DASH, FF_MINUS];
+ const serviceKeys = [DELETE, BACKSPACE, TAB, ESCAPE, ENTER];
+ const arrows = [LEFT_ARROW, RIGHT_ARROW];
+ const allowedKeys = [HOME, END].concat(arrows).concat(serviceKeys).concat(minuses);
+
+ const isIEPeriod = (e) => e.key === '.' || e.key === 'Decimal';
+ const isNotIEPeriod = (e) => e.key === '.' || e.key === ',';
+
+ // Decimal is for IE
+ const isPeriod = (e) => this._platform.EDGE || this._platform.TRIDENT
+ ? isIEPeriod(e)
+ : isNotIEPeriod(e);
+
+ if (allowedKeys.indexOf(keyCode) !== -1 ||
+ isCtrlA(event) ||
+ isCtrlC(event) ||
+ isCtrlV(event) ||
+ isCtrlX(event) ||
+ isFKey(event) ||
+ isPeriod(event)
+ ) {
+ // let it happen, don't do anything
+ return;
+ }
+ // Ensure that it is not a number and stop the keypress
+ if (event.shiftKey || !isNumber(event)) {
+ event.preventDefault();
+
+ // process steps
+ const step = event.shiftKey ? this._bigStep : this._step;
+
+ if (keyCode === UP_ARROW) {
+ this.stepUp(step);
+ }
+
+ if (keyCode === DOWN_ARROW) {
+ this.stepDown(step);
+ }
+ }
+ }
+
+ onPaste(event) {
+ let value = event.clipboardData.getData('text');
+ value = this.normalizeSplitter(value);
+
+ if (!this.isDigit(value)) {
+ event.preventDefault();
+ }
+ }
+
+ stepUp(step: number) {
+ this._elementRef.nativeElement.focus();
+ const res = stepUp(this._host.valueAsNumber, this._max, this._min, step);
+ this._host.value = res === null ? '' : res.toString();
+ this._model.update.emit(this._host.valueAsNumber);
+ }
+
+ stepDown(step: number) {
+ this._elementRef.nativeElement.focus();
+ const res = stepDown(this._host.valueAsNumber, this._max, this._min, step);
+ this._host.value = res === null ? '' : res.toString();
+ this._model.update.emit(this._host.valueAsNumber);
+ }
+
+ private normalizeSplitter(value: string): string {
+ return value ? value.replace(/,/g, '.') : value;
+ }
+
+ private isDigit(value: string): boolean {
+ return this.isFloat(value) || this.isInt(value);
+ }
+
+ private isFloat(value: string): boolean {
+ return /^-?\d+\.\d+$/.test(value);
+ }
+
+ private isInt(value: string): boolean {
+ return /^-?\d+$/.test(value);
+ }
+}
+
@Directive({
selector: `input[mcInput]`,
exportAs: 'mcInput',
host: {
- 'class': 'mc-input',
+ class: 'mc-input',
// Native input properties that are overwritten by Angular inputs need to be synced with
// the native input element. Otherwise property bindings for those don't work.
'[attr.id]': 'id',
@@ -196,7 +394,6 @@ export class McInput extends _McInputMixinBase implements McFormFieldControl {
+ it('stepUp common', () => {
+ expect(stepUp(10, 100, -100, 1)).toBe(11);
+ });
+
+ it('stepDown common', () => {
+ expect(stepDown(10, 100, -100, 1)).toBe(9);
+ });
+
+
+ it('stepUp empty', () => {
+ expect(stepUp(null, 100, -100, 1)).toBe(-99);
+ });
+
+ it('stepDown empty', () => {
+ expect(stepDown(null, 100, -100, 1)).toBe(99);
+ });
+
+
+ it('stepUp over step', () => {
+ expect(stepUp(99, 100, -100, 5)).toBe(100);
+ });
+
+ it('stepDown over step', () => {
+ expect(stepDown(-99, 100, -100, 5)).toBe(-100);
+ });
+
+
+ it('stepUp no max', () => {
+ expect(stepUp(99, Infinity, -100, 5)).toBe(104);
+ });
+
+ it('stepDown no min', () => {
+ expect(stepDown(-99, 100, -Infinity, 5)).toBe(-104);
+ });
+
+ it('stepUp no min', () => {
+ expect(stepUp(null, 100, -Infinity, 5)).toBe(null);
+ });
+
+ it('stepDown no max', () => {
+ expect(stepDown(null, Infinity, -100, 5)).toBe(null);
+ });
+});
diff --git a/src/lib/input/stepperUtils.ts b/src/lib/input/stepperUtils.ts
new file mode 100644
index 000000000..80a20537c
--- /dev/null
+++ b/src/lib/input/stepperUtils.ts
@@ -0,0 +1,58 @@
+function sanitizeNumber(value: number): number | null {
+ return !isFinite(value) || isNaN(value)
+ ? null
+ : value;
+}
+
+function getPrecision(value: number): number {
+ const arr = value.toString().split('.');
+
+ return arr.length === 1
+ ? 1
+ // tslint:disable-next-line:no-magic-numbers
+ : Math.pow(10, arr[1].length);
+}
+
+function add(value1: number, value2: number) {
+ const precision = Math.max(getPrecision(value1), getPrecision(value2));
+
+ const res = (value1 * precision + value2 * precision) / precision;
+
+ return sanitizeNumber(res);
+}
+
+export const stepUp = (value: number | null,
+ max: number,
+ min: number,
+ step: number
+): number | null => {
+ let res;
+
+ if (value === null) {
+ res = add(min, step);
+
+ return res === null ? null : Math.min(res, max);
+ }
+
+ res = add(value, step);
+
+ return res === null ? null : Math.max(Math.min(res, max), min);
+};
+
+export const stepDown = (value: number | null,
+ max: number,
+ min: number,
+ step: number
+): number | null => {
+ let res;
+
+ if (value === null) {
+ res = add(max, -step);
+
+ return res === null ? null : Math.max(res, min);
+ }
+
+ res = add(value, -step);
+
+ return res === null ? null : Math.min(Math.max(res, min), max);
+};