diff --git a/examples/vanilla/index.html b/examples/vanilla/index.html new file mode 100644 index 0000000..bf7c922 --- /dev/null +++ b/examples/vanilla/index.html @@ -0,0 +1,16 @@ + + + + + + + FAQ + + + Back +
+ +
+ + + diff --git a/examples/vanilla/index.js b/examples/vanilla/index.js new file mode 100644 index 0000000..697fa99 --- /dev/null +++ b/examples/vanilla/index.js @@ -0,0 +1,3 @@ +import { select, selectAll } from "../../dist/mjs/index.js"; + +console.log(select, selectAll); diff --git a/index.html b/index.html new file mode 100644 index 0000000..c2bde63 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + Vite App + + + + + diff --git a/package-lock.json b/package-lock.json index 3cd5d2a..9387120 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@mluaka/simple-npm-package", - "version": "2.0.3", + "name": "@mluaka/multiselect", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@mluaka/simple-npm-package", - "version": "2.0.3", + "name": "@mluaka/multiselect", + "version": "0.0.0", "license": "ISC", "devDependencies": { "@types/chai": "^4.3.6", @@ -16,7 +16,8 @@ "semantic-release": "^22.0.5", "ts-jest": "^29.1.4", "ts-node": "^10.9.1", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "vite": "^2.9.9" } }, "node_modules/@ampproject/remapping": { @@ -658,6 +659,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2618,6 +2635,362 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/esbuild": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", + "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", + "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -4562,6 +4935,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8387,6 +8778,34 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -8802,6 +9221,21 @@ "node": ">=0.10.0" } }, + "node_modules/rollup": { + "version": "2.77.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz", + "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9090,6 +9524,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -9686,6 +10129,43 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vite": { + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz", + "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.14.27", + "postcss": "^8.4.13", + "resolve": "^1.22.0", + "rollup": ">=2.59.0 <2.78.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": ">=12.2.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "less": "*", + "sass": "*", + "stylus": "*" + }, + "peerDependenciesMeta": { + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + } + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index 1f1825c..a6a0201 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "build": "rm -fr dist/* && tsc -p ./configs/tsconfig.json && tsc -p ./configs/tsconfig.cjs.json && bash ./fixup", "test": "jest", "prepack": "npm run build", - "semantic-release": "semantic-release" + "semantic-release": "semantic-release", + "dev": "vite" }, "files": [ "dist/" @@ -32,6 +33,7 @@ "semantic-release": "^22.0.5", "ts-jest": "^29.1.4", "ts-node": "^10.9.1", + "vite": "^2.9.9", "typescript": "^5.2.2" }, "release": { diff --git a/src/index.spec.ts b/src/index.spec.ts index 81b8b78..cba2197 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -1,37 +1,223 @@ -import { helloWorld, goodBye, howAreYou } from "../src/index"; +import { select, selectAll } from "."; -describe("Hello World Function", () => { - it("should be a function", () => { - expect(typeof helloWorld).toBe("function"); - }); +describe("multiselect", () => { + let indices: number[]; + let selectedIndex: number; + let selectedIndices: number[]; - it("should return the hello world message", () => { - const expected = "Hello World from my example modern npm package!"; - const actual = helloWorld(); - expect(actual).toBe(expected); + beforeEach(() => { + indices = [...Array(7).keys()]; + selectedIndex = 0; + selectedIndices = [0]; }); -}); -describe("Goodbye Function", () => { - it("should be a function", () => { - expect(typeof goodBye).toBe("function"); + describe("select", () => { + it("should select an index and add it to the list of selected indices", () => { + expect(select(selectedIndices, selectedIndex, 2)).toEqual([2, [0, 2]]); + }); + + it("should remove an index from the list of selected indices", () => { + const allIndices = selectAll({ + selectedIds: selectedIndices, + selectedId: selectedIndex, + ids: indices, + id: 3, + }); + + expect(select(allIndices, selectedIndex, 2)).toEqual([3, [0, 1, 3]]); + }); + + it("should remove an index even when it is the only index in the selected indices", () => { + expect(select(selectedIndices, selectedIndex, 0)).toEqual([0, []]); + }); }); - it("should return the goodbye message", () => { - const expected = "Goodbye from my example modern npm package!"; - const actual = goodBye(); - expect(actual).toBe(expected); + describe("selectAll", () => { + beforeEach(() => { + selectedIndices = selectAll({ + selectedIds: selectedIndices, + selectedId: selectedIndex, + ids: indices, + id: 4, + }); + }); + + it("should select multiple indices given a range", () => { + expect(selectedIndices).toEqual([0, 1, 2, 3, 4]); + }); + + it("should select multiple indices starting from the current index after removing an index from the list of selected indices", () => { + const [nextSelectedIndex, nextSelectedIndices] = select( + selectedIndices, + selectedIndex, + 2 + ); + expect(nextSelectedIndices).toEqual([0, 1, 3, 4]); + + selectedIndices = selectAll({ + selectedIds: nextSelectedIndices, + selectedId: nextSelectedIndex, + ids: indices, + id: 5, + }); + expect(selectedIndices).toEqual([0, 1, 3, 4, 5]); + }); + + it("should select multiple indices starting from the next selected index after removing current selected index", () => { + let [nextIndex, nextIndices] = select(selectedIndices, selectedIndex, 4); + [nextIndex, nextIndices] = select(nextIndices, nextIndex, 4); + expect(nextIndices).toEqual([0, 1, 2, 3, 4]); + + selectedIndices = selectAll({ + selectedIds: nextIndices, + selectedId: nextIndex, + ids: indices, + id: 5, + }); + expect(selectedIndices).toEqual([4, 5]); + }); + + it("should select multiple indices starting from the previous selected index after removing current selected index", () => { + // remove a single index + let [nextIndex, nextIndices] = select(selectedIndices, selectedIndex, 2); + expect(nextIndices).toEqual([0, 1, 3, 4]); + + // remove the currently selected index + [nextIndex, nextIndices] = select(nextIndices, nextIndex, 4); + [nextIndex, nextIndices] = select(nextIndices, nextIndex, 4); + expect(nextIndices).toEqual([0, 1, 3, 4]); + + selectedIndices = selectAll({ + selectedIds: nextIndices, + selectedId: nextIndex, + ids: indices, + id: 6, + }); + expect(selectedIndices).toEqual([0, 1, 4, 5, 6]); + }); + + it("should select indices starting from the last selected index", () => { + const [nextSelectedIndex, nextSelectedIndices] = select( + selectedIndices, + selectedIndex, + 2 + ); + expect(nextSelectedIndices).toEqual([0, 1, 3, 4]); + + selectedIndices = selectAll({ + selectedIds: nextSelectedIndices, + selectedId: nextSelectedIndex, + ids: indices, + id: 2, + }); + expect(selectedIndices).toEqual([2, 3]); + }); }); -}); -describe("How Are You Function", () => { - it("should be a function", () => { - expect(typeof howAreYou).toBe("function"); + describe("selectAll (with filtered subset of indices)", () => { + beforeEach(() => { + indices = indices.map((x) => x * 7); + selectedIndices = selectAll({ + selectedIds: selectedIndices, + selectedId: selectedIndex, + ids: indices, + id: 28, + }); + }); + + it("should select multiple indices given a range", () => { + expect(selectedIndices).toEqual([0, 7, 14, 21, 28]); + }); + + it("should select multiple indices starting from the current index after removing an index from the list of selected indices", () => { + const [nextSelectedIndex, nextSelectedIndices] = select( + selectedIndices, + selectedIndex, + 14 + ); + expect(nextSelectedIndices).toEqual([0, 7, 21, 28]); + + selectedIndices = selectAll({ + selectedIds: nextSelectedIndices, + selectedId: nextSelectedIndex, + ids: indices, + id: 35, + }); + expect(selectedIndices).toEqual([0, 7, 21, 28, 35]); + }); + + it("should select multiple indices starting from the next selected index after removing current selected index", () => { + let [nextIndex, nextIndices] = select(selectedIndices, selectedIndex, 28); + [nextIndex, nextIndices] = select(nextIndices, nextIndex, 28); + expect(nextIndices).toEqual([0, 7, 14, 21, 28]); + + selectedIndices = selectAll({ + selectedIds: nextIndices, + selectedId: nextIndex, + ids: indices, + id: 35, + }); + expect(selectedIndices).toEqual([28, 35]); + }); + + it("should select multiple indices starting from the previous selected index after removing current selected index", () => { + // remove a single index + let [nextIndex, nextIndices] = select(selectedIndices, selectedIndex, 14); + expect(nextIndices).toEqual([0, 7, 21, 28]); + + // remove the currently selected index + [nextIndex, nextIndices] = select(nextIndices, nextIndex, 28); + [nextIndex, nextIndices] = select(nextIndices, nextIndex, 28); + expect(nextIndices).toEqual([0, 7, 21, 28]); + + selectedIndices = selectAll({ + selectedIds: nextIndices, + selectedId: nextIndex, + ids: indices, + id: 42, + }); + expect(selectedIndices).toEqual([0, 7, 28, 35, 42]); + }); + + it("should select indices starting from the last selected index", () => { + const [nextSelectedIndex, nextSelectedIndices] = select( + selectedIndices, + selectedIndex, + 14 + ); + expect(nextSelectedIndices).toEqual([0, 7, 21, 28]); + + selectedIndices = selectAll({ + selectedIds: nextSelectedIndices, + selectedId: nextSelectedIndex, + ids: indices, + id: 14, + }); + + expect(selectedIndices).toEqual([14, 21]); + }); }); - it("should return the how are you message", () => { - const expected = "How are you from my example modern npm package!!"; - const actual = howAreYou(); - expect(actual).toBe(expected); + it("should extend with a non-consecutive set", () => { + indices = indices.map((x) => x * 7).reverse(); + selectedIndex = 42; + selectedIndices = [42]; + + selectedIndices = selectAll({ + selectedIds: selectedIndices, + selectedId: selectedIndex, + ids: indices, + id: 28, + }); + let [nextIndex, nextIndices] = select(selectedIndices, selectedIndex, 35); + + selectedIndices = selectAll({ + selectedIds: nextIndices, + selectedId: nextIndex, + ids: indices, + id: 14, + }); + + expect(selectedIndices).toEqual([42, 28, 21, 14]); }); }); diff --git a/src/index.ts b/src/index.ts index c422e5e..06ca838 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,68 @@ -export function helloWorld() { - const message = "Hello World from my example modern npm package!"; - return message; -} +type SelectAllParams = { + selectedIds: T[]; + selectedId: T; + ids: T[]; + id: T; +}; + +export function select(selectedIds: T[], selectedId: T, id: T): [T, T[]] { + if (!selectedIds.includes(id)) return [id, selectedIds.concat(id)]; + + if (selectedIds.length === 1) { + return selectedIds.includes(id) ? [id, []] : [selectedId, selectedIds]; + } -export function goodBye() { - const message = "Goodbye from my example modern npm package!"; - return message; + const pivotIndex = selectedIds.findIndex((x) => x === id); + const nextSelectedIndex = + selectedIds[pivotIndex + 1] ?? selectedIds[pivotIndex - 1] ?? id; + const nextSelectedIndices = selectedIds.filter((x) => x !== id); + + return [nextSelectedIndex, nextSelectedIndices]; } -export function howAreYou() { - const message = "How are you from my example modern npm package!!"; - return message; +export function selectAll({ + selectedIds, + selectedId, + ids, + id, +}: SelectAllParams): T[] { + const reverseIndexLookup = (x: T) => ids.indexOf(x); + const [_index, _selectedIndex] = [id, selectedId].map(reverseIndexLookup); + const _selectedIndices = selectedIds.map(reverseIndexLookup); + + if (selectedIds.length <= 1) { + return _selectAll(ids, _selectedIndex, _index); + } + + let nonConsecutiveIndex = -1; + for (let i = 1, len = _selectedIndices.length; i < len; i++) { + const [a, b] = [_selectedIndices[i - 1], _selectedIndices[i]]; + if (a > b || Math.abs(a - b) > 1) { + nonConsecutiveIndex = i; + } + } + + if (nonConsecutiveIndex === -1) { + return _selectAll(ids, _selectedIndex, _index); + } + + if (reverseIndexLookup(selectedId) > reverseIndexLookup(id)) { + // current selection extends backward to meet target + return ids.slice(_index, _selectedIndex + 1); + } + + // select remaining indices + return selectedIds + .slice(0, nonConsecutiveIndex) + .concat(ids.slice(_selectedIndex, _index + 1)); } -export default { - helloWorld, - goodBye, - howAreYou, -}; +function _selectAll(indices: T[], selectedIndex: number, index: number) { + if (selectedIndex > index) { + // current selection extends backward to meet target + return indices.slice(index, selectedIndex + 1); + } + + // current selection extends forward to meet target + return indices.slice(selectedIndex, index + 1); +}