diff --git a/lib/DataHarmonizer.js b/lib/DataHarmonizer.js index 1f38186b..0d931931 100644 --- a/lib/DataHarmonizer.js +++ b/lib/DataHarmonizer.js @@ -67,6 +67,7 @@ class DataHarmonizer { // Currently selected cell range[row,col,row2,col2] current_selection = [null, null, null, null]; field_settings = {}; + fields = []; template_unique_keys = []; constructor(root, options = {}) { @@ -364,15 +365,15 @@ class DataHarmonizer { this.setExportField(new_field, true); /* https://linkml.io/linkml-model/docs/structured_pattern/ - https://github.com/linkml/linkml/issues/674 - Look up its parts in "settings", and assemble a regular - expression for them to compile into "pattern" field. - This augments basic datatype validation - structured_pattern: - syntax: "{float} {unit.length}" - interpolated: true ## all {...}s are replaced using settings - partial_match: false - */ + https://github.com/linkml/linkml/issues/674 + Look up its parts in "settings", and assemble a regular + expression for them to compile into "pattern" field. + This augments basic datatype validation + structured_pattern: + syntax: "{float} {unit.length}" + interpolated: true ## all {...}s are replaced using settings + partial_match: false + */ if ('structured_pattern' in new_field) { switch (new_field.structured_pattern.syntax) { case '{UPPER_CASE}': @@ -413,6 +414,8 @@ class DataHarmonizer { this.template[ptr]['children'].sort((a, b) => a.rank - b.rank); } + this.fields = this.getFields(); + this.createHot(); } @@ -522,18 +525,18 @@ class DataHarmonizer { self.helpSidebar.setContent(helpContent); } }, - afterRender: () => { - // Bit of a hackey way to RESTORE classes to secondary headers. They are - // removed by Handsontable when re-rendering main table. - $('.secondary-header-text').each((_, e) => { - const $cellElement = $(e).closest('th'); - $cellElement.addClass('secondary-header-cell'); - if ($(e).hasClass('required')) { - $cellElement.addClass('required'); - } else if ($(e).hasClass('recommended')) { - $cellElement.addClass('recommended'); + // Bit of a hackey way to RESTORE classes to secondary headers. They are + // removed by Handsontable when re-rendering main table. + afterGetColHeader: (column, TH, headerlev) => { + if (headerlev == 1) { + // Enables double-click listener for column help + $(TH).addClass('secondary-header-cell'); + if (column > -1) { + const field = self.fields[column]; + if (field.recommended) $(TH).addClass('recommended'); + else if (field.required) $(TH).addClass('required'); } - }); + } }, afterRenderer: (TD, row, col) => { if (Object.prototype.hasOwnProperty.call(self.invalid_cells, row)) { @@ -559,14 +562,19 @@ class DataHarmonizer { addRowsToBottom(numRows) { this.runBehindLoadingScreen(() => { - this.hot.alter('insert_row', this.hot.countRows() - 1 + numRows, numRows); + this.hot.alter( + 'insert_row_below', + this.hot.countRows() - 1 + numRows, + numRows + ); }); } showAllColumns() { this.hot.scrollViewportTo(0, 1); const hiddenColsPlugin = this.hot.getPlugin('hiddenColumns'); - hiddenColsPlugin.showColumns(hiddenColsPlugin.hiddenColumns); + const hidden = hiddenColsPlugin.getHiddenColumns(); + if (hidden) hiddenColsPlugin.showColumns(hidden); this.hot.render(); } @@ -626,7 +634,8 @@ class DataHarmonizer { // Un-hide all currently hidden cols const hiddenRowsPlugin = this.hot.getPlugin('hiddenRows'); - hiddenRowsPlugin.showRows(hiddenRowsPlugin.hiddenRows); + const hidden = hiddenRowsPlugin.getHiddenRows(); + hiddenRowsPlugin.showRows(hidden); // Hide user-specified rows const rows = [...Array(this.hot.countRows()).keys()]; @@ -818,8 +827,9 @@ class DataHarmonizer { * @param {Object} hot Handsontable instance of grid. */ scrollTo(row, column) { - const hiddenCols = this.hot.getPlugin('hiddenColumns').hiddenColumns; - if (hiddenCols.includes(column)) { + const hiddenColsPlugin = this.hot.getPlugin('hiddenColumns'); + const hidden = hiddenColsPlugin.getHiddenColumns(); + if (hidden.includes(column)) { // If user wants to scroll to a hidden column, make all columns unhidden this.showAllColumns(); } @@ -1671,9 +1681,9 @@ class DataHarmonizer { } // This will output a list of fields added to exportHeaders by way of template specification which haven't been included in export.js //if (field_message) - // console.log('Export fields added by template:', field_message) + // console.log('Export fields added by template:', field_message) //if (field_export_message) - // console.log('Export fields stated in export.js):', field_export_message) + // console.log('Export fields stated in export.js):', field_export_message) } /** @@ -2049,15 +2059,15 @@ class DataHarmonizer { } /** - * Adjust given dateString date to match year or month granularity given by - * dateGranularity parameter. If month unit required but not supplied, then - * a yyyy-__-01 will be supplied to indicate that month needs attention. - * - * @param {String} dateGranularity, either 'year' or 'month' - * @param {String} ISO 8601 date string or leading part, possibly just YYYY or - YYYY-MM - * @return {String} ISO 8601 date string. - */ + * Adjust given dateString date to match year or month granularity given by + * dateGranularity parameter. If month unit required but not supplied, then + * a yyyy-__-01 will be supplied to indicate that month needs attention. + * + * @param {String} dateGranularity, either 'year' or 'month' + * @param {String} ISO 8601 date string or leading part, possibly just YYYY or + YYYY-MM + * @return {String} ISO 8601 date string. + */ setDateChange(dateGranularity, dateString, dateBlank = '__') { var dateParts = dateString.split('-'); // Incomming date may have nothing in it. @@ -2189,7 +2199,7 @@ class DataHarmonizer { timeFormat: this.timeFormat, }); - let provenanceChanges = []; + let cellChanges = []; const indexToRowMap = new Map(); const rowToIndexMap = new Map(); let index = 0; @@ -2225,7 +2235,7 @@ class DataHarmonizer { // 1st row of provenance datatype field is forced to have a // 'DataHarmonizer Version: 0.13.0' etc. value. Change happens silently. if (datatype === 'Provenance') { - checkProvenance(provenanceChanges, full_version, cellVal, row, col); + checkProvenance(cellChanges, full_version, cellVal, row, col); } let valid = false; @@ -2335,7 +2345,7 @@ class DataHarmonizer { valid = vocabValid; if (update) { - this.hot.setDataAtCell(row, col, update, 'thisChange'); + cellChanges.push([row, col, update, 'thisChange']); } } else { const [vocabValid, update] = validateValAgainstVocab( @@ -2344,7 +2354,7 @@ class DataHarmonizer { ); valid = vocabValid; if (update) { - this.hot.setDataAtCell(row, col, update, 'thisChange'); + cellChanges.push([row, col, update, 'thisChange']); } } // Hardcoded case: If field is xsd:token, and 1st picklist is @@ -2373,6 +2383,7 @@ class DataHarmonizer { } // row loop end // Check row uniqueness for identifier fields and unique_key sets + // This is not affected by a column's datatype. const doUniqueValidation = (columnNumbers) => { const values = columnNumbers.map((columnNumber) => { if (columnNumber < 0) { @@ -2397,6 +2408,7 @@ class DataHarmonizer { }); }; + // Returns FIRST index for a field marked as an .identifier const identifierFieldCol = fields.findIndex( (field) => field.identifier && field.identifier === true ); @@ -2404,6 +2416,9 @@ class DataHarmonizer { doUniqueValidation([identifierFieldCol]); } + // .template_unique_keys contains an object of 0 or more unique keys, + // each key being a combination of one or more .unique_key_slots names. + // Does unique validation on each unique key combo. for (const unique_key of this.template_unique_keys) { const uniqueKeyCols = unique_key.unique_key_slots.map((fieldName) => { return fields.findIndex((field) => field.name === fieldName); @@ -2411,8 +2426,10 @@ class DataHarmonizer { doUniqueValidation(uniqueKeyCols); } - // Here an array of (row, column, value)... is being passed - if (provenanceChanges.length) this.hot.setDataAtCell(provenanceChanges); + // Here an array of (row, column, value)... is being passed, which causes + // rendering operations to happen like .batch(), after all setDataAtCell() + // operations are completed. + if (cellChanges.length) this.hot.setDataAtCell(cellChanges); return invalidCells; } diff --git a/lib/editors/FlatpickrEditor.js b/lib/editors/FlatpickrEditor.js index bd047686..728c3382 100644 --- a/lib/editors/FlatpickrEditor.js +++ b/lib/editors/FlatpickrEditor.js @@ -52,14 +52,14 @@ class FlatpickrEditor extends Handsontable.editors.TextEditor { */ const eventManager = new Handsontable.EventManager(this); eventManager.addEventListener(this.datePicker, 'mousedown', (event) => { - Handsontable.dom.stopPropagation(event); + Handsontable.dom.stopImmediatePropagation(event); }); eventManager.addEventListener(this.datePicker, 'keydown', (event) => { - Handsontable.dom.stopPropagation(event); + Handsontable.dom.stopImmediatePropagation(event); }); eventManager.addEventListener(this.TEXTAREA, 'keydown', (event) => { if (event.keyCode === Handsontable.helper.KEY_CODES.ENTER) { - Handsontable.dom.stopPropagation(event); + Handsontable.dom.stopImmediatePropagation(event); } }); this.hideDatepicker(); diff --git a/package.json b/package.json index 0bbb0454..52ad1266 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "date-fns": "^2.28.0", "file-saver": "^2.0.5", "flatpickr": "^4.6.13", - "handsontable": "^7.4.2", + "handsontable": "13.1.0", "sifter": "^0.5.4", "xlsx": "^0.18.5" }, diff --git a/yarn.lock b/yarn.lock index aba7d5e9..24cddfa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1032,14 +1032,6 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@handsontable/formulajs@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@handsontable/formulajs/-/formulajs-2.0.2.tgz" - integrity sha512-maIyMJtYjA5e/R9nyA22Qd7Yw73MBSxClJvle0a8XWAS/5l6shc/OFpQqrmwMy4IXUCmywJ9ER0gOGz/YA720w== - dependencies: - bessel "^1.0.2" - jstat "^1.9.2" - "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz" @@ -1635,7 +1627,7 @@ "@types/pikaday@1.7.4": version "1.7.4" - resolved "https://registry.npmjs.org/@types/pikaday/-/pikaday-1.7.4.tgz" + resolved "https://registry.yarnpkg.com/@types/pikaday/-/pikaday-1.7.4.tgz#aa41f928f0f5af31a4a656f471a78177a9260abf" integrity sha512-0KsHVyw5pTG829nqG4IRu7m+BFQlFEBdbE/1i3S5182HeKUKv1uEW0gyEmkJVp5i4IV+9pyh23O83+KpRkSQbw== dependencies: moment ">=2.14.0" @@ -2168,14 +2160,9 @@ batch@0.6.1: resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== -bessel@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/bessel/-/bessel-1.0.2.tgz" - integrity sha512-Al3nHGQGqDYqqinXhQzmwmcRToe/3WyBv4N8aZc5Pef8xw2neZlR9VPi84Sa23JtgWcucu18HxVZrnI0fn2etw== - bignumber.js@^8.0.1: version "8.1.1" - resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.1.1.tgz" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885" integrity sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ== binary-extensions@^2.0.0: @@ -2380,6 +2367,13 @@ char-regex@^1.0.2: resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chevrotain@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-6.5.0.tgz#dcbef415516b0af80fd423cc0d96b28d3f11374e" + integrity sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg== + dependencies: + regexp-to-ast "0.4.0" + chokidar@^3.4.0, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" @@ -2593,10 +2587,10 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1: browserslist "^4.21.3" semver "7.0.0" -core-js@^3.0.0: - version "3.23.2" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.23.2.tgz" - integrity sha512-ELJOWxNrJfOH/WK4VJ3Qd+fOqZuOuDNDJz0xG6Bt4mGg2eO/UT9CljCrbqDGovjLKUrGajEEBcoTOc0w+yBYeQ== +core-js@^3.31.1: + version "3.32.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7" + integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ== core-util-is@~1.0.0: version "1.0.3" @@ -2885,6 +2879,11 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +dompurify@^2.1.1: + version "2.4.7" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.7.tgz#277adeb40a2c84be2d42a8bcd45f582bfa4d0cfc" + integrity sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ== + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" @@ -3498,17 +3497,19 @@ handle-thing@^2.0.0: resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -handsontable@^7.4.2: - version "7.4.2" - resolved "https://registry.npmjs.org/handsontable/-/handsontable-7.4.2.tgz" - integrity sha512-xJ81nZfXWHmS+K8/Eshj776MQSe8003iue1hHumgb0bnJmG/WLOxRpN+Vurdl/WPwI3+fQOqb9nTzmM5n/LI2g== +handsontable@13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/handsontable/-/handsontable-13.1.0.tgz#a820d331088a9e4a7db9f4974661208f51789771" + integrity sha512-KqJtS3rJeOWsFWCffnDlM8fcLMTqmW+Vbc7OCVM4X7dYDpfefgFerjNLIxLfT9xohcQoUOS1jcvXwV8dwSppVQ== dependencies: "@types/pikaday" "1.7.4" - core-js "^3.0.0" - hot-formula-parser "^3.0.1" - moment "2.24.0" + core-js "^3.31.1" + dompurify "^2.1.1" + moment "2.29.4" numbro "2.1.2" - pikaday "1.8.0" + pikaday "1.8.2" + optionalDependencies: + hyperformula "^2.4.0" has-flag@^3.0.0: version "3.0.0" @@ -3544,14 +3545,6 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hot-formula-parser@^3.0.1: - version "3.0.2" - resolved "https://registry.npmjs.org/hot-formula-parser/-/hot-formula-parser-3.0.2.tgz" - integrity sha512-W/Dj/UbIyuViMIQOQD6tUEVySl7jd6ei+gfWslTiRqa4yRhkyHnIz8N4oLnqgDRhhVAQIcFF5NfNz49k4X8IxQ== - dependencies: - "@handsontable/formulajs" "^2.0.2" - tiny-emitter "^2.1.0" - hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz" @@ -3667,6 +3660,15 @@ humanize@^0.0.9: resolved "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz" integrity sha512-bvZZ7vXpr1RKoImjuQ45hJb5OvE2oJafHysiD/AL3nkqTZH2hFCjQ3YZfCd63FefDitbJze/ispUPP0gfDsT2Q== +hyperformula@^2.4.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hyperformula/-/hyperformula-2.5.0.tgz#b4c88de766ed5f6fb9f811ff4ef02290fbfe3a76" + integrity sha512-HkP7JZAmG7EQFF5XAhB3aGtTHvafblSRITTMYUsVoT9czIvYY7CvMQFfK1JNHJUVS844t8bnJpKEOqwcgBcHZg== + dependencies: + chevrotain "^6.5.0" + tiny-emitter "^2.1.0" + unorm "^1.6.0" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -4346,11 +4348,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jstat@^1.9.2: - version "1.9.5" - resolved "https://registry.npmjs.org/jstat/-/jstat-1.9.5.tgz" - integrity sha512-cWnp4vObF5GmB2XsIEzxI/1ZTcYlcfNqxQ/9Fp5KFUa0Jf/4tO0ZkGVnqoEHDisJvYgvn5n3eWZbd2xTVJJPUQ== - kind-of@^6.0.2: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" @@ -4558,15 +4555,10 @@ minimist@~0.0.1: resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" integrity sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw== -moment@2.24.0: - version "2.24.0" - resolved "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== - -moment@>=2.14.0: - version "2.29.3" - resolved "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz" - integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== +moment@2.29.4, moment@>=2.14.0: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== ms@2.0.0: version "2.0.0" @@ -4665,7 +4657,7 @@ nth-check@^2.0.1: numbro@2.1.2: version "2.1.2" - resolved "https://registry.npmjs.org/numbro/-/numbro-2.1.2.tgz" + resolved "https://registry.yarnpkg.com/numbro/-/numbro-2.1.2.tgz#2d51104f09b5d69aef7e15bb565d7795e47ecfd6" integrity sha512-7w833BxZmKGLE9HI0aREtNVRVH6WTYUUlWf4qgA5gKNhPQ4F/MRZ14sc0v8eoLORprk9ZTVwYaLwj8N3Zgxwiw== dependencies: bignumber.js "^8.0.1" @@ -4894,10 +4886,10 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pikaday@1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/pikaday/-/pikaday-1.8.0.tgz" - integrity sha512-SgGxMYX0NHj9oQnMaSyAipr2gOrbB4Lfs/TJTb6H6hRHs39/5c5VZi73Q8hr53+vWjdn6HzkWcj8Vtl3c9ziaA== +pikaday@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.8.2.tgz#72cc73fab7ccc068cbdf7dcaa1ce400fcfd894e3" + integrity sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew== pirates@^4.0.4: version "4.0.5" @@ -5340,6 +5332,11 @@ regenerator-transform@^0.15.0: dependencies: "@babel/runtime" "^7.8.4" +regexp-to-ast@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.4.0.tgz#f3dbcb42726cd71902ba50193f63eab5325cd7cb" + integrity sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw== + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" @@ -5990,7 +5987,7 @@ thunky@^1.0.2: tiny-emitter@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== tmpl@1.0.5: @@ -6090,6 +6087,11 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unorm@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af" + integrity sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"