@playkit-js/playkit-js-ui 0.83.9 → 0.83.10

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.
@@ -1,336 +1,352 @@
1
- import style from '../../styles/style.scss';
2
- import {h, Component, toChildArray, cloneElement, VNode} from 'preact';
3
- import {connect} from 'react-redux';
4
- import {withEventManager} from '../../event';
5
- import {WithEventManagerProps} from '../../event/with-event-manager';
6
- import {KeyMap} from '../../utils/key-map';
7
-
8
- interface ReduxStateProps {
9
- playerClientRect?: DOMRect;
10
- guiClientRect?: DOMRect;
11
- isMobile?: boolean;
12
- }
13
-
14
- type ToolTipPosition = 'top' | 'bottom' | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'left' | 'right';
15
-
16
- interface TooltipOwnProps {
17
- type?: ToolTipPosition;
18
- maxWidth?: string;
19
- label: string;
20
- strictPosition?: boolean;
21
- className?: string;
22
- }
23
-
24
- type TooltipProps = ReduxStateProps & TooltipOwnProps;
25
-
26
- const PLAYER_MARGIN = 5;
27
-
28
- /**
29
- * mapping state to props
30
- * @param {*} state - redux store state
31
- * @returns {Object} - mapped state to this component
32
- */
33
- const mapStateToProps = state => ({
34
- playerClientRect: state.shell.playerClientRect,
35
- guiClientRect: state.shell.guiClientRect,
36
- isMobile: state.shell.isMobile
37
- });
38
-
39
- const TOOLTIP_SHOW_TIMEOUT: number = 750;
40
-
41
- // notice the order represents the order of the alternative fallback
42
- const ToolTipType: {[type: string]: ToolTipPosition} = {
43
- Top: 'top',
44
- Bottom: 'bottom',
45
- TopRight: 'top-right',
46
- TopLeft: 'top-left',
47
- BottomRight: 'bottom-right',
48
- BottomLeft: 'bottom-left',
49
- Left: 'left',
50
- Right: 'right'
51
- };
52
-
53
- /**
54
- * Tooltip component
55
- *
56
- * @class Tooltip
57
- * @example <Tooltip>...</Tooltip>
58
- * @extends {Component}
59
- */
60
- @connect(mapStateToProps)
61
- @withEventManager
62
- class Tooltip extends Component<TooltipProps & WithEventManagerProps, any> {
63
- _hoverTimeout: number | null = null;
64
- textElement!: HTMLSpanElement;
65
- tooltipElement!: HTMLDivElement;
66
- lastAlternativeTypeIndex: number = -1;
67
- _buttonRef: HTMLButtonElement | null = null;
68
-
69
- /**
70
- * default component props
71
- * @type {Object}
72
- * @memberof Tooltip
73
- */
74
- static defaultProps = {
75
- type: ToolTipType.Top,
76
- maxWidth: '240px',
77
- strictPosition: false
78
- };
79
-
80
- /**
81
- * clear hover timeout
82
- *
83
- * @returns {void}
84
- * @memberof Tooltip
85
- */
86
- _clearHoverTimeout(): void {
87
- if (this._hoverTimeout) {
88
- clearTimeout(this._hoverTimeout);
89
- this._hoverTimeout = null;
90
- }
91
- }
92
-
93
- /**
94
- * displays tooltip.
95
- * @memberof Tooltip
96
- * @returns {void}
97
- */
98
- showTooltip = (): void => {
99
- this.setState({showTooltip: true});
100
- };
101
-
102
- /**
103
- * hide tooltip.
104
- * @memberof Tooltip
105
- * @returns {void}
106
- */
107
- hideTooltip = (): void => {
108
- this.setState({showTooltip: false});
109
- };
110
-
111
- /**
112
- * handle keyDown
113
- * @memberof Tooltip
114
- * @returns {void}
115
- */
116
- handleKeyDown = (event: KeyboardEvent): void => {
117
- if (event.keyCode === KeyMap.ESC) {
118
- this.hideTooltip();
119
- }
120
- };
121
-
122
- /**
123
- * set button ref
124
- * @memberof Tooltip
125
- * @returns {void}
126
- */
127
- setButtonRef = (element: HTMLButtonElement | null) => {
128
- this._buttonRef = element;
129
- };
130
-
131
- /**
132
- * handle focus on wrapped element
133
- * @memberof Tooltip
134
- * @returns {void}
135
- */
136
- handleFocusOnChildren = (): void => {
137
- const {onFocus} = (this.props.children as VNode<any>).props;
138
- this.showTooltip();
139
- if (onFocus) {
140
- onFocus();
141
- }
142
- };
143
-
144
- /**
145
- * handle blur on wrapped element
146
- * @memberof Tooltip
147
- * @returns {void}
148
- */
149
- handleBlurOnChildren = (): void => {
150
- const {onBlur} = (this.props.children as VNode<any>).props;
151
- this.hideTooltip();
152
- if (onBlur) {
153
- onBlur();
154
- }
155
- };
156
-
157
- /**
158
- * on mouse over handler.
159
- * @memberof Tooltip
160
- * @returns {void}
161
- */
162
- onMouseOver = (): void => {
163
- this._clearHoverTimeout();
164
- // @ts-ignore
165
- this._hoverTimeout = setTimeout(() => {
166
- this.showTooltip();
167
- }, TOOLTIP_SHOW_TIMEOUT);
168
- };
169
-
170
- /**
171
- * on mouse leave handler.
172
- * @memberof Tooltip
173
- * @returns {void}
174
- */
175
- onMouseLeave = (): void => {
176
- this.hideTooltip();
177
- this._clearHoverTimeout();
178
- };
179
-
180
- /**
181
- * brings another tooltip type which hasn't been marked as invalid
182
- * @memberof Tooltip
183
- * @returns {string} tooltip type
184
- */
185
- getAlternateType(): string | undefined {
186
- return Object.values<string>(ToolTipType).find((item, index) => {
187
- if (index > this.lastAlternativeTypeIndex && item != this.props.type) {
188
- this.lastAlternativeTypeIndex = index;
189
- return true;
190
- } else {
191
- return false;
192
- }
193
- });
194
- }
195
-
196
- /**
197
- * checks if the current tooltip type is within the player boundaries
198
- * @memberof Tooltip
199
- * @returns {string} is in boundaries
200
- */
201
- isToolTipInBoundaries(): boolean {
202
- if (this.props.strictPosition) {
203
- return true;
204
- }
205
- const tooltipBoundingRect = this.textElement.getBoundingClientRect();
206
- const playerContainerRect = this.props.playerClientRect;
207
-
208
- return (
209
- tooltipBoundingRect.top > playerContainerRect!.top + PLAYER_MARGIN &&
210
- tooltipBoundingRect.bottom < playerContainerRect!.bottom - PLAYER_MARGIN &&
211
- tooltipBoundingRect.right < playerContainerRect!.right - PLAYER_MARGIN &&
212
- tooltipBoundingRect.left > playerContainerRect!.left + PLAYER_MARGIN
213
- );
214
- }
215
-
216
- /**
217
- * sets the requested type prop of the tooltip as a state cause it can change if is not valid
218
- * @memberof Tooltip
219
- * @returns {void}
220
- */
221
- componentWillMount(): void {
222
- this.setState({valid: false, type: this.props.type});
223
- }
224
-
225
- /**
226
- * after component mounted, set event listener to click outside the component
227
- *
228
- * @returns {void}
229
- * @memberof Tooltip
230
- */
231
- componentDidMount() {
232
- const {eventManager} = this.props;
233
- eventManager!.listen(document, 'click', e => this.handleClickOutside(e));
234
- if (this._buttonRef?.addEventListener) {
235
- eventManager!.listen(this._buttonRef, 'keydown', this.handleKeyDown);
236
- }
237
- }
238
-
239
- /**
240
- * event listener for clicking outside handler.
241
- *
242
- * @param {*} e - click event
243
- * @returns {void}
244
- * @memberof Tooltip
245
- */
246
- handleClickOutside(e: any) {
247
- if (!this.tooltipElement?.contains(e.target) && this.state.showTooltip) {
248
- this.hideTooltip();
249
- }
250
- }
251
-
252
- handleRef = (el: HTMLButtonElement | null) => {
253
- this.setButtonRef(el);
254
-
255
- // Forward the child’s original ref (callback or ref object)
256
- // so Tooltip keeps its own ref without breaking the child’s.
257
- const { ref } = (this.props.children as VNode<any>);
258
- if (typeof ref === 'function') {
259
- ref(el);
260
- } else if (ref && typeof ref === 'object') {
261
- (ref as any).current = el;
262
- }
263
- };
264
-
265
- /**
266
- * checks if after the render the tooltip is within boundaries of the player
267
- * if not it will try to set a new type which will be checked after the next render
268
- * @param {Object} prevProps - previous component props
269
- * @memberof Tooltip
270
- * @returns {void}
271
- */
272
- componentDidUpdate(prevProps: any): void {
273
- if (this.props.guiClientRect !== prevProps.guiClientRect) {
274
- this.lastAlternativeTypeIndex = -1;
275
- this.setState({valid: false, type: this.props.type});
276
- } else if (this.state.showTooltip) {
277
- if (this.isToolTipInBoundaries()) {
278
- if (!this.state.valid) {
279
- this.setState({valid: true});
280
- }
281
- } else {
282
- const alternative = this.getAlternateType();
283
- if (alternative) {
284
- this.setState({valid: false, type: alternative});
285
- }
286
- }
287
- }
288
- }
289
-
290
- /**
291
- * after component unmount, clear timeouts
292
- *
293
- * @returns {void}
294
- * @memberof Tooltip
295
- */
296
- componentWillUnmount(): void {
297
- this._clearHoverTimeout();
298
- }
299
-
300
- /**
301
- * render component
302
- *
303
- * @param {*} props - component props
304
- * @returns {React$Element} - component element
305
- * @memberof Tooltip
306
- */
307
- render(props: any): VNode<any> {
308
- const className = [style.tooltipLabel, style[`tooltip-${this.state.type}`]];
309
- if (props.className) {
310
- className.push(props.className);
311
- }
312
- this.state.showTooltip && this.state.valid ? className.push(style.show) : className.push(style.hide);
313
- if (props.isMobile) {
314
- return toChildArray(props.children)[0] as VNode<any>;
315
- }
316
- const children = cloneElement(props.children, {
317
- onFocus: this.handleFocusOnChildren,
318
- onBlur: this.handleBlurOnChildren,
319
- ref: this.handleRef
320
- });
321
- return (
322
- <div
323
- className={style.tooltip}
324
- onMouseOver={this.onMouseOver}
325
- onMouseLeave={this.onMouseLeave}
326
- ref={el => (el ? (this.tooltipElement = el) : undefined)}>
327
- {children}
328
- <span aria-hidden="true" style={{maxWidth: props.maxWidth}} ref={el => (el ? (this.textElement = el) : undefined)} className={className.join(' ')}>
329
- {props.label}
330
- </span>
331
- </div>
332
- );
333
- }
334
- }
335
-
336
- export {Tooltip, ToolTipType};
1
+ import style from '../../styles/style.scss';
2
+ import {h, Component, toChildArray, cloneElement, VNode} from 'preact';
3
+ import {connect} from 'react-redux';
4
+ import {withEventManager} from '../../event';
5
+ import {WithEventManagerProps} from '../../event/with-event-manager';
6
+ import {KeyMap} from '../../utils/key-map';
7
+
8
+ interface ReduxStateProps {
9
+ playerClientRect?: DOMRect;
10
+ guiClientRect?: DOMRect;
11
+ isMobile?: boolean;
12
+ }
13
+
14
+ type ToolTipPosition = 'top' | 'bottom' | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'left' | 'right';
15
+
16
+ interface TooltipOwnProps {
17
+ type?: ToolTipPosition;
18
+ maxWidth?: string;
19
+ label: string;
20
+ strictPosition?: boolean;
21
+ className?: string;
22
+ }
23
+
24
+ type TooltipProps = ReduxStateProps & TooltipOwnProps;
25
+
26
+ const PLAYER_MARGIN = 5;
27
+
28
+ /**
29
+ * mapping state to props
30
+ * @param {*} state - redux store state
31
+ * @returns {Object} - mapped state to this component
32
+ */
33
+ const mapStateToProps = state => ({
34
+ playerClientRect: state.shell.playerClientRect,
35
+ guiClientRect: state.shell.guiClientRect,
36
+ isMobile: state.shell.isMobile
37
+ });
38
+
39
+ const TOOLTIP_SHOW_TIMEOUT: number = 750;
40
+
41
+ // notice the order represents the order of the alternative fallback
42
+ const ToolTipType: {[type: string]: ToolTipPosition} = {
43
+ Top: 'top',
44
+ Bottom: 'bottom',
45
+ TopRight: 'top-right',
46
+ TopLeft: 'top-left',
47
+ BottomRight: 'bottom-right',
48
+ BottomLeft: 'bottom-left',
49
+ Left: 'left',
50
+ Right: 'right'
51
+ };
52
+
53
+ /**
54
+ * Tooltip component
55
+ *
56
+ * @class Tooltip
57
+ * @example <Tooltip>...</Tooltip>
58
+ * @extends {Component}
59
+ */
60
+ @connect(mapStateToProps)
61
+ @withEventManager
62
+ class Tooltip extends Component<TooltipProps & WithEventManagerProps, any> {
63
+ _hoverTimeout: number | null = null;
64
+ textElement!: HTMLSpanElement;
65
+ tooltipElement!: HTMLDivElement;
66
+ lastAlternativeTypeIndex: number = -1;
67
+ _buttonRef: HTMLButtonElement | null = null;
68
+
69
+ /**
70
+ * default component props
71
+ * @type {Object}
72
+ * @memberof Tooltip
73
+ */
74
+ static defaultProps = {
75
+ type: ToolTipType.Top,
76
+ maxWidth: '240px',
77
+ strictPosition: false
78
+ };
79
+
80
+ /**
81
+ * clear hover timeout
82
+ *
83
+ * @returns {void}
84
+ * @memberof Tooltip
85
+ */
86
+ _clearHoverTimeout(): void {
87
+ if (this._hoverTimeout) {
88
+ clearTimeout(this._hoverTimeout);
89
+ this._hoverTimeout = null;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * displays tooltip.
95
+ * @memberof Tooltip
96
+ * @returns {void}
97
+ */
98
+ showTooltip = (): void => {
99
+ this.setState({showTooltip: true});
100
+ };
101
+
102
+ /**
103
+ * hide tooltip.
104
+ * @memberof Tooltip
105
+ * @returns {void}
106
+ */
107
+ hideTooltip = (): void => {
108
+ this.setState({showTooltip: false});
109
+ };
110
+
111
+ /**
112
+ * handle keyDown
113
+ * @memberof Tooltip
114
+ * @returns {void}
115
+ */
116
+ handleKeyDown = (event: KeyboardEvent): void => {
117
+ if (event.keyCode === KeyMap.ESC) {
118
+ this.hideTooltip();
119
+ }
120
+ };
121
+
122
+ /**
123
+ * set button ref
124
+ * @memberof Tooltip
125
+ * @returns {void}
126
+ */
127
+ setButtonRef = (element: HTMLButtonElement | null) => {
128
+ this._buttonRef = element;
129
+ };
130
+
131
+ /**
132
+ * handle focus on wrapped element
133
+ * @memberof Tooltip
134
+ * @returns {void}
135
+ */
136
+ handleFocusOnChildren = (event: Event): void => {
137
+ const {onFocus} = (this.props.children as VNode<any>).props;
138
+ // SUP-52316: The A11y HOC (popup-keyboard-accessibility) sets a data attribute on the trigger
139
+ // element synchronously in componentWillUnmount, before the focusElement() setInterval fires.
140
+ // By the time the interval calls .focus() (~100ms later), the settings panel is already unmounted
141
+ // so relatedTarget is document.body — the closest() guard on relatedTarget cannot work.
142
+ // Instead, we consume the data attribute flag here: if it is present, this focus event is a
143
+ // programmatic focus-restore, not a deliberate user navigation, so we skip showing the tooltip.
144
+ const target = event.target as HTMLElement | null;
145
+ if (!target) {
146
+ if (onFocus) onFocus(event);
147
+ return;
148
+ }
149
+ if (target?.dataset?.kalturaFocusRestore) {
150
+ delete target.dataset.kalturaFocusRestore;
151
+ if (onFocus) onFocus(event);
152
+ return;
153
+ }
154
+ this.showTooltip();
155
+ if (onFocus) {
156
+ onFocus(event);
157
+ }
158
+ };
159
+
160
+ /**
161
+ * handle blur on wrapped element
162
+ * @memberof Tooltip
163
+ * @returns {void}
164
+ */
165
+ handleBlurOnChildren = (): void => {
166
+ const {onBlur} = (this.props.children as VNode<any>).props;
167
+ this.hideTooltip();
168
+ if (onBlur) {
169
+ onBlur();
170
+ }
171
+ };
172
+
173
+ /**
174
+ * on mouse over handler.
175
+ * @memberof Tooltip
176
+ * @returns {void}
177
+ */
178
+ onMouseOver = (): void => {
179
+ this._clearHoverTimeout();
180
+ // @ts-ignore
181
+ this._hoverTimeout = setTimeout(() => {
182
+ this.showTooltip();
183
+ }, TOOLTIP_SHOW_TIMEOUT);
184
+ };
185
+
186
+ /**
187
+ * on mouse leave handler.
188
+ * @memberof Tooltip
189
+ * @returns {void}
190
+ */
191
+ onMouseLeave = (): void => {
192
+ this.hideTooltip();
193
+ this._clearHoverTimeout();
194
+ };
195
+
196
+ /**
197
+ * brings another tooltip type which hasn't been marked as invalid
198
+ * @memberof Tooltip
199
+ * @returns {string} tooltip type
200
+ */
201
+ getAlternateType(): string | undefined {
202
+ return Object.values<string>(ToolTipType).find((item, index) => {
203
+ if (index > this.lastAlternativeTypeIndex && item != this.props.type) {
204
+ this.lastAlternativeTypeIndex = index;
205
+ return true;
206
+ } else {
207
+ return false;
208
+ }
209
+ });
210
+ }
211
+
212
+ /**
213
+ * checks if the current tooltip type is within the player boundaries
214
+ * @memberof Tooltip
215
+ * @returns {string} is in boundaries
216
+ */
217
+ isToolTipInBoundaries(): boolean {
218
+ if (this.props.strictPosition) {
219
+ return true;
220
+ }
221
+ const tooltipBoundingRect = this.textElement.getBoundingClientRect();
222
+ const playerContainerRect = this.props.playerClientRect;
223
+
224
+ return (
225
+ tooltipBoundingRect.top > playerContainerRect!.top + PLAYER_MARGIN &&
226
+ tooltipBoundingRect.bottom < playerContainerRect!.bottom - PLAYER_MARGIN &&
227
+ tooltipBoundingRect.right < playerContainerRect!.right - PLAYER_MARGIN &&
228
+ tooltipBoundingRect.left > playerContainerRect!.left + PLAYER_MARGIN
229
+ );
230
+ }
231
+
232
+ /**
233
+ * sets the requested type prop of the tooltip as a state cause it can change if is not valid
234
+ * @memberof Tooltip
235
+ * @returns {void}
236
+ */
237
+ componentWillMount(): void {
238
+ this.setState({valid: false, type: this.props.type});
239
+ }
240
+
241
+ /**
242
+ * after component mounted, set event listener to click outside the component
243
+ *
244
+ * @returns {void}
245
+ * @memberof Tooltip
246
+ */
247
+ componentDidMount() {
248
+ const {eventManager} = this.props;
249
+ eventManager!.listen(document, 'click', e => this.handleClickOutside(e));
250
+ if (this._buttonRef?.addEventListener) {
251
+ eventManager!.listen(this._buttonRef, 'keydown', this.handleKeyDown);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * event listener for clicking outside handler.
257
+ *
258
+ * @param {*} e - click event
259
+ * @returns {void}
260
+ * @memberof Tooltip
261
+ */
262
+ handleClickOutside(e: any) {
263
+ if (!this.tooltipElement?.contains(e.target) && this.state.showTooltip) {
264
+ this.hideTooltip();
265
+ }
266
+ }
267
+
268
+ handleRef = (el: HTMLButtonElement | null) => {
269
+ this.setButtonRef(el);
270
+
271
+ // Forward the child’s original ref (callback or ref object)
272
+ // so Tooltip keeps its own ref without breaking the child’s.
273
+ const { ref } = (this.props.children as VNode<any>);
274
+ if (typeof ref === 'function') {
275
+ ref(el);
276
+ } else if (ref && typeof ref === 'object') {
277
+ (ref as any).current = el;
278
+ }
279
+ };
280
+
281
+ /**
282
+ * checks if after the render the tooltip is within boundaries of the player
283
+ * if not it will try to set a new type which will be checked after the next render
284
+ * @param {Object} prevProps - previous component props
285
+ * @memberof Tooltip
286
+ * @returns {void}
287
+ */
288
+ componentDidUpdate(prevProps: any): void {
289
+ if (this.props.guiClientRect !== prevProps.guiClientRect) {
290
+ this.lastAlternativeTypeIndex = -1;
291
+ this.setState({valid: false, type: this.props.type});
292
+ } else if (this.state.showTooltip) {
293
+ if (this.isToolTipInBoundaries()) {
294
+ if (!this.state.valid) {
295
+ this.setState({valid: true});
296
+ }
297
+ } else {
298
+ const alternative = this.getAlternateType();
299
+ if (alternative) {
300
+ this.setState({valid: false, type: alternative});
301
+ }
302
+ }
303
+ }
304
+ }
305
+
306
+ /**
307
+ * after component unmount, clear timeouts
308
+ *
309
+ * @returns {void}
310
+ * @memberof Tooltip
311
+ */
312
+ componentWillUnmount(): void {
313
+ this._clearHoverTimeout();
314
+ }
315
+
316
+ /**
317
+ * render component
318
+ *
319
+ * @param {*} props - component props
320
+ * @returns {React$Element} - component element
321
+ * @memberof Tooltip
322
+ */
323
+ render(props: any): VNode<any> {
324
+ const className = [style.tooltipLabel, style[`tooltip-${this.state.type}`]];
325
+ if (props.className) {
326
+ className.push(props.className);
327
+ }
328
+ this.state.showTooltip && this.state.valid ? className.push(style.show) : className.push(style.hide);
329
+ if (props.isMobile) {
330
+ return toChildArray(props.children)[0] as VNode<any>;
331
+ }
332
+ const children = cloneElement(props.children, {
333
+ onFocus: this.handleFocusOnChildren,
334
+ onBlur: this.handleBlurOnChildren,
335
+ ref: this.handleRef
336
+ });
337
+ return (
338
+ <div
339
+ className={style.tooltip}
340
+ onMouseOver={this.onMouseOver}
341
+ onMouseLeave={this.onMouseLeave}
342
+ ref={el => (el ? (this.tooltipElement = el) : undefined)}>
343
+ {children}
344
+ <span aria-hidden="true" style={{maxWidth: props.maxWidth}} ref={el => (el ? (this.textElement = el) : undefined)} className={className.join(' ')}>
345
+ {props.label}
346
+ </span>
347
+ </div>
348
+ );
349
+ }
350
+ }
351
+
352
+ export {Tooltip, ToolTipType};