From 59c90a5987dc1543ee46b5d5287a830fc6bddc83 Mon Sep 17 00:00:00 2001 From: Sebazzz Date: Thu, 26 Apr 2018 20:06:53 +0200 Subject: [PATCH 1/2] Typescriptification: initial rename and build configuration --- package.json | 2 + packages/node_modules | 1 - packages/tko.utils/package.json | 3 +- packages/tko.utils/src/{array.js => array.ts} | 0 packages/tko.utils/src/{async.js => async.ts} | 0 .../src/{bind-shim.js => bind-shim.ts} | 0 packages/tko.utils/src/{css.js => css.ts} | 0 .../tko.utils/src/dom/{data.js => data.ts} | 0 .../src/dom/{disposal.js => disposal.ts} | 0 .../tko.utils/src/dom/{event.js => event.ts} | 0 .../tko.utils/src/dom/{fixes.js => fixes.ts} | 0 .../tko.utils/src/dom/{html.js => html.ts} | 0 .../tko.utils/src/dom/{info.js => info.ts} | 0 .../dom/{manipulation.js => manipulation.ts} | 0 ...electExtensions.js => selectExtensions.ts} | 0 ...{virtualElements.js => virtualElements.ts} | 0 packages/tko.utils/src/{error.js => error.ts} | 0 .../src/{function.js => function.ts} | 0 packages/tko.utils/src/{ie.js => ie.ts} | 0 packages/tko.utils/src/{index.js => index.ts} | 0 .../tko.utils/src/{jquery.js => jquery.ts} | 0 .../src/{memoization.js => memoization.ts} | 0 .../tko.utils/src/{object.js => object.ts} | 0 .../tko.utils/src/{options.js => options.ts} | 0 packages/tko.utils/src/{proto.js => proto.ts} | 0 .../tko.utils/src/{string.js => string.ts} | 0 .../tko.utils/src/{symbol.js => symbol.ts} | 0 packages/tko.utils/src/{tasks.js => tasks.ts} | 0 tsconfig.json | 4 +- tslint.json | 46 +++++++++++ yarn.lock | 78 ++++++++++++++++++- 31 files changed, 130 insertions(+), 4 deletions(-) delete mode 120000 packages/node_modules rename packages/tko.utils/src/{array.js => array.ts} (100%) rename packages/tko.utils/src/{async.js => async.ts} (100%) rename packages/tko.utils/src/{bind-shim.js => bind-shim.ts} (100%) rename packages/tko.utils/src/{css.js => css.ts} (100%) rename packages/tko.utils/src/dom/{data.js => data.ts} (100%) rename packages/tko.utils/src/dom/{disposal.js => disposal.ts} (100%) rename packages/tko.utils/src/dom/{event.js => event.ts} (100%) rename packages/tko.utils/src/dom/{fixes.js => fixes.ts} (100%) rename packages/tko.utils/src/dom/{html.js => html.ts} (100%) rename packages/tko.utils/src/dom/{info.js => info.ts} (100%) rename packages/tko.utils/src/dom/{manipulation.js => manipulation.ts} (100%) rename packages/tko.utils/src/dom/{selectExtensions.js => selectExtensions.ts} (100%) rename packages/tko.utils/src/dom/{virtualElements.js => virtualElements.ts} (100%) rename packages/tko.utils/src/{error.js => error.ts} (100%) rename packages/tko.utils/src/{function.js => function.ts} (100%) rename packages/tko.utils/src/{ie.js => ie.ts} (100%) rename packages/tko.utils/src/{index.js => index.ts} (100%) rename packages/tko.utils/src/{jquery.js => jquery.ts} (100%) rename packages/tko.utils/src/{memoization.js => memoization.ts} (100%) rename packages/tko.utils/src/{object.js => object.ts} (100%) rename packages/tko.utils/src/{options.js => options.ts} (100%) rename packages/tko.utils/src/{proto.js => proto.ts} (100%) rename packages/tko.utils/src/{string.js => string.ts} (100%) rename packages/tko.utils/src/{symbol.js => symbol.ts} (100%) rename packages/tko.utils/src/{tasks.js => tasks.ts} (100%) create mode 100644 tslint.json diff --git a/package.json b/package.json index 09d5e711..40998c0c 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "rollup-plugin-visualizer": "^0.3.1", "sinon": "^4.1", "standard": "^10.0.3", + "tslint": "5.9.1", + "tslib": "1.9.0", "typescript": "^2.6.2" }, "workspaces": [ diff --git a/packages/node_modules b/packages/node_modules deleted file mode 120000 index 945c9b46..00000000 --- a/packages/node_modules +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/packages/tko.utils/package.json b/packages/tko.utils/package.json index 3055fa0b..5f98e772 100644 --- a/packages/tko.utils/package.json +++ b/packages/tko.utils/package.json @@ -27,7 +27,8 @@ ] }, "dependencies": { - "tslib": "^1.8.0" + "@types/jquery": "3.3.1", + "tslib": "1.9.0" }, "__about__shared.package.json": "These properties are copied into all packages/*/package.json", "standard": { diff --git a/packages/tko.utils/src/array.js b/packages/tko.utils/src/array.ts similarity index 100% rename from packages/tko.utils/src/array.js rename to packages/tko.utils/src/array.ts diff --git a/packages/tko.utils/src/async.js b/packages/tko.utils/src/async.ts similarity index 100% rename from packages/tko.utils/src/async.js rename to packages/tko.utils/src/async.ts diff --git a/packages/tko.utils/src/bind-shim.js b/packages/tko.utils/src/bind-shim.ts similarity index 100% rename from packages/tko.utils/src/bind-shim.js rename to packages/tko.utils/src/bind-shim.ts diff --git a/packages/tko.utils/src/css.js b/packages/tko.utils/src/css.ts similarity index 100% rename from packages/tko.utils/src/css.js rename to packages/tko.utils/src/css.ts diff --git a/packages/tko.utils/src/dom/data.js b/packages/tko.utils/src/dom/data.ts similarity index 100% rename from packages/tko.utils/src/dom/data.js rename to packages/tko.utils/src/dom/data.ts diff --git a/packages/tko.utils/src/dom/disposal.js b/packages/tko.utils/src/dom/disposal.ts similarity index 100% rename from packages/tko.utils/src/dom/disposal.js rename to packages/tko.utils/src/dom/disposal.ts diff --git a/packages/tko.utils/src/dom/event.js b/packages/tko.utils/src/dom/event.ts similarity index 100% rename from packages/tko.utils/src/dom/event.js rename to packages/tko.utils/src/dom/event.ts diff --git a/packages/tko.utils/src/dom/fixes.js b/packages/tko.utils/src/dom/fixes.ts similarity index 100% rename from packages/tko.utils/src/dom/fixes.js rename to packages/tko.utils/src/dom/fixes.ts diff --git a/packages/tko.utils/src/dom/html.js b/packages/tko.utils/src/dom/html.ts similarity index 100% rename from packages/tko.utils/src/dom/html.js rename to packages/tko.utils/src/dom/html.ts diff --git a/packages/tko.utils/src/dom/info.js b/packages/tko.utils/src/dom/info.ts similarity index 100% rename from packages/tko.utils/src/dom/info.js rename to packages/tko.utils/src/dom/info.ts diff --git a/packages/tko.utils/src/dom/manipulation.js b/packages/tko.utils/src/dom/manipulation.ts similarity index 100% rename from packages/tko.utils/src/dom/manipulation.js rename to packages/tko.utils/src/dom/manipulation.ts diff --git a/packages/tko.utils/src/dom/selectExtensions.js b/packages/tko.utils/src/dom/selectExtensions.ts similarity index 100% rename from packages/tko.utils/src/dom/selectExtensions.js rename to packages/tko.utils/src/dom/selectExtensions.ts diff --git a/packages/tko.utils/src/dom/virtualElements.js b/packages/tko.utils/src/dom/virtualElements.ts similarity index 100% rename from packages/tko.utils/src/dom/virtualElements.js rename to packages/tko.utils/src/dom/virtualElements.ts diff --git a/packages/tko.utils/src/error.js b/packages/tko.utils/src/error.ts similarity index 100% rename from packages/tko.utils/src/error.js rename to packages/tko.utils/src/error.ts diff --git a/packages/tko.utils/src/function.js b/packages/tko.utils/src/function.ts similarity index 100% rename from packages/tko.utils/src/function.js rename to packages/tko.utils/src/function.ts diff --git a/packages/tko.utils/src/ie.js b/packages/tko.utils/src/ie.ts similarity index 100% rename from packages/tko.utils/src/ie.js rename to packages/tko.utils/src/ie.ts diff --git a/packages/tko.utils/src/index.js b/packages/tko.utils/src/index.ts similarity index 100% rename from packages/tko.utils/src/index.js rename to packages/tko.utils/src/index.ts diff --git a/packages/tko.utils/src/jquery.js b/packages/tko.utils/src/jquery.ts similarity index 100% rename from packages/tko.utils/src/jquery.js rename to packages/tko.utils/src/jquery.ts diff --git a/packages/tko.utils/src/memoization.js b/packages/tko.utils/src/memoization.ts similarity index 100% rename from packages/tko.utils/src/memoization.js rename to packages/tko.utils/src/memoization.ts diff --git a/packages/tko.utils/src/object.js b/packages/tko.utils/src/object.ts similarity index 100% rename from packages/tko.utils/src/object.js rename to packages/tko.utils/src/object.ts diff --git a/packages/tko.utils/src/options.js b/packages/tko.utils/src/options.ts similarity index 100% rename from packages/tko.utils/src/options.js rename to packages/tko.utils/src/options.ts diff --git a/packages/tko.utils/src/proto.js b/packages/tko.utils/src/proto.ts similarity index 100% rename from packages/tko.utils/src/proto.js rename to packages/tko.utils/src/proto.ts diff --git a/packages/tko.utils/src/string.js b/packages/tko.utils/src/string.ts similarity index 100% rename from packages/tko.utils/src/string.js rename to packages/tko.utils/src/string.ts diff --git a/packages/tko.utils/src/symbol.js b/packages/tko.utils/src/symbol.ts similarity index 100% rename from packages/tko.utils/src/symbol.js rename to packages/tko.utils/src/symbol.ts diff --git a/packages/tko.utils/src/tasks.js b/packages/tko.utils/src/tasks.ts similarity index 100% rename from packages/tko.utils/src/tasks.js rename to packages/tko.utils/src/tasks.ts diff --git a/tsconfig.json b/tsconfig.json index ab16a324..e1b7c65d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,10 @@ "compileOnSave": false, "compilerOptions": { "downlevelIteration": true, - "target": "ES3", + "target": "ES5", + "lib": [ "dom", "es5", "es2015.core", "es2015.collection", "es2015.promise", "es2015.symbol", "es2015.symbol.wellknown", "scripthost" ], "module": "ES2015", + "moduleResolution": "node", "allowJs": true, "importHelpers": true, "strict": true diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..406fd033 --- /dev/null +++ b/tslint.json @@ -0,0 +1,46 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "arrow-parens": [true, "ban-single-arg-parens"], + "quotemark": [true, "single","avoid-template", "avoid-escape"], + "object-literal-sort-keys": false, + "ordered-imports": [ + true, + { + "import-sources-order": "any", + "named-imports-order": "any" + } + ], + "member-ordering": false, + "max-classes-per-file": [true, 6], + "max-line-length": [true, 250], + "no-conditional-assignment": false, + "no-empty": false, + "one-variable-per-declaration": false, + "no-console": false, + "no-shadowed-variable": false, + "trailing-comma": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8dc9782f..9d9b7609 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,6 +6,10 @@ version "1.0.0" resolved "https://registry.yarnpkg.com/@comandeer/babel-plugin-banner/-/babel-plugin-banner-1.0.0.tgz#40bcce0bbee084b5b02545a33635d053c248356f" +"@types/jquery@3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.1.tgz#55758d44d422756d6329cbf54e6d41931d7ba28f" + "@types/node@^7.0.18": version "7.0.46" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.46.tgz#c3dedd25558c676b3d6303e51799abb9c3f8f314" @@ -119,6 +123,12 @@ ansi-styles@^3.1.0: dependencies: color-convert "^1.9.0" +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -275,7 +285,7 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.16.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.16.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -771,6 +781,14 @@ chalk@^2.0.0, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +chalk@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + check-error@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -890,6 +908,10 @@ commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" +commander@^2.12.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + commander@^2.9.0: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" @@ -1324,6 +1346,10 @@ diff@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" +diff@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -2204,6 +2230,10 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2636,6 +2666,13 @@ js-yaml@^3.5.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.7.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -3906,6 +3943,12 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.4.0: dependencies: path-parse "^1.0.5" +resolve@^1.3.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" + dependencies: + path-parse "^1.0.5" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -4406,6 +4449,12 @@ supports-color@^4.0.0, supports-color@^4.4.0: dependencies: has-flag "^2.0.0" +supports-color@^5.3.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + table@^3.7.8: version "3.8.3" resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" @@ -4553,10 +4602,37 @@ tryit@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" +tslib@1.9.0, tslib@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" + tslib@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" +tslint@5.9.1: + version "5.9.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" + dependencies: + babel-code-frame "^6.22.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.7.0" + minimatch "^3.0.4" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.12.1" + +tsutils@^2.12.1: + version "2.26.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.26.1.tgz#9e4a0cb9ff173863f34c22a961969081270d1878" + dependencies: + tslib "^1.8.1" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" From 5154c99cec9b18a069515b541f775e52cb6da34e Mon Sep 17 00:00:00 2001 From: Sebazzz Date: Thu, 26 Apr 2018 20:07:24 +0200 Subject: [PATCH 2/2] Initial Typescriptification of tko.utils - pending compilation issues in rollup --- packages/tko.utils/src/array.ts | 194 ++++++++------- packages/tko.utils/src/async.ts | 32 +-- packages/tko.utils/src/css.ts | 39 +-- packages/tko.utils/src/dom/data.ts | 84 +++---- packages/tko.utils/src/dom/disposal.ts | 120 ++++++---- packages/tko.utils/src/dom/event.ts | 108 +++++---- packages/tko.utils/src/dom/fixes.ts | 45 ++-- packages/tko.utils/src/dom/html.ts | 155 ++++++------ packages/tko.utils/src/dom/info.ts | 57 +++-- packages/tko.utils/src/dom/manipulation.ts | 88 +++---- .../tko.utils/src/dom/selectExtensions.ts | 62 ++--- packages/tko.utils/src/dom/virtualElements.ts | 226 ++++++++++-------- packages/tko.utils/src/error.ts | 22 +- packages/tko.utils/src/function.ts | 15 +- packages/tko.utils/src/ie.ts | 29 +-- packages/tko.utils/src/index.ts | 48 ++-- packages/tko.utils/src/jquery.ts | 8 +- packages/tko.utils/src/memoization.ts | 79 +++--- packages/tko.utils/src/object.ts | 53 ++-- packages/tko.utils/src/options.ts | 78 ++++-- packages/tko.utils/src/proto.ts | 16 +- packages/tko.utils/src/string.ts | 38 +-- packages/tko.utils/src/symbol.ts | 6 +- packages/tko.utils/src/tasks.ts | 107 +++++---- 24 files changed, 932 insertions(+), 777 deletions(-) diff --git a/packages/tko.utils/src/array.ts b/packages/tko.utils/src/array.ts index 567a0542..c975d1c0 100644 --- a/packages/tko.utils/src/array.ts +++ b/packages/tko.utils/src/array.ts @@ -4,165 +4,193 @@ // Note that the array functions may be called with // Array-like things, such as NodeList. -const {isArray} = Array +const {isArray} = Array; -export function arrayForEach (array, action, thisArg) { - if (arguments.length > 2) { action = action.bind(thisArg) } +// export function arrayForEach(array: T[], action: (this: TThis, item: T, index: number, array: T[]) => void, thisArg: TThis): void; +export function arrayForEach(array: T[], action: (item: T, index: number, array: T[]) => void, thisArg?: any) { + if (thisArg) { action = action.bind(thisArg); } for (let i = 0, j = array.length; i < j; ++i) { - action(array[i], i, array) + action(array[i], i, array); } } -export function arrayIndexOf (array, item) { - return (isArray(array) ? array : [...array]).indexOf(item) +export function arrayIndexOf(array: T[], item: T) { + return (isArray(array) ? array : [...array]).indexOf(item); } -export function arrayFirst (array, predicate, predicateOwner) { - return (isArray(array) ? array : [...array]) - .find(predicate, predicateOwner) || undefined +// export function arrayFirst(array: T[], predicate: (this: TThis, value: T, index: number, obj: T[]) => boolean, predicateOwner: TThis): void; +export function arrayFirst(array: T[], predicate: (value: T, index: number, obj: T[]) => boolean, predicateOwner?: any) { + const arr: T[] = (isArray(array) ? array : [...array]); + return arr.find(predicate, predicateOwner) || undefined; } -export function arrayMap (array = [], mapping, thisArg) { - if (arguments.length > 2) { mapping = mapping.bind(thisArg) } - return Array.from(array, mapping) +export function arrayMap(array: T[], mapping: (this: TTHis, v: T, k: number) => U, thisArg: TTHis): U[]; +export function arrayMap(array: T[] = [], mapping?: (v: T, k: number) => U, thisArg?: any) { + return thisArg && mapping ? Array.from(array, mapping.bind(thisArg)) : Array.from(array); } -export function arrayRemoveItem (array, itemToRemove) { - var index = arrayIndexOf(array, itemToRemove) +export function arrayRemoveItem(array: T[], itemToRemove: T) { + const index = arrayIndexOf(array, itemToRemove); if (index > 0) { - array.splice(index, 1) + array.splice(index, 1); } else if (index === 0) { - array.shift() + array.shift(); } } -export function arrayGetDistinctValues (array = []) { - const seen = new Set() +export function arrayGetDistinctValues(array: T[] = []) { + const seen = new Set(); return (isArray(array) ? array : [...array]) - .filter(item => seen.has(item) ? false : seen.add(item)) + .filter(item => seen.has(item) ? false : seen.add(item)); } -export function arrayFilter (array, predicate, thisArg) { - if (arguments.length > 2) { predicate = predicate.bind(thisArg) } - return (isArray(array) ? array : [...array]).filter(predicate) +export function arrayFilter(array: T[], predicate: (this: TThis, value: T, index: number, array: T[]) => any, thisArg: TThis): T[]; +export function arrayFilter(array: T[], predicate: (value: T, index: number, array: T[]) => any, thisArg?: any) { + if (thisArg) { predicate = predicate.bind(thisArg); } + return (isArray(array) ? array : [...array]).filter(predicate); } -export function arrayPushAll (array, valuesToPush) { +export function arrayPushAll(array: T[], valuesToPush: ArrayLike) { if (isArray(valuesToPush)) { - array.push.apply(array, valuesToPush) + array.push.apply(array, valuesToPush); } else { - for (var i = 0, j = valuesToPush.length; i < j; i++) { array.push(valuesToPush[i]) } + for (let i = 0, j = valuesToPush.length; i < j; i++) { + array.push(valuesToPush[i]); + } } - return array + + return array; } -export function addOrRemoveItem (array, value, included) { - var existingEntryIndex = arrayIndexOf(typeof array.peek === 'function' ? array.peek() : array, value) +export function addOrRemoveItem(array: T[], value: T, included?: boolean) { + const existingEntryIndex = arrayIndexOf('peek' in array && typeof (array as any).peek === 'function' ? (array as any).peek() : array, value); if (existingEntryIndex < 0) { - if (included) { array.push(value) } + if (included) { array.push(value); } } else { - if (!included) { array.splice(existingEntryIndex, 1) } + if (!included) { array.splice(existingEntryIndex, 1); } } } -export function makeArray (arrayLikeObject) { - return Array.from(arrayLikeObject) +export function makeArray(arrayLikeObject: ArrayLike) { + return Array.from(arrayLikeObject); } -export function range (min, max) { - min = typeof min === 'function' ? min() : min - max = typeof max === 'function' ? max() : max - var result = [] - for (var i = min; i <= max; i++) { result.push(i) } - return result +export type RangeFunction = () => number; +export function range(min: number|RangeFunction, max: number|RangeFunction) { + min = typeof min === 'function' ? min() : min; + max = typeof max === 'function' ? max() : max; + const result = []; + + for (let i = min; i <= max; i++) { result.push(i); } + + return result; } // Go through the items that have been added and deleted and try to find matches between them. -export function findMovesInArrayComparison (left, right, limitFailedCompares) { +export function findMovesInArrayComparison(left: any[], right: any[], limitFailedCompares: number|boolean) { if (left.length && right.length) { - var failedCompares, l, r, leftItem, rightItem + let failedCompares, l, r, leftItem, rightItem; + + // tslint:disable-next-line:no-conditional-assignment for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) { + // tslint:disable-next-line:no-conditional-assignment for (r = 0; rightItem = right[r]; ++r) { - if (leftItem['value'] === rightItem['value']) { - leftItem['moved'] = rightItem['index'] - rightItem['moved'] = leftItem['index'] - right.splice(r, 1) // This item is marked as moved; so remove it from right list - failedCompares = r = 0 // Reset failed compares count because we're checking for consecutive failures - break + if (leftItem.value === rightItem.value) { + leftItem.moved = rightItem.index; + rightItem.moved = leftItem.index; + right.splice(r, 1); // This item is marked as moved; so remove it from right list + failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures + break; } } - failedCompares += r + failedCompares += r; } } } -var statusNotInOld = 'added', statusNotInNew = 'deleted' +const statusNotInOld = 'added', statusNotInNew = 'deleted'; + +export interface ICompareArrayOptions { + dontLimitMoves?: boolean; + sparse?: boolean; +} // Simple calculation based on Levenshtein distance. -export function compareArrays (oldArray, newArray, options) { +export function compareArrays(oldArray: T[], newArray: T[], options ?: boolean|ICompareArrayOptions) { // For backward compatibility, if the third arg is actually a bool, interpret // it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }. - options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {}) - oldArray = oldArray || [] - newArray = newArray || [] + options = (typeof options === 'boolean') ? { dontLimitMoves: options } : (options || {}); + oldArray = oldArray || []; + newArray = newArray || []; - if (oldArray.length < newArray.length) { return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options) } else { return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options) } + if (oldArray.length < newArray.length) { + return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options); + } else { + return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options); + } } -function compareSmallArrayToBigArray (smlArray, bigArray, statusNotInSml, statusNotInBig, options) { - var myMin = Math.min, +function compareSmallArrayToBigArray(smlArray: T[], bigArray: T[], statusNotInSml: any, statusNotInBig: any, options: ICompareArrayOptions) { + // tslint:disable:prefer-const + let myMin = Math.min, myMax = Math.max, editDistanceMatrix = [], smlIndex, smlIndexMax = smlArray.length, bigIndex, bigIndexMax = bigArray.length, compareRange = (bigIndexMax - smlIndexMax) || 1, maxDistance = smlIndexMax + bigIndexMax + 1, - thisRow, lastRow, - bigIndexMaxForRow, bigIndexMinForRow + thisRow, lastRow: any, + bigIndexMaxForRow, bigIndexMinForRow; for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) { - lastRow = thisRow - editDistanceMatrix.push(thisRow = []) - bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange) - bigIndexMinForRow = myMax(0, smlIndex - 1) + lastRow = thisRow; + editDistanceMatrix.push(thisRow = []); + bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange); + bigIndexMinForRow = myMax(0, smlIndex - 1); for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) { - if (!bigIndex) { thisRow[bigIndex] = smlIndex + 1 } else if (!smlIndex) // Top row - transform empty array into new array via additions - { thisRow[bigIndex] = bigIndex + 1 } else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1]) { thisRow[bigIndex] = lastRow[bigIndex - 1] } // copy value (no edit) - else { - var northDistance = lastRow[bigIndex] || maxDistance // not in big (deletion) - var westDistance = thisRow[bigIndex - 1] || maxDistance // not in small (addition) - thisRow[bigIndex] = myMin(northDistance, westDistance) + 1 - } + if (!bigIndex) { + thisRow[bigIndex] = smlIndex + 1; + } else if (!smlIndex) { + // Top row - transform empty array into new array via additions + thisRow[bigIndex] = bigIndex + 1; + } else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1]) { + // copy value (no edit) + thisRow[bigIndex] = lastRow && lastRow[bigIndex - 1]; + } else { + const northDistance = lastRow && lastRow[bigIndex] || maxDistance; // not in big (deletion) + const westDistance: any = thisRow[bigIndex - 1] || maxDistance; // not in small (addition) + thisRow[bigIndex] = myMin(northDistance, westDistance) + 1; + } } } - var editScript = [], meMinusOne, notInSml = [], notInBig = [] + let editScript = [], meMinusOne, notInSml = [], notInBig = []; for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) { - meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1 + meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1; if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex - 1]) { notInSml.push(editScript[editScript.length] = { // added - 'status': statusNotInSml, - 'value': bigArray[--bigIndex], - 'index': bigIndex }) + status: statusNotInSml, + value: bigArray[--bigIndex], + index: bigIndex }); } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) { notInBig.push(editScript[editScript.length] = { // deleted - 'status': statusNotInBig, - 'value': smlArray[--smlIndex], - 'index': smlIndex }) + status: statusNotInBig, + value: smlArray[--smlIndex], + index: smlIndex }); } else { - --bigIndex - --smlIndex - if (!options['sparse']) { + --bigIndex; + --smlIndex; + if (!options.sparse) { editScript.push({ - 'status': 'retained', - 'value': bigArray[bigIndex] }) + status: 'retained', + value: bigArray[bigIndex] }); } } } // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of // smlIndexMax keeps the time complexity of this algorithm linear. - findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10) + findMovesInArrayComparison(notInBig, notInSml, !options.dontLimitMoves && smlIndexMax * 10); - return editScript.reverse() + return editScript.reverse(); } diff --git a/packages/tko.utils/src/async.ts b/packages/tko.utils/src/async.ts index d700aef6..629c2df2 100644 --- a/packages/tko.utils/src/async.ts +++ b/packages/tko.utils/src/async.ts @@ -1,24 +1,26 @@ // // Asynchronous functionality // --- -import { safeSetTimeout } from './error.js' +import { safeSetTimeout } from './error'; -export function throttle (callback, timeout) { - var timeoutInstance - return function (...args) { +// tslint:disable-next-line:ban-types +export function throttle(callback: Function, timeout: number) { + let timeoutInstance: number|undefined; + return (...args: any[]) => { if (!timeoutInstance) { - timeoutInstance = safeSetTimeout(function () { - timeoutInstance = undefined - callback(...args) - }, timeout) + timeoutInstance = safeSetTimeout(() => { + timeoutInstance = undefined; + callback(...args); + }, timeout); } - } + }; } -export function debounce (callback, timeout) { - var timeoutInstance - return function (...args) { - clearTimeout(timeoutInstance) - timeoutInstance = safeSetTimeout(() => callback(...args), timeout) - } +// tslint:disable-next-line:ban-types +export function debounce(callback: Function, timeout: number) { + let timeoutInstance: number|undefined; + return (...args: any[]) => { + clearTimeout(timeoutInstance); + timeoutInstance = safeSetTimeout(() => callback(...args), timeout); + }; } diff --git a/packages/tko.utils/src/css.ts b/packages/tko.utils/src/css.ts index e1e2aefe..4a4fa778 100644 --- a/packages/tko.utils/src/css.ts +++ b/packages/tko.utils/src/css.ts @@ -2,36 +2,37 @@ // DOM - CSS // -import { arrayForEach, addOrRemoveItem } from './array.js' +import { arrayForEach, addOrRemoveItem } from './array'; // For details on the pattern for changing node classes // see: https://github.com/knockout/knockout/issues/1597 -var cssClassNameRegex = /\S+/g +const cssClassNameRegex = /\S+/g; -function toggleDomNodeCssClass (node, classNames, shouldHaveClass) { - var addOrRemoveFn - if (!classNames) { return } +function toggleDomNodeCssClass(node: HTMLElement, classNames: string, shouldHaveClass?: boolean) { + let addOrRemoveFn: (className: string) => void; + if (!classNames) { return; } if (typeof node.classList === 'object') { - addOrRemoveFn = node.classList[shouldHaveClass ? 'add' : 'remove'] - arrayForEach(classNames.match(cssClassNameRegex), function (className) { - addOrRemoveFn.call(node.classList, className) - }) - } else if (typeof node.className['baseVal'] === 'string') { + addOrRemoveFn = node.classList[shouldHaveClass ? 'add' : 'remove']; + arrayForEach(classNames.match(cssClassNameRegex)!, (className: string) => { + addOrRemoveFn.call(node.classList, className); + }); + } else if (typeof (node.className as any).baseVal === 'string') { // SVG tag .classNames is an SVGAnimatedString instance - toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass) + toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass); } else { // node.className ought to be a string. - toggleObjectClassPropertyString(node, 'className', classNames, shouldHaveClass) + toggleObjectClassPropertyString(node, 'className', classNames, shouldHaveClass); } } -function toggleObjectClassPropertyString (obj, prop, classNames, shouldHaveClass) { +function toggleObjectClassPropertyString(obj: any, prop: string, classNames: string, shouldHaveClass?: boolean) { // obj/prop is either a node/'className' or a SVGAnimatedString/'baseVal'. - var currentClassNames = obj[prop].match(cssClassNameRegex) || [] - arrayForEach(classNames.match(cssClassNameRegex), function (className) { - addOrRemoveItem(currentClassNames, className, shouldHaveClass) - }) - obj[prop] = currentClassNames.join(' ') + const currentClassNames = obj[prop].match(cssClassNameRegex) || []; + arrayForEach(classNames.match(cssClassNameRegex)!, (className: string) => { + addOrRemoveItem(currentClassNames, className, shouldHaveClass); + }); + + obj[prop] = currentClassNames.join(' '); } -export { toggleDomNodeCssClass } +export { toggleDomNodeCssClass }; diff --git a/packages/tko.utils/src/dom/data.ts b/packages/tko.utils/src/dom/data.ts index d7f1215a..7eb022e9 100644 --- a/packages/tko.utils/src/dom/data.ts +++ b/packages/tko.utils/src/dom/data.ts @@ -1,13 +1,13 @@ // // DOM node data // -import { ieVersion } from '../ie' +import { ieVersion } from '../ie'; -const datastoreTime = new Date().getTime() -const dataStoreKeyExpandoPropertyName = `__ko__${datastoreTime}` -const dataStoreSymbol = Symbol('Knockout data') -var dataStore -let uniqueId = 0 +const datastoreTime = new Date().getTime(); +const dataStoreKeyExpandoPropertyName = `__ko__${datastoreTime}`; +const dataStoreSymbol = Symbol('Knockout data'); +const dataStore: any = {}; +let uniqueId = 0; /* * We considered using WeakMap, but it has a problem in IE 11 and Edge that @@ -15,70 +15,74 @@ let uniqueId = 0 * on the node. See https://github.com/knockout/knockout/issues/2141 */ const modern = { - getDataForNode (node, createIfNotFound) { - let dataForNode = node[dataStoreSymbol] + getDataForNode(node: any, createIfNotFound?: boolean): any { + let dataForNode = node[dataStoreSymbol]; if (!dataForNode && createIfNotFound) { - dataForNode = node[dataStoreSymbol] = {} + dataForNode = node[dataStoreSymbol] = {}; } - return dataForNode + return dataForNode; }, - clear (node) { - if (node[dataStoreSymbol]) { - delete node[dataStoreSymbol] - return true + clear(node: Node) { + const internalNode = node as any; + if (internalNode[dataStoreSymbol]) { + delete internalNode[dataStoreSymbol]; + return true; } - return false + return false; } -} +}; /** * Old IE versions have memory issues if you store objects on the node, so we * use a separate data storage and link to it from the node using a string key. */ const IE = { - getDataforNode (node, createIfNotFound) { - let dataStoreKey = node[dataStoreKeyExpandoPropertyName] - const hasExistingDataStore = dataStoreKey && (dataStoreKey !== 'null') && dataStore[dataStoreKey] + getDataForNode(node: any, createIfNotFound?: boolean): any { + let dataStoreKey = node[dataStoreKeyExpandoPropertyName]; + const hasExistingDataStore = dataStoreKey && (dataStoreKey !== 'null') && dataStore[dataStoreKey]; if (!hasExistingDataStore) { if (!createIfNotFound) { - return undefined + return undefined; } - dataStoreKey = node[dataStoreKeyExpandoPropertyName] = 'ko' + uniqueId++ - dataStore[dataStoreKey] = {} + dataStoreKey = node[dataStoreKeyExpandoPropertyName] = 'ko' + uniqueId++; + dataStore[dataStoreKey] = {}; } - return dataStore[dataStoreKey] + return dataStore[dataStoreKey]; }, - clear (node) { - const dataStoreKey = node[dataStoreKeyExpandoPropertyName] + clear(node: Node) { + const internalNode = node as any; + const dataStoreKey = internalNode[dataStoreKeyExpandoPropertyName]; if (dataStoreKey) { - delete dataStore[dataStoreKey] - node[dataStoreKeyExpandoPropertyName] = null - return true // Exposing 'did clean' flag purely so specs can infer whether things have been cleaned up as intended + delete dataStore[dataStoreKey]; + internalNode[dataStoreKeyExpandoPropertyName] = null; + return true; // Exposing 'did clean' flag purely so specs can infer whether things have been cleaned up as intended } - return false + return false; } -} +}; -const {getDataForNode, clear} = ieVersion ? IE : modern +const {getDataForNode, clear} = ieVersion ? IE : modern; /** * Create a unique key-string identifier. */ -export function nextKey () { - return (uniqueId++) + dataStoreKeyExpandoPropertyName +export function nextKey() { + return (uniqueId++) + dataStoreKeyExpandoPropertyName; } -function get (node, key) { - const dataForNode = getDataForNode(node, false) - return dataForNode && dataForNode[key] +function get(node: Node, key: string) { + const dataForNode = getDataForNode(node, false); + return dataForNode && dataForNode[key]; } -function set (node, key, value) { +function set(node: Node, key: string, value?: T) { // Make sure we don't actually create a new domData key if we are actually deleting a value - var dataForNode = getDataForNode(node, value !== undefined /* createIfNotFound */) - dataForNode && (dataForNode[key] = value) + const dataForNode = getDataForNode(node, value !== undefined /* createIfNotFound */); + if (dataForNode) { + dataForNode[key] = value; + } } -export { get, set, clear } +export { get, set, clear }; diff --git a/packages/tko.utils/src/dom/disposal.ts b/packages/tko.utils/src/dom/disposal.ts index 96b80ed7..a65a4b00 100644 --- a/packages/tko.utils/src/dom/disposal.ts +++ b/packages/tko.utils/src/dom/disposal.ts @@ -2,116 +2,132 @@ // DOM node disposal // /* eslint no-cond-assign: 0 */ -import * as domData from './data.js' -import {arrayRemoveItem} from '../array.js' -import {jQueryInstance} from '../jquery.js' +import * as domData from './data'; +import {arrayRemoveItem} from '../array'; +import {jQueryInstance} from '../jquery'; + +const domDataKey = domData.nextKey(); -var domDataKey = domData.nextKey() // Node types: // 1: Element // 8: Comment // 9: Document -var cleanableNodeTypes = { 1: true, 8: true, 9: true } -var cleanableNodeTypesWithDescendants = { 1: true, 9: true } +const cleanableNodeTypes: {[nodeType: number]: boolean|undefined} = { 1: true, 8: true, 9: true }; +const cleanableNodeTypesWithDescendants: {[nodeType: number]: boolean|undefined} = { 1: true, 9: true }; -function getDisposeCallbacksCollection (node, createIfNotFound) { - var allDisposeCallbacks = domData.get(node, domDataKey) +function getDisposeCallbacksCollection(node: Node, createIfNotFound?: boolean) { + let allDisposeCallbacks = domData.get(node, domDataKey); if ((allDisposeCallbacks === undefined) && createIfNotFound) { - allDisposeCallbacks = [] - domData.set(node, domDataKey, allDisposeCallbacks) + allDisposeCallbacks = []; + domData.set(node, domDataKey, allDisposeCallbacks); } - return allDisposeCallbacks + return allDisposeCallbacks; } -function destroyCallbacksCollection (node) { - domData.set(node, domDataKey, undefined) + +function destroyCallbacksCollection(node: Node) { + domData.set(node, domDataKey, undefined); } -function cleanSingleNode (node) { - // Run all the dispose callbacks - var callbacks = getDisposeCallbacksCollection(node, false) +// Expose supplemental node cleaning functions. +export type NodeCleanerCallback = (node: Node) => void; +export const otherNodeCleanerFunctions: NodeCleanerCallback[] = []; + +function cleanSingleNode(node: Node) { + // Run all the dispose callbacks + let callbacks = getDisposeCallbacksCollection(node, false); if (callbacks) { - callbacks = callbacks.slice(0) // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves) - for (let i = 0; i < callbacks.length; i++) { callbacks[i](node) } + callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves) + for (const cb of callbacks) { + callbacks(node); + } } // Erase the DOM data - domData.clear(node) + domData.clear(node); // Perform cleanup needed by external libraries (currently only jQuery, but can be extended) for (let i = 0, j = otherNodeCleanerFunctions.length; i < j; ++i) { - otherNodeCleanerFunctions[i](node) + otherNodeCleanerFunctions[i](node); } // Clear any immediate-child comment nodes, as these wouldn't have been found by // node.getElementsByTagName('*') in cleanNode() (comment nodes aren't elements) - if (cleanableNodeTypesWithDescendants[node.nodeType]) { cleanImmediateCommentTypeChildren(node) } + if (cleanableNodeTypesWithDescendants[node.nodeType]) { cleanImmediateCommentTypeChildren(node); } } -function cleanImmediateCommentTypeChildren (nodeWithChildren) { - const children = nodeWithChildren.childNodes - let cleanedNode +function cleanImmediateCommentTypeChildren(nodeWithChildren: Node) { + const children = nodeWithChildren.childNodes; + let cleanedNode: Node; + + // tslint:disable-next-line:prefer-for-of for (let i = 0; i < children.length; ++i) { if (children[i].nodeType === 8) { - cleanSingleNode(cleanedNode = children[i]) + cleanSingleNode(cleanedNode = children[i]); if (children[i] !== cleanedNode) { - throw Error('ko.cleanNode: An already cleaned node was removed from the document') + throw Error('ko.cleanNode: An already cleaned node was removed from the document'); } } } } // Exports -export function addDisposeCallback (node, callback) { - if (typeof callback !== 'function') { throw new Error('Callback must be a function') } - getDisposeCallbacksCollection(node, true).push(callback) +export type DisposeCallback = () => void; + +export function addDisposeCallback(node: Node, callback: DisposeCallback) { + if (typeof callback !== 'function') { throw new Error('Callback must be a function'); } + getDisposeCallbacksCollection(node, true).push(callback); } -export function removeDisposeCallback (node, callback) { - var callbacksCollection = getDisposeCallbacksCollection(node, false) +export function removeDisposeCallback(node: Node, callback: DisposeCallback) { + const callbacksCollection = getDisposeCallbacksCollection(node, false); if (callbacksCollection) { - arrayRemoveItem(callbacksCollection, callback) - if (callbacksCollection.length === 0) { destroyCallbacksCollection(node) } + arrayRemoveItem(callbacksCollection, callback); + if (callbacksCollection.length === 0) { destroyCallbacksCollection(node); } } } -export function cleanNode (node) { +function isCleanableNodeWithDescendants(node: Node): node is Element { + return cleanableNodeTypesWithDescendants[node.nodeType] === true; +} + +export function cleanNode(node: Node) { // First clean this node, where applicable if (cleanableNodeTypes[node.nodeType]) { - cleanSingleNode(node) + cleanSingleNode(node); // ... then its descendants, where applicable - if (cleanableNodeTypesWithDescendants[node.nodeType]) { - const descendants = node.getElementsByTagName('*') + if (isCleanableNodeWithDescendants(node)) { + const descendants = node.getElementsByTagName('*'); + + // tslint:disable-next-line:prefer-for-of for (let i = 0; i < descendants.length; ++i) { - let cleanedNode = descendants[i] - cleanSingleNode(cleanedNode) + const cleanedNode = descendants[i]; + cleanSingleNode(cleanedNode); if (descendants[i] !== cleanedNode) { - throw Error('ko.cleanNode: An already cleaned node was removed from the document') + throw Error('ko.cleanNode: An already cleaned node was removed from the document'); } } } } - return node + return node; } -export function removeNode (node) { - cleanNode(node) - if (node.parentNode) { node.parentNode.removeChild(node) } +export function removeNode(node: Node) { + cleanNode(node); + if (node.parentNode) { node.parentNode.removeChild(node); } } -// Expose supplemental node cleaning functions. -export var otherNodeCleanerFunctions = [] - // Special support for jQuery here because it's so commonly used. // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData // so notify it to tear down any resources associated with the node & descendants here. -export function cleanjQueryData (node) { - var jQueryCleanNodeFn = jQueryInstance - ? jQueryInstance.cleanData : null +export function cleanjQueryData(node: Node) { + // Note: cleanData is an internal jQuery function + const jQueryCleanNodeFn = jQueryInstance + ? (jQueryInstance as any).cleanData : null; if (jQueryCleanNodeFn) { - jQueryCleanNodeFn([node]) + jQueryCleanNodeFn([node]); } } -otherNodeCleanerFunctions.push(cleanjQueryData) +otherNodeCleanerFunctions.push(cleanjQueryData); diff --git a/packages/tko.utils/src/dom/event.ts b/packages/tko.utils/src/dom/event.ts index ccc08444..0d660951 100644 --- a/packages/tko.utils/src/dom/event.ts +++ b/packages/tko.utils/src/dom/event.ts @@ -2,94 +2,100 @@ // DOM Events // -import { objectForEach } from '../object.js' -import { jQueryInstance } from '../jquery.js' -import { ieVersion } from '../ie.js' -import { catchFunctionErrors } from '../error.js' +import { objectForEach } from '../object'; +import { jQueryInstance } from '../jquery'; +import { ieVersion } from '../ie'; +import { catchFunctionErrors } from '../error'; -import { tagNameLower } from './info.js' -import { addDisposeCallback } from './disposal.js' -import options from '../options.js' +import { tagNameLower } from './info'; +import { addDisposeCallback } from './disposal'; +import options from '../options'; // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup) -var knownEvents = {}, - knownEventTypesByEventName = {} +const knownEvents: {[evType: string]: string[]} = {}, + knownEventTypesByEventName: {[evType: string]: string} = {}; -var keyEventTypeName = (options.global.navigator && /Firefox\/2/i.test(options.global.navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents' +const keyEventTypeName = (options.global.navigator && /Firefox\/2/i.test(options.global.navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents'; -knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'] +knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress']; -knownEvents['MouseEvents'] = [ +knownEvents.MouseEvents = [ 'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', - 'mouseout', 'mouseenter', 'mouseleave'] + 'mouseout', 'mouseenter', 'mouseleave']; -objectForEach(knownEvents, function (eventType, knownEventsForType) { +objectForEach(knownEvents, (eventType, knownEventsForType) => { if (knownEventsForType.length) { - for (var i = 0, j = knownEventsForType.length; i < j; i++) { knownEventTypesByEventName[knownEventsForType[i]] = eventType } + for (let i = 0, j = knownEventsForType.length; i < j; i++) { knownEventTypesByEventName[knownEventsForType[i]] = eventType; } } -}) +}); -function isClickOnCheckableElement (element, eventType) { - if ((tagNameLower(element) !== 'input') || !element.type) return false - if (eventType.toLowerCase() != 'click') return false - var inputType = element.type - return (inputType == 'checkbox') || (inputType == 'radio') +function isClickOnCheckableElement(element: HTMLElement, eventType: string) { + if ((tagNameLower(element) !== 'input') || !(element as HTMLInputElement).type) { + return false; + } + + if (eventType.toLowerCase() !== 'click') { + return false; + } + + const inputType = (element as HTMLInputElement).type; + return (inputType === 'checkbox') || (inputType === 'radio'); } // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406 -var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true } -let jQueryEventAttachName +const eventsThatMustBeRegisteredUsingAttachEvent: {[type: string]: boolean} = { propertychange: true }; +let jQueryEventAttachName: any; -export function registerEventHandler (element, eventType, handler, eventOptions = false) { - const wrappedHandler = catchFunctionErrors(handler) - const mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType] - const mustUseNative = Boolean(eventOptions) +export function registerEventHandler(element: HTMLElement, eventType: string, handler: EventListener, eventOptions = false) { + const wrappedHandler = catchFunctionErrors(handler); + const mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType]; + const mustUseNative = Boolean(eventOptions); if (!options.useOnlyNativeEvents && !mustUseAttachEvent && !mustUseNative && jQueryInstance) { if (!jQueryEventAttachName) { - jQueryEventAttachName = (typeof jQueryInstance(element).on === 'function') ? 'on' : 'bind' + jQueryEventAttachName = (typeof jQueryInstance(element).on === 'function') ? 'on' : 'bind'; } - jQueryInstance(element)[jQueryEventAttachName](eventType, wrappedHandler) + (jQueryInstance(element) as any)[jQueryEventAttachName](eventType, wrappedHandler); } else if (!mustUseAttachEvent && typeof element.addEventListener === 'function') { - element.addEventListener(eventType, wrappedHandler, eventOptions) - } else if (typeof element.attachEvent !== 'undefined') { - const attachEventHandler = function (event) { wrappedHandler.call(element, event) } - const attachEventName = 'on' + eventType - element.attachEvent(attachEventName, attachEventHandler) + element.addEventListener(eventType, wrappedHandler, eventOptions); + } else if (typeof (element as any).attachEvent !== 'undefined') { + const attachEventHandler = (event: any) => wrappedHandler.call(element, event); + const attachEventName = 'on' + eventType; + (element as any).attachEvent(attachEventName, attachEventHandler); // IE does not dispose attachEvent handlers automatically (unlike with addEventListener) // so to avoid leaks, we have to remove them manually. See bug #856 - addDisposeCallback(element, function () { - element.detachEvent(attachEventName, attachEventHandler) - }) + addDisposeCallback(element, () => { + (element as any).detachEvent(attachEventName, attachEventHandler); + }); } else { - throw new Error("Browser doesn't support addEventListener or attachEvent") + throw new Error("Browser doesn't support addEventListener or attachEvent"); } } -export function triggerEvent (element, eventType) { - if (!(element && element.nodeType)) { throw new Error('element must be a DOM node when calling triggerEvent') } +export function triggerEvent(element: HTMLElement, eventType: string) { + if (!(element && element.nodeType)) { throw new Error('element must be a DOM node when calling triggerEvent'); } // For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the // event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.) // IE doesn't change the checked state when you trigger the click event using "fireEvent". // In both cases, we'll use the click method instead. - var useClickWorkaround = isClickOnCheckableElement(element, eventType) + const useClickWorkaround = isClickOnCheckableElement(element, eventType); if (!options.useOnlyNativeEvents && jQueryInstance && !useClickWorkaround) { - jQueryInstance(element).trigger(eventType) + jQueryInstance(element).trigger(eventType); } else if (typeof document.createEvent === 'function') { if (typeof element.dispatchEvent === 'function') { - var eventCategory = knownEventTypesByEventName[eventType] || 'HTMLEvents' - var event = document.createEvent(eventCategory) - event.initEvent(eventType, true, true, options.global, 0, 0, 0, 0, 0, false, false, false, false, 0, element) - element.dispatchEvent(event) - } else { throw new Error("The supplied element doesn't support dispatchEvent") } + const eventCategory = knownEventTypesByEventName[eventType] || 'HTMLEvents'; + const event = document.createEvent(eventCategory); + (event as any).initEvent(eventType, true, true, options.global, 0, 0, 0, 0, 0, false, false, false, false, 0, element); + element.dispatchEvent(event); + } else { throw new Error("The supplied element doesn't support dispatchEvent"); } } else if (useClickWorkaround && element.click) { - element.click() - } else if (typeof element.fireEvent !== 'undefined') { - element.fireEvent('on' + eventType) + element.click(); + } else if (typeof (element as any).fireEvent !== 'undefined') { + (element as any).fireEvent('on' + eventType); } else { - throw new Error("Browser doesn't support triggering events") + throw new Error("Browser doesn't support triggering events"); } } diff --git a/packages/tko.utils/src/dom/fixes.ts b/packages/tko.utils/src/dom/fixes.ts index e99b7903..0db13c7e 100644 --- a/packages/tko.utils/src/dom/fixes.ts +++ b/packages/tko.utils/src/dom/fixes.ts @@ -1,9 +1,9 @@ // // DOM node manipulation // -import { ieVersion } from '../ie.js' +import { ieVersion } from '../ie'; -export function fixUpContinuousNodeArray (continuousNodeArray, parentNode) { +export function fixUpContinuousNodeArray(continuousNodeArray: Node[], parentNode: Node) { // Before acting on a set of nodes that were previously outputted by a template function, we have to reconcile // them against what is in the DOM right now. It may be that some of the nodes have already been removed, or that // new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been @@ -22,50 +22,51 @@ export function fixUpContinuousNodeArray (continuousNodeArray, parentNode) { if (continuousNodeArray.length) { // The parent node can be a virtual element; so get the real parent node - parentNode = (parentNode.nodeType === 8 && parentNode.parentNode) || parentNode + parentNode = (parentNode.nodeType === 8 && parentNode.parentNode) || parentNode; // Rule [A] - while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode) { continuousNodeArray.splice(0, 1) } + while (continuousNodeArray.length && continuousNodeArray[0].parentNode !== parentNode) { continuousNodeArray.splice(0, 1); } // Rule [B] - while (continuousNodeArray.length > 1 && continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode) { continuousNodeArray.length-- } + while (continuousNodeArray.length > 1 && continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode) { continuousNodeArray.length--; } // Rule [C] if (continuousNodeArray.length > 1) { - var current = continuousNodeArray[0], last = continuousNodeArray[continuousNodeArray.length - 1] + let current: Node|null = continuousNodeArray[0]; + const last = continuousNodeArray[continuousNodeArray.length - 1]; // Replace with the actual new continuous node set - continuousNodeArray.length = 0 - while (current !== last) { - continuousNodeArray.push(current) - current = current.nextSibling + continuousNodeArray.length = 0; + while (current !== last && current) { + continuousNodeArray.push(current); + current = current.nextSibling; } - continuousNodeArray.push(last) + continuousNodeArray.push(last); } } - return continuousNodeArray + return continuousNodeArray; } -export function setOptionNodeSelectionState (optionNode, isSelected) { +export function setOptionNodeSelectionState(optionNode: HTMLOptionElement, isSelected: boolean) { // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser. - if (ieVersion < 7) { optionNode.setAttribute('selected', isSelected) } else { optionNode.selected = isSelected } + if (ieVersion && ieVersion < 7) { optionNode.setAttribute('selected', isSelected.toString()); } else { optionNode.selected = isSelected; } } -export function forceRefresh (node) { +export function forceRefresh(node: Node) { // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209 - if (ieVersion >= 9) { + if (ieVersion && ieVersion >= 9) { // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container - var elem = node.nodeType == 1 ? node : node.parentNode - if (elem.style) { elem.style.zoom = elem.style.zoom } + const elem = (node.nodeType === 1 ? node : node.parentNode) as HTMLElement; + if (elem && elem.style) { elem.style.zoom = elem.style.zoom; } } } -export function ensureSelectElementIsRenderedCorrectly (selectElement) { +export function ensureSelectElementIsRenderedCorrectly(selectElement: HTMLSelectElement) { // Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width. // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option) // Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839) if (ieVersion) { - var originalWidth = selectElement.style.width - selectElement.style.width = 0 - selectElement.style.width = originalWidth + const originalWidth = selectElement.style.width; + selectElement.style.width = '0'; + selectElement.style.width = originalWidth; } } diff --git a/packages/tko.utils/src/dom/html.ts b/packages/tko.utils/src/dom/html.ts index 09bade62..2bef2aa9 100644 --- a/packages/tko.utils/src/dom/html.ts +++ b/packages/tko.utils/src/dom/html.ts @@ -1,50 +1,54 @@ // // HTML-based manipulation // -import { stringTrim } from '../string.js' -import { makeArray } from '../array.js' -import { emptyDomNode, moveCleanedNodesToContainerElement } from './manipulation.js' -import { jQueryInstance } from '../jquery.js' -import * as virtualElements from './virtualElements' -import options from '../options' - -var none = [0, '', ''], - table = [1, '', '
'], - tbody = [2, '', '
'], - colgroup = [ 2, '', '
'], - tr = [3, '', '
'], - select = [1, "'], - fieldset = [1, '
', '
'], - map = [1, '', ''], - object = [1, '', ''], - lookup = { - 'area': map, - 'col': colgroup, - 'colgroup': table, - 'caption': table, - 'legend': fieldset, - 'thead': table, - 'tbody': table, - 'tfoot': table, - 'tr': tbody, - 'td': tr, - 'th': tr, - 'option': select, - 'optgroup': select, - 'param': object +import { stringTrim } from '../string'; +import { makeArray } from '../array'; +import { emptyDomNode, moveCleanedNodesToContainerElement } from './manipulation'; +import { jQueryInstance } from '../jquery'; +import * as virtualElements from './virtualElements'; +import options from '../options'; + +type TagMap = [number, string, string]; +const none: TagMap = [0, '', ''], + table: TagMap = [1, '', '
'], + tbody: TagMap = [2, '', '
'], + colgroup: TagMap = [ 2, '', '
'], + tr: TagMap = [3, '', '
'], + select: TagMap = [1, "'], + fieldset: TagMap = [1, '
', '
'], + map: TagMap = [1, '', ''], + object: TagMap = [1, '', ''], + lookup: {[tag: string]: TagMap} = { + area: map, + col: colgroup, + colgroup: table, + caption: table, + legend: fieldset, + thead: table, + tbody: table, + tfoot: table, + tr: tbody, + td: tr, + th: tr, + option: select, + optgroup: select, + param: object }, // The canonical way to test that the HTML5