ultra-light-js 1.0.18
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/LICENSE.md +674 -0
- package/README.md +437 -0
- package/dist/jsx.d.ts +684 -0
- package/dist/jsx.d.ts.map +1 -0
- package/dist/jsx.js +109 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/ultra-light.d.ts +71 -0
- package/dist/ultra-light.d.ts.map +1 -0
- package/dist/ultra-light.js +816 -0
- package/package.json +77 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import { hasCleanup } from './types';
|
|
2
|
+
const SVG_EXCLUSIVE_TAGS = new Set([
|
|
3
|
+
'svg', 'circle', 'ellipse', 'line', 'polygon', 'polyline', 'rect', 'path', 'g',
|
|
4
|
+
'defs', 'symbol', 'use', 'marker', 'clipPath', 'mask', 'pattern', 'title', 'text', 'tspan',
|
|
5
|
+
'linearGradient', 'radialGradient', 'meshGradient', 'stop', 'hatch', 'hatchpath',
|
|
6
|
+
'animate', 'animateMotion', 'animateTransform', 'set', 'animateColor', 'mpath',
|
|
7
|
+
'filter', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite',
|
|
8
|
+
'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight',
|
|
9
|
+
'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR',
|
|
10
|
+
'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
|
|
11
|
+
'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile',
|
|
12
|
+
'feTurbulence', 'view', 'font', 'glyph', 'missing-glyph', 'vkern', 'hkern',
|
|
13
|
+
'color-profile', 'switch', 'cursor', 'image'
|
|
14
|
+
]);
|
|
15
|
+
const TAG_REGEX = /^<([a-z][a-z0-9-]*)/i;
|
|
16
|
+
export function parseHTMLString(htmlString, document) {
|
|
17
|
+
if (!document)
|
|
18
|
+
document = window.document;
|
|
19
|
+
if (typeof htmlString !== 'string')
|
|
20
|
+
return htmlString;
|
|
21
|
+
const trimmed = htmlString
|
|
22
|
+
.trim()
|
|
23
|
+
.replace(/\n/g, '')
|
|
24
|
+
.replace(/\s{2,}/g, ' ');
|
|
25
|
+
if (!trimmed)
|
|
26
|
+
return null;
|
|
27
|
+
const tagMatch = trimmed.match(TAG_REGEX);
|
|
28
|
+
if (!tagMatch)
|
|
29
|
+
return null;
|
|
30
|
+
const tag = tagMatch[1].toLowerCase();
|
|
31
|
+
if (SVG_EXCLUSIVE_TAGS.has(tag)) {
|
|
32
|
+
const temp = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
33
|
+
temp.innerHTML = trimmed;
|
|
34
|
+
return temp.firstElementChild;
|
|
35
|
+
}
|
|
36
|
+
const template = document.createElement('template');
|
|
37
|
+
template.innerHTML = trimmed;
|
|
38
|
+
return template.content.firstElementChild;
|
|
39
|
+
}
|
|
40
|
+
function stableHash(str) {
|
|
41
|
+
let hash = 0;
|
|
42
|
+
for (let i = 0; i < str.length; i++) {
|
|
43
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
44
|
+
hash |= 0;
|
|
45
|
+
}
|
|
46
|
+
return Math.abs(hash).toString(36);
|
|
47
|
+
}
|
|
48
|
+
function deepFreeze(obj) {
|
|
49
|
+
if (typeof obj !== 'object' || obj === null)
|
|
50
|
+
return obj;
|
|
51
|
+
Object.keys(obj).forEach(key => deepFreeze(obj[key]));
|
|
52
|
+
return Object.freeze(obj);
|
|
53
|
+
}
|
|
54
|
+
let activeScope = null;
|
|
55
|
+
function registerInScope(unsub) {
|
|
56
|
+
if (activeScope)
|
|
57
|
+
activeScope.push(unsub);
|
|
58
|
+
}
|
|
59
|
+
export function ultraScope(fn) {
|
|
60
|
+
const prev = activeScope;
|
|
61
|
+
const scope = [];
|
|
62
|
+
activeScope = scope;
|
|
63
|
+
try {
|
|
64
|
+
const result = fn();
|
|
65
|
+
return [result, () => scope.forEach(unsub => {
|
|
66
|
+
try {
|
|
67
|
+
unsub();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('Error while disposing ultraScope:', error);
|
|
71
|
+
}
|
|
72
|
+
})];
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
activeScope = prev;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function ultraState(initialValue, freeze = false) {
|
|
79
|
+
if (initialValue === undefined) {
|
|
80
|
+
console.warn('ultraState: initialValue is undefined');
|
|
81
|
+
}
|
|
82
|
+
const maybeFreeze = (v) => (freeze && typeof v === 'object' && v !== null) ? deepFreeze(v) : v;
|
|
83
|
+
let value = maybeFreeze(initialValue);
|
|
84
|
+
const subscribers = new Set();
|
|
85
|
+
const setValue = (newValue) => {
|
|
86
|
+
if (typeof value !== 'object' && value === newValue)
|
|
87
|
+
return;
|
|
88
|
+
value = maybeFreeze(newValue);
|
|
89
|
+
subscribers.forEach(fn => {
|
|
90
|
+
try {
|
|
91
|
+
fn(value);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error('Error en subscriber de ultraState:', error);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const subscribe = (fn) => {
|
|
99
|
+
subscribers.add(fn);
|
|
100
|
+
const unsubscribe = () => { subscribers.delete(fn); };
|
|
101
|
+
registerInScope(unsubscribe);
|
|
102
|
+
return unsubscribe;
|
|
103
|
+
};
|
|
104
|
+
return [
|
|
105
|
+
() => value,
|
|
106
|
+
setValue,
|
|
107
|
+
subscribe
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
export function UltraContext(initialValue, displayName) {
|
|
111
|
+
let owner = null;
|
|
112
|
+
let assigned = false;
|
|
113
|
+
displayName = (!displayName) ? 'context' : displayName;
|
|
114
|
+
if (initialValue === undefined) {
|
|
115
|
+
console.warn('UltraContext: initialValue is undefined');
|
|
116
|
+
}
|
|
117
|
+
let value = initialValue;
|
|
118
|
+
const subscribers = new Set();
|
|
119
|
+
function canReach(candidate) {
|
|
120
|
+
if (!owner)
|
|
121
|
+
return true;
|
|
122
|
+
if (!candidate)
|
|
123
|
+
return false;
|
|
124
|
+
if (!owner?.contains(candidate)) {
|
|
125
|
+
console.warn('UltraContext: unreachable context');
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
;
|
|
133
|
+
function getValue(candidate) {
|
|
134
|
+
if (!canReach(candidate))
|
|
135
|
+
return "undefined";
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function setValue(newValue, candidate) {
|
|
139
|
+
if (!canReach(candidate))
|
|
140
|
+
return;
|
|
141
|
+
if (typeof value !== 'object' && value === newValue)
|
|
142
|
+
return;
|
|
143
|
+
value = newValue;
|
|
144
|
+
subscribers.forEach(fn => {
|
|
145
|
+
try {
|
|
146
|
+
fn(value);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error('UltraContext: error in subscriber:', error);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
;
|
|
154
|
+
function subscribe(fn, candidate) {
|
|
155
|
+
if (!canReach(candidate))
|
|
156
|
+
return () => { };
|
|
157
|
+
subscribers.add(fn);
|
|
158
|
+
return () => subscribers.delete(fn);
|
|
159
|
+
}
|
|
160
|
+
;
|
|
161
|
+
function own(newOwner) {
|
|
162
|
+
if (assigned) {
|
|
163
|
+
throw new Error(`UltraContext: context owner for "${displayName}" cannot be reassigned.\n`
|
|
164
|
+
+ `Current owner: ${owner?.tagName || 'null'}.`);
|
|
165
|
+
}
|
|
166
|
+
owner = newOwner;
|
|
167
|
+
assigned = true;
|
|
168
|
+
}
|
|
169
|
+
;
|
|
170
|
+
return {
|
|
171
|
+
set: setValue,
|
|
172
|
+
get: getValue,
|
|
173
|
+
subscribe,
|
|
174
|
+
own
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export function ultraQueryParams() {
|
|
178
|
+
const urlData = new URLSearchParams(window.location.search);
|
|
179
|
+
const params = {};
|
|
180
|
+
urlData.forEach((value, key) => {
|
|
181
|
+
params[key] = value;
|
|
182
|
+
});
|
|
183
|
+
return params;
|
|
184
|
+
}
|
|
185
|
+
function matchRoute(routePath, currentPath) {
|
|
186
|
+
if (routePath === currentPath) {
|
|
187
|
+
return { params: {}, matched: true };
|
|
188
|
+
}
|
|
189
|
+
if (routePath === '/*') {
|
|
190
|
+
return { params: {}, matched: true, isWildcard: true };
|
|
191
|
+
}
|
|
192
|
+
const routeParts = routePath.split('/').filter(p => p);
|
|
193
|
+
const pathParts = currentPath.split('/').filter(p => p);
|
|
194
|
+
if (routeParts.length !== pathParts.length) {
|
|
195
|
+
return { matched: false, params: {} };
|
|
196
|
+
}
|
|
197
|
+
const params = {};
|
|
198
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
199
|
+
if (routeParts[i].startsWith(':')) {
|
|
200
|
+
params[routeParts[i].slice(1)] = pathParts[i];
|
|
201
|
+
}
|
|
202
|
+
else if (routeParts[i] !== pathParts[i]) {
|
|
203
|
+
return { matched: false, params: {} };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { params, matched: true };
|
|
207
|
+
}
|
|
208
|
+
function reconcileRouteScope(hasTargetComponent, wildcardComponent, targetDispose, wildcardDispose) {
|
|
209
|
+
if (!hasTargetComponent && wildcardComponent) {
|
|
210
|
+
return wildcardDispose;
|
|
211
|
+
}
|
|
212
|
+
wildcardDispose?.();
|
|
213
|
+
return targetDispose;
|
|
214
|
+
}
|
|
215
|
+
export function UltraRouter(...routes) {
|
|
216
|
+
const paths = routes.map(r => r.path);
|
|
217
|
+
const duplicates = paths.filter((p, i) => paths.indexOf(p) !== i && p !== '/*');
|
|
218
|
+
if (duplicates.length > 0) {
|
|
219
|
+
console.warn('UltraRouter: Duplicate routes detected:', duplicates.join(', '));
|
|
220
|
+
}
|
|
221
|
+
const container = document.createElement('div');
|
|
222
|
+
container.classList.add('browser-router');
|
|
223
|
+
let currentCleanup = null;
|
|
224
|
+
const renderRoute = () => {
|
|
225
|
+
if (currentCleanup) {
|
|
226
|
+
try {
|
|
227
|
+
currentCleanup();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error('Error while cleaning up previous route:', error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const currentPath = window.location.pathname;
|
|
234
|
+
let targetComponent = null;
|
|
235
|
+
let routeParams = {};
|
|
236
|
+
let wildcardComponent = null;
|
|
237
|
+
let targetDispose = null;
|
|
238
|
+
let wildcardDispose = null;
|
|
239
|
+
routes.forEach(route => {
|
|
240
|
+
const match = matchRoute(route.path, currentPath);
|
|
241
|
+
if (match.matched && !match.isWildcard && !targetComponent) {
|
|
242
|
+
routeParams = match.params;
|
|
243
|
+
const [component, dispose] = ultraScope(() => route.component(routeParams));
|
|
244
|
+
targetComponent = parseHTMLString(component);
|
|
245
|
+
targetDispose = dispose;
|
|
246
|
+
}
|
|
247
|
+
else if (match.matched && match.isWildcard) {
|
|
248
|
+
const [component, dispose] = ultraScope(() => route.component());
|
|
249
|
+
wildcardComponent = parseHTMLString(component);
|
|
250
|
+
wildcardDispose = dispose;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const scopeDispose = reconcileRouteScope(!!targetComponent, wildcardComponent, targetDispose, wildcardDispose);
|
|
254
|
+
if (!targetComponent && wildcardComponent) {
|
|
255
|
+
targetComponent = wildcardComponent;
|
|
256
|
+
}
|
|
257
|
+
container.innerHTML = '';
|
|
258
|
+
if (targetComponent) {
|
|
259
|
+
container.appendChild(targetComponent);
|
|
260
|
+
}
|
|
261
|
+
const nodeCleanup = targetComponent?._cleanup;
|
|
262
|
+
if (nodeCleanup || scopeDispose) {
|
|
263
|
+
currentCleanup = () => {
|
|
264
|
+
nodeCleanup?.();
|
|
265
|
+
scopeDispose?.();
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
renderRoute();
|
|
270
|
+
const handler = () => renderRoute();
|
|
271
|
+
window.addEventListener('popstate', handler);
|
|
272
|
+
const cleanup = () => {
|
|
273
|
+
window.removeEventListener('popstate', handler);
|
|
274
|
+
};
|
|
275
|
+
container._cleanup = () => {
|
|
276
|
+
if (cleanup)
|
|
277
|
+
cleanup();
|
|
278
|
+
if (currentCleanup)
|
|
279
|
+
currentCleanup();
|
|
280
|
+
};
|
|
281
|
+
return container;
|
|
282
|
+
}
|
|
283
|
+
export function ultraNavigate({ href, viewTransition = false }) {
|
|
284
|
+
function navigate() {
|
|
285
|
+
try {
|
|
286
|
+
window.history.pushState({}, '', href);
|
|
287
|
+
window.scrollTo({ top: 0, behavior: 'instant' });
|
|
288
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
console.error('ultraNavigate: Navigation error:', error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!href) {
|
|
295
|
+
console.warn('ultraNavigate: a valid href is required');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!viewTransition) {
|
|
299
|
+
navigate();
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
if (!document.startViewTransition) {
|
|
303
|
+
navigate();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
document.startViewTransition(navigate);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
export function UltraLink({ href, children, viewTransition = false, className = [] }) {
|
|
310
|
+
if (!href) {
|
|
311
|
+
console.warn('UltraLink: href is required');
|
|
312
|
+
}
|
|
313
|
+
const link = document.createElement('a');
|
|
314
|
+
link.href = href;
|
|
315
|
+
function navigate() {
|
|
316
|
+
try {
|
|
317
|
+
window.history.pushState({}, '', href);
|
|
318
|
+
window.scrollTo({ top: 0, behavior: 'instant' });
|
|
319
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error('UltraLink: Navigation error:', error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function clickHandler(e) {
|
|
326
|
+
if (e.ctrlKey || e.metaKey)
|
|
327
|
+
return;
|
|
328
|
+
e.preventDefault();
|
|
329
|
+
if (window.location.pathname === href)
|
|
330
|
+
return;
|
|
331
|
+
if (!viewTransition) {
|
|
332
|
+
navigate();
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
if (!document.startViewTransition) {
|
|
336
|
+
navigate();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
document.startViewTransition(navigate);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
;
|
|
343
|
+
link.addEventListener('click', clickHandler);
|
|
344
|
+
children.forEach(child => {
|
|
345
|
+
if (!child)
|
|
346
|
+
return;
|
|
347
|
+
const childElement = parseHTMLString(child);
|
|
348
|
+
if (childElement) {
|
|
349
|
+
link.appendChild(childElement);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
className.forEach(sel => {
|
|
353
|
+
if (!sel)
|
|
354
|
+
return;
|
|
355
|
+
link.classList.add(sel);
|
|
356
|
+
});
|
|
357
|
+
link._cleanup = () => {
|
|
358
|
+
link.removeEventListener('click', clickHandler);
|
|
359
|
+
};
|
|
360
|
+
return link;
|
|
361
|
+
}
|
|
362
|
+
export function UltraFragment(...children) {
|
|
363
|
+
const fragment = document.createDocumentFragment();
|
|
364
|
+
children.forEach(component => {
|
|
365
|
+
if (!component)
|
|
366
|
+
return;
|
|
367
|
+
const element = parseHTMLString(component);
|
|
368
|
+
if (element) {
|
|
369
|
+
fragment.appendChild(element);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
return fragment;
|
|
373
|
+
}
|
|
374
|
+
export function UltraComponent({ component, eventHandler = {}, attributes = {}, styles = {}, className = [], children = [], trigger = [], onMount = [], cleanup = [], }) {
|
|
375
|
+
const node = parseHTMLString(component);
|
|
376
|
+
if (!node) {
|
|
377
|
+
console.error('UltraComponent: Could not create node');
|
|
378
|
+
return document.createElement('div');
|
|
379
|
+
}
|
|
380
|
+
const cleanupFunctions = [];
|
|
381
|
+
Object.keys(eventHandler).forEach((event) => {
|
|
382
|
+
const handler = eventHandler[event];
|
|
383
|
+
if (handler) {
|
|
384
|
+
node.addEventListener(event, handler);
|
|
385
|
+
cleanupFunctions.push(() => node.removeEventListener(event, handler));
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
Object.keys(styles).forEach(key => {
|
|
389
|
+
try {
|
|
390
|
+
node.style[key] = styles[key];
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.error(`Error al aplicar estilo ${key}:`, error);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
Object.keys(attributes).forEach(key => {
|
|
397
|
+
try {
|
|
398
|
+
node.setAttribute(key, attributes[key]);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
console.error(`Error applying attribute ${String(key)}:`, error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
className.forEach(className => {
|
|
405
|
+
try {
|
|
406
|
+
if (className)
|
|
407
|
+
node.classList.add(className);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.error(`Error al aplicar clase ${className}:`, error);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
children.forEach(child => {
|
|
414
|
+
if (!child)
|
|
415
|
+
return;
|
|
416
|
+
const childElement = parseHTMLString(child);
|
|
417
|
+
if (childElement) {
|
|
418
|
+
node.appendChild(childElement);
|
|
419
|
+
if (hasCleanup(childElement)) {
|
|
420
|
+
cleanupFunctions.push(childElement._cleanup);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
onMount.forEach(fn => {
|
|
425
|
+
requestAnimationFrame(() => {
|
|
426
|
+
void (async () => {
|
|
427
|
+
try {
|
|
428
|
+
const result = fn(node);
|
|
429
|
+
const cleanup = result instanceof Promise ? await result : result;
|
|
430
|
+
if (cleanup)
|
|
431
|
+
cleanupFunctions.push(cleanup);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
console.error('Error while executing onMount function(s):', error);
|
|
435
|
+
}
|
|
436
|
+
})();
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
trigger.forEach(t => {
|
|
440
|
+
const { subscriber, triggerFunction: subscriberFunction, defer } = t;
|
|
441
|
+
if (subscriber && subscriberFunction) {
|
|
442
|
+
try {
|
|
443
|
+
const callback = defer
|
|
444
|
+
? () => requestAnimationFrame(() => subscriberFunction(node))
|
|
445
|
+
: () => subscriberFunction(node);
|
|
446
|
+
const subscribers = Array.isArray(subscriber) ? subscriber : [subscriber];
|
|
447
|
+
subscribers.forEach(sub => {
|
|
448
|
+
const unsubscribe = sub(callback);
|
|
449
|
+
if (unsubscribe) {
|
|
450
|
+
cleanupFunctions.push(unsubscribe);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
console.error('Error en trigger de UltraComponent:', error);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
cleanup.forEach(fn => {
|
|
460
|
+
try {
|
|
461
|
+
cleanupFunctions.push(fn);
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
console.error('Error añadiendo cleanup de UltraComponent:', error);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
node._cleanup = () => {
|
|
468
|
+
cleanupFunctions.forEach(cleanup => {
|
|
469
|
+
try {
|
|
470
|
+
cleanup();
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
console.error('Error al limpiar UltraComponent:', error);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
return node;
|
|
478
|
+
}
|
|
479
|
+
export function UltraActivity({ component, eventHandler = {}, attributes = {}, styles = {}, className = [], children = [], mode, trigger = [], type = 'display', onMount = [], cleanup = [] }) {
|
|
480
|
+
const supportedTypes = ['display', 'visibility'];
|
|
481
|
+
if (!supportedTypes.includes(type)) {
|
|
482
|
+
console.warn(`Activity: type not supported. Display will be used by default. Supported types: ${supportedTypes.join(', ')}`);
|
|
483
|
+
type = 'display';
|
|
484
|
+
}
|
|
485
|
+
const element = parseHTMLString(component);
|
|
486
|
+
if (!element) {
|
|
487
|
+
console.error('Activity: Could not create element');
|
|
488
|
+
return document.createElement('div');
|
|
489
|
+
}
|
|
490
|
+
const childrenElements = children.filter(child => child !== null).map(child => parseHTMLString(child));
|
|
491
|
+
const cleanupFunctions = [];
|
|
492
|
+
Object.keys(eventHandler).forEach((event) => {
|
|
493
|
+
const handler = eventHandler[event];
|
|
494
|
+
if (handler) {
|
|
495
|
+
element.addEventListener(event, handler);
|
|
496
|
+
cleanupFunctions.push(() => element.removeEventListener(event, handler));
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
Object.keys(attributes).forEach(key => {
|
|
500
|
+
try {
|
|
501
|
+
element.setAttribute(key, attributes[key]);
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
console.error(`Error applying attribute ${String(key)}:`, error);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
Object.keys(styles).forEach(key => {
|
|
508
|
+
try {
|
|
509
|
+
element.style[key] = styles[key];
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
console.error(`Error while applying style ${key}:`, error);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
className.forEach(className => {
|
|
516
|
+
try {
|
|
517
|
+
if (className)
|
|
518
|
+
element.classList.add(className);
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
console.error(`Error while applying class ${className}:`, error);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
const fragmentChildren = [];
|
|
525
|
+
if (element.nodeType === 11) {
|
|
526
|
+
Array.from(element.childNodes).forEach(child => fragmentChildren.push(child));
|
|
527
|
+
}
|
|
528
|
+
childrenElements.forEach(childElement => {
|
|
529
|
+
if (childElement) {
|
|
530
|
+
if (element.nodeType === 11) {
|
|
531
|
+
fragmentChildren.push(childElement);
|
|
532
|
+
}
|
|
533
|
+
element.appendChild(childElement);
|
|
534
|
+
if (hasCleanup(childElement)) {
|
|
535
|
+
cleanupFunctions.push(childElement._cleanup);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
const update = () => {
|
|
540
|
+
try {
|
|
541
|
+
const current = mode.state();
|
|
542
|
+
const targets = (element.nodeType === 11)
|
|
543
|
+
? fragmentChildren
|
|
544
|
+
: [element];
|
|
545
|
+
if (type === 'display') {
|
|
546
|
+
targets.forEach(el => el.style.display = current ? '' : 'none');
|
|
547
|
+
}
|
|
548
|
+
else if (type === 'visibility') {
|
|
549
|
+
targets.forEach(el => el.style.visibility = current ? 'visible' : 'hidden');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
console.error('Error while updating Activity:', error);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
if (Array.isArray(mode.subscriber)) {
|
|
557
|
+
mode.subscriber.forEach(subscriber => {
|
|
558
|
+
const unsubscribe = subscriber(update);
|
|
559
|
+
if (unsubscribe) {
|
|
560
|
+
cleanupFunctions.push(unsubscribe);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const unsubscribe = mode.subscriber(update);
|
|
566
|
+
if (unsubscribe) {
|
|
567
|
+
cleanupFunctions.push(unsubscribe);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
update();
|
|
571
|
+
onMount.forEach(fn => {
|
|
572
|
+
requestAnimationFrame(() => {
|
|
573
|
+
void (async () => {
|
|
574
|
+
try {
|
|
575
|
+
const result = fn(element);
|
|
576
|
+
const cleanup = result instanceof Promise ? await result : result;
|
|
577
|
+
if (cleanup)
|
|
578
|
+
cleanupFunctions.push(cleanup);
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
console.error('Error while executing onMount function(s):', error);
|
|
582
|
+
}
|
|
583
|
+
})();
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
trigger.forEach(t => {
|
|
587
|
+
const { subscriber, triggerFunction, defer } = t;
|
|
588
|
+
if (subscriber && triggerFunction) {
|
|
589
|
+
try {
|
|
590
|
+
const callback = defer
|
|
591
|
+
? () => requestAnimationFrame(() => triggerFunction(element))
|
|
592
|
+
: () => triggerFunction(element);
|
|
593
|
+
const subscribers = Array.isArray(subscriber) ? subscriber : [subscriber];
|
|
594
|
+
subscribers.forEach(sub => {
|
|
595
|
+
const unsub = sub(callback);
|
|
596
|
+
if (unsub) {
|
|
597
|
+
cleanupFunctions.push(unsub);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
console.error('Error in Activity trigger:', error);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
cleanup.forEach(fn => {
|
|
607
|
+
try {
|
|
608
|
+
cleanupFunctions.push(fn);
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
console.error('Error adding cleanup to Activity:', error);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
element._cleanup = () => {
|
|
615
|
+
cleanupFunctions.forEach(cleanup => {
|
|
616
|
+
try {
|
|
617
|
+
cleanup();
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
console.error('Error while cleaning up Activity:', error);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
return element;
|
|
625
|
+
}
|
|
626
|
+
const styleCache = new Map();
|
|
627
|
+
export function ultraStyles(cssString, document) {
|
|
628
|
+
if (!document)
|
|
629
|
+
document = window.document;
|
|
630
|
+
if (!cssString || typeof cssString !== 'string') {
|
|
631
|
+
console.warn('ultraStyles: invalid cssString');
|
|
632
|
+
return {};
|
|
633
|
+
}
|
|
634
|
+
const hash = stableHash(cssString);
|
|
635
|
+
if (styleCache.has(hash)) {
|
|
636
|
+
return styleCache.get(hash);
|
|
637
|
+
}
|
|
638
|
+
let styleEl = document.getElementById('ultra-styles');
|
|
639
|
+
if (!styleEl) {
|
|
640
|
+
styleEl = document.createElement('style');
|
|
641
|
+
styleEl.id = 'ultra-styles';
|
|
642
|
+
document.head.appendChild(styleEl);
|
|
643
|
+
}
|
|
644
|
+
const classMap = {};
|
|
645
|
+
const classRegex = /\.([a-zA-Z_-][a-zA-Z0-9_-]*)/g;
|
|
646
|
+
let match;
|
|
647
|
+
const classNames = new Set();
|
|
648
|
+
while ((match = classRegex.exec(cssString)) !== null) {
|
|
649
|
+
classNames.add(match[1]);
|
|
650
|
+
}
|
|
651
|
+
let scopedCSS = cssString;
|
|
652
|
+
classNames.forEach(className => {
|
|
653
|
+
const hashedClass = `${className}_${hash}`;
|
|
654
|
+
classMap[className] = hashedClass;
|
|
655
|
+
const pattern = new RegExp(`\\.${className}\\b`, 'g');
|
|
656
|
+
scopedCSS = scopedCSS.replace(pattern, `.${hashedClass}`);
|
|
657
|
+
});
|
|
658
|
+
if (!styleEl.textContent?.includes(`/*${hash}*/`)) {
|
|
659
|
+
styleEl.textContent += `\n/*${hash}*/\n${scopedCSS}`;
|
|
660
|
+
}
|
|
661
|
+
styleCache.set(hash, classMap);
|
|
662
|
+
return classMap;
|
|
663
|
+
}
|
|
664
|
+
export function ultraCompState(initialComp, freeze = false) {
|
|
665
|
+
if (typeof initialComp !== 'object' || initialComp === null) {
|
|
666
|
+
throw new Error('ultraCompState: initial value cannot be a primitive or null.');
|
|
667
|
+
}
|
|
668
|
+
const comp = {};
|
|
669
|
+
Object.keys(initialComp).forEach(key => {
|
|
670
|
+
if (typeof initialComp[key] === 'function') {
|
|
671
|
+
const fn = initialComp[key];
|
|
672
|
+
comp[key] = (...args) => fn(comp, ...args);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const [getValue, setValue, subscribeToValue] = ultraState(initialComp[key], freeze);
|
|
676
|
+
comp[key] = {
|
|
677
|
+
get: getValue,
|
|
678
|
+
set: setValue,
|
|
679
|
+
subscribe: subscribeToValue,
|
|
680
|
+
};
|
|
681
|
+
});
|
|
682
|
+
return comp;
|
|
683
|
+
}
|
|
684
|
+
export function ultraQuery() {
|
|
685
|
+
const [cache, setCache, subscribeToCache] = ultraState({});
|
|
686
|
+
const [isFetching, setIsFetching, subscribeToFetching] = ultraState(false);
|
|
687
|
+
const [hasError, setHasError, subscribeToHasError] = ultraState(false);
|
|
688
|
+
const [error, setError, subscribeToError] = ultraState(null);
|
|
689
|
+
const timerMap = new Map();
|
|
690
|
+
const pendingMap = new Map();
|
|
691
|
+
const invalidateCache = (key) => {
|
|
692
|
+
const newCache = { ...cache() };
|
|
693
|
+
delete newCache[key];
|
|
694
|
+
setCache(newCache);
|
|
695
|
+
if (timerMap.has(key)) {
|
|
696
|
+
clearTimeout(timerMap.get(key));
|
|
697
|
+
timerMap.delete(key);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
const addCache = (key, value, staleTime) => {
|
|
701
|
+
if (timerMap.has(key)) {
|
|
702
|
+
clearTimeout(timerMap.get(key));
|
|
703
|
+
}
|
|
704
|
+
setCache({ ...cache(), [key]: value });
|
|
705
|
+
const timer = setTimeout(() => {
|
|
706
|
+
invalidateCache(key);
|
|
707
|
+
}, staleTime);
|
|
708
|
+
timerMap.set(key, timer);
|
|
709
|
+
};
|
|
710
|
+
const fetch = async (key, fetcher, staleTime = 5 * 60 * 1000) => {
|
|
711
|
+
if (Object.hasOwn(cache(), key)) {
|
|
712
|
+
return {
|
|
713
|
+
hasError,
|
|
714
|
+
isFetching,
|
|
715
|
+
data: cache()[key],
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
if (pendingMap.has(key)) {
|
|
719
|
+
await pendingMap.get(key);
|
|
720
|
+
return {
|
|
721
|
+
hasError,
|
|
722
|
+
isFetching,
|
|
723
|
+
data: cache()[key],
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
setIsFetching(true);
|
|
727
|
+
setHasError(false);
|
|
728
|
+
setError(null);
|
|
729
|
+
const pending = (async () => {
|
|
730
|
+
try {
|
|
731
|
+
const data = await fetcher();
|
|
732
|
+
addCache(key, data, staleTime);
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
setHasError(true);
|
|
736
|
+
setError(error);
|
|
737
|
+
}
|
|
738
|
+
finally {
|
|
739
|
+
setIsFetching(false);
|
|
740
|
+
pendingMap.delete(key);
|
|
741
|
+
}
|
|
742
|
+
})();
|
|
743
|
+
pendingMap.set(key, pending);
|
|
744
|
+
await pending;
|
|
745
|
+
return {
|
|
746
|
+
hasError,
|
|
747
|
+
isFetching,
|
|
748
|
+
data: cache()[key],
|
|
749
|
+
};
|
|
750
|
+
};
|
|
751
|
+
return {
|
|
752
|
+
fetch,
|
|
753
|
+
isFetching,
|
|
754
|
+
hasError,
|
|
755
|
+
error,
|
|
756
|
+
subscribeToFetching,
|
|
757
|
+
subscribeToHasError,
|
|
758
|
+
subscribeToError,
|
|
759
|
+
subscribeToCache,
|
|
760
|
+
cache,
|
|
761
|
+
invalidateCache,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
export function ultraPortal(app, portal) {
|
|
765
|
+
const $app = (typeof app === 'string') ? document.querySelector(app) : app;
|
|
766
|
+
if (!$app) {
|
|
767
|
+
throw new Error('UltraPortal: No application found with selector:');
|
|
768
|
+
}
|
|
769
|
+
const $portalElement = parseHTMLString(portal);
|
|
770
|
+
if (!$portalElement) {
|
|
771
|
+
throw new Error('UltraPortal: Invalid portal element');
|
|
772
|
+
}
|
|
773
|
+
$app.after($portalElement);
|
|
774
|
+
}
|
|
775
|
+
function isValidCssObject(value) {
|
|
776
|
+
return (value !== null &&
|
|
777
|
+
typeof value === 'object' &&
|
|
778
|
+
!Array.isArray(value) &&
|
|
779
|
+
Object.getPrototypeOf(value) === Object.prototype &&
|
|
780
|
+
Object.keys(value).length > 0);
|
|
781
|
+
}
|
|
782
|
+
export function ultraStyles2(cssObject, document = window.document) {
|
|
783
|
+
if (!isValidCssObject(cssObject)) {
|
|
784
|
+
console.warn('ultraStyles2: invalid cssObject');
|
|
785
|
+
return {};
|
|
786
|
+
}
|
|
787
|
+
let $styleEl = document.getElementById('ultra-styles');
|
|
788
|
+
if (!$styleEl) {
|
|
789
|
+
$styleEl = document.createElement('style');
|
|
790
|
+
$styleEl.id = 'ultra-styles';
|
|
791
|
+
document.head.appendChild($styleEl);
|
|
792
|
+
}
|
|
793
|
+
const returnable = {};
|
|
794
|
+
const hash = stableHash(JSON.stringify(cssObject));
|
|
795
|
+
if (styleCache.has(hash)) {
|
|
796
|
+
return styleCache.get(hash);
|
|
797
|
+
}
|
|
798
|
+
let cssString = '';
|
|
799
|
+
for (const selector of Object.keys(cssObject)) {
|
|
800
|
+
const className = `${selector}_${hash}`;
|
|
801
|
+
returnable[selector] = className;
|
|
802
|
+
cssString += `.${className}{`;
|
|
803
|
+
const styles = cssObject[selector];
|
|
804
|
+
for (const prop in styles) {
|
|
805
|
+
const value = styles[prop];
|
|
806
|
+
if (value === undefined || value === null)
|
|
807
|
+
continue;
|
|
808
|
+
const kebabProp = prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
809
|
+
cssString += `${kebabProp}:${String(value)};`;
|
|
810
|
+
}
|
|
811
|
+
cssString += `}\n`;
|
|
812
|
+
}
|
|
813
|
+
$styleEl.appendChild(document.createTextNode(cssString));
|
|
814
|
+
styleCache.set(hash, returnable);
|
|
815
|
+
return returnable;
|
|
816
|
+
}
|