universe-code 0.0.85 → 0.0.87
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/dist/angular/index.js +2 -1
- package/dist/angular/spark/index.js +328 -0
- package/dist/react/index.js +3 -1
- package/dist/react/spark/index.js +144 -0
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { IdbService } from './indexdb/services/idbService.js';
|
|
1
|
+
export { IdbService } from './indexdb/services/idbService.js';
|
|
2
|
+
export { ColorSparkService, initSpark, singleSpark } from './spark/index.js';
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
class ColorSparkService {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.observers = new Map();
|
|
4
|
+
this.intervals = new Map();
|
|
5
|
+
this.previousValues = new Map();
|
|
6
|
+
|
|
7
|
+
this._injectStyles(); // <-- inject CSS on service creation
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
_injectStyles() {
|
|
11
|
+
if (document.getElementById('color-spark-style')) return;
|
|
12
|
+
|
|
13
|
+
const style = document.createElement('style');
|
|
14
|
+
style.id = 'color-spark-style';
|
|
15
|
+
|
|
16
|
+
style.textContent = `
|
|
17
|
+
@-webkit-keyframes anim-opac {
|
|
18
|
+
from { opacity: 1; }
|
|
19
|
+
to { opacity: 0; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes anim-opac {
|
|
23
|
+
from { opacity: 1; }
|
|
24
|
+
to { opacity: 0; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* BACK highlight */
|
|
28
|
+
.spark-back::before {
|
|
29
|
+
-webkit-animation: anim-opac 1s both;
|
|
30
|
+
animation: anim-opac 1s both;
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset: 0;
|
|
33
|
+
background: var(--spark-back-bg, #f8e71c);
|
|
34
|
+
border: 1px solid var(--spark-back-border, #f8e71c);
|
|
35
|
+
content: "";
|
|
36
|
+
border-radius: inherit;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* LAY highlight */
|
|
43
|
+
.spark-lay::before {
|
|
44
|
+
-webkit-animation: anim-opac 1s both;
|
|
45
|
+
animation: anim-opac 1s both;
|
|
46
|
+
position: absolute;
|
|
47
|
+
inset: 0;
|
|
48
|
+
background: var(--spark-lay-bg, #ff3b30);
|
|
49
|
+
border: 1px solid var(--spark-lay-border, #ff3b30);
|
|
50
|
+
content: "";
|
|
51
|
+
border-radius: inherit;
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
document.head.appendChild(style);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Monitor a single element for value changes and apply spark effect
|
|
65
|
+
*
|
|
66
|
+
* @param {HTMLElement} element - Element to monitor
|
|
67
|
+
* @returns {Function} Cleanup function to stop monitoring
|
|
68
|
+
*/
|
|
69
|
+
sparkElement(element) {
|
|
70
|
+
if (!element || !(element instanceof HTMLElement)) {
|
|
71
|
+
console.warn('[ColorSpark] Invalid element provided');
|
|
72
|
+
return () => { };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const elementId = this._getElementId(element);
|
|
76
|
+
|
|
77
|
+
// 1️⃣ Watch element visibility (performance optimization)
|
|
78
|
+
const visibilityObserver = new IntersectionObserver((entries) => {
|
|
79
|
+
entries.forEach((entry) => {
|
|
80
|
+
if (entry.isIntersecting) {
|
|
81
|
+
this._startMonitoring(element, elementId);
|
|
82
|
+
} else {
|
|
83
|
+
this._stopMonitoring(elementId);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
visibilityObserver.observe(element);
|
|
89
|
+
this.observers.set(elementId, visibilityObserver);
|
|
90
|
+
|
|
91
|
+
// 2️⃣ Watch for element removal from DOM
|
|
92
|
+
const removalObserver = new MutationObserver((mutations) => {
|
|
93
|
+
mutations.forEach((mutation) => {
|
|
94
|
+
mutation.removedNodes.forEach((node) => {
|
|
95
|
+
if (node === element) {
|
|
96
|
+
this.cleanup(elementId);
|
|
97
|
+
removalObserver.disconnect();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
removalObserver.observe(document.body, {
|
|
104
|
+
childList: true,
|
|
105
|
+
subtree: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Return cleanup function
|
|
109
|
+
return () => {
|
|
110
|
+
this.cleanup(elementId);
|
|
111
|
+
removalObserver.disconnect();
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Initialize global spark monitoring for all [data-color-spark] elements
|
|
117
|
+
*
|
|
118
|
+
* @returns {Function} Global cleanup function
|
|
119
|
+
*/
|
|
120
|
+
initGlobal() {
|
|
121
|
+
if (typeof document === 'undefined') {
|
|
122
|
+
console.warn('[ColorSpark] Document not available (SSR?)');
|
|
123
|
+
return () => { };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const cleanupFunctions = [];
|
|
127
|
+
|
|
128
|
+
// 3️⃣ Find and monitor all existing elements
|
|
129
|
+
const existingElements = document.querySelectorAll('[data-color-spark]');
|
|
130
|
+
|
|
131
|
+
existingElements.forEach((el) => {
|
|
132
|
+
if (el instanceof HTMLElement) {
|
|
133
|
+
const cleanup = this.sparkElement(el);
|
|
134
|
+
cleanupFunctions.push(cleanup);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 4️⃣ Watch for new elements added to DOM
|
|
139
|
+
const dynamicObserver = new MutationObserver((mutations) => {
|
|
140
|
+
mutations.forEach((mutation) => {
|
|
141
|
+
mutation.addedNodes.forEach((node) => {
|
|
142
|
+
if (!(node instanceof HTMLElement)) return;
|
|
143
|
+
|
|
144
|
+
// Check if node itself has the attribute
|
|
145
|
+
if (node.hasAttribute('data-color-spark')) {
|
|
146
|
+
const cleanup = this.sparkElement(node);
|
|
147
|
+
cleanupFunctions.push(cleanup);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check children for the attribute
|
|
151
|
+
const children = node.querySelectorAll('[data-color-spark]');
|
|
152
|
+
children.forEach((child) => {
|
|
153
|
+
if (child instanceof HTMLElement) {
|
|
154
|
+
const cleanup = this.sparkElement(child);
|
|
155
|
+
cleanupFunctions.push(cleanup);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
dynamicObserver.observe(document.body, {
|
|
163
|
+
childList: true,
|
|
164
|
+
subtree: true,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 5️⃣ Return global cleanup
|
|
168
|
+
return () => {
|
|
169
|
+
dynamicObserver.disconnect();
|
|
170
|
+
cleanupFunctions.forEach((fn) => fn?.());
|
|
171
|
+
this.cleanupAll();
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ================================
|
|
176
|
+
* INTERNAL LOGIC
|
|
177
|
+
* ================================ */
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Start monitoring element for text changes
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
183
|
+
_startMonitoring(element, elementId) {
|
|
184
|
+
// Already monitoring
|
|
185
|
+
if (this.intervals.has(elementId)) return;
|
|
186
|
+
|
|
187
|
+
const checkInterval = setInterval(() => {
|
|
188
|
+
const currentValue = element.innerText || element.textContent || '';
|
|
189
|
+
const previousValue = this.previousValues.get(elementId);
|
|
190
|
+
|
|
191
|
+
// Value changed → Apply spark effect
|
|
192
|
+
if (
|
|
193
|
+
previousValue !== null &&
|
|
194
|
+
previousValue !== undefined &&
|
|
195
|
+
previousValue !== currentValue
|
|
196
|
+
) {
|
|
197
|
+
const sparkClass = this._getSparkClass(element);
|
|
198
|
+
|
|
199
|
+
if (sparkClass) {
|
|
200
|
+
element.classList.add(sparkClass);
|
|
201
|
+
|
|
202
|
+
// Remove after animation completes
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
element.classList.remove(sparkClass);
|
|
205
|
+
}, 600);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.previousValues.set(elementId, currentValue);
|
|
210
|
+
}, 600);
|
|
211
|
+
|
|
212
|
+
this.intervals.set(elementId, checkInterval);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Stop monitoring element
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
_stopMonitoring(elementId) {
|
|
220
|
+
const interval = this.intervals.get(elementId);
|
|
221
|
+
|
|
222
|
+
if (interval) {
|
|
223
|
+
clearInterval(interval);
|
|
224
|
+
this.intervals.delete(elementId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Determine which spark animation to apply based on element classes
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
_getSparkClass(element) {
|
|
233
|
+
// Check element itself
|
|
234
|
+
if (this._isBackElement(element)) return 'spark-back';
|
|
235
|
+
if (this._isLayElement(element)) return 'spark-lay';
|
|
236
|
+
|
|
237
|
+
// Check parent element
|
|
238
|
+
const parent = element.closest('.back, .lay, .btn-back, .btn-lay');
|
|
239
|
+
|
|
240
|
+
if (parent) {
|
|
241
|
+
if (this._isBackElement(parent)) return 'spark-back';
|
|
242
|
+
if (this._isLayElement(parent)) return 'spark-lay';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Default to back animation
|
|
246
|
+
return 'spark-back';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if element has BACK-related classes
|
|
251
|
+
* @private
|
|
252
|
+
*/
|
|
253
|
+
_isBackElement(element) {
|
|
254
|
+
return (
|
|
255
|
+
element.classList.contains('back') ||
|
|
256
|
+
element.classList.contains('btn-back')
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if element has LAY-related classes
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
_isLayElement(element) {
|
|
265
|
+
return (
|
|
266
|
+
element.classList.contains('lay') ||
|
|
267
|
+
element.classList.contains('btn-lay')
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Cleanup specific element's monitoring
|
|
273
|
+
*
|
|
274
|
+
* @param {string} elementId - Unique element identifier
|
|
275
|
+
*/
|
|
276
|
+
cleanup(elementId) {
|
|
277
|
+
this._stopMonitoring(elementId);
|
|
278
|
+
|
|
279
|
+
const observer = this.observers.get(elementId);
|
|
280
|
+
if (observer) {
|
|
281
|
+
observer.disconnect();
|
|
282
|
+
this.observers.delete(elementId);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.previousValues.delete(elementId);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Cleanup all monitoring (called on destroy)
|
|
290
|
+
*/
|
|
291
|
+
cleanupAll() {
|
|
292
|
+
this.intervals.forEach((interval) => clearInterval(interval));
|
|
293
|
+
this.observers.forEach((observer) => observer.disconnect());
|
|
294
|
+
|
|
295
|
+
this.intervals.clear();
|
|
296
|
+
this.observers.clear();
|
|
297
|
+
this.previousValues.clear();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Generate or retrieve unique ID for element
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
_getElementId(element) {
|
|
305
|
+
const idKey = '__colorSparkId';
|
|
306
|
+
|
|
307
|
+
if (!element[idKey]) {
|
|
308
|
+
element[idKey] = 'spark_' + Math.random().toString(36).slice(2, 10);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return element[idKey];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
function initSpark() {
|
|
317
|
+
const service = new ColorSparkService();
|
|
318
|
+
return service.initGlobal();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
function singleSpark(element) {
|
|
323
|
+
const service = new ColorSparkService();
|
|
324
|
+
return service.sparkElement(element);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
export { ColorSparkService, initSpark, singleSpark };
|
package/dist/react/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { getIdbStore } from './indexdb/store/idbStore.js';
|
|
2
2
|
import { useIdbStore } from './indexdb/hooks/idbHook.js';
|
|
3
3
|
import { configureIdb } from "./indexdb/store/config.js";
|
|
4
|
+
import { useColorSpark } from "./spark/index.js";
|
|
4
5
|
|
|
5
6
|
// Export everything as a unified object or individual pieces
|
|
6
|
-
export { configureIdb, getIdbStore, useIdbStore };
|
|
7
|
+
export { configureIdb, getIdbStore, useIdbStore, useColorSpark };
|
|
7
8
|
|
|
8
9
|
// Default export for easy usage
|
|
9
10
|
export default {
|
|
10
11
|
configureIdb,
|
|
11
12
|
getIdbStore,
|
|
12
13
|
useIdbStore,
|
|
14
|
+
useColorSpark
|
|
13
15
|
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
export function useColorSpark() {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
injectSparkStyles();
|
|
6
|
+
|
|
7
|
+
function injectSparkStyles() {
|
|
8
|
+
if (document.getElementById("spark-style")) return;
|
|
9
|
+
|
|
10
|
+
const style = document.createElement("style");
|
|
11
|
+
style.id = "spark-style";
|
|
12
|
+
|
|
13
|
+
style.textContent = `
|
|
14
|
+
.spark-back::before {
|
|
15
|
+
-webkit-animation: anim-opac 1s both;
|
|
16
|
+
animation: anim-opac 1s both;
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 0;
|
|
19
|
+
bottom: 0;
|
|
20
|
+
left: 0;
|
|
21
|
+
right: 0;
|
|
22
|
+
background: var(--spark-back-bg, #f8e71c);
|
|
23
|
+
border: 1px solid var(--spark-back-border, #f8e71c);
|
|
24
|
+
content: " ";
|
|
25
|
+
border-radius: inherit;
|
|
26
|
+
display: flex;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes anim-opac {
|
|
32
|
+
from {
|
|
33
|
+
opacity: 1;
|
|
34
|
+
}
|
|
35
|
+
to {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.spark-lay::before {
|
|
41
|
+
-webkit-animation: anim-opac 1s both;
|
|
42
|
+
animation: anim-opac 1s both;
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 0;
|
|
45
|
+
bottom: 0;
|
|
46
|
+
left: 0;
|
|
47
|
+
right: 0;
|
|
48
|
+
background: var(--spark-lay-bg, #ff3b30);
|
|
49
|
+
border: 1px solid var(--spark-lay-border, #ff3b30);
|
|
50
|
+
content: " ";
|
|
51
|
+
border-radius: inherit;
|
|
52
|
+
display: flex;
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: 100%;
|
|
55
|
+
}`;
|
|
56
|
+
document.head.appendChild(style);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function attachColorSpark(el) {
|
|
60
|
+
let previousValue = null;
|
|
61
|
+
let intervalId = null;
|
|
62
|
+
let isVisible = false;
|
|
63
|
+
|
|
64
|
+
const visibilityObserver = new IntersectionObserver((entries) => {
|
|
65
|
+
entries.forEach((entry) => {
|
|
66
|
+
isVisible = entry.isIntersecting;
|
|
67
|
+
if (isVisible) start();
|
|
68
|
+
else stop();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
visibilityObserver.observe(el);
|
|
73
|
+
|
|
74
|
+
function start() {
|
|
75
|
+
if (intervalId) return;
|
|
76
|
+
|
|
77
|
+
intervalId = window.setInterval(() => {
|
|
78
|
+
const currentValue = el.innerText.trim();
|
|
79
|
+
|
|
80
|
+
if (previousValue !== null && previousValue !== currentValue) {
|
|
81
|
+
if (el.classList.contains("back")) {
|
|
82
|
+
el.classList.add("spark-back");
|
|
83
|
+
} else if (el.classList.contains("lay")) {
|
|
84
|
+
el.classList.add("spark-lay");
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
el.classList.remove("spark-back");
|
|
88
|
+
el.classList.remove("spark-lay");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
previousValue = currentValue;
|
|
92
|
+
}, 600);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function stop() {
|
|
96
|
+
if (!intervalId) return;
|
|
97
|
+
clearInterval(intervalId);
|
|
98
|
+
intervalId = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const domObserver = new MutationObserver((mutations) => {
|
|
102
|
+
mutations.forEach((mutation) => {
|
|
103
|
+
mutation.removedNodes.forEach((node) => {
|
|
104
|
+
if (node === el) {
|
|
105
|
+
stop();
|
|
106
|
+
visibilityObserver.disconnect();
|
|
107
|
+
domObserver.disconnect();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
domObserver.observe(document.body, { childList: true, subtree: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// existing elements
|
|
117
|
+
Array.from(
|
|
118
|
+
document.querySelectorAll("[data-color-spark]")
|
|
119
|
+
).forEach(attachColorSpark);
|
|
120
|
+
|
|
121
|
+
// future elements
|
|
122
|
+
const rootObserver = new MutationObserver((mutations) => {
|
|
123
|
+
mutations.forEach((mutation) => {
|
|
124
|
+
mutation.addedNodes.forEach((node) => {
|
|
125
|
+
if (!(node instanceof HTMLElement)) return;
|
|
126
|
+
|
|
127
|
+
if (node.hasAttribute("data-color-spark")) {
|
|
128
|
+
attachColorSpark(node);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Array.from(
|
|
132
|
+
node.querySelectorAll("[data-color-spark]")
|
|
133
|
+
).forEach(attachColorSpark);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
rootObserver.observe(document.body, { childList: true, subtree: true });
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
rootObserver.disconnect();
|
|
142
|
+
};
|
|
143
|
+
}, []);
|
|
144
|
+
}
|