@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.
Files changed (116) hide show
  1. package/EventEmitterListener.d.ts +3 -3
  2. package/EventEmitterListener.js +222 -1
  3. package/EventEmitterListener.utils.d.ts +24 -13
  4. package/EventEmitterListener.utils.js +41 -1
  5. package/EventListeners.js +58 -1
  6. package/FullscreenController.js +193 -1
  7. package/README.md +159 -20
  8. package/WakeLockController.js +76 -1
  9. package/base64ToDataUrl.js +3 -1
  10. package/blobToDataUrl.js +10 -1
  11. package/checkPermission.js +8 -1
  12. package/copyToClipboard.js +37 -1
  13. package/createLoop.js +30 -1
  14. package/createRafLoop.js +56 -1
  15. package/dataUrlToBlob.js +13 -1
  16. package/fromBase64.js +10 -1
  17. package/fullscreen.js +167 -1
  18. package/fullscreenUtils.js +37 -1
  19. package/getAspectRatio.js +8 -1
  20. package/getBrowserLanguage.js +10 -1
  21. package/getCurrentScriptUrl.js +4 -1
  22. package/getEventAwaiter.js +41 -1
  23. package/getGeoCoordinates.js +6 -1
  24. package/getGeoLocality.js +19 -1
  25. package/getInnerRect.js +8 -1
  26. package/getInnerXDimensions.js +9 -1
  27. package/getInnerYDimensions.js +9 -1
  28. package/getPinchZoomHandlers.js +134 -1
  29. package/getRandomID.js +4 -1
  30. package/getScreenSize.js +23 -1
  31. package/getSecondsCounter.js +47 -1
  32. package/iframe/getAutoConnector.js +251 -1
  33. package/iframe/getOriginFromMessage.js +3 -1
  34. package/iframe/isIframeLoaded.js +9 -1
  35. package/iframe/messages.d.ts +2 -2
  36. package/iframe/messages.js +50 -1
  37. package/iframe/utils.js +33 -1
  38. package/imageToBlob.js +20 -1
  39. package/isImageTypeSupported.js +8 -1
  40. package/isWebPSupported.js +15 -1
  41. package/loadImage.js +29 -1
  42. package/loadScript.d.ts +1 -1
  43. package/loadScript.js +67 -1
  44. package/media/Capabilities.js +44 -1
  45. package/media/MediaNotAttachedError.d.ts +1 -1
  46. package/media/MediaNotAttachedError.js +6 -1
  47. package/media/MediaStreamController.js +84 -1
  48. package/media/PipController.d.ts +2 -2
  49. package/media/PipController.js +140 -1
  50. package/media/TextTracksController/TextTracksController.d.ts +0 -3
  51. package/media/TextTracksController/TextTracksController.js +251 -1
  52. package/media/TextTracksController/index.js +1 -1
  53. package/media/TextTracksController/utils.js +147 -1
  54. package/media/getDurationTime.js +3 -1
  55. package/media/getMediaSource.js +11 -1
  56. package/media/getSourceBuffer.js +5 -1
  57. package/media/isMediaSeekable.js +4 -1
  58. package/media/parseCueText.js +224 -1
  59. package/media/resetMedia.js +17 -1
  60. package/media/timeRanges.js +9 -1
  61. package/media/toggleNativeSubtitles.js +21 -1
  62. package/metrics/ga/DataLayerProxy.js +11 -1
  63. package/metrics/ga/getHandler.js +99 -1
  64. package/metrics/ga/types.js +1 -1
  65. package/metrics/types.js +1 -1
  66. package/metrics/yandex/DataLayerProxy.js +11 -1
  67. package/metrics/yandex/getHandler.js +63 -1
  68. package/metrics/yandex/types.js +2 -1
  69. package/onDOMReady.js +12 -1
  70. package/onPageReady.js +27 -1
  71. package/package.json +16 -13
  72. package/patchConsoleLogging.js +27 -1
  73. package/performance/getNavigationTiming.js +14 -1
  74. package/platform/Semver.js +23 -1
  75. package/platform/getChromeVersion.d.ts +2 -0
  76. package/platform/getChromeVersion.js +16 -0
  77. package/platform/getIOSVersion.js +18 -1
  78. package/platform/getPlatformInfo.js +49 -1
  79. package/platform/isAirPlayAvailable.js +3 -1
  80. package/platform/isAndroid.js +7 -1
  81. package/platform/isChrome.js +7 -1
  82. package/platform/isEMESupported.js +9 -1
  83. package/platform/isIOS.js +9 -1
  84. package/platform/isMSESupported.js +16 -1
  85. package/platform/isMacOS.js +12 -1
  86. package/platform/isMediaCapabilitiesSupported.js +6 -1
  87. package/platform/isMobile.js +16 -1
  88. package/platform/isMobileSimulation.js +7 -1
  89. package/platform/isSafari.js +14 -1
  90. package/platform/isScreenHDR.js +4 -1
  91. package/platform/isStandaloneApp.js +4 -1
  92. package/platform/isTelegramWebView.js +3 -1
  93. package/platform/isTouchSupported.js +4 -1
  94. package/preventDefault.d.ts +2 -2
  95. package/preventDefault.js +5 -1
  96. package/rafCallback.js +9 -1
  97. package/responsive/MediaQuery.js +40 -1
  98. package/responsive/MediaQueryListener.js +55 -1
  99. package/responsive/ViewSize.js +82 -1
  100. package/responsive/getViewSizeQueryMap.js +7 -1
  101. package/saveFileAs.js +22 -1
  102. package/serviceWorker/ServiceWorkerInstaller.d.ts +0 -1
  103. package/serviceWorker/ServiceWorkerInstaller.js +112 -1
  104. package/serviceWorker/utils.d.ts +1 -1
  105. package/serviceWorker/utils.js +86 -1
  106. package/stopPropagation.d.ts +2 -2
  107. package/stopPropagation.js +3 -1
  108. package/takeScreenshot.js +51 -1
  109. package/toBase64.js +9 -1
  110. package/toLocalPoint.js +9 -1
  111. package/types/index.js +2 -1
  112. package/types/refs.js +1 -1
  113. package/viewableTracker.js +69 -1
  114. package/webrtc/PeerConnection.js +212 -1
  115. package/webrtc/sdputils.js +417 -1
  116. package/ws/WSController.js +148 -1
