native-document 1.0.0 โ 1.0.1
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/package.json +1 -1
- package/src/data/Observable.js +0 -1
- package/src/router/Router.js +1 -2
- package/src/router/RouterComponent.js +1 -3
- package/src/utils/debug-manager.js +4 -7
- package/src/wrappers/AttributesWrapper.js +0 -1
- package/dist/native-document.dev.js +0 -2346
- package/dist/native-document.min.js +0 -1
|
@@ -1,2346 +0,0 @@
|
|
|
1
|
-
var NativeDocument = (function (exports) {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
function eventWrapper(element, name, callback) {
|
|
5
|
-
element.addEventListener(name, callback);
|
|
6
|
-
return element;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {HTMLElement} element
|
|
12
|
-
* @returns {HTMLElement}
|
|
13
|
-
*/
|
|
14
|
-
function HtmlElementEventsWrapper(element) {
|
|
15
|
-
|
|
16
|
-
if(!element.nd) {
|
|
17
|
-
element.nd = {};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @param {Object<string,Function>} events
|
|
22
|
-
*/
|
|
23
|
-
element.nd.on = function(events) {
|
|
24
|
-
for(const event in events) {
|
|
25
|
-
const callback = events[event];
|
|
26
|
-
eventWrapper(element, event, callback);
|
|
27
|
-
}
|
|
28
|
-
return element;
|
|
29
|
-
};
|
|
30
|
-
element.nd.on.prevent = function(events) {
|
|
31
|
-
for(const event in events) {
|
|
32
|
-
const callback = events[event];
|
|
33
|
-
eventWrapper(element, event, (event) => {
|
|
34
|
-
event.preventDefault();
|
|
35
|
-
callback && callback(event);
|
|
36
|
-
return element;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return element;
|
|
40
|
-
};
|
|
41
|
-
const events = {
|
|
42
|
-
click: (callback) => eventWrapper(element, 'click', callback),
|
|
43
|
-
focus: (callback) => eventWrapper(element, 'focus', callback),
|
|
44
|
-
blur: (callback) => eventWrapper(element, 'blur', callback),
|
|
45
|
-
input: (callback) => eventWrapper(element, 'input', callback),
|
|
46
|
-
change: (callback) => eventWrapper(element, 'change', callback),
|
|
47
|
-
keyup: (callback) => eventWrapper(element, 'keyup', callback),
|
|
48
|
-
keydown: (callback) => eventWrapper(element, 'keydown', callback),
|
|
49
|
-
beforeInput: (callback) => eventWrapper(element, 'beforeinput', callback),
|
|
50
|
-
mouseOver: (callback) => eventWrapper(element, 'mouseover', callback),
|
|
51
|
-
mouseOut: (callback) => eventWrapper(element, 'mouseout', callback),
|
|
52
|
-
mouseDown: (callback) => eventWrapper(element, 'mousedown', callback),
|
|
53
|
-
mouseUp: (callback) => eventWrapper(element, 'mouseup', callback),
|
|
54
|
-
mouseMove: (callback) => eventWrapper(element, 'mousemove', callback),
|
|
55
|
-
hover: (mouseInCallback, mouseOutCallback) => {
|
|
56
|
-
element.addEventListener('mouseover', mouseInCallback);
|
|
57
|
-
element.addEventListener('mouseout', mouseOutCallback);
|
|
58
|
-
},
|
|
59
|
-
dropped: (callback) => eventWrapper(element, 'drop', callback),
|
|
60
|
-
submit: (callback) => eventWrapper(element, 'submit', callback),
|
|
61
|
-
dragEnd: (callback) => eventWrapper(element, 'dragend', callback),
|
|
62
|
-
dragStart: (callback) => eventWrapper(element, 'dragstart', callback),
|
|
63
|
-
drop: (callback) => eventWrapper(element, 'drop', callback),
|
|
64
|
-
dragOver: (callback) => eventWrapper(element, 'dragover', callback),
|
|
65
|
-
dragEnter: (callback) => eventWrapper(element, 'dragenter', callback),
|
|
66
|
-
dragLeave: (callback) => eventWrapper(element, 'dragleave', callback),
|
|
67
|
-
};
|
|
68
|
-
for(let event in events) {
|
|
69
|
-
element.nd.on[event] = events[event];
|
|
70
|
-
element.nd.on.prevent[event] = function(callback) {
|
|
71
|
-
eventWrapper(element, event.toLowerCase(), (event) => {
|
|
72
|
-
event.preventDefault();
|
|
73
|
-
callback && callback(event);
|
|
74
|
-
});
|
|
75
|
-
return element;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return element;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Build configuration
|
|
83
|
-
const isProd = process.env.NODE_ENV === 'production';
|
|
84
|
-
|
|
85
|
-
const DebugManager = {
|
|
86
|
-
enabled: !isProd,
|
|
87
|
-
|
|
88
|
-
enable() {
|
|
89
|
-
this.enabled = true;
|
|
90
|
-
console.log('๐ NativeDocument Debug Mode enabled');
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
disable() {
|
|
94
|
-
this.enabled = false;
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
log: isProd ? () => {} : function(category, message, data) {
|
|
98
|
-
if (!this.enabled) return;
|
|
99
|
-
console.group(`๐ [${category}] ${message}`);
|
|
100
|
-
if (data) console.log(data);
|
|
101
|
-
console.trace();
|
|
102
|
-
console.groupEnd();
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
warn: isProd ? () => {} : function(category, message, data) {
|
|
106
|
-
if (!this.enabled) return;
|
|
107
|
-
console.warn(`โ ๏ธ [${category}] ${message}`, data);
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
error: isProd ? () => {} : function(category, message, error) {
|
|
111
|
-
console.error(`โ [${category}] ${message}`, error);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const MemoryManager = (function() {
|
|
116
|
-
|
|
117
|
-
let $nexObserverId = 0;
|
|
118
|
-
const $observables = new Map();
|
|
119
|
-
const $registry = new FinalizationRegistry((heldValue) => {
|
|
120
|
-
DebugManager.log('MemoryManager', '๐งน Auto-cleanup observable:', heldValue);
|
|
121
|
-
heldValue.listeners.splice(0);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
/**
|
|
126
|
-
* Register an observable and return an id.
|
|
127
|
-
*
|
|
128
|
-
* @param {ObservableItem} observable
|
|
129
|
-
* @param {Function[]} listeners
|
|
130
|
-
* @returns {number}
|
|
131
|
-
*/
|
|
132
|
-
register(observable, listeners) {
|
|
133
|
-
const id = ++$nexObserverId;
|
|
134
|
-
const heldValue = {
|
|
135
|
-
id: id,
|
|
136
|
-
listeners
|
|
137
|
-
};
|
|
138
|
-
$registry.register(observable, heldValue);
|
|
139
|
-
$observables.set(id, new WeakRef(observable));
|
|
140
|
-
return id;
|
|
141
|
-
},
|
|
142
|
-
cleanup() {
|
|
143
|
-
for (const [_, weakObservableRef] of $observables) {
|
|
144
|
-
const observable = weakObservableRef.deref();
|
|
145
|
-
if (observable) {
|
|
146
|
-
observable.cleanup();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
$observables.clear();
|
|
150
|
-
},
|
|
151
|
-
/**
|
|
152
|
-
* Clean observables that are not referenced anymore.
|
|
153
|
-
* @param {number} threshold
|
|
154
|
-
*/
|
|
155
|
-
cleanObservables(threshold) {
|
|
156
|
-
if($observables.size < threshold) return;
|
|
157
|
-
let cleanedCount = 0;
|
|
158
|
-
for (const [id, weakObservableRef] of $observables) {
|
|
159
|
-
if (!weakObservableRef.deref()) {
|
|
160
|
-
$observables.delete(id);
|
|
161
|
-
cleanedCount++;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (cleanedCount > 0) {
|
|
165
|
-
DebugManager.log('Memory Auto Clean', `๐งน Cleaned ${cleanedCount} orphaned observables`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}());
|
|
170
|
-
|
|
171
|
-
class NativeDocumentError extends Error {
|
|
172
|
-
constructor(message, context = {}) {
|
|
173
|
-
super(message);
|
|
174
|
-
this.name = 'NativeDocumentError';
|
|
175
|
-
this.context = context;
|
|
176
|
-
this.timestamp = new Date().toISOString();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
*
|
|
182
|
-
* @param {ObservableItem} $observable
|
|
183
|
-
* @param {Function} $checker
|
|
184
|
-
* @class ObservableChecker
|
|
185
|
-
*/
|
|
186
|
-
function ObservableChecker($observable, $checker) {
|
|
187
|
-
this.observable = $observable;
|
|
188
|
-
this.checker = $checker;
|
|
189
|
-
|
|
190
|
-
this.subscribe = function(callback) {
|
|
191
|
-
return $observable.subscribe((value) => {
|
|
192
|
-
callback && callback($checker(value));
|
|
193
|
-
});
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
this.val = function() {
|
|
197
|
-
return $checker && $checker($observable.val());
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
this.cleanup = function() {
|
|
201
|
-
return $observable.cleanup();
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
*
|
|
207
|
-
* @param {*} value
|
|
208
|
-
* @class ObservableItem
|
|
209
|
-
*/
|
|
210
|
-
function ObservableItem(value) {
|
|
211
|
-
if (value === undefined) {
|
|
212
|
-
throw new NativeDocumentError('ObservableItem requires an initial value');
|
|
213
|
-
}
|
|
214
|
-
if(value instanceof ObservableItem) {
|
|
215
|
-
throw new NativeDocumentError('ObservableItem cannot be an Observable');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const $initialValue = (typeof value === 'object') ? JSON.parse(JSON.stringify(value)) : value;
|
|
219
|
-
|
|
220
|
-
let $previousValue = value;
|
|
221
|
-
let $currentValue = value;
|
|
222
|
-
let $isCleanedUp = false;
|
|
223
|
-
|
|
224
|
-
const $listeners = [];
|
|
225
|
-
|
|
226
|
-
MemoryManager.register(this, $listeners);
|
|
227
|
-
|
|
228
|
-
this.trigger = () => {
|
|
229
|
-
$listeners.forEach(listener => {
|
|
230
|
-
try {
|
|
231
|
-
listener($currentValue, $previousValue);
|
|
232
|
-
} catch (error) {
|
|
233
|
-
DebugManager.error('Listener Undefined', 'Error in observable listener:', error);
|
|
234
|
-
this.unsubscribe(listener);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
this.originalValue = () => $initialValue;
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* @param {*} data
|
|
244
|
-
*/
|
|
245
|
-
this.set = (data) => {
|
|
246
|
-
const newValue = (typeof data === 'function') ? data($currentValue) : data;
|
|
247
|
-
if($currentValue === newValue) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
$previousValue = $currentValue;
|
|
251
|
-
$currentValue = newValue;
|
|
252
|
-
this.trigger();
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
this.val = () => $currentValue;
|
|
256
|
-
|
|
257
|
-
this.cleanup = function() {
|
|
258
|
-
$listeners.splice(0);
|
|
259
|
-
$isCleanedUp = true;
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
*
|
|
264
|
-
* @param {Function} callback
|
|
265
|
-
* @returns {(function(): void)}
|
|
266
|
-
*/
|
|
267
|
-
this.subscribe = (callback) => {
|
|
268
|
-
if ($isCleanedUp) {
|
|
269
|
-
DebugManager.warn('Observable subscription', 'โ ๏ธ Attempted to subscribe to a cleaned up observable.');
|
|
270
|
-
return () => {};
|
|
271
|
-
}
|
|
272
|
-
if (typeof callback !== 'function') {
|
|
273
|
-
throw new NativeDocumentError('Callback must be a function');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
$listeners.push(callback);
|
|
277
|
-
return () => this.unsubscribe(callback);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Unsubscribe from an observable.
|
|
282
|
-
* @param {Function} callback
|
|
283
|
-
*/
|
|
284
|
-
this.unsubscribe = (callback) => {
|
|
285
|
-
const index = $listeners.indexOf(callback);
|
|
286
|
-
if (index > -1) {
|
|
287
|
-
$listeners.splice(index, 1);
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Create an Observable checker instance
|
|
293
|
-
* @param callback
|
|
294
|
-
* @returns {ObservableChecker}
|
|
295
|
-
*/
|
|
296
|
-
this.check = function(callback) {
|
|
297
|
-
return new ObservableChecker(this, callback)
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const Validator = {
|
|
303
|
-
isObservable(value) {
|
|
304
|
-
return value instanceof ObservableItem || value instanceof ObservableChecker;
|
|
305
|
-
},
|
|
306
|
-
isProxy(value) {
|
|
307
|
-
return value?.__isProxy__
|
|
308
|
-
},
|
|
309
|
-
isObservableChecker(value) {
|
|
310
|
-
return value instanceof ObservableChecker;
|
|
311
|
-
},
|
|
312
|
-
isArray(value) {
|
|
313
|
-
return Array.isArray(value);
|
|
314
|
-
},
|
|
315
|
-
isString(value) {
|
|
316
|
-
return typeof value === 'string';
|
|
317
|
-
},
|
|
318
|
-
isNumber(value) {
|
|
319
|
-
return typeof value === 'number';
|
|
320
|
-
},
|
|
321
|
-
isBoolean(value) {
|
|
322
|
-
return typeof value === 'boolean';
|
|
323
|
-
},
|
|
324
|
-
isFunction(value) {
|
|
325
|
-
return typeof value === 'function';
|
|
326
|
-
},
|
|
327
|
-
isObject(value) {
|
|
328
|
-
return typeof value === 'object';
|
|
329
|
-
},
|
|
330
|
-
isJson(value) {
|
|
331
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
332
|
-
},
|
|
333
|
-
isElement(value) {
|
|
334
|
-
return value instanceof HTMLElement || value instanceof DocumentFragment || value instanceof Text;
|
|
335
|
-
},
|
|
336
|
-
isFragment(value) {
|
|
337
|
-
return value instanceof DocumentFragment;
|
|
338
|
-
},
|
|
339
|
-
isStringOrObservable(value) {
|
|
340
|
-
return this.isString(value) || this.isObservable(value);
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
isValidChild(child) {
|
|
344
|
-
return child === null ||
|
|
345
|
-
this.isElement(child) ||
|
|
346
|
-
this.isObservable(child) ||
|
|
347
|
-
['string', 'number', 'boolean'].includes(typeof child);
|
|
348
|
-
},
|
|
349
|
-
isValidChildren(children) {
|
|
350
|
-
if (!Array.isArray(children)) {
|
|
351
|
-
children = [children];
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const invalid = children.filter(child => !this.isValidChild(child));
|
|
355
|
-
return invalid.length === 0;
|
|
356
|
-
},
|
|
357
|
-
validateChildren(children) {
|
|
358
|
-
if (!Array.isArray(children)) {
|
|
359
|
-
children = [children];
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const invalid = children.filter(child => !this.isValidChild(child));
|
|
363
|
-
if (invalid.length > 0) {
|
|
364
|
-
throw new NativeDocumentError(`Invalid children detected: ${invalid.map(i => typeof i).join(', ')}`);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return children;
|
|
368
|
-
},
|
|
369
|
-
|
|
370
|
-
validateAttributes(attributes) {
|
|
371
|
-
if (!attributes || typeof attributes !== 'object') {
|
|
372
|
-
return attributes;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const reserved = [];
|
|
376
|
-
const foundReserved = Object.keys(attributes).filter(key => reserved.includes(key));
|
|
377
|
-
|
|
378
|
-
if (foundReserved.length > 0) {
|
|
379
|
-
DebugManager.warn('Validator', `Reserved attributes found: ${foundReserved.join(', ')}`);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return attributes;
|
|
383
|
-
},
|
|
384
|
-
|
|
385
|
-
validateEventCallback(callback) {
|
|
386
|
-
if (typeof callback !== 'function') {
|
|
387
|
-
throw new NativeDocumentError('Event callback must be a function');
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
*
|
|
394
|
-
* @param {HTMLElement} element
|
|
395
|
-
* @param {string} className
|
|
396
|
-
* @param {string} value
|
|
397
|
-
*/
|
|
398
|
-
const toggleClassItem = function(element, className, value) {
|
|
399
|
-
if(value) {
|
|
400
|
-
element.classList.add(className);
|
|
401
|
-
} else {
|
|
402
|
-
element.classList.remove(className);
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
*
|
|
408
|
-
* @param {HTMLElement} element
|
|
409
|
-
* @param {Object} data
|
|
410
|
-
*/
|
|
411
|
-
function bindClassAttribute(element, data) {
|
|
412
|
-
for(let className in data) {
|
|
413
|
-
const value = data[className];
|
|
414
|
-
if(Validator.isObservable(value)) {
|
|
415
|
-
toggleClassItem(element, className, value.val());
|
|
416
|
-
value.subscribe(newValue => toggleClassItem(element, className, newValue));
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
toggleClassItem(element, className, value);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
*
|
|
425
|
-
* @param {HTMLElement} element
|
|
426
|
-
* @param {Object} data
|
|
427
|
-
*/
|
|
428
|
-
function bindStyleAttribute(element, data) {
|
|
429
|
-
for(let styleName in data) {
|
|
430
|
-
const value = data[styleName];
|
|
431
|
-
if(Validator.isObservable(value)) {
|
|
432
|
-
element.style[styleName] = value.val();
|
|
433
|
-
value.subscribe(newValue => {
|
|
434
|
-
element.style[styleName] = newValue;
|
|
435
|
-
});
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
element.style[styleName] = value;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
*
|
|
444
|
-
* @param {HTMLElement} element
|
|
445
|
-
* @param {Object} attributes
|
|
446
|
-
*/
|
|
447
|
-
function AttributesWrapper(element, attributes) {
|
|
448
|
-
|
|
449
|
-
Validator.validateAttributes(attributes);
|
|
450
|
-
|
|
451
|
-
if(!Validator.isObject(attributes)) {
|
|
452
|
-
console.log(attributes);
|
|
453
|
-
throw new NativeDocumentError('Attributes must be an object');
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
for(let attributeName in attributes) {
|
|
457
|
-
const value = attributes[attributeName];
|
|
458
|
-
if(Validator.isObservable(value)) {
|
|
459
|
-
value.subscribe(newValue => element.setAttribute(attributeName, newValue));
|
|
460
|
-
element.setAttribute(attributeName, value.val());
|
|
461
|
-
if(attributeName === 'value') {
|
|
462
|
-
if(['checkbox', 'radio'].includes(element.type)) {
|
|
463
|
-
element.addEventListener('input', () => value.set(element.checked));
|
|
464
|
-
} else {
|
|
465
|
-
element.addEventListener('input', () => value.set(element.value));
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
if(attributeName === 'class' && Validator.isJson(value)) {
|
|
471
|
-
bindClassAttribute(element, value);
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
if(attributeName === 'style' && Validator.isJson(value)) {
|
|
475
|
-
bindStyleAttribute(element, value);
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
element.setAttribute(attributeName, value);
|
|
479
|
-
}
|
|
480
|
-
return element;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
*
|
|
485
|
-
* @param {Function} fn
|
|
486
|
-
* @param {number} delay
|
|
487
|
-
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
|
|
488
|
-
* @returns {(function(...[*]): void)|*}
|
|
489
|
-
*/
|
|
490
|
-
const throttle = function(fn, delay, options = {}) {
|
|
491
|
-
let timer = null;
|
|
492
|
-
let lastExecTime = 0;
|
|
493
|
-
const { leading = true, trailing = true, debounce = false } = options;
|
|
494
|
-
|
|
495
|
-
return function(...args) {
|
|
496
|
-
const now = Date.now();
|
|
497
|
-
if (debounce) {
|
|
498
|
-
// debounce mode: reset the timer for each call
|
|
499
|
-
clearTimeout(timer);
|
|
500
|
-
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
if (leading && now - lastExecTime >= delay) {
|
|
504
|
-
fn.apply(this, args);
|
|
505
|
-
lastExecTime = now;
|
|
506
|
-
}
|
|
507
|
-
if (trailing && !timer) {
|
|
508
|
-
timer = setTimeout(() => {
|
|
509
|
-
fn.apply(this, args);
|
|
510
|
-
lastExecTime = Date.now();
|
|
511
|
-
timer = null;
|
|
512
|
-
}, delay - (now - lastExecTime));
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
const trim = function(str, char) {
|
|
518
|
-
return str.replace(new RegExp(`^[${char}]+|[${char}]+$`, 'g'), '');
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
const DocumentObserver = {
|
|
522
|
-
elements: new Map(),
|
|
523
|
-
observer: null,
|
|
524
|
-
checkMutation: throttle(function() {
|
|
525
|
-
for(const [element, data] of DocumentObserver.elements.entries()) {
|
|
526
|
-
const isCurrentlyInDom = document.body.contains(element);
|
|
527
|
-
if(isCurrentlyInDom && !data.inDom) {
|
|
528
|
-
data.inDom = true;
|
|
529
|
-
data.mounted.forEach(callback => callback(element));
|
|
530
|
-
} else if(!isCurrentlyInDom && data.inDom) {
|
|
531
|
-
data.inDom = false;
|
|
532
|
-
data.unmounted.forEach(callback => callback(element));
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}, 10, { debounce: true }),
|
|
536
|
-
/**
|
|
537
|
-
*
|
|
538
|
-
* @param {HTMLElement} element
|
|
539
|
-
* @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
|
|
540
|
-
*/
|
|
541
|
-
watch: function(element) {
|
|
542
|
-
let data = {};
|
|
543
|
-
if(DocumentObserver.elements.has(element)) {
|
|
544
|
-
data = DocumentObserver.elements.get(element);
|
|
545
|
-
} else {
|
|
546
|
-
const inDom = document.body.contains(element);
|
|
547
|
-
data = {
|
|
548
|
-
inDom,
|
|
549
|
-
mounted: new Set(),
|
|
550
|
-
unmounted: new Set(),
|
|
551
|
-
};
|
|
552
|
-
DocumentObserver.elements.set(element, data);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return {
|
|
556
|
-
watch: () => DocumentObserver.elements.set(element, data),
|
|
557
|
-
disconnect: () => DocumentObserver.elements.delete(element),
|
|
558
|
-
mounted: (callback) => data.mounted.add(callback),
|
|
559
|
-
unmounted: (callback) => data.unmounted.add(callback)
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
|
|
565
|
-
DocumentObserver.observer.observe(document.body, {
|
|
566
|
-
childList: true,
|
|
567
|
-
subtree: true,
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
*
|
|
572
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
573
|
-
* @param {ObservableItem} observable
|
|
574
|
-
* @returns {Text}
|
|
575
|
-
*/
|
|
576
|
-
const createObservableNode = function(parent, observable) {
|
|
577
|
-
const text = document.createTextNode('');
|
|
578
|
-
observable.subscribe(value => text.textContent = String(value));
|
|
579
|
-
text.textContent = observable.val();
|
|
580
|
-
parent && parent.appendChild(text);
|
|
581
|
-
return text;
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
*
|
|
586
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
587
|
-
* @param {*} value
|
|
588
|
-
* @returns {Text}
|
|
589
|
-
*/
|
|
590
|
-
const createStaticTextNode = function(parent, value) {
|
|
591
|
-
const text = document.createTextNode('');
|
|
592
|
-
text.textContent = String(value);
|
|
593
|
-
parent && parent.appendChild(text);
|
|
594
|
-
return text;
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
*
|
|
599
|
-
* @param {HTMLElement} element
|
|
600
|
-
*/
|
|
601
|
-
const addUtilsMethods = function(element) {
|
|
602
|
-
element.nd.wrap = (callback) => {
|
|
603
|
-
if(!Validator.isFunction(callback)) {
|
|
604
|
-
throw new NativeDocumentError('Callback must be a function');
|
|
605
|
-
}
|
|
606
|
-
callback && callback(element);
|
|
607
|
-
return element;
|
|
608
|
-
};
|
|
609
|
-
element.nd.ref = (target, name) => {
|
|
610
|
-
target[name] = element;
|
|
611
|
-
return element;
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
let $observer = null;
|
|
615
|
-
|
|
616
|
-
element.nd.lifecycle = function(states) {
|
|
617
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
618
|
-
|
|
619
|
-
states.mounted && $observer.mounted(states.mounted);
|
|
620
|
-
states.unmounted && $observer.unmounted(states.unmounted);
|
|
621
|
-
return element;
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
element.nd.mounted = (callback) => {
|
|
625
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
626
|
-
$observer.mounted(callback);
|
|
627
|
-
return element;
|
|
628
|
-
};
|
|
629
|
-
element.nd.unmounted = (callback) => {
|
|
630
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
631
|
-
$observer.unmounted(callback);
|
|
632
|
-
return element;
|
|
633
|
-
};
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
*
|
|
638
|
-
* @param {*} value
|
|
639
|
-
* @returns {Text}
|
|
640
|
-
*/
|
|
641
|
-
const createTextNode = function(value) {
|
|
642
|
-
return (Validator.isObservable(value))
|
|
643
|
-
? createObservableNode(null, value)
|
|
644
|
-
: createStaticTextNode(null, value);
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
const ElementCreator = {
|
|
648
|
-
/**
|
|
649
|
-
*
|
|
650
|
-
* @param {string} name
|
|
651
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
652
|
-
*/
|
|
653
|
-
createElement(name) {
|
|
654
|
-
return name ? document.createElement(name) : document.createDocumentFragment();
|
|
655
|
-
},
|
|
656
|
-
/**
|
|
657
|
-
*
|
|
658
|
-
* @param {*} children
|
|
659
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
660
|
-
*/
|
|
661
|
-
processChildren(children, parent) {
|
|
662
|
-
if(children === null) return;
|
|
663
|
-
const childrenArray = Array.isArray(children) ? children : [children];
|
|
664
|
-
childrenArray.forEach(child => {
|
|
665
|
-
if (child === null) return;
|
|
666
|
-
if (Validator.isElement(child)) {
|
|
667
|
-
parent.appendChild(child);
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
if (Validator.isObservable(child)) {
|
|
671
|
-
createObservableNode(parent, child);
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
if (child) {
|
|
675
|
-
createStaticTextNode(parent, child);
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
},
|
|
679
|
-
/**
|
|
680
|
-
*
|
|
681
|
-
* @param {HTMLElement} element
|
|
682
|
-
* @param {Object} attributes
|
|
683
|
-
*/
|
|
684
|
-
processAttributes(element, attributes) {
|
|
685
|
-
if(Validator.isFragment(element)) return;
|
|
686
|
-
if (attributes) {
|
|
687
|
-
AttributesWrapper(element, attributes);
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
/**
|
|
691
|
-
*
|
|
692
|
-
* @param {HTMLElement} element
|
|
693
|
-
* @param {Object} attributes
|
|
694
|
-
* @param {?Function} customWrapper
|
|
695
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
696
|
-
*/
|
|
697
|
-
setup(element, attributes, customWrapper) {
|
|
698
|
-
element.nd = {};
|
|
699
|
-
HtmlElementEventsWrapper(element);
|
|
700
|
-
const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
701
|
-
addUtilsMethods(item);
|
|
702
|
-
return item;
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
/**
|
|
707
|
-
*
|
|
708
|
-
* @param {string} name
|
|
709
|
-
* @param {?Function} customWrapper
|
|
710
|
-
* @returns {Function}
|
|
711
|
-
*/
|
|
712
|
-
function HtmlElementWrapper(name, customWrapper) {
|
|
713
|
-
const $tagName = name.toLowerCase().trim();
|
|
714
|
-
|
|
715
|
-
const builder = function(attributes, children = null) {
|
|
716
|
-
try {
|
|
717
|
-
if(Validator.isValidChildren(attributes)) {
|
|
718
|
-
const tempChildren = children;
|
|
719
|
-
children = attributes;
|
|
720
|
-
attributes = tempChildren;
|
|
721
|
-
}
|
|
722
|
-
const element = ElementCreator.createElement($tagName);
|
|
723
|
-
|
|
724
|
-
ElementCreator.processAttributes(element, attributes);
|
|
725
|
-
ElementCreator.processChildren(children, element);
|
|
726
|
-
|
|
727
|
-
return ElementCreator.setup(element, attributes, customWrapper);
|
|
728
|
-
} catch (error) {
|
|
729
|
-
DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
builder.hold = (children, attributes) => (() => builder(children, attributes));
|
|
734
|
-
|
|
735
|
-
return builder;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
class ArgTypesError extends Error {
|
|
739
|
-
constructor(message, errors) {
|
|
740
|
-
super(`${message}\n\n${errors.join("\n")}\n\n`);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
*
|
|
746
|
-
* @type {{string: (function(*): {name: *, type: string, validate: function(*): boolean}),
|
|
747
|
-
* number: (function(*): {name: *, type: string, validate: function(*): boolean}),
|
|
748
|
-
* boolean: (function(*): {name: *, type: string, validate: function(*): boolean}),
|
|
749
|
-
* observable: (function(*): {name: *, type: string, validate: function(*): boolean}),
|
|
750
|
-
* element: (function(*): {name: *, type: string, validate: function(*): *}),
|
|
751
|
-
* function: (function(*): {name: *, type: string, validate: function(*): boolean}),
|
|
752
|
-
* object: (function(*): {name: *, type: string, validate: function(*): boolean}),
|
|
753
|
-
* objectNotNull: (function(*): {name: *, type: string, validate: function(*): *}),
|
|
754
|
-
* children: (function(*): {name: *, type: string, validate: function(*): *}),
|
|
755
|
-
* attributes: (function(*): {name: *, type: string, validate: function(*): *}),
|
|
756
|
-
* optional: (function(*): *&{optional: boolean}),
|
|
757
|
-
* oneOf: (function(*, ...[*]): {name: *, type: string, types: *[],
|
|
758
|
-
* validate: function(*): boolean})
|
|
759
|
-
* }}
|
|
760
|
-
*/
|
|
761
|
-
const ArgTypes = {
|
|
762
|
-
string: (name) => ({ name, type: 'string', validate: (v) => Validator.isString(v) }),
|
|
763
|
-
number: (name) => ({ name, type: 'number', validate: (v) => Validator.isNumber(v) }),
|
|
764
|
-
boolean: (name) => ({ name, type: 'boolean', validate: (v) => Validator.isBoolean(v) }),
|
|
765
|
-
observable: (name) => ({ name, type: 'observable', validate: (v) => Validator.isObservable(v) }),
|
|
766
|
-
element: (name) => ({ name, type: 'element', validate: (v) => Validator.isElement(v) }),
|
|
767
|
-
function: (name) => ({ name, type: 'function', validate: (v) => Validator.isFunction(v) }),
|
|
768
|
-
object: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v)) }),
|
|
769
|
-
objectNotNull: (name) => ({ name, type: 'object', validate: (v) => (Validator.isObject(v) && v !== null) }),
|
|
770
|
-
children: (name) => ({ name, type: 'children', validate: (v) => Validator.validateChildren(v) }),
|
|
771
|
-
attributes: (name) => ({ name, type: 'attributes', validate: (v) => Validator.validateAttributes(v) }),
|
|
772
|
-
|
|
773
|
-
// Optional arguments
|
|
774
|
-
optional: (argType) => ({ ...argType, optional: true }),
|
|
775
|
-
|
|
776
|
-
// Union types
|
|
777
|
-
oneOf: (name, ...argTypes) => ({
|
|
778
|
-
name,
|
|
779
|
-
type: 'oneOf',
|
|
780
|
-
types: argTypes,
|
|
781
|
-
validate: (v) => argTypes.some(type => type.validate(v))
|
|
782
|
-
})
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
*
|
|
787
|
-
* @param {Array} args
|
|
788
|
-
* @param {Array} argSchema
|
|
789
|
-
* @param {string} fnName
|
|
790
|
-
*/
|
|
791
|
-
const validateArgs = (args, argSchema, fnName = 'Function') => {
|
|
792
|
-
if (!argSchema) return;
|
|
793
|
-
|
|
794
|
-
const errors = [];
|
|
795
|
-
|
|
796
|
-
// Check the number of arguments
|
|
797
|
-
const requiredCount = argSchema.filter(arg => !arg.optional).length;
|
|
798
|
-
if (args.length < requiredCount) {
|
|
799
|
-
errors.push(`${fnName}: Expected at least ${requiredCount} arguments, got ${args.length}`);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// Validate each argument
|
|
803
|
-
argSchema.forEach((schema, index) => {
|
|
804
|
-
const position = index + 1;
|
|
805
|
-
const value = args[index];
|
|
806
|
-
|
|
807
|
-
if (value === undefined) {
|
|
808
|
-
if (!schema.optional) {
|
|
809
|
-
errors.push(`${fnName}: Missing required argument '${schema.name}' at position ${position}`);
|
|
810
|
-
}
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
if (!schema.validate(value)) {
|
|
815
|
-
const valueTypeOf = value?.constructor?.name || typeof value;
|
|
816
|
-
errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
if (errors.length > 0) {
|
|
821
|
-
throw new ArgTypesError(`Argument validation failed`, errors);
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* @param {Function} fn
|
|
827
|
-
* @param {Array} argSchema
|
|
828
|
-
* @param {string} fnName
|
|
829
|
-
* @returns {Function}
|
|
830
|
-
*/
|
|
831
|
-
const withValidation = (fn, argSchema, fnName = 'Function') => {
|
|
832
|
-
if(!Validator.isArray(argSchema)) {
|
|
833
|
-
throw new NativeDocumentError('withValidation : argSchema must be an array');
|
|
834
|
-
}
|
|
835
|
-
return function(...args) {
|
|
836
|
-
validateArgs(args, argSchema, fn.name || fnName);
|
|
837
|
-
return fn.apply(this, args);
|
|
838
|
-
};
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
Function.prototype.args = function(...args) {
|
|
842
|
-
return withValidation(this, args);
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
Function.prototype.errorBoundary = function(callback) {
|
|
846
|
-
return (...args) => {
|
|
847
|
-
try {
|
|
848
|
-
return this.apply(this, args);
|
|
849
|
-
} catch(e) {
|
|
850
|
-
return callback(e);
|
|
851
|
-
}
|
|
852
|
-
};
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
*
|
|
857
|
-
* @param {*} value
|
|
858
|
-
* @returns {ObservableItem}
|
|
859
|
-
* @constructor
|
|
860
|
-
*/
|
|
861
|
-
function Observable(value) {
|
|
862
|
-
return new ObservableItem(value);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
Observable.computed = function(callback, dependencies = []) {
|
|
867
|
-
const initialValue = callback();
|
|
868
|
-
const observable = new ObservableItem(initialValue);
|
|
869
|
-
|
|
870
|
-
const updatedValue = throttle(() => observable.set(callback()), 10, { debounce: true });
|
|
871
|
-
|
|
872
|
-
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
873
|
-
|
|
874
|
-
return observable;
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
*
|
|
879
|
-
* @param {ObservableItem} observable
|
|
880
|
-
*/
|
|
881
|
-
Observable.cleanup = function(observable) {
|
|
882
|
-
observable.cleanup();
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
* Get the value of an observable or an object of observables.
|
|
887
|
-
* @param {ObservableItem|Object<ObservableItem>} object
|
|
888
|
-
* @returns {{}|*|null}
|
|
889
|
-
*/
|
|
890
|
-
Observable.value = function(data) {
|
|
891
|
-
if(Validator.isObservable(data)) {
|
|
892
|
-
return data.val();
|
|
893
|
-
}
|
|
894
|
-
if(Validator.isProxy(data)) {
|
|
895
|
-
return data.$val();
|
|
896
|
-
}
|
|
897
|
-
if(Validator.isArray(data)) {
|
|
898
|
-
const result = [];
|
|
899
|
-
data.forEach(item => {
|
|
900
|
-
result.push(Observable.value(item));
|
|
901
|
-
});
|
|
902
|
-
return result;
|
|
903
|
-
}
|
|
904
|
-
return data;
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
*
|
|
909
|
-
* @param {Object} value
|
|
910
|
-
* @returns {Proxy}
|
|
911
|
-
*/
|
|
912
|
-
Observable.init = function(value) {
|
|
913
|
-
const data = {};
|
|
914
|
-
for(const key in value) {
|
|
915
|
-
const itemValue = value[key];
|
|
916
|
-
if(Validator.isJson(itemValue)) {
|
|
917
|
-
console.log(itemValue);
|
|
918
|
-
data[key] = Observable.init(itemValue);
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
data[key] = Observable(itemValue);
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const $val = function() {
|
|
925
|
-
const result = {};
|
|
926
|
-
for(const key in data) {
|
|
927
|
-
const dataItem = data[key];
|
|
928
|
-
if(Validator.isObservable(dataItem)) {
|
|
929
|
-
result[key] = dataItem.val();
|
|
930
|
-
} else if(Validator.isProxy(dataItem)) {
|
|
931
|
-
result[key] = dataItem.$val();
|
|
932
|
-
} else {
|
|
933
|
-
result[key] = dataItem;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
return result;
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
return new Proxy(data, {
|
|
940
|
-
get(target, property) {
|
|
941
|
-
if(property === '__isProxy__') {
|
|
942
|
-
return true;
|
|
943
|
-
}
|
|
944
|
-
if(property === '$val') {
|
|
945
|
-
return $val;
|
|
946
|
-
}
|
|
947
|
-
if(target[property] !== undefined) {
|
|
948
|
-
return target[property];
|
|
949
|
-
}
|
|
950
|
-
return undefined;
|
|
951
|
-
},
|
|
952
|
-
set(target, prop, newValue) {
|
|
953
|
-
if(target[prop] !== undefined) {
|
|
954
|
-
target[prop].set(newValue);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
})
|
|
958
|
-
};
|
|
959
|
-
|
|
960
|
-
Observable.object = Observable.init;
|
|
961
|
-
/**
|
|
962
|
-
*
|
|
963
|
-
* @param {Array} target
|
|
964
|
-
* @returns {ObservableItem}
|
|
965
|
-
*/
|
|
966
|
-
Observable.array = function(target) {
|
|
967
|
-
if(!Array.isArray(target)) {
|
|
968
|
-
throw new NativeDocumentError('Observable.array : target must be an array');
|
|
969
|
-
}
|
|
970
|
-
const observer = Observable(target);
|
|
971
|
-
|
|
972
|
-
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
|
|
973
|
-
|
|
974
|
-
methods.forEach((method) => {
|
|
975
|
-
observer[method] = function(...values) {
|
|
976
|
-
const target = observer.val();
|
|
977
|
-
const result = target[method].apply(target, arguments);
|
|
978
|
-
observer.trigger();
|
|
979
|
-
return result;
|
|
980
|
-
};
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
|
|
984
|
-
overrideMethods.forEach((method) => {
|
|
985
|
-
observer[method] = function(callback) {
|
|
986
|
-
return observer.val()[method](callback);
|
|
987
|
-
};
|
|
988
|
-
});
|
|
989
|
-
|
|
990
|
-
return observer;
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Enable auto cleanup of observables.
|
|
995
|
-
* @param {Boolean} enable
|
|
996
|
-
* @param {{interval:Boolean, threshold:number}} options
|
|
997
|
-
*/
|
|
998
|
-
Observable.autoCleanup = function(enable = false, options = {}) {
|
|
999
|
-
if(!enable) {
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
const { interval = 60000, threshold = 100 } = options;
|
|
1003
|
-
|
|
1004
|
-
window.addEventListener('beforeunload', () => {
|
|
1005
|
-
MemoryManager.cleanup();
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
setInterval(() => MemoryManager.cleanObservables(threshold), interval);
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
const Store = (function() {
|
|
1012
|
-
|
|
1013
|
-
const $stores = new Map();
|
|
1014
|
-
|
|
1015
|
-
return {
|
|
1016
|
-
/**
|
|
1017
|
-
* Create a new state follower and return it.
|
|
1018
|
-
* @param {string} name
|
|
1019
|
-
* @returns {ObservableItem}
|
|
1020
|
-
*/
|
|
1021
|
-
use(name) {
|
|
1022
|
-
const {observer: originalObserver, followers } = $stores.get(name);
|
|
1023
|
-
const observerFollower = Observable(originalObserver.val());
|
|
1024
|
-
const unSubscriber = originalObserver.subscribe(value => observerFollower.set(value));
|
|
1025
|
-
const updaterUnsubscriber = observerFollower.subscribe(value => originalObserver.set(value));
|
|
1026
|
-
observerFollower.destroy = () => {
|
|
1027
|
-
unSubscriber();
|
|
1028
|
-
updaterUnsubscriber();
|
|
1029
|
-
observerFollower.cleanup();
|
|
1030
|
-
};
|
|
1031
|
-
followers.add(observerFollower);
|
|
1032
|
-
|
|
1033
|
-
return observerFollower;
|
|
1034
|
-
},
|
|
1035
|
-
/**
|
|
1036
|
-
* @param {string} name
|
|
1037
|
-
* @returns {ObservableItem}
|
|
1038
|
-
*/
|
|
1039
|
-
follow(name) {
|
|
1040
|
-
return this.use(name);
|
|
1041
|
-
},
|
|
1042
|
-
/**
|
|
1043
|
-
* Create a new state and return the observer.
|
|
1044
|
-
* @param {string} name
|
|
1045
|
-
* @param {*} value
|
|
1046
|
-
* @returns {ObservableItem}
|
|
1047
|
-
*/
|
|
1048
|
-
create(name, value) {
|
|
1049
|
-
const observer = Observable(value);
|
|
1050
|
-
$stores.set(name, { observer, followers: new Set()});
|
|
1051
|
-
return observer;
|
|
1052
|
-
},
|
|
1053
|
-
/**
|
|
1054
|
-
* Get the observer for a state.
|
|
1055
|
-
* @param {string} name
|
|
1056
|
-
* @returns {null|ObservableItem}
|
|
1057
|
-
*/
|
|
1058
|
-
get(name) {
|
|
1059
|
-
const item = $stores.get(name);
|
|
1060
|
-
return item ? item.observer : null;
|
|
1061
|
-
},
|
|
1062
|
-
/**
|
|
1063
|
-
*
|
|
1064
|
-
* @param {string} name
|
|
1065
|
-
* @returns {{observer: ObservableItem, followers: Set}}
|
|
1066
|
-
*/
|
|
1067
|
-
getWithFollowers(name) {
|
|
1068
|
-
return $stores.get(name);
|
|
1069
|
-
},
|
|
1070
|
-
/**
|
|
1071
|
-
* Delete a state.
|
|
1072
|
-
* @param {string} name
|
|
1073
|
-
*/
|
|
1074
|
-
delete(name) {
|
|
1075
|
-
const item = $stores.get(name);
|
|
1076
|
-
if(!item) return;
|
|
1077
|
-
item.observer.cleanup();
|
|
1078
|
-
item.followers.forEach(follower => follower.destroy());
|
|
1079
|
-
item.observer.clear();
|
|
1080
|
-
}
|
|
1081
|
-
};
|
|
1082
|
-
}());
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
*
|
|
1086
|
-
* @param {*} item
|
|
1087
|
-
* @param {string|null} defaultKey
|
|
1088
|
-
* @param {?Function} key
|
|
1089
|
-
* @returns {*}
|
|
1090
|
-
*/
|
|
1091
|
-
const getKey = (item, defaultKey, key) => {
|
|
1092
|
-
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
1093
|
-
if(Validator.isObservable(item)) {
|
|
1094
|
-
const val = item.val();
|
|
1095
|
-
return (val && key) ? val[key] : defaultKey;
|
|
1096
|
-
}
|
|
1097
|
-
return item[key] ?? defaultKey;
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
/**
|
|
1101
|
-
*
|
|
1102
|
-
* @param {Map} cache
|
|
1103
|
-
* @param {Set} keyIds
|
|
1104
|
-
*/
|
|
1105
|
-
const cleanBlockByCache = (cache, keyIds) => {
|
|
1106
|
-
for(const [key, {child}] of cache.entries()) {
|
|
1107
|
-
if(keyIds.has(key)) {
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
child.remove();
|
|
1111
|
-
}
|
|
1112
|
-
};
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
*
|
|
1116
|
-
* @param {Array|Object|ObservableItem} data
|
|
1117
|
-
* @param {Function} callback
|
|
1118
|
-
* @param {?Function} key
|
|
1119
|
-
* @returns {DocumentFragment}
|
|
1120
|
-
*/
|
|
1121
|
-
function ForEach(data, callback, key) {
|
|
1122
|
-
const element = document.createDocumentFragment();
|
|
1123
|
-
const blockStart = document.createComment('Foreach start');
|
|
1124
|
-
const blockEnd = document.createComment('Foreach end');
|
|
1125
|
-
|
|
1126
|
-
element.appendChild(blockStart);
|
|
1127
|
-
element.appendChild(blockEnd);
|
|
1128
|
-
|
|
1129
|
-
let cache = new Map();
|
|
1130
|
-
|
|
1131
|
-
const handleContentItem = (item, indexKey) => {
|
|
1132
|
-
const keyId = getKey(item, indexKey, key);
|
|
1133
|
-
|
|
1134
|
-
if(cache.has(keyId)) {
|
|
1135
|
-
cache.get(keyId).indexObserver.set(indexKey);
|
|
1136
|
-
}
|
|
1137
|
-
else {
|
|
1138
|
-
const indexObserver = Observable(indexKey);
|
|
1139
|
-
let child = callback(item, indexObserver);
|
|
1140
|
-
if(Validator.isStringOrObservable(child)) {
|
|
1141
|
-
child = createTextNode(child);
|
|
1142
|
-
}
|
|
1143
|
-
cache.set(keyId, { child, indexObserver});
|
|
1144
|
-
}
|
|
1145
|
-
return keyId;
|
|
1146
|
-
};
|
|
1147
|
-
const keyIds = new Set();
|
|
1148
|
-
|
|
1149
|
-
const buildContent = () => {
|
|
1150
|
-
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1151
|
-
const parent = blockEnd.parentNode;
|
|
1152
|
-
if(!parent) {
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
keyIds.clear();
|
|
1156
|
-
if(Array.isArray(items)) {
|
|
1157
|
-
items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
|
|
1158
|
-
} else {
|
|
1159
|
-
for(const indexKey in items) {
|
|
1160
|
-
keyIds.add(handleContentItem(items[indexKey], indexKey));
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
cleanBlockByCache(cache, keyIds);
|
|
1165
|
-
let nextElementSibling = blockEnd;
|
|
1166
|
-
for(const item of [...keyIds].reverse()) {
|
|
1167
|
-
const { child } = cache.get(item);
|
|
1168
|
-
if(child) {
|
|
1169
|
-
if(nextElementSibling && nextElementSibling.previousSibling === child) {
|
|
1170
|
-
nextElementSibling = child;
|
|
1171
|
-
continue;
|
|
1172
|
-
}
|
|
1173
|
-
parent.insertBefore(child, nextElementSibling);
|
|
1174
|
-
nextElementSibling = child;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
|
|
1179
|
-
buildContent();
|
|
1180
|
-
if(Validator.isObservable(data)) {
|
|
1181
|
-
data.subscribe(throttle((newValue, oldValue) => {
|
|
1182
|
-
buildContent();
|
|
1183
|
-
}, 50, { debounce: true }));
|
|
1184
|
-
}
|
|
1185
|
-
return element;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
/**
|
|
1189
|
-
* Show the element if the condition is true
|
|
1190
|
-
*
|
|
1191
|
-
* @param {ObservableItem|ObservableChecker} condition
|
|
1192
|
-
* @param {*} child
|
|
1193
|
-
* @param {string|null} comment
|
|
1194
|
-
* @returns {DocumentFragment}
|
|
1195
|
-
*/
|
|
1196
|
-
const ShowIf = function(condition, child, comment) {
|
|
1197
|
-
if(!(Validator.isObservable(condition))) {
|
|
1198
|
-
return DebugManager.warn('ShowIf', "ShowIf : condition must be an Observable / "+comment, condition);
|
|
1199
|
-
}
|
|
1200
|
-
const element = document.createDocumentFragment();
|
|
1201
|
-
const positionKeeperStart = document.createComment('Show if : '+(comment || ''));
|
|
1202
|
-
const positionKeeperEnd = document.createComment('Show if : '+(comment || ''));
|
|
1203
|
-
|
|
1204
|
-
element.appendChild(positionKeeperStart);
|
|
1205
|
-
element.appendChild(positionKeeperEnd);
|
|
1206
|
-
|
|
1207
|
-
let childElement = null;
|
|
1208
|
-
const getChildElement = () => {
|
|
1209
|
-
if(childElement) {
|
|
1210
|
-
return childElement;
|
|
1211
|
-
}
|
|
1212
|
-
if(typeof child === 'function') {
|
|
1213
|
-
childElement = child();
|
|
1214
|
-
}
|
|
1215
|
-
else {
|
|
1216
|
-
childElement = child;
|
|
1217
|
-
}
|
|
1218
|
-
if(Validator.isStringOrObservable(childElement)) {
|
|
1219
|
-
childElement = createTextNode(childElement);
|
|
1220
|
-
}
|
|
1221
|
-
return childElement;
|
|
1222
|
-
};
|
|
1223
|
-
|
|
1224
|
-
const currentValue = condition.val();
|
|
1225
|
-
|
|
1226
|
-
if(currentValue) {
|
|
1227
|
-
element.appendChild(getChildElement());
|
|
1228
|
-
}
|
|
1229
|
-
condition.subscribe(value => {
|
|
1230
|
-
const parent = positionKeeperEnd.parentNode;
|
|
1231
|
-
if(value && parent) {
|
|
1232
|
-
parent.insertBefore(getChildElement(), positionKeeperEnd);
|
|
1233
|
-
} else {
|
|
1234
|
-
if(Validator.isElement(childElement)){
|
|
1235
|
-
childElement.remove();
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
return element;
|
|
1241
|
-
};
|
|
1242
|
-
|
|
1243
|
-
/**
|
|
1244
|
-
* Hide the element if the condition is true
|
|
1245
|
-
* @param {ObservableItem|ObservableChecker} condition
|
|
1246
|
-
* @param child
|
|
1247
|
-
* @param comment
|
|
1248
|
-
* @returns {DocumentFragment}
|
|
1249
|
-
*/
|
|
1250
|
-
const HideIf = function(condition, child, comment) {
|
|
1251
|
-
|
|
1252
|
-
const hideCondition = Observable(!condition.val());
|
|
1253
|
-
condition.subscribe(value => hideCondition.set(!value));
|
|
1254
|
-
|
|
1255
|
-
return ShowIf(hideCondition, child, comment);
|
|
1256
|
-
};
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Hide the element if the condition is false
|
|
1260
|
-
*
|
|
1261
|
-
* @param {ObservableItem|ObservableChecker} condition
|
|
1262
|
-
* @param {*} child
|
|
1263
|
-
* @param {string|null} comment
|
|
1264
|
-
* @returns {DocumentFragment}
|
|
1265
|
-
*/
|
|
1266
|
-
const HideIfNot = function(condition, child, comment) {
|
|
1267
|
-
return ShowIf(condition, child, comment);
|
|
1268
|
-
};
|
|
1269
|
-
|
|
1270
|
-
/**
|
|
1271
|
-
*
|
|
1272
|
-
* @param {ObservableItem|ObservableChecker} condition
|
|
1273
|
-
* @param {*} onTrue
|
|
1274
|
-
* @param {*} onFalse
|
|
1275
|
-
* @returns {DocumentFragment}
|
|
1276
|
-
*/
|
|
1277
|
-
const Switch = function (condition, onTrue, onFalse) {
|
|
1278
|
-
|
|
1279
|
-
if(!Validator.isObservable(condition)) {
|
|
1280
|
-
throw new NativeDocumentError("Toggle : condition must be an Observable");
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
const commentStart = document.createComment('Toggle Start');
|
|
1284
|
-
const commentEnd = document.createComment('Toggle End');
|
|
1285
|
-
|
|
1286
|
-
const element = document.createDocumentFragment();
|
|
1287
|
-
element.appendChild(commentStart);
|
|
1288
|
-
element.appendChild(commentEnd);
|
|
1289
|
-
|
|
1290
|
-
const elements = {
|
|
1291
|
-
onTrueNode: (Validator.isFunction(onTrue)) ? null : onTrue,
|
|
1292
|
-
onFalseNode: (Validator.isFunction(onFalse)) ? null : onFalse,
|
|
1293
|
-
};
|
|
1294
|
-
|
|
1295
|
-
if(Validator.isStringOrObservable(elements.onTrueNode)) {
|
|
1296
|
-
elements.onTrueNode = createTextNode(elements.onTrueNode);
|
|
1297
|
-
}
|
|
1298
|
-
if(Validator.isStringOrObservable(elements.onFalseNode)) {
|
|
1299
|
-
elements.onFalseNode = createTextNode(elements.onFalseNode);
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
const handle = (value) => {
|
|
1303
|
-
const parent = commentEnd.parentNode;
|
|
1304
|
-
if(!parent) {
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
if(value) {
|
|
1308
|
-
if(!elements.onTrueNode && Validator.isFunction(onTrue)) {
|
|
1309
|
-
elements.onTrueNode = onTrue();
|
|
1310
|
-
}
|
|
1311
|
-
elements.onFalseNode && elements.onFalseNode.remove();
|
|
1312
|
-
parent.insertBefore(elements.onTrueNode, commentEnd);
|
|
1313
|
-
} else {
|
|
1314
|
-
if(!elements.onFalseNode && Validator.isFunction(onFalse)) {
|
|
1315
|
-
elements.onFalseNode = onFalse();
|
|
1316
|
-
}
|
|
1317
|
-
elements.onTrueNode && elements.onTrueNode.remove();
|
|
1318
|
-
parent.insertBefore(elements.onFalseNode, commentEnd);
|
|
1319
|
-
}
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
|
-
condition.subscribe(handle);
|
|
1323
|
-
handle(condition.val());
|
|
1324
|
-
|
|
1325
|
-
return element;
|
|
1326
|
-
};
|
|
1327
|
-
|
|
1328
|
-
/**
|
|
1329
|
-
*
|
|
1330
|
-
* @param condition
|
|
1331
|
-
* @returns {{show: Function, otherwise: (((*) => {}):DocumentFragment)}
|
|
1332
|
-
*/
|
|
1333
|
-
const When = function(condition) {
|
|
1334
|
-
let $onTrue = null;
|
|
1335
|
-
let $onFalse = null;
|
|
1336
|
-
|
|
1337
|
-
return {
|
|
1338
|
-
show(onTrue) {
|
|
1339
|
-
if(!Validator.isElement(onTrue) && !Validator.isFunction(onTrue)) {
|
|
1340
|
-
throw new NativeDocumentError("When : onTrue must be a valid Element");
|
|
1341
|
-
}
|
|
1342
|
-
$onTrue = onTrue;
|
|
1343
|
-
return this;
|
|
1344
|
-
},
|
|
1345
|
-
otherwise(onFalse) {
|
|
1346
|
-
if(!Validator.isElement(onFalse) && !Validator.isFunction(onFalse)) {
|
|
1347
|
-
throw new NativeDocumentError("When : onFalse must be a valid Element");
|
|
1348
|
-
}
|
|
1349
|
-
$onFalse = onFalse;
|
|
1350
|
-
return Switch(condition, $onTrue, $onFalse);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
};
|
|
1354
|
-
|
|
1355
|
-
const Div = HtmlElementWrapper('div');
|
|
1356
|
-
const Span = HtmlElementWrapper('span');
|
|
1357
|
-
const Label = HtmlElementWrapper('span');
|
|
1358
|
-
const P = HtmlElementWrapper('p');
|
|
1359
|
-
const Paragraph = P;
|
|
1360
|
-
const Strong = HtmlElementWrapper('strong');
|
|
1361
|
-
const H1 = HtmlElementWrapper('h1');
|
|
1362
|
-
const H2 = HtmlElementWrapper('h2');
|
|
1363
|
-
const H3 = HtmlElementWrapper('h3');
|
|
1364
|
-
const H4 = HtmlElementWrapper('h4');
|
|
1365
|
-
const H5 = HtmlElementWrapper('h5');
|
|
1366
|
-
const H6 = HtmlElementWrapper('h6');
|
|
1367
|
-
|
|
1368
|
-
const Br = HtmlElementWrapper('br');
|
|
1369
|
-
|
|
1370
|
-
const Link$1 = HtmlElementWrapper('a');
|
|
1371
|
-
const Pre = HtmlElementWrapper('pre');
|
|
1372
|
-
const Code = HtmlElementWrapper('code');
|
|
1373
|
-
const Blockquote = HtmlElementWrapper('blockquote');
|
|
1374
|
-
const Hr = HtmlElementWrapper('hr');
|
|
1375
|
-
const Em = HtmlElementWrapper('em');
|
|
1376
|
-
const Small = HtmlElementWrapper('small');
|
|
1377
|
-
const Mark = HtmlElementWrapper('mark');
|
|
1378
|
-
const Del = HtmlElementWrapper('del');
|
|
1379
|
-
const Ins = HtmlElementWrapper('ins');
|
|
1380
|
-
const Sub = HtmlElementWrapper('sub');
|
|
1381
|
-
const Sup = HtmlElementWrapper('sup');
|
|
1382
|
-
const Abbr = HtmlElementWrapper('abbr');
|
|
1383
|
-
const Cite = HtmlElementWrapper('cite');
|
|
1384
|
-
const Quote = HtmlElementWrapper('q');
|
|
1385
|
-
|
|
1386
|
-
const Dl = HtmlElementWrapper('dl');
|
|
1387
|
-
const Dt = HtmlElementWrapper('dt');
|
|
1388
|
-
const Dd = HtmlElementWrapper('dd');
|
|
1389
|
-
|
|
1390
|
-
const Form = HtmlElementWrapper('form', function(el) {
|
|
1391
|
-
|
|
1392
|
-
el.submit = function(action) {
|
|
1393
|
-
if(typeof action === 'function') {
|
|
1394
|
-
el.on.submit((e) => {
|
|
1395
|
-
e.preventDefault();
|
|
1396
|
-
action(e);
|
|
1397
|
-
});
|
|
1398
|
-
return el;
|
|
1399
|
-
}
|
|
1400
|
-
this.setAttribute('action', action);
|
|
1401
|
-
return el;
|
|
1402
|
-
};
|
|
1403
|
-
el.multipartFormData = function() {
|
|
1404
|
-
this.setAttribute('enctype', 'multipart/form-data');
|
|
1405
|
-
return el;
|
|
1406
|
-
};
|
|
1407
|
-
el.post = function(action) {
|
|
1408
|
-
this.setAttribute('method', 'post');
|
|
1409
|
-
this.setAttribute('action', action);
|
|
1410
|
-
return el;
|
|
1411
|
-
};
|
|
1412
|
-
el.get = function(action) {
|
|
1413
|
-
this.setAttribute('method', 'get');
|
|
1414
|
-
this.setAttribute('action', action);
|
|
1415
|
-
};
|
|
1416
|
-
return el;
|
|
1417
|
-
});
|
|
1418
|
-
|
|
1419
|
-
const Input = HtmlElementWrapper('input');
|
|
1420
|
-
|
|
1421
|
-
const TextArea = HtmlElementWrapper('textarea');
|
|
1422
|
-
const TextInput = TextArea;
|
|
1423
|
-
|
|
1424
|
-
const Select = HtmlElementWrapper('select');
|
|
1425
|
-
const FieldSet = HtmlElementWrapper('fieldset', );
|
|
1426
|
-
const Option = HtmlElementWrapper('option');
|
|
1427
|
-
const Legend = HtmlElementWrapper('legend');
|
|
1428
|
-
const Datalist = HtmlElementWrapper('datalist');
|
|
1429
|
-
const Output = HtmlElementWrapper('output');
|
|
1430
|
-
const Progress = HtmlElementWrapper('progress');
|
|
1431
|
-
const Meter = HtmlElementWrapper('meter');
|
|
1432
|
-
|
|
1433
|
-
const ReadonlyInput = (attributes) => Input({ readonly: true, ...attributes });
|
|
1434
|
-
const HiddenInput = (attributes) => Input({type: 'hidden', ...attributes });
|
|
1435
|
-
const FileInput = (attributes) => Input({ type: 'file', ...attributes });
|
|
1436
|
-
const PasswordInput = (attributes) => Input({ type: 'password', ...attributes });
|
|
1437
|
-
const Checkbox = (attributes) => Input({ type: 'checkbox', ...attributes });
|
|
1438
|
-
const Radio = (attributes) => Input({ type: 'radio', ...attributes });
|
|
1439
|
-
|
|
1440
|
-
const RangeInput = (attributes) => Input({ type: 'range', ...attributes });
|
|
1441
|
-
const ColorInput = (attributes) => Input({ type: 'color', ...attributes });
|
|
1442
|
-
const DateInput = (attributes) => Input({ type: 'date', ...attributes });
|
|
1443
|
-
const TimeInput = (attributes) => Input({ type: 'time', ...attributes });
|
|
1444
|
-
const DateTimeInput = (attributes) => Input({ type: 'datetime-local', ...attributes });
|
|
1445
|
-
const WeekInput = (attributes) => Input({ type: 'week', ...attributes });
|
|
1446
|
-
const MonthInput = (attributes) => Input({ type: 'month', ...attributes });
|
|
1447
|
-
const SearchInput = (attributes) => Input({ type: 'search', ...attributes });
|
|
1448
|
-
const TelInput = (attributes) => Input({ type: 'tel', ...attributes });
|
|
1449
|
-
const UrlInput = (attributes) => Input({ type: 'url', ...attributes });
|
|
1450
|
-
const EmailInput = (attributes) => Input({ type: 'email', ...attributes });
|
|
1451
|
-
const NumberInput = (attributes) => Input({ type: 'number', ...attributes });
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
const Button = HtmlElementWrapper('button');
|
|
1455
|
-
const SimpleButton = (child, attributes) => Button(child, { type: 'button', ...attributes });
|
|
1456
|
-
const SubmitButton = (child, attributes) => Button(child, { type: 'submit', ...attributes });
|
|
1457
|
-
|
|
1458
|
-
const Main = HtmlElementWrapper('main');
|
|
1459
|
-
const Section = HtmlElementWrapper('section');
|
|
1460
|
-
const Article = HtmlElementWrapper('article');
|
|
1461
|
-
const Aside = HtmlElementWrapper('aside');
|
|
1462
|
-
const Nav = HtmlElementWrapper('nav');
|
|
1463
|
-
const Figure = HtmlElementWrapper('figure');
|
|
1464
|
-
const FigCaption = HtmlElementWrapper('figcaption');
|
|
1465
|
-
|
|
1466
|
-
const Header = HtmlElementWrapper('header');
|
|
1467
|
-
const Footer = HtmlElementWrapper('footer');
|
|
1468
|
-
|
|
1469
|
-
const BaseImage = HtmlElementWrapper('img');
|
|
1470
|
-
const Img = function(src, attributes) {
|
|
1471
|
-
return BaseImage({ src, ...attributes });
|
|
1472
|
-
};
|
|
1473
|
-
|
|
1474
|
-
/**
|
|
1475
|
-
*
|
|
1476
|
-
* @param {string} src
|
|
1477
|
-
* @param {string|null} defaultImage
|
|
1478
|
-
* @param {Object} attributes
|
|
1479
|
-
* @param {?Function} callback
|
|
1480
|
-
* @returns {Image}
|
|
1481
|
-
*/
|
|
1482
|
-
const AsyncImg = function(src, defaultImage, attributes, callback) {
|
|
1483
|
-
const image = Img(defaultImage || src, attributes);
|
|
1484
|
-
const img = new Image();
|
|
1485
|
-
img.onload = () => {
|
|
1486
|
-
Validator.isFunction(callback) && callback(null, image);
|
|
1487
|
-
image.src = src;
|
|
1488
|
-
};
|
|
1489
|
-
img.onerror = () => {
|
|
1490
|
-
Validator.isFunction(callback) && callback(new NativeDocumentError('Image not found'));
|
|
1491
|
-
};
|
|
1492
|
-
if(Validator.isObservable(src)) {
|
|
1493
|
-
src.subscribe(newSrc => {
|
|
1494
|
-
img.src = newSrc;
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
img.src = src;
|
|
1498
|
-
return image;
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
*
|
|
1503
|
-
* @param {string} src
|
|
1504
|
-
* @param {Object} attributes
|
|
1505
|
-
* @returns {Image}
|
|
1506
|
-
*/
|
|
1507
|
-
const LazyImg = function(src, attributes) {
|
|
1508
|
-
return Img(src, { ...attributes, loading: 'lazy' });
|
|
1509
|
-
};
|
|
1510
|
-
|
|
1511
|
-
const Details = HtmlElementWrapper('details');
|
|
1512
|
-
const Summary = HtmlElementWrapper('summary');
|
|
1513
|
-
const Dialog = HtmlElementWrapper('dialog');
|
|
1514
|
-
const Menu = HtmlElementWrapper('menu');
|
|
1515
|
-
|
|
1516
|
-
const OrderedList = HtmlElementWrapper('ol');
|
|
1517
|
-
const UnorderedList = HtmlElementWrapper('ul');
|
|
1518
|
-
const ListItem = HtmlElementWrapper('li');
|
|
1519
|
-
|
|
1520
|
-
const Audio = HtmlElementWrapper('audio');
|
|
1521
|
-
const Video = HtmlElementWrapper('video');
|
|
1522
|
-
const Source = HtmlElementWrapper('source');
|
|
1523
|
-
const Track = HtmlElementWrapper('track');
|
|
1524
|
-
const Canvas = HtmlElementWrapper('canvas');
|
|
1525
|
-
const Svg = HtmlElementWrapper('svg');
|
|
1526
|
-
|
|
1527
|
-
const Time = HtmlElementWrapper('time');
|
|
1528
|
-
const Data = HtmlElementWrapper('data');
|
|
1529
|
-
const Address = HtmlElementWrapper('address');
|
|
1530
|
-
const Kbd = HtmlElementWrapper('kbd');
|
|
1531
|
-
const Samp = HtmlElementWrapper('samp');
|
|
1532
|
-
const Var = HtmlElementWrapper('var');
|
|
1533
|
-
const Wbr = HtmlElementWrapper('wbr');
|
|
1534
|
-
|
|
1535
|
-
const Caption = HtmlElementWrapper('caption');
|
|
1536
|
-
const Table = HtmlElementWrapper('table');
|
|
1537
|
-
const THead = HtmlElementWrapper('thead');
|
|
1538
|
-
const TFoot = HtmlElementWrapper('tfoot');
|
|
1539
|
-
const TBody = HtmlElementWrapper('tbody');
|
|
1540
|
-
const Tr = HtmlElementWrapper('tr');
|
|
1541
|
-
const TRow = Tr;
|
|
1542
|
-
const Th = HtmlElementWrapper('th');
|
|
1543
|
-
const THeadCell = Th;
|
|
1544
|
-
const TFootCell = Th;
|
|
1545
|
-
const Td = HtmlElementWrapper('td');
|
|
1546
|
-
const TBodyCell = Td;
|
|
1547
|
-
|
|
1548
|
-
const Fragment = HtmlElementWrapper('');
|
|
1549
|
-
|
|
1550
|
-
var elements = /*#__PURE__*/Object.freeze({
|
|
1551
|
-
__proto__: null,
|
|
1552
|
-
Abbr: Abbr,
|
|
1553
|
-
Address: Address,
|
|
1554
|
-
Article: Article,
|
|
1555
|
-
Aside: Aside,
|
|
1556
|
-
AsyncImg: AsyncImg,
|
|
1557
|
-
Audio: Audio,
|
|
1558
|
-
BaseImage: BaseImage,
|
|
1559
|
-
Blockquote: Blockquote,
|
|
1560
|
-
Br: Br,
|
|
1561
|
-
Button: Button,
|
|
1562
|
-
Canvas: Canvas,
|
|
1563
|
-
Caption: Caption,
|
|
1564
|
-
Checkbox: Checkbox,
|
|
1565
|
-
Cite: Cite,
|
|
1566
|
-
Code: Code,
|
|
1567
|
-
ColorInput: ColorInput,
|
|
1568
|
-
Data: Data,
|
|
1569
|
-
Datalist: Datalist,
|
|
1570
|
-
DateInput: DateInput,
|
|
1571
|
-
DateTimeInput: DateTimeInput,
|
|
1572
|
-
Dd: Dd,
|
|
1573
|
-
Del: Del,
|
|
1574
|
-
Details: Details,
|
|
1575
|
-
Dialog: Dialog,
|
|
1576
|
-
Div: Div,
|
|
1577
|
-
Dl: Dl,
|
|
1578
|
-
Dt: Dt,
|
|
1579
|
-
Em: Em,
|
|
1580
|
-
EmailInput: EmailInput,
|
|
1581
|
-
FieldSet: FieldSet,
|
|
1582
|
-
FigCaption: FigCaption,
|
|
1583
|
-
Figure: Figure,
|
|
1584
|
-
FileInput: FileInput,
|
|
1585
|
-
Footer: Footer,
|
|
1586
|
-
ForEach: ForEach,
|
|
1587
|
-
Form: Form,
|
|
1588
|
-
Fragment: Fragment,
|
|
1589
|
-
H1: H1,
|
|
1590
|
-
H2: H2,
|
|
1591
|
-
H3: H3,
|
|
1592
|
-
H4: H4,
|
|
1593
|
-
H5: H5,
|
|
1594
|
-
H6: H6,
|
|
1595
|
-
Header: Header,
|
|
1596
|
-
HiddenInput: HiddenInput,
|
|
1597
|
-
HideIf: HideIf,
|
|
1598
|
-
HideIfNot: HideIfNot,
|
|
1599
|
-
Hr: Hr,
|
|
1600
|
-
Img: Img,
|
|
1601
|
-
Input: Input,
|
|
1602
|
-
Ins: Ins,
|
|
1603
|
-
Kbd: Kbd,
|
|
1604
|
-
Label: Label,
|
|
1605
|
-
LazyImg: LazyImg,
|
|
1606
|
-
Legend: Legend,
|
|
1607
|
-
Link: Link$1,
|
|
1608
|
-
ListItem: ListItem,
|
|
1609
|
-
Main: Main,
|
|
1610
|
-
Mark: Mark,
|
|
1611
|
-
Menu: Menu,
|
|
1612
|
-
Meter: Meter,
|
|
1613
|
-
MonthInput: MonthInput,
|
|
1614
|
-
Nav: Nav,
|
|
1615
|
-
NumberInput: NumberInput,
|
|
1616
|
-
Option: Option,
|
|
1617
|
-
OrderedList: OrderedList,
|
|
1618
|
-
Output: Output,
|
|
1619
|
-
P: P,
|
|
1620
|
-
Paragraph: Paragraph,
|
|
1621
|
-
PasswordInput: PasswordInput,
|
|
1622
|
-
Pre: Pre,
|
|
1623
|
-
Progress: Progress,
|
|
1624
|
-
Quote: Quote,
|
|
1625
|
-
Radio: Radio,
|
|
1626
|
-
RangeInput: RangeInput,
|
|
1627
|
-
ReadonlyInput: ReadonlyInput,
|
|
1628
|
-
Samp: Samp,
|
|
1629
|
-
SearchInput: SearchInput,
|
|
1630
|
-
Section: Section,
|
|
1631
|
-
Select: Select,
|
|
1632
|
-
ShowIf: ShowIf,
|
|
1633
|
-
SimpleButton: SimpleButton,
|
|
1634
|
-
Small: Small,
|
|
1635
|
-
Source: Source,
|
|
1636
|
-
Span: Span,
|
|
1637
|
-
Strong: Strong,
|
|
1638
|
-
Sub: Sub,
|
|
1639
|
-
SubmitButton: SubmitButton,
|
|
1640
|
-
Summary: Summary,
|
|
1641
|
-
Sup: Sup,
|
|
1642
|
-
Svg: Svg,
|
|
1643
|
-
Switch: Switch,
|
|
1644
|
-
TBody: TBody,
|
|
1645
|
-
TBodyCell: TBodyCell,
|
|
1646
|
-
TFoot: TFoot,
|
|
1647
|
-
TFootCell: TFootCell,
|
|
1648
|
-
THead: THead,
|
|
1649
|
-
THeadCell: THeadCell,
|
|
1650
|
-
TRow: TRow,
|
|
1651
|
-
Table: Table,
|
|
1652
|
-
Td: Td,
|
|
1653
|
-
TelInput: TelInput,
|
|
1654
|
-
TextArea: TextArea,
|
|
1655
|
-
TextInput: TextInput,
|
|
1656
|
-
Th: Th,
|
|
1657
|
-
Time: Time,
|
|
1658
|
-
TimeInput: TimeInput,
|
|
1659
|
-
Tr: Tr,
|
|
1660
|
-
Track: Track,
|
|
1661
|
-
UnorderedList: UnorderedList,
|
|
1662
|
-
UrlInput: UrlInput,
|
|
1663
|
-
Var: Var,
|
|
1664
|
-
Video: Video,
|
|
1665
|
-
Wbr: Wbr,
|
|
1666
|
-
WeekInput: WeekInput,
|
|
1667
|
-
When: When
|
|
1668
|
-
});
|
|
1669
|
-
|
|
1670
|
-
const RouteParamPatterns = {
|
|
1671
|
-
|
|
1672
|
-
};
|
|
1673
|
-
|
|
1674
|
-
/**
|
|
1675
|
-
*
|
|
1676
|
-
* @param {string} $path
|
|
1677
|
-
* @param {Function} $component
|
|
1678
|
-
* @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }}$options
|
|
1679
|
-
* @class
|
|
1680
|
-
*/
|
|
1681
|
-
function Route($path, $component, $options = {}) {
|
|
1682
|
-
|
|
1683
|
-
$path = '/'+trim($path, '/');
|
|
1684
|
-
|
|
1685
|
-
let $pattern = null;
|
|
1686
|
-
let $name = $options.name || null;
|
|
1687
|
-
|
|
1688
|
-
const $middlewares = $options.middlewares || [];
|
|
1689
|
-
const $shouldRebuild = $options.shouldRebuild || false;
|
|
1690
|
-
const $paramsValidators = $options.with || {};
|
|
1691
|
-
|
|
1692
|
-
const $params = {};
|
|
1693
|
-
const $paramsNames = [];
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const paramsExtractor = (description) => {
|
|
1697
|
-
if(!description) return null;
|
|
1698
|
-
const [name, type] = description.split(':');
|
|
1699
|
-
|
|
1700
|
-
let pattern = $paramsValidators[name];
|
|
1701
|
-
if(!pattern && type) {
|
|
1702
|
-
pattern = RouteParamPatterns[type];
|
|
1703
|
-
}
|
|
1704
|
-
if(!pattern) {
|
|
1705
|
-
pattern = '[^/]+';
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
pattern = pattern.replace('(', '(?:');
|
|
1709
|
-
|
|
1710
|
-
return { name, pattern: `(${pattern})` };
|
|
1711
|
-
};
|
|
1712
|
-
|
|
1713
|
-
const getPattern = () => {
|
|
1714
|
-
if($pattern) {
|
|
1715
|
-
return $pattern;
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
const patternDescription = $path.replace(/\{(.*?)}/ig, (block, definition) => {
|
|
1719
|
-
const description = paramsExtractor(definition);
|
|
1720
|
-
if(!description || !description.pattern) return block;
|
|
1721
|
-
$params[description.name] = description.pattern;
|
|
1722
|
-
$paramsNames.push(description.name);
|
|
1723
|
-
return description.pattern;
|
|
1724
|
-
});
|
|
1725
|
-
|
|
1726
|
-
$pattern = new RegExp('^'+patternDescription+'$');
|
|
1727
|
-
return $pattern;
|
|
1728
|
-
};
|
|
1729
|
-
|
|
1730
|
-
this.name = () => $name;
|
|
1731
|
-
this.component = () => $component;
|
|
1732
|
-
this.middlewares = () => $middlewares;
|
|
1733
|
-
this.shouldRebuild = () => $shouldRebuild;
|
|
1734
|
-
this.path = () => $path;
|
|
1735
|
-
|
|
1736
|
-
/**
|
|
1737
|
-
*
|
|
1738
|
-
* @param {string} path
|
|
1739
|
-
*/
|
|
1740
|
-
this.match = function(path) {
|
|
1741
|
-
path = '/'+trim(path, '/');
|
|
1742
|
-
const match = getPattern().exec(path);
|
|
1743
|
-
if(!match) return false;
|
|
1744
|
-
const params = {};
|
|
1745
|
-
|
|
1746
|
-
getPattern().exec(path).forEach((value, index) => {
|
|
1747
|
-
if(index < 1) return;
|
|
1748
|
-
const name = $paramsNames[index - 1];
|
|
1749
|
-
params[name] = value;
|
|
1750
|
-
});
|
|
1751
|
-
|
|
1752
|
-
return params;
|
|
1753
|
-
};
|
|
1754
|
-
/**
|
|
1755
|
-
* @param {{params: ?Object, query: ?Object, basePath: ?string}} configs
|
|
1756
|
-
*/
|
|
1757
|
-
this.url = function(configs) {
|
|
1758
|
-
const path = $path.replace(/\{(.*?)}/ig, (block, definition) => {
|
|
1759
|
-
const description = paramsExtractor(definition);
|
|
1760
|
-
if(configs.params && configs.params[description.name]) {
|
|
1761
|
-
return configs.params[description.name];
|
|
1762
|
-
}
|
|
1763
|
-
throw new Error(`Missing parameter '${description.name}'`);
|
|
1764
|
-
});
|
|
1765
|
-
|
|
1766
|
-
const queryString = (typeof configs.query === 'object') ? (new URLSearchParams(configs.query)).toString() : null;
|
|
1767
|
-
return (configs.basePath ? configs.basePath : '') + (queryString ? `${path}?${queryString}` : path);
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
class RouterError extends Error {
|
|
1772
|
-
constructor(message, context) {
|
|
1773
|
-
super(message);
|
|
1774
|
-
this.context = context;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
const RouteGroupHelper = {
|
|
1780
|
-
/**
|
|
1781
|
-
*
|
|
1782
|
-
* @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
|
|
1783
|
-
* @param {string} path
|
|
1784
|
-
* @returns {string}
|
|
1785
|
-
*/
|
|
1786
|
-
fullPath: ($groupTree, path) => {
|
|
1787
|
-
const fullPath = [];
|
|
1788
|
-
$groupTree.forEach(group => {
|
|
1789
|
-
fullPath.push(trim(group.suffix, '/'));
|
|
1790
|
-
});
|
|
1791
|
-
fullPath.push(trim(path, '/'));
|
|
1792
|
-
return fullPath.join('/');
|
|
1793
|
-
},
|
|
1794
|
-
/**
|
|
1795
|
-
*
|
|
1796
|
-
* @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
|
|
1797
|
-
* @param {Function[]} middlewares
|
|
1798
|
-
* @returns {Function[]}
|
|
1799
|
-
*/
|
|
1800
|
-
fullMiddlewares: ($groupTree, middlewares) => {
|
|
1801
|
-
const fullMiddlewares = [];
|
|
1802
|
-
$groupTree.forEach(group => {
|
|
1803
|
-
if(group.options.middlewares) {
|
|
1804
|
-
fullMiddlewares.push(...group.options.middlewares);
|
|
1805
|
-
}
|
|
1806
|
-
});
|
|
1807
|
-
if(middlewares) {
|
|
1808
|
-
fullMiddlewares.push(...middlewares);
|
|
1809
|
-
}
|
|
1810
|
-
return fullMiddlewares;
|
|
1811
|
-
},
|
|
1812
|
-
/**
|
|
1813
|
-
*
|
|
1814
|
-
* @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
|
|
1815
|
-
* @param {string} name
|
|
1816
|
-
* @returns {string}
|
|
1817
|
-
*/
|
|
1818
|
-
fullName: ($groupTree, name) => {
|
|
1819
|
-
const fullName = [];
|
|
1820
|
-
$groupTree.forEach(group => {
|
|
1821
|
-
if(group.options?.name) {
|
|
1822
|
-
fullName.push(group.options.name);
|
|
1823
|
-
}
|
|
1824
|
-
});
|
|
1825
|
-
name && fullName.push(name);
|
|
1826
|
-
return fullName.join('.');
|
|
1827
|
-
}
|
|
1828
|
-
};
|
|
1829
|
-
|
|
1830
|
-
function HashRouter() {
|
|
1831
|
-
|
|
1832
|
-
const $history = [];
|
|
1833
|
-
let $currentIndex = 0;
|
|
1834
|
-
|
|
1835
|
-
/**
|
|
1836
|
-
*
|
|
1837
|
-
* @param {number} delta
|
|
1838
|
-
*/
|
|
1839
|
-
const go = (delta) => {
|
|
1840
|
-
const index = $currentIndex + delta;
|
|
1841
|
-
if(!$history[index]) {
|
|
1842
|
-
return;
|
|
1843
|
-
}
|
|
1844
|
-
$currentIndex = index;
|
|
1845
|
-
const { route, params, query, path } = $history[index];
|
|
1846
|
-
setHash(path);
|
|
1847
|
-
};
|
|
1848
|
-
|
|
1849
|
-
const canGoBack = function() {
|
|
1850
|
-
return $currentIndex > 0;
|
|
1851
|
-
};
|
|
1852
|
-
const canGoForward = function() {
|
|
1853
|
-
return $currentIndex < $history.length - 1;
|
|
1854
|
-
};
|
|
1855
|
-
|
|
1856
|
-
/**
|
|
1857
|
-
*
|
|
1858
|
-
* @param {string} path
|
|
1859
|
-
*/
|
|
1860
|
-
const setHash = (path) => {
|
|
1861
|
-
window.location.replace(`${window.location.pathname}${window.location.search}#${path}`);
|
|
1862
|
-
};
|
|
1863
|
-
|
|
1864
|
-
const getCurrentHash = () => window.location.hash.slice(1);
|
|
1865
|
-
|
|
1866
|
-
/**
|
|
1867
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
1868
|
-
*/
|
|
1869
|
-
this.push = function(target) {
|
|
1870
|
-
const { route, params, query, path } = this.resolve(target);
|
|
1871
|
-
if(path === getCurrentHash()) {
|
|
1872
|
-
return;
|
|
1873
|
-
}
|
|
1874
|
-
$history.splice($currentIndex + 1);
|
|
1875
|
-
$history.push({ route, params, query, path });
|
|
1876
|
-
$currentIndex++;
|
|
1877
|
-
setHash(path);
|
|
1878
|
-
};
|
|
1879
|
-
/**
|
|
1880
|
-
*
|
|
1881
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
1882
|
-
*/
|
|
1883
|
-
this.replace = function(target) {
|
|
1884
|
-
const { route, params, query, path } = this.resolve(target);
|
|
1885
|
-
if(path === getCurrentHash()) {
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
$history[$currentIndex] = { route, params, query, path };
|
|
1889
|
-
};
|
|
1890
|
-
this.forward = function() {
|
|
1891
|
-
return canGoForward() && go(1);
|
|
1892
|
-
};
|
|
1893
|
-
this.back = function() {
|
|
1894
|
-
return canGoBack() && go(-1);
|
|
1895
|
-
};
|
|
1896
|
-
|
|
1897
|
-
/**
|
|
1898
|
-
* @param {string} defaultPath
|
|
1899
|
-
*/
|
|
1900
|
-
this.init = function(defaultPath) {
|
|
1901
|
-
window.addEventListener('hashchange', () => {
|
|
1902
|
-
const { route, params, query, path } = this.resolve(getCurrentHash());
|
|
1903
|
-
this.handleRouteChange(route, params, query, path);
|
|
1904
|
-
});
|
|
1905
|
-
const { route, params, query, path } = this.resolve(defaultPath || getCurrentHash());
|
|
1906
|
-
$history.push({ route, params, query, path });
|
|
1907
|
-
$currentIndex = 0;
|
|
1908
|
-
this.handleRouteChange(route, params, query, path);
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
function HistoryRouter() {
|
|
1913
|
-
|
|
1914
|
-
/**
|
|
1915
|
-
*
|
|
1916
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
1917
|
-
*/
|
|
1918
|
-
this.push = function(target) {
|
|
1919
|
-
try {
|
|
1920
|
-
const { route, path, params, query } = this.resolve(target);
|
|
1921
|
-
if(window.history.state && window.history.state.path === path) {
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
window.history.pushState({ name: route.name(), params, path}, route.name() || path , path);
|
|
1925
|
-
this.handleRouteChange(route, params, query, path);
|
|
1926
|
-
} catch (e) {
|
|
1927
|
-
DebugManager.error('HistoryRouter', 'Error in pushState', e);
|
|
1928
|
-
}
|
|
1929
|
-
};
|
|
1930
|
-
/**
|
|
1931
|
-
*
|
|
1932
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
1933
|
-
*/
|
|
1934
|
-
this.replace = function(target) {
|
|
1935
|
-
const { route, path, params } = this.resolve(target);
|
|
1936
|
-
try {
|
|
1937
|
-
window.history.replaceState({ name: route.name(), params, path}, route.name() || path , path);
|
|
1938
|
-
this.handleRouteChange(route, params, {}, path);
|
|
1939
|
-
} catch(e) {
|
|
1940
|
-
DebugManager.error('HistoryRouter', 'Error in replaceState', e);
|
|
1941
|
-
}
|
|
1942
|
-
};
|
|
1943
|
-
this.forward = function() {
|
|
1944
|
-
window.history.forward();
|
|
1945
|
-
};
|
|
1946
|
-
|
|
1947
|
-
this.back = function() {
|
|
1948
|
-
window.history.back();
|
|
1949
|
-
};
|
|
1950
|
-
|
|
1951
|
-
/**
|
|
1952
|
-
* @param {string} defaultPath
|
|
1953
|
-
*/
|
|
1954
|
-
this.init = function(defaultPath) {
|
|
1955
|
-
window.addEventListener('popstate', (event) => {
|
|
1956
|
-
try {
|
|
1957
|
-
if(!event.state || !event.state.path) {
|
|
1958
|
-
return;
|
|
1959
|
-
}
|
|
1960
|
-
const statePath = event.state.path;
|
|
1961
|
-
const {route, params, query, path} = this.resolve(statePath);
|
|
1962
|
-
if(!route) {
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
this.handleRouteChange(route, params, query, path);
|
|
1966
|
-
} catch(e) {
|
|
1967
|
-
DebugManager.error('HistoryRouter', 'Error in popstate event', e);
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
const { route, params, query, path } = this.resolve(defaultPath || (window.location.pathname+window.location.search));
|
|
1971
|
-
this.handleRouteChange(route, params, query, path);
|
|
1972
|
-
};
|
|
1973
|
-
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
function MemoryRouter() {
|
|
1977
|
-
const $history = [];
|
|
1978
|
-
let $currentIndex = 0;
|
|
1979
|
-
|
|
1980
|
-
/**
|
|
1981
|
-
*
|
|
1982
|
-
* @param {number} delta
|
|
1983
|
-
*/
|
|
1984
|
-
const go = (delta) => {
|
|
1985
|
-
const index = $currentIndex + delta;
|
|
1986
|
-
if(!$history[index]) {
|
|
1987
|
-
return;
|
|
1988
|
-
}
|
|
1989
|
-
$currentIndex = index;
|
|
1990
|
-
const { route, params, query, path } = $history[index];
|
|
1991
|
-
this.handleRouteChange(route, params, query, path);
|
|
1992
|
-
};
|
|
1993
|
-
|
|
1994
|
-
const canGoBack = function() {
|
|
1995
|
-
return $currentIndex > 0;
|
|
1996
|
-
};
|
|
1997
|
-
const canGoForward = function() {
|
|
1998
|
-
return $currentIndex < $history.length - 1;
|
|
1999
|
-
};
|
|
2000
|
-
|
|
2001
|
-
/**
|
|
2002
|
-
*
|
|
2003
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
2004
|
-
*/
|
|
2005
|
-
this.push = function(target) {
|
|
2006
|
-
const { route, params, query, path} = this.resolve(target);
|
|
2007
|
-
if($history[$currentIndex] && $history[$currentIndex].path === path) {
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
$history.splice($currentIndex + 1);
|
|
2011
|
-
$history.push({ route, params, query, path });
|
|
2012
|
-
$currentIndex++;
|
|
2013
|
-
this.handleRouteChange(route, params, query, path);
|
|
2014
|
-
};
|
|
2015
|
-
|
|
2016
|
-
/**
|
|
2017
|
-
*
|
|
2018
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
2019
|
-
*/
|
|
2020
|
-
this.replace = function(target) {
|
|
2021
|
-
const { route, params, query, path} = this.resolve(target);
|
|
2022
|
-
$history[$currentIndex] = { route, params, query, path };
|
|
2023
|
-
this.handleRouteChange(route, params, query, path);
|
|
2024
|
-
};
|
|
2025
|
-
|
|
2026
|
-
this.forward = function() {
|
|
2027
|
-
return canGoForward() && go(1);
|
|
2028
|
-
};
|
|
2029
|
-
|
|
2030
|
-
this.back = function() {
|
|
2031
|
-
return canGoBack() && go(-1);
|
|
2032
|
-
};
|
|
2033
|
-
|
|
2034
|
-
/**
|
|
2035
|
-
* @param {string} defaultPath
|
|
2036
|
-
*/
|
|
2037
|
-
this.init = function(defaultPath) {
|
|
2038
|
-
const currentPath = defaultPath || (window.location.pathname + window.location.search);
|
|
2039
|
-
const { route, params, query, path } = this.resolve(currentPath);
|
|
2040
|
-
$history.push({ route, params, query, path });
|
|
2041
|
-
$currentIndex = 0;
|
|
2042
|
-
|
|
2043
|
-
this.handleRouteChange(route, params, query, path);
|
|
2044
|
-
};
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
/**
|
|
2048
|
-
*
|
|
2049
|
-
* @param {Router} router
|
|
2050
|
-
* @param {?HTMLElement} container
|
|
2051
|
-
*/
|
|
2052
|
-
function RouterComponent(router, container) {
|
|
2053
|
-
|
|
2054
|
-
const $cache = new Map();
|
|
2055
|
-
|
|
2056
|
-
const updateContainer = function(node) {
|
|
2057
|
-
container.innerHTML = '';
|
|
2058
|
-
container.appendChild(node);
|
|
2059
|
-
};
|
|
2060
|
-
|
|
2061
|
-
const handleCurrentRouterState = function(state) {
|
|
2062
|
-
if(!state.route) {
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
const { route, params, query, path } = state;
|
|
2066
|
-
if($cache.has(path)) {
|
|
2067
|
-
const cacheNode = $cache.get(path);
|
|
2068
|
-
console.log(cacheNode);
|
|
2069
|
-
updateContainer(cacheNode);
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
const Component = route.component();
|
|
2073
|
-
console.log({ params, query });
|
|
2074
|
-
const node = Component({ params, query });
|
|
2075
|
-
$cache.set(path, node);
|
|
2076
|
-
updateContainer(node);
|
|
2077
|
-
};
|
|
2078
|
-
|
|
2079
|
-
router.subscribe(handleCurrentRouterState);
|
|
2080
|
-
|
|
2081
|
-
handleCurrentRouterState(router.currentState());
|
|
2082
|
-
return container;
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
const DEFAULT_ROUTER_NAME = 'default';
|
|
2086
|
-
|
|
2087
|
-
/**
|
|
2088
|
-
*
|
|
2089
|
-
* @param {{mode: 'memory'|'history'|'hash'}} $options
|
|
2090
|
-
* @constructor
|
|
2091
|
-
*/
|
|
2092
|
-
function Router($options = {}) {
|
|
2093
|
-
|
|
2094
|
-
/** @type {Route[]} */
|
|
2095
|
-
const $routes = [];
|
|
2096
|
-
/** @type {{[string]: Route}} */
|
|
2097
|
-
const $routesByName = {};
|
|
2098
|
-
const $groupTree = [];
|
|
2099
|
-
const $listeners = [];
|
|
2100
|
-
const $currentState = { route: null, params: null, query: null, path: null, hash: null };
|
|
2101
|
-
|
|
2102
|
-
if($options.mode === 'hash') {
|
|
2103
|
-
HashRouter.apply(this, []);
|
|
2104
|
-
} else if($options.mode === 'history') {
|
|
2105
|
-
HistoryRouter.apply(this, []);
|
|
2106
|
-
} else if($options.mode === 'memory') {
|
|
2107
|
-
MemoryRouter.apply(this, []);
|
|
2108
|
-
} else {
|
|
2109
|
-
throw new RouterError('Invalid router mode '+$options.mode);
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
const trigger = function(request, next) {
|
|
2113
|
-
for(const listener of $listeners) {
|
|
2114
|
-
try {
|
|
2115
|
-
listener(request);
|
|
2116
|
-
next && next(request);
|
|
2117
|
-
} catch (e) {
|
|
2118
|
-
DebugManager.warn('Route Listener', 'Error in listener:', e);
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
};
|
|
2122
|
-
|
|
2123
|
-
this.routes = () => [...$routes];
|
|
2124
|
-
this.currentState = () => ({ ...$currentState });
|
|
2125
|
-
|
|
2126
|
-
/**
|
|
2127
|
-
*
|
|
2128
|
-
* @param {string} path
|
|
2129
|
-
* @param {Function} component
|
|
2130
|
-
* @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }} options
|
|
2131
|
-
* @returns {this}
|
|
2132
|
-
*/
|
|
2133
|
-
this.add = function(path, component, options) {
|
|
2134
|
-
const route = new Route(RouteGroupHelper.fullPath($groupTree, path), component, {
|
|
2135
|
-
...options,
|
|
2136
|
-
middlewares: RouteGroupHelper.fullMiddlewares($groupTree, options?.middlewares || []),
|
|
2137
|
-
name: options?.name ? RouteGroupHelper.fullName($groupTree, options.name) : null,
|
|
2138
|
-
});
|
|
2139
|
-
$routes.push(route);
|
|
2140
|
-
if(route.name()) {
|
|
2141
|
-
$routesByName[route.name()] = route;
|
|
2142
|
-
}
|
|
2143
|
-
return this;
|
|
2144
|
-
};
|
|
2145
|
-
|
|
2146
|
-
/**
|
|
2147
|
-
*
|
|
2148
|
-
* @param {string} suffix
|
|
2149
|
-
* @param {{ middlewares: Function[], name: string}} options
|
|
2150
|
-
* @param {Function} callback
|
|
2151
|
-
* @returns {this}
|
|
2152
|
-
*/
|
|
2153
|
-
this.group = function(suffix, options, callback) {
|
|
2154
|
-
if(!Validator.isFunction(callback)) {
|
|
2155
|
-
throw new RouterError('Callback must be a function');
|
|
2156
|
-
}
|
|
2157
|
-
$groupTree.push({suffix, options});
|
|
2158
|
-
callback();
|
|
2159
|
-
$groupTree.pop();
|
|
2160
|
-
return this;
|
|
2161
|
-
};
|
|
2162
|
-
|
|
2163
|
-
/**
|
|
2164
|
-
*
|
|
2165
|
-
* @param {string} name
|
|
2166
|
-
* @param {Object}params
|
|
2167
|
-
* @param {Object} query
|
|
2168
|
-
* @returns {*}
|
|
2169
|
-
*/
|
|
2170
|
-
this.generateUrl = function(name, params = {}, query = {}) {
|
|
2171
|
-
const route = $routesByName[name];
|
|
2172
|
-
if(!route) {
|
|
2173
|
-
throw new RouterError(`Route not found for name: ${name}`);
|
|
2174
|
-
}
|
|
2175
|
-
return route.url({ params, query });
|
|
2176
|
-
};
|
|
2177
|
-
|
|
2178
|
-
/**
|
|
2179
|
-
*
|
|
2180
|
-
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
2181
|
-
* @returns {{route:Route, params:Object, query:Object, path:string}}
|
|
2182
|
-
*/
|
|
2183
|
-
this.resolve = function(target) {
|
|
2184
|
-
if(Validator.isJson(target)) {
|
|
2185
|
-
const route = $routesByName[target.name];
|
|
2186
|
-
if(!route) {
|
|
2187
|
-
throw new RouterError(`Route not found for name: ${target.name}`);
|
|
2188
|
-
}
|
|
2189
|
-
return {
|
|
2190
|
-
route,
|
|
2191
|
-
params: target.params,
|
|
2192
|
-
query: target.query,
|
|
2193
|
-
path: route.url({ ...target })
|
|
2194
|
-
};
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
const [urlPath, urlQuery] = target.split('?');
|
|
2198
|
-
const path = '/'+trim(urlPath, '/');
|
|
2199
|
-
let routeFound = null, params;
|
|
2200
|
-
|
|
2201
|
-
for(const route of $routes) {
|
|
2202
|
-
params = route.match(path);
|
|
2203
|
-
if(params) {
|
|
2204
|
-
routeFound = route;
|
|
2205
|
-
break;
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
if(!routeFound) {
|
|
2209
|
-
throw new RouterError(`Route not found for url: ${urlPath}`);
|
|
2210
|
-
}
|
|
2211
|
-
const queryParams = {};
|
|
2212
|
-
if(urlQuery) {
|
|
2213
|
-
const queries = new URLSearchParams(urlQuery).entries();
|
|
2214
|
-
for (const [key, value] of queries) {
|
|
2215
|
-
queryParams[key] = value;
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
return { route: routeFound, params, query: queryParams, path: target };
|
|
2220
|
-
};
|
|
2221
|
-
|
|
2222
|
-
/**
|
|
2223
|
-
*
|
|
2224
|
-
* @param {Function} listener
|
|
2225
|
-
* @returns {(function(): void)|*}
|
|
2226
|
-
*/
|
|
2227
|
-
this.subscribe = function(listener) {
|
|
2228
|
-
if(!Validator.isFunction(listener)) {
|
|
2229
|
-
throw new RouterError('Listener must be a function');
|
|
2230
|
-
}
|
|
2231
|
-
$listeners.push(listener);
|
|
2232
|
-
return () => {
|
|
2233
|
-
$listeners.splice($listeners.indexOf(listener), 1);
|
|
2234
|
-
};
|
|
2235
|
-
};
|
|
2236
|
-
|
|
2237
|
-
/**
|
|
2238
|
-
*
|
|
2239
|
-
* @param {Route} route
|
|
2240
|
-
* @param {Object} params
|
|
2241
|
-
* @param {Object} query
|
|
2242
|
-
* @param {string} path
|
|
2243
|
-
*/
|
|
2244
|
-
this.handleRouteChange = function(route, params, query, path) {
|
|
2245
|
-
$currentState.route = route;
|
|
2246
|
-
$currentState.params = params;
|
|
2247
|
-
$currentState.query = query;
|
|
2248
|
-
$currentState.path = path;
|
|
2249
|
-
|
|
2250
|
-
console.log($currentState.query);
|
|
2251
|
-
const middlewares = [...route.middlewares(), trigger];
|
|
2252
|
-
let currentIndex = 0;
|
|
2253
|
-
const request = { ...$currentState };
|
|
2254
|
-
|
|
2255
|
-
const next = (editableRequest) => {
|
|
2256
|
-
currentIndex++;
|
|
2257
|
-
if(currentIndex >= middlewares.length) {
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
return middlewares[currentIndex](editableRequest || request, next);
|
|
2261
|
-
};
|
|
2262
|
-
return middlewares[currentIndex](request, next);
|
|
2263
|
-
};
|
|
2264
|
-
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
Router.routers = {};
|
|
2268
|
-
|
|
2269
|
-
/**
|
|
2270
|
-
*
|
|
2271
|
-
* @param {{mode: 'memory'|'history'|'hash', name:string, entry: string}} options
|
|
2272
|
-
* @param {Function} callback
|
|
2273
|
-
* @param {Element} container
|
|
2274
|
-
*/
|
|
2275
|
-
Router.create = function(options, callback) {
|
|
2276
|
-
if(!Validator.isFunction(callback)) {
|
|
2277
|
-
DebugManager.error('Router', 'Callback must be a function', e);
|
|
2278
|
-
throw new RouterError('Callback must be a function');
|
|
2279
|
-
}
|
|
2280
|
-
const router = new Router(options);
|
|
2281
|
-
Router.routers[options.name || DEFAULT_ROUTER_NAME] = router;
|
|
2282
|
-
callback(router);
|
|
2283
|
-
|
|
2284
|
-
router.init(options.entry);
|
|
2285
|
-
|
|
2286
|
-
return {
|
|
2287
|
-
mount: (container) => {
|
|
2288
|
-
if(Validator.isString(container)) {
|
|
2289
|
-
const mountContainer = document.querySelector(container);
|
|
2290
|
-
if(!mountContainer) {
|
|
2291
|
-
throw new RouterError(`Container not found for selector: ${container}`);
|
|
2292
|
-
}
|
|
2293
|
-
container = mountContainer;
|
|
2294
|
-
} else if(!Validator.isElement(container)) {
|
|
2295
|
-
throw new RouterError('Container must be a string or an Element');
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
RouterComponent(router, container);
|
|
2299
|
-
}
|
|
2300
|
-
};
|
|
2301
|
-
};
|
|
2302
|
-
|
|
2303
|
-
Router.get = function(name) {
|
|
2304
|
-
return Router.routers[name || DEFAULT_ROUTER_NAME];
|
|
2305
|
-
};
|
|
2306
|
-
|
|
2307
|
-
function Link(attributes, children){
|
|
2308
|
-
const target = attributes.to || attributes.href;
|
|
2309
|
-
if(Validator.isString(target)) {
|
|
2310
|
-
const router = Router.get();
|
|
2311
|
-
return Link$1({ ...attributes, href: target}, children).nd.on.prevent.click(() => {
|
|
2312
|
-
router.push(target);
|
|
2313
|
-
});
|
|
2314
|
-
}
|
|
2315
|
-
const router = Router.get(target.router);
|
|
2316
|
-
if(!router) {
|
|
2317
|
-
throw new RouterError('Router not found "'+target.router+'" for link "'+target.name+'"');
|
|
2318
|
-
}
|
|
2319
|
-
const url = router.generateUrl(target.name, target.params, target.query);
|
|
2320
|
-
return Link$1({ ...attributes, href: url }, children).nd.on.prevent.click(() => {
|
|
2321
|
-
router.push(url);
|
|
2322
|
-
});
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
Link.blank = function(attributes, children){
|
|
2326
|
-
return Link$1({ ...attributes, target: '_blank'}, children);
|
|
2327
|
-
};
|
|
2328
|
-
|
|
2329
|
-
var router = /*#__PURE__*/Object.freeze({
|
|
2330
|
-
__proto__: null,
|
|
2331
|
-
Link: Link,
|
|
2332
|
-
Router: Router
|
|
2333
|
-
});
|
|
2334
|
-
|
|
2335
|
-
exports.ArgTypes = ArgTypes;
|
|
2336
|
-
exports.ElementCreator = ElementCreator;
|
|
2337
|
-
exports.HtmlElementWrapper = HtmlElementWrapper;
|
|
2338
|
-
exports.Observable = Observable;
|
|
2339
|
-
exports.Store = Store;
|
|
2340
|
-
exports.elements = elements;
|
|
2341
|
-
exports.router = router;
|
|
2342
|
-
exports.withValidation = withValidation;
|
|
2343
|
-
|
|
2344
|
-
return exports;
|
|
2345
|
-
|
|
2346
|
-
})({});
|