@24i/bigscreen-sdk 1.0.6-alpha.2139 → 1.0.6-alpha.2143

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/bigscreen-sdk",
3
- "version": "1.0.6-alpha.2139",
3
+ "version": "1.0.6-alpha.2143",
4
4
  "description": "SmartApps BIGscreen SDK monorepo",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,6 +35,12 @@ type Props = {
35
35
  profileName: string,
36
36
  /** Version of application. */
37
37
  appVersion: string,
38
+ /** Version of Bigscreen SDK currently used. */
39
+ sdkVersion: string,
40
+ /** Version of Bigscreen WL currently used. */
41
+ wlVersion: string,
42
+ /** Version of Bigscreen DataLayer currently used. */
43
+ dlVersion: string,
38
44
  /** App resolution, in format like "1280x720". */
39
45
  appResolution: string,
40
46
  /** OVP/CMS back-end base URL. */
@@ -263,6 +269,21 @@ class TechnicalInfoProvider implements ITechnicalInfoProvider {
263
269
  return this.props.backendBaseUrl || unknown;
264
270
  }
265
271
 
272
+ async getSdkVersion() {
273
+ const { unknown } = this.translations;
274
+ return this.props.sdkVersion || unknown;
275
+ }
276
+
277
+ async getWlVersion() {
278
+ const { unknown } = this.translations;
279
+ return this.props.wlVersion || unknown;
280
+ }
281
+
282
+ async getDlVersion() {
283
+ const { unknown } = this.translations;
284
+ return this.props.dlVersion || unknown;
285
+ }
286
+
266
287
  getSourceDataLayout() {
267
288
  const {
268
289
  username, profile, appVersion, firmwareVersion, deviceModel,
@@ -270,11 +291,15 @@ class TechnicalInfoProvider implements ITechnicalInfoProvider {
270
291
  publicIpAddress, connectionType, macAddress, memoryHeapSize,
271
292
  usedLocalStorage, timeZone, dateAndTime, flavour, userAgent,
272
293
  emeSupport, emeDrmSystems, mseSupport, fourKSupport, resolution, baseUrl,
294
+ sdkVersion, wlVersion, dlVersion,
273
295
  } = this.translations;
274
296
  const dataLayout: SourceDataLayout = [
275
297
  { label: username, getter: this.getUserName },
276
298
  { label: profile, getter: this.getProfileName },
277
299
  { label: appVersion, getter: this.getAppVersion },
300
+ { label: sdkVersion, getter: this.getSdkVersion },
301
+ { label: wlVersion, getter: this.getWlVersion },
302
+ { label: dlVersion, getter: this.getDlVersion },
278
303
  { label: firmwareVersion, getter: this.getFirmwareVersion },
279
304
  { label: deviceModel, getter: this.getDeviceModelName },
280
305
  { label: deviceManufacturer, getter: this.getDeviceManufacturer },
@@ -35,6 +35,9 @@ export type TranslationKeys = {
35
35
  fourKSupport: string,
36
36
  resolution: string,
37
37
  baseUrl: string,
38
+ sdkVersion: string,
39
+ wlVersion: string,
40
+ dlVersion: string,
38
41
  };
39
42
 
40
43
  export const defaultTranslations = {
@@ -72,4 +75,7 @@ export const defaultTranslations = {
72
75
  fourKSupport: '4K support',
73
76
  resolution: 'Resolution',
74
77
  baseUrl: 'Base URL',
78
+ sdkVersion: 'SDK version',
79
+ wlVersion: 'Whitelabel version',
80
+ dlVersion: 'DataLayer version',
75
81
  };
@@ -128,6 +128,16 @@ const matrix = [
128
128
  ];
129
129
  ```
130
130
 
131
+ ## Prevent blur on long press
132
+ When the user holds a navigation key, he usually scrolls to the boundary of the layout
133
+ and then releases the key. This operation is very quick and most of the time he releases
134
+ the key too late, so the keydown event from the layout will propagate to another component
135
+ or another layout. In some cases, we don't want this behavior and for those situations and
136
+ to have better control above the layout navigation, there is a prop `preventBlurOnLongPress`.
137
+ When the user scrolls to the boundary of the layout while holding a navigation key,
138
+ he needs to release the key and press it again to propagate the event higher and focus
139
+ another component or layout.
140
+
131
141
  ## Limitations
132
142
  For the div to properly receive keydown events it needs to have set `tabIndex`. If you do not set `tabIndex` then it won't receive keydown events and those will not propagate to the Vertical/Horizontal component.
133
143
 
@@ -1,14 +1,21 @@
1
1
  import { Component, DeclareProps, Reference } from '@24i/bigscreen-sdk/jsx';
2
2
  import { noop } from '@24i/bigscreen-sdk/utils/noop';
3
+ import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
3
4
  import { IFocusable } from '../IFocusable';
4
5
  import { Errors, hasValidDomRef } from './constants';
5
6
  import { SharedProps } from './types';
6
7
 
8
+ const PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS = 2;
9
+
7
10
  export type Props = SharedProps & {
8
11
  focusOptions?: FocusOptions;
9
12
  focusableChildren: Array<Reference>,
10
13
  onFocusChanged?: (focused: Reference, index: number) => any,
11
14
  onBeforeFocusChange?: (focused: Reference, index: number) => any,
15
+ preventBlurOnLongPress?: {
16
+ forward?: boolean,
17
+ backward?: boolean,
18
+ }
12
19
  };
13
20
 
14
21
  export interface ILayout {
@@ -25,12 +32,15 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
25
32
  class Layout extends Component<Props> implements ILayout, IFocusable {
26
33
  index = 0;
27
34
 
35
+ preventBlurCounter = 0;
36
+
28
37
  static defaultProps = {
29
38
  onFocusChanged: noop,
30
39
  onBeforeFocusChange: noop,
31
40
  focusOptions: { preventScroll: true },
32
41
  dir: 'auto',
33
- };
42
+ preventBlurOnLongPress: {},
43
+ } as const;
34
44
 
35
45
  declare props: DeclareProps<Props, typeof Layout.defaultProps>;
36
46
 
@@ -40,6 +50,7 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
40
50
  throw new Error(Errors.DOM_REF_ERROR);
41
51
  }
42
52
  domRef.current!.addEventListener('keydown', this.onKeyDown, false);
53
+ domRef.current!.addEventListener('keyup', this.onKeyUp, false);
43
54
  }
44
55
 
45
56
  componentWillUnmount() {
@@ -48,12 +59,17 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
48
59
  throw new Error(Errors.DOM_REF_ERROR);
49
60
  }
50
61
  domRef.current!.removeEventListener('keydown', this.onKeyDown, false);
62
+ domRef.current!.removeEventListener('keyup', this.onKeyUp, false);
51
63
  }
52
64
 
53
65
  onKeyDown = (e: KeyboardEvent) => {
54
66
  onKeyDown(e, this);
55
67
  };
56
68
 
69
+ onKeyUp = () => {
70
+ this.preventBlurCounter = 0;
71
+ };
72
+
57
73
  getFromIndex(target: HTMLElement) {
58
74
  const { focusableChildren } = this.props;
59
75
  if (
@@ -78,25 +94,34 @@ export const getLayoutBase = (onKeyDown: (event: KeyboardEvent, layout: ILayout)
78
94
  }
79
95
 
80
96
  backward(e: KeyboardEvent) {
97
+ const { preventBlurOnLongPress } = this.props;
81
98
  const { target } = e;
82
99
  const fromIndex = this.getFromIndex(target as HTMLElement);
100
+ if (preventBlurOnLongPress.backward) {
101
+ this.preventBlurCounter += 1;
102
+ }
83
103
  if (fromIndex > 0) {
84
104
  this.setIndex(fromIndex - 1);
85
105
  this.focusCurrentIndex();
86
- e.preventDefault();
87
- e.stopPropagation();
106
+ stopEvent(e);
107
+ } else if (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS) {
108
+ stopEvent(e);
88
109
  }
89
110
  }
90
111
 
91
112
  forward(e: KeyboardEvent) {
92
- const { focusableChildren } = this.props;
113
+ const { focusableChildren, preventBlurOnLongPress } = this.props;
93
114
  const { target } = e;
94
115
  const fromIndex = this.getFromIndex(target as HTMLElement);
116
+ if (preventBlurOnLongPress.forward) {
117
+ this.preventBlurCounter += 1;
118
+ }
95
119
  if (fromIndex < focusableChildren.length - 1) {
96
120
  this.setIndex(fromIndex + 1);
97
121
  this.focusCurrentIndex();
98
- e.preventDefault();
99
- e.stopPropagation();
122
+ stopEvent(e);
123
+ } else if (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS) {
124
+ stopEvent(e);
100
125
  }
101
126
  }
102
127
 
@@ -8,6 +8,8 @@ import { isRtl } from './isRtl';
8
8
  import { Errors, hasValidDomRef } from './constants';
9
9
  import { SharedProps } from './types';
10
10
 
11
+ const PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS = 2;
12
+
11
13
  // Used in binary operations for shorter checks
12
14
  export enum CircularDirection {
13
15
  NONE = 0,
@@ -21,7 +23,13 @@ export type Props = SharedProps & {
21
23
  matrix: Array<Array<Reference>>,
22
24
  onFocusChanged?: (focused: Reference, index: Index2D) => any,
23
25
  onBeforeFocusChange?: (focused: Reference, index: Index2D) => any,
24
- circular?: CircularDirection;
26
+ circular?: CircularDirection,
27
+ preventBlurOnLongPress?: {
28
+ left?: boolean,
29
+ right?: boolean,
30
+ up?: boolean,
31
+ down?: boolean,
32
+ },
25
33
  };
26
34
 
27
35
  export type Index2D = {
@@ -32,11 +40,14 @@ export type Index2D = {
32
40
  export class Matrix extends Component<Props> implements IFocusable {
33
41
  index: Index2D = { x: 0, y: 0 };
34
42
 
43
+ preventBlurCounter = 0;
44
+
35
45
  static defaultProps = {
36
46
  onFocusChanged: noop,
37
47
  onBeforeFocusChange: noop,
38
48
  circular: CircularDirection.NONE,
39
49
  dir: 'auto',
50
+ preventBlurOnLongPress: {},
40
51
  } as const;
41
52
 
42
53
  declare props: DeclareProps<Props, typeof Matrix.defaultProps>;
@@ -47,6 +58,7 @@ export class Matrix extends Component<Props> implements IFocusable {
47
58
  throw new Error(Errors.DOM_REF_ERROR);
48
59
  }
49
60
  domRef.current!.addEventListener('keydown', this.onKeyDown, false);
61
+ domRef.current!.addEventListener('keyup', this.onKeyUp, false);
50
62
  }
51
63
 
52
64
  componentWillUnmount() {
@@ -55,26 +67,46 @@ export class Matrix extends Component<Props> implements IFocusable {
55
67
  throw new Error(Errors.DOM_REF_ERROR);
56
68
  }
57
69
  domRef.current!.removeEventListener('keydown', this.onKeyDown, false);
70
+ domRef.current!.removeEventListener('keyup', this.onKeyUp, false);
58
71
  }
59
72
 
60
73
  onKeyDown = (e: KeyboardEvent) => {
74
+ const { preventBlurOnLongPress } = this.props;
61
75
  let newIndex: Index2D | null = null;
62
76
  if (this.isDirectionLeft(e)) {
63
77
  newIndex = this.moveHorizontaly('left');
78
+ if (preventBlurOnLongPress.left) {
79
+ this.preventBlurCounter += 1;
80
+ }
64
81
  } else if (this.isDirectionRight(e)) {
65
82
  newIndex = this.moveHorizontaly('right');
83
+ if (preventBlurOnLongPress.right) {
84
+ this.preventBlurCounter += 1;
85
+ }
66
86
  } else if (device.isDirectionUp(e)) {
67
87
  newIndex = this.moveVerticaly('up');
88
+ if (preventBlurOnLongPress.up) {
89
+ this.preventBlurCounter += 1;
90
+ }
68
91
  } else if (device.isDirectionDown(e)) {
69
92
  newIndex = this.moveVerticaly('down');
93
+ if (preventBlurOnLongPress.down) {
94
+ this.preventBlurCounter += 1;
95
+ }
70
96
  }
71
97
  if (newIndex) {
72
98
  stopEvent(e);
73
99
  this.index = newIndex;
74
100
  this.focusCurrentIndex();
101
+ } else if (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS) {
102
+ stopEvent(e);
75
103
  }
76
104
  };
77
105
 
106
+ onKeyUp = () => {
107
+ this.preventBlurCounter = 0;
108
+ };
109
+
78
110
  getRefAt({ x, y }: Index2D): Reference {
79
111
  const { matrix } = this.props;
80
112
  if (matrix[y] && matrix[y][x]) return matrix[y][x];
@@ -1,12 +1,13 @@
1
+ import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
1
2
  import { IFocusable } from '../../IFocusable';
2
3
  import { getLayoutBase } from '../Base';
3
4
  import { rootRef, refChildA, refChildB, refChildC, refChildD } from './shared';
4
5
 
6
+ jest.mock('@24i/bigscreen-sdk/utils/stopEvent');
7
+
5
8
  const mockKeydown = jest.fn();
6
9
  const mockEvent: any = {
7
10
  target: null,
8
- preventDefault: jest.fn(),
9
- stopPropagation: jest.fn(),
10
11
  };
11
12
 
12
13
  const Base = getLayoutBase(mockKeydown);
@@ -15,18 +16,14 @@ describe('Focus Layout Base', () => {
15
16
  let base: any;
16
17
  beforeEach(() => {
17
18
  base = new Base({
19
+ ...Base.defaultProps,
18
20
  domRef: rootRef,
19
21
  children: 'children',
20
22
  focusableChildren: [refChildA, refChildB, refChildC],
21
23
  onFocusChanged: jest.fn(),
22
24
  onBeforeFocusChange: jest.fn(),
23
25
  });
24
- mockKeydown.mockClear();
25
- refChildA.current!.focus.mockClear();
26
- refChildB.current!.focus.mockClear();
27
- refChildC.current!.focus.mockClear();
28
- mockEvent.preventDefault.mockClear();
29
- mockEvent.stopPropagation.mockClear();
26
+ jest.clearAllMocks();
30
27
  });
31
28
 
32
29
  it('should throw without domRef on componentDidMount', () => {
@@ -108,30 +105,43 @@ describe('Focus Layout Base', () => {
108
105
  expect(refChildA.current!.focus).not.toHaveBeenCalled();
109
106
  });
110
107
 
111
- it('should call preventDefault and stopPropagation when going forward', () => {
108
+ it('should call stopEvent when going forward', () => {
112
109
  base.forward(mockEvent);
113
- expect(mockEvent.preventDefault).toHaveBeenCalled();
114
- expect(mockEvent.stopPropagation).toHaveBeenCalled();
110
+ expect(stopEvent).toHaveBeenCalledTimes(1);
115
111
  });
116
112
 
117
- it('should call preventDefault and stopPropagation when going backward', () => {
113
+ it('should call stopEvent when going backward', () => {
118
114
  base.index = 1;
119
115
  base.backward(mockEvent);
120
- expect(mockEvent.preventDefault).toHaveBeenCalled();
121
- expect(mockEvent.stopPropagation).toHaveBeenCalled();
116
+ expect(stopEvent).toHaveBeenCalledTimes(1);
122
117
  });
123
118
 
124
- it('should not call preventDefault and stopPropagation when can\'t go forward', () => {
119
+ it('should not call stopEvent when can\'t go forward', () => {
125
120
  base.index = 2;
126
121
  base.forward(mockEvent);
127
- expect(mockEvent.preventDefault).not.toHaveBeenCalled();
128
- expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
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);
129
136
  });
130
137
 
131
- it('should not call preventDefault and stopPropagation when can\'t go backward', () => {
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);
132
143
  base.backward(mockEvent);
133
- expect(mockEvent.preventDefault).not.toHaveBeenCalled();
134
- expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
144
+ expect(stopEvent).toHaveBeenCalledTimes(3);
135
145
  });
136
146
 
137
147
  it('should call onFocusChanged when focusing current index', () => {
@@ -163,6 +173,7 @@ describe('Focus Layout Base', () => {
163
173
 
164
174
  it('should take result of hasDom into account of determining current focus position', () => {
165
175
  base = new Base({
176
+ ...Base.defaultProps,
166
177
  domRef: rootRef,
167
178
  children: 'children',
168
179
  focusableChildren: [refChildA, refChildB, refChildC, refChildD],
@@ -10,8 +10,6 @@ const isRtlMock = isRtl as jest.MockedFn<typeof isRtl>;
10
10
 
11
11
  const mockEvent = {
12
12
  target: false,
13
- preventDefault: jest.fn(),
14
- stopPropagation: jest.fn(),
15
13
  };
16
14
  const mockLEFTEvent = {
17
15
  code: 'ArrowLeft',
@@ -1,11 +1,12 @@
1
1
  import { createRef } from '@24i/bigscreen-sdk/jsx';
2
+ import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
2
3
  import { LEFT, RIGHT, UP, DOWN, Key } from '@24i/bigscreen-sdk/device/keymap';
3
4
  import { Matrix, CircularDirection } from '../Matrix';
4
5
 
6
+ jest.mock('@24i/bigscreen-sdk/utils/stopEvent');
7
+
5
8
  const simulateKey = (key: Key): KeyboardEvent => ({
6
9
  code: key.code,
7
- preventDefault: jest.fn(),
8
- stopPropagation: jest.fn(),
9
10
  } as unknown as KeyboardEvent);
10
11
 
11
12
  const getFocusable = () => ({
@@ -35,6 +36,7 @@ describe('Matrix focus', () => {
35
36
 
36
37
  beforeEach(() => {
37
38
  matrix = new Matrix({ ...Matrix.defaultProps, matrix: MATRIX, domRef, children: [] });
39
+ jest.clearAllMocks();
38
40
  });
39
41
 
40
42
  it('should have default index at 0, 0', () => {
@@ -46,8 +48,7 @@ describe('Matrix focus', () => {
46
48
  const event = simulateKey(RIGHT);
47
49
  matrix.onKeyDown(event);
48
50
  expect(B1.current!.focus).toHaveBeenCalled();
49
- expect(event.preventDefault).toHaveBeenCalled();
50
- expect(event.stopPropagation).toHaveBeenCalled();
51
+ expect(stopEvent).toHaveBeenCalledTimes(1);
51
52
  B1.current!.focus.mockClear();
52
53
  });
53
54
 
@@ -56,8 +57,7 @@ describe('Matrix focus', () => {
56
57
  const event = simulateKey(DOWN);
57
58
  matrix.onKeyDown(event);
58
59
  expect(A2.current!.focus).toHaveBeenCalled();
59
- expect(event.preventDefault).toHaveBeenCalled();
60
- expect(event.stopPropagation).toHaveBeenCalled();
60
+ expect(stopEvent).toHaveBeenCalledTimes(1);
61
61
  A2.current!.focus.mockClear();
62
62
  });
63
63
 
@@ -66,8 +66,7 @@ describe('Matrix focus', () => {
66
66
  const event = simulateKey(LEFT);
67
67
  matrix.onKeyDown(event);
68
68
  expect(C3.current!.focus).toHaveBeenCalled();
69
- expect(event.preventDefault).toHaveBeenCalled();
70
- expect(event.stopPropagation).toHaveBeenCalled();
69
+ expect(stopEvent).toHaveBeenCalledTimes(1);
71
70
  C3.current!.focus.mockClear();
72
71
  });
73
72
 
@@ -76,8 +75,7 @@ describe('Matrix focus', () => {
76
75
  const event = simulateKey(UP);
77
76
  matrix.onKeyDown(event);
78
77
  expect(C2.current!.focus).toHaveBeenCalled();
79
- expect(event.preventDefault).toHaveBeenCalled();
80
- expect(event.stopPropagation).toHaveBeenCalled();
78
+ expect(stopEvent).toHaveBeenCalledTimes(1);
81
79
  C2.current!.focus.mockClear();
82
80
  });
83
81
 
@@ -86,8 +84,7 @@ describe('Matrix focus', () => {
86
84
  const event = simulateKey(LEFT);
87
85
  matrix.onKeyDown(event);
88
86
  expect(matrix.index).toStrictEqual({ x: 0, y: 0 });
89
- expect(event.preventDefault).not.toHaveBeenCalled();
90
- expect(event.stopPropagation).not.toHaveBeenCalled();
87
+ expect(stopEvent).not.toHaveBeenCalledTimes(1);
91
88
  });
92
89
 
93
90
  it('should not leave top boundary', () => {
@@ -95,8 +92,7 @@ describe('Matrix focus', () => {
95
92
  const event = simulateKey(UP);
96
93
  matrix.onKeyDown(event);
97
94
  expect(matrix.index).toStrictEqual({ x: 1, y: 2 });
98
- expect(event.preventDefault).not.toHaveBeenCalled();
99
- expect(event.stopPropagation).not.toHaveBeenCalled();
95
+ expect(stopEvent).not.toHaveBeenCalledTimes(1);
100
96
  });
101
97
 
102
98
  it('should not leave right boundary', () => {
@@ -104,8 +100,7 @@ describe('Matrix focus', () => {
104
100
  const event = simulateKey(RIGHT);
105
101
  matrix.onKeyDown(event);
106
102
  expect(matrix.index).toStrictEqual({ x: 2, y: 1 });
107
- expect(event.preventDefault).not.toHaveBeenCalled();
108
- expect(event.stopPropagation).not.toHaveBeenCalled();
103
+ expect(stopEvent).not.toHaveBeenCalledTimes(1);
109
104
  });
110
105
 
111
106
  it('should not leave bottom boundary', () => {
@@ -113,8 +108,51 @@ describe('Matrix focus', () => {
113
108
  const event = simulateKey(DOWN);
114
109
  matrix.onKeyDown(event);
115
110
  expect(matrix.index).toStrictEqual({ x: 3, y: 2 });
116
- expect(event.preventDefault).not.toHaveBeenCalled();
117
- expect(event.stopPropagation).not.toHaveBeenCalled();
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);
118
156
  });
119
157
 
120
158
  it('should keep row (Ax -> RIGHT -> B1 -> RIGHT -> Cx)', () => {
@@ -3,8 +3,6 @@ import { rootRef, refChildA, refChildB, refChildC } from './shared';
3
3
 
4
4
  const mockEvent = {
5
5
  target: false,
6
- preventDefault: jest.fn(),
7
- stopPropagation: jest.fn(),
8
6
  };
9
7
  const mockUPEvent = {
10
8
  code: 'ArrowUp',
@@ -0,0 +1,101 @@
1
+ import { processYear, dateFormatter, toNumericLocaleTimeString } from './common';
2
+ import { DateTimeFormatOptions, Day, Month, Weekday } from './types';
3
+
4
+ const HE_MONTHS = {
5
+ narrow: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
6
+ short: [
7
+ 'ינו׳',
8
+ 'פבר׳',
9
+ 'מרץ',
10
+ 'אפר׳',
11
+ 'מאי',
12
+ 'יוני',
13
+ 'יולי',
14
+ 'אוג׳',
15
+ 'ספט׳',
16
+ 'אוק׳',
17
+ 'נוב׳',
18
+ 'דצמ׳',
19
+ ],
20
+ long: [
21
+ 'ינואר',
22
+ 'פברואר',
23
+ 'מרץ',
24
+ 'אפריל',
25
+ 'מאי',
26
+ 'יוני',
27
+ 'יולי',
28
+ 'אוגוסט',
29
+ 'ספטמבר',
30
+ 'אוקטובר',
31
+ 'נובמבר',
32
+ 'דצמבר',
33
+ ],
34
+ };
35
+
36
+ const HE_WEEKDAYS = {
37
+ narrow: ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'],
38
+ short: ['יום א׳', 'יום ב׳', 'יום ג׳', 'יום ד׳', 'יום ה׳', 'יום ו׳', 'שבת'],
39
+ long: ['יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'יום שבת'],
40
+ };
41
+
42
+ const processWeekday = (
43
+ dateStringParts: string[],
44
+ weekday: Weekday | undefined,
45
+ date: Date,
46
+ ) => {
47
+ if (typeof weekday === 'undefined') return;
48
+ const weekdayString = HE_WEEKDAYS[weekday][date.getDay()];
49
+ dateStringParts.push(`${weekdayString},`);
50
+ };
51
+
52
+ const processDay = (dateParts: string[], day: Day | undefined, date: Date) => {
53
+ if (typeof day === 'undefined') return;
54
+ const strDate = date.getDate().toString();
55
+ dateParts.push(strDate);
56
+ };
57
+
58
+ const processMonth = (
59
+ dateParts: string[],
60
+ day:Day | undefined,
61
+ month: Month | undefined,
62
+ date: Date,
63
+ ) => {
64
+ if (typeof month === 'undefined') return;
65
+ if (month === '2-digit' || month === 'numeric') {
66
+ const strMonth = (date.getMonth() + 1).toString();
67
+ dateParts.push(strMonth);
68
+ return;
69
+ }
70
+ const finalMonth = HE_MONTHS[month][date.getMonth()];
71
+ if (day != null) {
72
+ dateParts.push(`ב${finalMonth}`);
73
+ return;
74
+ }
75
+ dateParts.push(finalMonth);
76
+ };
77
+
78
+ const toLocaleDateString = (date: Date) => (
79
+ { weekday, year, month, day }: DateTimeFormatOptions = {
80
+ year: 'numeric', month: '2-digit', day: '2-digit',
81
+ },
82
+ ) => {
83
+ const dateStringParts: string[] = [];
84
+ processWeekday(dateStringParts, weekday, date);
85
+ const dateConnection = month === 'numeric' || month === '2-digit' ? '.' : '';
86
+ const dateParts: string[] = [];
87
+ processDay(dateParts, day, date);
88
+ if ((month === 'narrow' ||
89
+ month === 'short' ||
90
+ month === 'long') &&
91
+ day != null
92
+ ) {
93
+ dateParts.push(' ');
94
+ }
95
+ processMonth(dateParts, day, month, date);
96
+ if (month === 'long') dateParts.push(' ');
97
+ processYear(dateParts, year, date);
98
+ dateStringParts.push(dateParts.join(dateConnection));
99
+ return dateStringParts.join(' ');
100
+ };
101
+ export const heDateFormatter = dateFormatter(toNumericLocaleTimeString, toLocaleDateString);
@@ -3,6 +3,7 @@ import { csDateFormatter } from './date/cs';
3
3
  import { deDateFormatter } from './date/de';
4
4
  import { esDateFormatter } from './date/es';
5
5
  import { frDateFormatter } from './date/fr';
6
+ import { heDateFormatter } from './date/he';
6
7
  import { isoDateFormatter } from './date/iso';
7
8
  import { Formatters } from './types';
8
9
 
@@ -27,6 +28,7 @@ class L10n implements IEvents<Events> {
27
28
  fr: frDateFormatter,
28
29
  es: esDateFormatter,
29
30
  de: deDateFormatter,
31
+ he: heDateFormatter,
30
32
  },
31
33
  };
32
34