@playkit-js/transcript 3.0.1-canary.44-4c445bb → 3.0.1
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/CHANGELOG.md +1 -1
- package/dist/playkit-transcript.js +1 -1
- package/dist/playkit-transcript.js.map +1 -1
- package/package.json +3 -3
- package/src/components/caption/caption.tsx +24 -20
- package/src/components/caption-list/captionList.tsx +5 -5
- package/src/components/close-button/index.tsx +3 -9
- package/src/components/download-print-menu/download-print-menu.scss +16 -0
- package/src/components/download-print-menu/download-print-menu.tsx +84 -48
- package/src/components/plugin-button/plugin-button.tsx +2 -10
- package/src/components/popover-menu/index.ts +1 -0
- package/src/components/popover-menu/popover-menu.scss +6 -0
- package/src/components/popover-menu/popover-menu.tsx +52 -0
- package/src/components/search/search.tsx +57 -93
- package/src/components/transcript/transcript.tsx +7 -22
- package/src/transcript-plugin.tsx +31 -18
- package/src/utils/index.ts +1 -0
- package/src/utils/popover/popover.scss +30 -0
- package/src/utils/popover/popover.tsx +178 -0
- package/src/components/popover/index.ts +0 -1
- package/src/components/popover/popover.scss +0 -43
- package/src/components/popover/popover.tsx +0 -142
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
import {h, Component} from 'preact';
|
|
2
|
-
import {A11yWrapper, OnClickEvent} from '@playkit-js/common';
|
|
3
2
|
import * as styles from './search.scss';
|
|
4
3
|
import {debounce} from '../../utils';
|
|
5
4
|
const DEBOUNCE_TIMEOUT = 300;
|
|
6
5
|
|
|
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
|
-
|
|
25
6
|
export interface SearchProps {
|
|
26
7
|
onChange(value: string): void;
|
|
27
8
|
searchQuery: string;
|
|
@@ -32,12 +13,6 @@ export interface SearchProps {
|
|
|
32
13
|
value: string;
|
|
33
14
|
activeSearchIndex: number;
|
|
34
15
|
totalSearchResults: number;
|
|
35
|
-
|
|
36
|
-
searchLabel?: string;
|
|
37
|
-
clearSearchLabel?: string;
|
|
38
|
-
nextMatchLabel?: string;
|
|
39
|
-
prevMatchLabel?: string;
|
|
40
|
-
searchResultsLabel?: string;
|
|
41
16
|
}
|
|
42
17
|
|
|
43
18
|
interface SearchState {
|
|
@@ -45,7 +20,7 @@ interface SearchState {
|
|
|
45
20
|
focused: boolean;
|
|
46
21
|
}
|
|
47
22
|
|
|
48
|
-
class
|
|
23
|
+
export class Search extends Component<SearchProps, SearchState> {
|
|
49
24
|
state: SearchState = {
|
|
50
25
|
active: false,
|
|
51
26
|
focused: false
|
|
@@ -86,8 +61,8 @@ class SearchComponent extends Component<SearchProps, SearchState> {
|
|
|
86
61
|
this.props.onChange(e.target.value);
|
|
87
62
|
};
|
|
88
63
|
|
|
89
|
-
private _onClear = (event:
|
|
90
|
-
if (
|
|
64
|
+
private _onClear = (event: MouseEvent) => {
|
|
65
|
+
if (event.x !== 0 && event.y !== 0) {
|
|
91
66
|
this._focusedByMouse = true;
|
|
92
67
|
}
|
|
93
68
|
this._inputRef?.focus();
|
|
@@ -160,8 +135,7 @@ class SearchComponent extends Component<SearchProps, SearchState> {
|
|
|
160
135
|
</div>
|
|
161
136
|
<input
|
|
162
137
|
className={styles.searchInput}
|
|
163
|
-
|
|
164
|
-
placeholder={this.props.searchLabel}
|
|
138
|
+
placeholder={'Search in Transcript'}
|
|
165
139
|
value={searchQuery}
|
|
166
140
|
onInput={this._handleOnChange}
|
|
167
141
|
onFocus={this._onFocus}
|
|
@@ -173,82 +147,72 @@ class SearchComponent extends Component<SearchProps, SearchState> {
|
|
|
173
147
|
}}
|
|
174
148
|
/>
|
|
175
149
|
{searchQuery && (
|
|
176
|
-
<
|
|
177
|
-
<
|
|
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}>
|
|
178
176
|
<svg
|
|
179
|
-
width="
|
|
180
|
-
height="
|
|
181
|
-
viewBox="
|
|
177
|
+
width="14px"
|
|
178
|
+
height="12px"
|
|
179
|
+
viewBox="1 0 14 12"
|
|
182
180
|
version="1.1"
|
|
183
181
|
xmlns="http://www.w3.org/2000/svg"
|
|
184
182
|
xmlnsXlink="http://www.w3.org/1999/xlink">
|
|
185
|
-
<g id="Icons/
|
|
183
|
+
<g id="Icons/16/Arrow/-up" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
186
184
|
<path
|
|
187
|
-
d="
|
|
188
|
-
id="
|
|
189
|
-
fill="#cccccc"
|
|
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>
|
|
190
189
|
</g>
|
|
191
190
|
</svg>
|
|
192
191
|
</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>
|
|
224
192
|
)}
|
|
225
193
|
{searchQuery && (
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
</button>
|
|
246
|
-
</A11yWrapper>
|
|
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>
|
|
247
213
|
)}
|
|
248
214
|
</div>
|
|
249
215
|
</div>
|
|
250
216
|
);
|
|
251
217
|
}
|
|
252
218
|
}
|
|
253
|
-
|
|
254
|
-
export const Search = withText(translates)(SearchComponent);
|
|
@@ -8,13 +8,7 @@ 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
|
-
|
|
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
|
-
};
|
|
11
|
+
const {ENTER, Space, Tab, Esc} = KalturaPlayer.ui.utils.KeyMap;
|
|
18
12
|
|
|
19
13
|
export interface TranscriptProps {
|
|
20
14
|
onSeek(time: number): void;
|
|
@@ -34,7 +28,6 @@ export interface TranscriptProps {
|
|
|
34
28
|
highlightedMap: HighlightedMap;
|
|
35
29
|
pluginMode: PluginPositions;
|
|
36
30
|
onItemClicked: (n: number) => void;
|
|
37
|
-
autoScrollLabel?: string;
|
|
38
31
|
}
|
|
39
32
|
|
|
40
33
|
interface TranscriptState {
|
|
@@ -57,7 +50,7 @@ const initialSearch = {
|
|
|
57
50
|
|
|
58
51
|
const SEARCHBAR_HEIGHT = 38; // height of search bar with margins
|
|
59
52
|
|
|
60
|
-
export class
|
|
53
|
+
export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
61
54
|
private _transcriptListRef: HTMLElement | null = null;
|
|
62
55
|
private _captionListRef: any = null;
|
|
63
56
|
private _skipTranscriptButtonRef: HTMLDivElement | null = null;
|
|
@@ -113,7 +106,6 @@ export class TranscriptComponent extends Component<TranscriptProps, TranscriptSt
|
|
|
113
106
|
role="button"
|
|
114
107
|
className={`${styles.autoscrollButton} ${isAutoScrollEnabled ? '' : styles.autoscrollButtonVisible}`}
|
|
115
108
|
tabIndex={isAutoScrollEnabled ? -1 : 1}
|
|
116
|
-
aria-label={this.props.autoScrollLabel}
|
|
117
109
|
ref={node => {
|
|
118
110
|
this._autoscrollButtonRef = node;
|
|
119
111
|
}}>
|
|
@@ -209,13 +201,8 @@ export class TranscriptComponent extends Component<TranscriptProps, TranscriptSt
|
|
|
209
201
|
);
|
|
210
202
|
};
|
|
211
203
|
|
|
212
|
-
private _handleClick = (event: MouseEvent | KeyboardEvent) => {
|
|
213
|
-
event.preventDefault();
|
|
214
|
-
this._autoscrollButtonRef?.focus();
|
|
215
|
-
};
|
|
216
|
-
|
|
217
204
|
private _handleKeyDown = (event: KeyboardEvent) => {
|
|
218
|
-
if (event.keyCode ===
|
|
205
|
+
if (event.keyCode === Tab && !event.shiftKey) {
|
|
219
206
|
this.setState({
|
|
220
207
|
isAutoScrollEnabled: false
|
|
221
208
|
});
|
|
@@ -224,8 +211,9 @@ export class TranscriptComponent extends Component<TranscriptProps, TranscriptSt
|
|
|
224
211
|
event.preventDefault();
|
|
225
212
|
captionRef.focus();
|
|
226
213
|
}
|
|
227
|
-
} else if (event.keyCode === ENTER || event.keyCode ===
|
|
228
|
-
|
|
214
|
+
} else if (event.keyCode === ENTER || event.keyCode === Space) {
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
this._autoscrollButtonRef?.focus();
|
|
229
217
|
}
|
|
230
218
|
};
|
|
231
219
|
|
|
@@ -238,7 +226,6 @@ export class TranscriptComponent extends Component<TranscriptProps, TranscriptSt
|
|
|
238
226
|
}}
|
|
239
227
|
className={styles.skipTranscriptButton}
|
|
240
228
|
onKeyDown={this._handleKeyDown}
|
|
241
|
-
onClick={this._handleClick}
|
|
242
229
|
tabIndex={1}>
|
|
243
230
|
Skip transcript
|
|
244
231
|
</div>
|
|
@@ -364,7 +351,7 @@ export class TranscriptComponent extends Component<TranscriptProps, TranscriptSt
|
|
|
364
351
|
};
|
|
365
352
|
|
|
366
353
|
private _handleEsc = (event: KeyboardEvent) => {
|
|
367
|
-
if (event.keyCode ===
|
|
354
|
+
if (event.keyCode === Esc) {
|
|
368
355
|
this.props.onClose();
|
|
369
356
|
}
|
|
370
357
|
};
|
|
@@ -399,5 +386,3 @@ export class TranscriptComponent extends Component<TranscriptProps, TranscriptSt
|
|
|
399
386
|
);
|
|
400
387
|
}
|
|
401
388
|
}
|
|
402
|
-
|
|
403
|
-
export const Transcript = withText(translates)(TranscriptComponent);
|
|
@@ -10,6 +10,14 @@ 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
|
+
});
|
|
13
21
|
|
|
14
22
|
interface TimedMetadataEvent {
|
|
15
23
|
payload: {
|
|
@@ -112,15 +120,14 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
|
|
|
112
120
|
};
|
|
113
121
|
|
|
114
122
|
private _onTimedMetadataChange = ({payload}: TimedMetadataEvent) => {
|
|
115
|
-
const transcriptCuePoints: Array<CuePoint> = payload.cues
|
|
116
|
-
.
|
|
117
|
-
return cue.metadata.cuePointType === ItemTypes.Caption;
|
|
123
|
+
const transcriptCuePoints: Array<CuePoint> = payload.cues.filter((cue: CuePoint) => {
|
|
124
|
+
return cue.metadata.cuePointType === ItemTypes.Caption;
|
|
118
125
|
})
|
|
119
126
|
.filter((cue, index, array) => {
|
|
120
127
|
// filter out captions that has endTime eq to next caption startTime
|
|
121
128
|
const nextCue = array[index + 1];
|
|
122
129
|
return !nextCue || cue.endTime !== nextCue.startTime;
|
|
123
|
-
|
|
130
|
+
});
|
|
124
131
|
this._activeCuePointsMap = {};
|
|
125
132
|
transcriptCuePoints.forEach(cue => {
|
|
126
133
|
this._activeCuePointsMap[cue.id] = true;
|
|
@@ -159,14 +166,17 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
|
|
|
159
166
|
label: 'Download or print transcript',
|
|
160
167
|
area: ReservedPresetAreas.TopBarRightControls,
|
|
161
168
|
presets: [ReservedPresetNames.Playback, ReservedPresetNames.Live],
|
|
162
|
-
get: () => (
|
|
169
|
+
get: withText(translates)(({printDownloadAreaLabel, printTranscript, downloadTranscript}: Record<string, string>) => (
|
|
163
170
|
<DownloadPrintMenu
|
|
164
171
|
onDownload={this._handleDownload}
|
|
165
172
|
onPrint={this._handlePrint}
|
|
166
173
|
downloadDisabled={getConfigValue(downloadDisabled, isBoolean, false)}
|
|
167
174
|
printDisabled={getConfigValue(printDisabled, isBoolean, false)}
|
|
175
|
+
dropdownAriaLabel={printDownloadAreaLabel}
|
|
176
|
+
printButtonAriaLabel={printTranscript}
|
|
177
|
+
downloadButtonAriaLabel={downloadTranscript}
|
|
168
178
|
/>
|
|
169
|
-
)
|
|
179
|
+
))
|
|
170
180
|
});
|
|
171
181
|
}
|
|
172
182
|
|
|
@@ -204,18 +214,21 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
|
|
|
204
214
|
},
|
|
205
215
|
iconComponent: ({isActive}: {isActive: boolean}) => {
|
|
206
216
|
return (
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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>
|
|
219
232
|
);
|
|
220
233
|
},
|
|
221
234
|
presets: [ReservedPresetNames.Playback, ReservedPresetNames.Live, ReservedPresetNames.Ads],
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./popover";
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
@import '../../variables.scss';
|
|
2
|
-
|
|
3
|
-
.popover-container {
|
|
4
|
-
position: relative;
|
|
5
|
-
.popover-component {
|
|
6
|
-
background-color: #222222;
|
|
7
|
-
border-radius: 4px;
|
|
8
|
-
position: absolute;
|
|
9
|
-
right: 0px;
|
|
10
|
-
font-size: 15px;
|
|
11
|
-
display: block;
|
|
12
|
-
&.hidden {
|
|
13
|
-
display: none;
|
|
14
|
-
}
|
|
15
|
-
&.bottom {
|
|
16
|
-
top: 100%;
|
|
17
|
-
margin-top: 6px;
|
|
18
|
-
}
|
|
19
|
-
&.left {
|
|
20
|
-
right: 0px;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
.popover-menu {
|
|
25
|
-
padding-top: 6px;
|
|
26
|
-
padding-bottom: 6px;
|
|
27
|
-
}
|
|
28
|
-
.popover-menu-item {
|
|
29
|
-
display: flex;
|
|
30
|
-
align-items: center;
|
|
31
|
-
padding: 9px 24px 9px 16px;
|
|
32
|
-
white-space: nowrap;
|
|
33
|
-
min-height: 30px;
|
|
34
|
-
line-height: 18px;
|
|
35
|
-
font-size: 15px;
|
|
36
|
-
&:hover {
|
|
37
|
-
cursor: pointer;
|
|
38
|
-
background-color: $semigray-color;
|
|
39
|
-
}
|
|
40
|
-
&:focus {
|
|
41
|
-
outline: 1px solid $focus-color;
|
|
42
|
-
}
|
|
43
|
-
}
|