@briannorman9/eli-utils 1.0.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/README.md +81 -0
- package/index.js +262 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @briannorman9/eli-utils
|
|
2
|
+
|
|
3
|
+
Utility functions for ELI web experiments.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @briannorman9/eli-utils
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
In your ELI project code, you can import utils using the special `@eli/utils` import syntax (the server will automatically resolve it to the installed `eli-utils` package):
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import utils from '@eli/utils';
|
|
17
|
+
|
|
18
|
+
// Wait for an element to appear
|
|
19
|
+
utils.waitForElement('#myElement').then(element => {
|
|
20
|
+
console.log('Element found:', element);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Wait until a condition is met
|
|
24
|
+
utils.waitUntil(() => document.readyState === 'complete');
|
|
25
|
+
|
|
26
|
+
// DOM manipulation
|
|
27
|
+
utils.addClass('#myButton', 'active');
|
|
28
|
+
utils.removeClass('#myButton', 'inactive');
|
|
29
|
+
|
|
30
|
+
// Event handling
|
|
31
|
+
utils.on('#myButton', 'click', () => console.log('Clicked!'));
|
|
32
|
+
|
|
33
|
+
// Cookies
|
|
34
|
+
const userId = utils.getCookie('userId');
|
|
35
|
+
utils.setCookie('userId', '12345', 30);
|
|
36
|
+
|
|
37
|
+
// Query parameters
|
|
38
|
+
const campaign = utils.getQueryParam('campaign');
|
|
39
|
+
|
|
40
|
+
// Custom events
|
|
41
|
+
utils.triggerEvent('experimentLoaded', { variant: 'v1' });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Reference
|
|
45
|
+
|
|
46
|
+
### Element Utilities
|
|
47
|
+
|
|
48
|
+
- `waitForElement(selector)` - Wait for element to appear (waits indefinitely)
|
|
49
|
+
- `waitUntil(condition, interval)` - Wait until condition is true (waits indefinitely, checks every `interval` ms, default: 100ms)
|
|
50
|
+
- `select(selector, context)` - Select single element
|
|
51
|
+
- `selectAll(selector, context)` - Select multiple elements
|
|
52
|
+
- `addClass(element, className)` - Add class to element
|
|
53
|
+
- `removeClass(element, className)` - Remove class from element
|
|
54
|
+
- `toggleClass(element, className)` - Toggle class on element
|
|
55
|
+
- `hasClass(element, className)` - Check if element has class
|
|
56
|
+
|
|
57
|
+
### Event Utilities
|
|
58
|
+
|
|
59
|
+
- `on(element, event, handler)` - Add event listener
|
|
60
|
+
- `off(element, event, handler)` - Remove event listener
|
|
61
|
+
- `delegate(parent, selector, event, handler)` - Event delegation
|
|
62
|
+
- `triggerEvent(eventName, data, target)` - Trigger custom event
|
|
63
|
+
|
|
64
|
+
### Cookie Utilities
|
|
65
|
+
|
|
66
|
+
- `getCookie(name)` - Get cookie value
|
|
67
|
+
- `setCookie(name, value, days, path)` - Set cookie
|
|
68
|
+
|
|
69
|
+
### URL Utilities
|
|
70
|
+
|
|
71
|
+
- `getQueryParam(name, url)` - Get URL query parameter
|
|
72
|
+
|
|
73
|
+
### Viewport Utilities
|
|
74
|
+
|
|
75
|
+
- `getViewport()` - Get viewport dimensions
|
|
76
|
+
- `isInViewport(element, threshold)` - Check if element is in viewport
|
|
77
|
+
- `scrollIntoView(element, options)` - Scroll element into view
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// ELI Utils - Utility functions for web experiments
|
|
2
|
+
// This package can be installed via: npm install @briannorman9/eli-utils
|
|
3
|
+
// Then imported in variant code using: import utils from '@eli/utils';
|
|
4
|
+
// (The server automatically resolves @eli/utils to the @briannorman9/eli-utils package)
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
/**
|
|
8
|
+
* Wait for an element to appear in the DOM
|
|
9
|
+
* @param {string|Element} selector - CSS selector or element
|
|
10
|
+
* @returns {Promise<Element>} Promise that resolves with the element (waits indefinitely)
|
|
11
|
+
*/
|
|
12
|
+
waitForElement: function(selector) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
// If selector is already an element, return it immediately
|
|
15
|
+
if (selector instanceof Element) {
|
|
16
|
+
resolve(selector);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Check if element already exists
|
|
21
|
+
const element = document.querySelector(selector);
|
|
22
|
+
if (element) {
|
|
23
|
+
resolve(element);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Set up MutationObserver to watch for element
|
|
28
|
+
const observer = new MutationObserver((mutations, obs) => {
|
|
29
|
+
const element = document.querySelector(selector);
|
|
30
|
+
if (element) {
|
|
31
|
+
obs.disconnect();
|
|
32
|
+
resolve(element);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
observer.observe(document.body, {
|
|
37
|
+
childList: true,
|
|
38
|
+
subtree: true
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wait until a condition function returns true
|
|
45
|
+
* @param {Function} condition - Function that returns a boolean
|
|
46
|
+
* @param {number} interval - Check interval in milliseconds (default: 100)
|
|
47
|
+
* @returns {Promise} Promise that resolves when condition is met (waits indefinitely)
|
|
48
|
+
*/
|
|
49
|
+
waitUntil: function(condition, interval = 100) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const checkCondition = () => {
|
|
52
|
+
try {
|
|
53
|
+
if (condition()) {
|
|
54
|
+
resolve();
|
|
55
|
+
} else {
|
|
56
|
+
setTimeout(checkCondition, interval);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
reject(error);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
checkCondition();
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get a cookie value by name
|
|
69
|
+
* @param {string} name - Cookie name
|
|
70
|
+
* @returns {string|null} Cookie value or null if not found
|
|
71
|
+
*/
|
|
72
|
+
getCookie: function(name) {
|
|
73
|
+
const value = `; ${document.cookie}`;
|
|
74
|
+
const parts = value.split(`; ${name}=`);
|
|
75
|
+
if (parts.length === 2) {
|
|
76
|
+
return parts.pop().split(';').shift();
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set a cookie
|
|
83
|
+
* @param {string} name - Cookie name
|
|
84
|
+
* @param {string} value - Cookie value
|
|
85
|
+
* @param {number} days - Number of days until expiration (default: 365)
|
|
86
|
+
* @param {string} path - Cookie path (default: '/')
|
|
87
|
+
*/
|
|
88
|
+
setCookie: function(name, value, days = 365, path = '/') {
|
|
89
|
+
const expires = new Date();
|
|
90
|
+
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
91
|
+
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=${path}`;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get a URL query parameter value
|
|
96
|
+
* @param {string} name - Parameter name
|
|
97
|
+
* @param {string} url - Optional URL (defaults to current window location)
|
|
98
|
+
* @returns {string|null} Parameter value or null if not found
|
|
99
|
+
*/
|
|
100
|
+
getQueryParam: function(name, url = window.location.href) {
|
|
101
|
+
const urlObj = new URL(url);
|
|
102
|
+
return urlObj.searchParams.get(name);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Trigger a custom event
|
|
107
|
+
* @param {string} eventName - Event name
|
|
108
|
+
* @param {Object} data - Event data
|
|
109
|
+
* @param {Element|string} target - Target element (defaults to document)
|
|
110
|
+
*/
|
|
111
|
+
triggerEvent: function(eventName, data = {}, target = document) {
|
|
112
|
+
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
113
|
+
if (targetEl) {
|
|
114
|
+
const event = new CustomEvent(eventName, { detail: data });
|
|
115
|
+
targetEl.dispatchEvent(event);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Select a single element
|
|
121
|
+
* @param {string} selector - CSS selector
|
|
122
|
+
* @param {Element} context - Context element (defaults to document)
|
|
123
|
+
* @returns {Element|null} Element or null if not found
|
|
124
|
+
*/
|
|
125
|
+
select: function(selector, context = document) {
|
|
126
|
+
return context.querySelector(selector);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Select multiple elements
|
|
131
|
+
* @param {string} selector - CSS selector
|
|
132
|
+
* @param {Element} context - Context element (defaults to document)
|
|
133
|
+
* @returns {NodeList} NodeList of elements
|
|
134
|
+
*/
|
|
135
|
+
selectAll: function(selector, context = document) {
|
|
136
|
+
return context.querySelectorAll(selector);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Add a class to an element
|
|
141
|
+
* @param {Element|string} element - Element or selector
|
|
142
|
+
* @param {string} className - Class name to add
|
|
143
|
+
*/
|
|
144
|
+
addClass: function(element, className) {
|
|
145
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
146
|
+
if (el) el.classList.add(className);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Remove a class from an element
|
|
151
|
+
* @param {Element|string} element - Element or selector
|
|
152
|
+
* @param {string} className - Class name to remove
|
|
153
|
+
*/
|
|
154
|
+
removeClass: function(element, className) {
|
|
155
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
156
|
+
if (el) el.classList.remove(className);
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Toggle a class on an element
|
|
161
|
+
* @param {Element|string} element - Element or selector
|
|
162
|
+
* @param {string} className - Class name to toggle
|
|
163
|
+
*/
|
|
164
|
+
toggleClass: function(element, className) {
|
|
165
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
166
|
+
if (el) el.classList.toggle(className);
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if an element has a class
|
|
171
|
+
* @param {Element|string} element - Element or selector
|
|
172
|
+
* @param {string} className - Class name to check
|
|
173
|
+
* @returns {boolean} True if element has the class
|
|
174
|
+
*/
|
|
175
|
+
hasClass: function(element, className) {
|
|
176
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
177
|
+
return el ? el.classList.contains(className) : false;
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Add an event listener
|
|
182
|
+
* @param {Element|string} element - Element or selector
|
|
183
|
+
* @param {string} event - Event name
|
|
184
|
+
* @param {Function} handler - Event handler
|
|
185
|
+
*/
|
|
186
|
+
on: function(element, event, handler) {
|
|
187
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
188
|
+
if (el) el.addEventListener(event, handler);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Remove an event listener
|
|
193
|
+
* @param {Element|string} element - Element or selector
|
|
194
|
+
* @param {string} event - Event name
|
|
195
|
+
* @param {Function} handler - Event handler
|
|
196
|
+
*/
|
|
197
|
+
off: function(element, event, handler) {
|
|
198
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
199
|
+
if (el) el.removeEventListener(event, handler);
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Event delegation - attach event to parent, handle on children
|
|
204
|
+
* @param {Element|string} parent - Parent element or selector
|
|
205
|
+
* @param {string} selector - Child selector to match
|
|
206
|
+
* @param {string} event - Event name
|
|
207
|
+
* @param {Function} handler - Event handler
|
|
208
|
+
*/
|
|
209
|
+
delegate: function(parent, selector, event, handler) {
|
|
210
|
+
const parentEl = typeof parent === 'string' ? document.querySelector(parent) : parent;
|
|
211
|
+
if (parentEl) {
|
|
212
|
+
parentEl.addEventListener(event, (e) => {
|
|
213
|
+
if (e.target.matches(selector) || e.target.closest(selector)) {
|
|
214
|
+
handler(e);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get the viewport dimensions
|
|
222
|
+
* @returns {Object} Object with width and height
|
|
223
|
+
*/
|
|
224
|
+
getViewport: function() {
|
|
225
|
+
return {
|
|
226
|
+
width: window.innerWidth || document.documentElement.clientWidth,
|
|
227
|
+
height: window.innerHeight || document.documentElement.clientHeight
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if element is in viewport
|
|
233
|
+
* @param {Element|string} element - Element or selector
|
|
234
|
+
* @param {number} threshold - Visibility threshold (0-1, default: 0)
|
|
235
|
+
* @returns {boolean} True if element is in viewport
|
|
236
|
+
*/
|
|
237
|
+
isInViewport: function(element, threshold = 0) {
|
|
238
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
239
|
+
if (!el) return false;
|
|
240
|
+
|
|
241
|
+
const rect = el.getBoundingClientRect();
|
|
242
|
+
const viewport = this.getViewport();
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
rect.top >= -threshold * rect.height &&
|
|
246
|
+
rect.left >= -threshold * rect.width &&
|
|
247
|
+
rect.bottom <= viewport.height + threshold * rect.height &&
|
|
248
|
+
rect.right <= viewport.width + threshold * rect.width
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Scroll element into view
|
|
254
|
+
* @param {Element|string} element - Element or selector
|
|
255
|
+
* @param {Object} options - ScrollIntoView options
|
|
256
|
+
*/
|
|
257
|
+
scrollIntoView: function(element, options = { behavior: 'smooth', block: 'center' }) {
|
|
258
|
+
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
|
259
|
+
if (el) el.scrollIntoView(options);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@briannorman9/eli-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Utility functions for ELI web experiments",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"eli",
|
|
12
|
+
"utils",
|
|
13
|
+
"web-experiments",
|
|
14
|
+
"utilities"
|
|
15
|
+
],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=12.0.0"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/briannorman/eli.git",
|
|
24
|
+
"directory": "packages/eli-utils"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|