@@ -1 +1,224 @@
1
- import{splitRows}from"./TextTracksController/utils";const ESCAPE={"&amp;":"&","&lt;":"<","&gt;":">","&lrm;":"‎","&rlm;":"‏","&nbsp;":" "},TAG_NAME={c:"span",i:"i",b:"b",u:"u",ruby:"ruby",rt:"rt",lang:"span"},TAG_ANNOTATION={v:"title",lang:"lang"},NEEDS_PARENT={rt:"ruby"};function computeSeconds(e,t,n,o){return 3600*(+e||0)+60*(+t||0)+(+n||0)+(+o||0)/1e3}function parseTimeStamp(e){const t=e.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);if(!t)return;const[,n,o,r,l]=t;return l?computeSeconds(n,o,r.replace(":",""),l):+n>59?computeSeconds(n,o,"",l):computeSeconds("",n,o,l)}function unescape(e){let t,n=e;for(;t=n.match(/&(amp|lt|gt|lrm|rlm|nbsp);/);)n=n.replace(t[0],e=>ESCAPE[e]);return n}function nextToken(e){if(!e)return[e,void 0];const t=e.match(/^([^<]*)(<[^>]*>?)?/);if(!t)return[e,void 0];const n=t[1]?t[1]:t[2];return null==n?[e,n]:[e.substring(n.length),n]}function shouldAdd(e,t){return!NEEDS_PARENT[t.localName]||NEEDS_PARENT[t.localName]===e.localName}function createHtmlNode(e,t){const n=TAG_NAME[e];if(!n)return;const o=window.document.createElement(n),r=TAG_ANNOTATION[e];return r&&t&&(o[r]=t.trim()),o}export function parseCueText(e,t,{preferLineLength:n=0}={}){let o,r,l,s=e,c=-1;const a=[],u=[],i=[],d=e=>{0===u.length&&u.push([]),u.at(-1).push(t?t(e,(u.at(-1)??u.at(-2))?.at(-1)):e)},p=e=>{const t=a.pop()??"",n=o;if(o=n.parentElement||void 0,null==o){d({id:e,startTime:c>=0?c:void 0,tag:t,node:n}),r=n}},m=()=>`${u.length}-${(u.at(-1)?.length??0)+1}`,h=(e,t)=>{t?i.push(e):i[i.length-1]+=e,t&&u.push([]),null==o?0===e.trim().length&&!t&&r?r.appendChild(window.document.createTextNode(unescape(e))):(e=>{const t=createHtmlNode("c","");t.appendChild(window.document.createTextNode(unescape(e)));const n={id:m(),node:t,tag:"c",startTime:c>=0?c:void 0};d(n)})(e):o.appendChild(window.document.createTextNode(unescape(e)))};for(;null!=([s,l]=nextToken(s),l);)if("<"===l[0])if("/"===l[1])a.at(-1)===l.substring(2).replace(">","")&&p(m());else{const e=parseTimeStamp(l.substring(1,l.length-1));if(e)c=e;else{const e=l.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/),t=e&&createHtmlNode(e[1],e[3]);!t||o&&!shouldAdd(o,t)||(e[2]&&(t.className=e[2].substring(1).replace("."," ")),a.push(e[1]),o?.appendChild(t),o=t)}}else if(0===i.length||n>0&&i.at(-1).length+l.length>n){const e=splitRows(l,n),{length:t}=e;for(let n=0;n<t;n+=1)h(e[n],!0)}else h(l,!1);return{segments:u,rawText:i}}
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ /* eslint-disable no-cond-assign */
3
+ // https://github.com/mozilla/vtt.js/blob/master/lib/vtt.js
4
+ import { splitRows } from './TextTracksController/utils';
5
+ const ESCAPE = {
6
+ '&amp;': '&',
7
+ '&lt;': '<',
8
+ '&gt;': '>',
9
+ '&lrm;': '\u200e',
10
+ '&rlm;': '\u200f',
11
+ '&nbsp;': '\u00a0',
12
+ };
13
+ const TAG_NAME = {
14
+ c: 'span',
15
+ i: 'i',
16
+ b: 'b',
17
+ u: 'u',
18
+ ruby: 'ruby',
19
+ rt: 'rt',
20
+ // v: 'span', ignore voice tag because it might be unclosed and it is unsupported by parser.
21
+ lang: 'span',
22
+ };
23
+ const TAG_ANNOTATION = {
24
+ v: 'title',
25
+ lang: 'lang',
26
+ };
27
+ const NEEDS_PARENT = {
28
+ rt: 'ruby',
29
+ };
30
+ function computeSeconds(h, m, s, f) {
31
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
32
+ return (+h || 0) * 3600 + (+m || 0) * 60 + (+s || 0) + (+f || 0) / 1000;
33
+ }
34
+ // Try to parse input as a time stamp.
35
+ function parseTimeStamp(input) {
36
+ const matchAr = /^(\d+):(\d{2})(:\d{2})?\.(\d{3})/.exec(input);
37
+ if (!matchAr) {
38
+ return undefined;
39
+ }
40
+ const [, h, m, s, f] = matchAr;
41
+ // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
42
+ if (f) {
43
+ return computeSeconds(h, m, s.replace(':', ''), f);
44
+ }
45
+ // Timestamp takes the form of [hours]:[minutes].[milliseconds]
46
+ // First position is hours as it's over 59.
47
+ if (+h > 59) {
48
+ return computeSeconds(h, m, '', f);
49
+ }
50
+ // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
51
+ return computeSeconds('', h, m, f);
52
+ }
53
+ function unescape(str0) {
54
+ let str = str0;
55
+ let matchAr;
56
+ while ((matchAr = /&(amp|lt|gt|lrm|rlm|nbsp);/.exec(str))) {
57
+ // Unescape a string 's'.
58
+ str = str.replace(matchAr[0], (s) => ESCAPE[s]);
59
+ }
60
+ return str;
61
+ }
62
+ function nextToken(input) {
63
+ // Check for end-of-string.
64
+ if (!input) {
65
+ return [input, undefined];
66
+ }
67
+ const matchAr = /^([^<]*)(<[^>]*>?)?/.exec(input);
68
+ if (!matchAr) {
69
+ return [input, undefined];
70
+ }
71
+ // If there is some text before the next tag, return it, otherwise return the tag.
72
+ const token = matchAr[1] ? matchAr[1] : matchAr[2];
73
+ // If tag is invalid or it is not a tag. Eg: <>.
74
+ if (token == null) {
75
+ return [input, token];
76
+ }
77
+ // Consume 'n' characters from the input.
78
+ return [input.substring(token.length), token];
79
+ }
80
+ function shouldAdd(current, element) {
81
+ return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
82
+ }
83
+ // Create an element for this tag.
84
+ function createHtmlNode(type, annotation) {
85
+ const tagName = TAG_NAME[type];
86
+ if (!tagName) {
87
+ return undefined;
88
+ }
89
+ const element = window.document.createElement(tagName);
90
+ // element.localName = tagName;
91
+ const name = TAG_ANNOTATION[type];
92
+ if (name && annotation) {
93
+ element[name] = annotation.trim();
94
+ }
95
+ return element;
96
+ }
97
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format#cue_payload
98
+ export function parseCueText(input0, map, { preferLineLength = 0 } = {}) {
99
+ let current;
100
+ let prevCurrent;
101
+ let input = input0;
102
+ let token;
103
+ let timeStamp = -1;
104
+ const tagStack = [];
105
+ const segments = [];
106
+ const rawText = [];
107
+ const addSegmentToRow = (segment) => {
108
+ if (segments.length === 0)
109
+ segments.push([]);
110
+ segments
111
+ .at(-1)
112
+ .push(map ? map(segment, (segments.at(-1) ?? segments.at(-2))?.at(-1)) : segment);
113
+ };
114
+ const addSegment = (id) => {
115
+ const tag = tagStack.pop() ?? '';
116
+ const node = current;
117
+ current = node.parentElement ?? undefined;
118
+ if (current == null) {
119
+ const segment = {
120
+ id,
121
+ startTime: timeStamp >= 0 ? timeStamp : undefined,
122
+ tag,
123
+ node,
124
+ };
125
+ addSegmentToRow(segment);
126
+ // All next nodes will be with the same timeStamp until the next timeStamp.
127
+ // timeStamp = -1;
128
+ prevCurrent = node;
129
+ }
130
+ };
131
+ const getNextId = () => {
132
+ return `${segments.length}-${(segments.at(-1)?.length ?? 0) + 1}`;
133
+ };
134
+ const addLeafToken = (text) => {
135
+ const node = createHtmlNode('c', '');
136
+ node.appendChild(window.document.createTextNode(unescape(text)));
137
+ const segment = {
138
+ id: getNextId(),
139
+ node,
140
+ tag: 'c',
141
+ startTime: timeStamp >= 0 ? timeStamp : undefined,
142
+ };
143
+ addSegmentToRow(segment);
144
+ };
145
+ const addTextNode = (text, newRow) => {
146
+ if (newRow) {
147
+ rawText.push(text);
148
+ }
149
+ else {
150
+ rawText[rawText.length - 1] += text;
151
+ }
152
+ if (newRow)
153
+ segments.push([]);
154
+ if (current == null) {
155
+ // Append whitespaces to prev node.
156
+ if (text.trim().length === 0 && !newRow && prevCurrent) {
157
+ prevCurrent.appendChild(window.document.createTextNode(unescape(text)));
158
+ }
159
+ else {
160
+ addLeafToken(text);
161
+ }
162
+ }
163
+ else {
164
+ current.appendChild(window.document.createTextNode(unescape(text)));
165
+ }
166
+ };
167
+ while ((([input, token] = nextToken(input)), token) != null) {
168
+ // console.log('token', token);
169
+ if (token.startsWith('<')) {
170
+ // If the closing tag matches, move back up to the parent node.
171
+ // Otherwise just ignore the end tag.
172
+ if (token[1] === '/') {
173
+ if (tagStack.at(-1) === token.substring(2).replace('>', '')) {
174
+ addSegment(getNextId());
175
+ }
176
+ }
177
+ else {
178
+ // Try to parse timestamp.
179
+ // Timestamps are lead nodes as well.
180
+ const ts = parseTimeStamp(token.substring(1, token.length - 1));
181
+ if (ts != null && Number.isFinite(ts)) {
182
+ timeStamp = ts;
183
+ }
184
+ else {
185
+ const matchAr = /^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/.exec(token);
186
+ // If we can't parse the tag, skip to the next tag.
187
+ // Try to construct an element, and ignore the tag if we couldn't.
188
+ const node = matchAr && createHtmlNode(matchAr[1], matchAr[3]);
189
+ // Determine if the tag should be added based on the context of where it
190
+ // is placed in the cuetext.
191
+ if (node && (!current || shouldAdd(current, node))) {
192
+ // Set the class list (as a list of classes, separated by space).
193
+ if (matchAr[2]) {
194
+ node.className = matchAr[2].substring(1).replace('.', ' ');
195
+ }
196
+ // Append the node to the current node, and enter the scope of the new node.
197
+ tagStack.push(matchAr[1]);
198
+ current?.appendChild(node);
199
+ current = node;
200
+ }
201
+ }
202
+ }
203
+ }
204
+ // Text nodes are leaf nodes.
205
+ else {
206
+ // console.log(token, current, preferLength);
207
+ // eslint-disable-next-line no-lonely-if
208
+ if (rawText.length === 0 ||
209
+ (preferLineLength > 0 && rawText.at(-1).length + token.length > preferLineLength)) {
210
+ const rows = splitRows(token, preferLineLength);
211
+ const { length } = rows;
212
+ for (let i = 0; i < length; i += 1) {
213
+ addTextNode(rows[i], true);
214
+ }
215
+ }
216
+ else {
217
+ addTextNode(token, false);
218
+ }
219
+ }
220
+ }
221
+ // console.log(rawText);
222
+ // console.log(segments);
223
+ return { segments, rawText };
224
+ }
@@ -1 +1,17 @@
1
- export function resetMedia(e){(e.src||e.srcObject||e.childElementCount>0)&&(URL.revokeObjectURL(e.src),e.removeAttribute("src"),e.srcObject=null,e.childElementCount>0&&Array.prototype.filter.call(e.children,e=>e instanceof HTMLSourceElement).forEach(r=>{URL.revokeObjectURL(r.src),e.removeChild(r)}),e.load())}
1
+ /** Remove media from hardware decoder buffer. */
2
+ export function resetMedia(media) {
3
+ if (media.src || media.srcObject || media.childElementCount > 0) {
4
+ URL.revokeObjectURL(media.src);
5
+ media.removeAttribute('src');
6
+ media.srcObject = null;
7
+ if (media.childElementCount > 0) {
8
+ Array.prototype.filter
9
+ .call(media.children, (element) => element instanceof HTMLSourceElement)
10
+ .forEach((el) => {
11
+ URL.revokeObjectURL(el.src);
12
+ media.removeChild(el);
13
+ });
14
+ }
15
+ media.load();
16
+ }
17
+ }
@@ -1 +1,9 @@
1
- export function getTimeRangeStart(e){return e.length>0?e.start(e.length-1):0}export function getTimeRangeEnd(e){return e.length>0?e.end(e.length-1):0}export function getTimeRangeDuration(e){return getTimeRangeEnd(e)-getTimeRangeStart(e)}
1
+ export function getTimeRangeStart(timeRanges) {
2
+ return timeRanges.length > 0 ? timeRanges.start(timeRanges.length - 1) : 0;
3
+ }
4
+ export function getTimeRangeEnd(timeRanges) {
5
+ return timeRanges.length > 0 ? timeRanges.end(timeRanges.length - 1) : 0;
6
+ }
7
+ export function getTimeRangeDuration(timeRanges) {
8
+ return getTimeRangeEnd(timeRanges) - getTimeRangeStart(timeRanges);
9
+ }
@@ -1 +1,21 @@
1
- export function toggleNativeSubtitles(e,t){const{length:o}=t;for(let n=0;n<o;n+=1){const o=t[n];e?o.native=!0:delete o.native,e&&"hidden"===o.mode?o.mode="showing":e||"showing"!==o.mode||(o.mode="hidden")}}
1
+ export function toggleNativeSubtitles(native, textTracks) {
2
+ // console.log('toggleNativeSubtitles');
3
+ const { length } = textTracks;
4
+ for (let i = 0; i < length; i += 1) {
5
+ const track = textTracks[i];
6
+ if (native) {
7
+ track.native = true;
8
+ }
9
+ else {
10
+ delete track.native;
11
+ }
12
+ if (native && track.mode === 'hidden') {
13
+ track.mode = 'showing';
14
+ }
15
+ else if (!native && track.mode === 'showing') {
16
+ track.mode = 'hidden';
17
+ }
18
+ // console.log(track.label, track.mode);
19
+ }
20
+ // console.log('toggleNativeSubtitles end');
21
+ }
@@ -1 +1,11 @@
1
- import{getHandler}from"./getHandler";export class DataLayerProxy{handler;constructor(r){const t=getHandler("auto",r);this.handler=t}push(r){this.handler(r)}}
1
+ import { getHandler, } from './getHandler';
2
+ export class DataLayerProxy {
3
+ handler;
4
+ constructor(transformers) {
5
+ const handler = getHandler('auto', transformers);
6
+ this.handler = handler;
7
+ }
8
+ push(data) {
9
+ this.handler(data);
10
+ }
11
+ }
@@ -1 +1,99 @@
1
- import{UnreachableCaseError}from"@js-toolkit/utils/UnreachableCaseError";function gtmHandler(e,r,t){e.push(r(t))}function gtagHandler(e,r){const{eventType:t,eventCategory:n,measurementId:a,label:o}=r;e("event",t,{send_to:a,event_category:n,event_label:o,value:void 0})}function iframeHandler(e,r){window.parent.postMessage({type:e,event:r},"*")}export function getHandler(e,r){switch(e){case"auto":if(window.gtag)return getHandler("gtag",void 0);if(window.dataLayer)return getHandler("gtm",r);if(window.parent!==window)return getHandler("iframe",r);throw new Error("Unable to create auto handler due to Google Analytics is not configured or current window is not inside iframe.");case"iframe":return e=>{const t=r.iframe(e);iframeHandler(t.type,t.event)};case"gtm":{const{dataLayer:e}=window;if(!e)throw new Error("Unable to create handler: `dataLayer` is undefined.");return t=>{gtmHandler(e,r.gtm,t)}}case"gtag":{const{gtag:e}=window;if(!e)throw new Error("Unable to create handler: `gtag` is undefined.");return r=>{gtagHandler(e,r)}}default:throw new UnreachableCaseError(e,`Unknown GA lib type '${e}'.`)}}
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-parameters */
2
+ /* eslint-disable @typescript-eslint/prefer-function-type */
3
+ import { UnreachableCaseError } from '@js-toolkit/utils/UnreachableCaseError';
4
+ /** Google Tag Manager handler */
5
+ function gtmHandler(gtm, transform, data) {
6
+ gtm.push(transform(data));
7
+ }
8
+ /** Google tag handler */
9
+ function gtagHandler(gtag, data) {
10
+ const { eventType, eventCategory, measurementId, label } = data;
11
+ gtag('event', eventType, {
12
+ send_to: measurementId,
13
+ event_category: eventCategory,
14
+ event_label: label,
15
+ value: undefined,
16
+ });
17
+ }
18
+ function iframeHandler(type, data) {
19
+ window.parent.postMessage({ type, event: data }, '*');
20
+ }
21
+ // /** Universal Analytics without gtag handler */
22
+ // function gaHandler<D extends GAEventData>(
23
+ // gaObjectName: string,
24
+ // trackerCache: Record<string, GATracker>,
25
+ // data: D
26
+ // ): void {
27
+ // const ga = window[gaObjectName as 'ga'];
28
+ // if (!ga) return;
29
+ // const { action, eventCategory, measurementId, label } = data;
30
+ // const gaData: GAObjectEventData = {
31
+ // hitType: 'event',
32
+ // eventCategory,
33
+ // eventAction: action,
34
+ // eventLabel: label,
35
+ // eventValue: undefined,
36
+ // };
37
+ // if (measurementId) {
38
+ // const send = (): void => {
39
+ // const tracker =
40
+ // trackerCache[measurementId] ?? ga.getAll().find((t) => t.get('measurementId') === measurementId);
41
+ // if (!tracker) return;
42
+ // // eslint-disable-next-line no-param-reassign
43
+ // trackerCache[measurementId] = tracker;
44
+ // tracker.send(gaData);
45
+ // };
46
+ // if (ga.loaded) send();
47
+ // else ga(send);
48
+ // } else {
49
+ // ga('send', gaData);
50
+ // }
51
+ // }
52
+ export function getHandler(gaLib, transformers) {
53
+ switch (gaLib) {
54
+ case 'auto': {
55
+ if (window.gtag)
56
+ return getHandler('gtag', undefined);
57
+ // if (
58
+ // (window.GoogleAnalyticsObject && window[window.GoogleAnalyticsObject as 'ga']) ||
59
+ // window.ga
60
+ // )
61
+ // return getHandler('ga', undefined);
62
+ if (window.dataLayer)
63
+ return getHandler('gtm', transformers);
64
+ if (window.parent !== window)
65
+ return getHandler('iframe', transformers);
66
+ throw new Error('Unable to create auto handler due to Google Analytics is not configured or current window is not inside iframe.');
67
+ }
68
+ case 'iframe': {
69
+ return (data) => {
70
+ const msg = transformers.iframe(data);
71
+ iframeHandler(msg.type, msg.event);
72
+ };
73
+ }
74
+ case 'gtm': {
75
+ const { dataLayer } = window;
76
+ if (!dataLayer)
77
+ throw new Error('Unable to create handler: `dataLayer` is undefined.');
78
+ return (data) => {
79
+ gtmHandler(dataLayer, transformers.gtm, data);
80
+ };
81
+ }
82
+ case 'gtag': {
83
+ const { gtag } = window;
84
+ if (!gtag)
85
+ throw new Error('Unable to create handler: `gtag` is undefined.');
86
+ return (data) => {
87
+ gtagHandler(gtag, data);
88
+ };
89
+ }
90
+ // case 'ga': {
91
+ // const propName = window.GoogleAnalyticsObject || 'ga';
92
+ // const obj = window[propName as 'ga'];
93
+ // return obj ? gaHandler.bind(undefined, propName, {}) : undefined;
94
+ // }
95
+ default: {
96
+ throw new UnreachableCaseError(gaLib, `Unknown GA lib type '${gaLib}'.`);
97
+ }
98
+ }
99
+ }
@@ -1 +1 @@
1
- export{};
1
+ export {};
package/metrics/types.js CHANGED
@@ -1 +1 @@
1
- export{};
1
+ export {};
@@ -1 +1,11 @@
1
- import{getHandler}from"./getHandler";export class DataLayerProxy{handler;constructor(r,e){const t=getHandler(r,e);this.handler=t}push(r){this.handler(r)}}
1
+ import { getHandler, } from './getHandler';
2
+ export class DataLayerProxy {
3
+ handler;
4
+ constructor(lib, transformers) {
5
+ const handler = getHandler(lib, transformers);
6
+ this.handler = handler;
7
+ }
8
+ push(data) {
9
+ this.handler(data);
10
+ }
11
+ }
@@ -1 +1,63 @@
1
- import{UnreachableCaseError}from"@js-toolkit/utils/UnreachableCaseError";function ytmHandler(e,r,n){e.push(r(n))}function ymHandler(e,r,n){const[t,a,...o]=r(n);e(t,a,...o)}function iframeHandler(e,r){window.parent.postMessage({type:e,event:r},"*")}export function getHandler(e,r){switch(e){case"auto":if(window.ym)return getHandler("ym",r);if(window.dataLayer)return getHandler("ytm",r);if(window.parent!==window)return getHandler("iframe",r);throw new Error("Unable to create auto handler due to Yandex Metrics is not configured or current window is not inside iframe.");case"iframe":return e=>{const n=r.iframe(e);iframeHandler(n.type,n.event)};case"ytm":{const{dataLayer:e}=window;if(!e)throw new Error("Unable to create handler: `dataLayer` is undefined.");return n=>{ytmHandler(e,r.ytm,n)}}case"ym":{const{ym:e}=window;if(!e)throw new Error("Unable to create handler: `ym` is undefined.");return n=>{ymHandler(e,r.ym,n)}}default:throw new UnreachableCaseError(e,`Unknown Ya lib type '${e}'.`)}}
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-parameters */
2
+ /* eslint-disable @typescript-eslint/prefer-function-type */
3
+ import { UnreachableCaseError } from '@js-toolkit/utils/UnreachableCaseError';
4
+ /** Yandex Tag Manager handler */
5
+ function ytmHandler(ytm, transform, data) {
6
+ // https://yandex.ru/support/tag-manager/ru/triggers/types#special-event-trigger-type
7
+ ytm.push(transform(data));
8
+ }
9
+ function ymHandler(ym, transform, data) {
10
+ // counterId.forEach((item) => ym(typeof item === 'string' ? item : item.id, 'params', data));
11
+ const [counterId, fn, ...rest] = transform(data);
12
+ ym(counterId, fn, ...rest);
13
+ }
14
+ function iframeHandler(type, data) {
15
+ window.parent.postMessage({ type, event: data }, '*');
16
+ }
17
+ export function getHandler(yaLib, transformers) {
18
+ switch (yaLib) {
19
+ case 'auto': {
20
+ if (window.ym)
21
+ return getHandler('ym', transformers);
22
+ if (window.dataLayer)
23
+ return getHandler('ytm', transformers);
24
+ if (window.parent !== window)
25
+ return getHandler('iframe', transformers);
26
+ throw new Error('Unable to create auto handler due to Yandex Metrics is not configured or current window is not inside iframe.');
27
+ }
28
+ case 'iframe': {
29
+ return (data) => {
30
+ const msg = transformers.iframe(data);
31
+ iframeHandler(msg.type, msg.event);
32
+ };
33
+ }
34
+ case 'ytm': {
35
+ const { dataLayer } = window;
36
+ if (!dataLayer)
37
+ throw new Error('Unable to create handler: `dataLayer` is undefined.');
38
+ return (data) => {
39
+ ytmHandler(dataLayer, transformers.ytm, data);
40
+ };
41
+ }
42
+ case 'ym': {
43
+ // const { ym, Ya } = window;
44
+ // if (!ym || !Ya) throw new Error('Unable to create handler: `ym` or `Ya` is undefined.');
45
+ // const getCounters =
46
+ // // eslint-disable-next-line no-underscore-dangle
47
+ // Ya.Metrica2?.counters.bind(Ya.Metrica2) || Ya._metrica?.getCounters.bind(Ya._metrica);
48
+ // if (!getCounters) throw new Error('Unable to create handler: counters getter is undefined.');
49
+ // return (data) => {
50
+ // ymHandler(ym, data, data.measurementId ? [data.measurementId] : getCounters());
51
+ // };
52
+ const { ym } = window;
53
+ if (!ym)
54
+ throw new Error('Unable to create handler: `ym` is undefined.');
55
+ return (data) => {
56
+ ymHandler(ym, transformers.ym, data);
57
+ };
58
+ }
59
+ default: {
60
+ throw new UnreachableCaseError(yaLib, `Unknown Ya lib type '${yaLib}'.`);
61
+ }
62
+ }
63
+ }
@@ -1 +1,2 @@
1
- export{};
1
+ /* eslint-disable @typescript-eslint/prefer-function-type */
2
+ export {};
package/onDOMReady.js CHANGED
@@ -1 +1,12 @@
1
- export function onDOMReady(e){return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e,{once:!0}):e(),()=>{document.removeEventListener("DOMContentLoaded",e)}}
1
+ /** @returns cancel wait function */
2
+ export function onDOMReady(callback) {
3
+ if (document.readyState === 'loading') {
4
+ document.addEventListener('DOMContentLoaded', callback, { once: true });
5
+ }
6
+ else {
7
+ callback();
8
+ }
9
+ return () => {
10
+ document.removeEventListener('DOMContentLoaded', callback);
11
+ };
12
+ }
package/onPageReady.js CHANGED
@@ -1 +1,27 @@
1
- import{delay}from"@js-toolkit/utils/delay";export function isPageReady(){return"complete"===document.readyState}export function onPageReady(e,t){let o;const n=()=>{window.removeEventListener("load",a),o?.cancel()},a=()=>{n(),e()};return isPageReady()?e():(window.addEventListener("load",a,{once:!0}),t?.timeout&&(o=delay(a,t.timeout))),n}
1
+ import { delay } from '@js-toolkit/utils/delay';
2
+ export function isPageReady() {
3
+ return document.readyState === 'complete';
4
+ }
5
+ /** @returns cancel wait function. */
6
+ export function onPageReady(callback, options) {
7
+ let delayed;
8
+ const cancel = () => {
9
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
10
+ window.removeEventListener('load', cbWrapper);
11
+ delayed?.cancel();
12
+ };
13
+ const cbWrapper = () => {
14
+ cancel();
15
+ callback();
16
+ };
17
+ if (isPageReady()) {
18
+ callback();
19
+ }
20
+ else {
21
+ window.addEventListener('load', cbWrapper, { once: true });
22
+ if (options?.timeout != null && options.timeout > 0) {
23
+ delayed = delay(cbWrapper, options.timeout);
24
+ }
25
+ }
26
+ return cancel;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-toolkit/web-utils",
3
- "version": "1.67.0",
3
+ "version": "1.68.0",
4
4
  "description": "Web utils",
5
5
  "author": "VZH",
6
6
  "license": "MIT",
@@ -12,7 +12,8 @@
12
12
  "clean": "rimraf dist/*",
13
13
  "copy:configs": "copyfiles package.json .npmignore README.md LICENSE ./dist/",
14
14
  "minify": "node-utils-minify --replace ./dist",
15
- "build": "npm run clean && tsc --build ./tsconfig.json && npm run minify",
15
+ "build": "npm run clean && tsc --build ./tsconfig.json",
16
+ "lint": "eslint '**/*.{js,jsx,ts,tsx}' --flag unstable_native_nodejs_ts_config",
16
17
  "patch-publish": "npm run build && npm version patch --force --no-workspaces-update -m 'v%s' && npm run copy:configs && cd ./dist && npm publish --access public && git push --follow-tags",
17
18
  "minor-publish": "npm run build && npm version minor --force --no-workspaces-update -m 'v%s' && npm run copy:configs && cd ./dist && npm publish --access public && git push --follow-tags"
18
19
  },
