@24i/bigscreen-sdk 1.0.25-alpha.2440 → 1.0.26-alpha.2447

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/packages/create-package/dist/createPackage.js +66 -0
  3. package/packages/create-package/dist/createPackage.js.map +1 -0
  4. package/packages/create-package/dist/index.js +5 -0
  5. package/packages/create-package/dist/index.js.map +1 -0
  6. package/packages/create-package/dist/questionnaire/questions.js +35 -0
  7. package/packages/create-package/dist/questionnaire/questions.js.map +1 -0
  8. package/packages/create-package/dist/settings/Settings.js +10 -0
  9. package/packages/create-package/dist/settings/Settings.js.map +1 -0
  10. package/packages/create-package/dist/types.js +3 -0
  11. package/packages/create-package/dist/types.js.map +1 -0
  12. package/packages/focus/README.md +18 -0
  13. package/packages/focus/src/IFocusable.ts +1 -0
  14. package/packages/focus/src/Layout/Base.tsx +12 -2
  15. package/packages/focus/src/hasFocus.ts +36 -0
  16. package/packages/focus/src/index.ts +1 -0
  17. package/packages/sdk-installer/dist/index.js +5 -0
  18. package/packages/sdk-installer/dist/sdkInstaller.js +38 -0
  19. package/packages/sdk-installer/dist/services/clean.js +11 -0
  20. package/packages/sdk-installer/dist/services/download.js +55 -0
  21. package/packages/sdk-installer/dist/services/index.js +11 -0
  22. package/packages/sdk-installer/dist/services/install.js +24 -0
  23. package/packages/sdk-installer/dist/services/unzip.js +12 -0
  24. package/packages/sdk-installer/dist/types/index.d.ts +1 -0
  25. package/packages/sdk-installer/dist/types/sdkInstaller.d.ts +7 -0
  26. package/packages/sdk-installer/dist/types/services/clean.d.ts +2 -0
  27. package/packages/sdk-installer/dist/types/services/download.d.ts +2 -0
  28. package/packages/sdk-installer/dist/types/services/index.d.ts +4 -0
  29. package/packages/sdk-installer/dist/types/services/install.d.ts +2 -0
  30. package/packages/sdk-installer/dist/types/services/unzip.d.ts +2 -0
  31. package/packages/utils/README.md +4 -0
  32. package/packages/utils/src/elementUtils.ts +25 -0
  33. package/packages/utils/src/index.ts +1 -0
  34. package/packages/focus/src/Layout/__test__/Base.spec.ts +0 -191
  35. package/packages/focus/src/Layout/__test__/Horizontal.spec.ts +0 -92
  36. package/packages/focus/src/Layout/__test__/Matrix.spec.ts +0 -299
  37. package/packages/focus/src/Layout/__test__/Vertical.spec.ts +0 -44
  38. package/packages/focus/src/Layout/__test__/isRtl.spec.ts +0 -23
  39. package/packages/focus/src/Layout/__test__/shared.ts +0 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/bigscreen-sdk",
3
- "version": "1.0.25-alpha.2440",
3
+ "version": "1.0.26-alpha.2447",
4
4
  "description": "SmartApps BIGscreen SDK monorepo",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.main = void 0;
5
+ const path = require("path");
6
+ const fs = require("fs");
7
+ const fse = require("fs-extra");
8
+ const chalk = require("chalk");
9
+ const minimist = require("minimist");
10
+ const questions_1 = require("./questionnaire/questions");
11
+ const Settings_1 = require("./settings/Settings");
12
+ async function isDirectoryEmpty(dirPath) {
13
+ const files = await fs.promises.readdir(dirPath);
14
+ return !files.length;
15
+ }
16
+ async function copyTemplateFiles(templateDir, targetDir) {
17
+ return fse.copy(templateDir, targetDir);
18
+ }
19
+ function updatePackageJsonFile(filePath, options) {
20
+ let data = fs.readFileSync(filePath, 'utf8');
21
+ const keywords = options.keywords.map((word) => `"${word}"`).join(', ');
22
+ data = data.replace(/("name": ")(.*)(")/, `$1${options.name}$3`);
23
+ data = data.replace(/("version": ")(.*)(")/, `$1${options.version}$3`);
24
+ data = data.replace(/("description": ")(.*)(")/, `$1${options.description}$3`);
25
+ data = data.replace(/("keywords": \[)(.*)(\])/, `$1${keywords}$3`);
26
+ data = data.replace(/("author": ")(.*)(")/, `$1${options.author}$3`);
27
+ fs.writeFileSync(filePath, data);
28
+ }
29
+ function parseArguments(rawArgv) {
30
+ const argv = minimist(rawArgv.slice(2));
31
+ const dirName = argv._[0];
32
+ if (!dirName) {
33
+ console.error('Error: Missing package name');
34
+ process.exit(1);
35
+ }
36
+ return {
37
+ dirName,
38
+ };
39
+ }
40
+ async function main() {
41
+ const { dirName: destDirName } = parseArguments(process.argv);
42
+ const destDir = path.join(process.cwd(), 'packages/', destDirName);
43
+ if (fs.existsSync(destDir)) {
44
+ console.error(`Error: Target directory "${destDir}" already exists`);
45
+ process.exit(1);
46
+ }
47
+ fs.mkdirSync(destDir, { recursive: true });
48
+ const isDirEmpty = await isDirectoryEmpty(destDir);
49
+ if (!isDirEmpty) {
50
+ console.error(`Error: Target directory "${destDir}" is not empty.`);
51
+ process.exit(1);
52
+ }
53
+ const settings = Object.assign({}, Settings_1.Settings);
54
+ settings.name = `${Settings_1.Settings.name}${destDirName}`.toLowerCase();
55
+ const details = await (0, questions_1.promptPackageDetails)(settings);
56
+ const templateDir = path.join(__dirname, '..', 'templates/typescript');
57
+ await copyTemplateFiles(templateDir, destDir);
58
+ const packageJsonFile = path.join(destDir, 'package.json');
59
+ updatePackageJsonFile(packageJsonFile, details);
60
+ const docsSymlinkTarget = path.join('../../../packages/', destDirName, '/README.md');
61
+ const docsSymlinkPath = path.join(process.cwd(), 'docs/packages/', destDirName, '/README.md');
62
+ await fse.ensureSymlink(docsSymlinkTarget, docsSymlinkPath);
63
+ console.log(`${chalk.green('Success')}, package has been initialized!`);
64
+ }
65
+ exports.main = main;
66
+ //# sourceMappingURL=createPackage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createPackage.js","sourceRoot":"","sources":["../src/createPackage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const createPackage_1 = require("./createPackage");
4
+ (0, createPackage_1.main)();
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,mDAAuC;AAEvC,IAAA,oBAAI,GAAE,CAAC"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promptPackageDetails = void 0;
4
+ const inquirer = require("inquirer");
5
+ async function promptPackageDetails(defaultValues) {
6
+ return inquirer.prompt([
7
+ {
8
+ type: 'input',
9
+ name: 'name',
10
+ message: 'Package name:',
11
+ default: defaultValues.name,
12
+ }, {
13
+ type: 'input',
14
+ name: 'version',
15
+ message: 'Version:',
16
+ default: defaultValues.version,
17
+ }, {
18
+ type: 'input',
19
+ name: 'description',
20
+ message: 'Description:',
21
+ }, {
22
+ type: 'input',
23
+ name: 'keywords',
24
+ message: 'Keywords:',
25
+ filter: (ans) => ans.split(/[\s,]+/),
26
+ }, {
27
+ type: 'input',
28
+ name: 'author',
29
+ message: 'Author:',
30
+ default: defaultValues.author,
31
+ },
32
+ ]);
33
+ }
34
+ exports.promptPackageDetails = promptPackageDetails;
35
+ //# sourceMappingURL=questions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questions.js","sourceRoot":"","sources":["../../src/questionnaire/questions.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AAWrC,KAAK,UAAU,oBAAoB,CAAC,aAA4C;IAC5E,OAAO,QAAQ,CAAC,MAAM,CAAC;QACnB;YACI,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,eAAe;YACxB,OAAO,EAAE,aAAa,CAAC,IAAI;SAC9B,EAAE;YACC,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,aAAa,CAAC,OAAO;SACjC,EAAE;YACC,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,cAAc;SAC1B,EAAE;YACC,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;SACvC,EAAE;YACC,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,aAAa,CAAC,MAAM;SAChC;KACJ,CAAC,CAAC;AACP,CAAC;AAEQ,oDAAoB"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Settings = void 0;
4
+ const Settings = {
5
+ name: '@24i/smartapps-bigscreen-',
6
+ version: '0.0.1',
7
+ author: '24i',
8
+ };
9
+ exports.Settings = Settings;
10
+ //# sourceMappingURL=Settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Settings.js","sourceRoot":"","sources":["../../src/settings/Settings.ts"],"names":[],"mappings":";;;AAAA,MAAM,QAAQ,GAAG;IACb,IAAI,EAAE,2BAA2B;IACjC,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,KAAK;CAChB,CAAC;AAEO,4BAAQ"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -14,11 +14,13 @@ IFocusable is an interface that should be implemented by anything that can be fo
14
14
  interface IFocusable {
15
15
  focus(options?: FocusOptions): void,
16
16
  hasDom?(element: HTMLElement): boolean,
17
+ hasFocus?(): boolean,
17
18
  }
