iobroker.webui 1.25.2 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/io-package.json +14 -14
- package/package.json +14 -14
- package/www/dist/frontend/common/Common.globals.d.ts +1 -1
- package/www/dist/frontend/common/Runtime.d.ts +2 -2
- package/www/dist/frontend/config/IobrokerWebuiSolutionExplorer.js +4 -4
- package/www/dist/frontend/generated/ScriptCommands.json +8 -3
- package/www/dist/frontend/helper/DialogHelper.js +7 -4
- package/www/dist/frontend/runtime/ScreenViewer.js +13 -16
- package/www/dist/frontend/scripting/IobrokerWebuiScriptSystem.js +2 -1
- package/www/index.html +1 -1
- package/www/node_modules/@adobe/css-tools/dist/index.mjs +1 -1
- package/www/node_modules/@iobroker/socket-client/dist/esm/AdminConnection.js +7 -4
- package/www/node_modules/@iobroker/socket-client/dist/esm/Connection.d.ts +82 -2
- package/www/node_modules/@iobroker/socket-client/dist/esm/Connection.js +266 -2
- package/www/node_modules/@node-projects/base-custom-webcomponent/dist/BaseCustomWebComponent.d.ts +2 -1
- package/www/node_modules/@node-projects/base-custom-webcomponent/dist/BaseCustomWebComponent.js +63 -53
- package/www/node_modules/@node-projects/base-custom-webcomponent/dist/Effect.js +2 -0
- package/www/node_modules/@node-projects/base-custom-webcomponent/dist/SignalDecorator.js +16 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/BaseCustomWebComponent.js +795 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/Debounce.js +19 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/DeclaritiveBaseCustomWebcomponent.js +176 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/DomHelper.js +96 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/HotModuleReplacement.js +125 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/LazyLoader.js +52 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/TouchContextMenu.js +20 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/TypedEvent.js +48 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/WeakArray.js +27 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/dist/index.js +9 -0
- package/www/node_modules/@node-projects/propertygrid.webcomponent/node_modules/@node-projects/base-custom-webcomponent/jsr.json +5 -0
- package/www/node_modules/@node-projects/web-component-designer/dist/elements/helper/getBoxQuads.js +14 -15
- package/www/node_modules/@node-projects/web-component-designer/dist/elements/services/ServiceContainer.js +1 -0
- package/www/node_modules/@node-projects/web-component-designer/dist/elements/services/propertiesService/services/NativeElementsPropertiesService.js +37 -0
- package/www/node_modules/@node-projects/web-component-designer/dist/elements/widgets/designerView/designerCanvas.js +2 -0
- package/www/node_modules/@node-projects/web-component-designer/dist/elements/widgets/designerView/extensions/buttons/OptionsContextMenuButton.js +29 -4
- package/www/node_modules/es-module-shims/dist/es-module-shims.js +754 -628
- package/www/node_modules/long/index.js +360 -246
- package/www/node_modules/long/umd/index.js +1585 -1395
- package/www/node_modules/wunderbaum/dist/wunderbaum.css +1 -1
- package/www/node_modules/wunderbaum/dist/wunderbaum.esm.min.js +35 -35
|
@@ -28,6 +28,10 @@ const ADAPTERS = ['material', 'echarts', 'vis'];
|
|
|
28
28
|
export class Connection {
|
|
29
29
|
constructor(props) {
|
|
30
30
|
this.props = this.applyDefaultProps(props);
|
|
31
|
+
// Create unique ID of this instance
|
|
32
|
+
this.connId = `${this.props.name ? `${this.props.name}-` : ''}${Math.round(Math.random() * 1000000)
|
|
33
|
+
.toString()
|
|
34
|
+
.padStart(6, '0')}`;
|
|
31
35
|
this.waitForSocketLib()
|
|
32
36
|
.then(() => this.startSocket())
|
|
33
37
|
.catch(e => {
|
|
@@ -51,6 +55,8 @@ export class Connection {
|
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
props;
|
|
58
|
+
connId;
|
|
59
|
+
lastAccessToken = null;
|
|
54
60
|
ignoreState = '';
|
|
55
61
|
connected = false;
|
|
56
62
|
subscribed = false;
|
|
@@ -83,7 +89,8 @@ export class Connection {
|
|
|
83
89
|
_instanceSubscriptions = {};
|
|
84
90
|
/** Cache for server requests */
|
|
85
91
|
_promises = {};
|
|
86
|
-
_authTimer;
|
|
92
|
+
_authTimer = null;
|
|
93
|
+
_refreshTimer = null;
|
|
87
94
|
_systemConfig;
|
|
88
95
|
/** The "system.config" object */
|
|
89
96
|
get systemConfig() {
|
|
@@ -184,6 +191,20 @@ export class Connection {
|
|
|
184
191
|
token: this.props.token,
|
|
185
192
|
});
|
|
186
193
|
this._socket.on('connect', noTimeout => {
|
|
194
|
+
// Listen for messages from other tabs
|
|
195
|
+
window.addEventListener('storage', this.onAccessTokenUpdated);
|
|
196
|
+
const tokens = Connection.readTokens();
|
|
197
|
+
if (tokens && !tokens.owner) {
|
|
198
|
+
// Take the ownership of the token
|
|
199
|
+
const now = Date.now();
|
|
200
|
+
this.saveTokens({
|
|
201
|
+
access_token: tokens.access_token,
|
|
202
|
+
refresh_token: tokens.refresh_token,
|
|
203
|
+
expires_in: Math.round((tokens.expires_in.getTime() - now) / 1000),
|
|
204
|
+
refresh_token_expires_in: Math.round((tokens.refresh_token_expires_in.getTime() - now) / 1000),
|
|
205
|
+
token_type: 'Bearer',
|
|
206
|
+
}, tokens.stayLoggedIn);
|
|
207
|
+
}
|
|
187
208
|
this.onReadyDone = false;
|
|
188
209
|
// If the user is not admin, it takes some time to install the handlers, because all rights must be checked
|
|
189
210
|
if (noTimeout !== true) {
|
|
@@ -313,9 +334,252 @@ export class Connection {
|
|
|
313
334
|
}
|
|
314
335
|
this._subscribe(true);
|
|
315
336
|
this.onConnectionHandlers.forEach(cb => cb(true));
|
|
337
|
+
this.checkAccessTokenExpire();
|
|
316
338
|
}
|
|
317
339
|
this._waitForFirstConnectionPromise.resolve();
|
|
318
340
|
}
|
|
341
|
+
static readTokens() {
|
|
342
|
+
let tokenString = window.sessionStorage.getItem('iob_tokens');
|
|
343
|
+
const stayLoggedIn = !tokenString;
|
|
344
|
+
if (!tokenString) {
|
|
345
|
+
tokenString = window.localStorage.getItem('iob_tokens');
|
|
346
|
+
}
|
|
347
|
+
if (!tokenString) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
const [refresh_token, refresh_token_expires_in, access_token, expires_in, owner] = tokenString.split(';');
|
|
351
|
+
const refreshExpires = new Date(refresh_token_expires_in);
|
|
352
|
+
if (refreshExpires.getTime() < Date.now()) {
|
|
353
|
+
// refresh token expired
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
refresh_token,
|
|
358
|
+
refresh_token_expires_in: refreshExpires,
|
|
359
|
+
access_token,
|
|
360
|
+
expires_in: new Date(expires_in),
|
|
361
|
+
owner,
|
|
362
|
+
stayLoggedIn,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
static saveTokensStatic(data, stayLoggedIn, owner) {
|
|
366
|
+
const tokenStr = `${data.refresh_token};${new Date(Date.now() + data.refresh_token_expires_in * 1000).toISOString()};${data.access_token};${new Date(Date.now() + data.expires_in * 1000).toISOString()}${owner ? `;${owner}` : ''}`;
|
|
367
|
+
if (stayLoggedIn) {
|
|
368
|
+
window.localStorage.setItem('iob_tokens', tokenStr);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
window.sessionStorage.setItem('iob_tokens', tokenStr);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
saveTokens(data, stayLoggedIn) {
|
|
375
|
+
Connection.saveTokensStatic(data, stayLoggedIn, this.connId);
|
|
376
|
+
}
|
|
377
|
+
static deleteTokensStatic() {
|
|
378
|
+
window.localStorage.removeItem('iob_tokens');
|
|
379
|
+
window.sessionStorage.removeItem('iob_tokens');
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Destroy tokens if they were created by this connection if they expired or invalid
|
|
383
|
+
*
|
|
384
|
+
* @param stayLoggedIn if stored in localStorage or in sessionStorage
|
|
385
|
+
* @param logout if logout is requested
|
|
386
|
+
*/
|
|
387
|
+
deleteTokens(stayLoggedIn, logout) {
|
|
388
|
+
const tokens = Connection.readTokens();
|
|
389
|
+
if (tokens) {
|
|
390
|
+
if (logout) {
|
|
391
|
+
Connection.deleteTokensStatic();
|
|
392
|
+
}
|
|
393
|
+
else if (tokens.stayLoggedIn === stayLoggedIn && tokens.owner === this.connId) {
|
|
394
|
+
if (tokens.stayLoggedIn) {
|
|
395
|
+
window.localStorage.removeItem('iob_tokens');
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
window.sessionStorage.removeItem('iob_tokens');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
onAccessTokenUpdated = (event) => {
|
|
404
|
+
// Storage event is only fired in other tabs/windows (or iframes) of the same origin when the localStorage (or sessionStorage) is modified, and not in the same window where the change was made.
|
|
405
|
+
if (event.key === 'iob_tokens') {
|
|
406
|
+
const tokens = Connection.readTokens();
|
|
407
|
+
if (tokens) {
|
|
408
|
+
console.log(`Tab ${this.connId} received updated token: ${tokens.access_token}`);
|
|
409
|
+
this.updateTokenExpiration(tokens.access_token);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
updateTokenExpiration(accessToken) {
|
|
414
|
+
// This connection is not a token owner, so only read the new access token and inform the server
|
|
415
|
+
if (this.lastAccessToken !== accessToken) {
|
|
416
|
+
this.lastAccessToken = accessToken;
|
|
417
|
+
this._socket.emit('updateTokenExpiration', accessToken, (err, success) => {
|
|
418
|
+
if (err) {
|
|
419
|
+
console.error(`Cannot update expiration time: ${err}`);
|
|
420
|
+
window.location.reload();
|
|
421
|
+
}
|
|
422
|
+
else if (!success) {
|
|
423
|
+
console.error('Cannot update expiration time');
|
|
424
|
+
window.location.reload();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
this.checkAccessTokenExpire();
|
|
429
|
+
}
|
|
430
|
+
refreshTokens(tokenStructure, takeOwnership) {
|
|
431
|
+
if (!tokenStructure) {
|
|
432
|
+
// Refresh the page, as we cannot refresh the token
|
|
433
|
+
setTimeout(() => window.location.reload(), 500);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (takeOwnership || !tokenStructure.owner || tokenStructure.owner === this.connId) {
|
|
437
|
+
if (this.acquireTokenLock()) {
|
|
438
|
+
// Access token will expire soon => Send authentication again
|
|
439
|
+
fetch('./oauth/token', {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
headers: {
|
|
442
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
443
|
+
},
|
|
444
|
+
body: `grant_type=refresh_token&refresh_token=${tokenStructure.refresh_token}&client_id=ioBroker&stayloggedin=${tokenStructure.stayLoggedIn}`,
|
|
445
|
+
})
|
|
446
|
+
.then(response => {
|
|
447
|
+
if (response.ok) {
|
|
448
|
+
return response.json();
|
|
449
|
+
}
|
|
450
|
+
throw new Error('Cannot refresh access token');
|
|
451
|
+
})
|
|
452
|
+
.then((data) => {
|
|
453
|
+
if (data.access_token) {
|
|
454
|
+
this.saveTokens(data, tokenStructure.stayLoggedIn);
|
|
455
|
+
this.releaseTokenLock();
|
|
456
|
+
this.updateTokenExpiration(data.access_token);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
throw new Error('Cannot get access token');
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
.catch(err => {
|
|
463
|
+
this.releaseTokenLock();
|
|
464
|
+
this.deleteTokens(tokenStructure.stayLoggedIn);
|
|
465
|
+
console.error(err);
|
|
466
|
+
window.location.reload();
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
// Someone else is updating the token, so wait for the next check
|
|
471
|
+
this.checkAccessTokenExpire();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
else if (this.lastAccessToken !== tokenStructure.access_token) {
|
|
475
|
+
this.updateTokenExpiration(tokenStructure.access_token);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Attempts to acquire the semaphore lock.
|
|
480
|
+
*
|
|
481
|
+
* @returns True if the lock was acquired successfully; otherwise, false.
|
|
482
|
+
*/
|
|
483
|
+
acquireTokenLock() {
|
|
484
|
+
const now = Date.now();
|
|
485
|
+
const lock = localStorage.getItem('iob_token_semaphore');
|
|
486
|
+
if (lock) {
|
|
487
|
+
try {
|
|
488
|
+
const lockData = JSON.parse(lock);
|
|
489
|
+
// If the current lock is still valid, we cannot acquire the lock.
|
|
490
|
+
if (now < lockData.expiry) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
// Otherwise, the lock has expired and we can override it.
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
// ignore
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Create a new lock with expiry 10 seconds from now.
|
|
500
|
+
const newLock = {
|
|
501
|
+
connId: this.connId,
|
|
502
|
+
expiry: now + 10 * 1000, // 10 seconds in milliseconds
|
|
503
|
+
};
|
|
504
|
+
localStorage.setItem('iob_token_semaphore', JSON.stringify(newLock));
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
/** Releases the semaphore lock if it's owned by the given connection ID. */
|
|
508
|
+
releaseTokenLock() {
|
|
509
|
+
const lock = localStorage.getItem('iob_token_semaphore');
|
|
510
|
+
if (lock) {
|
|
511
|
+
try {
|
|
512
|
+
const lockData = JSON.parse(lock);
|
|
513
|
+
// Only remove the lock if it's owned by the current connection.
|
|
514
|
+
if (lockData.connId === this.connId) {
|
|
515
|
+
localStorage.removeItem('iob_token_semaphore');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
// If parsing fails, remove the lock just in case.
|
|
520
|
+
localStorage.removeItem('iob_token_semaphore');
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
checkAccessTokenExpire() {
|
|
525
|
+
if (this._refreshTimer) {
|
|
526
|
+
clearTimeout(this._refreshTimer);
|
|
527
|
+
this._refreshTimer = null;
|
|
528
|
+
}
|
|
529
|
+
if (this.isSecure) {
|
|
530
|
+
const tokens = Connection.readTokens();
|
|
531
|
+
if (tokens) {
|
|
532
|
+
const accessExpireInUnixMs = tokens.expires_in.getTime();
|
|
533
|
+
// Check if the access token expires in the next 30 seconds
|
|
534
|
+
if (accessExpireInUnixMs < Date.now() + 30_000) {
|
|
535
|
+
const takeOwnership = accessExpireInUnixMs < Date.now() + 5_500;
|
|
536
|
+
if (!tokens.refresh_token) {
|
|
537
|
+
// Refresh the page, as we cannot refresh the token
|
|
538
|
+
setTimeout(() => window.location.reload(), Date.now() > accessExpireInUnixMs ? 500 : accessExpireInUnixMs - Date.now());
|
|
539
|
+
}
|
|
540
|
+
else if (tokens.owner === this.connId ||
|
|
541
|
+
// We gave 25 seconds to the owner to update the token, and now we will do it and take the ownership
|
|
542
|
+
takeOwnership) {
|
|
543
|
+
// Handle token expiration if the connection is the owner of the token
|
|
544
|
+
if (this.props.tokenTimeoutHandler) {
|
|
545
|
+
// Asc if the user wants to stay logged in
|
|
546
|
+
void this.props.tokenTimeoutHandler(accessExpireInUnixMs).then(prolong => {
|
|
547
|
+
if (prolong) {
|
|
548
|
+
this.refreshTokens(tokens, takeOwnership);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
// Refresh the page, as we cannot refresh the token
|
|
552
|
+
setTimeout(() => window.location.reload(), Date.now() > accessExpireInUnixMs ? 500 : accessExpireInUnixMs - Date.now());
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
this.refreshTokens(tokens, takeOwnership);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
else if (this.lastAccessToken !== tokens.access_token) {
|
|
561
|
+
// The connection is not the owner, so just check if access_token changed, so inform the server about it
|
|
562
|
+
this.refreshTokens(tokens);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
// What 3 seconds and check again, maybe the owner connection will update the token
|
|
566
|
+
this._refreshTimer = setTimeout(() => {
|
|
567
|
+
this._refreshTimer = null;
|
|
568
|
+
this.checkAccessTokenExpire();
|
|
569
|
+
}, 3_000);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this._refreshTimer = setTimeout(() => {
|
|
574
|
+
this._refreshTimer = null;
|
|
575
|
+
this.checkAccessTokenExpire();
|
|
576
|
+
}, accessExpireInUnixMs - Date.now() - 30_000 > 120_000
|
|
577
|
+
? 120_000
|
|
578
|
+
: accessExpireInUnixMs - Date.now() - 30_000);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
319
583
|
/**
|
|
320
584
|
* Checks if running in ioBroker cloud
|
|
321
585
|
*/
|
|
@@ -355,7 +619,7 @@ export class Connection {
|
|
|
355
619
|
reject(err);
|
|
356
620
|
}
|
|
357
621
|
else {
|
|
358
|
-
resolve(acl);
|
|
622
|
+
resolve(acl || null);
|
|
359
623
|
}
|
|
360
624
|
});
|
|
361
625
|
},
|
package/www/node_modules/@node-projects/base-custom-webcomponent/dist/BaseCustomWebComponent.d.ts
CHANGED
|
@@ -49,12 +49,13 @@ export declare class BaseCustomWebComponentNoAttachedTemplate extends HTMLElemen
|
|
|
49
49
|
* ==> this could also be nested
|
|
50
50
|
*
|
|
51
51
|
*/
|
|
52
|
-
protected _bindingsParse(node?: Node, removeAttributes?: boolean, host?: any, context?: any): void;
|
|
52
|
+
protected _bindingsParse(node?: Node, removeAttributes?: boolean, host?: any, context?: any, useSignals?: boolean): void;
|
|
53
53
|
private _bindingsInternalParse;
|
|
54
54
|
private _textFragmentBinding;
|
|
55
55
|
private _bindingRunEval;
|
|
56
56
|
private _bindingRunEvalInt;
|
|
57
57
|
private _bindingConditional;
|
|
58
|
+
oldValuesSymbol: symbol;
|
|
58
59
|
private _bindingRepeat;
|
|
59
60
|
private _bindingSetNodeValue;
|
|
60
61
|
private _bindingSetElementCssValue;
|
package/www/node_modules/@node-projects/base-custom-webcomponent/dist/BaseCustomWebComponent.js
CHANGED
|
@@ -137,10 +137,10 @@ export class BaseCustomWebComponentNoAttachedTemplate extends HTMLElement {
|
|
|
137
137
|
* ==> this could also be nested
|
|
138
138
|
*
|
|
139
139
|
*/
|
|
140
|
-
_bindingsParse(node, removeAttributes = false, host = null, context = null) {
|
|
141
|
-
this._bindingsInternalParse(node, null, removeAttributes, host, context);
|
|
140
|
+
_bindingsParse(node, removeAttributes = false, host = null, context = null, useSignals = false) {
|
|
141
|
+
this._bindingsInternalParse(node, null, removeAttributes, host, context, useSignals);
|
|
142
142
|
}
|
|
143
|
-
_bindingsInternalParse(startNode, repeatBindingItems, removeAttributes, host, context) {
|
|
143
|
+
_bindingsInternalParse(startNode, repeatBindingItems, removeAttributes, host, context, useSignals = false) {
|
|
144
144
|
if (!this._bindings)
|
|
145
145
|
this._bindings = [];
|
|
146
146
|
if (!startNode)
|
|
@@ -384,64 +384,74 @@ export class BaseCustomWebComponentNoAttachedTemplate extends HTMLElement {
|
|
|
384
384
|
console.warn(error.message, 'Failed to bind Conditional to expression "' + expression + '"', this, node);
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
|
+
oldValuesSymbol = Symbol('oldValuesSymbol');
|
|
387
388
|
_bindingRepeat(node, bindingProperty, bindingIndexName, expression, callback, repeatBindingItems, elementsCache, host, context) {
|
|
388
389
|
try {
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
390
|
+
let values = this._bindingRunEval(expression, repeatBindingItems, null, host, context);
|
|
391
|
+
if (values && !Array.isArray(values))
|
|
392
|
+
values = [...values];
|
|
393
|
+
const oldValue = node[this.oldValuesSymbol];
|
|
394
|
+
if (oldValue !== values && (oldValue?.length !== values?.length || (values.some((x, i) => x !== oldValue?.[i])))) {
|
|
395
|
+
if (Array.isArray(values))
|
|
396
|
+
node[this.oldValuesSymbol] = [...values];
|
|
393
397
|
else
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
398
|
+
node[this.oldValuesSymbol] = values;
|
|
399
|
+
if (callback) {
|
|
400
|
+
if (callback.startsWith('[[') && callback.endsWith(']]'))
|
|
401
|
+
callback = callback.substring(2, callback.length - 2);
|
|
402
|
+
else
|
|
403
|
+
callback = "this." + callback;
|
|
404
|
+
}
|
|
405
|
+
for (let c of elementsCache) {
|
|
406
|
+
if (c.parentNode) {
|
|
407
|
+
let intRepeatBindingItems = [];
|
|
408
|
+
intRepeatBindingItems.push({ name: 'nodes', item: [c] });
|
|
409
|
+
intRepeatBindingItems.push({ name: 'callbackType', item: 'remove' });
|
|
410
|
+
this._bindingRunEval(callback, intRepeatBindingItems, null, host, context);
|
|
411
|
+
const bnds = this._repeatBindings.get(c);
|
|
412
|
+
if (bnds?.length) {
|
|
413
|
+
for (const b of bnds) {
|
|
414
|
+
const idx = this._bindings.indexOf(b);
|
|
415
|
+
if (idx >= 0)
|
|
416
|
+
this._bindings.splice(idx, 1);
|
|
417
|
+
}
|
|
408
418
|
}
|
|
419
|
+
c.parentNode.removeChild(c);
|
|
409
420
|
}
|
|
410
|
-
c.parentNode.removeChild(c);
|
|
411
421
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
intRepeatBindingItems
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
nds
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
422
|
+
elementsCache.length = 0;
|
|
423
|
+
if (values) {
|
|
424
|
+
if (!this._repeatBindings)
|
|
425
|
+
this._repeatBindings = new WeakMap();
|
|
426
|
+
//todo -> copy values to compare and only generate new controls...
|
|
427
|
+
let i = 0;
|
|
428
|
+
for (let e of values) {
|
|
429
|
+
let intRepeatBindingItems = [];
|
|
430
|
+
if (repeatBindingItems)
|
|
431
|
+
intRepeatBindingItems = repeatBindingItems.slice();
|
|
432
|
+
intRepeatBindingItems.push({ name: bindingProperty, item: e });
|
|
433
|
+
intRepeatBindingItems.push({ name: bindingIndexName, item: i });
|
|
434
|
+
let nd = node.content.cloneNode(true);
|
|
435
|
+
elementsCache.push(...nd.children);
|
|
436
|
+
const bndCount = this._bindings.length;
|
|
437
|
+
this._bindingsInternalParse(nd, intRepeatBindingItems, true, host, context);
|
|
438
|
+
this._repeatBindings.set(nd.children[0], this._bindings.toSpliced(0, bndCount));
|
|
439
|
+
if (callback) {
|
|
440
|
+
intRepeatBindingItems.push({ name: 'nodes', item: [...nd.children] });
|
|
441
|
+
intRepeatBindingItems.push({ name: 'callbackType', item: 'create' });
|
|
442
|
+
let nds = this._bindingRunEval(callback, intRepeatBindingItems, null, host, context);
|
|
443
|
+
if (nds === undefined)
|
|
444
|
+
nds = nd.children;
|
|
445
|
+
if (nds) {
|
|
446
|
+
for (let n of Array.from(nds))
|
|
447
|
+
node.parentNode.insertBefore(n, node);
|
|
448
|
+
}
|
|
439
449
|
}
|
|
450
|
+
else {
|
|
451
|
+
node.parentNode.insertBefore(nd, node);
|
|
452
|
+
}
|
|
453
|
+
i++;
|
|
440
454
|
}
|
|
441
|
-
else {
|
|
442
|
-
node.parentNode.insertBefore(nd, node);
|
|
443
|
-
}
|
|
444
|
-
i++;
|
|
445
455
|
}
|
|
446
456
|
}
|
|
447
457
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//https://github.com/proposal-signals/signal-polyfill
|
|
2
|
+
import { Signal } from "signal-polyfill";
|
|
3
|
+
export function signal(target) {
|
|
4
|
+
const { get } = target;
|
|
5
|
+
return {
|
|
6
|
+
get() {
|
|
7
|
+
return get.call(this).get();
|
|
8
|
+
},
|
|
9
|
+
set(value) {
|
|
10
|
+
get.call(this).set(value);
|
|
11
|
+
},
|
|
12
|
+
init(value) {
|
|
13
|
+
return new Signal.State(value);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|