@@ -20,30 +21,32 @@
20
21
  "@js-toolkit/node-utils": "1.2.6"
21
22
  },
22
23
  "devDependencies": {
23
- "@eslint/compat": "2.0.1",
24
- "@eslint/eslintrc": "3.3.3",
25
- "@eslint/js": "9.39.2",
26
- "@js-toolkit/configs": "3.99.1",
24
+ "@eslint/compat": "2.0.3",
25
+ "@eslint/js": "10.0.1",
26
+ "@js-toolkit/configs": "3.105.1",
27
27
  "@js-toolkit/utils": "1.61.0",
28
28
  "@types/eslint": "9.6.1",
29
29
  "@types/lodash.throttle": "4.1.9",
30
30
  "@types/uuid": "11.0.0",
31
31
  "copyfiles": "2.4.1",
32
- "eslint": "9.39.2",
32
+ "eslint": "9.39.4",
33
+ "eslint-config-airbnb-base": "15.0.0",
33
34
  "eslint-config-prettier": "10.1.8",
35
+ "eslint-config-standard": "17.1.0",
34
36
  "eslint-import-resolver-typescript": "4.4.4",
35
- "eslint-plugin-import-x": "4.16.1",
37
+ "eslint-plugin-import-x": "4.16.2",
36
38
  "eslint-plugin-prettier": "5.5.5",
39
+ "eslint-plugin-promise": "7.2.1",
37
40
  "lodash.throttle": "4.1.1",
38
- "prettier": "3.8.0",
41
+ "prettier": "3.8.1",
39
42
  "reconnecting-websocket": "4.4.0",
40
- "rimraf": "6.1.2",
43
+ "rimraf": "6.1.3",
41
44
  "terser": "5.46.0",
42
45
  "typescript": "5.9.3",
43
- "typescript-eslint": "8.53.1",
44
- "ua-parser-js": "2.0.8",
46
+ "typescript-eslint": "8.57.0",
47
+ "ua-parser-js": "2.0.9",
45
48
  "uuid": "13.0.0",
46
- "webpack": "5.104.1",
49
+ "webpack": "5.105.4",
47
50
  "yargs": "18.0.0"
48
51
  }