18
19
  ```
19
20
  - focus is the main function that is called when focusing component/element.
20
21
  - hasDom is optional optimization for components to help Layout components locate the currently
21
22
  focused child.
23
+ - hasFocus is optional check if the component has focus inside or is itself focused.
22
24
 
23
25
  ## Layout components
24
26
  - Vertical: Use it to create focus between elements vertically (up/down)
@@ -40,6 +42,7 @@ to the focused element and its index. Ideal place to change other elements
40
42
  - onBeforeFocusChange: Optional callback just before focus change. It receives a reference
41
43
  to the focused element and its index. Ideal place for preparing for the change
42
44
  (e.g. moving the container to fit new element in the screen)
45
+ - circular: makes focus circular, e.g. when pressing left at leftmost element focus will go to rightmost element
43
46
  - dir: Optional forceful setting of the component direction. Defaults to `'auto'` which follows
44
47
  the direction of the app.
45
48
  `'ltr'` always forces left-to-right layout,
@@ -328,3 +331,18 @@ outside of the component and not focused.
328
331
  ```ts
329
332
  const refocusAfterHashChange = (focusWrapperOrReference: HTMLElement | Reference<HTMLElement>) => void;
330
333
  ```
334
+
335
+ ## hasFocus
336
+ Test if the element (or element in the reference) has the `activeElement` inside or optionally,
337
+ if the element itself is the `activeElement`.
338
+
339
+ ```ts
340
+ const hasFocus = (elementOrReference: HTMLElement | Reference<HTMLElement>, includeElementItself = false) => boolean;
341
+ ```
342
+
343
+ ## isFocused
344
+ Test if the element (or element in the reference) is the `activeElement`.
345
+
346
+ ```ts
347
+ const isFocused = (elementOrReference: HTMLElement | Reference<HTMLElement>) => boolean;
348
+ ```
@@ -1,4 +1,5 @@
1
1
  export interface IFocusable {
2
2
  focus(options?: FocusOptions): void,
3
3
  hasDom?(element: HTMLElement): boolean,
4
+ hasFocus?(): boolean,
4
5
  }
@@ -12,6 +12,7 @@ export type Props = SharedProps & {
12
12
  focusableChildren: Array<Reference>,
13
13
  onFocusChanged?: (focused: Reference, index: number) => any,
14
14
  onBeforeFocusChange?: (focused: Reference, index: number) => any,
15
+ circular?: boolean,
15
16
  preventBlurOnLongPress?: {
16
17
  forward?: boolean,
17
18
  backward?: boolean,
@@ -39,6 +40,7 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
39
40
  onBeforeFocusChange: noop,
40
41
  focusOptions: { preventScroll: true },
41
42
  dir: 'auto',
43
+ circular: false,
42
44
  preventBlurOnLongPress: {},
43
45
  } as const;
44
46
 
@@ -94,7 +96,7 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
94
96
  }
95
97
 
96
98
  backward(e: KeyboardEvent) {
97
- const { preventBlurOnLongPress } = this.props;
99
+ const { focusableChildren, preventBlurOnLongPress, circular } = this.props;
98
100
  const { target } = e;
99
101
  const fromIndex = this.getFromIndex(target as HTMLElement);
100
102
  if (preventBlurOnLongPress.backward) {
@@ -104,13 +106,17 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
104
106
  this.setIndex(fromIndex - 1);
105
107
  this.focusCurrentIndex();
106
108
  stopEvent(e);
109
+ } else if (circular) {
110
+ this.setIndex(focusableChildren.length - 1);
111
+ this.focusCurrentIndex();
112
+ stopEvent(e);
107
113
  } else if (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS) {
108
114
  stopEvent(e);
109
115
  }
110
116
  }
111
117
 
112
118
  forward(e: KeyboardEvent) {
113
- const { focusableChildren, preventBlurOnLongPress } = this.props;
119
+ const { focusableChildren, preventBlurOnLongPress, circular } = this.props;
114
120
  const { target } = e;
115
121
  const fromIndex = this.getFromIndex(target as HTMLElement);
116
122
  if (preventBlurOnLongPress.forward) {
@@ -120,6 +126,10 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
120
126
  this.setIndex(fromIndex + 1);
121
127
  this.focusCurrentIndex();
122
128
  stopEvent(e);
129
+ } else if (circular) {
130
+ this.setIndex(0);
131
+ this.focusCurrentIndex();
132
+ stopEvent(e);
123
133
  } else if (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS) {
124
134
  stopEvent(e);
125
135
  }
@@ -0,0 +1,36 @@
1
+ import { Reference, unwrapReference } from '@24i/bigscreen-sdk/jsx';
2
+ import { isElementWrappedBy } from '@24i/bigscreen-sdk/utils/elementUtils';
3
+
4
+ /**
5
+ * Test if the element (or element in the reference) has the `activeElement` inside or optionally,
6
+ * if the element itself is the `activeElement`.
7
+ * @param elementOrReference DOM element or reference to element on which we test focus
8
+ * @param includeElementItself if true, active element is also tested with the provided element.
9
+ * Default is false
10
+ * @returns true if the element has focused element inside or is itself focused
11
+ */
12
+ export const hasFocus = (
13
+ elementOrReference: HTMLElement | Reference<HTMLElement>,
14
+ includeElementItself = false,
15
+ ) => {
16
+ const element = unwrapReference(elementOrReference);
17
+ if (!element) return false;
18
+ return isElementWrappedBy(
19
+ document.activeElement as HTMLElement,
20
+ element,
21
+ includeElementItself,
22
+ );
23
+ };
24
+
25
+ /**
26
+ * Test if the element (or element in the reference) is the `activeElement`.
27
+ * @param elementOrReference DOM element or reference to element on which we test focus
28
+ * @returns true if the element is focused
29
+ */
30
+ export const isFocused = (
31
+ elementOrReference: HTMLElement | Reference<HTMLElement>,
32
+ ) => {
33
+ const element = unwrapReference(elementOrReference);
34
+ if (!element) return false;
35
+ return document.activeElement === element;
36
+ };
@@ -13,3 +13,4 @@ export {
13
13
  } from './candidate';
14
14
  export { focusFirstExisting, preventScrollOnFocus } from './focusFirstExisting';
15
15
  export { refocusAfterHashChange } from './refocusAfterHashChange';
16
+ export { hasFocus, isFocused } from './hasFocus';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sdkInstaller = void 0;
4
+ var sdkInstaller_1 = require("./sdkInstaller");
5
+ Object.defineProperty(exports, "sdkInstaller", { enumerable: true, get: function () { return sdkInstaller_1.sdkInstaller; } });
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sdkInstaller = void 0;
4
+ const path_1 = require("path");
5
+ const url_1 = require("url");
6
+ const services_1 = require("./services");
7
+ const log = (message) => process.stdout.write(message);
8
+ const sdkInstaller = async ({ zipPath, installPath, enableLogs }) => {
9
+ const logger = enableLogs ? log : () => false;
10
+ const pathsToDelete = [];
11
+ let usedInstallPath;
12
+ try {
13
+ const parsedZipPath = (0, path_1.parse)(zipPath);
14
+ const parsedZipUrl = (0, url_1.parse)(zipPath);
15
+ if (!parsedZipUrl.protocol) {
16
+ throw new Error('Installation from local packages is not supported yet.');
17
+ }
18
+ logger('Downloading...');
19
+ const downloadedFilePath = `${process.cwd()}/${parsedZipPath.base}`;
20
+ pathsToDelete.push(downloadedFilePath);
21
+ await (0, services_1.download)(zipPath, downloadedFilePath);
22
+ logger('\rUnpacking...');
23
+ const unzippedDirPath = `${process.cwd()}/${parsedZipPath.name}`;
24
+ pathsToDelete.push(unzippedDirPath);
25
+ (0, services_1.unzip)(downloadedFilePath);
26
+ logger('\rInstalling...');
27
+ usedInstallPath = (0, services_1.install)(unzippedDirPath, installPath);
28
+ }
29
+ catch (e) {
30
+ (0, services_1.clean)(pathsToDelete);
31
+ throw e;
32
+ }
33
+ logger('\rCleaning...');
34
+ (0, services_1.clean)(pathsToDelete);
35
+ logger(`\rInstalled to ${usedInstallPath}\n`);
36
+ return usedInstallPath;
37
+ };
38
+ exports.sdkInstaller = sdkInstaller;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clean = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const deleteFileOrDirectory = (path) => {
6
+ (0, child_process_1.spawnSync)('rm', ['-r', path]);
7
+ };
8
+ const clean = (pathsToBeDeleted) => {
9
+ pathsToBeDeleted.forEach(deleteFileOrDirectory);
10
+ };
11
+ exports.clean = clean;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.download = void 0;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const url = require("url");
7
+ const http = require("http");
8
+ const https = require("https");
9
+ const createDirPathIfDoesntExist = async (dirPath) => {
10
+ if (!fs.existsSync(dirPath)) {
11
+ fs.mkdirSync(dirPath, { recursive: true });
12
+ }
13
+ };
14
+ const getNetworkProtocolAdapter = (requestedUrl) => {
15
+ const { protocol } = url.parse(requestedUrl);
16
+ switch (protocol) {
17
+ case 'http:': return http;
18
+ case 'https:': return https;
19
+ default: throw new Error(`${protocol} protocol is not supported`);
20
+ }
21
+ };
22
+ const getResponseError = (response) => {
23
+ const HTTP_RESPONSE_STATUS_CODE_OK = 200;
24
+ const { statusCode, headers } = response;
25
+ const contentType = headers['content-type'];
26
+ if (statusCode !== HTTP_RESPONSE_STATUS_CODE_OK) {
27
+ return new Error(`Request Failed. Status Code: ${statusCode}`);
28
+ }
29
+ if (!contentType) {
30
+ return new Error('Missing Content-Type: expected application/zip');
31
+ }
32
+ if (!/^application\/zip/.test(contentType)) {
33
+ return new Error(`Wrong Content-Type: expected application/zip, received ${contentType}`);
34
+ }
35
+ return null;
36
+ };
37
+ const download = async (fromUrl, toFilePath) => {
38
+ const toDirPath = path.parse(toFilePath).dir;
39
+ await createDirPathIfDoesntExist(toDirPath);
40
+ return new Promise((resolve, reject) => {
41
+ const request = getNetworkProtocolAdapter(fromUrl).get(fromUrl, (response) => {
42
+ const error = getResponseError(response);
43
+ if (error) {
44
+ reject(error);
45
+ return;
46
+ }
47
+ const file = fs.createWriteStream(toFilePath);
48
+ file.on('error', reject);
49
+ file.on('finish', resolve);
50
+ response.pipe(file);
51
+ });
52
+ request.on('error', reject);
53
+ });
54
+ };
55
+ exports.download = download;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clean = exports.install = exports.unzip = exports.download = void 0;
4
+ var download_1 = require("./download");
5
+ Object.defineProperty(exports, "download", { enumerable: true, get: function () { return download_1.download; } });
6
+ var unzip_1 = require("./unzip");
7
+ Object.defineProperty(exports, "unzip", { enumerable: true, get: function () { return unzip_1.unzip; } });
8
+ var install_1 = require("./install");
9
+ Object.defineProperty(exports, "install", { enumerable: true, get: function () { return install_1.install; } });
10
+ var clean_1 = require("./clean");
11
+ Object.defineProperty(exports, "clean", { enumerable: true, get: function () { return clean_1.clean; } });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.install = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const getInstalledPathFromOutput = (output) => {
6
+ var _a;
7
+ const prefix = 'INSTALL_PATH=';
8
+ const installedPathRow = output
9
+ .split('\n')
10
+ .map((outputRow) => outputRow.trim())
11
+ .filter((trimmedOutputRow) => trimmedOutputRow.startsWith(prefix));
12
+ return ((_a = installedPathRow[0]) === null || _a === void 0 ? void 0 : _a.replace(prefix, '')) || '';
13
+ };
14
+ const install = (installerDirPath, installToPath) => {
15
+ const installationScriptPath = `${installerDirPath}/install.js`;
16
+ const installationCommandWithoutCustomPath = `node ${installationScriptPath}`;
17
+ const installationCmdToBeUsed = installToPath
18
+ ? `${installationCommandWithoutCustomPath} ${installToPath}`
19
+ : installationCommandWithoutCustomPath;
20
+ const execOptions = { maxBuffer: 5242880 };
21
+ const installationScriptOutput = (0, child_process_1.execSync)(installationCmdToBeUsed, execOptions).toString();
22
+ return getInstalledPathFromOutput(installationScriptOutput);
23
+ };
24
+ exports.install = install;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unzip = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const unzip = (filePath) => {
6
+ const spawnOptions = { maxBuffer: 5242880 };
7
+ const unzipProcess = (0, child_process_1.spawnSync)('unzip', ['-n', filePath], spawnOptions);
8
+ if (unzipProcess.stderr.length) {
9
+ throw new Error(unzipProcess.stderr.toString());
10
+ }
11
+ };
12
+ exports.unzip = unzip;
@@ -0,0 +1 @@
1
+ export { sdkInstaller } from './sdkInstaller';
@@ -0,0 +1,7 @@
1
+ declare type SdkInstallerOptions = {
2
+ zipPath: string;
3
+ installPath?: string;
4
+ enableLogs?: boolean;
5
+ };
6
+ declare const sdkInstaller: ({ zipPath, installPath, enableLogs }: SdkInstallerOptions) => Promise<string>;
7
+ export { sdkInstaller };
@@ -0,0 +1,2 @@
1
+ declare const clean: (pathsToBeDeleted: string[]) => void;
2
+ export { clean };
@@ -0,0 +1,2 @@
1
+ declare const download: (fromUrl: string, toFilePath: string) => Promise<void>;
2
+ export { download };
@@ -0,0 +1,4 @@
1
+ export { download } from './download';
2
+ export { unzip } from './unzip';
3
+ export { install } from './install';
4
+ export { clean } from './clean';
@@ -0,0 +1,2 @@
1
+ declare const install: (installerDirPath: string, installToPath?: string | undefined) => string;
2
+ export { install };
@@ -0,0 +1,2 @@
1
+ declare const unzip: (filePath: string) => void;
2
+ export { unzip };
@@ -21,6 +21,7 @@ import { generateUuid } from '@24i/bigscreen-sdk/utils/generateUuid';
21
21
  - **[addClass](#addClass)** - safely adds class to reference.
22
22
  - **[debounce](#debounce)** - debounced function is only executed again after a period of not being called, supports leading, trailing (default) and both executions.
23
23
  - **[displayToggler](#displayToggler)** - exports `getDisplayToggler` that allows you to easily show/hide an element with guard preventing unnecessary DOM calls.
24
+ - **[elementUtils](#elementUtils)** - various utils above HTMLElement.
24
25
  - **[forceReflow](#forceReflow)** - To force a DOM reflow on given DOM element.
25
26
  - **[generateUuid](#generateUuid)** - function to get UUIDv4 according to RFC 4122.
26
27
  - **[isMouseUsed](#isMouseUsed)** - **DEPRECATED** moved to `device` package.
@@ -95,6 +96,9 @@ And returns `IDisplayToggler` interface with 4 functions:
95
96
  - `isVisible: () => boolean;` - returns true if the element is visible, false otherwise
96
97
  - `isHidden: () => boolean;` - returns true if the element is hidden, false otherwise
97
98
 
99
+ ### elementUtils
100
+ Various utils for HTMLElement such as visibility tests or element wrapping tests.
101
+
98
102
  ### forceReflow
99
103
  To force a DOM reflow on given DOM element. It can be needed for transitions
100
104
  or other purposes.
@@ -4,6 +4,12 @@ import { SCREEN_WIDTH } from './sizes';
4
4
 
5
5
  const SAFE_TIMEOUT = 100;
6
6
 
7
+ /**
8
+ * Test if the element (or element in the reference) is wrapped visible. This means that the
9
+ * element does not have `display: none` or `visibility: hidden`.
10
+ * @param elementOrReference DOM element or reference to element on which we test wrapping
11
+ * @returns true if element is visible
12
+ */
7
13
  export const isElementVisible = (
8
14
  elementOrReference: HTMLElement | Reference<HTMLElement>,
9
15
  ) => {
@@ -13,10 +19,18 @@ export const isElementVisible = (
13
19
  return style.display !== 'none' && style.visibility !== 'hidden';
14
20
  };
15
21
 
22
+ /**
23
+ * Test if the element (or element in the reference) is wrapped fully visible. This means that the
24
+ * element does not have `display: none` or `visibility: hidden` and exists
25
+ * inside visible DOM tree, which exists in the document.body.
26
+ * @param elementOrReference DOM element or reference to element on which we test wrapping
27
+ * @returns true if element is visible
28
+ */
16
29
  export const isElementFullyVisible = (
17
30
  elementOrReference: HTMLElement | Reference<HTMLElement>,
18
31
  ) => {
19
32
  const element = unwrapReference(elementOrReference);
33
+ if (!element) return false;
20
34
  if (isElementVisible(element)) {
21
35
  let parent = element.parentElement;
22
36
  while (parent) {
@@ -33,6 +47,16 @@ export const isElementFullyVisible = (
33
47
  return false;
34
48
  };
35
49
 
50
+ /**
51
+ * Test if the element (or element in the reference) is wrapped by another element.
52
+ * @param elementOrReference DOM element or reference to element on which we test wrapping
53
+ * @param wrapperElementOrReferenceOrCondition wrapper element or certain condition (utils is
54
+ * running upwards in the DOM tree from first element till some element has some specific condition)
55
+ * @param includesElementItself if true, test will check also provided element itself.
56
+ * This is useful in some cases when we need to include also provided element as wrapper
57
+ * in one run of the util.
58
+ * @returns true if the element is wrapped by provided wrapper
59
+ */
36
60
  export const isElementWrappedBy = (
37
61
  elementOrReference: HTMLElement | Reference<HTMLElement>,
38
62
  wrapperElementOrReferenceOrCondition:
@@ -40,6 +64,7 @@ export const isElementWrappedBy = (
40
64
  includesElementItself = false,
41
65
  ) => {
42
66
  const element = unwrapReference(elementOrReference);
67
+ if (!element) return false;
43
68
  let currentParent = includesElementItself ? element : element.parentElement;
44
69
  let wrapperElement: HTMLElement | null = null;
45
70
  let condition: ((currentWrapper: HTMLElement) => boolean) | null = null;
@@ -16,6 +16,7 @@ export {
16
16
  isElementVisible,
17
17
  isElementFullyVisible,
18
18
  isElementWrappedBy,
19
+ preventDefaultScroll,
19
20
  } from './elementUtils';
20
21
  export { createInterval, createTimeout, runAsync } from './timers';
21
22
  export type { TimedFunction } from './timers';
@@ -1,191 +0,0 @@
1
- import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
2
- import { IFocusable } from '../../IFocusable';
3
- import { getLayoutBase } from '../Base';
4
- import { rootRef, refChildA, refChildB, refChildC, refChildD } from './shared';
5
-
6
- jest.mock('@24i/bigscreen-sdk/utils/stopEvent');
7
-
8
- const mockKeydown = jest.fn();
9
- const mockEvent: any = {
10
- target: null,
11
- };
12
-
13
- const Base = getLayoutBase(mockKeydown);
14
-
15
- describe('Focus Layout Base', () => {
16
- let base: any;
17
- beforeEach(() => {
18
- base = new Base({
19
- ...Base.defaultProps,
20
- domRef: rootRef,
21
- children: 'children',
22
- focusableChildren: [refChildA, refChildB, refChildC],
23
- onFocusChanged: jest.fn(),
24
- onBeforeFocusChange: jest.fn(),
25
- });
26
- jest.clearAllMocks();
27
- });
28
-
29
- it('should throw without domRef on componentDidMount', () => {
30
- // @ts-ignore - the point of the test is to test possible runtime problem
31
- const invalidBase = new Base({ domRef: null, children: [], focusableChildren: [] });
32
- expect(() => invalidBase.componentDidMount()).toThrow();
33
- });
34
-
35
- it('should throw without domRef on componentWillUnmount', () => {
36
- // @ts-ignore - the point of the test is to test possible runtime problem
37
- const invalidBase = new Base({ domRef: null, children: [], focusableChildren: [] });
38
- expect(() => invalidBase.componentWillUnmount()).toThrow();
39
- });
40
-
41
- it('should not throw with valid domRef on componentDidMount', () => {
42
- expect(() => base.componentDidMount()).not.toThrow();
43
- });
44
-
45
- it('should not throw with valid domRef on componentWillUnmount', () => {
46
- expect(() => base.componentWillUnmount()).not.toThrow();
47
- });
48
-
49
- it('should render children', () => {
50
- expect(base.render()).toBe('children');
51
- });
52
-
53
- it('should return false when setting index out of range', () => {
54
- expect(base.setIndex(4)).toBe(false);
55
- });
56
-
57
- it('should return true when setting index out of range', () => {
58
- expect(base.setIndex(1)).toBe(true);
59
- });
60
-
61
- it('should increase index by one on forward', () => {
62
- const oldIndex = base.index;
63
- base.forward(mockEvent);
64
- expect(base.index).toBe(oldIndex + 1);
65
- });
66
-
67
- it('should deecrease index by one on backward', () => {
68
- base.index = 2;
69
- const oldIndex = base.index;
70
- base.backward(mockEvent);
71
- expect(base.index).toBe(oldIndex - 1);
72
- });
73
-
74
- it('should not increase index above focusableChildren.length - 1', () => {
75
- base.index = base.props.focusableChildren.length - 1;
76
- base.forward(mockEvent);
77
- expect(base.index).toBe(base.props.focusableChildren.length - 1);
78
- });
79
-
80
- it('should not decrease index below 0', () => {
81
- base.index = 0;
82
- base.backward(mockEvent);
83
- expect(base.index).toBe(0);
84
- });
85
-
86
- it('should call focus on new element when going forward', () => {
87
- base.forward(mockEvent);
88
- expect(refChildB.current!.focus).toHaveBeenCalled();
89
- });
90
-
91
- it('should call focus on new element when going backward', () => {
92
- base.index = 1;
93
- base.backward(mockEvent);
94
- expect(refChildA.current!.focus).toHaveBeenCalled();
95
- });
96
-
97
- it('should not call focus when can\'t go forward', () => {
98
- base.index = 2;
99
- base.forward(mockEvent);
100
- expect(refChildC.current!.focus).not.toHaveBeenCalled();
101
- });
102
-
103
- it('should not call focus when can\'t go backward', () => {
104
- base.backward(mockEvent);
105
- expect(refChildA.current!.focus).not.toHaveBeenCalled();
106
- });
107
-
108
- it('should call stopEvent when going forward', () => {
109
- base.forward(mockEvent);
110
- expect(stopEvent).toHaveBeenCalledTimes(1);
111
- });
112
-
113
- it('should call stopEvent when going backward', () => {
114
- base.index = 1;
115
- base.backward(mockEvent);
116
- expect(stopEvent).toHaveBeenCalledTimes(1);
117
- });
118
-
119
- it('should not call stopEvent when can\'t go forward', () => {
120
- base.index = 2;
121
- base.forward(mockEvent);
122
- expect(stopEvent).not.toHaveBeenCalledTimes(1);
123
- });
124
-
125
- it('should not call stopEvent when can\'t go backward', () => {
126
- base.backward(mockEvent);
127
- expect(stopEvent).not.toHaveBeenCalledTimes(1);
128
- });
129
-
130
- it('should call stopEvent when going forward and preventBlurOnLongPress is on', () => {
131
- base.props.preventBlurOnLongPress.forward = true;
132
- base.forward(mockEvent);
133
- base.forward(mockEvent);
134
- base.forward(mockEvent);
135
- expect(stopEvent).toHaveBeenCalledTimes(3);
136
- });
137
-
138
- it('should call stopEvent when going backward and preventBlurOnLongPress is on', () => {
139
- base.index = 2;
140
- base.props.preventBlurOnLongPress.backward = true;
141
- base.backward(mockEvent);
142
- base.backward(mockEvent);
143
- base.backward(mockEvent);
144
- expect(stopEvent).toHaveBeenCalledTimes(3);
145
- });
146
-
147
- it('should call onFocusChanged when focusing current index', () => {
148
- base.focusCurrentIndex();
149
- expect(base.props.onFocusChanged).toHaveBeenCalled();
150
- });
151
-
152
- it('should call onBeforeFocusChange when focusing current index', () => {
153
- base.focusCurrentIndex();
154
- expect(base.props.onBeforeFocusChange).toHaveBeenCalled();
155
- });
156
-
157
- it('should focus current index on focus', () => {
158
- base.index = 2;
159
- base.focus();
160
- expect(refChildC.current!.focus).toHaveBeenCalled();
161
- });
162
-
163
- it('should call passed onKeyDown on onKeyDown', () => {
164
- base.onKeyDown();
165
- expect(mockKeydown).toHaveBeenCalled();
166
- });
167
-
168
- it('should throw in focusCurrentIndex when element does not implement focus', () => {
169
- base.props.focusableChildren[2] = {};
170
- base.index = 2;
171
- expect(() => base.focusCurrentIndex()).toThrow();
172
- });
173
-
174
- it('should take result of hasDom into account of determining current focus position', () => {
175
- base = new Base({
176
- ...Base.defaultProps,
177
- domRef: rootRef,
178
- children: 'children',
179
- focusableChildren: [refChildA, refChildB, refChildC, refChildD],
180
- onFocusChanged: jest.fn(),
181
- onBeforeFocusChange: jest.fn(),
182
- });
183
- ((refChildD.current as IFocusable).hasDom as jest.MockedFn<
184
- Exclude<IFocusable['hasDom'], undefined>
185
- >).mockReturnValueOnce(true);
186
- base.backward(mockEvent);
187
- expect(base.index).toBe(2);
188
- base.backward(mockEvent);
189
- expect(base.index).toBe(1);
190
- });
191
- });
@@ -1,92 +0,0 @@
1
- import { isRtl } from '@24i/bigscreen-sdk/i18n';
2
- import { Horizontal } from '../Horizontal';
3
- import { rootRef, refChildA, refChildB, refChildC } from './shared';
4
-
5
- jest.mock('@24i/bigscreen-sdk/i18n', () => ({
6
- ...jest.requireActual('@24i/bigscreen-sdk/i18n'),
7
- isRtl: jest.fn().mockReturnValue(false),
8
- }));
9
- const isRtlMock = isRtl as jest.MockedFn<typeof isRtl>;
10
-
11
- const mockEvent = {
12
- target: false,
13
- };
14
- const mockLEFTEvent = {
15
- code: 'ArrowLeft',
16
- ...mockEvent,
17
- };
18
- const mockRIGHTEvent = {
19
- code: 'ArrowRight',
20
- ...mockEvent,
21
- };
22
-
23
- describe('Horizontal Focus Layout', () => {
24
- let horizontal: any;
25
- beforeEach(() => {
26
- horizontal = new Horizontal({
27
- domRef: rootRef,
28
- children: 'children',
29
- focusableChildren: [refChildA, refChildB, refChildC],
30
- onFocusChanged: jest.fn(),
31
- onBeforeFocusChange: jest.fn(),
32
- });
33
- horizontal.forward = jest.fn();
34
- horizontal.backward = jest.fn();
35
- });
36
-
37
- it('should be a class component', () => {
38
- expect(Horizontal.isClass).toBe(true);
39
- });
40
-
41
- it('should call forward on RIGHT', () => {
42
- horizontal.onKeyDown(mockRIGHTEvent);
43
- expect(horizontal.forward).toHaveBeenCalled();
44
- });
45
-
46
- it('should call backward on LEFT', () => {
47
- horizontal.setIndex(1);
48
- horizontal.onKeyDown(mockLEFTEvent);
49
- expect(horizontal.backward).toHaveBeenCalled();
50
- });
51
-
52
- it('should call backward on RIGHT when RTL', () => {
53
- isRtlMock.mockReturnValueOnce(true);
54
- horizontal.setIndex(1);
55
- horizontal.onKeyDown(mockRIGHTEvent);
56
- expect(horizontal.backward).toHaveBeenCalled();
57
- });
58
-
59
- it('should call forward on LEFT when RTL', () => {
60
- isRtlMock.mockReturnValueOnce(true);
61
- horizontal.onKeyDown(mockLEFTEvent);
62
- expect(horizontal.forward).toHaveBeenCalled();
63
- });
64
-
65
- it('should call backward on RIGHT when forced RTL', () => {
66
- horizontal.props.dir = 'rtl';
67
- horizontal.setIndex(1);
68
- horizontal.onKeyDown(mockRIGHTEvent);
69
- expect(horizontal.backward).toHaveBeenCalled();
70
- });
71
-
72
- it('should call forward on LEFT when forced RTL', () => {
73
- horizontal.props.dir = 'rtl';
74
- horizontal.onKeyDown(mockLEFTEvent);
75
- expect(horizontal.forward).toHaveBeenCalled();
76
- });
77
-
78
- it('should call backward on LEFT when RTL and forced LTR', () => {
79
- isRtlMock.mockReturnValueOnce(true);
80
- horizontal.props.dir = 'ltr';
81
- horizontal.setIndex(1);
82
- horizontal.onKeyDown(mockLEFTEvent);
83
- expect(horizontal.backward).toHaveBeenCalled();
84
- });
85
-
86
- it('should call forward on RIGHT when RTL and forced LTR', () => {
87
- isRtlMock.mockReturnValueOnce(true);
88
- horizontal.props.dir = 'ltr';
89
- horizontal.onKeyDown(mockRIGHTEvent);
90
- expect(horizontal.forward).toHaveBeenCalled();
91
- });
92
- });
@@ -1,299 +0,0 @@
1
- import { createRef } from '@24i/bigscreen-sdk/jsx';
2
- import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
3
- import { LEFT, RIGHT, UP, DOWN, Key } from '@24i/bigscreen-sdk/device/keymap';
4
- import { Matrix, CircularDirection } from '../Matrix';
5
-
6
- jest.mock('@24i/bigscreen-sdk/utils/stopEvent');
7
-
8
- const simulateKey = (key: Key): KeyboardEvent => ({
9
- code: key.code,
10
- } as unknown as KeyboardEvent);
11
-
12
- const getFocusable = () => ({
13
- focus: jest.fn(),
14
- });
15
-
16
- const domRef = createRef(document.createElement('div'));
17
-
18
- const A1 = createRef(getFocusable());
19
- const A2 = createRef(getFocusable());
20
- const A3 = createRef(getFocusable());
21
- const B1 = createRef(getFocusable());
22
- const C1 = createRef(getFocusable());
23
- const C2 = createRef(getFocusable());
24
- const C3 = createRef(getFocusable());
25
- const D1 = createRef(getFocusable());
26
- const D2 = createRef(getFocusable());
27
-
28
- const MATRIX = [
29
- [A1, B1, C1, D1],
30
- [A2, B1, C2, C2],
31
- [A3, B1, C3, D2],
32
- ];
33
-
34
- describe('Matrix focus', () => {
35
- let matrix: Matrix;
36
-
37
- beforeEach(() => {
38
- matrix = new Matrix({ ...Matrix.defaultProps, matrix: MATRIX, domRef, children: [] });
39
- jest.clearAllMocks();
40
- });
41
-
42
- it('should have default index at 0, 0', () => {
43
- expect(matrix.index).toStrictEqual({ x: 0, y: 0 });
44
- });
45
-
46
- it('should go A1 -> RIGHT -> B1', () => {
47
- matrix.index = { x: 0, y: 0 };
48
- const event = simulateKey(RIGHT);
49
- matrix.onKeyDown(event);
50
- expect(B1.current!.focus).toHaveBeenCalled();
51
- expect(stopEvent).toHaveBeenCalledTimes(1);
52
- B1.current!.focus.mockClear();
53
- });
54
-
55
- it('should go A1 -> DOWN -> A2', () => {
56
- matrix.index = { x: 0, y: 0 };
57
- const event = simulateKey(DOWN);
58
- matrix.onKeyDown(event);
59
- expect(A2.current!.focus).toHaveBeenCalled();
60
- expect(stopEvent).toHaveBeenCalledTimes(1);
61
- A2.current!.focus.mockClear();
62
- });
63
-
64
- it('should go D2 -> LEFT -> C3', () => {
65
- matrix.index = { x: 3, y: 2 };
66
- const event = simulateKey(LEFT);
67
- matrix.onKeyDown(event);
68
- expect(C3.current!.focus).toHaveBeenCalled();
69
- expect(stopEvent).toHaveBeenCalledTimes(1);
70
- C3.current!.focus.mockClear();
71
- });
72
-
73
- it('should go D2 -> UP -> C2', () => {
74
- matrix.index = { x: 3, y: 2 };
75
- const event = simulateKey(UP);
76
- matrix.onKeyDown(event);
77
- expect(C2.current!.focus).toHaveBeenCalled();
78
- expect(stopEvent).toHaveBeenCalledTimes(1);
79
- C2.current!.focus.mockClear();
80
- });
81
-
82
- it('should not leave left boundary', () => {
83
- matrix.index = { x: 0, y: 0 };
84
- const event = simulateKey(LEFT);
85
- matrix.onKeyDown(event);
86
- expect(matrix.index).toStrictEqual({ x: 0, y: 0 });
87
- expect(stopEvent).not.toHaveBeenCalledTimes(1);
88
- });
89
-
90
- it('should not leave top boundary', () => {
91
- matrix.index = { x: 1, y: 2 };
92
- const event = simulateKey(UP);
93
- matrix.onKeyDown(event);
94
- expect(matrix.index).toStrictEqual({ x: 1, y: 2 });
95
- expect(stopEvent).not.toHaveBeenCalledTimes(1);
96
- });
97
-
98
- it('should not leave right boundary', () => {
99
- matrix.index = { x: 2, y: 1 };
100
- const event = simulateKey(RIGHT);
101
- matrix.onKeyDown(event);
102
- expect(matrix.index).toStrictEqual({ x: 2, y: 1 });
103
- expect(stopEvent).not.toHaveBeenCalledTimes(1);
104
- });
105
-
106
- it('should not leave bottom boundary', () => {
107
- matrix.index = { x: 3, y: 2 };
108
- const event = simulateKey(DOWN);
109
- matrix.onKeyDown(event);
110
- expect(matrix.index).toStrictEqual({ x: 3, y: 2 });
111
- expect(stopEvent).not.toHaveBeenCalledTimes(1);
112
- });
113
-
114
- it('should call stopEvent when on left and preventBlurOnLongPress is on', () => {
115
- matrix.props.preventBlurOnLongPress.left = true;
116
- matrix.index = { x: 1, y: 0 };
117
- const event = simulateKey(LEFT);
118
- matrix.onKeyDown(event);
119
- matrix.onKeyDown(event);
120
- matrix.onKeyDown(event);
121
- expect(matrix.index).toStrictEqual({ x: 0, y: 0 });
122
- expect(stopEvent).toHaveBeenCalledTimes(3);
123
- });
124
-
125
- it('should call stopEvent when on up and preventBlurOnLongPress is on', () => {
126
- matrix.props.preventBlurOnLongPress.up = true;
127
- matrix.index = { x: 0, y: 1 };
128
- const event = simulateKey(UP);
129
- matrix.onKeyDown(event);
130
- matrix.onKeyDown(event);
131
- matrix.onKeyDown(event);
132
- expect(matrix.index).toStrictEqual({ x: 0, y: 0 });
133
- expect(stopEvent).toHaveBeenCalledTimes(3);
134
- });
135
-
136
- it('should call stopEvent when on right and preventBlurOnLongPress is on', () => {
137
- matrix.props.preventBlurOnLongPress.right = true;
138
- matrix.index = { x: 2, y: 0 };
139
- const event = simulateKey(RIGHT);
140
- matrix.onKeyDown(event);
141
- matrix.onKeyDown(event);
142
- matrix.onKeyDown(event);
143
- expect(matrix.index).toStrictEqual({ x: 3, y: 0 });
144
- expect(stopEvent).toHaveBeenCalledTimes(3);
145
- });
146
-
147
- it('should call stopEvent when on down and preventBlurOnLongPress is on', () => {
148
- matrix.props.preventBlurOnLongPress.down = true;
149
- matrix.index = { x: 0, y: 1 };
150
- const event = simulateKey(DOWN);
151
- matrix.onKeyDown(event);
152
- matrix.onKeyDown(event);
153
- matrix.onKeyDown(event);
154
- expect(matrix.index).toStrictEqual({ x: 0, y: 2 });
155
- expect(stopEvent).toHaveBeenCalledTimes(3);
156
- });
157
-
158
- it('should keep row (Ax -> RIGHT -> B1 -> RIGHT -> Cx)', () => {
159
- const event = simulateKey(RIGHT);
160
- matrix.index = { x: 0, y: 0 };
161
- matrix.onKeyDown(event);
162
- matrix.onKeyDown(event);
163
- expect(C1.current!.focus).toHaveBeenCalled();
164
- matrix.index = { x: 0, y: 1 };
165
- matrix.onKeyDown(event);
166
- matrix.onKeyDown(event);
167
- expect(C2.current!.focus).toHaveBeenCalled();
168
- matrix.index = { x: 0, y: 2 };
169
- matrix.onKeyDown(event);
170
- matrix.onKeyDown(event);
171
- expect(C3.current!.focus).toHaveBeenCalled();
172
- C1.current!.focus.mockClear();
173
- C2.current!.focus.mockClear();
174
- C3.current!.focus.mockClear();
175
- });
176
-
177
- it('should keep column (C3/D2 -> UP -> C2 -> UP -> C1/D1)', () => {
178
- const event = simulateKey(UP);
179
- matrix.index = { x: 2, y: 2 };
180
- matrix.onKeyDown(event);
181
- matrix.onKeyDown(event);
182
- expect(C1.current!.focus).toHaveBeenCalled();
183
- matrix.index = { x: 3, y: 2 };
184
- matrix.onKeyDown(event);
185
- matrix.onKeyDown(event);
186
- expect(D1.current!.focus).toHaveBeenCalled();
187
- C1.current!.focus.mockClear();
188
- D1.current!.focus.mockClear();
189
- });
190
-
191
- it('should skip same and focus next (C2 -> LEFT -> C2 -> B1)', () => {
192
- matrix.index = { x: 3, y: 1 };
193
- matrix.onKeyDown(simulateKey(LEFT));
194
- expect(B1.current!.focus).toHaveBeenCalled();
195
- B1.current!.focus.mockClear();
196
- });
197
-
198
- describe('circular focus', () => {
199
- const commonProps = {
200
- ...Matrix.defaultProps,
201
- matrix: MATRIX,
202
- domRef,
203
- children: null as any,
204
- };
205
- it('should circle to the left (A1 -> left -> D1)', () => {
206
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.HORIZONTAL });
207
- matrix.index = { x: 0, y: 0 };
208
- const event = simulateKey(LEFT);
209
- matrix.onKeyDown(event);
210
- expect(D1.current!.focus).toHaveBeenCalled();
211
- expect(matrix.index).toEqual({ x: 3, y: 0 });
212
- D1.current!.focus.mockClear();
213
- });
214
-
215
- it('should circle to the right (A1 -> right -> D1)', () => {
216
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.HORIZONTAL });
217
- matrix.index = { x: 3, y: 0 };
218
- const event = simulateKey(RIGHT);
219
- matrix.onKeyDown(event);
220
- expect(A1.current!.focus).toHaveBeenCalled();
221
- expect(matrix.index).toEqual({ x: 0, y: 0 });
222
- A1.current!.focus.mockClear();
223
- });
224
-
225
- it('should circle to upwards (A1 -> up -> A3)', () => {
226
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.VERTICAL });
227
- matrix.index = { x: 0, y: 0 };
228
- const event = simulateKey(UP);
229
- matrix.onKeyDown(event);
230
- expect(A3.current!.focus).toHaveBeenCalled();
231
- expect(matrix.index).toEqual({ x: 0, y: 2 });
232
- A3.current!.focus.mockClear();
233
- });
234
-
235
- it('should circle to downwards (A3 -> down -> A1)', () => {
236
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.VERTICAL });
237
- matrix.index = { x: 0, y: 2 };
238
- const event = simulateKey(DOWN);
239
- matrix.onKeyDown(event);
240
- expect(A1.current!.focus).toHaveBeenCalled();
241
- expect(matrix.index).toEqual({ x: 0, y: 0 });
242
- A1.current!.focus.mockClear();
243
- });
244
-
245
- it('should circle horizontally and vertically (A3 -> down -> A1 -> left -> D1)', () => {
246
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.ALL });
247
- matrix.index = { x: 0, y: 2 };
248
- let event = simulateKey(DOWN);
249
- matrix.onKeyDown(event);
250
- expect(matrix.index).toEqual({ x: 0, y: 0 });
251
-
252
- event = simulateKey(LEFT);
253
- matrix.onKeyDown(event);
254
- expect(matrix.index).toEqual({ x: 3, y: 0 });
255
- A1.current!.focus.mockClear();
256
- D1.current!.focus.mockClear();
257
- });
258
-
259
- it('given vertical should not circle to horizontally (A1 -> left -> A1)', () => {
260
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.VERTICAL });
261
- matrix.index = { x: 0, y: 0 };
262
- const event = simulateKey(LEFT);
263
- matrix.onKeyDown(event);
264
- expect(matrix.index).toEqual({ x: 0, y: 0 });
265
- });
266
-
267
- it('given horizontal should not circle to vertically (A1 -> up -> A1)', () => {
268
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.HORIZONTAL });
269
- matrix.index = { x: 0, y: 0 };
270
- const event = simulateKey(UP);
271
- matrix.onKeyDown(event);
272
- expect(matrix.index).toEqual({ x: 0, y: 0 });
273
- });
274
-
275
- it('given 1 vertical key should not loop infinitely', () => {
276
- matrix = new Matrix({ ...commonProps, circular: CircularDirection.VERTICAL });
277
- matrix.index = { x: 1, y: 0 };
278
- const event = simulateKey(UP);
279
- matrix.onKeyDown(event);
280
- expect(matrix.index).toEqual({ x: 1, y: 0 });
281
- });
282
-
283
- it('given 1 horizontal key should not loop infinitely', () => {
284
- const keyMatrix = [
285
- [A1, B1, C1, D1],
286
- [C2, C2, C2, C2],
287
- ];
288
- matrix = new Matrix({
289
- ...commonProps,
290
- matrix: keyMatrix,
291
- circular: CircularDirection.VERTICAL,
292
- });
293
- matrix.index = { x: 0, y: 1 };
294
- const event = simulateKey(LEFT);
295
- matrix.onKeyDown(event);
296
- expect(matrix.index).toEqual({ x: 0, y: 1 });
297
- });
298
- });
299
- });
@@ -1,44 +0,0 @@
1
- import { Vertical } from '../Vertical';
2
- import { rootRef, refChildA, refChildB, refChildC } from './shared';
3
-
4
- const mockEvent = {
5
- target: false,
6
- };
7
- const mockUPEvent = {
8
- code: 'ArrowUp',
9
- ...mockEvent,
10
- };
11
- const mockDOWNEvent = {
12
- code: 'ArrowDown',
13
- ...mockEvent,
14
- };
15
-
16
- describe('Vertical Focus Layout', () => {
17
- let vertical: any;
18
- beforeEach(() => {
19
- vertical = new Vertical({
20
- domRef: rootRef,
21
- children: 'children',
22
- focusableChildren: [refChildA, refChildB, refChildC],
23
- onFocusChanged: jest.fn(),
24
- onBeforeFocusChange: jest.fn(),
25
- });
26
- vertical.forward = jest.fn();
27
- vertical.backward = jest.fn();
28
- });
29
-
30
- it('should be a class component', () => {
31
- expect(Vertical.isClass).toBe(true);
32
- });
33
-
34
- it('should call forward on DOWN', () => {
35
- vertical.onKeyDown(mockDOWNEvent);
36
- expect(vertical.forward).toHaveBeenCalled();
37
- });
38
-
39
- it('should call forward on UP', () => {
40
- vertical.setIndex(1);
41
- vertical.onKeyDown(mockUPEvent);
42
- expect(vertical.backward).toHaveBeenCalled();
43
- });
44
- });
@@ -1,23 +0,0 @@
1
- import * as i18n from '@24i/bigscreen-sdk/i18n';
2
- import { isRtl } from '../isRtl';
3
- import { SharedProps } from '../types';
4
-
5
- describe('isRtl', () => {
6
- const mockIsRtl = jest.spyOn(i18n, 'isRtl');
7
-
8
- it.each([
9
- { focusDir: 'ltr', langDir: 'ltr', expectedIsRtl: false },
10
- { focusDir: 'rtl', langDir: 'ltr', expectedIsRtl: true },
11
- { focusDir: 'auto', langDir: 'ltr', expectedIsRtl: false },
12
- { focusDir: undefined, langDir: 'ltr', expectedIsRtl: false },
13
- { focusDir: 'ltr', langDir: 'rtl', expectedIsRtl: false },
14
- { focusDir: 'rtl', langDir: 'rtl', expectedIsRtl: true },
15
- { focusDir: 'auto', langDir: 'rtl', expectedIsRtl: true },
16
- { focusDir: undefined, langDir: 'rtl', expectedIsRtl: true },
17
- ])('should for $focusDir focus and $langDir language return $expectedIsRtl', ({
18
- focusDir, langDir, expectedIsRtl,
19
- }) => {
20
- mockIsRtl.mockReturnValue(langDir === 'rtl');
21
- expect(isRtl(focusDir as SharedProps['dir'])).toBe(expectedIsRtl);
22
- });
23
- });
@@ -1,19 +0,0 @@
1
- import { createRef } from '@24i/bigscreen-sdk/jsx';
2
- import { IFocusable } from '../../IFocusable';
3
-
4
- class ChildMock {
5
- focus = jest.fn();
6
- }
7
-
8
- const root = document.createElement('div');
9
- export const rootRef = createRef(root);
10
-
11
- const childA = new ChildMock();
12
- const childB = new ChildMock();
13
- const childC = new ChildMock();
14
- const childD = new ChildMock();
15
- (childD as IFocusable).hasDom = jest.fn().mockReturnValue(false);
16
- export const refChildA = createRef(childA);
17
- export const refChildB = createRef(childB);
18
- export const refChildC = createRef(childC);
19
- export const refChildD = createRef(childD);