@js-toolkit/web-utils 1.67.0 → 1.68.0
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/EventEmitterListener.d.ts +3 -3
- package/EventEmitterListener.js +222 -1
- package/EventEmitterListener.utils.d.ts +24 -13
- package/EventEmitterListener.utils.js +41 -1
- package/EventListeners.js +58 -1
- package/FullscreenController.js +193 -1
- package/README.md +159 -20
- package/WakeLockController.js +76 -1
- package/base64ToDataUrl.js +3 -1
- package/blobToDataUrl.js +10 -1
- package/checkPermission.js +8 -1
- package/copyToClipboard.js +37 -1
- package/createLoop.js +30 -1
- package/createRafLoop.js +56 -1
- package/dataUrlToBlob.js +13 -1
- package/fromBase64.js +10 -1
- package/fullscreen.js +167 -1
- package/fullscreenUtils.js +37 -1
- package/getAspectRatio.js +8 -1
- package/getBrowserLanguage.js +10 -1
- package/getCurrentScriptUrl.js +4 -1
- package/getEventAwaiter.js +41 -1
- package/getGeoCoordinates.js +6 -1
- package/getGeoLocality.js +19 -1
- package/getInnerRect.js +8 -1
- package/getInnerXDimensions.js +9 -1
- package/getInnerYDimensions.js +9 -1
- package/getPinchZoomHandlers.js +134 -1
- package/getRandomID.js +4 -1
- package/getScreenSize.js +23 -1
- package/getSecondsCounter.js +47 -1
- package/iframe/getAutoConnector.js +251 -1
- package/iframe/getOriginFromMessage.js +3 -1
- package/iframe/isIframeLoaded.js +9 -1
- package/iframe/messages.d.ts +2 -2
- package/iframe/messages.js +50 -1
- package/iframe/utils.js +33 -1
- package/imageToBlob.js +20 -1
- package/isImageTypeSupported.js +8 -1
- package/isWebPSupported.js +15 -1
- package/loadImage.js +29 -1
- package/loadScript.d.ts +1 -1
- package/loadScript.js +67 -1
- package/media/Capabilities.js +44 -1
- package/media/MediaNotAttachedError.d.ts +1 -1
- package/media/MediaNotAttachedError.js +6 -1
- package/media/MediaStreamController.js +84 -1
- package/media/PipController.d.ts +2 -2
- package/media/PipController.js +140 -1
- package/media/TextTracksController/TextTracksController.d.ts +0 -3
- package/media/TextTracksController/TextTracksController.js +251 -1
- package/media/TextTracksController/index.js +1 -1
- package/media/TextTracksController/utils.js +147 -1
- package/media/getDurationTime.js +3 -1
- package/media/getMediaSource.js +11 -1
- package/media/getSourceBuffer.js +5 -1
- package/media/isMediaSeekable.js +4 -1
- package/media/parseCueText.js +224 -1
- package/media/resetMedia.js +17 -1
- package/media/timeRanges.js +9 -1
- package/media/toggleNativeSubtitles.js +21 -1
- package/metrics/ga/DataLayerProxy.js +11 -1
- package/metrics/ga/getHandler.js +99 -1
- package/metrics/ga/types.js +1 -1
- package/metrics/types.js +1 -1
- package/metrics/yandex/DataLayerProxy.js +11 -1
- package/metrics/yandex/getHandler.js +63 -1
- package/metrics/yandex/types.js +2 -1
- package/onDOMReady.js +12 -1
- package/onPageReady.js +27 -1
- package/package.json +16 -13
- package/patchConsoleLogging.js +27 -1
- package/performance/getNavigationTiming.js +14 -1
- package/platform/Semver.js +23 -1
- package/platform/getChromeVersion.d.ts +2 -0
- package/platform/getChromeVersion.js +16 -0
- package/platform/getIOSVersion.js +18 -1
- package/platform/getPlatformInfo.js +49 -1
- package/platform/isAirPlayAvailable.js +3 -1
- package/platform/isAndroid.js +7 -1
- package/platform/isChrome.js +7 -1
- package/platform/isEMESupported.js +9 -1
- package/platform/isIOS.js +9 -1
- package/platform/isMSESupported.js +16 -1
- package/platform/isMacOS.js +12 -1
- package/platform/isMediaCapabilitiesSupported.js +6 -1
- package/platform/isMobile.js +16 -1
- package/platform/isMobileSimulation.js +7 -1
- package/platform/isSafari.js +14 -1
- package/platform/isScreenHDR.js +4 -1
- package/platform/isStandaloneApp.js +4 -1
- package/platform/isTelegramWebView.js +3 -1
- package/platform/isTouchSupported.js +4 -1
- package/preventDefault.d.ts +2 -2
- package/preventDefault.js +5 -1
- package/rafCallback.js +9 -1
- package/responsive/MediaQuery.js +40 -1
- package/responsive/MediaQueryListener.js +55 -1
- package/responsive/ViewSize.js +82 -1
- package/responsive/getViewSizeQueryMap.js +7 -1
- package/saveFileAs.js +22 -1
- package/serviceWorker/ServiceWorkerInstaller.d.ts +0 -1
- package/serviceWorker/ServiceWorkerInstaller.js +112 -1
- package/serviceWorker/utils.d.ts +1 -1
- package/serviceWorker/utils.js +86 -1
- package/stopPropagation.d.ts +2 -2
- package/stopPropagation.js +3 -1
- package/takeScreenshot.js +51 -1
- package/toBase64.js +9 -1
- package/toLocalPoint.js +9 -1
- package/types/index.js +2 -1
- package/types/refs.js +1 -1
- package/viewableTracker.js +69 -1
- package/webrtc/PeerConnection.js +212 -1
- package/webrtc/sdputils.js +417 -1
- package/ws/WSController.js +148 -1
package/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @js-toolkit/web-utils
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.org/package/@js-toolkit/web-utils)
|
|
4
|
+
[](https://www.npmjs.org/package/@js-toolkit/web-utils)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
TypeScript utilities for the browser: media, WebSocket, WebRTC, fullscreen, platform detection, responsive helpers, events, DOM, service workers, and more.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Install the library via npm:
|
|
8
|
+
## Install
|
|
10
9
|
|
|
11
10
|
```bash
|
|
12
11
|
yarn add @js-toolkit/web-utils
|
|
@@ -14,28 +13,168 @@ yarn add @js-toolkit/web-utils
|
|
|
14
13
|
npm install @js-toolkit/web-utils
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
## Import
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
Use subpath imports for tree-shaking:
|
|
20
19
|
|
|
21
|
-
```
|
|
20
|
+
```typescript
|
|
21
|
+
import { FullscreenController } from '@js-toolkit/web-utils/FullscreenController';
|
|
22
22
|
import { getRandomID } from '@js-toolkit/web-utils/getRandomID';
|
|
23
|
+
import { isMobile } from '@js-toolkit/web-utils/platform/isMobile';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## API Overview
|
|
27
|
+
|
|
28
|
+
### Media and Video
|
|
29
|
+
|
|
30
|
+
| Module | Description |
|
|
31
|
+
|--------|-------------|
|
|
32
|
+
| `media/TextTracksController` | HTML5 video text tracks management |
|
|
33
|
+
| `media/PipController` | Picture-in-Picture controller |
|
|
34
|
+
| `media/MediaStreamController` | MediaStream attach/detach |
|
|
35
|
+
| `media/Capabilities` | Media type support detection |
|
|
36
|
+
| `media/timeRanges` | TimeRanges utilities |
|
|
37
|
+
| `media/resetMedia` | Reset media element and clear decoder buffer |
|
|
38
|
+
| `media/getMediaSource` | Get MediaSource or ManagedMediaSource |
|
|
39
|
+
| `media/getSourceBuffer` | Get SourceBuffer or ManagedSourceBuffer |
|
|
40
|
+
| `media/parseCueText` | WebVTT cue text parser |
|
|
41
|
+
|
|
42
|
+
### WebSocket and WebRTC
|
|
43
|
+
|
|
44
|
+
| Module | Description |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `ws/WSController` | WebSocket controller with auto-reconnect |
|
|
47
|
+
| `webrtc/PeerConnection` | RTCPeerConnection wrapper |
|
|
48
|
+
| `webrtc/sdputils` | SDP manipulation utilities |
|
|
49
|
+
|
|
50
|
+
### Fullscreen
|
|
51
|
+
|
|
52
|
+
| Module | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `FullscreenController` | Fullscreen API controller with fallback |
|
|
55
|
+
| `fullscreen` | Cross-browser fullscreen utilities |
|
|
56
|
+
| `fullscreenUtils` | Pseudo-fullscreen helpers |
|
|
57
|
+
|
|
58
|
+
### Platform Detection
|
|
59
|
+
|
|
60
|
+
| Module | Description |
|
|
61
|
+
|--------|-------------|
|
|
62
|
+
| `platform/getPlatformInfo` | Browser/platform detection (ua-parser-js) |
|
|
63
|
+
| `platform/isSafari`, `isChrome` | Browser detection |
|
|
64
|
+
| `platform/isIOS`, `isAndroid`, `isMacOS` | OS detection |
|
|
65
|
+
| `platform/isMobile` | Mobile device detection |
|
|
66
|
+
| `platform/isTouchSupported` | Touch support detection |
|
|
67
|
+
| `platform/isMSESupported` | Media Source Extensions support |
|
|
68
|
+
| `platform/isEMESupported` | Encrypted Media Extensions support |
|
|
69
|
+
| `platform/isAirPlayAvailable` | AirPlay availability |
|
|
70
|
+
| `platform/Semver` | Semantic version parser |
|
|
71
|
+
|
|
72
|
+
### Responsive
|
|
73
|
+
|
|
74
|
+
| Module | Description |
|
|
75
|
+
|--------|-------------|
|
|
76
|
+
| `responsive/MediaQueryListener` | Media query change listener |
|
|
77
|
+
| `responsive/MediaQuery` | Static media query utilities |
|
|
78
|
+
| `responsive/ViewSize` | Viewport size enum and helpers |
|
|
79
|
+
|
|
80
|
+
### Events
|
|
81
|
+
|
|
82
|
+
| Module | Description |
|
|
83
|
+
|--------|-------------|
|
|
84
|
+
| `EventEmitterListener` | Unified listener for DOM/EventEmitter/EventTarget |
|
|
85
|
+
| `EventListeners` | Multi-target event listener manager |
|
|
86
|
+
| `getEventAwaiter` | Promise-based event waiting |
|
|
87
|
+
|
|
88
|
+
### DOM
|
|
89
|
+
|
|
90
|
+
| Module | Description |
|
|
91
|
+
|--------|-------------|
|
|
92
|
+
| `onDOMReady` | Execute callback when DOM is ready |
|
|
93
|
+
| `onPageReady` | Execute callback when page is loaded |
|
|
94
|
+
| `getInnerRect` | Element inner dimensions |
|
|
95
|
+
| `toLocalPoint` | Convert coordinates to local element space |
|
|
96
|
+
| `loadScript` | Dynamic script loading with deduplication |
|
|
97
|
+
| `copyToClipboard` | Copy text to clipboard |
|
|
98
|
+
|
|
99
|
+
### Files and Blobs
|
|
100
|
+
|
|
101
|
+
| Module | Description |
|
|
102
|
+
|--------|-------------|
|
|
103
|
+
| `saveFileAs` | Trigger file download |
|
|
104
|
+
| `loadImage` | Load image with caching |
|
|
105
|
+
| `blobToDataUrl`, `dataUrlToBlob` | Blob/DataURL conversions |
|
|
106
|
+
| `toBase64`, `fromBase64` | Unicode-safe base64 encoding |
|
|
107
|
+
| `takeScreenshot` | Capture screenshot from CanvasImageSource |
|
|
23
108
|
|
|
24
|
-
|
|
109
|
+
### Animation
|
|
110
|
+
|
|
111
|
+
| Module | Description |
|
|
112
|
+
|--------|-------------|
|
|
113
|
+
| `createLoop` | Interval-based loop with RAF throttling |
|
|
114
|
+
| `createRafLoop` | RequestAnimationFrame loop |
|
|
115
|
+
|
|
116
|
+
### Metrics
|
|
117
|
+
|
|
118
|
+
| Module | Description |
|
|
119
|
+
|--------|-------------|
|
|
120
|
+
| `metrics/ga/*` | Google Analytics helpers |
|
|
121
|
+
| `metrics/yandex/*` | Yandex Metrica helpers |
|
|
122
|
+
|
|
123
|
+
### Other
|
|
124
|
+
|
|
125
|
+
| Module | Description |
|
|
126
|
+
|--------|-------------|
|
|
127
|
+
| `getRandomID` | Random ID via crypto API |
|
|
128
|
+
| `getBrowserLanguage` | Browser language detection |
|
|
129
|
+
| `WakeLockController` | Screen wake lock |
|
|
130
|
+
| `serviceWorker/ServiceWorkerInstaller` | Service worker registration |
|
|
131
|
+
| `iframe/*` | Iframe parent-child communication |
|
|
132
|
+
| `viewableTracker` | Element visibility tracking |
|
|
133
|
+
|
|
134
|
+
## Usage Examples
|
|
135
|
+
|
|
136
|
+
### FullscreenController
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { FullscreenController } from '@js-toolkit/web-utils/FullscreenController';
|
|
140
|
+
|
|
141
|
+
const controller = new FullscreenController(document.getElementById('player')!);
|
|
142
|
+
await controller.requestFullscreen();
|
|
143
|
+
console.log(controller.isFullscreen);
|
|
25
144
|
```
|
|
26
145
|
|
|
27
|
-
|
|
146
|
+
### Platform detection
|
|
28
147
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
148
|
+
```typescript
|
|
149
|
+
import { isMobile } from '@js-toolkit/web-utils/platform/isMobile';
|
|
150
|
+
import { isSafari } from '@js-toolkit/web-utils/platform/isSafari';
|
|
32
151
|
|
|
33
|
-
|
|
152
|
+
if (isMobile()) {
|
|
153
|
+
// mobile-specific logic
|
|
154
|
+
}
|
|
155
|
+
if (isSafari()) {
|
|
156
|
+
// Safari workaround
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Event awaiter
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { getEventAwaiter } from '@js-toolkit/web-utils/getEventAwaiter';
|
|
164
|
+
|
|
165
|
+
const video = document.querySelector('video')!;
|
|
166
|
+
await getEventAwaiter(video, 'canplay').wait();
|
|
167
|
+
video.play();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Dynamic script loading
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { loadScript } from '@js-toolkit/web-utils/loadScript';
|
|
174
|
+
|
|
175
|
+
await loadScript('https://cdn.example.com/sdk.js');
|
|
176
|
+
```
|
|
34
177
|
|
|
35
|
-
|
|
178
|
+
## Repository
|
|
36
179
|
|
|
37
|
-
|
|
38
|
-
1. Create a new branch (git checkout -b feature/my-feature-name).
|
|
39
|
-
1. Commit your changes (git commit -m 'Add some feature').
|
|
40
|
-
1. Push the branch (git push origin feature/my-feature-name).
|
|
41
|
-
1. Open a pull request.
|
|
180
|
+
[https://github.com/js-toolkit/web-utils](https://github.com/js-toolkit/web-utils)
|
package/WakeLockController.js
CHANGED
|
@@ -1 +1,76 @@
|
|
|
1
|
-
import{EventEmitter}from
|
|
1
|
+
import { EventEmitter } from '@js-toolkit/utils/EventEmitter';
|
|
2
|
+
import { hasIn } from '@js-toolkit/utils/hasIn';
|
|
3
|
+
export class WakeLockController extends EventEmitter {
|
|
4
|
+
static isApiAvailable() {
|
|
5
|
+
return hasIn(navigator, 'wakeLock') && navigator.wakeLock != null;
|
|
6
|
+
}
|
|
7
|
+
wakelock;
|
|
8
|
+
releasing = false;
|
|
9
|
+
/** Lock when document will be visible. */
|
|
10
|
+
relockOnVisible = false;
|
|
11
|
+
restoreWakeLock = () => {
|
|
12
|
+
if (document.visibilityState === 'visible' && this.relockOnVisible) {
|
|
13
|
+
this.relockOnVisible = false;
|
|
14
|
+
this.request().catch((error) => this.emit('error', { error }));
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
onRelease = () => {
|
|
18
|
+
if (!this.wakelock)
|
|
19
|
+
return;
|
|
20
|
+
this.wakelock.removeEventListener('release', this.onRelease);
|
|
21
|
+
this.wakelock = undefined;
|
|
22
|
+
// Unsubscribe only if document is visible else keep subscription to re-lock when document will be visible again.
|
|
23
|
+
// Release the wakelock happens before `visibilitychange` callback.
|
|
24
|
+
if (this.releasing) {
|
|
25
|
+
document.removeEventListener('visibilitychange', this.restoreWakeLock);
|
|
26
|
+
}
|
|
27
|
+
// If not releasing via api
|
|
28
|
+
else if (document.visibilityState === 'hidden') {
|
|
29
|
+
this.relockOnVisible = true;
|
|
30
|
+
document.addEventListener('visibilitychange', this.restoreWakeLock);
|
|
31
|
+
}
|
|
32
|
+
this.emit('deactivated');
|
|
33
|
+
};
|
|
34
|
+
isActive() {
|
|
35
|
+
return !!this.wakelock && !this.wakelock.released;
|
|
36
|
+
}
|
|
37
|
+
async request() {
|
|
38
|
+
if (this.wakelock)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
this.wakelock = await navigator.wakeLock.request('screen');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Probably do not have permissions.
|
|
45
|
+
if (document.visibilityState === 'visible')
|
|
46
|
+
throw error;
|
|
47
|
+
// If trying to wakelock when document inactive.
|
|
48
|
+
this.relockOnVisible = true;
|
|
49
|
+
document.addEventListener('visibilitychange', this.restoreWakeLock);
|
|
50
|
+
this.emit('error', { error });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.relockOnVisible = false;
|
|
54
|
+
this.wakelock.addEventListener('release', this.onRelease);
|
|
55
|
+
this.emit('activated');
|
|
56
|
+
}
|
|
57
|
+
async release() {
|
|
58
|
+
if (!this.wakelock || this.wakelock.released)
|
|
59
|
+
return;
|
|
60
|
+
try {
|
|
61
|
+
this.releasing = true;
|
|
62
|
+
await this.wakelock.release();
|
|
63
|
+
this.relockOnVisible = false;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
this.releasing = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async destroy() {
|
|
70
|
+
await this.release();
|
|
71
|
+
this.removeAllListeners();
|
|
72
|
+
}
|
|
73
|
+
[Symbol.asyncDispose]() {
|
|
74
|
+
return this.destroy();
|
|
75
|
+
}
|
|
76
|
+
}
|
package/base64ToDataUrl.js
CHANGED
package/blobToDataUrl.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
export function blobToDataUrl(
|
|
1
|
+
export function blobToDataUrl(blob) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const reader = new FileReader();
|
|
4
|
+
reader.onload = () => {
|
|
5
|
+
resolve(reader.result);
|
|
6
|
+
};
|
|
7
|
+
reader.onerror = reject;
|
|
8
|
+
reader.readAsDataURL(blob);
|
|
9
|
+
});
|
|
10
|
+
}
|
package/checkPermission.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
export async function checkPermission(
|
|
1
|
+
export async function checkPermission(name) {
|
|
2
|
+
try {
|
|
3
|
+
const result = await navigator.permissions?.query?.({ name });
|
|
4
|
+
return result.state;
|
|
5
|
+
}
|
|
6
|
+
catch { }
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
package/copyToClipboard.js
CHANGED
|
@@ -1 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
|
2
|
+
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-deprecated */
|
|
4
|
+
export function copyToClipboard(text) {
|
|
5
|
+
if (navigator.clipboard) {
|
|
6
|
+
return navigator.clipboard.writeText(text);
|
|
7
|
+
}
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const textarea = document.createElement('textarea');
|
|
10
|
+
textarea.textContent = text;
|
|
11
|
+
textarea.style.position = 'fixed';
|
|
12
|
+
textarea.style.bottom = '-100px';
|
|
13
|
+
textarea.readOnly = true; // To avoid showing keyboard;
|
|
14
|
+
document.body.appendChild(textarea);
|
|
15
|
+
const range = document.createRange();
|
|
16
|
+
range.selectNode(textarea);
|
|
17
|
+
const selection = window.getSelection();
|
|
18
|
+
if (!selection) {
|
|
19
|
+
reject(new Error('No selection for copy to clipboard.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
selection.removeAllRanges();
|
|
23
|
+
selection.addRange(range);
|
|
24
|
+
textarea.setSelectionRange(0, 999999);
|
|
25
|
+
try {
|
|
26
|
+
document.execCommand('copy', false);
|
|
27
|
+
resolve();
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
reject(err);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
selection.removeAllRanges();
|
|
34
|
+
document.body.removeChild(textarea);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
package/createLoop.js
CHANGED
|
@@ -1 +1,30 @@
|
|
|
1
|
-
export function createLoop()
|
|
1
|
+
export function createLoop() {
|
|
2
|
+
let timer;
|
|
3
|
+
let raf;
|
|
4
|
+
let active = false;
|
|
5
|
+
let rafCallback;
|
|
6
|
+
const call = () => {
|
|
7
|
+
if (active && rafCallback) {
|
|
8
|
+
raf != null && cancelAnimationFrame(raf);
|
|
9
|
+
raf = requestAnimationFrame(rafCallback);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
start: (callback, wait) => {
|
|
14
|
+
rafCallback = callback;
|
|
15
|
+
if (!active) {
|
|
16
|
+
active = true;
|
|
17
|
+
timer = window.setInterval(call, wait);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
stop: () => {
|
|
21
|
+
if (active) {
|
|
22
|
+
active = false;
|
|
23
|
+
window.clearInterval(timer);
|
|
24
|
+
raf != null && cancelAnimationFrame(raf);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
call,
|
|
28
|
+
isActive: () => active,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/createRafLoop.js
CHANGED
|
@@ -1 +1,56 @@
|
|
|
1
|
-
export function createRafLoop()
|
|
1
|
+
export function createRafLoop() {
|
|
2
|
+
let active = false;
|
|
3
|
+
let timer;
|
|
4
|
+
let raf;
|
|
5
|
+
let suspendTimeout = 0;
|
|
6
|
+
let scope = window;
|
|
7
|
+
let rafCallback;
|
|
8
|
+
const reset = () => {
|
|
9
|
+
scope.clearTimeout(timer);
|
|
10
|
+
raf != null && scope.cancelAnimationFrame(raf);
|
|
11
|
+
raf = undefined;
|
|
12
|
+
suspendTimeout = 0;
|
|
13
|
+
timer = undefined;
|
|
14
|
+
};
|
|
15
|
+
const call = () => {
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
17
|
+
raf = scope.requestAnimationFrame(step);
|
|
18
|
+
};
|
|
19
|
+
const step = (time) => {
|
|
20
|
+
if (!rafCallback)
|
|
21
|
+
return;
|
|
22
|
+
if (active) {
|
|
23
|
+
rafCallback(time);
|
|
24
|
+
if (suspendTimeout > 0) {
|
|
25
|
+
timer = scope.setTimeout(call, suspendTimeout);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
call();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Wait last call
|
|
32
|
+
else if (raf != null || timer != null) {
|
|
33
|
+
reset();
|
|
34
|
+
rafCallback(time);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
start: (callback, { suspendTimeout: _suspendTimeout = 0, scope: _scope = window } = {}) => {
|
|
39
|
+
rafCallback = callback;
|
|
40
|
+
suspendTimeout = _suspendTimeout;
|
|
41
|
+
scope = _scope;
|
|
42
|
+
if (!active) {
|
|
43
|
+
active = true;
|
|
44
|
+
call();
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
stop: (waitLast) => {
|
|
48
|
+
if (active) {
|
|
49
|
+
active = false;
|
|
50
|
+
if (!waitLast)
|
|
51
|
+
reset();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
isActive: () => active,
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dataUrlToBlob.js
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
// https://stackoverflow.com/a/12300351
|
|
2
|
+
export function dataUrlToBlob(dataUrl) {
|
|
3
|
+
const [type, base64] = dataUrl.split(',');
|
|
4
|
+
const byteString = window.atob(base64);
|
|
5
|
+
const mimeString = type.split(':')[1].split(';')[0];
|
|
6
|
+
const ab = new ArrayBuffer(byteString.length);
|
|
7
|
+
const ia = new Uint8Array(ab);
|
|
8
|
+
const { length } = byteString;
|
|
9
|
+
for (let i = 0; i < length; i += 1) {
|
|
10
|
+
ia[i] = byteString.charCodeAt(i);
|
|
11
|
+
}
|
|
12
|
+
return new Blob([ab], { type: mimeString });
|
|
13
|
+
}
|
package/fromBase64.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-deprecated */
|
|
3
|
+
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
|
4
|
+
/** Safe for unicode string. */
|
|
5
|
+
export function fromBase64(str) {
|
|
6
|
+
const binString = window.atob(str);
|
|
7
|
+
return window.TextDecoder
|
|
8
|
+
? new TextDecoder().decode(Uint8Array.from(binString, (m) => m.codePointAt(0)))
|
|
9
|
+
: decodeURIComponent(escape(binString));
|
|
10
|
+
}
|
package/fullscreen.js
CHANGED
|
@@ -1 +1,167 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
2
|
+
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
4
|
+
import { es5ErrorCompat } from '@js-toolkit/utils/es5ErrorCompat';
|
|
5
|
+
import { promisify } from '@js-toolkit/utils/promisify';
|
|
6
|
+
export class FullscreenUnavailableError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super('Fullscreen is not available');
|
|
9
|
+
es5ErrorCompat(this, FullscreenUnavailableError);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export var fullscreen;
|
|
13
|
+
(function (fullscreen) {
|
|
14
|
+
fullscreen.names = (() => {
|
|
15
|
+
const fnNames = [
|
|
16
|
+
{
|
|
17
|
+
requestFullscreenName: 'requestFullscreen',
|
|
18
|
+
exitFullscreenName: 'exitFullscreen',
|
|
19
|
+
fullscreenElementName: 'fullscreenElement',
|
|
20
|
+
fullscreenEnabledName: 'fullscreenEnabled',
|
|
21
|
+
changeEventName: 'fullscreenchange',
|
|
22
|
+
errorEventName: 'fullscreenerror',
|
|
23
|
+
},
|
|
24
|
+
// New WebKit
|
|
25
|
+
{
|
|
26
|
+
requestFullscreenName: 'webkitRequestFullscreen',
|
|
27
|
+
exitFullscreenName: 'webkitExitFullscreen',
|
|
28
|
+
fullscreenElementName: 'webkitFullscreenElement',
|
|
29
|
+
fullscreenEnabledName: 'webkitFullscreenEnabled',
|
|
30
|
+
changeEventName: 'webkitfullscreenchange',
|
|
31
|
+
errorEventName: 'webkitfullscreenerror',
|
|
32
|
+
},
|
|
33
|
+
// Old WebKit
|
|
34
|
+
{
|
|
35
|
+
requestFullscreenName: 'webkitRequestFullScreen',
|
|
36
|
+
exitFullscreenName: 'webkitCancelFullScreen',
|
|
37
|
+
fullscreenElementName: 'webkitCurrentFullScreenElement',
|
|
38
|
+
fullscreenEnabledName: 'webkitCancelFullScreen',
|
|
39
|
+
changeEventName: 'webkitfullscreenchange',
|
|
40
|
+
errorEventName: 'webkitfullscreenerror',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
requestFullscreenName: 'mozRequestFullScreen',
|
|
44
|
+
exitFullscreenName: 'mozCancelFullScreen',
|
|
45
|
+
fullscreenElementName: 'mozFullScreenElement',
|
|
46
|
+
fullscreenEnabledName: 'mozFullScreenEnabled',
|
|
47
|
+
changeEventName: 'mozfullscreenchange',
|
|
48
|
+
errorEventName: 'mozfullscreenerror',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
requestFullscreenName: 'msRequestFullscreen',
|
|
52
|
+
exitFullscreenName: 'msExitFullscreen',
|
|
53
|
+
fullscreenElementName: 'msFullscreenElement',
|
|
54
|
+
fullscreenEnabledName: 'msFullscreenEnabled',
|
|
55
|
+
changeEventName: 'MSFullscreenChange',
|
|
56
|
+
errorEventName: 'MSFullscreenError',
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
return fnNames.find(({ exitFullscreenName }) => exitFullscreenName in document);
|
|
60
|
+
})();
|
|
61
|
+
const eventNameMap = {
|
|
62
|
+
change: fullscreen.names?.changeEventName,
|
|
63
|
+
error: fullscreen.names?.errorEventName,
|
|
64
|
+
};
|
|
65
|
+
fullscreen.UnavailableError = FullscreenUnavailableError;
|
|
66
|
+
function isApiAvailable() {
|
|
67
|
+
return !!fullscreen.names;
|
|
68
|
+
}
|
|
69
|
+
fullscreen.isApiAvailable = isApiAvailable;
|
|
70
|
+
function isApiEnabled() {
|
|
71
|
+
// Coerce to boolean in case of old WebKit
|
|
72
|
+
return !!fullscreen.names && Boolean(document[fullscreen.names.fullscreenEnabledName]);
|
|
73
|
+
}
|
|
74
|
+
fullscreen.isApiEnabled = isApiEnabled;
|
|
75
|
+
function isFullscreen() {
|
|
76
|
+
if (!fullscreen.names)
|
|
77
|
+
throw new fullscreen.UnavailableError();
|
|
78
|
+
return Boolean(document[fullscreen.names.fullscreenElementName]);
|
|
79
|
+
}
|
|
80
|
+
fullscreen.isFullscreen = isFullscreen;
|
|
81
|
+
function getElement() {
|
|
82
|
+
if (!fullscreen.names)
|
|
83
|
+
throw new fullscreen.UnavailableError();
|
|
84
|
+
return document[fullscreen.names.fullscreenElementName];
|
|
85
|
+
}
|
|
86
|
+
fullscreen.getElement = getElement;
|
|
87
|
+
function on(type, listener, options) {
|
|
88
|
+
const eventName = eventNameMap[type];
|
|
89
|
+
if (eventName)
|
|
90
|
+
document.addEventListener(eventName, listener, options);
|
|
91
|
+
}
|
|
92
|
+
fullscreen.on = on;
|
|
93
|
+
function off(type, listener, options) {
|
|
94
|
+
const eventName = eventNameMap[type];
|
|
95
|
+
if (eventName)
|
|
96
|
+
document.removeEventListener(eventName, listener, options);
|
|
97
|
+
}
|
|
98
|
+
fullscreen.off = off;
|
|
99
|
+
function request(elem, options) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
if (!fullscreen.names) {
|
|
102
|
+
throw new fullscreen.UnavailableError();
|
|
103
|
+
}
|
|
104
|
+
const onFullScreenEntered = () => {
|
|
105
|
+
off('change', onFullScreenEntered);
|
|
106
|
+
off('error', onFullScreenError);
|
|
107
|
+
resolve();
|
|
108
|
+
};
|
|
109
|
+
const onFullScreenError = (event) => {
|
|
110
|
+
off('change', onFullScreenEntered);
|
|
111
|
+
off('error', onFullScreenError);
|
|
112
|
+
reject(event);
|
|
113
|
+
};
|
|
114
|
+
on('change', onFullScreenEntered);
|
|
115
|
+
on('error', onFullScreenError);
|
|
116
|
+
void elem[fullscreen.names.requestFullscreenName](options);
|
|
117
|
+
// const result = ((elem as AnyObject)[names.requestFullscreenName] as AnyAsyncFunction)(
|
|
118
|
+
// options
|
|
119
|
+
// );
|
|
120
|
+
// if (result instanceof Promise) {
|
|
121
|
+
// result.then(onFullScreenEntered).catch(onFullScreenError);
|
|
122
|
+
// }
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
fullscreen.request = request;
|
|
126
|
+
function exit() {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
if (!fullscreen.names) {
|
|
129
|
+
throw new fullscreen.UnavailableError();
|
|
130
|
+
}
|
|
131
|
+
if (!isFullscreen()) {
|
|
132
|
+
resolve();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const onFullScreenExited = () => {
|
|
136
|
+
off('change', onFullScreenExited);
|
|
137
|
+
off('error', onFullScreenError);
|
|
138
|
+
resolve();
|
|
139
|
+
};
|
|
140
|
+
const onFullScreenError = (event) => {
|
|
141
|
+
off('change', onFullScreenExited);
|
|
142
|
+
off('error', onFullScreenError);
|
|
143
|
+
reject(event);
|
|
144
|
+
};
|
|
145
|
+
on('change', onFullScreenExited);
|
|
146
|
+
on('error', onFullScreenError);
|
|
147
|
+
void document[fullscreen.names.exitFullscreenName]();
|
|
148
|
+
// const result = ((document as AnyObject)[names.exitFullscreenName] as AnyAsyncFunction)();
|
|
149
|
+
// if (result instanceof Promise) {
|
|
150
|
+
// result.then(onFullScreenExited).catch(onFullScreenError);
|
|
151
|
+
// }
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
fullscreen.exit = exit;
|
|
155
|
+
function toggle(elem) {
|
|
156
|
+
return promisify(() => (isFullscreen() ? exit() : request(elem)));
|
|
157
|
+
}
|
|
158
|
+
fullscreen.toggle = toggle;
|
|
159
|
+
function onChange(listener) {
|
|
160
|
+
on('change', listener);
|
|
161
|
+
}
|
|
162
|
+
fullscreen.onChange = onChange;
|
|
163
|
+
function onError(listener) {
|
|
164
|
+
on('error', listener);
|
|
165
|
+
}
|
|
166
|
+
fullscreen.onError = onError;
|
|
167
|
+
})(fullscreen || (fullscreen = {}));
|