@playkit-js/transcript 3.0.1 → 3.0.2-canary.1-f9e6d51

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,8 +1,27 @@
1
1
  import {h, Component} from 'preact';
2
+ import {A11yWrapper, OnClickEvent} from '@playkit-js/common';
2
3
  import * as styles from './search.scss';
3
4
  import {debounce} from '../../utils';
4
5
  const DEBOUNCE_TIMEOUT = 300;
5
6
 
7
+ const {withText, Text} = KalturaPlayer.ui.preacti18n;
8
+ const translates = ({activeSearchIndex, totalSearchResults}: SearchProps) => ({
9
+ searchLabel: <Text id="transcript.search">Search in Transcript</Text>,
10
+ clearSearchLabel: <Text id="transcript.clear_search">Clear search</Text>,
11
+ nextMatchLabel: <Text id="transcript.next_search_match">Next</Text>,
12
+ prevMatchLabel: <Text id="transcript.prev_search_match">Previous</Text>,
13
+ searchResultsLabel: (
14
+ <Text
15
+ id="transcript.prev_search_match"
16
+ fields={{
17
+ current: totalSearchResults > 0 ? activeSearchIndex : 0,
18
+ total: totalSearchResults
19
+ }}>
20
+ {`Result ${totalSearchResults > 0 ? activeSearchIndex : 0} of ${totalSearchResults}`}
21
+ </Text>
22
+ )
23
+ });
24
+
6
25
  export interface SearchProps {
7
26
  onChange(value: string): void;
8
27
  searchQuery: string;
@@ -13,6 +32,12 @@ export interface SearchProps {
13
32
  value: string;
14
33
  activeSearchIndex: number;
15
34
  totalSearchResults: number;
35
+
36
+ searchLabel?: string;
37
+ clearSearchLabel?: string;
38
+ nextMatchLabel?: string;
39
+ prevMatchLabel?: string;
40
+ searchResultsLabel?: string;
16
41
  }
17
42
 
18
43
  interface SearchState {
@@ -20,7 +45,7 @@ interface SearchState {
20
45
  focused: boolean;
21
46
  }
22
47
 
23
- export class Search extends Component<SearchProps, SearchState> {
48
+ class SearchComponent extends Component<SearchProps, SearchState> {
24
49
  state: SearchState = {
25
50
  active: false,
26
51
  focused: false
@@ -61,8 +86,8 @@ export class Search extends Component<SearchProps, SearchState> {
61
86
  this.props.onChange(e.target.value);
62
87
  };
63
88
 
64
- private _onClear = (event: MouseEvent) => {
65
- if (event.x !== 0 && event.y !== 0) {
89
+ private _onClear = (event: OnClickEvent, byKeyboard?: boolean) => {
90
+ if (!byKeyboard) {
66
91
  this._focusedByMouse = true;
67
92
  }
68
93
  this._inputRef?.focus();
@@ -135,7 +160,8 @@ export class Search extends Component<SearchProps, SearchState> {
135
160
  </div>
136
161
  <input
137
162
  className={styles.searchInput}
138
- placeholder={'Search in Transcript'}
163
+ aria-label={this.props.searchLabel}
164
+ placeholder={this.props.searchLabel}
139
165
  value={searchQuery}
140
166
  onInput={this._handleOnChange}
141
167
  onFocus={this._onFocus}
@@ -147,72 +173,82 @@ export class Search extends Component<SearchProps, SearchState> {
147
173
  }}
148
174
  />
149
175
  {searchQuery && (
150
- <button className={styles.clearIcon} onClick={this._onClear} tabIndex={1}>
151
- <svg
152
- width="32px"
153
- height="32px"
154
- viewBox="0 0 32 32"
155
- version="1.1"
156
- xmlns="http://www.w3.org/2000/svg"
157
- xmlnsXlink="http://www.w3.org/1999/xlink">
158
- <g id="Icons/32/Clere" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
159
- <path
160
- d="M16,8 C20.418278,8 24,11.581722 24,16 C24,20.418278 20.418278,24 16,24 C11.581722,24 8,20.418278 8,16 C8,11.581722 11.581722,8 16,8 Z M19.8665357,12.1334643 C19.6885833,11.9555119 19.4000655,11.9555119 19.2221131,12.1334643 L16,15.356 L12.7778869,12.1334643 L12.7064039,12.0750737 C12.5295326,11.9582924 12.2891726,11.977756 12.1334643,12.1334643 L12.0750737,12.2049473 C11.9582924,12.3818186 11.977756,12.6221786 12.1334643,12.7778869 L15.356,16 L12.1334643,19.2221131 C11.9555119,19.4000655 11.9555119,19.6885833 12.1334643,19.8665357 C12.3114167,20.0444881 12.5999345,20.0444881 12.7778869,19.8665357 L16,16.644 L19.2221131,19.8665357 L19.2935961,19.9249263 C19.4704674,20.0417076 19.7108274,20.022244 19.8665357,19.8665357 L19.9249263,19.7950527 C20.0417076,19.6181814 20.022244,19.3778214 19.8665357,19.2221131 L16.644,16 L19.8665357,12.7778869 C20.0444881,12.5999345 20.0444881,12.3114167 19.8665357,12.1334643 Z"
161
- id="Shape"
162
- fill="#cccccc"></path>
163
- </g>
164
- </svg>
165
- </button>
166
- )}
167
- {searchQuery && (
168
- <div className={styles.searchResults}>{`${totalSearchResults > 0 ? `${activeSearchIndex}/${totalSearchResults}` : '0/0'}`}</div>
169
- )}
170
- <div className={styles.prevNextWrapper}>
171
- {searchQuery && (
172
- <button
173
- tabIndex={1}
174
- className={`${styles.prevNextButton} ${totalSearchResults === 0 ? styles.disabled : ''}`}
175
- onClick={this._goToPrevSearchResult}>
176
+ <A11yWrapper onClick={this._onClear}>
177
+ <button className={styles.clearIcon} tabIndex={1} aria-label={this.props.clearSearchLabel}>
176
178
  <svg
177
- width="14px"
178
- height="12px"
179
- viewBox="1 0 14 12"
179
+ width="32px"
180
+ height="32px"
181
+ viewBox="0 0 32 32"
180
182
  version="1.1"
181
183
  xmlns="http://www.w3.org/2000/svg"
182
184
  xmlnsXlink="http://www.w3.org/1999/xlink">
183
- <g id="Icons/16/Arrow/-up" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
185
+ <g id="Icons/32/Clere" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
184
186
  <path
185
- d="M4.78325732,5.37830235 C4.43990319,4.94572127 3.81088342,4.87338855 3.37830235,5.21674268 C2.94572127,5.56009681 2.87338855,6.18911658 3.21674268,6.62169765 L7.21674268,11.6611718 C7.61710439,12.165575 8.38289561,12.165575 8.78325732,11.6611718 L12.7832573,6.62169765 C13.1266115,6.18911658 13.0542787,5.56009681 12.6216977,5.21674268 C12.1891166,4.87338855 11.5600968,4.94572127 11.2167427,5.37830235 L8,9.43097528 L4.78325732,5.37830235 Z"
186
- id="Path-2"
187
- fill="#cccccc"
188
- transform="translate(8.000000, 8.519717) scale(1, -1) translate(-8.000000, -8.519717) "></path>
187
+ d="M16,8 C20.418278,8 24,11.581722 24,16 C24,20.418278 20.418278,24 16,24 C11.581722,24 8,20.418278 8,16 C8,11.581722 11.581722,8 16,8 Z M19.8665357,12.1334643 C19.6885833,11.9555119 19.4000655,11.9555119 19.2221131,12.1334643 L16,15.356 L12.7778869,12.1334643 L12.7064039,12.0750737 C12.5295326,11.9582924 12.2891726,11.977756 12.1334643,12.1334643 L12.0750737,12.2049473 C11.9582924,12.3818186 11.977756,12.6221786 12.1334643,12.7778869 L15.356,16 L12.1334643,19.2221131 C11.9555119,19.4000655 11.9555119,19.6885833 12.1334643,19.8665357 C12.3114167,20.0444881 12.5999345,20.0444881 12.7778869,19.8665357 L16,16.644 L19.2221131,19.8665357 L19.2935961,19.9249263 C19.4704674,20.0417076 19.7108274,20.022244 19.8665357,19.8665357 L19.9249263,19.7950527 C20.0417076,19.6181814 20.022244,19.3778214 19.8665357,19.2221131 L16.644,16 L19.8665357,12.7778869 C20.0444881,12.5999345 20.0444881,12.3114167 19.8665357,12.1334643 Z"
188
+ id="Shape"
189
+ fill="#cccccc"></path>
189
190
  </g>
190
191
  </svg>
191
192
  </button>
193
+ </A11yWrapper>
194
+ )}
195
+ {searchQuery && (
196
+ <div className={styles.searchResults} aria-live="polite" aria-label={this.props.searchResultsLabel}>{`${
197
+ totalSearchResults > 0 ? `${activeSearchIndex}/${totalSearchResults}` : '0/0'
198
+ }`}</div>
199
+ )}
200
+ <div className={styles.prevNextWrapper}>
201
+ {searchQuery && (
202
+ <A11yWrapper onClick={this._goToPrevSearchResult}>
203
+ <button
204
+ tabIndex={1}
205
+ className={`${styles.prevNextButton} ${totalSearchResults === 0 ? styles.disabled : ''}`}
206
+ aria-label={this.props.prevMatchLabel}>
207
+ <svg
208
+ width="14px"
209
+ height="12px"
210
+ viewBox="1 0 14 12"
211
+ version="1.1"
212
+ xmlns="http://www.w3.org/2000/svg"
213
+ xmlnsXlink="http://www.w3.org/1999/xlink">
214
+ <g id="Icons/16/Arrow/-up" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
215
+ <path
216
+ d="M4.78325732,5.37830235 C4.43990319,4.94572127 3.81088342,4.87338855 3.37830235,5.21674268 C2.94572127,5.56009681 2.87338855,6.18911658 3.21674268,6.62169765 L7.21674268,11.6611718 C7.61710439,12.165575 8.38289561,12.165575 8.78325732,11.6611718 L12.7832573,6.62169765 C13.1266115,6.18911658 13.0542787,5.56009681 12.6216977,5.21674268 C12.1891166,4.87338855 11.5600968,4.94572127 11.2167427,5.37830235 L8,9.43097528 L4.78325732,5.37830235 Z"
217
+ id="Path-2"
218
+ fill="#cccccc"
219
+ transform="translate(8.000000, 8.519717) scale(1, -1) translate(-8.000000, -8.519717) "></path>
220
+ </g>
221
+ </svg>
222
+ </button>
223
+ </A11yWrapper>
192
224
  )}
193
225
  {searchQuery && (
194
- <button
195
- tabIndex={1}
196
- className={`${styles.prevNextButton} ${totalSearchResults === 0 ? styles.disabled : ''}`}
197
- onClick={this._goToNextSearchResult}>
198
- <svg
199
- width="14px"
200
- height="12px"
201
- viewBox="1 2 14 12"
202
- version="1.1"
203
- xmlns="http://www.w3.org/2000/svg"
204
- xmlnsXlink="http://www.w3.org/1999/xlink">
205
- <g id="Icons/16/Arrow/down" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
206
- <path
207
- d="M4.78325732,5.37830235 C4.43990319,4.94572127 3.81088342,4.87338855 3.37830235,5.21674268 C2.94572127,5.56009681 2.87338855,6.18911658 3.21674268,6.62169765 L7.21674268,11.6611718 C7.61710439,12.165575 8.38289561,12.165575 8.78325732,11.6611718 L12.7832573,6.62169765 C13.1266115,6.18911658 13.0542787,5.56009681 12.6216977,5.21674268 C12.1891166,4.87338855 11.5600968,4.94572127 11.2167427,5.37830235 L8,9.43097528 L4.78325732,5.37830235 Z"
208
- id="Path-2"
209
- fill="#cccccc"></path>
210
- </g>
211
- </svg>
212
- </button>
226
+ <A11yWrapper onClick={this._goToNextSearchResult}>
227
+ <button
228
+ tabIndex={1}
229
+ className={`${styles.prevNextButton} ${totalSearchResults === 0 ? styles.disabled : ''}`}
230
+ aria-label={this.props.nextMatchLabel}>
231
+ <svg
232
+ width="14px"
233
+ height="12px"
234
+ viewBox="1 2 14 12"
235
+ version="1.1"
236
+ xmlns="http://www.w3.org/2000/svg"
237
+ xmlnsXlink="http://www.w3.org/1999/xlink">
238
+ <g id="Icons/16/Arrow/down" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
239
+ <path
240
+ d="M4.78325732,5.37830235 C4.43990319,4.94572127 3.81088342,4.87338855 3.37830235,5.21674268 C2.94572127,5.56009681 2.87338855,6.18911658 3.21674268,6.62169765 L7.21674268,11.6611718 C7.61710439,12.165575 8.38289561,12.165575 8.78325732,11.6611718 L12.7832573,6.62169765 C13.1266115,6.18911658 13.0542787,5.56009681 12.6216977,5.21674268 C12.1891166,4.87338855 11.5600968,4.94572127 11.2167427,5.37830235 L8,9.43097528 L4.78325732,5.37830235 Z"
241
+ id="Path-2"
242
+ fill="#cccccc"></path>
243
+ </g>
244
+ </svg>
245
+ </button>
246
+ </A11yWrapper>
213
247
  )}
214
248
  </div>
215
249
  </div>
216
250
  );
217
251
  }
218
252
  }
253
+
254
+ export const Search = withText(translates)(SearchComponent);
@@ -8,7 +8,13 @@ import {CaptionList} from '../caption-list';
8
8
  import {HighlightedMap, CuePointData, PluginPositions} from '../../types';
9
9
  import {CloseButton} from '../close-button';
10
10
  import {ErrorIcon} from './error-icon';
11
- const {ENTER, Space, Tab, Esc} = KalturaPlayer.ui.utils.KeyMap;
11
+
12
+ const {ENTER, SPACE, TAB, ESC} = KalturaPlayer.ui.utils.KeyMap;
13
+ const {withText, Text} = KalturaPlayer.ui.preacti18n;
14
+
15
+ const translates = {
16
+ autoScrollLabel: <Text id="transcript.auto_scroll">Enable auto scroll</Text>
17
+ };
12
18
 
13
19
  export interface TranscriptProps {
14
20
  onSeek(time: number): void;
@@ -28,6 +34,7 @@ export interface TranscriptProps {
28
34
  highlightedMap: HighlightedMap;
29
35
  pluginMode: PluginPositions;
30
36
  onItemClicked: (n: number) => void;
37
+ autoScrollLabel?: string;
31
38
  }
32
39
 
33
40
  interface TranscriptState {
@@ -50,7 +57,7 @@ const initialSearch = {
50
57
 
51
58
  const SEARCHBAR_HEIGHT = 38; // height of search bar with margins
52
59
 
53
- export class Transcript extends Component<TranscriptProps, TranscriptState> {
60
+ export class TranscriptComponent extends Component<TranscriptProps, TranscriptState> {
54
61
  private _transcriptListRef: HTMLElement | null = null;
55
62
  private _captionListRef: any = null;
56
63
  private _skipTranscriptButtonRef: HTMLDivElement | null = null;
@@ -106,6 +113,7 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
106
113
  role="button"
107
114
  className={`${styles.autoscrollButton} ${isAutoScrollEnabled ? '' : styles.autoscrollButtonVisible}`}
108
115
  tabIndex={isAutoScrollEnabled ? -1 : 1}
116
+ aria-label={this.props.autoScrollLabel}
109
117
  ref={node => {
110
118
  this._autoscrollButtonRef = node;
111
119
  }}>
@@ -201,8 +209,13 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
201
209
  );
202
210
  };
203
211
 
212
+ private _handleClick = (event: MouseEvent | KeyboardEvent) => {
213
+ event.preventDefault();
214
+ this._autoscrollButtonRef?.focus();
215
+ };
216
+
204
217
  private _handleKeyDown = (event: KeyboardEvent) => {
205
- if (event.keyCode === Tab && !event.shiftKey) {
218
+ if (event.keyCode === TAB && !event.shiftKey) {
206
219
  this.setState({
207
220
  isAutoScrollEnabled: false
208
221
  });
@@ -211,9 +224,8 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
211
224
  event.preventDefault();
212
225
  captionRef.focus();
213
226
  }
214
- } else if (event.keyCode === ENTER || event.keyCode === Space) {
215
- event.preventDefault();
216
- this._autoscrollButtonRef?.focus();
227
+ } else if (event.keyCode === ENTER || event.keyCode === SPACE) {
228
+ this._handleClick(event);
217
229
  }
218
230
  };
219
231
 
@@ -226,6 +238,7 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
226
238
  }}
227
239
  className={styles.skipTranscriptButton}
228
240
  onKeyDown={this._handleKeyDown}
241
+ onClick={this._handleClick}
229
242
  tabIndex={1}>
230
243
  Skip transcript
231
244
  </div>
@@ -351,7 +364,7 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
351
364
  };
352
365
 
353
366
  private _handleEsc = (event: KeyboardEvent) => {
354
- if (event.keyCode === Esc) {
367
+ if (event.keyCode === ESC) {
355
368
  this.props.onClose();
356
369
  }
357
370
  };
@@ -386,3 +399,5 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
386
399
  );
387
400
  }
388
401
  }
402
+
403
+ export const Transcript = withText(translates)(TranscriptComponent);
@@ -10,14 +10,6 @@ import {DownloadPrintMenu, downloadContent, printContent} from './components/dow
10
10
 
11
11
  const {SidePanelModes, SidePanelPositions, ReservedPresetNames, ReservedPresetAreas} = ui;
12
12
  const {get} = ObjectUtils;
13
- const {Tooltip} = KalturaPlayer.ui.components;
14
- const {withText, Text} = KalturaPlayer.ui.preacti18n;
15
-
16
- const translates = () => ({
17
- printDownloadAreaLabel: <Text id="transcript.print_download_area_label">Download or print current transcript</Text>,
18
- printTranscript: <Text id="transcript.print_transcript">Print current transcript</Text>,
19
- downloadTranscript: <Text id="transcript.download_transcript">Download current transcript</Text>
20
- });
21
13
 
22
14
  interface TimedMetadataEvent {
23
15
  payload: {
@@ -120,14 +112,15 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
120
112
  };
121
113
 
122
114
  private _onTimedMetadataChange = ({payload}: TimedMetadataEvent) => {
123
- const transcriptCuePoints: Array<CuePoint> = payload.cues.filter((cue: CuePoint) => {
124
- return cue.metadata.cuePointType === ItemTypes.Caption;
115
+ const transcriptCuePoints: Array<CuePoint> = payload.cues
116
+ .filter((cue: CuePoint) => {
117
+ return cue.metadata.cuePointType === ItemTypes.Caption;
125
118
  })
126
119
  .filter((cue, index, array) => {
127
120
  // filter out captions that has endTime eq to next caption startTime
128
121
  const nextCue = array[index + 1];
129
122
  return !nextCue || cue.endTime !== nextCue.startTime;
130
- });
123
+ });
131
124
  this._activeCuePointsMap = {};
132
125
  transcriptCuePoints.forEach(cue => {
133
126
  this._activeCuePointsMap[cue.id] = true;
@@ -166,17 +159,14 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
166
159
  label: 'Download or print transcript',
167
160
  area: ReservedPresetAreas.TopBarRightControls,
168
161
  presets: [ReservedPresetNames.Playback, ReservedPresetNames.Live],
169
- get: withText(translates)(({printDownloadAreaLabel, printTranscript, downloadTranscript}: Record<string, string>) => (
162
+ get: () => (
170
163
  <DownloadPrintMenu
171
164
  onDownload={this._handleDownload}
172
165
  onPrint={this._handlePrint}
173
166
  downloadDisabled={getConfigValue(downloadDisabled, isBoolean, false)}
174
167
  printDisabled={getConfigValue(printDisabled, isBoolean, false)}
175
- dropdownAriaLabel={printDownloadAreaLabel}
176
- printButtonAriaLabel={printTranscript}
177
- downloadButtonAriaLabel={downloadTranscript}
178
168
  />
179
- ))
169
+ )
180
170
  });
181
171
  }
182
172
 
@@ -214,21 +204,18 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
214
204
  },
215
205
  iconComponent: ({isActive}: {isActive: boolean}) => {
216
206
  return (
217
- <Tooltip label={buttonLabel} type="bottom">
218
- <PluginButton
219
- isActive={isActive}
220
- label={buttonLabel}
221
- onClick={(e: OnClickEvent, byKeyboard?: boolean) => {
222
- if (this.sidePanelsManager.isItemActive(this._transcriptPanel)) {
223
- this._triggeredByKeyboard = false;
224
- this._handleCloseClick();
225
- } else {
226
- this._triggeredByKeyboard = Boolean(byKeyboard);
227
- this.sidePanelsManager.activateItem(this._transcriptPanel);
228
- }
229
- }}
230
- />
231
- </Tooltip>
207
+ <PluginButton
208
+ isActive={isActive}
209
+ onClick={(e: OnClickEvent, byKeyboard?: boolean) => {
210
+ if (this.sidePanelsManager.isItemActive(this._transcriptPanel)) {
211
+ this._triggeredByKeyboard = false;
212
+ this._handleCloseClick();
213
+ } else {
214
+ this._triggeredByKeyboard = Boolean(byKeyboard);
215
+ this.sidePanelsManager.activateItem(this._transcriptPanel);
216
+ }
217
+ }}
218
+ />
232
219
  );
233
220
  },
234
221
  presets: [ReservedPresetNames.Playback, ReservedPresetNames.Live, ReservedPresetNames.Ads],
@@ -1,4 +1,3 @@
1
1
  export * from './utils';
2
2
  export * from './object-utils';
3
3
  export * from './debounce';
4
- export * from './popover/popover';
@@ -1 +0,0 @@
1
- export * from './popover-menu';
@@ -1,6 +0,0 @@
1
- @import '../../variables.scss';
2
-
3
- .popover-menu {
4
- padding-top: 6px;
5
- padding-bottom: 6px;
6
- }
@@ -1,52 +0,0 @@
1
- import {h, Component, ComponentChild} from 'preact';
2
- import * as styles from './popover-menu.scss';
3
-
4
- export interface PopoverMenuItem {
5
- label: string;
6
- onMenuChosen: Function;
7
- customRenderer?: (el: PopoverMenuItem) => ComponentChild;
8
- }
9
-
10
- interface PopoverMenuProps {
11
- options: Array<PopoverMenuItem>;
12
- itemRenderer?: (el: PopoverMenuItem) => ComponentChild;
13
- }
14
-
15
- /**
16
- * Popover menu renders list of options.
17
- * options example:
18
- * [
19
- * {label: 'option_1', onMenuChosen: () => console.log('selected first')},
20
- * {label: 'option_2', onMenuChosen: () => console.log('selected second')}
21
- * ]
22
- * In case when 'itemRenderer' properdy hasn't provided - PopoverMenu renders
23
- * div with class "popover-menu-item" that contain label for the current option.
24
- * Default render of options can be changed by providing 'itemRenderer' - it should be
25
- * function that takes current option and returns valid 'preact' node.
26
- * If some option need to be rendered with a different method - specific render
27
- * method can be provided with 'customRenderer' property for the current option.
28
- * option example with specific render method:
29
- * { label: 'specific render', onMenuChosen: () => {}, customRenderer: el => (<span>{el.label}</span>)}
30
- */
31
-
32
- export class PopoverMenu extends Component<PopoverMenuProps> {
33
- render(props: any) {
34
- return (
35
- <div className={styles.popoverMenu}>
36
- {props.options.map((el: PopoverMenuItem) => {
37
- if (el.customRenderer) {
38
- return el.customRenderer(el);
39
- }
40
- if (props.itemRenderer) {
41
- return props.itemRenderer(el);
42
- }
43
- return (
44
- <div className="popover-menu-item" onClick={() => el.onMenuChosen(el)}>
45
- {el.label}
46
- </div>
47
- );
48
- })}
49
- </div>
50
- );
51
- }
52
- }
@@ -1,30 +0,0 @@
1
- .popover-container {
2
- position: relative;
3
- .popover-component {
4
- background-color: #222222;
5
- border-radius: 4px;
6
- position: absolute;
7
- right: 0px;
8
- font-size: 15px;
9
- display: block;
10
- &.visible {
11
- visibility: visible;
12
- opacity: 1;
13
- z-index: 10;
14
- }
15
- &.top {
16
- bottom: 100%;
17
- margin-bottom: 6px;
18
- }
19
- &.bottom {
20
- top: 100%;
21
- margin-top: 6px;
22
- }
23
- &.right {
24
- left: 0px;
25
- }
26
- &.left {
27
- right: 0px;
28
- }
29
- }
30
- }
@@ -1,178 +0,0 @@
1
- import {h, Component, ComponentChild} from 'preact';
2
- import * as styles from './popover.scss';
3
-
4
- const {ENTER, Esc} = KalturaPlayer.ui.utils.KeyMap;
5
-
6
- export enum PopoverVerticalPositions {
7
- Top = 'top',
8
- Bottom = 'bottom'
9
- }
10
- export enum PopoverHorizontalPositions {
11
- Left = 'left',
12
- Right = 'right'
13
- }
14
- export enum PopoverTriggerMode {
15
- Click = 'click',
16
- Hover = 'hover'
17
- }
18
-
19
- const CLOSE_ON_HOVER_DELAY = 500;
20
-
21
- const defaultProps = {
22
- verticalPosition: PopoverVerticalPositions.Top,
23
- horizontalPosition: PopoverHorizontalPositions.Left,
24
- triggerMode: PopoverTriggerMode.Click,
25
- className: 'popover',
26
- closeOnEsc: true,
27
- closeOnClick: true
28
- };
29
-
30
- interface PopoverProps {
31
- onClose?: () => void;
32
- onOpen?: () => void;
33
- closeOnClick: boolean;
34
- closeOnEsc: boolean;
35
- verticalPosition: PopoverVerticalPositions;
36
- horizontalPosition: PopoverHorizontalPositions;
37
- className: string;
38
- triggerMode: PopoverTriggerMode;
39
- content: ComponentChild;
40
- children: ComponentChild;
41
- }
42
-
43
- interface PopoverState {
44
- open: boolean;
45
- }
46
-
47
- export class Popover extends Component<PopoverProps, PopoverState> {
48
- private _closeTimeout: any = null;
49
- private _controlElement: HTMLDivElement | null = null;
50
- static defaultProps = {
51
- ...defaultProps
52
- };
53
- state = {
54
- open: false
55
- };
56
-
57
- componentWillUnmount() {
58
- this._removeListeners();
59
- }
60
-
61
- private _clearTimeout = () => {
62
- clearTimeout(this._closeTimeout);
63
- this._closeTimeout = null;
64
- };
65
-
66
- private _handleMouseEvent = (event: MouseEvent) => {
67
- if (!this._controlElement?.contains(event.target as Node | null) && this.props.closeOnClick) {
68
- this._closePopover();
69
- }
70
- };
71
-
72
- private _handleKeyboardEvent = (event: KeyboardEvent) => {
73
- if (this._controlElement?.contains(event.target as Node | null) && event.keyCode === ENTER) {
74
- // handle Enter key pressed on Target icon to prevent triggering of _closePopover twice
75
- return;
76
- }
77
- if ((this.props.closeOnEsc && event.keyCode === Esc) || event.keyCode === ENTER) {
78
- // handle if ESC or Enter button presesd
79
- this._closePopover();
80
- }
81
- };
82
-
83
- private _openPopover = () => {
84
- const {onOpen} = this.props;
85
- this._clearTimeout();
86
- this.setState({open: true}, () => {
87
- this._addListeners();
88
- if (onOpen) {
89
- onOpen();
90
- }
91
- });
92
- };
93
-
94
- private _closePopover = () => {
95
- const {onClose} = this.props;
96
- this._clearTimeout();
97
- this.setState({open: false}, () => {
98
- this._removeListeners();
99
- if (onClose) {
100
- onClose();
101
- }
102
- });
103
- };
104
-
105
- private _togglePopover = (e: MouseEvent | KeyboardEvent) => {
106
- if (this.state.open) {
107
- this._closePopover();
108
- } else {
109
- this._openPopover();
110
- }
111
- };
112
- private _handleMouseEnter = () => {
113
- if (!this.state.open) {
114
- this._openPopover();
115
- }
116
- };
117
- private _handleMouseLeave = () => {
118
- this._closeTimeout = setTimeout(this._closePopover, CLOSE_ON_HOVER_DELAY);
119
- };
120
- private _handleHoverOnPopover = () => {
121
- if (this.state.open && this._closeTimeout) {
122
- this._clearTimeout();
123
- } else {
124
- this._closePopover();
125
- }
126
- };
127
- private _addListeners = () => {
128
- document.addEventListener('click', this._handleMouseEvent);
129
- document.addEventListener('keydown', this._handleKeyboardEvent);
130
- };
131
- private _removeListeners = () => {
132
- document.removeEventListener('click', this._handleMouseEvent);
133
- document.removeEventListener('keydown', this._handleKeyboardEvent);
134
- };
135
- private _getHoverEvents = () => {
136
- if (this.props.triggerMode === PopoverTriggerMode.Hover) {
137
- return {
138
- targetEvents: {
139
- onMouseEnter: this._handleMouseEnter,
140
- onMouseLeave: this._handleMouseLeave
141
- },
142
- popoverEvents: {
143
- onMouseEnter: this._handleHoverOnPopover,
144
- onMouseLeave: this._handleHoverOnPopover
145
- }
146
- };
147
- }
148
- return {targetEvents: {onClick: this._togglePopover}, popoverEvents: {}};
149
- };
150
- render(props: PopoverProps) {
151
- if (!props.content || !props.children) {
152
- return null;
153
- }
154
- const {targetEvents, popoverEvents} = this._getHoverEvents();
155
- return (
156
- <div className={styles.popoverContainer}>
157
- <div
158
- className="popover-anchor-container"
159
- ref={node => {
160
- this._controlElement = node;
161
- }}
162
- {...targetEvents}
163
- >
164
- {props.children}
165
- </div>
166
- {this.state.open && (
167
- <div
168
- aria-expanded="true"
169
- className={[props.className, styles.popoverComponent, styles[props.verticalPosition], styles[props.horizontalPosition]].join(' ')}
170
- {...popoverEvents}
171
- >
172
- {props.content}
173
- </div>
174
- )}
175
- </div>
176
- );
177
- }
178
- }