diff --git a/package-lock.json b/package-lock.json
index d66943ede4..31bf465203 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4606,6 +4606,10 @@
"resolved": "packages/infrastructure/jest",
"link": true
},
+ "node_modules/@k8slens/keyboard-shortcuts": {
+ "resolved": "packages/business-features/keyboard-shortcuts",
+ "link": true
+ },
"node_modules/@k8slens/legacy-extension-example": {
"resolved": "packages/legacy-extension-example",
"link": true
@@ -5042,13 +5046,13 @@
}
},
"node_modules/@lerna/legacy-package-management/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -5069,15 +5073,15 @@
}
},
"node_modules/@lerna/legacy-package-management/node_modules/glob/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -6022,13 +6026,13 @@
}
},
"node_modules/@npmcli/arborist/node_modules/cacache/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -6040,15 +6044,15 @@
}
},
"node_modules/@npmcli/arborist/node_modules/cacache/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -6091,9 +6095,9 @@
}
},
"node_modules/@npmcli/arborist/node_modules/ignore-walk/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz",
+ "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -6612,13 +6616,13 @@
}
},
"node_modules/@npmcli/arborist/node_modules/read-package-json/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -6630,15 +6634,15 @@
}
},
"node_modules/@npmcli/arborist/node_modules/read-package-json/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -6888,13 +6892,13 @@
}
},
"node_modules/@npmcli/map-workspaces/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -6905,10 +6909,25 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/@npmcli/map-workspaces/node_modules/glob/node_modules/minimatch": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/@npmcli/map-workspaces/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz",
+ "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -7040,13 +7059,13 @@
}
},
"node_modules/@npmcli/metavuln-calculator/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -7081,6 +7100,21 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
+ "node_modules/@npmcli/metavuln-calculator/node_modules/ignore-walk/node_modules/minimatch": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz",
+ "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/@npmcli/metavuln-calculator/node_modules/json-parse-even-better-errors": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz",
@@ -7260,15 +7294,15 @@
}
},
"node_modules/@npmcli/metavuln-calculator/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -9386,9 +9420,9 @@
}
},
"node_modules/@tufjs/models/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz",
+ "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -24023,13 +24057,13 @@
}
},
"node_modules/lerna/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -24050,15 +24084,15 @@
}
},
"node_modules/lerna/node_modules/glob/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -31480,9 +31514,9 @@
}
},
"node_modules/promise-call-limit": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz",
- "integrity": "sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.2.tgz",
+ "integrity": "sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -33701,13 +33735,13 @@
}
},
"node_modules/sigstore/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -33754,15 +33788,15 @@
}
},
"node_modules/sigstore/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -35858,13 +35892,13 @@
}
},
"node_modules/tuf-js/node_modules/glob": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz",
- "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==",
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz",
+ "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
- "minimatch": "^7.4.1",
+ "minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
@@ -35911,15 +35945,15 @@
}
},
"node_modules/tuf-js/node_modules/minimatch": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
- "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz",
+ "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -37809,7 +37843,6 @@
"packages/business-features/keyboard-shortcuts": {
"name": "@k8slens/keyboard-shortcuts",
"version": "1.0.0-alpha.0",
- "extraneous": true,
"license": "MIT",
"devDependencies": {
"@async-fn/jest": "^1.6.4",
@@ -42366,6 +42399,7 @@
"@k8slens/core": "^6.5.0-alpha.3",
"@k8slens/ensure-binaries": "^6.5.0-alpha.1",
"@k8slens/feature-core": "^6.5.0-alpha.1",
+ "@k8slens/keyboard-shortcuts": "^1.0.0-alpha.0",
"@k8slens/legacy-extension-example": "^1.0.0-alpha.1",
"@k8slens/legacy-extensions": "^1.0.0-alpha.1",
"@k8slens/messaging": "^1.0.0-alpha.1",
diff --git a/packages/business-features/keyboard-shortcuts/.eslintrc.json b/packages/business-features/keyboard-shortcuts/.eslintrc.json
new file mode 100644
index 0000000000..b15115cb69
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": "@k8slens/eslint-config/eslint",
+ "parserOptions": {
+ "project": "./tsconfig.json"
+ }
+}
diff --git a/packages/business-features/keyboard-shortcuts/.prettierrc b/packages/business-features/keyboard-shortcuts/.prettierrc
new file mode 100644
index 0000000000..edd47b479e
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/.prettierrc
@@ -0,0 +1 @@
+"@k8slens/eslint-config/prettier"
diff --git a/packages/business-features/keyboard-shortcuts/README.md b/packages/business-features/keyboard-shortcuts/README.md
new file mode 100644
index 0000000000..25cc2f8cdb
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/README.md
@@ -0,0 +1,21 @@
+# @k8slens/keyboard-shortcuts
+
+This Feature enables keyboard shortcuts in Lens
+
+# Usage
+
+```bash
+$ npm install @k8slens/keyboard-shortcuts
+```
+
+```typescript
+import { keyboardShortcutsFeature } from "@k8slens/keyboard-shortcuts";
+import { registerFeature } from "@k8slens/feature-core";
+import { createContainer } from "@ogre-tools/injectable";
+
+const di = createContainer("some-container");
+
+registerFeature(di, keyboardShortcutsFeature);
+```
+
+## Extendability
diff --git a/packages/business-features/keyboard-shortcuts/index.ts b/packages/business-features/keyboard-shortcuts/index.ts
new file mode 100644
index 0000000000..7f8ac51c47
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/index.ts
@@ -0,0 +1,7 @@
+export { KeyboardShortcutScope } from "./src/keyboard-shortcut-scope";
+export type { KeyboardShortcutScopeProps } from "./src/keyboard-shortcut-scope";
+
+export { keyboardShortcutInjectionToken } from "./src/keyboard-shortcut-injection-token";
+export type { Binding, KeyboardShortcut } from "./src/keyboard-shortcut-injection-token";
+
+export { keyboardShortcutsFeature } from "./src/feature";
diff --git a/packages/business-features/keyboard-shortcuts/jest.config.js b/packages/business-features/keyboard-shortcuts/jest.config.js
new file mode 100644
index 0000000000..38d54ab7b6
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/jest.config.js
@@ -0,0 +1 @@
+module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
diff --git a/packages/business-features/keyboard-shortcuts/package.json b/packages/business-features/keyboard-shortcuts/package.json
new file mode 100644
index 0000000000..1b60e66133
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@k8slens/keyboard-shortcuts",
+ "private": false,
+ "version": "1.0.0-alpha.0",
+ "description": "Keyboard shortcuts for Lens",
+ "type": "commonjs",
+ "files": [
+ "dist"
+ ],
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/lensapp/lens.git"
+ },
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "author": {
+ "name": "OpenLens Authors",
+ "email": "info@k8slens.dev"
+ },
+ "license": "MIT",
+ "homepage": "https://github.com/lensapp/lens",
+ "scripts": {
+ "build": "webpack",
+ "clean": "rimraf dist/",
+ "test:unit": "jest --coverage --runInBand",
+ "lint": "lens-lint",
+ "lint:fix": "lens-lint --fix"
+ },
+ "peerDependencies": {
+ "@k8slens/feature-core": "^6.5.0-alpha.0",
+ "@k8slens/react-application": "^1.0.0-alpha.0",
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
+ "@ogre-tools/fp": "^15.1.2",
+ "lodash": "^4.17.21"
+ },
+ "devDependencies": {
+ "@async-fn/jest": "^1.6.4",
+ "@k8slens/eslint-config": "6.5.0-alpha.1",
+ "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0"
+ }
+}
diff --git a/packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap b/packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap
new file mode 100644
index 0000000000..b2cba6a543
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`keyboard-shortcuts when application is started renders 1`] = `
+
+
+
+`;
diff --git a/packages/business-features/keyboard-shortcuts/src/feature.ts b/packages/business-features/keyboard-shortcuts/src/feature.ts
new file mode 100644
index 0000000000..32d5f2bc62
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/feature.ts
@@ -0,0 +1,17 @@
+import { getFeature } from "@k8slens/feature-core";
+import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
+import { reactApplicationFeature } from "@k8slens/react-application";
+
+export const keyboardShortcutsFeature = getFeature({
+ id: "keyboard-shortcuts",
+
+ register: (di) => {
+ autoRegister({
+ di,
+ targetModule: module,
+ getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
+ });
+ },
+
+ dependencies: [reactApplicationFeature],
+});
diff --git a/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts b/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts
new file mode 100644
index 0000000000..5b0c324203
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts
@@ -0,0 +1,94 @@
+import { pipeline } from "@ogre-tools/fp";
+import { filter, isString } from "lodash/fp";
+import { getInjectable } from "@ogre-tools/injectable";
+import {
+ Binding,
+ KeyboardShortcut,
+ keyboardShortcutInjectionToken,
+} from "./keyboard-shortcut-injection-token";
+import platformInjectable from "./platform.injectable";
+
+export type InvokeShortcut = (event: KeyboardEvent) => void;
+
+const toShortcutsWithMatchingScope = (shortcut: KeyboardShortcut) => {
+ const activeScopeElement = document.activeElement?.closest("[data-keyboard-shortcut-scope]");
+
+ if (!activeScopeElement) {
+ const shortcutIsRootLevel = !shortcut.scope;
+
+ return shortcutIsRootLevel;
+ }
+
+ const castedActiveScopeElementHtml = activeScopeElement as HTMLDivElement;
+
+ // eslint-disable-next-line xss/no-mixed-html
+ const activeScope = castedActiveScopeElementHtml.dataset.keyboardShortcutScope;
+
+ return shortcut.scope === activeScope;
+};
+
+const toBindingWithDefaults = (binding: Binding) =>
+ isString(binding)
+ ? {
+ code: binding,
+ shift: false,
+ ctrl: false,
+ altOrOption: false,
+ meta: false,
+ ctrlOrCommand: false,
+ }
+ : {
+ ctrl: false,
+ shift: false,
+ altOrOption: false,
+ meta: false,
+ ctrlOrCommand: false,
+ ...binding,
+ };
+
+const toShortcutsWithMatchingBinding =
+ (event: KeyboardEvent, platform: string) => (shortcut: KeyboardShortcut) => {
+ const binding = toBindingWithDefaults(shortcut.binding);
+
+ const shiftModifierMatches = binding.shift === event.shiftKey;
+ const altModifierMatches = binding.altOrOption === event.altKey;
+
+ const isMac = platform === "darwin";
+
+ const ctrlModifierMatches =
+ binding.ctrl === event.ctrlKey || (!isMac && binding.ctrlOrCommand === event.ctrlKey);
+
+ const metaModifierMatches =
+ binding.meta === event.metaKey || (isMac && binding.ctrlOrCommand === event.metaKey);
+
+ return (
+ event.code === binding.code &&
+ shiftModifierMatches &&
+ ctrlModifierMatches &&
+ altModifierMatches &&
+ metaModifierMatches
+ );
+ };
+
+const invokeShortcutInjectable = getInjectable({
+ id: "invoke-shortcut",
+
+ instantiate: (di): InvokeShortcut => {
+ const getShortcuts = () => di.injectMany(keyboardShortcutInjectionToken);
+ const platform = di.inject(platformInjectable);
+
+ return (event) => {
+ const shortcutsToInvoke = pipeline(
+ getShortcuts(),
+ filter(toShortcutsWithMatchingBinding(event, platform)),
+ filter(toShortcutsWithMatchingScope),
+ );
+
+ if (shortcutsToInvoke.length) {
+ shortcutsToInvoke.forEach((shortcut) => shortcut.invoke());
+ }
+ };
+ },
+});
+
+export default invokeShortcutInjectable;
diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts
new file mode 100644
index 0000000000..637990b107
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts
@@ -0,0 +1,22 @@
+import { getInjectionToken } from "@ogre-tools/injectable";
+
+export type Binding =
+ | string
+ | {
+ code: string;
+ shift?: boolean;
+ ctrl?: boolean;
+ altOrOption?: boolean;
+ meta?: boolean;
+ ctrlOrCommand?: boolean;
+ };
+
+export type KeyboardShortcut = {
+ binding: Binding;
+ invoke: () => void;
+ scope?: string;
+};
+
+export const keyboardShortcutInjectionToken = getInjectionToken({
+ id: "keyboard-shortcut-injection-token",
+});
diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx
new file mode 100644
index 0000000000..ed22ef7fe8
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx
@@ -0,0 +1,10 @@
+import { getInjectable } from "@ogre-tools/injectable";
+import { KeyboardShortcutListener } from "./keyboard-shortcut-listener";
+import { reactApplicationHigherOrderComponentInjectionToken } from "@k8slens/react-application";
+
+export const keyboardShortcutListenerReactApplicationHocInjectable = getInjectable({
+ id: "keyboard-shortcut-listener-react-application-hoc",
+ instantiate: () => KeyboardShortcutListener,
+
+ injectionToken: reactApplicationHigherOrderComponentInjectionToken,
+});
diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx
new file mode 100644
index 0000000000..5f2479d059
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx
@@ -0,0 +1,41 @@
+import { withInjectables } from "@ogre-tools/injectable-react";
+import React, { useEffect } from "react";
+
+import invokeShortcutInjectable, { InvokeShortcut } from "./invoke-shortcut.injectable";
+
+export interface KeyboardShortcutListenerProps {
+ children: React.ReactNode;
+}
+
+interface Dependencies {
+ invokeShortcut: InvokeShortcut;
+}
+
+const NonInjectedKeyboardShortcutListener = ({
+ children,
+ invokeShortcut,
+}: KeyboardShortcutListenerProps & Dependencies) => {
+ useEffect(() => {
+ document.addEventListener("keydown", invokeShortcut);
+
+ return () => {
+ document.removeEventListener("keydown", invokeShortcut);
+ };
+ });
+
+ return <>{children}>;
+};
+
+export const KeyboardShortcutListener = withInjectables<
+ Dependencies,
+ KeyboardShortcutListenerProps
+>(
+ NonInjectedKeyboardShortcutListener,
+
+ {
+ getProps: (di, props) => ({
+ invokeShortcut: di.inject(invokeShortcutInjectable),
+ ...props,
+ }),
+ },
+);
diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx
new file mode 100644
index 0000000000..0e725cc4d4
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx
@@ -0,0 +1,12 @@
+import React from "react";
+
+export interface KeyboardShortcutScopeProps {
+ id: string;
+ children: React.ReactNode;
+}
+
+export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => (
+
+ {children}
+
+);
diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx
new file mode 100644
index 0000000000..d2bbfb85a4
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx
@@ -0,0 +1,353 @@
+import userEvent from "@testing-library/user-event";
+import type { RenderResult } from "@testing-library/react";
+import { render } from "@testing-library/react";
+import { createContainer, DiContainer, getInjectable } from "@ogre-tools/injectable";
+import { registerInjectableReact } from "@ogre-tools/injectable-react";
+import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
+import { keyboardShortcutInjectionToken } from "./keyboard-shortcut-injection-token";
+import { registerFeature } from "@k8slens/feature-core";
+import { keyboardShortcutsFeature } from "./feature";
+import React from "react";
+import { computed, runInAction } from "mobx";
+import { KeyboardShortcutScope } from "./keyboard-shortcut-scope";
+import { Discover, discoverFor } from "@k8slens/react-testing-library-discovery";
+import { startApplicationInjectionToken } from "@k8slens/application";
+import { renderInjectionToken } from "@k8slens/react-application";
+import { reactApplicationChildrenInjectionToken } from "@k8slens/react-application";
+import platformInjectable from "./platform.injectable";
+
+describe("keyboard-shortcuts", () => {
+ let di: DiContainer;
+ let invokeMock: jest.Mock;
+ let rendered: RenderResult;
+
+ beforeEach(() => {
+ di = createContainer("irrelevant");
+
+ registerInjectableReact(di);
+ registerMobX(di);
+
+ runInAction(() => {
+ registerFeature(di, keyboardShortcutsFeature);
+ });
+
+ invokeMock = jest.fn();
+
+ const someKeyboardShortcutInjectable = getInjectable({
+ id: "some-keyboard-shortcut",
+
+ instantiate: () => ({
+ binding: "Escape",
+ invoke: () => invokeMock("esc-in-root"),
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ const someScopedKeyboardShortcutInjectable = getInjectable({
+ id: "some-scoped-keyboard-shortcut",
+
+ instantiate: () => ({
+ binding: "Escape",
+ invoke: () => invokeMock("esc-in-scope"),
+ scope: "some-scope",
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ const someOtherKeyboardShortcutInjectable = getInjectable({
+ id: "some-other-keyboard-shortcut",
+
+ instantiate: () => ({
+ binding: "something-else-than-esc",
+ invoke: () => invokeMock("something-else-than-esc"),
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ const childComponentForScopeInjectable = getInjectable({
+ id: "some-child-component-for-scope",
+
+ instantiate: () => ({
+ id: "some-child-component-for-scope",
+
+ enabled: computed(() => true),
+
+ Component: () => (
+
+
+
+ ),
+ }),
+
+ injectionToken: reactApplicationChildrenInjectionToken,
+ });
+
+ runInAction(() => {
+ di.register(
+ someKeyboardShortcutInjectable,
+ someScopedKeyboardShortcutInjectable,
+ someOtherKeyboardShortcutInjectable,
+ childComponentForScopeInjectable,
+ );
+ });
+
+ di.override(renderInjectionToken, () => (application) => {
+ rendered = render(application);
+ });
+ });
+
+ describe("when application is started", () => {
+ let discover: Discover;
+
+ beforeEach(async () => {
+ const startApplication = di.inject(startApplicationInjectionToken);
+
+ await startApplication();
+
+ discover = discoverFor(() => rendered);
+ });
+
+ it("renders", () => {
+ expect(rendered.baseElement).toMatchSnapshot();
+ });
+
+ it("given focus is in the body, when pressing the shortcut, calls shortcut in global scope", () => {
+ userEvent.keyboard("{Escape}");
+
+ expect(invokeMock.mock.calls).toEqual([["esc-in-root"]]);
+ });
+
+ it("given focus inside a nested scope, when pressing the shortcut, calls only the callback for the scope", () => {
+ const result = discover.getSingleElement("keyboard-shortcut-scope", "some-scope");
+
+ const discoveredHtml = result.discovered as HTMLDivElement;
+
+ discoveredHtml.focus();
+
+ userEvent.keyboard("{Escape}");
+
+ expect(invokeMock.mock.calls).toEqual([["esc-in-scope"]]);
+ });
+
+ it("given conflicting shortcut, when pressing the shortcut, calls both callbacks", () => {
+ const conflictingShortcutInjectable = getInjectable({
+ id: "some-conflicting-keyboard-shortcut",
+
+ instantiate: () => ({
+ binding: "Escape",
+ invoke: () => invokeMock("conflicting-esc-in-root"),
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ runInAction(() => {
+ di.register(conflictingShortcutInjectable);
+ });
+
+ userEvent.keyboard("{Escape}");
+
+ expect(invokeMock.mock.calls).toEqual([["esc-in-root"], ["conflicting-esc-in-root"]]);
+ });
+
+ [
+ {
+ scenario: "given shortcut without modifiers, when shortcut is pressed, calls the callback",
+ binding: { code: "Escape" },
+ keyboard: "{Escape}",
+ shouldCallCallback: true,
+ },
+ {
+ scenario:
+ "given shortcut without modifiers, when shortcut is pressed but with modifier, does not call the callback",
+ binding: { code: "F1" },
+ keyboard: "{Meta>}[F1]",
+ shouldCallCallback: false,
+ },
+ {
+ scenario: "given shortcut with meta modifier, when shortcut is pressed, calls the callback",
+
+ binding: { meta: true, code: "F1" },
+ keyboard: "{Meta>}[F1]",
+ shouldCallCallback: true,
+ },
+ {
+ scenario:
+ "given shortcut with shift modifier, when shortcut is pressed, calls the callback",
+
+ binding: { shift: true, code: "F1" },
+ keyboard: "{Shift>}[F1]",
+ shouldCallCallback: true,
+ },
+ {
+ scenario: "given shortcut with alt modifier, when shortcut is pressed, calls the callback",
+ binding: { altOrOption: true, code: "F1" },
+ keyboard: "{Alt>}[F1]",
+ shouldCallCallback: true,
+ },
+ {
+ scenario: "given shortcut with ctrl modifier, when shortcut is pressed, calls the callback",
+ binding: { ctrl: true, code: "F1" },
+ keyboard: "{Control>}[F1]",
+ shouldCallCallback: true,
+ },
+ {
+ scenario: "given shortcut with all modifiers, when shortcut is pressed, calls the callback",
+
+ binding: { ctrl: true, altOrOption: true, shift: true, meta: true, code: "F1" },
+ keyboard: "{Meta>}{Shift>}{Alt>}{Control>}[F1]",
+ shouldCallCallback: true,
+ },
+ ].forEach(({ binding, keyboard, scenario, shouldCallCallback }) => {
+ // eslint-disable-next-line jest/valid-title
+ it(scenario, () => {
+ const invokeMock = jest.fn();
+
+ const shortcutInjectable = getInjectable({
+ id: "shortcut",
+
+ instantiate: () => ({
+ binding,
+ invoke: invokeMock,
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ runInAction(() => {
+ di.register(shortcutInjectable);
+ });
+
+ userEvent.keyboard(keyboard);
+
+ if (shouldCallCallback) {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(invokeMock).toHaveBeenCalled();
+ } else {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(invokeMock).not.toHaveBeenCalled();
+ }
+ });
+ });
+ });
+
+ describe("given in mac and keyboard shortcut with modifier for ctrl or command", () => {
+ beforeEach(async () => {
+ di.override(platformInjectable, () => "darwin");
+
+ invokeMock = jest.fn();
+
+ const shortcutInjectable = getInjectable({
+ id: "shortcut",
+
+ instantiate: () => ({
+ binding: { code: "KeyK", ctrlOrCommand: true },
+ invoke: invokeMock,
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ runInAction(() => {
+ di.register(shortcutInjectable);
+ });
+
+ const startApplication = di.inject(startApplicationInjectionToken);
+
+ await startApplication();
+ });
+
+ it("when pressing the keyboard shortcut with command, calls the callback", () => {
+ userEvent.keyboard("{Meta>}[KeyK]");
+
+ expect(invokeMock).toHaveBeenCalled();
+ });
+
+ it("when pressing the keyboard shortcut with ctrl, does not call the callback", () => {
+ userEvent.keyboard("{Control>}[KeyK]");
+
+ expect(invokeMock).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("given in windows and keyboard shortcut with modifier for ctrl or command", () => {
+ beforeEach(async () => {
+ di.override(platformInjectable, () => "win32");
+
+ invokeMock = jest.fn();
+
+ const shortcutInjectable = getInjectable({
+ id: "shortcut",
+
+ instantiate: () => ({
+ binding: { code: "KeyK", ctrlOrCommand: true },
+ invoke: invokeMock,
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ runInAction(() => {
+ di.register(shortcutInjectable);
+ });
+
+ const startApplication = di.inject(startApplicationInjectionToken);
+
+ await startApplication();
+ });
+
+ it("when pressing the keyboard shortcut with windows, does not call the callback", () => {
+ userEvent.keyboard("{Meta>}[KeyK]");
+
+ expect(invokeMock).not.toHaveBeenCalled();
+ });
+
+ it("when pressing the keyboard shortcut with ctrl, calls the callback", () => {
+ userEvent.keyboard("{Control>}[KeyK]");
+
+ expect(invokeMock).toHaveBeenCalled();
+ });
+ });
+
+ describe("given in any other platform and keyboard shortcut with modifier for ctrl or command", () => {
+ beforeEach(async () => {
+ di.override(platformInjectable, () => "some-other-platform");
+
+ invokeMock = jest.fn();
+
+ const shortcutInjectable = getInjectable({
+ id: "shortcut",
+
+ instantiate: () => ({
+ binding: { code: "KeyK", ctrlOrCommand: true },
+ invoke: invokeMock,
+ }),
+
+ injectionToken: keyboardShortcutInjectionToken,
+ });
+
+ runInAction(() => {
+ di.register(shortcutInjectable);
+ });
+
+ const startApplication = di.inject(startApplicationInjectionToken);
+
+ await startApplication();
+ });
+
+ it("when pressing the keyboard shortcut with meta, does not call the callback", () => {
+ userEvent.keyboard("{Meta>}[KeyK]");
+
+ expect(invokeMock).not.toHaveBeenCalled();
+ });
+
+ it("when pressing the keyboard shortcut with ctrl, calls the callback", () => {
+ userEvent.keyboard("{Control>}[KeyK]");
+
+ expect(invokeMock).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts b/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts
new file mode 100644
index 0000000000..407af8a43d
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts
@@ -0,0 +1,11 @@
+import { getInjectable } from "@ogre-tools/injectable";
+
+export const allPlatforms = ["win32", "darwin", "linux"] as const;
+
+const platformInjectable = getInjectable({
+ id: "platform",
+ instantiate: () => process.platform as (typeof allPlatforms)[number],
+ causesSideEffects: true,
+});
+
+export default platformInjectable;
diff --git a/packages/business-features/keyboard-shortcuts/tsconfig.json b/packages/business-features/keyboard-shortcuts/tsconfig.json
new file mode 100644
index 0000000000..9e140d79da
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@k8slens/typescript/config/base.json",
+ "include": ["**/*.ts", "**/*.tsx"],
+}
diff --git a/packages/business-features/keyboard-shortcuts/webpack.config.js b/packages/business-features/keyboard-shortcuts/webpack.config.js
new file mode 100644
index 0000000000..1cda407f5a
--- /dev/null
+++ b/packages/business-features/keyboard-shortcuts/webpack.config.js
@@ -0,0 +1 @@
+module.exports = require("@k8slens/webpack").configForReact;
diff --git a/packages/open-lens/package.json b/packages/open-lens/package.json
index 7fa19eeb23..c229a32d58 100644
--- a/packages/open-lens/package.json
+++ b/packages/open-lens/package.json
@@ -190,6 +190,7 @@
"@k8slens/core": "^6.5.0-alpha.3",
"@k8slens/ensure-binaries": "^6.5.0-alpha.1",
"@k8slens/feature-core": "^6.5.0-alpha.1",
+ "@k8slens/keyboard-shortcuts": "^1.0.0-alpha.0",
"@k8slens/legacy-extension-example": "^1.0.0-alpha.1",
"@k8slens/legacy-extensions": "^1.0.0-alpha.1",
"@k8slens/messaging": "^1.0.0-alpha.1",
diff --git a/packages/open-lens/src/renderer/index.ts b/packages/open-lens/src/renderer/index.ts
index 2d5dda2f04..d14e817347 100644
--- a/packages/open-lens/src/renderer/index.ts
+++ b/packages/open-lens/src/renderer/index.ts
@@ -15,6 +15,7 @@ import { createContainer } from "@ogre-tools/injectable";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import { registerInjectableReact } from "@ogre-tools/injectable-react";
import { messagingFeatureForRenderer } from "@k8slens/messaging-for-renderer";
+import { keyboardShortcutsFeature } from "@k8slens/keyboard-shortcuts";
import { reactApplicationFeature } from "@k8slens/react-application";
const environment = "renderer";
@@ -30,6 +31,7 @@ runInAction(() => {
di,
applicationFeature,
messagingFeatureForRenderer,
+ keyboardShortcutsFeature,
reactApplicationFeature
);