From 4f6abec01a9aae681df8dfb3ef0efc5b3dbda5e4 Mon Sep 17 00:00:00 2001 From: eyelidlessness Date: Fri, 17 Mar 2023 15:40:16 -0700 Subject: [PATCH 1/2] Fix: preserve high fidelity image data when resizing draw-widget canvas --- .eslintrc.json | 2 +- .vscode/settings.json | 3 - Gruntfile.js | 26 +- package-lock.json | 893 ++++++++---------- package.json | 9 +- src/widget/draw/ResizableSignaturePad.js | 649 +++++++++++++ src/widget/draw/draw-widget.js | 294 +----- .../ResizableSignaturePad/1200x540.png | Bin 0 -> 23100 bytes .../ResizableSignaturePad/400x400.png | Bin 0 -> 12614 bytes .../720x540-after-undo.png | Bin 0 -> 26991 bytes .../720x540-with-drawing.png | Bin 0 -> 34071 bytes .../ResizableSignaturePad/720x540.png | Bin 0 -> 18985 bytes .../ResizableSignaturePad/drawing-data.json | 767 +++++++++++++++ test/karma.conf.js | 4 + test/spec/ResizableSignaturePad.spec.js | 544 +++++++++++ test/types/env.d.ts | 1 + 16 files changed, 2433 insertions(+), 759 deletions(-) create mode 100644 src/widget/draw/ResizableSignaturePad.js create mode 100644 test/fixtures/ResizableSignaturePad/1200x540.png create mode 100644 test/fixtures/ResizableSignaturePad/400x400.png create mode 100644 test/fixtures/ResizableSignaturePad/720x540-after-undo.png create mode 100644 test/fixtures/ResizableSignaturePad/720x540-with-drawing.png create mode 100644 test/fixtures/ResizableSignaturePad/720x540.png create mode 100644 test/fixtures/ResizableSignaturePad/drawing-data.json create mode 100644 test/spec/ResizableSignaturePad.spec.js create mode 100644 test/types/env.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index a126de88d..69ffae794 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,7 +12,7 @@ "plugins": ["chai-friendly", "jsdoc", "prettier", "unicorn"], "parserOptions": { "sourceType": "module", - "ecmaVersion": 2020 + "ecmaVersion": 2022 }, "settings": { "jsdoc": { diff --git a/.vscode/settings.json b/.vscode/settings.json index 9cfadf59d..687c2fc9d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,6 @@ { // Formatting "eslint.enable": true, - "eslint.options": { - "extends": ["../.eslintrc.json"] - }, "eslint.nodePath": "../node_modules/.bin", "eslint.validate": ["markdown", "md"], "prettier.configPath": ".prettierrc.json", diff --git a/Gruntfile.js b/Gruntfile.js index 28c851b89..14afa1466 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -21,6 +21,17 @@ module.exports = (grunt) => { '!test/mock/forms.js', ]; + const karmaWatchOptions = { + autoWatch: true, + client: { + mocha: { + timeout: Number.MAX_SAFE_INTEGER, + }, + }, + reporters: ['dots'], + singleRun: false, + }; + // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), @@ -127,16 +138,11 @@ module.exports = (grunt) => { }, watch: { browsers: ['ChromeHeadlessDebug'], - options: { - autoWatch: true, - client: { - mocha: { - timeout: Number.MAX_SAFE_INTEGER, - }, - }, - reporters: ['dots'], - singleRun: false, - }, + options: karmaWatchOptions, + }, + watchBrowsers: { + browsers: ['Chrome', 'Firefox', 'Safari'], + options: karmaWatchOptions, }, }, sass: { diff --git a/package-lock.json b/package-lock.json index f09d99e77..b5588bb4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,31 +97,6 @@ } } }, - "@babel/eslint-parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz", - "integrity": "sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA==", - "dev": true, - "requires": { - "eslint-scope": "^5.1.1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "@babel/generator": { "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", @@ -434,88 +409,108 @@ "jsdoc-type-pratt-parser": "~4.0.0" } }, + "@eslint-community/eslint-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", + "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", + "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.5.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", "dev": true, "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -536,6 +531,12 @@ } } }, + "@eslint/js": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", + "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "dev": true + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -543,20 +544,20 @@ "dev": true }, "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "dependencies": { "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -570,10 +571,16 @@ } } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "@istanbuljs/schema": { @@ -582,6 +589,32 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -668,10 +701,10 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", "dev": true }, "@types/cookie": { @@ -692,7 +725,7 @@ "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/minimatch": { @@ -707,6 +740,12 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "@types/node": { "version": "18.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", @@ -719,6 +758,21 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -950,7 +1004,7 @@ "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, "array-flatten": { @@ -1965,9 +2019,9 @@ } }, "builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true }, "busboy": { @@ -2172,9 +2226,9 @@ "dev": true }, "ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true }, "clean-regexp": { @@ -2282,7 +2336,7 @@ "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, "combined-stream": { @@ -2481,7 +2535,7 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", "dev": true }, "damerau-levenshtein": { @@ -2691,7 +2745,7 @@ "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, "detect-libc": { @@ -2703,7 +2757,7 @@ "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, "diff": { @@ -2724,7 +2778,7 @@ "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { "custom-event": "~1.0.0", @@ -3020,19 +3074,10 @@ } } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, "env-paths": { @@ -3217,132 +3262,78 @@ "dev": true }, "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", + "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.1", + "@eslint/js": "8.36.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.5.0", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - } - } - }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "chalk": { @@ -3353,26 +3344,6 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "color-convert": { @@ -3391,9 +3362,9 @@ "dev": true }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -3406,95 +3377,45 @@ "dev": true }, "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "estraverse": "^5.2.0" } }, "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", "dev": true, "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "file-entry-cache": { @@ -3516,16 +3437,19 @@ "rimraf": "^3.0.2" } }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -3537,12 +3461,21 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3579,48 +3512,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - } - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3636,38 +3527,13 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "table": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } + "has-flag": "^4.0.0" } }, "type-check": { @@ -4378,23 +4244,25 @@ "dev": true }, "eslint-plugin-unicorn": { - "version": "36.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-36.0.0.tgz", - "integrity": "sha512-xxN2vSctGWnDW6aLElm/LKIwcrmk6mdiEcW55Uv5krcrVcIFSWMmEgc/hwpemYfZacKZ5npFERGNz4aThsp1AA==", + "version": "40.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-40.1.0.tgz", + "integrity": "sha512-y5doK2DF9Sr5AqKEHbHxjFllJ167nKDRU01HDcWyv4Tnmaoe9iNxMrBnaybZvWZUaE3OC5Unu0lNIevYamloig==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "ci-info": "^3.2.0", + "@babel/helper-validator-identifier": "^7.15.7", + "ci-info": "^3.3.0", "clean-regexp": "^1.0.0", - "eslint-template-visitor": "^2.3.2", "eslint-utils": "^3.0.0", + "esquery": "^1.4.0", + "indent-string": "^4.0.0", "is-builtin-module": "^3.1.0", "lodash": "^4.17.21", "pluralize": "^8.0.0", "read-pkg-up": "^7.0.1", - "regexp-tree": "^0.1.23", + "regexp-tree": "^0.1.24", "safe-regex": "^2.1.1", - "semver": "^7.3.5" + "semver": "^7.3.5", + "strip-indent": "^3.0.0" }, "dependencies": { "eslint-utils": { @@ -4413,9 +4281,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -4433,27 +4301,6 @@ "estraverse": "^4.1.1" } }, - "eslint-template-visitor": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/eslint-template-visitor/-/eslint-template-visitor-2.3.2.tgz", - "integrity": "sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA==", - "dev": true, - "requires": { - "@babel/core": "^7.12.16", - "@babel/eslint-parser": "^7.12.16", - "eslint-visitor-keys": "^2.0.0", - "esquery": "^1.3.1", - "multimap": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", @@ -4541,7 +4388,7 @@ "eventemitter2": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", "dev": true }, "eventemitter3": { @@ -4559,13 +4406,13 @@ "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { "homedir-polyfill": "^1.0.1" @@ -4768,6 +4615,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -4834,6 +4690,45 @@ } } }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, "findup-sync": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", @@ -4923,13 +4818,13 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "for-own": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { "for-in": "^1.0.1" @@ -5173,7 +5068,7 @@ "global-prefix": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { "expand-tilde": "^2.0.2", @@ -5268,6 +5163,12 @@ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -5435,29 +5336,28 @@ } }, "grunt-eslint": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-23.0.0.tgz", - "integrity": "sha512-QqHSAiGF08EVD7YlD4OSRWuLRaDvpsRdTptwy9WaxUXE+03mCLVA/lEaR6SHWehF7oUwIqCEjaNONeeeWlB4LQ==", + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-24.0.1.tgz", + "integrity": "sha512-gFzp+ikAkwyu6nqBE2zx1pLVL0JPrerG7jaO4uJV3XUGKPIipv4mfhDOS5MyiMrzUtGdXSW8FkRHjoUnfqbW+g==", "dev": true, "requires": { - "chalk": "^4.0.0", - "eslint": "^7.0.0" + "chalk": "^4.1.2", + "eslint": "^8.0.1" }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5486,9 +5386,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6118,7 +6018,7 @@ "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, "ip": { @@ -6218,12 +6118,12 @@ } }, "is-builtin-module": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz", - "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, "requires": { - "builtin-modules": "^3.0.0" + "builtin-modules": "^3.3.0" } }, "is-callable": { @@ -6331,6 +6231,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6487,7 +6393,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, "isstream": { @@ -6605,6 +6511,12 @@ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, + "js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7041,16 +6953,10 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, "lodash.isfinite": { @@ -7065,12 +6971,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -7267,7 +7167,7 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, "map-obj": { @@ -7920,12 +7820,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "multimap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz", - "integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==", - "dev": true - }, "multimatch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", @@ -8392,7 +8286,7 @@ "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { "abbrev": "1" @@ -8456,7 +8350,7 @@ "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { "path-key": "^2.0.0" @@ -8538,7 +8432,7 @@ "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "dev": true, "requires": { "array-each": "^1.0.1", @@ -8890,7 +8784,7 @@ "object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", "dev": true, "requires": { "for-own": "^1.0.0", @@ -8900,7 +8794,7 @@ "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -9210,7 +9104,7 @@ "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "dev": true, "requires": { "is-absolute": "^1.0.0", @@ -9239,7 +9133,7 @@ "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, "parseurl": { @@ -9275,7 +9169,7 @@ "path-root": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "dev": true, "requires": { "path-root-regex": "^0.1.0" @@ -9284,7 +9178,7 @@ "path-root-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, "path-to-regexp": { @@ -9502,6 +9396,12 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -9754,12 +9654,6 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -9811,16 +9705,10 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, "resolve": { @@ -9835,7 +9723,7 @@ "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "dev": true, "requires": { "expand-tilde": "^2.0.0", @@ -9881,6 +9769,12 @@ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -9918,6 +9812,15 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -10291,9 +10194,9 @@ "dev": true }, "signature_pad": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-2.3.2.tgz", - "integrity": "sha512-peYXLxOsIY6MES2TrRLDiNg2T++8gGbpP2yaC+6Ohtxr+a2dzoaqWosWDY9sWqTAAk6E/TyQO+LJw9zQwyu5kA==" + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-4.1.5.tgz", + "integrity": "sha512-VOE846UbQMeLBbcR08KwjwE1wNLgp3gqC7yr/AELkgSMs/BdRpxIZna6K5XyZJpA7IWq9GiInw1C8PLm57VO6Q==" }, "sinon": { "version": "11.1.2", @@ -10921,7 +10824,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-indent": { @@ -11364,7 +11267,7 @@ "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true }, "underscore.string": { @@ -11531,7 +11434,7 @@ "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, "websocket-driver": { diff --git a/package.json b/package.json index da6b2bf5e..40a14bb79 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,9 @@ "beautify": "npx prettier --write . && grunt eslint:fix" }, "devDependencies": { + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "@types/sinon": "^10.0.13", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", "cross-env": "^7.0.3", @@ -64,12 +67,12 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-unicorn": "^36.0.0", + "eslint-plugin-unicorn": "^40.0.0", "grunt": "^1.6.1", "grunt-concurrent": "^3.0.0", "grunt-contrib-connect": "^3.0.0", "grunt-contrib-watch": "^1.1.0", - "grunt-eslint": "^23.0.0", + "grunt-eslint": "^24.0.1", "grunt-karma": "^4.0.2", "grunt-mocha-test": "^0.13.3", "grunt-sass": "^3.1.0", @@ -111,7 +114,7 @@ "mergexml": "1.2.3", "node-forge": "^1.3.1", "openrosa-xpath-evaluator": "^2.0.13", - "signature_pad": "2.3.x" + "signature_pad": "^4.1.5" }, "volta": { "node": "16.5.0", diff --git a/src/widget/draw/ResizableSignaturePad.js b/src/widget/draw/ResizableSignaturePad.js new file mode 100644 index 000000000..4ff5bfde2 --- /dev/null +++ b/src/widget/draw/ResizableSignaturePad.js @@ -0,0 +1,649 @@ +// @ts-check + +import SignaturePad from 'signature_pad'; + +/** + * Gets the theoretical maximum width of a given element, determined by its own + * computed `max-width` style (in `px` units), or that of one of its + * `offsetParent`s. If no such element is found, the element's current + * `offsetWidth` is returned. + * + * Note that this will fail for elements outside the visible DOM tree. + * + * @package - exported only for testing + * @param {HTMLElement} element + */ +export const getMaxWidth = (element) => { + /** @type {HTMLElement | null} */ + let clone = null; + + const { isConnected } = element; + + if (!isConnected) { + throw new Error( + 'Max width cannot be computed for elements outside the visible DOM tree' + ); + } + + let current = element; + let { maxWidth } = getComputedStyle(current); + + while (maxWidth === 'none' && current.offsetParent != null) { + maxWidth = getComputedStyle(current).maxWidth; + current = /** @type {HTMLElement} */ (current.offsetParent); + } + + if (maxWidth === 'none') { + return element.offsetWidth; + } + + const maxPixels = Number(maxWidth.replace(/^(\d+)px$/, '$1')); + + if (Number.isNaN(maxPixels)) { + return element.offsetWidth; + } + + if (current !== element) { + element.classList.add('__TEMP_GET_MAX_WIDTH__'); + + if (clone == null) { + clone = /** @type {HTMLElement} */ (current.cloneNode(true)); + + element.classList.remove('__TEMP_GET_MAX_WIDTH__'); + + clone.style.opacity = '0'; + clone.style.position = 'absolute'; + clone.style.width = maxWidth; + document.body.append(clone); + } + + const { offsetWidth } = /** @type {HTMLElement} */ ( + clone.querySelector('.__TEMP_GET_MAX_WIDTH__') + ); + + clone.remove(); + + return offsetWidth; + } + + return maxPixels; +}; + +/** + * @param {HTMLCanvasElement} canvas + * @return {CanvasRenderingContext2D} + */ +const get2dRenderingContext = (canvas) => { + const context = canvas.getContext('2d'); + + if (context == null) { + throw new Error('Could not get rendering context'); + } + + return context; +}; + +/** + * For some reason, drawing `ImageBitmap` data to a canvas does nothing when the + * canvas element is detached. As a workaround, we attach the canvas element to + * this visibly hidden container to support that operation. + */ +const detachedCanvasContainer = document.createElement('div'); + +detachedCanvasContainer.ariaHidden = 'true'; + +Object.assign(detachedCanvasContainer.style, { + postition: 'absolute', + top: '-1px', + left: '-1px', + opacity: 0, + width: '1px', + height: '1px', + overflow: 'hidden', +}); +document.body.append(detachedCanvasContainer); + +/** + * @typedef {import('signature_pad').Options} SignaturePadOptions + */ + +/** + * @typedef {import('signature_pad').PointGroup} PointGroup + */ + +/** + * @extends {SignaturePad} + * @implements {SignaturePad} + */ +export class ResizableSignaturePad extends SignaturePad { + /** + * Tracks which `ResizableSignaturePad` is associated with its canvas + * element. This allows more efficient (and idiomatic) use of + * `IntersectionObserver` and `ResizeObserver`, where the same logic is + * attached and detached to individual elements as needed. + * + * @type {WeakMap} + */ + static canvasToPad = new WeakMap(); + + /** + * @private + * + * There are several non-obvious implications to how `intersectionObserver` + * and {@link resizeObserver} are used to address, and in some cases improve + * upon, several behaviors previously handled in `draw-widget.js`. + * + * In both this implementation and the prior one, some behavior is deferred + * when the canvas element is determined not to be visible. + * + * - Previously, this determination was based on the canvas having a + * width/height of 0 (e.g. an inactive question in "pages" mode). When it + * was determined the canvas element is not visible, resize behavior would + * be deferred. + * + * - It is now determined by `IntersectionObserver`, now standard for + * determining element visibility, even if the element is below the fold + * but would otherwise be visible. While a canvas element is not visible + * in the viewport, it is removed from resize observation until it becomes + * visible again. + * + * In both implementations, there is logic to update state specific to + * behaviors of both the HTML canvas API and the underlying `SignaturePad` + * when the canvas element is resized. + * + * - Previously, resize logic was performed: + * + * - any time the browser window was resized and the canvas element was + * determined to be visible + * + * - explicitly when the editing state changes on mobile + * + * - explicitly when the current page changes in "pages" mode + * + * - It is now determined by the standard `ResizeObserver`, and each canvas + * element is observed directly. + * + * In both implementations, there is a need to perform the same "resize" + * logic upon initializtion, as the canvas element's default size may not + * match the initial display size. + * + * - Previously, this was performed by explicitly invoking the resize logic + * at a specific point in the initialization process. + * + * - This initialization is now performed implicitly when each canvas + * element is observed (i.e. the built-in behavior of both observer APIs). + */ + static intersectionObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + const { target: canvas } = entry; + const pad = this.canvasToPad.get( + /** @type {HTMLCanvasElement} */ (canvas) + ); + + if (pad == null) { + return; + } + + const { isIntersecting: isVisible } = entry; + + // Note: the reason these observers are static members of + // `ResizableSignatureObserver`, rather than top-level constants, is + // to allow access to private instance members. + pad.isVisible = isVisible; + + if (isVisible) { + this.resizeObserver.observe(canvas); + } else { + this.resizeObserver.unobserve(canvas); + } + + /** + * TODO: how (if at all) should we handle removal of the canvas + * element from the visible DOM tree? It seems at least probable + * that: + * + * - The reference in {@link canvasToPad} will be freed eventually. + * + * - We'll detect that the canvas element is no longer visible _if + * it had been_, and its observation {@link resizeObserver} will + * be detached, but we won't stop observing visibility in any + * case. This would be sufficient to detect cases where the canvas + * may be reattached, but it's not clear how likely that is. + * + * - All other internal references/state will not be freed, + * potentially causing a leak. + * + * - If the canvas element **were reattached**, it would likely be + * detected as a **new instance** of the widget, creating a + * redundant instance of this class. + * + * It seems the most appropriate immediate solution would be to tear + * down everything on DOM removal. This would probably be sufficient + * for the currently implied tight coupling with `DrawWidget`. But + * the design of `ResizableSignaturePad` is very nearly appropriate + * to break that tight coupling and either contribute upstream or + * release as a separate wrapper package. If that were the case, + * we'd want to anticipate the more general case where + * detaching/reattaching arbitrary DOM nodes is quite common. + */ + }); + }); + + /** + * @private + * @see {@link intersectionObserver} + */ + static resizeObserver = new ResizeObserver((entries) => { + const targets = [...new Set(entries.map(({ target }) => target))]; + + targets.forEach((target) => { + const pad = this.canvasToPad.get(target); + + if (pad == null) { + return; + } + + pad.redraw(); + }); + }); + + /** @private */ + isChangePending = false; + + /** @private */ + accessorState = { + isVisible: false, + displayCanvasMaxWidth: 0, + }; + + /** @private */ + get isVisible() { + return ( + this.accessorState.isVisible && + this.displayCanvas.offsetWidth > 0 && + this.displayCanvas.offsetHeight > 0 + ); + } + + /** @private */ + set isVisible(isVisible) { + const wasVisible = this.accessorState.isVisible; + + this.accessorState.isVisible = isVisible; + + if (!wasVisible && isVisible && this.isChangePending) { + this.dispatchChangeEvent(); + } + } + + /** @private */ + get canvasHeightRatio() { + return this.displayCanvas.offsetHeight / this.displayCanvas.offsetWidth; + } + + /** + * @private + * @type {number} + */ + get displayCanvasMaxWidth() { + const { displayCanvasMaxWidth } = this.accessorState; + + if (displayCanvasMaxWidth) { + return displayCanvasMaxWidth; + } + + this.accessorState.displayCanvasMaxWidth = getMaxWidth( + this.displayCanvas + ); + + return this.accessorState.displayCanvasMaxWidth; + } + + /** @private */ + set displayCanvasMaxWidth(maxWidth) { + /** @private */ + this.accessorState.displayCanvasMaxWidth = maxWidth; + } + + /** @private */ + get displayCanvasMaxHeight() { + return (this.displayCanvasMaxWidth ?? 0) * this.canvasHeightRatio; + } + + /** + * @param {HTMLCanvasElement} canvas + * @param {SignaturePadOptions} [options] + */ + constructor(canvas, options) { + super(canvas, options); + + ResizableSignaturePad.canvasToPad.set(canvas, this); + ResizableSignaturePad.intersectionObserver.observe(canvas); + + /** + * @private + * @readonly + * @type {HTMLCanvasElement} + */ + this.displayCanvas = canvas; + + /** + * @private + * @readonly + * @type {HTMLCanvasElement} + */ + this.fullSizeCanvas = document.createElement('canvas'); + + /** + * @see {@link detatchedCanvasContainer} + */ + detachedCanvasContainer.append(this.fullSizeCanvas); + + /** + * @private + * @readonly + * @type {SignaturePad} + */ + this.fullSizePad = new SignaturePad(this.fullSizeCanvas, options); + + /** + * @private + * @type {PointGroup[]} + */ + this.pointGroups = []; + + /** + * @private + * @type {ImageBitmap | null} + * @see {@link drawBaseImage} + */ + this.baseImage = null; + + /** + * @private + * @type {string} + */ + this.objectURL = ''; + + this.addEventListener('endStroke', () => { + const maxWidthScale = + this.displayCanvasMaxWidth / this.displayCanvas.offsetWidth; + + this.pointGroups = this.scalePointGroups( + this.toData(), + maxWidthScale + ); + this.dispatchChangeEvent(); + }); + } + + /** @private */ + dispatchChangeEvent() { + const { objectURL } = this; + + if (objectURL !== '') { + // This will fail silently if it is performed after the request for + // the current blob state below + URL.revokeObjectURL(objectURL); + } + + if (!this.isVisible) { + this.isChangePending = true; + + return; + } + + this.isChangePending = false; + this.dispatchEvent(new Event('change')); + } + + /** @private */ + resizeDisplayCanvas() { + const { offsetWidth, offsetHeight } = this.displayCanvas; + const ratio = Math.max(window.devicePixelRatio || 1, 1); + + this.displayCanvas.width = + (offsetWidth || this.displayCanvasMaxWidth) * ratio; + this.displayCanvas.height = + (offsetHeight || this.displayCanvasMaxHeight) * ratio; + get2dRenderingContext(this.displayCanvas).scale(ratio, ratio); + } + + /** @private */ + redraw() { + this.clear(); + this.resizeDisplayCanvas(); + this.drawBaseImage(); + this.setDrawingData(this.pointGroups); + } + + /** + * @private + * @param {HTMLCanvasElement} [canvas] + * + * While {@link setBaseImage} is inherently asynchronous, `drawBaseImage` is + * able to be synchronous, because it uses a cached `ImageBitmap` instance. + * This API is somewhat obscure, but its use results in significantly better + * performance. The improvement is significant enough to eliminate the need + * for artificial delays on `getObjectURL`. Combined with use of + * `ResizeObserver`, it's also significant enough to eliminate the need for + * throttling. + * + * This approach was discovered when, even with throttling, asynchronously + * redrawing binary image data with the base library's `fromDataURL` would + * cause flickering while resizing. + */ + drawBaseImage(canvas = this.displayCanvas) { + const { baseImage: baseImageBitmap } = this; + + if (baseImageBitmap == null) { + const { width, height } = canvas; + + get2dRenderingContext(canvas).clearRect(0, 0, width, height); + + return; + } + + const { offsetHeight, offsetWidth } = canvas; + + let { height, width } = baseImageBitmap; + + const widthRatio = this.displayCanvasMaxWidth / width; + const heightRatio = this.displayCanvasMaxHeight / height; + const bitmapRatio = Math.min(heightRatio, widthRatio); + const canvasRatio = offsetWidth / this.displayCanvasMaxWidth; + + width = width * bitmapRatio * canvasRatio; + height = height * bitmapRatio * canvasRatio; + + const xOffset = Math.max(0, (offsetWidth - width) / 2); + const yOffset = Math.max(0, (offsetHeight - height) / 2); + + get2dRenderingContext(canvas).drawImage( + baseImageBitmap, + xOffset, + yOffset, + width, + height + ); + } + + /** + * @param {string | Blob} image + */ + async setBaseImage(image) { + this.baseImage?.close(); + + /** @type {Blob} */ + let blob; + + if (image instanceof Blob) { + blob = image; + } else { + const response = await fetch(image); + + blob = await response.blob(); + } + + this.baseImage?.close(); + this.baseImage = await createImageBitmap(blob); + this.redraw(); + this.dispatchChangeEvent(); + } + + /** + * @private + * @param {PointGroup[]} pointGroups + * @param {number} scale + * @param {number} [minLineWidth] + * @param {number} [maxLineWidth] + * @return {PointGroup[]} + */ + scalePointGroups( + pointGroups, + scale, + minLineWidth = 0.5, + maxLineWidth = minLineWidth * 5 + ) { + return pointGroups.map((group) => ({ + ...group, + minWidth: minLineWidth, + maxWidth: maxLineWidth, + + points: group.points.map((point) => { + const { x, y, pressure, time, ...rest } = point; + + return { + ...rest, + x: x * scale, + y: y * scale, + pressure: pressure * scale, + time: time * scale, + }; + }), + })); + } + + /** + * @private + * @param {PointGroup[]} pointGroups + */ + setDrawingData(pointGroups) { + const { displayCanvas, displayCanvasMaxWidth } = this; + const { offsetWidth } = displayCanvas; + const scale = + offsetWidth === 0 ? 1 : offsetWidth / displayCanvasMaxWidth; + const minLineWidth = Math.max( + 0.325, + (0.5 * offsetWidth) / displayCanvasMaxWidth + ); + const maxLineWidth = minLineWidth * 5; + + this.minWidth = minLineWidth; + this.maxWidth = maxLineWidth; + + const scaled = this.scalePointGroups( + pointGroups, + scale, + minLineWidth, + maxLineWidth + ); + + this.pointGroups = pointGroups; + + this.fromData(scaled, { + clear: false, + }); + } + + /** @private */ + clearDrawingData() { + this.clear(); + this.setDrawingData([]); + this.drawBaseImage(); + this.dispatchChangeEvent(); + } + + undoStroke() { + const { pointGroups } = this; + + this.setDrawingData(pointGroups.slice(0, pointGroups.length - 1)); + this.redraw(); + this.dispatchChangeEvent(); + } + + reset() { + this.baseImage?.close(); + this.baseImage = null; + this.clearDrawingData(); + super.clear(); + this.dispatchChangeEvent(); + } + + /** @private */ + resizeFullSizeCanvas() { + let { + displayCanvasMaxWidth: canvasWidth, + displayCanvasMaxHeight: canvasHeight, + } = this; + + if (this.baseImage != null) { + const { canvasHeightRatio } = this; + const { width, height } = this.baseImage; + const heightRatio = height / width; + + canvasHeight = + heightRatio > canvasHeightRatio + ? height + : width * canvasHeightRatio; + canvasWidth = canvasHeight / canvasHeightRatio; + } + + this.fullSizeCanvas.style.width = `${canvasWidth}px`; + this.fullSizeCanvas.style.height = `${canvasHeight}px`; + this.fullSizeCanvas.width = canvasWidth; + this.fullSizeCanvas.height = canvasHeight; + get2dRenderingContext(this.fullSizeCanvas).scale(1, 1); + } + + async toObjectURL() { + if (this.baseImage == null && this.pointGroups.length === 0) { + this.objectURL = ''; + + return ''; + } + + this.resizeFullSizeCanvas(); + + const pointGroups = this.scalePointGroups( + this.pointGroups, + this.fullSizeCanvas.width / this.displayCanvasMaxWidth + ); + + this.fullSizePad.clear(); + this.drawBaseImage(this.fullSizeCanvas); + this.fullSizePad.fromData(pointGroups, { + clear: false, + }); + + const blob = await new Promise((resolve) => { + this.fullSizeCanvas.toBlob((result) => { + if (result instanceof Blob) { + resolve(result); + } else { + resolve(null); + } + }); + }); + + if (blob == null) { + this.objectURL = ''; + + return ''; + } + + this.objectURL = URL.createObjectURL(blob); + + return this.objectURL; + } +} diff --git a/src/widget/draw/draw-widget.js b/src/widget/draw/draw-widget.js index 437073038..a135227f1 100644 --- a/src/widget/draw/draw-widget.js +++ b/src/widget/draw/draw-widget.js @@ -1,9 +1,5 @@ import $ from 'jquery'; import fileManager from 'enketo/file-manager'; -/** - * @external SignaturePad - */ -import SignaturePad from 'signature_pad'; import { t } from 'enketo/translator'; import dialog from 'enketo/dialog'; import support from '../../js/support'; @@ -11,92 +7,7 @@ import events from '../../js/event'; import Widget from '../../js/widget'; import { dataUriToBlobSync, getFilename } from '../../js/utils'; import downloadUtils from '../../js/download-utils'; - -const DELAY = 1500; - -/** - * SignaturePad.prototype.fromDataURL is asynchronous and does not return - * a Promise. This is a rewrite returning a promise and the objectUrl. - * In addition it also fixes a bug where a loaded image is stretched to fit - * the canvas. - * - * @function external:SignaturePad#fromObjectURL - * @param {*} objectUrl - ObjectURL - * @param {object} options - options - * @param {number} [options.ratio] - ratio - * @param {number} [options.width] - width - * @param {number} [options.height] - height - * @return {Promise} a promise that resolves with an objectURL - */ -SignaturePad.prototype.fromObjectURL = function (objectUrl, options) { - const image = new Image(); - options = options || {}; - const deviceRatio = options.ratio || window.devicePixelRatio || 1; - const width = options.width || this._canvas.width / deviceRatio; - const height = options.height || this._canvas.height / deviceRatio; - const that = this; - - this._reset(); - - return new Promise((resolve) => { - image.src = objectUrl; - image.onload = () => { - const imgWidth = image.width; - const imgHeight = image.height; - const hRatio = width / imgWidth; - const vRatio = height / imgHeight; - let left; - let top; - - if (hRatio < 1 || vRatio < 1) { - // if image is bigger than canvas then fit within the canvas - const ratio = Math.min(hRatio, vRatio); - left = (width - imgWidth * ratio) / 2; - top = (height - imgHeight * ratio) / 2; - that._ctx.drawImage( - image, - 0, - 0, - imgWidth, - imgHeight, - left, - top, - imgWidth * ratio, - imgHeight * ratio - ); - } else { - // if image is smaller than canvas then show it in the center and don't stretch it - left = (width - imgWidth) / 2; - top = (height - imgHeight) / 2; - that._ctx.drawImage(image, left, top, imgWidth, imgHeight); - } - resolve(objectUrl); - }; - that._isEmpty = false; - }); -}; - -/** - * Similar to SignaturePad.prototype.fromData except that it doesn't clear the canvas. - * This is to facilitate undoing a drawing stroke over a background (bitmap) image. - * - * @function external:SignaturePad#updateData - * @param {*} pointGroups - pointGroups - */ -SignaturePad.prototype.updateData = function (pointGroups) { - const that = this; - this._fromData( - pointGroups, - (curve, widths) => { - that._drawCurve(curve, widths.start, widths.end); - }, - (rawPoint) => { - that._drawDot(rawPoint); - } - ); - - this._data = pointGroups; -}; +import { ResizableSignaturePad } from './ResizableSignaturePad'; /** * Widget to obtain user-provided drawings or signature. @@ -128,35 +39,22 @@ class DrawWidget extends Widget { this.$widget = $(question.querySelector('.widget')); canvas = this.$widget[0].querySelector('.draw-widget__body__canvas'); - this._handleResize(canvas); - this._resizeCanvas(canvas); if (this.props.load) { this._handleFiles(existingFilename); } - // This listener serves to capture a drawing when the submit button is clicked within [DELAY] - // milliseconds after the last stroke ended. Note that this could be the entire drawing/signature. - canvas.addEventListener('blur', this._forceUpdate.bind(this)); - - // We built a delay in saving on stroke "end", to avoid excessive updating - // This event does not fire on touchscreens for which we use the .hide-canvas-btn click - // to do the same thing. - this.initialize = fileManager.init().then(() => { - that.pad = new SignaturePad(canvas, { - onEnd: () => { - // keep replacing this timer so continuous drawing - // doesn't update the value after every stroke. - clearTimeout(that._updateWithDelay); - that._updateWithDelay = setTimeout( - that._updateValue.bind(that), - DELAY - ); - }, + this.pad = new ResizableSignaturePad(canvas, { penColor: that.props.colors[0] || 'black', }); - that.pad.off(); + + this.pad.addEventListener('change', () => { + this._updateValue(); + }); + + this.pad.off(); + if (existingFilename) { that.element.value = existingFilename; @@ -167,6 +65,7 @@ class DrawWidget extends Widget { return true; }); + this.disable(); this.initialize .then(() => { @@ -191,28 +90,12 @@ class DrawWidget extends Widget { .end() .find('.draw-widget__undo') .on('click', () => { - const data = that.pad.toData(); - that.pad.clear(); - const fileInput = - that.$widget[0].querySelector('input[type=file]'); - // that.element.dataset.loadedFileName will have been removed only after resetting - const fileToLoad = - fileInput && fileInput.files[0] - ? fileInput.files[0] - : that.element.dataset.loadedFileName; - that._loadFileIntoPad(fileToLoad).then(() => { - that.pad.updateData(data.slice(0, -1)); - that._updateValue(); - that.pad.penColor = that.$widget.find( - '.draw-widget__colorpicker .current' - )[0].dataset.color; - }); + this.pad.undoStroke(); }) .end() .find('.show-canvas-btn') .on('click', () => { that.$widget.addClass('full-screen'); - that._resizeCanvas(canvas); that.enable(); return false; @@ -222,47 +105,21 @@ class DrawWidget extends Widget { .on('click', () => { that.$widget.removeClass('full-screen'); that.pad.off(); - that._forceUpdate(); - that._resizeCanvas(canvas); + that._updateValue(); return false; }) .click(); - $(canvas).on('canvasreload', () => { - if (that.cache) { - that.pad - .fromObjectURL(that.cache) - .then(that._updateValue.bind(that, false)); - } - }); that.enable(); }) .catch((error) => { that._showFeedback(error.message); }); - $(this.element) - .on('applyfocus', () => { - canvas.focus(); - }) - .closest('[role="page"]') - .on(events.PageFlip().type, () => { - // When an existing value is loaded into the canvas and is not - // the first page, it won't become visible until the canvas is clicked - // or the window is resized: - // https://github.com/kobotoolbox/enketo-express/issues/895 - // This also fixes a similar issue with an empty canvas: - // https://github.com/kobotoolbox/enketo-express/issues/844 - that._resizeCanvas(canvas); - }); - } - - _forceUpdate() { - if (this._updateWithDelay) { - clearTimeout(this._updateWithDelay); - this._updateValue(); - } + $(this.element).on('applyfocus', () => { + canvas.focus(); + }); } // All this is copied from the file-picker widget @@ -307,7 +164,7 @@ class DrawWidget extends Widget { // Process the file if (!fileManager.isTooLarge(file)) { // Update UI - that.pad.clear(); + that.pad.reset(); that._loadFileIntoPad(this.files[0]).then(() => { that._updateValue.call(that); that._showFileName(file.name); @@ -435,25 +292,14 @@ class DrawWidget extends Widget { return fragment; } - /** - * Updates value - * - * @param {boolean} [changed] - whether the value has changed - */ - _updateValue(changed = true) { - const newValue = this.pad.toDataURL(); + async _updateValue() { + const newValue = await this.pad.toObjectURL(); + if (this.value !== newValue) { const now = new Date(); const postfix = `-${now.getHours()}_${now.getMinutes()}_${now.getSeconds()}`; this.element.dataset.filenamePostfix = postfix; - // Note that this.element has become a text input. - // When a default file is loaded this function is called by the canvasreload handler, but the user hasn't changed anything. - // We want to make sure the model remains unchanged in that case. - if (changed) { - this.originalInputValue = this.props.filename; - } - // pad.toData() doesn't seem to work when redrawing on a smaller canvas. Doesn't scale. - // pad.toDataURL() is crude and memory-heavy but the advantage is that it will also work for appearance=annotate + this.originalInputValue = this.props.filename; this.value = newValue; this._updateDownloadLink(this.value); } @@ -479,7 +325,7 @@ class DrawWidget extends Widget { if (!confirmed) { return; } - that.pad.clear(); + that.pad.reset(); that.cache = null; // Only upon reset is loadedFileName removed, so that "undo" will work // for drawings loaded from storage. @@ -487,11 +333,7 @@ class DrawWidget extends Widget { delete that.element.dataset.loadedUrl; that.element.dataset.filenamePostfix = ''; $(that.element).val('').trigger('change'); - if (that._updateWithDelay) { - // This ensures that an emptied canvas will not be considered a drawing to be captured - // in _forceUpdate, e.g. after the blur event fires on an empty canvas see issue #924 - that._updateWithDelay = null; - } + // Annotate file input that.$widget .find('input[type=file]') @@ -505,36 +347,36 @@ class DrawWidget extends Widget { } /** - * @param {string|File} file - Either a filename or a file. - * @return {Promise} promise resolving with a string + * @param {string | File} file - Either a filename or a file. + * @return {Promise} promise resolving with a string */ - _loadFileIntoPad(file) { - const that = this; + async _loadFileIntoPad(file) { if (!file) { - return Promise.resolve(''); - } - if ( - typeof file === 'string' && - file.startsWith('jr://') && - this.element.dataset.loadedUrl - ) { - file = this.element.dataset.loadedUrl; + this.pad.clearBaseImage(); + + return ''; } - return fileManager - .getObjectUrl(file) - .then(that.pad.fromObjectURL.bind(that.pad)) - .then((objectUrl) => { - that.cache = objectUrl; + try { + if ( + typeof file === 'string' && + file.startsWith('jr://') && + this.element.dataset.loadedUrl + ) { + file = this.element.dataset.loadedUrl; + } - return objectUrl; - }) - .catch(() => { - that._showFeedback( - 'File could not be loaded (leave unchanged if already submitted and you want to preserve it).', - 'error' - ); - }); + this.pad.reset(); + + await this.pad.setBaseImage(file); + + return this.pad.toObjectURL(); + } catch { + this._showFeedback( + 'File could not be loaded (leave unchanged if already submitted and you want to preserve it).', + 'error' + ); + } } /** @@ -567,41 +409,6 @@ class DrawWidget extends Widget { ); } - /** - * Forces update and resizes canvas on window resize - * - * @param {Element} canvas - Canvas element - */ - _handleResize(canvas) { - const that = this; - $(window).on('resize', () => { - // that._forceUpdate(); - that._resizeCanvas(canvas); - }); - } - - /** - * Adjust canvas coordinate space taking into account pixel ratio, - * to make it look crisp on mobile devices. - * This also causes canvas to be cleared. - * - * @param {Element} canvas - Canvas element - */ - _resizeCanvas(canvas) { - // Use a little trick to avoid resizing currently-hidden canvases - // https://github.com/enketo/enketo-core/issues/605 - if (canvas.offsetWidth > 0) { - // When zoomed out to less than 100%, for some very strange reason, - // some browsers report devicePixelRatio as less than 1 - // and only part of the canvas is cleared then. - const ratio = Math.max(window.devicePixelRatio || 1, 1); - canvas.width = canvas.offsetWidth * ratio; - canvas.height = canvas.offsetHeight * ratio; - canvas.getContext('2d').scale(ratio, ratio); - $(canvas).trigger('canvasreload'); - } - } - /** * Disables widget */ @@ -632,13 +439,6 @@ class DrawWidget extends Widget { canvas.classList.remove('disabled'); that.$widget.find('.btn-reset').prop('disabled', false); } - // https://github.com/enketo/enketo-core/issues/450 - // When loading a question with a relevant, it is invisible - // until branch.js removes the "pre-init" class. The rendering of the - // canvas may therefore still be ongoing when this widget is instantiated. - // For that reason we call _resizeCanvas when enable is called to make - // sure the canvas is rendered properly. - that._resizeCanvas(canvas); }); } diff --git a/test/fixtures/ResizableSignaturePad/1200x540.png b/test/fixtures/ResizableSignaturePad/1200x540.png new file mode 100644 index 0000000000000000000000000000000000000000..e58823135488831745cb1bef535ab35d4286da95 GIT binary patch literal 23100 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/ResizableSignaturePad/400x400.png b/test/fixtures/ResizableSignaturePad/400x400.png new file mode 100644 index 0000000000000000000000000000000000000000..68e3ea5e5f8b5bf6340e5590c354b2ae6dd3185b GIT binary patch literal 12614 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/ResizableSignaturePad/720x540-after-undo.png b/test/fixtures/ResizableSignaturePad/720x540-after-undo.png new file mode 100644 index 0000000000000000000000000000000000000000..68d2360be25b9e92b8473774768512919b33649a GIT binary patch literal 26991 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 M*EYOL)^v*gA1hkUf&c&j literal 0 HcmV?d00001 diff --git a/test/fixtures/ResizableSignaturePad/720x540-with-drawing.png b/test/fixtures/ResizableSignaturePad/720x540-with-drawing.png new file mode 100644 index 0000000000000000000000000000000000000000..32a7ac791368f0f51325e389477aae8b32b12d82 GIT binary patch literal 34071 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 z%0AgEBNZg>BRv(3F-Dg;ivtm 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 A-v9sr literal 0 HcmV?d00001 diff --git a/test/fixtures/ResizableSignaturePad/720x540.png b/test/fixtures/ResizableSignaturePad/720x540.png new file mode 100644 index 0000000000000000000000000000000000000000..b527e14dcd02b55f23bb5d6e22bb19708b84e6b1 GIT binary patch literal 18985 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/ResizableSignaturePad/drawing-data.json b/test/fixtures/ResizableSignaturePad/drawing-data.json new file mode 100644 index 000000000..41e0cfb8f --- /dev/null +++ b/test/fixtures/ResizableSignaturePad/drawing-data.json @@ -0,0 +1,767 @@ +[ + { + "penColor": "black", + "dotSize": 0, + "minWidth": 0.5, + "maxWidth": 2.5, + "velocityFilterWeight": 0.7, + "points": [ + { + "time": 1679163236198, + "x": 110.484375, + "y": 70.8828125, + "pressure": 0.5 + }, + { + "time": 1679163236313, + "x": 106.40625, + "y": 67.3125, + "pressure": 0.5 + }, + { + "time": 1679163236369, + "x": 93.59765625, + "y": 66.9140625, + "pressure": 0.5 + }, + { + "time": 1679163236389, + "x": 83.42578125, + "y": 66.9140625, + "pressure": 0.5 + }, + { + "time": 1679163236406, + "x": 75.95703125, + "y": 69.33984375, + "pressure": 0.5 + }, + { + "time": 1679163236422, + "x": 68.00390625, + "y": 73.0703125, + "pressure": 0.5 + }, + { + "time": 1679163236443, + "x": 62.28125, + "y": 77.03125, + "pressure": 0.5 + }, + { + "time": 1679163236462, + "x": 58.03125, + "y": 81.69921875, + "pressure": 0.5 + }, + { + "time": 1679163236479, + "x": 55.98828125, + "y": 87.05078125, + "pressure": 0.5 + }, + { + "time": 1679163236496, + "x": 53.859375, + "y": 99.58984375, + "pressure": 0.5 + }, + { + "time": 1679163236516, + "x": 53.859375, + "y": 105.7578125, + "pressure": 0.5 + }, + { + "time": 1679163236536, + "x": 53.859375, + "y": 111.92578125, + "pressure": 0.5 + }, + { + "time": 1679163236556, + "x": 56.40625, + "y": 117.4453125, + "pressure": 0.5 + }, + { + "time": 1679163236573, + "x": 63.21875, + "y": 126.5234375, + "pressure": 0.5 + }, + { + "time": 1679163236590, + "x": 70.453125, + "y": 131.8359375, + "pressure": 0.5 + }, + { + "time": 1679163236605, + "x": 77.2421875, + "y": 135.90625, + "pressure": 0.5 + }, + { + "time": 1679163236621, + "x": 83.8359375, + "y": 138.98828125, + "pressure": 0.5 + }, + { + "time": 1679163236641, + "x": 91.78125, + "y": 140.37109375, + "pressure": 0.5 + }, + { + "time": 1679163236662, + "x": 102.00390625, + "y": 140.8515625, + "pressure": 0.5 + }, + { + "time": 1679163236687, + "x": 113.85546875, + "y": 140.8515625, + "pressure": 0.5 + }, + { + "time": 1679163236706, + "x": 140.9921875, + "y": 135.203125, + "pressure": 0.5 + }, + { + "time": 1679163236728, + "x": 149.96484375, + "y": 132.6484375, + "pressure": 0.5 + }, + { + "time": 1679163236764, + "x": 155.44140625, + "y": 128.31640625, + "pressure": 0.5 + }, + { + "time": 1679163236783, + "x": 157.13671875, + "y": 122.30859375, + "pressure": 0.5 + }, + { + "time": 1679163236803, + "x": 158.06640625, + "y": 109.26953125, + "pressure": 0.5 + }, + { + "time": 1679163236819, + "x": 158.06640625, + "y": 99.85546875, + "pressure": 0.5 + }, + { + "time": 1679163236843, + "x": 158.06640625, + "y": 92.92578125, + "pressure": 0.5 + }, + { + "time": 1679163236862, + "x": 153.3125, + "y": 81.046875, + "pressure": 0.5 + }, + { + "time": 1679163236901, + "x": 144.28125, + "y": 71.9609375, + "pressure": 0.5 + }, + { + "time": 1679163236920, + "x": 137.75390625, + "y": 66.65625, + "pressure": 0.5 + }, + { + "time": 1679163236987, + "x": 125.56640625, + "y": 60.75, + "pressure": 0.5 + }, + { + "time": 1679163237042, + "x": 117.16015625, + "y": 60.37890625, + "pressure": 0.5 + }, + { + "time": 1679163237081, + "x": 111.17578125, + "y": 66.03515625, + "pressure": 0.5 + }, + { + "time": 1679163237098, + "x": 102.29296875, + "y": 72.21484375, + "pressure": 0.5 + }, + { + "time": 1679163237117, + "x": 95.04296875, + "y": 75.37890625, + "pressure": 0.5 + }, + { + "time": 1679163237136, + "x": 89.5703125, + "y": 77.1015625, + "pressure": 0.5 + }, + { + "time": 1679163237170, + "x": 81.44140625, + "y": 78.65625, + "pressure": 0.5 + } + ] + }, + { + "penColor": "black", + "dotSize": 0, + "minWidth": 0.5, + "maxWidth": 2.5, + "velocityFilterWeight": 0.7, + "points": [ + { + "time": 1679163238886, + "x": 277.1953125, + "y": 350.4375, + "pressure": 0.5 + }, + { + "time": 1679163239023, + "x": 279.1171875, + "y": 340.08984375, + "pressure": 0.5 + }, + { + "time": 1679163239046, + "x": 285.96484375, + "y": 323.90234375, + "pressure": 0.5 + }, + { + "time": 1679163239062, + "x": 303.74609375, + "y": 285, + "pressure": 0.5 + }, + { + "time": 1679163239079, + "x": 311.0703125, + "y": 273.27734375, + "pressure": 0.5 + }, + { + "time": 1679163239096, + "x": 324.76171875, + "y": 247.3515625, + "pressure": 0.5 + }, + { + "time": 1679163239112, + "x": 332.4375, + "y": 233.1953125, + "pressure": 0.5 + }, + { + "time": 1679163239130, + "x": 336.47265625, + "y": 224.203125, + "pressure": 0.5 + }, + { + "time": 1679163239170, + "x": 338.23046875, + "y": 218.45703125, + "pressure": 0.5 + }, + { + "time": 1679163239622, + "x": 339.01171875, + "y": 212.37890625, + "pressure": 0.5 + }, + { + "time": 1679163239850, + "x": 343.57421875, + "y": 201.625, + "pressure": 0.5 + }, + { + "time": 1679163239891, + "x": 346.21484375, + "y": 195.953125, + "pressure": 0.5 + }, + { + "time": 1679163240174, + "x": 363.859375, + "y": 210.8828125, + "pressure": 0.5 + }, + { + "time": 1679163240190, + "x": 373.42578125, + "y": 220.44921875, + "pressure": 0.5 + }, + { + "time": 1679163240207, + "x": 378.50390625, + "y": 225.52734375, + "pressure": 0.5 + }, + { + "time": 1679163240224, + "x": 391.90625, + "y": 239.55078125, + "pressure": 0.5 + }, + { + "time": 1679163240240, + "x": 398.15234375, + "y": 246.27734375, + "pressure": 0.5 + }, + { + "time": 1679163240265, + "x": 401.203125, + "y": 250.34375, + "pressure": 0.5 + }, + { + "time": 1679163240282, + "x": 422.734375, + "y": 290.84375, + "pressure": 0.5 + }, + { + "time": 1679163240302, + "x": 425.6953125, + "y": 298.54296875, + "pressure": 0.5 + }, + { + "time": 1679163240320, + "x": 436.796875, + "y": 332.08203125, + "pressure": 0.5 + }, + { + "time": 1679163240339, + "x": 442.7421875, + "y": 353.125, + "pressure": 0.5 + }, + { + "time": 1679163240356, + "x": 444.265625, + "y": 358.20703125, + "pressure": 0.5 + }, + { + "time": 1679163240374, + "x": 446.5859375, + "y": 364.38671875, + "pressure": 0.5 + }, + { + "time": 1679163240679, + "x": 437.24609375, + "y": 363.81640625, + "pressure": 0.5 + }, + { + "time": 1679163240697, + "x": 430.47265625, + "y": 362.12109375, + "pressure": 0.5 + }, + { + "time": 1679163240718, + "x": 415.99609375, + "y": 359.21484375, + "pressure": 0.5 + }, + { + "time": 1679163240737, + "x": 380.85546875, + "y": 352.921875, + "pressure": 0.5 + }, + { + "time": 1679163240753, + "x": 363.48046875, + "y": 350.4375, + "pressure": 0.5 + }, + { + "time": 1679163240773, + "x": 348.07421875, + "y": 348.65625, + "pressure": 0.5 + }, + { + "time": 1679163240789, + "x": 341.30078125, + "y": 348.08984375, + "pressure": 0.5 + }, + { + "time": 1679163240806, + "x": 320.05078125, + "y": 346.9296875, + "pressure": 0.5 + }, + { + "time": 1679163240823, + "x": 313.27734375, + "y": 346.9296875, + "pressure": 0.5 + }, + { + "time": 1679163240839, + "x": 300.21484375, + "y": 345.51171875, + "pressure": 0.5 + }, + { + "time": 1679163240856, + "x": 292.91015625, + "y": 344.6015625, + "pressure": 0.5 + }, + { + "time": 1679163240872, + "x": 287.55078125, + "y": 343.77734375, + "pressure": 0.5 + }, + { + "time": 1679163240912, + "x": 281.6171875, + "y": 342.45703125, + "pressure": 0.5 + }, + { + "time": 1679163241133, + "x": 278.0078125, + "y": 348.57421875, + "pressure": 0 + } + ] + }, + { + "penColor": "black", + "dotSize": 0, + "minWidth": 0.5, + "maxWidth": 2.5, + "velocityFilterWeight": 0.7, + "points": [ + { + "time": 1679163242657, + "x": 504.140625, + "y": 502.6640625, + "pressure": 0.5 + }, + { + "time": 1679163242801, + "x": 504.140625, + "y": 493.484375, + "pressure": 0.5 + }, + { + "time": 1679163242818, + "x": 511.8515625, + "y": 470.359375, + "pressure": 0.5 + }, + { + "time": 1679163242838, + "x": 528.078125, + "y": 424.5859375, + "pressure": 0.5 + }, + { + "time": 1679163242856, + "x": 541.65625, + "y": 389.0234375, + "pressure": 0.5 + }, + { + "time": 1679163242880, + "x": 550.5, + "y": 359.83984375, + "pressure": 0.5 + }, + { + "time": 1679163242904, + "x": 555.94140625, + "y": 343.44921875, + "pressure": 0.5 + }, + { + "time": 1679163242924, + "x": 561.41796875, + "y": 325.30078125, + "pressure": 0.5 + }, + { + "time": 1679163242944, + "x": 563.59375, + "y": 320.28125, + "pressure": 0.5 + }, + { + "time": 1679163243209, + "x": 568.828125, + "y": 329.4609375, + "pressure": 0.5 + }, + { + "time": 1679163243240, + "x": 581.67578125, + "y": 353.46875, + "pressure": 0.5 + }, + { + "time": 1679163243259, + "x": 627.18359375, + "y": 418.64453125, + "pressure": 0.5 + }, + { + "time": 1679163243278, + "x": 640.76953125, + "y": 440.91796875, + "pressure": 0.5 + }, + { + "time": 1679163243294, + "x": 647.453125, + "y": 452.0078125, + "pressure": 0.5 + }, + { + "time": 1679163243315, + "x": 650.3671875, + "y": 456.65234375, + "pressure": 0.5 + }, + { + "time": 1679163243375, + "x": 652.06640625, + "y": 467.99609375, + "pressure": 0.5 + }, + { + "time": 1679163243409, + "x": 653.22265625, + "y": 476.9765625, + "pressure": 0.5 + }, + { + "time": 1679163243620, + "x": 648.609375, + "y": 480.16015625, + "pressure": 0.5 + }, + { + "time": 1679163243638, + "x": 627.8671875, + "y": 472.9453125, + "pressure": 0.5 + }, + { + "time": 1679163243654, + "x": 553.46875, + "y": 449.10546875, + "pressure": 0.5 + }, + { + "time": 1679163243672, + "x": 520.2578125, + "y": 438.7578125, + "pressure": 0.5 + }, + { + "time": 1679163243689, + "x": 499.58984375, + "y": 433.37109375, + "pressure": 0.5 + }, + { + "time": 1679163243708, + "x": 485.984375, + "y": 431.0546875, + "pressure": 0.5 + }, + { + "time": 1679163243725, + "x": 474.890625, + "y": 430.54296875, + "pressure": 0.5 + }, + { + "time": 1679163243757, + "x": 467.81640625, + "y": 430.54296875, + "pressure": 0.5 + }, + { + "time": 1679163243973, + "x": 472.60546875, + "y": 428.90234375, + "pressure": 0.5 + }, + { + "time": 1679163243990, + "x": 485.07421875, + "y": 422.12890625, + "pressure": 0.5 + }, + { + "time": 1679163244006, + "x": 518.31640625, + "y": 406.3203125, + "pressure": 0.5 + }, + { + "time": 1679163244026, + "x": 554.9921875, + "y": 388.87890625, + "pressure": 0.5 + }, + { + "time": 1679163244042, + "x": 606.04296875, + "y": 364.859375, + "pressure": 0.5 + }, + { + "time": 1679163244058, + "x": 643.69921875, + "y": 347.74609375, + "pressure": 0.5 + }, + { + "time": 1679163244076, + "x": 656.16796875, + "y": 341.5078125, + "pressure": 0.5 + }, + { + "time": 1679163244094, + "x": 675.0703125, + "y": 331.76953125, + "pressure": 0.5 + }, + { + "time": 1679163244127, + "x": 679.640625, + "y": 329.34375, + "pressure": 0.5 + }, + { + "time": 1679163244373, + "x": 673.9375, + "y": 335.29296875, + "pressure": 0.5 + }, + { + "time": 1679163244389, + "x": 661.46484375, + "y": 345.38671875, + "pressure": 0.5 + }, + { + "time": 1679163244407, + "x": 643.5078125, + "y": 359.8984375, + "pressure": 0.5 + }, + { + "time": 1679163244426, + "x": 625.12890625, + "y": 376.8046875, + "pressure": 0.5 + }, + { + "time": 1679163244442, + "x": 609.25390625, + "y": 394.078125, + "pressure": 0.5 + }, + { + "time": 1679163244459, + "x": 596.00390625, + "y": 410.6484375, + "pressure": 0.5 + }, + { + "time": 1679163244476, + "x": 581.484375, + "y": 428.59765625, + "pressure": 0.5 + }, + { + "time": 1679163244496, + "x": 574.71875, + "y": 437.390625, + "pressure": 0.5 + }, + { + "time": 1679163244520, + "x": 552.1171875, + "y": 464.26171875, + "pressure": 0.5 + }, + { + "time": 1679163244542, + "x": 538.2265625, + "y": 478.703125, + "pressure": 0.5 + }, + { + "time": 1679163244577, + "x": 523.5859375, + "y": 490.2109375, + "pressure": 0.5 + }, + { + "time": 1679163244599, + "x": 517.05859375, + "y": 495.08203125, + "pressure": 0.5 + }, + { + "time": 1679163244638, + "x": 507.80078125, + "y": 503.5234375, + "pressure": 0.5 + }, + { + "time": 1679163244673, + "x": 501.26171875, + "y": 509.3046875, + "pressure": 0.5 + } + ] + } +] diff --git a/test/karma.conf.js b/test/karma.conf.js index 47c903535..9a63d8622 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -29,6 +29,10 @@ module.exports = (config) => { pattern: 'src/widget/*/*.*', included: false, }, + { + pattern: 'test/fixtures/**/*.*', + included: false, + }, { pattern: 'config.js', included: false, diff --git a/test/spec/ResizableSignaturePad.spec.js b/test/spec/ResizableSignaturePad.spec.js new file mode 100644 index 000000000..e6c8ecf1a --- /dev/null +++ b/test/spec/ResizableSignaturePad.spec.js @@ -0,0 +1,544 @@ +// @ts-check + +import { expect } from 'chai'; +import { + getMaxWidth, + ResizableSignaturePad, +} from '../../src/widget/draw/ResizableSignaturePad'; + +// This is tested independently, in anticipation that it may be general enough +// to reuse elsewhere +describe('Determining element max width', () => { + const maxWidthElement = document.createElement('div'); + + beforeEach(() => { + document.body.append(maxWidthElement); + }); + + afterEach(() => { + maxWidthElement.remove(); + }); + + maxWidthElement.style.maxWidth = '720px'; + + it("gets the max width on an element's direct styles", () => { + const maxWidth = getMaxWidth(maxWidthElement); + + expect(maxWidth).to.equal(720); + }); + + it('fails to get the max width for disconnected elements', () => { + maxWidthElement.remove(); + + const fn = () => getMaxWidth(maxWidthElement); + + expect(fn).to.throw(); + }); + + it('gets the max width of a descendant element', () => { + const outer = document.createElement('div'); + const inner = document.createElement('div'); + + maxWidthElement.append(outer); + outer.append(inner); + + const maxWidth = getMaxWidth(inner); + + expect(maxWidth).to.equal(720); + }); + + it('accounts for reduced space inside the ancestors', () => { + const outer = document.createElement('div'); + const inner = document.createElement('div'); + + maxWidthElement.append(outer); + outer.append(inner); + + [maxWidthElement, outer].forEach((element) => { + element.style.boxSizing = 'border-box'; + element.style.padding = '10px'; + }); + + const maxWidth = getMaxWidth(inner); + + expect(maxWidth).to.equal(680); + }); +}); + +describe('Resizable extensions to the signature_pad library', () => { + // Several of these tests depend on comparing actual bitmap binary data to + // fixture images which were manually created in Chrome. Those tests will + // fail in other browsers, due to very minor differences in rasterization. + // For now, we'll skip this suite except in the main Chrome run. + if (!navigator.userAgent.includes('HeadlessChrome')) { + return; + } + + const mutationObserver = new MutationObserver(() => {}); + + const waitForNextAnimationFrame = async () => { + await new Promise((resolve) => { + requestAnimationFrame(() => { + requestAnimationFrame(resolve); + }); + }); + }; + + const waitForIdleDOM = async () => { + await waitForNextAnimationFrame(); + + while (mutationObserver.takeRecords().length > 0) { + // This could probably be accomplished with an async generator, but + // the intent would be much less obvious + // eslint-disable-next-line no-await-in-loop + await waitForNextAnimationFrame(); + } + }; + + /** + * @return {Promise} + */ + const loadPadImage = async () => { + await waitForIdleDOM(); + + const url = await pad.toObjectURL(); + + if (url === '') { + return; + } + + const response = await fetch(url); + const blob = await response.blob(); + + return createImageBitmap(blob); + }; + + /** + * @param {string} url + */ + const getJSON = async (url) => { + const response = await fetch(url); + + return response.json(); + }; + + /** + * @param {string} url + */ + const getImageData = async (url) => { + const response = await fetch(url); + const blob = await response.blob(); + const arrayBuffer = await blob.arrayBuffer(); + + return new Int8Array(arrayBuffer); + }; + + /** + * @param {ResizableSignaturePad} pad + */ + const getPadRasterImageData = async (pad) => { + const url = await pad.toObjectURL(); + + return getImageData(url); + }; + + /** + * @param {string} fileName + */ + const fixture = (fileName) => + `${window.location.origin}/base/test/fixtures/ResizableSignaturePad/${fileName}`; + + /** @type {sinon.SinonSandbox} */ + let sandbox; + + /** @type {HTMLDivElement} */ + let container; + + /** @type {HTMLDivElement} */ + let inner; + + /** @type {HTMLCanvasElement} */ + let canvas; + + /** @type {ResizableSignaturePad} */ + let pad; + + /** @type {HTMLImageElement} */ + let image; + + /** @type {number} */ + let devicePixelRatio; + + /** @type {sinon.SinonSpy | null} */ + let onChange = null; + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + + devicePixelRatio = 1; + sandbox.stub(window, 'devicePixelRatio').get(() => devicePixelRatio); + + container = document.createElement('div'); + inner = document.createElement('div'); + canvas = document.createElement('canvas'); + + Object.assign(container.style, { + position: 'relative', + width: '720px', + }); + + Object.assign(inner.style, { + position: 'relative', + width: '100%', + paddingTop: '75%', + }); + + Object.assign(canvas.style, { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }); + + document.body.append(container); + container.append(inner); + inner.append(canvas); + + await waitForIdleDOM(); + + canvas.width = 1; + canvas.height = 1; + + image = new Image(); + + container.append(image); + + pad = new ResizableSignaturePad(canvas); + + onChange = null; + + mutationObserver.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + }); + }); + + afterEach(async () => { + sandbox.restore(); + container.remove(); + + if (onChange != null) { + pad.removeEventListener('change', onChange); + } + + await waitForIdleDOM(); + + mutationObserver.disconnect(); + mutationObserver.takeRecords(); + }); + + it('scales the displayed canvas on load', async () => { + await waitForIdleDOM(); + + expect(canvas.width).to.equal(720); + expect(canvas.height).to.equal(540); + }); + + it('scales the displayed canvas when resized by its container', async () => { + container.style.width = '500px'; + + await waitForIdleDOM(); + + expect(canvas.width).to.equal(500); + expect(canvas.height).to.equal(375); + }); + + it('scales the displayed canvas with the current pixel ratio', async () => { + devicePixelRatio = 2; + + await waitForIdleDOM(); + + expect(canvas.width).to.equal(1440); + expect(canvas.height).to.equal(1080); + }); + + it('does not scale below a pixel ratio of 1', async () => { + devicePixelRatio = 0.5; + + await waitForIdleDOM(); + + expect(canvas.width).to.equal(720); + expect(canvas.height).to.equal(540); + }); + + it('sets a base image by URL', async () => { + const url = fixture('720x540.png'); + + await pad.setBaseImage(url); + await waitForIdleDOM(); + + const expected = await getImageData(url); + const actual = await getPadRasterImageData(pad); + + expect(actual).to.deep.equal(expected); + }); + + /** + * @param {string} url + */ + const getFile = async (url) => { + const response = await fetch(url); + const blob = await response.blob(); + + return new File([blob], 'file-name'); + }; + + it('sets a base image by File', async () => { + const url = fixture('720x540.png'); + const file = await getFile(url); + + await pad.setBaseImage(file); + await waitForIdleDOM(); + + const expected = await getImageData(url); + const actual = await getPadRasterImageData(pad); + + expect(actual).to.deep.equal(expected); + }); + + it('renders drawing data on a base image', async () => { + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + const drawingDataURL = fixture('drawing-data.json'); + const drawingData = await getJSON(drawingDataURL); + + sandbox.stub(pad, 'toData').callsFake(() => drawingData); + pad.dispatchEvent(new CustomEvent('endStroke')); + + await waitForIdleDOM(); + + const url = fixture('720x540-with-drawing.png'); + const expected = await getImageData(url); + const actual = await getPadRasterImageData(pad); + + expect(actual).to.deep.equal(expected); + }); + + it('rasterizes the scaled image at the maximum display size', async () => { + devicePixelRatio = 2; + + const drawingDataURL = fixture('drawing-data.json'); + const drawingData = await getJSON(drawingDataURL); + + sandbox.stub(pad, 'toData').callsFake(() => drawingData); + pad.dispatchEvent(new CustomEvent('endStroke')); + + await waitForIdleDOM(); + + container.style.width = '500px'; + + await waitForIdleDOM(); + + const { width, height } = (await loadPadImage()) ?? {}; + + expect(width).to.equal(720); + expect(height).to.equal(540); + }); + + const fixturesToDimensions = [ + { + fileName: '400x400.png', + constraint: 'height', + dimensions: { + width: 533, + height: 400, + }, + }, + { + fileName: '1200x540.png', + constraint: 'width', + dimensions: { + width: 1200, + height: 900, + }, + }, + ]; + + fixturesToDimensions.forEach(({ fileName, constraint, dimensions }) => { + it(`scales the rasterized image proportionally to fit the base image's ${constraint}`, async () => { + devicePixelRatio = 2; + + const baseImageURL = fixture(fileName); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + const { width, height } = (await loadPadImage()) ?? {}; + + expect({ width, height }).to.deep.equal(dimensions); + }); + }); + + it('renders drawing data at full size fidelity when the canvas has been resized by its container, then restored to its maximum size', async () => { + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + const drawingDataURL = fixture('drawing-data.json'); + const drawingData = await getJSON(drawingDataURL); + + sandbox.stub(pad, 'toData').callsFake(() => drawingData); + pad.dispatchEvent(new CustomEvent('endStroke')); + + await waitForIdleDOM(); + + container.style.width = '500px'; + + await waitForIdleDOM(); + + container.style.width = '720px'; + + await waitForIdleDOM(); + + const url = fixture('720x540-with-drawing.png'); + const expected = await getImageData(url); + const actual = await getPadRasterImageData(pad); + + expect(actual).to.deep.equal(expected); + }); + + // The implementation details under test here are the same as with the test + // above. This test was written first, but initially failed because the + // internal drawing data was being stored internally at the canvas element's + // current scale after a resize, causing minor inconsistencies (likely due + // to float precision errors). The above test is also meaningful in terms of + // describing the behavior, so both are kept despite total overlap of the + // underlying behavior under test. + it('renders drawing data at full size fidelity when the canvas is currently resized by its container', async () => { + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + const drawingDataURL = fixture('drawing-data.json'); + const drawingData = await getJSON(drawingDataURL); + + sandbox.stub(pad, 'toData').callsFake(() => drawingData); + pad.dispatchEvent(new CustomEvent('endStroke')); + + await waitForIdleDOM(); + + container.style.width = '500px'; + + await waitForIdleDOM(); + + const url = fixture('720x540-with-drawing.png'); + const expected = await getImageData(url); + const actual = await getPadRasterImageData(pad); + + expect(actual).to.deep.equal(expected); + }); + + it('reverts the most recent stroke', async () => { + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + const drawingDataURL = fixture('drawing-data.json'); + const drawingData = await getJSON(drawingDataURL); + + sandbox.stub(pad, 'toData').callsFake(() => drawingData); + pad.dispatchEvent(new CustomEvent('endStroke')); + + await waitForIdleDOM(); + + pad.undoStroke(); + + const url = fixture('720x540-after-undo.png'); + const expected = await getImageData(url); + const actual = await getPadRasterImageData(pad); + + expect(actual).to.deep.equal(expected); + }); + + it('resets the base image and drawing', async () => { + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + const drawingDataURL = fixture('drawing-data.json'); + const drawingData = await getJSON(drawingDataURL); + + sandbox.stub(pad, 'toData').callsFake(() => drawingData); + pad.dispatchEvent(new CustomEvent('endStroke')); + + await waitForIdleDOM(); + + pad.reset(); + + const objectURL = await pad.toObjectURL(); + + expect(objectURL).to.equal(''); + }); + + it('dispatches a change event when the canvas becomes visible', async () => { + onChange = sandbox.fake(); + pad.addEventListener('change', onChange); + + container.style.display = 'none'; + + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + expect(onChange.callCount).to.equal(0); + + container.style.display = 'block'; + + await waitForIdleDOM(); + + expect(onChange.callCount).to.equal(1); + }); + + it('revokes previously created object URLs on change, to release the reference to their underlying `Blob`s', async () => { + const initialBaseImageURL = fixture('400x400.png'); + + await pad.setBaseImage(initialBaseImageURL); + await waitForIdleDOM(); + + const initialObjectURL = await pad.toObjectURL(); + + const fetchInitialBlob = async () => { + try { + const response = await fetch(initialObjectURL); + const blob = await response.blob(); + + return blob; + } catch { + return null; + } + }; + + let initialBlob = await fetchInitialBlob(); + + expect(initialBlob).to.be.an.instanceOf(Blob); + + const baseImageURL = fixture('720x540.png'); + + await pad.setBaseImage(baseImageURL); + await waitForIdleDOM(); + + initialBlob = await fetchInitialBlob(); + + expect(initialBlob).to.equal(null); + }); +}); diff --git a/test/types/env.d.ts b/test/types/env.d.ts new file mode 100644 index 000000000..a7ca16a52 --- /dev/null +++ b/test/types/env.d.ts @@ -0,0 +1 @@ +declare const sinon: Sinon; From 86ee154efec1be004bfc6853097d113bf53c52e6 Mon Sep 17 00:00:00 2001 From: eyelidlessness Date: Mon, 20 Mar 2023 11:38:52 -0700 Subject: [PATCH 2/2] Hopefully make detecting idleness more reliable --- test/spec/ResizableSignaturePad.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/ResizableSignaturePad.spec.js b/test/spec/ResizableSignaturePad.spec.js index e6c8ecf1a..0ac2b46bb 100644 --- a/test/spec/ResizableSignaturePad.spec.js +++ b/test/spec/ResizableSignaturePad.spec.js @@ -87,12 +87,12 @@ describe('Resizable extensions to the signature_pad library', () => { const waitForIdleDOM = async () => { await waitForNextAnimationFrame(); - while (mutationObserver.takeRecords().length > 0) { + do { // This could probably be accomplished with an async generator, but // the intent would be much less obvious // eslint-disable-next-line no-await-in-loop await waitForNextAnimationFrame(); - } + } while (mutationObserver.takeRecords().length > 0); }; /**