@forcecalendar/interface 1.0.26 → 1.0.28

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.
@@ -3,291 +3,291 @@
3
3
  */
4
4
 
5
5
  export class DOMUtils {
6
- /**
7
- * Create element with attributes and children
8
- */
9
- static createElement(tag, attributes = {}, children = []) {
10
- const element = document.createElement(tag);
11
-
12
- // Set attributes
13
- Object.entries(attributes).forEach(([key, value]) => {
14
- if (key === 'className') {
15
- element.className = value;
16
- } else if (key === 'style' && typeof value === 'object') {
17
- Object.assign(element.style, value);
18
- } else if (key.startsWith('data-')) {
19
- element.setAttribute(key, value);
20
- } else if (key.startsWith('on') && typeof value === 'function') {
21
- const eventName = key.slice(2).toLowerCase();
22
- element.addEventListener(eventName, value);
23
- } else {
24
- element[key] = value;
25
- }
26
- });
27
-
28
- // Add children
29
- children.forEach(child => {
30
- if (typeof child === 'string') {
31
- element.appendChild(document.createTextNode(child));
32
- } else if (child instanceof Node) {
33
- element.appendChild(child);
34
- }
35
- });
36
-
37
- return element;
6
+ /**
7
+ * Create element with attributes and children
8
+ */
9
+ static createElement(tag, attributes = {}, children = []) {
10
+ const element = document.createElement(tag);
11
+
12
+ // Set attributes
13
+ Object.entries(attributes).forEach(([key, value]) => {
14
+ if (key === 'className') {
15
+ element.className = value;
16
+ } else if (key === 'style' && typeof value === 'object') {
17
+ Object.assign(element.style, value);
18
+ } else if (key.startsWith('data-')) {
19
+ element.setAttribute(key, value);
20
+ } else if (key.startsWith('on') && typeof value === 'function') {
21
+ const eventName = key.slice(2).toLowerCase();
22
+ element.addEventListener(eventName, value);
23
+ } else {
24
+ element[key] = value;
25
+ }
26
+ });
27
+
28
+ // Add children
29
+ children.forEach(child => {
30
+ if (typeof child === 'string') {
31
+ element.appendChild(document.createTextNode(child));
32
+ } else if (child instanceof Node) {
33
+ element.appendChild(child);
34
+ }
35
+ });
36
+
37
+ return element;
38
+ }
39
+
40
+ /**
41
+ * Add multiple event listeners
42
+ */
43
+ static addEventListeners(element, events) {
44
+ Object.entries(events).forEach(([event, handler]) => {
45
+ element.addEventListener(event, handler);
46
+ });
47
+
48
+ // Return cleanup function
49
+ return () => {
50
+ Object.entries(events).forEach(([event, handler]) => {
51
+ element.removeEventListener(event, handler);
52
+ });
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Delegate event handling
58
+ */
59
+ static delegate(element, selector, event, handler) {
60
+ const delegatedHandler = e => {
61
+ const target = e.target.closest(selector);
62
+ if (target && element.contains(target)) {
63
+ handler.call(target, e);
64
+ }
65
+ };
66
+
67
+ element.addEventListener(event, delegatedHandler);
68
+ return () => element.removeEventListener(event, delegatedHandler);
69
+ }
70
+
71
+ /**
72
+ * Get element position relative to viewport
73
+ */
74
+ static getPosition(element) {
75
+ const rect = element.getBoundingClientRect();
76
+ return {
77
+ top: rect.top + window.scrollY,
78
+ left: rect.left + window.scrollX,
79
+ bottom: rect.bottom + window.scrollY,
80
+ right: rect.right + window.scrollX,
81
+ width: rect.width,
82
+ height: rect.height
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Check if element is in viewport
88
+ */
89
+ static isInViewport(element, threshold = 0) {
90
+ const rect = element.getBoundingClientRect();
91
+ return (
92
+ rect.top >= -threshold &&
93
+ rect.left >= -threshold &&
94
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + threshold &&
95
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) + threshold
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Smooth scroll to element
101
+ */
102
+ static scrollToElement(element, options = {}) {
103
+ const { behavior = 'smooth', block = 'start', inline = 'nearest' } = options;
104
+ element.scrollIntoView({ behavior, block, inline });
105
+ }
106
+
107
+ /**
108
+ * Get computed style value
109
+ */
110
+ static getStyle(element, property) {
111
+ return window.getComputedStyle(element).getPropertyValue(property);
112
+ }
113
+
114
+ /**
115
+ * Set multiple styles
116
+ */
117
+ static setStyles(element, styles) {
118
+ Object.assign(element.style, styles);
119
+ }
120
+
121
+ /**
122
+ * Add/remove classes with animation support
123
+ */
124
+ static async animateClass(element, className, duration = 300) {
125
+ element.classList.add(className);
126
+ await this.wait(duration);
127
+ element.classList.remove(className);
128
+ }
129
+
130
+ /**
131
+ * Wait for animation/transition to complete
132
+ */
133
+ static waitForAnimation(element, eventType = 'animationend') {
134
+ return new Promise(resolve => {
135
+ const handler = () => {
136
+ element.removeEventListener(eventType, handler);
137
+ resolve();
138
+ };
139
+ element.addEventListener(eventType, handler);
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Utility wait function
145
+ */
146
+ static wait(ms) {
147
+ return new Promise(resolve => setTimeout(resolve, ms));
148
+ }
149
+
150
+ /**
151
+ * Parse HTML string safely
152
+ */
153
+ static parseHTML(htmlString) {
154
+ const template = document.createElement('template');
155
+ template.innerHTML = htmlString.trim();
156
+ return template.content.firstChild;
157
+ }
158
+
159
+ /**
160
+ * Escape HTML to prevent XSS
161
+ */
162
+ static escapeHTML(str) {
163
+ const div = document.createElement('div');
164
+ div.textContent = str;
165
+ return div.innerHTML;
166
+ }
167
+
168
+ /**
169
+ * Debounce function calls
170
+ */
171
+ static debounce(func, wait = 250) {
172
+ let timeout;
173
+ return function executedFunction(...args) {
174
+ const later = () => {
175
+ clearTimeout(timeout);
176
+ func(...args);
177
+ };
178
+ clearTimeout(timeout);
179
+ timeout = setTimeout(later, wait);
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Throttle function calls
185
+ */
186
+ static throttle(func, limit = 250) {
187
+ let inThrottle;
188
+ return function (...args) {
189
+ if (!inThrottle) {
190
+ func.apply(this, args);
191
+ inThrottle = true;
192
+ setTimeout(() => (inThrottle = false), limit);
193
+ }
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Get closest parent matching selector
199
+ */
200
+ static closest(element, selector) {
201
+ return element.closest(selector);
202
+ }
203
+
204
+ /**
205
+ * Get all parents matching selector
206
+ */
207
+ static parents(element, selector) {
208
+ const parents = [];
209
+ let parent = element.parentElement;
210
+
211
+ while (parent) {
212
+ if (parent.matches(selector)) {
213
+ parents.push(parent);
214
+ }
215
+ parent = parent.parentElement;
38
216
  }
39
217
 
40
- /**
41
- * Add multiple event listeners
42
- */
43
- static addEventListeners(element, events) {
44
- Object.entries(events).forEach(([event, handler]) => {
45
- element.addEventListener(event, handler);
46
- });
47
-
48
- // Return cleanup function
49
- return () => {
50
- Object.entries(events).forEach(([event, handler]) => {
51
- element.removeEventListener(event, handler);
52
- });
53
- };
218
+ return parents;
219
+ }
220
+
221
+ /**
222
+ * Measure element dimensions including margins
223
+ */
224
+ static getOuterDimensions(element) {
225
+ const styles = window.getComputedStyle(element);
226
+ const margin = {
227
+ top: parseInt(styles.marginTop),
228
+ right: parseInt(styles.marginRight),
229
+ bottom: parseInt(styles.marginBottom),
230
+ left: parseInt(styles.marginLeft)
231
+ };
232
+
233
+ return {
234
+ width: element.offsetWidth + margin.left + margin.right,
235
+ height: element.offsetHeight + margin.top + margin.bottom,
236
+ margin
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Clone element with event listeners
242
+ */
243
+ static cloneWithEvents(element, deep = true) {
244
+ const clone = element.cloneNode(deep);
245
+
246
+ // Copy event listeners (Note: This is a simplified version)
247
+ // In production, you'd need a more robust event copying mechanism
248
+ return clone;
249
+ }
250
+
251
+ /**
252
+ * Focus trap for modals/dialogs
253
+ */
254
+ static trapFocus(container) {
255
+ const focusableElements = container.querySelectorAll(
256
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
257
+ );
258
+
259
+ // Handle case where there are no focusable elements
260
+ if (focusableElements.length === 0) {
261
+ // Make container focusable as fallback
262
+ container.setAttribute('tabindex', '-1');
263
+ container.focus();
264
+ return () => container.removeAttribute('tabindex');
54
265
  }
55
266
 
56
- /**
57
- * Delegate event handling
58
- */
59
- static delegate(element, selector, event, handler) {
60
- const delegatedHandler = (e) => {
61
- const target = e.target.closest(selector);
62
- if (target && element.contains(target)) {
63
- handler.call(target, e);
64
- }
65
- };
66
-
67
- element.addEventListener(event, delegatedHandler);
68
- return () => element.removeEventListener(event, delegatedHandler);
69
- }
70
-
71
- /**
72
- * Get element position relative to viewport
73
- */
74
- static getPosition(element) {
75
- const rect = element.getBoundingClientRect();
76
- return {
77
- top: rect.top + window.scrollY,
78
- left: rect.left + window.scrollX,
79
- bottom: rect.bottom + window.scrollY,
80
- right: rect.right + window.scrollX,
81
- width: rect.width,
82
- height: rect.height
83
- };
84
- }
85
-
86
- /**
87
- * Check if element is in viewport
88
- */
89
- static isInViewport(element, threshold = 0) {
90
- const rect = element.getBoundingClientRect();
91
- return (
92
- rect.top >= -threshold &&
93
- rect.left >= -threshold &&
94
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + threshold &&
95
- rect.right <= (window.innerWidth || document.documentElement.clientWidth) + threshold
96
- );
97
- }
98
-
99
- /**
100
- * Smooth scroll to element
101
- */
102
- static scrollToElement(element, options = {}) {
103
- const { behavior = 'smooth', block = 'start', inline = 'nearest' } = options;
104
- element.scrollIntoView({ behavior, block, inline });
105
- }
106
-
107
- /**
108
- * Get computed style value
109
- */
110
- static getStyle(element, property) {
111
- return window.getComputedStyle(element).getPropertyValue(property);
112
- }
113
-
114
- /**
115
- * Set multiple styles
116
- */
117
- static setStyles(element, styles) {
118
- Object.assign(element.style, styles);
119
- }
120
-
121
- /**
122
- * Add/remove classes with animation support
123
- */
124
- static async animateClass(element, className, duration = 300) {
125
- element.classList.add(className);
126
- await this.wait(duration);
127
- element.classList.remove(className);
128
- }
129
-
130
- /**
131
- * Wait for animation/transition to complete
132
- */
133
- static waitForAnimation(element, eventType = 'animationend') {
134
- return new Promise(resolve => {
135
- const handler = () => {
136
- element.removeEventListener(eventType, handler);
137
- resolve();
138
- };
139
- element.addEventListener(eventType, handler);
140
- });
141
- }
142
-
143
- /**
144
- * Utility wait function
145
- */
146
- static wait(ms) {
147
- return new Promise(resolve => setTimeout(resolve, ms));
148
- }
149
-
150
- /**
151
- * Parse HTML string safely
152
- */
153
- static parseHTML(htmlString) {
154
- const template = document.createElement('template');
155
- template.innerHTML = htmlString.trim();
156
- return template.content.firstChild;
157
- }
267
+ const firstFocusable = focusableElements[0];
268
+ const lastFocusable = focusableElements[focusableElements.length - 1];
158
269
 
159
- /**
160
- * Escape HTML to prevent XSS
161
- */
162
- static escapeHTML(str) {
163
- const div = document.createElement('div');
164
- div.textContent = str;
165
- return div.innerHTML;
166
- }
270
+ const handleKeyDown = e => {
271
+ if (e.key !== 'Tab') return;
167
272
 
168
- /**
169
- * Debounce function calls
170
- */
171
- static debounce(func, wait = 250) {
172
- let timeout;
173
- return function executedFunction(...args) {
174
- const later = () => {
175
- clearTimeout(timeout);
176
- func(...args);
177
- };
178
- clearTimeout(timeout);
179
- timeout = setTimeout(later, wait);
180
- };
181
- }
182
-
183
- /**
184
- * Throttle function calls
185
- */
186
- static throttle(func, limit = 250) {
187
- let inThrottle;
188
- return function(...args) {
189
- if (!inThrottle) {
190
- func.apply(this, args);
191
- inThrottle = true;
192
- setTimeout(() => inThrottle = false, limit);
193
- }
194
- };
195
- }
196
-
197
- /**
198
- * Get closest parent matching selector
199
- */
200
- static closest(element, selector) {
201
- return element.closest(selector);
202
- }
203
-
204
- /**
205
- * Get all parents matching selector
206
- */
207
- static parents(element, selector) {
208
- const parents = [];
209
- let parent = element.parentElement;
210
-
211
- while (parent) {
212
- if (parent.matches(selector)) {
213
- parents.push(parent);
214
- }
215
- parent = parent.parentElement;
273
+ if (e.shiftKey) {
274
+ if (document.activeElement === firstFocusable) {
275
+ lastFocusable?.focus();
276
+ e.preventDefault();
216
277
  }
217
-
218
- return parents;
219
- }
220
-
221
- /**
222
- * Measure element dimensions including margins
223
- */
224
- static getOuterDimensions(element) {
225
- const styles = window.getComputedStyle(element);
226
- const margin = {
227
- top: parseInt(styles.marginTop),
228
- right: parseInt(styles.marginRight),
229
- bottom: parseInt(styles.marginBottom),
230
- left: parseInt(styles.marginLeft)
231
- };
232
-
233
- return {
234
- width: element.offsetWidth + margin.left + margin.right,
235
- height: element.offsetHeight + margin.top + margin.bottom,
236
- margin
237
- };
238
- }
239
-
240
- /**
241
- * Clone element with event listeners
242
- */
243
- static cloneWithEvents(element, deep = true) {
244
- const clone = element.cloneNode(deep);
245
-
246
- // Copy event listeners (Note: This is a simplified version)
247
- // In production, you'd need a more robust event copying mechanism
248
- return clone;
249
- }
250
-
251
- /**
252
- * Focus trap for modals/dialogs
253
- */
254
- static trapFocus(container) {
255
- const focusableElements = container.querySelectorAll(
256
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
257
- );
258
-
259
- // Handle case where there are no focusable elements
260
- if (focusableElements.length === 0) {
261
- // Make container focusable as fallback
262
- container.setAttribute('tabindex', '-1');
263
- container.focus();
264
- return () => container.removeAttribute('tabindex');
278
+ } else {
279
+ if (document.activeElement === lastFocusable) {
280
+ firstFocusable?.focus();
281
+ e.preventDefault();
265
282
  }
283
+ }
284
+ };
266
285
 
267
- const firstFocusable = focusableElements[0];
268
- const lastFocusable = focusableElements[focusableElements.length - 1];
269
-
270
- const handleKeyDown = (e) => {
271
- if (e.key !== 'Tab') return;
272
-
273
- if (e.shiftKey) {
274
- if (document.activeElement === firstFocusable) {
275
- lastFocusable?.focus();
276
- e.preventDefault();
277
- }
278
- } else {
279
- if (document.activeElement === lastFocusable) {
280
- firstFocusable?.focus();
281
- e.preventDefault();
282
- }
283
- }
284
- };
285
-
286
- container.addEventListener('keydown', handleKeyDown);
287
- firstFocusable?.focus();
288
-
289
- return () => container.removeEventListener('keydown', handleKeyDown);
290
- }
286
+ container.addEventListener('keydown', handleKeyDown);
287
+ firstFocusable?.focus();
288
+
289
+ return () => container.removeEventListener('keydown', handleKeyDown);
290
+ }
291
291
  }
292
292
 
293
- export default DOMUtils;
293
+ export default DOMUtils;