49
52
  }
@@ -1 +1,27 @@
1
- const methods={debug:console.debug.bind(console),error:console.error.bind(console),info:console.info.bind(console),log:console.log.bind(console),trace:console.trace.bind(console),warn:console.warn.bind(console)};export function restoreConsoleLogging(){Object.entries(methods).forEach(([o,e])=>{console[o]=e})}export function patchConsoleLogging(o,{mode:e="replace"}={}){restoreConsoleLogging(),Object.getOwnPropertyNames(methods).forEach(n=>{const s=n;console[s]=(...n)=>{"append"===e&&methods[s](...n),o(s,...n),"prepend"===e&&methods[s](...n)}})}
1
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
+ const methods = {
3
+ debug: console.debug.bind(console),
4
+ error: console.error.bind(console),
5
+ info: console.info.bind(console),
6
+ log: console.log.bind(console),
7
+ trace: console.trace.bind(console),
8
+ warn: console.warn.bind(console),
9
+ };
10
+ export function restoreConsoleLogging() {
11
+ Object.entries(methods).forEach(([name, fn]) => {
12
+ console[name] = fn;
13
+ });
14
+ }
15
+ export function patchConsoleLogging(customMethod, { mode = 'replace' } = {}) {
16
+ restoreConsoleLogging();
17
+ Object.getOwnPropertyNames(methods).forEach((key) => {
18
+ const name = key;
19
+ console[name] = (...args) => {
20
+ if (mode === 'append')
21
+ methods[name](...args);
22
+ customMethod(name, ...args);
23
+ if (mode === 'prepend')
24
+ methods[name](...args);
25
+ };
26
+ });
27
+ }
@@ -1 +1,14 @@
1
- import{onPageReady}from"../onPageReady";export function getNavigationTiming(){return new Promise(e=>{onPageReady(()=>setTimeout(()=>{try{const n=window.performance?.getEntriesByType("navigation")[0];e(n)}catch{e(void 0)}},0))})}
1
+ import { onPageReady } from '../onPageReady';
2
+ export function getNavigationTiming() {
3
+ return new Promise((resolve) => {
4
+ onPageReady(() => setTimeout(() => {
5
+ try {
6
+ const navigationEntry = window.performance?.getEntriesByType('navigation')[0];
7
+ resolve(navigationEntry);
8
+ }
9
+ catch {
10
+ resolve(undefined);
11
+ }
12
+ }, 0));
13
+ });
14
+ }