@technoapple/ga4 1.0.4 → 1.1.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/.github/workflows/node.js.yml +31 -31
- package/.prettierignore +1 -1
- package/LICENSE +21 -21
- package/README.md +386 -48
- package/REQUIREMENTS.md +548 -0
- package/babel.config.js +5 -5
- package/build/main/ga4/ga4.d.ts +13 -0
- package/build/main/ga4/ga4.js +24 -1
- package/build/main/helpers/debounce.d.ts +5 -0
- package/build/main/helpers/debounce.js +23 -0
- package/build/main/helpers/delegate.d.ts +8 -0
- package/build/main/helpers/delegate.js +37 -0
- package/build/main/helpers/dom-ready.d.ts +1 -0
- package/build/main/helpers/dom-ready.js +13 -0
- package/build/main/helpers/parse-url.d.ts +11 -0
- package/build/main/helpers/parse-url.js +32 -0
- package/build/main/helpers/session.d.ts +4 -0
- package/build/main/helpers/session.js +50 -0
- package/build/main/index.d.ts +9 -0
- package/build/main/index.js +19 -2
- package/build/main/plugins/clean-url-tracker.d.ts +17 -0
- package/build/main/plugins/clean-url-tracker.js +105 -0
- package/build/main/plugins/event-tracker.d.ts +27 -0
- package/build/main/plugins/event-tracker.js +76 -0
- package/build/main/plugins/impression-tracker.d.ts +32 -0
- package/build/main/plugins/impression-tracker.js +202 -0
- package/build/main/plugins/index.d.ts +8 -0
- package/build/main/plugins/index.js +20 -0
- package/build/main/plugins/media-query-tracker.d.ts +20 -0
- package/build/main/plugins/media-query-tracker.js +96 -0
- package/build/main/plugins/outbound-form-tracker.d.ts +17 -0
- package/build/main/plugins/outbound-form-tracker.js +55 -0
- package/build/main/plugins/outbound-link-tracker.d.ts +19 -0
- package/build/main/plugins/outbound-link-tracker.js +63 -0
- package/build/main/plugins/page-visibility-tracker.d.ts +24 -0
- package/build/main/plugins/page-visibility-tracker.js +93 -0
- package/build/main/plugins/url-change-tracker.d.ts +20 -0
- package/build/main/plugins/url-change-tracker.js +76 -0
- package/build/main/types/plugins.d.ts +78 -0
- package/build/main/types/plugins.js +3 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/docs/examples/react.md +95 -0
- package/docs/examples/vanilla.md +65 -0
- package/docs/examples/vue.md +87 -0
- package/jest.config.ts +195 -195
- package/package.json +56 -56
- package/src/dataLayer.ts +85 -85
- package/src/ga4/ga4.ts +69 -40
- package/src/ga4/ga4option.ts +4 -4
- package/src/ga4/index.ts +4 -4
- package/src/helpers/debounce.ts +28 -0
- package/src/helpers/delegate.ts +51 -0
- package/src/helpers/dom-ready.ts +7 -0
- package/src/helpers/parse-url.ts +37 -0
- package/src/helpers/session.ts +39 -0
- package/src/index.ts +34 -7
- package/src/plugins/clean-url-tracker.ts +112 -0
- package/src/plugins/event-tracker.ts +90 -0
- package/src/plugins/impression-tracker.ts +230 -0
- package/src/plugins/index.ts +8 -0
- package/src/plugins/media-query-tracker.ts +116 -0
- package/src/plugins/outbound-form-tracker.ts +65 -0
- package/src/plugins/outbound-link-tracker.ts +72 -0
- package/src/plugins/page-visibility-tracker.ts +104 -0
- package/src/plugins/url-change-tracker.ts +84 -0
- package/src/types/dataLayer.ts +9 -9
- package/src/types/global.ts +12 -12
- package/src/types/gtag.ts +259 -259
- package/src/types/plugins.ts +98 -0
- package/src/util.ts +18 -18
- package/test/dataLayer.spec.ts +55 -55
- package/test/ga4.spec.ts +36 -36
- package/tsconfig.json +28 -28
- package/tsconfig.module.json +11 -11
package/REQUIREMENTS.md
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
# Project Requirements — @technoapple/ga4 v2.0
|
|
2
|
+
|
|
3
|
+
> **Version:** 2.0
|
|
4
|
+
> **Last Updated:** 2026-02-24
|
|
5
|
+
> **Author:** keke78ui9
|
|
6
|
+
> **Status:** Draft
|
|
7
|
+
> **Reference:** [googleanalytics/autotrack](https://github.com/googleanalytics/autotrack) — tracking concepts adapted for GA4
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Overview
|
|
12
|
+
|
|
13
|
+
**Project Name:** @technoapple/ga4
|
|
14
|
+
**Description:** A TypeScript library that provides functions to support sending GA4 events, interacting with `window.dataLayer`, and **automatic tracking plugins** — providing enhanced tracking capabilities beyond GA4's built-in enhanced measurement.
|
|
15
|
+
|
|
16
|
+
**Goals:**
|
|
17
|
+
- Provide automatic tracking plugins for GA4 via `gtag()` / `dataLayer`
|
|
18
|
+
- Maintain the existing `ga4.init()`, `ga4.send()`, `ga4.gtag`, and `dataLayerHelper.get()` APIs
|
|
19
|
+
- Provide each plugin as an opt-in module (tree-shakeable) so consumers only pay for what they use
|
|
20
|
+
- Written in TypeScript with full type safety, following the existing codebase patterns
|
|
21
|
+
- Zero third-party runtime dependencies (browser APIs only)
|
|
22
|
+
|
|
23
|
+
**Out of Scope:**
|
|
24
|
+
- Server-side tracking / Measurement Protocol
|
|
25
|
+
- Google Tag Manager container management
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Background & Motivation
|
|
30
|
+
|
|
31
|
+
GA4's built-in enhanced measurement covers some basic automatic tracking (page views, scroll to 90%, outbound clicks, site search, video engagement, file downloads). However, many advanced tracking scenarios are not covered:
|
|
32
|
+
|
|
33
|
+
- **Declarative event tracking** via HTML attributes (no JS needed)
|
|
34
|
+
- **Granular scroll depth** tracking is limited — GA4 only fires a single event at 90%
|
|
35
|
+
- **Page visibility** duration (time in foreground vs. background tab)
|
|
36
|
+
- **SPA URL changes** require manual setup in many frameworks
|
|
37
|
+
- **Element impression** tracking (ads, CTAs entering the viewport)
|
|
38
|
+
- **Outbound form** submit tracking
|
|
39
|
+
- **URL normalization** to prevent fragmented reporting
|
|
40
|
+
- **Media query / breakpoint** change tracking
|
|
41
|
+
|
|
42
|
+
This library provides these capabilities as TypeScript plugins that integrate with GA4 via `gtag('event', ...)`.
|
|
43
|
+
|
|
44
|
+
> **Note:** GA4 enhanced measurement already tracks scroll events (at 90% depth). This library does **not** duplicate that — instead it focuses on capabilities GA4 does not provide out of the box.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. Functional Requirements — Existing Features (Already Implemented)
|
|
49
|
+
|
|
50
|
+
| ID | Requirement | Priority | Status |
|
|
51
|
+
|--------|-------------------------------------|----------|-----------|
|
|
52
|
+
| FR-001 | GA4 initialization via `ga4.init()` | High | ✅ Done |
|
|
53
|
+
| FR-002 | Send events via `ga4.send()` | High | ✅ Done |
|
|
54
|
+
| FR-003 | Direct `gtag()` access | High | ✅ Done |
|
|
55
|
+
| FR-004 | Read values from `dataLayer` | Medium | ✅ Done |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 4. Functional Requirements — New Plugins
|
|
60
|
+
|
|
61
|
+
### 4.1 Plugin Summary
|
|
62
|
+
|
|
63
|
+
| # | Plugin | Priority | Rationale |
|
|
64
|
+
|---|--------------------------|----------|-----------|
|
|
65
|
+
| 1 | `eventTracker` | High | Declarative event tracking via HTML `data-*` attributes. No JS needed for page authors. |
|
|
66
|
+
| 2 | `outboundLinkTracker` | High | Click delegation on `<a>` elements, compare hostnames. Uses `navigator.sendBeacon` for reliability. |
|
|
67
|
+
| 3 | `outboundFormTracker` | Medium | Submit delegation on `<form>` elements with external `action` URLs. |
|
|
68
|
+
| 4 | `pageVisibilityTracker` | High | `document.visibilitychange` API. Track visible/hidden time. Handle session timeout. |
|
|
69
|
+
| 5 | `urlChangeTracker` | High | `popstate` + monkey-patch `history.pushState`/`replaceState` for SPA `page_view` tracking. |
|
|
70
|
+
| 6 | `impressionTracker` | Medium | `IntersectionObserver` + `MutationObserver`. Track element visibility in viewport. |
|
|
71
|
+
| 7 | `cleanUrlTracker` | Medium | Normalize URLs before sending `page_view` (strip query params, trailing slashes, force lowercase). |
|
|
72
|
+
| 8 | `mediaQueryTracker` | Low | `window.matchMedia` API. Track responsive breakpoint changes. |
|
|
73
|
+
|
|
74
|
+
### 4.2 Detailed Plugin Requirements
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
#### FR-100: `eventTracker` — Declarative Event Tracking via HTML Attributes
|
|
79
|
+
|
|
80
|
+
**Description:**
|
|
81
|
+
Allow page authors to track user interactions by adding `data-ga4-*` attributes to HTML elements, without writing JavaScript. The plugin listens for DOM events (click, submit, etc.) on elements matching a configurable selector and sends GA4 events based on attribute values.
|
|
82
|
+
|
|
83
|
+
**Options:**
|
|
84
|
+
|
|
85
|
+
| Option | Type | Default | Description |
|
|
86
|
+
|--------|------|---------|-------------|
|
|
87
|
+
| `events` | `string[]` | `['click']` | DOM event types to listen for |
|
|
88
|
+
| `attributePrefix` | `string` | `'data-ga4-'` | Prefix for data attributes |
|
|
89
|
+
| `hitFilter` | `(params, element, event) => params \| null` | `undefined` | Filter/modify params before sending |
|
|
90
|
+
|
|
91
|
+
**HTML Example:**
|
|
92
|
+
```html
|
|
93
|
+
<button
|
|
94
|
+
data-ga4-on="click"
|
|
95
|
+
data-ga4-event-name="video_play"
|
|
96
|
+
data-ga4-video-title="My Video"
|
|
97
|
+
data-ga4-video-id="abc123">
|
|
98
|
+
Play video
|
|
99
|
+
</button>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Sent as:**
|
|
103
|
+
```js
|
|
104
|
+
gtag('event', 'video_play', { video_title: 'My Video', video_id: 'abc123' });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Acceptance Criteria:**
|
|
108
|
+
- [ ] Reads event name from `data-ga4-event-name` attribute
|
|
109
|
+
- [ ] Reads all `data-ga4-*` attributes as event parameters (kebab-case → snake_case)
|
|
110
|
+
- [ ] Supports configurable event types (`click`, `submit`, `change`, etc.)
|
|
111
|
+
- [ ] Uses event delegation on `document` for performance
|
|
112
|
+
- [ ] Provides `remove()` method to clean up listeners
|
|
113
|
+
- [ ] Does not throw errors when attributes are missing
|
|
114
|
+
|
|
115
|
+
**Implementation Notes:**
|
|
116
|
+
- Use event delegation (single listener on `document`) for performance
|
|
117
|
+
- Convert `data-ga4-video-title` → `video_title` parameter name
|
|
118
|
+
- Implement lightweight internal `delegate()` utility (zero dependencies)
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
#### FR-101: `outboundLinkTracker` — Automatic Outbound Link Click Tracking
|
|
123
|
+
|
|
124
|
+
**Description:**
|
|
125
|
+
Automatically detect when a user clicks a link pointing to an external domain and send a GA4 event.
|
|
126
|
+
|
|
127
|
+
**Options:**
|
|
128
|
+
|
|
129
|
+
| Option | Type | Default | Description |
|
|
130
|
+
|--------|------|---------|-------------|
|
|
131
|
+
| `events` | `string[]` | `['click']` | DOM events to listen for (e.g. add `'auxclick'`, `'contextmenu'`) |
|
|
132
|
+
| `linkSelector` | `string` | `'a, area'` | CSS selector for link elements |
|
|
133
|
+
| `shouldTrackOutboundLink` | `(link: HTMLAnchorElement, parseUrl: Function) => boolean` | hostname !== location.hostname | Customize outbound detection |
|
|
134
|
+
| `eventName` | `string` | `'outbound_link_click'` | GA4 event name |
|
|
135
|
+
| `attributePrefix` | `string` | `'data-ga4-'` | Prefix for declarative attribute overrides |
|
|
136
|
+
| `hitFilter` | `Function` | `undefined` | Filter/modify params before sending |
|
|
137
|
+
|
|
138
|
+
**Default event parameters sent:**
|
|
139
|
+
|
|
140
|
+
| Parameter | Value |
|
|
141
|
+
|-----------|-------|
|
|
142
|
+
| `event_name` | `'outbound_link_click'` |
|
|
143
|
+
| `link_url` | Full href of the clicked link |
|
|
144
|
+
| `link_domain` | Hostname of the outbound link |
|
|
145
|
+
| `outbound` | `true` |
|
|
146
|
+
|
|
147
|
+
**Acceptance Criteria:**
|
|
148
|
+
- [ ] Detects clicks on `<a>` and `<area>` elements pointing to external domains
|
|
149
|
+
- [ ] Uses `navigator.sendBeacon` transport for reliability (page may unload)
|
|
150
|
+
- [ ] Supports right-click and middle-click tracking via `events` option
|
|
151
|
+
- [ ] Provides `shouldTrackOutboundLink` callback for custom domain logic
|
|
152
|
+
- [ ] Provides `remove()` method to clean up all event listeners
|
|
153
|
+
- [ ] Handles links with `xlink:href` (SVG links)
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
#### FR-102: `outboundFormTracker` — Automatic Outbound Form Submit Tracking
|
|
158
|
+
|
|
159
|
+
**Description:**
|
|
160
|
+
Automatically detect when a form is submitted to an external domain and send a GA4 event.
|
|
161
|
+
|
|
162
|
+
**Options:**
|
|
163
|
+
|
|
164
|
+
| Option | Type | Default | Description |
|
|
165
|
+
|--------|------|---------|-------------|
|
|
166
|
+
| `formSelector` | `string` | `'form'` | CSS selector for forms |
|
|
167
|
+
| `shouldTrackOutboundForm` | `(form: HTMLFormElement, parseUrl: Function) => boolean` | action hostname !== location.hostname | Custom detection |
|
|
168
|
+
| `eventName` | `string` | `'outbound_form_submit'` | GA4 event name |
|
|
169
|
+
| `hitFilter` | `Function` | `undefined` | Filter/modify params |
|
|
170
|
+
|
|
171
|
+
**Acceptance Criteria:**
|
|
172
|
+
- [ ] Detects form submits where `form.action` points to an external domain
|
|
173
|
+
- [ ] Delays form submission briefly to ensure the GA4 event is sent
|
|
174
|
+
- [ ] Falls back gracefully if `navigator.sendBeacon` is not available
|
|
175
|
+
- [ ] Provides `remove()` method
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
#### FR-103: `pageVisibilityTracker` — Page Visibility Duration Tracking
|
|
180
|
+
|
|
181
|
+
**Description:**
|
|
182
|
+
Track how long a page is in the visible state vs. hidden (background tab). Optionally send a new `page_view` when the page becomes visible again after session timeout.
|
|
183
|
+
|
|
184
|
+
**Options:**
|
|
185
|
+
|
|
186
|
+
| Option | Type | Default | Description |
|
|
187
|
+
|--------|------|---------|-------------|
|
|
188
|
+
| `sendInitialPageview` | `boolean` | `false` | Plugin handles the initial page_view |
|
|
189
|
+
| `sessionTimeout` | `number` | `30` (minutes) | Minutes of hidden time before new session |
|
|
190
|
+
| `timeZone` | `string` | `undefined` | IANA timezone for session boundary |
|
|
191
|
+
| `pageLoadsMetricIndex` | `number` | `undefined` | Custom metric index |
|
|
192
|
+
| `visibleMetricIndex` | `number` | `undefined` | Custom metric for visible time |
|
|
193
|
+
| `eventName` | `string` | `'page_visibility'` | GA4 event name |
|
|
194
|
+
| `hitFilter` | `Function` | `undefined` | Filter/modify params |
|
|
195
|
+
|
|
196
|
+
**Default event parameters sent:**
|
|
197
|
+
|
|
198
|
+
| Parameter | Value |
|
|
199
|
+
|-----------|-------|
|
|
200
|
+
| `event_name` | `'page_visibility'` |
|
|
201
|
+
| `visibility_state` | `'visible'` or `'hidden'` |
|
|
202
|
+
| `visibility_duration` | Time in ms the page was in previous state |
|
|
203
|
+
| `page_path` | Current page path |
|
|
204
|
+
|
|
205
|
+
**Acceptance Criteria:**
|
|
206
|
+
- [ ] Listens for `visibilitychange` events on `document`
|
|
207
|
+
- [ ] Tracks cumulative visible time accurately
|
|
208
|
+
- [ ] Optionally sends new `page_view` on visible→hidden→visible session timeout
|
|
209
|
+
- [ ] Sends final visibility duration on `beforeunload`
|
|
210
|
+
- [ ] Provides `remove()` method
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
#### FR-104: `urlChangeTracker` — SPA URL Change Tracking
|
|
215
|
+
|
|
216
|
+
**Description:**
|
|
217
|
+
Automatically track URL changes in Single Page Applications by intercepting `history.pushState()`, `history.replaceState()`, and `popstate` events, sending a `page_view` event for each navigation.
|
|
218
|
+
|
|
219
|
+
**Options:**
|
|
220
|
+
|
|
221
|
+
| Option | Type | Default | Description |
|
|
222
|
+
|--------|------|---------|-------------|
|
|
223
|
+
| `shouldTrackUrlChange` | `(newPath: string, oldPath: string) => boolean` | `newPath !== oldPath` | Custom logic for what counts as a URL change |
|
|
224
|
+
| `trackReplaceState` | `boolean` | `false` | Whether `replaceState` triggers tracking |
|
|
225
|
+
| `hitFilter` | `Function` | `undefined` | Filter/modify params |
|
|
226
|
+
|
|
227
|
+
**Default event parameters sent:**
|
|
228
|
+
|
|
229
|
+
| Parameter | Value |
|
|
230
|
+
|-----------|-------|
|
|
231
|
+
| `event_name` | `'page_view'` |
|
|
232
|
+
| `page_path` | New URL path |
|
|
233
|
+
| `page_title` | `document.title` |
|
|
234
|
+
| `page_location` | Full URL |
|
|
235
|
+
|
|
236
|
+
**Acceptance Criteria:**
|
|
237
|
+
- [ ] Monkey-patches `history.pushState` and optionally `history.replaceState`
|
|
238
|
+
- [ ] Listens for `popstate` events (back/forward navigation)
|
|
239
|
+
- [ ] Sends `page_view` GA4 event on each tracked URL change
|
|
240
|
+
- [ ] Provides `shouldTrackUrlChange` callback for filtering
|
|
241
|
+
- [ ] Restores original `history.pushState`/`replaceState` on `remove()`
|
|
242
|
+
- [ ] Does not double-fire for the initial page load
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
#### FR-105: `impressionTracker` — Element Viewport Impression Tracking
|
|
247
|
+
|
|
248
|
+
**Description:**
|
|
249
|
+
Track when specific DOM elements become visible in the viewport using `IntersectionObserver`. Useful for tracking ad impressions, CTA visibility, etc.
|
|
250
|
+
|
|
251
|
+
**Options:**
|
|
252
|
+
|
|
253
|
+
| Option | Type | Default | Description |
|
|
254
|
+
|--------|------|---------|-------------|
|
|
255
|
+
| `elements` | `Array<string \| ElementConfig>` | `[]` | Element IDs or config objects to observe |
|
|
256
|
+
| `rootMargin` | `string` | `'0px'` | IntersectionObserver rootMargin |
|
|
257
|
+
| `attributePrefix` | `string` | `'data-ga4-'` | Attribute prefix for declarative params |
|
|
258
|
+
| `eventName` | `string` | `'element_impression'` | GA4 event name |
|
|
259
|
+
| `hitFilter` | `Function` | `undefined` | Filter/modify params |
|
|
260
|
+
|
|
261
|
+
**Element config object:**
|
|
262
|
+
|
|
263
|
+
| Property | Type | Default | Description |
|
|
264
|
+
|----------|------|---------|-------------|
|
|
265
|
+
| `id` | `string` | — | Element ID to observe |
|
|
266
|
+
| `threshold` | `number` | `0` | Visibility ratio (0-1) to trigger |
|
|
267
|
+
| `trackFirstImpressionOnly` | `boolean` | `true` | Only fire once per element |
|
|
268
|
+
|
|
269
|
+
**Acceptance Criteria:**
|
|
270
|
+
- [ ] Uses `IntersectionObserver` API to detect element visibility
|
|
271
|
+
- [ ] Uses `MutationObserver` to handle dynamically added/removed elements
|
|
272
|
+
- [ ] Supports per-element threshold configuration
|
|
273
|
+
- [ ] Supports `trackFirstImpressionOnly` option
|
|
274
|
+
- [ ] Provides `observeElements()`, `unobserveElements()`, `unobserveAllElements()` methods
|
|
275
|
+
- [ ] Feature-detects `IntersectionObserver` / `MutationObserver` — no-ops gracefully if unsupported
|
|
276
|
+
- [ ] Provides `remove()` method
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
#### FR-106: `cleanUrlTracker` — URL Normalization for page_view Events
|
|
281
|
+
|
|
282
|
+
**Description:**
|
|
283
|
+
Normalize URLs before they are sent with `page_view` events to ensure consistency in GA4 reports.
|
|
284
|
+
|
|
285
|
+
**Options:**
|
|
286
|
+
|
|
287
|
+
| Option | Type | Default | Description |
|
|
288
|
+
|--------|------|---------|-------------|
|
|
289
|
+
| `stripQuery` | `boolean` | `false` | Remove query string from URLs |
|
|
290
|
+
| `queryParamsAllowlist` | `string[]` | `undefined` | Query params to keep (when `stripQuery` is true) |
|
|
291
|
+
| `queryParamsDenylist` | `string[]` | `undefined` | Specific query params to remove |
|
|
292
|
+
| `trailingSlash` | `'add' \| 'remove'` | `undefined` | Normalize trailing slashes |
|
|
293
|
+
| `urlFilter` | `(url: string) => string` | `undefined` | Custom URL transformation function |
|
|
294
|
+
|
|
295
|
+
**Acceptance Criteria:**
|
|
296
|
+
- [ ] Strips query parameters when configured
|
|
297
|
+
- [ ] Supports allowlist/denylist for selective query param removal
|
|
298
|
+
- [ ] Normalizes trailing slashes
|
|
299
|
+
- [ ] Applies custom `urlFilter` function
|
|
300
|
+
- [ ] Applies transformations to `page_location` and `page_path` in page_view events
|
|
301
|
+
- [ ] Provides `remove()` method
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
#### FR-107: `mediaQueryTracker` — Responsive Breakpoint Tracking
|
|
306
|
+
|
|
307
|
+
**Description:**
|
|
308
|
+
Track which CSS media query breakpoints match and fire events when breakpoints change.
|
|
309
|
+
|
|
310
|
+
**Options:**
|
|
311
|
+
|
|
312
|
+
| Option | Type | Default | Description |
|
|
313
|
+
|--------|------|---------|-------------|
|
|
314
|
+
| `definitions` | `MediaQueryDefinition[]` | `[]` | Array of media query definitions |
|
|
315
|
+
| `changeTemplate` | `(oldValue: string, newValue: string) => string` | `'${oldValue} => ${newValue}'` | Template for change events |
|
|
316
|
+
| `changeTimeout` | `number` | `1000` | Debounce timeout in ms |
|
|
317
|
+
| `eventName` | `string` | `'media_query_change'` | GA4 event name |
|
|
318
|
+
| `hitFilter` | `Function` | `undefined` | Filter/modify params |
|
|
319
|
+
|
|
320
|
+
**MediaQueryDefinition:**
|
|
321
|
+
|
|
322
|
+
| Property | Type | Description |
|
|
323
|
+
|----------|------|-------------|
|
|
324
|
+
| `name` | `string` | e.g. `'Breakpoint'` |
|
|
325
|
+
| `dimensionIndex` | `number` | Custom dimension index |
|
|
326
|
+
| `items` | `Array<{name: string, media: string}>` | Media query items |
|
|
327
|
+
|
|
328
|
+
**Acceptance Criteria:**
|
|
329
|
+
- [ ] Uses `window.matchMedia()` API
|
|
330
|
+
- [ ] Fires event on breakpoint change with old and new values
|
|
331
|
+
- [ ] Debounces rapid changes (e.g. window resize)
|
|
332
|
+
- [ ] Feature-detects `matchMedia` — no-ops if unsupported
|
|
333
|
+
- [ ] Provides `remove()` method
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 5. Non-Functional Requirements
|
|
338
|
+
|
|
339
|
+
| ID | Requirement | Priority | Status |
|
|
340
|
+
|---------|--------------------------|----------|-------------|
|
|
341
|
+
| NFR-001 | Modern Browser Support | High | Not Started |
|
|
342
|
+
| NFR-002 | TypeScript Type Safety | High | Not Started |
|
|
343
|
+
| NFR-003 | Zero Runtime Dependencies | High | Not Started |
|
|
344
|
+
| NFR-004 | Tree-Shakeable Plugins | High | Not Started |
|
|
345
|
+
| NFR-005 | Bundle Size < 8KB gzip | Medium | Not Started |
|
|
346
|
+
| NFR-006 | Test Coverage >= 80% | Medium | Not Started |
|
|
347
|
+
| NFR-007 | Documentation per Plugin | Medium | Not Started |
|
|
348
|
+
| NFR-008 | Graceful Feature Detection | High | Not Started |
|
|
349
|
+
|
|
350
|
+
### 5.1 Details
|
|
351
|
+
|
|
352
|
+
- **Browser Compatibility:** Chrome 64+, Firefox 67+, Safari 12+, Edge 79+ (all browsers supporting `IntersectionObserver`, `MutationObserver`, `navigator.sendBeacon`)
|
|
353
|
+
- **TypeScript Version:** >= 4.x
|
|
354
|
+
- **Bundle Size Target:** < 8KB gzipped (all plugins), individual plugins < 2KB each
|
|
355
|
+
- **Test Coverage Target:** >= 80% line coverage
|
|
356
|
+
- **Graceful Degradation:** All plugins must feature-detect required browser APIs and silently no-op if unsupported (never throw errors)
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 6. Technical Requirements
|
|
361
|
+
|
|
362
|
+
- **Language:** TypeScript (strict mode)
|
|
363
|
+
- **Build Tool:** tsc
|
|
364
|
+
- **Test Framework:** Jest + jsdom
|
|
365
|
+
- **Package Registry:** npm (public, `@technoapple/ga4`)
|
|
366
|
+
- **Module Format:** CommonJS + ESM (dual publish)
|
|
367
|
+
- **Runtime Dependencies:** None (zero dependencies)
|
|
368
|
+
- **Node.js Version:** >= 16
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 7. Architecture & API Design
|
|
373
|
+
|
|
374
|
+
### 7.1 Existing APIs (unchanged)
|
|
375
|
+
|
|
376
|
+
| API | Method | Parameters | Returns | Description |
|
|
377
|
+
|-----|--------|------------|---------|-------------|
|
|
378
|
+
| `ga4` | `init` | `options: ga4Option` | `void` | Initialize GA4 with targetId |
|
|
379
|
+
| `ga4` | `send` | `event: string, params: KeyValueParams` | `boolean` | Send a GA4 event |
|
|
380
|
+
| `ga4` | `gtag` | (getter) | `gtag` | Direct access to `window.gtag` |
|
|
381
|
+
| `dataLayerHelper` | `get` | `key: string, getLast?: boolean` | `any` | Retrieve value from dataLayer |
|
|
382
|
+
|
|
383
|
+
### 7.2 New Plugin Architecture
|
|
384
|
+
|
|
385
|
+
Each plugin follows a consistent pattern:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
interface PluginInterface {
|
|
389
|
+
remove(): void; // Clean up all listeners, restore original state
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Plugin registration pattern (follows existing singleton pattern):**
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { ga4, plugins } from '@technoapple/ga4';
|
|
397
|
+
|
|
398
|
+
// Initialize GA4 (existing)
|
|
399
|
+
ga4.init({ targetId: 'G-XXXXXXX' });
|
|
400
|
+
|
|
401
|
+
// Register plugins (new)
|
|
402
|
+
ga4.use(plugins.outboundLinkTracker, { /* options */ });
|
|
403
|
+
ga4.use(plugins.pageVisibilityTracker);
|
|
404
|
+
ga4.use(plugins.urlChangeTracker);
|
|
405
|
+
|
|
406
|
+
// Or import individual plugins directly
|
|
407
|
+
import { OutboundLinkTracker } from '@technoapple/ga4/plugins';
|
|
408
|
+
const tracker = new OutboundLinkTracker(ga4, { /* options */ });
|
|
409
|
+
tracker.remove(); // cleanup
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 7.3 Proposed File Structure
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
src/
|
|
416
|
+
index.ts # Main exports (existing + new)
|
|
417
|
+
ga4/
|
|
418
|
+
ga4.ts # Core GA4 class (existing, add .use() method)
|
|
419
|
+
ga4option.ts # Options interface (existing)
|
|
420
|
+
index.ts # GA4 barrel export (existing)
|
|
421
|
+
dataLayer.ts # DataLayer helper (existing)
|
|
422
|
+
util.ts # Utilities (existing, extend)
|
|
423
|
+
types/
|
|
424
|
+
dataLayer.ts # DataLayer types (existing)
|
|
425
|
+
global.ts # Window augmentation (existing)
|
|
426
|
+
gtag.ts # gtag type definitions (existing)
|
|
427
|
+
plugins.ts # Plugin option types (new)
|
|
428
|
+
plugins/
|
|
429
|
+
index.ts # Barrel export for all plugins
|
|
430
|
+
plugin-base.ts # Base class / interface for plugins
|
|
431
|
+
event-tracker.ts # FR-100
|
|
432
|
+
outbound-link-tracker.ts # FR-101
|
|
433
|
+
outbound-form-tracker.ts # FR-102
|
|
434
|
+
page-visibility-tracker.ts # FR-103
|
|
435
|
+
url-change-tracker.ts # FR-104
|
|
436
|
+
impression-tracker.ts # FR-105
|
|
437
|
+
clean-url-tracker.ts # FR-106
|
|
438
|
+
media-query-tracker.ts # FR-107
|
|
439
|
+
helpers/
|
|
440
|
+
delegate.ts # Event delegation utility
|
|
441
|
+
parse-url.ts # URL parsing utility
|
|
442
|
+
dom-ready.ts # DOM ready utility
|
|
443
|
+
session.ts # Session timeout / storage utilities
|
|
444
|
+
debounce.ts # Debounce utility
|
|
445
|
+
test/
|
|
446
|
+
dataLayer.spec.ts # Existing
|
|
447
|
+
ga4.spec.ts # Existing
|
|
448
|
+
plugins/
|
|
449
|
+
event-tracker.spec.ts
|
|
450
|
+
outbound-link-tracker.spec.ts
|
|
451
|
+
outbound-form-tracker.spec.ts
|
|
452
|
+
page-visibility-tracker.spec.ts
|
|
453
|
+
url-change-tracker.spec.ts
|
|
454
|
+
impression-tracker.spec.ts
|
|
455
|
+
clean-url-tracker.spec.ts
|
|
456
|
+
media-query-tracker.spec.ts
|
|
457
|
+
helpers/
|
|
458
|
+
delegate.spec.ts
|
|
459
|
+
parse-url.spec.ts
|
|
460
|
+
session.spec.ts
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 7.4 Internal Utilities
|
|
464
|
+
|
|
465
|
+
Lightweight internal helpers to keep zero runtime dependencies:
|
|
466
|
+
|
|
467
|
+
| Utility | File | Description |
|
|
468
|
+
|---------|------|-------------|
|
|
469
|
+
| `delegate` | `helpers/delegate.ts` | Event delegation using `document.addEventListener` + selector matching |
|
|
470
|
+
| `parseUrl` | `helpers/parse-url.ts` | Create an `<a>` element to parse URLs (returns `Location`-like object) |
|
|
471
|
+
| `domReady` | `helpers/dom-ready.ts` | Wait for DOM `DOMContentLoaded` |
|
|
472
|
+
| `sessionManager` | `helpers/session.ts` | `sessionStorage`-based session tracking with configurable timeout |
|
|
473
|
+
| `debounce` | `helpers/debounce.ts` | Standard debounce implementation |
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## 8. User Stories
|
|
478
|
+
|
|
479
|
+
| ID | Story | Priority |
|
|
480
|
+
|-------|-------|----------|
|
|
481
|
+
| US-01 | As a developer, I want to track outbound link clicks automatically so I can see which external sites users navigate to | High |
|
|
482
|
+
| US-02 | As a developer, I want to track page visibility so I can measure actual engagement time | High |
|
|
483
|
+
| US-03 | As a developer, I want SPA URL changes to automatically send page_view events so my GA4 reports are accurate | High |
|
|
484
|
+
| US-04 | As a content author, I want to add tracking via HTML data attributes without writing JavaScript | High |
|
|
485
|
+
| US-05 | As a developer, I want to track when specific elements (ads, CTAs) become visible in the viewport | Medium |
|
|
486
|
+
| US-06 | As a developer, I want to track outbound form submissions so I don't lose visibility when users are sent to external payment/signup pages | Medium |
|
|
487
|
+
| US-07 | As a developer, I want to normalize page URLs in my tracking to avoid fragmented data in GA4 reports | Medium |
|
|
488
|
+
| US-08 | As a developer, I want to track which responsive breakpoints are active so I can correlate device size with behavior | Low |
|
|
489
|
+
| US-09 | As a developer, I want to only import the plugins I need so my bundle size stays small | High |
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 9. Constraints & Assumptions
|
|
494
|
+
|
|
495
|
+
### Constraints
|
|
496
|
+
- Must not introduce any third-party runtime dependencies
|
|
497
|
+
- Must work in browser environments only (`window`, `document` required)
|
|
498
|
+
- Must be backward compatible — existing `ga4.init()`, `ga4.send()`, `ga4.gtag`, `dataLayerHelper.get()` APIs must not change
|
|
499
|
+
- Each plugin must be independently importable (tree-shakeable)
|
|
500
|
+
- All DOM event listeners must be removable (no leaks)
|
|
501
|
+
|
|
502
|
+
### Assumptions
|
|
503
|
+
- `window` and `document` are available (browser environment)
|
|
504
|
+
- GA4's `gtag.js` script is loaded by the consumer (or `ga4.init()` has been called)
|
|
505
|
+
- Consumers use modern browsers (no IE11 support needed)
|
|
506
|
+
- `sessionStorage` is available for session-based tracking
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## 10. Risks & Mitigations
|
|
511
|
+
|
|
512
|
+
| Risk | Impact | Likelihood | Mitigation |
|
|
513
|
+
|------|--------|------------|------------|
|
|
514
|
+
| Google changes gtag.js API | High | Low | Use documented public API only; abstract `gtag()` calls through our `ga4.send()` |
|
|
515
|
+
| IntersectionObserver not available in older browsers | Medium | Low | Feature detection — impressionTracker silently no-ops |
|
|
516
|
+
| Monkey-patching `history.pushState` conflicts with other libraries (e.g. React Router) | Medium | Medium | Carefully chain the original function; test with popular routers |
|
|
517
|
+
| `sendBeacon` not available | Low | Low | Fallback to synchronous XHR or `_blank` target trick |
|
|
518
|
+
| `sessionStorage` quota exceeded or disabled | Low | Low | Wrap in try-catch, degrade gracefully |
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 11. Implementation Order & Milestones
|
|
523
|
+
|
|
524
|
+
Plugins are ordered by priority and dependency:
|
|
525
|
+
|
|
526
|
+
| Phase | Milestone | Plugins / Tasks | Target Date | Status |
|
|
527
|
+
|-------|-----------|-----------------|-------------|--------|
|
|
528
|
+
| 0 | Core infrastructure | `helpers/` utilities, plugin base class, `ga4.use()` method | TBD | Not Started |
|
|
529
|
+
| 1 | High-priority plugins | `eventTracker`, `outboundLinkTracker` | TBD | Not Started |
|
|
530
|
+
| 2 | SPA & visibility | `urlChangeTracker`, `pageVisibilityTracker` | TBD | Not Started |
|
|
531
|
+
| 3 | Remaining plugins | `impressionTracker`, `outboundFormTracker`, `cleanUrlTracker` | TBD | Not Started |
|
|
532
|
+
| 4 | Low-priority | `mediaQueryTracker` | TBD | Not Started |
|
|
533
|
+
| 5 | Polish | Documentation, README update, examples | TBD | Not Started |
|
|
534
|
+
| 6 | Release | npm publish v2.0.0 | TBD | Not Started |
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## 12. Sign-Off
|
|
539
|
+
|
|
540
|
+
| Role | Name | Date | Approved |
|
|
541
|
+
|----------------|------|------|----------|
|
|
542
|
+
| Product Owner | | | [ ] |
|
|
543
|
+
| Tech Lead | | | [ ] |
|
|
544
|
+
| QA | | | [ ] |
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
_This document is a living artifact. Update it as requirements evolve._
|
package/babel.config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
presets: [
|
|
3
|
-
['@babel/preset-env', {targets: {node: 'current'}}],
|
|
4
|
-
'@babel/preset-typescript',
|
|
5
|
-
],
|
|
1
|
+
module.exports = {
|
|
2
|
+
presets: [
|
|
3
|
+
['@babel/preset-env', {targets: {node: 'current'}}],
|
|
4
|
+
'@babel/preset-typescript',
|
|
5
|
+
],
|
|
6
6
|
};
|
package/build/main/ga4/ga4.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { ga4Option } from "./ga4option";
|
|
2
2
|
import { KeyValueParams, gtag } from "../types/gtag";
|
|
3
|
+
import { GA4Plugin, SendFunction } from "../types/plugins";
|
|
3
4
|
declare class ga4 {
|
|
4
5
|
private static instance;
|
|
6
|
+
private _plugins;
|
|
5
7
|
private constructor();
|
|
6
8
|
init(option: ga4Option): void;
|
|
7
9
|
static getInstance(): ga4;
|
|
8
10
|
send(eventName: string, eventParameters: KeyValueParams): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Register a plugin with the GA4 instance.
|
|
13
|
+
* @param PluginClass The plugin class constructor
|
|
14
|
+
* @param options Plugin-specific configuration options
|
|
15
|
+
* @returns The plugin instance (call `.remove()` to unregister)
|
|
16
|
+
*/
|
|
17
|
+
use<T extends GA4Plugin>(PluginClass: new (send: SendFunction, options?: any) => T, options?: any): T;
|
|
18
|
+
/**
|
|
19
|
+
* Remove all registered plugins and clean up their listeners.
|
|
20
|
+
*/
|
|
21
|
+
removeAll(): void;
|
|
9
22
|
get gtag(): gtag;
|
|
10
23
|
}
|
|
11
24
|
export { ga4 };
|
package/build/main/ga4/ga4.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ga4 = void 0;
|
|
4
4
|
class ga4 {
|
|
5
5
|
static instance;
|
|
6
|
+
_plugins = [];
|
|
6
7
|
constructor() {
|
|
7
8
|
}
|
|
8
9
|
init(option) {
|
|
@@ -23,9 +24,31 @@ class ga4 {
|
|
|
23
24
|
window.gtag('event', eventName, eventParameters);
|
|
24
25
|
return true;
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Register a plugin with the GA4 instance.
|
|
29
|
+
* @param PluginClass The plugin class constructor
|
|
30
|
+
* @param options Plugin-specific configuration options
|
|
31
|
+
* @returns The plugin instance (call `.remove()` to unregister)
|
|
32
|
+
*/
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
use(PluginClass, options) {
|
|
35
|
+
const send = (eventName, params) => {
|
|
36
|
+
window.gtag('event', eventName, params);
|
|
37
|
+
};
|
|
38
|
+
const plugin = new PluginClass(send, options);
|
|
39
|
+
this._plugins.push(plugin);
|
|
40
|
+
return plugin;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove all registered plugins and clean up their listeners.
|
|
44
|
+
*/
|
|
45
|
+
removeAll() {
|
|
46
|
+
this._plugins.forEach(p => p.remove());
|
|
47
|
+
this._plugins = [];
|
|
48
|
+
}
|
|
26
49
|
get gtag() {
|
|
27
50
|
return window.gtag;
|
|
28
51
|
}
|
|
29
52
|
}
|
|
30
53
|
exports.ga4 = ga4;
|
|
31
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
54
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2E0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2dhNC9nYTQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBTUEsTUFBTSxHQUFHO0lBRUcsTUFBTSxDQUFDLFFBQVEsQ0FBTTtJQUNyQixRQUFRLEdBQWdCLEVBQUUsQ0FBQztJQUVuQztJQUNBLENBQUM7SUFFTSxJQUFJLENBQUMsTUFBZ0I7UUFDeEIsTUFBTSxDQUFDLFNBQVMsR0FBRyxNQUFNLENBQUMsU0FBUyxLQUFJLEtBQXNCLENBQUEsQ0FBQztRQUM5RCxNQUFNLENBQUMsSUFBSSxHQUFHLE1BQU0sQ0FBQyxJQUFJLElBQUk7WUFDekIsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDckMsQ0FBQyxDQUFBO1FBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzlCLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMzQyxDQUFDO0lBRU0sTUFBTSxDQUFDLFdBQVc7UUFDckIsSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUU7WUFDZixHQUFHLENBQUMsUUFBUSxHQUFHLElBQUksR0FBRyxFQUFFLENBQUM7U0FDNUI7UUFDRCxPQUFPLEdBQUcsQ0FBQyxRQUFRLENBQUM7SUFDeEIsQ0FBQztJQUVNLElBQUksQ0FBQyxTQUFnQixFQUFFLGVBQStCO1FBRXpELE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFNBQVMsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUVqRCxPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCw4REFBOEQ7SUFDdkQsR0FBRyxDQUNOLFdBQXlELEVBQ3pELE9BQWE7UUFFYixNQUFNLElBQUksR0FBaUIsQ0FBQyxTQUFpQixFQUFFLE1BQStCLEVBQUUsRUFBRTtZQUM5RSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBd0IsQ0FBQyxDQUFDO1FBQzlELENBQUMsQ0FBQztRQUNGLE1BQU0sTUFBTSxHQUFHLElBQUksV0FBVyxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztRQUM5QyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzQixPQUFPLE1BQU0sQ0FBQztJQUNsQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxTQUFTO1FBQ1osSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUN2QyxJQUFJLENBQUMsUUFBUSxHQUFHLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBRUQsSUFBSSxJQUFJO1FBQ0osT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDO0lBQ3ZCLENBQUM7Q0FDSjtBQUVPLGtCQUFHIn0=
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.debounce = void 0;
|
|
4
|
+
function debounce(fn, delay) {
|
|
5
|
+
let timer = null;
|
|
6
|
+
const debounced = function (...args) {
|
|
7
|
+
if (timer !== null)
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
timer = setTimeout(() => {
|
|
10
|
+
timer = null;
|
|
11
|
+
fn.apply(this, args);
|
|
12
|
+
}, delay);
|
|
13
|
+
};
|
|
14
|
+
debounced.cancel = () => {
|
|
15
|
+
if (timer !== null) {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
timer = null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return debounced;
|
|
21
|
+
}
|
|
22
|
+
exports.debounce = debounce;
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVib3VuY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaGVscGVycy9kZWJvdW5jZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFLQSxTQUFnQixRQUFRLENBQ3BCLEVBQUssRUFDTCxLQUFhO0lBRWIsSUFBSSxLQUFLLEdBQXlDLElBQUksQ0FBQztJQUV2RCxNQUFNLFNBQVMsR0FBRyxVQUF5QixHQUFHLElBQW1CO1FBQzdELElBQUksS0FBSyxLQUFLLElBQUk7WUFBRSxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDeEMsS0FBSyxHQUFHLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDcEIsS0FBSyxHQUFHLElBQUksQ0FBQztZQUNiLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3pCLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNkLENBQXlCLENBQUM7SUFFMUIsU0FBUyxDQUFDLE1BQU0sR0FBRyxHQUFHLEVBQUU7UUFDcEIsSUFBSSxLQUFLLEtBQUssSUFBSSxFQUFFO1lBQ2hCLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNwQixLQUFLLEdBQUcsSUFBSSxDQUFDO1NBQ2hCO0lBQ0wsQ0FBQyxDQUFDO0lBRUYsT0FBTyxTQUFTLENBQUM7QUFDckIsQ0FBQztBQXRCRCw0QkFzQkMifQ==
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface DelegateHandle {
|
|
2
|
+
destroy(): void;
|
|
3
|
+
}
|
|
4
|
+
export interface DelegateOptions {
|
|
5
|
+
composed?: boolean;
|
|
6
|
+
useCapture?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function delegate(target: EventTarget, eventType: string, selector: string, handler: (event: Event, element: Element) => void, options?: DelegateOptions): DelegateHandle;
|