haori 0.6.0 → 0.6.2
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.ja.md +3 -1
- package/README.md +3 -1
- package/dist/haori.cjs.js +13 -13
- package/dist/haori.cjs.js.map +1 -1
- package/dist/haori.es.js +2603 -1778
- package/dist/haori.es.js.map +1 -1
- package/dist/haori.iife.js +13 -13
- package/dist/haori.iife.js.map +1 -1
- package/dist/index.d.ts +242 -1
- package/dist/package.json +1 -1
- package/dist/src/core.d.ts +158 -1
- package/dist/src/core.d.ts.map +1 -1
- package/dist/src/core.js +647 -79
- package/dist/src/core.js.map +1 -1
- package/dist/src/event.d.ts +2 -2
- package/dist/src/event.js +2 -2
- package/dist/src/event_dispatcher.d.ts.map +1 -1
- package/dist/src/event_dispatcher.js.map +1 -1
- package/dist/src/expression.d.ts +49 -3
- package/dist/src/expression.d.ts.map +1 -1
- package/dist/src/expression.js +148 -33
- package/dist/src/expression.js.map +1 -1
- package/dist/src/form.d.ts.map +1 -1
- package/dist/src/form.js +2 -1
- package/dist/src/form.js.map +1 -1
- package/dist/src/fragment.d.ts +84 -0
- package/dist/src/fragment.d.ts.map +1 -1
- package/dist/src/fragment.js +490 -75
- package/dist/src/fragment.js.map +1 -1
- package/dist/src/observer.d.ts.map +1 -1
- package/dist/src/observer.js +0 -7
- package/dist/src/observer.js.map +1 -1
- package/dist/src/procedure.d.ts.map +1 -1
- package/dist/src/procedure.js +1 -3
- package/dist/src/procedure.js.map +1 -1
- package/dist/tests/core.test.js +288 -4
- package/dist/tests/core.test.js.map +1 -1
- package/dist/tests/data-derive.test.js +159 -0
- package/dist/tests/data-derive.test.js.map +1 -1
- package/dist/tests/evaluation-profile.test.d.ts +2 -0
- package/dist/tests/evaluation-profile.test.d.ts.map +1 -0
- package/dist/tests/evaluation-profile.test.js +92 -0
- package/dist/tests/evaluation-profile.test.js.map +1 -0
- package/dist/tests/expression.test.js +35 -1
- package/dist/tests/expression.test.js.map +1 -1
- package/dist/tests/fragment.test.js +31 -0
- package/dist/tests/fragment.test.js.map +1 -1
- package/package.json +1 -1
package/dist/src/core.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* アプリケーションの中心的な機能を提供します。
|
|
6
6
|
*/
|
|
7
7
|
import Env from './env';
|
|
8
|
+
import Dev from './dev';
|
|
8
9
|
import Expression from './expression';
|
|
9
10
|
import Form from './form';
|
|
10
11
|
import Fragment, { ElementFragment, TextFragment } from './fragment';
|
|
@@ -296,20 +297,80 @@ class Core {
|
|
|
296
297
|
if (!fragment) {
|
|
297
298
|
return Promise.resolve();
|
|
298
299
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
300
|
+
return Core.initializeElementFragment(fragment, false);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* 新規 each 行を局所初期化します。
|
|
304
|
+
* 既存 scan の属性順序を保ちつつ、Fragment 木を直接たどります。
|
|
305
|
+
*
|
|
306
|
+
* @param fragment 新規挿入された行フラグメント
|
|
307
|
+
* @returns 初期化完了の Promise
|
|
308
|
+
*/
|
|
309
|
+
static initializeFreshEachRow(fragment) {
|
|
310
|
+
return Core.initializeElementFragment(fragment, true).then(() => {
|
|
311
|
+
if (Core.needsScheduledEvaluateAll(fragment)) {
|
|
312
|
+
Core.scheduleEvaluateAll(fragment);
|
|
308
313
|
}
|
|
309
|
-
|
|
310
|
-
|
|
314
|
+
return undefined;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* ElementFragment とその子孫を初期化します。
|
|
319
|
+
*
|
|
320
|
+
* @param fragment 対象フラグメント
|
|
321
|
+
* @param stopAtEach true の場合、data-each 要素では通常再帰を止める
|
|
322
|
+
* @returns 初期化完了の Promise
|
|
323
|
+
*/
|
|
324
|
+
static initializeElementFragment(fragment, stopAtEach) {
|
|
325
|
+
Core.syncMountedState(fragment);
|
|
326
|
+
if (stopAtEach && fragment.isFreshInitializationSkippable()) {
|
|
327
|
+
return Promise.resolve();
|
|
328
|
+
}
|
|
329
|
+
return Core.initializeElementAttributes(fragment).then(() => {
|
|
330
|
+
if (Core.shouldSkipChildInitialization(fragment, stopAtEach)) {
|
|
331
|
+
Core.refreshDerivedSubtreeSignature(fragment);
|
|
332
|
+
return undefined;
|
|
311
333
|
}
|
|
334
|
+
const childPromises = [];
|
|
335
|
+
fragment.getChildren().forEach(child => {
|
|
336
|
+
if (child instanceof ElementFragment) {
|
|
337
|
+
childPromises.push(Core.initializeElementFragment(child, stopAtEach));
|
|
338
|
+
}
|
|
339
|
+
else if (child instanceof TextFragment) {
|
|
340
|
+
childPromises.push(Core.evaluateText(child));
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
return Promise.all(childPromises).then(() => {
|
|
344
|
+
Core.refreshDerivedSubtreeSignature(fragment);
|
|
345
|
+
return undefined;
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* 要素初期化時の mounted 状態を同期します。
|
|
351
|
+
*
|
|
352
|
+
* @param fragment 対象フラグメント
|
|
353
|
+
*/
|
|
354
|
+
static syncMountedState(fragment) {
|
|
355
|
+
const parent = fragment.getParent();
|
|
356
|
+
if (parent?.isMounted()) {
|
|
357
|
+
fragment.setMounted(true);
|
|
358
|
+
return;
|
|
312
359
|
}
|
|
360
|
+
const target = fragment.getTarget();
|
|
361
|
+
if (target.parentNode && document.body.contains(target)) {
|
|
362
|
+
fragment.setMounted(true);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
fragment.setMounted(false);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* scan と fresh clone 初期化で共有する属性初期化を行います。
|
|
369
|
+
*
|
|
370
|
+
* @param fragment 対象フラグメント
|
|
371
|
+
* @returns 属性初期化完了の Promise
|
|
372
|
+
*/
|
|
373
|
+
static initializeElementAttributes(fragment) {
|
|
313
374
|
let attributeChain = Promise.resolve();
|
|
314
375
|
const processedAttributes = new Set();
|
|
315
376
|
for (const suffix of Core.PRIORITY_ATTRIBUTE_SUFFIXES) {
|
|
@@ -338,28 +399,25 @@ class Core {
|
|
|
338
399
|
processedAttributes.add(name);
|
|
339
400
|
}
|
|
340
401
|
}
|
|
341
|
-
return attributeChain
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return Promise.all(childPromises).then(() => undefined);
|
|
361
|
-
})
|
|
362
|
-
.then(() => undefined);
|
|
402
|
+
return attributeChain.then(() => undefined);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 子孫初期化をスキップすべきかどうかを返します。
|
|
406
|
+
*
|
|
407
|
+
* @param fragment 対象フラグメント
|
|
408
|
+
* @param stopAtEach true の場合、data-each 要素で通常再帰を止める
|
|
409
|
+
* @returns 子孫初期化をスキップするなら true
|
|
410
|
+
*/
|
|
411
|
+
static shouldSkipChildInitialization(fragment, stopAtEach) {
|
|
412
|
+
const condition = fragment.getAttribute(`${Env.prefix}if`);
|
|
413
|
+
if (fragment.hasAttribute(`${Env.prefix}if`) &&
|
|
414
|
+
(condition === false ||
|
|
415
|
+
condition === undefined ||
|
|
416
|
+
condition === null ||
|
|
417
|
+
Number.isNaN(condition))) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return stopAtEach && fragment.hasAttribute(`${Env.prefix}each`);
|
|
363
421
|
}
|
|
364
422
|
/**
|
|
365
423
|
* エレメントに属性を設定します。
|
|
@@ -380,6 +438,8 @@ class Core {
|
|
|
380
438
|
return fragment.setAliasedAttribute(name, aliasedAttributeName, value, fromObserver);
|
|
381
439
|
}
|
|
382
440
|
const promises = [];
|
|
441
|
+
let deriveChangedPromise = null;
|
|
442
|
+
let nextDeriveInputSignature = null;
|
|
383
443
|
switch (name) {
|
|
384
444
|
case `${Env.prefix}bind`: {
|
|
385
445
|
if (value === null) {
|
|
@@ -392,10 +452,14 @@ class Core {
|
|
|
392
452
|
break;
|
|
393
453
|
}
|
|
394
454
|
case `${Env.prefix}derive`:
|
|
395
|
-
|
|
455
|
+
nextDeriveInputSignature = Core.createDeriveInputSignature(fragment, value, fragment.getRawAttribute(`${Env.prefix}derive-name`));
|
|
456
|
+
deriveChangedPromise = Core.evaluateDerive(fragment, value, fragment.getRawAttribute(`${Env.prefix}derive-name`));
|
|
457
|
+
promises.push(deriveChangedPromise.then(() => undefined));
|
|
396
458
|
break;
|
|
397
459
|
case `${Env.prefix}derive-name`:
|
|
398
|
-
|
|
460
|
+
nextDeriveInputSignature = Core.createDeriveInputSignature(fragment, fragment.getRawAttribute(`${Env.prefix}derive`), value);
|
|
461
|
+
deriveChangedPromise = Core.evaluateDerive(fragment, fragment.getRawAttribute(`${Env.prefix}derive`), value);
|
|
462
|
+
promises.push(deriveChangedPromise.then(() => undefined));
|
|
399
463
|
break;
|
|
400
464
|
case `${Env.prefix}if`:
|
|
401
465
|
promises.push(Core.evaluateIf(fragment));
|
|
@@ -433,9 +497,14 @@ class Core {
|
|
|
433
497
|
}
|
|
434
498
|
return Promise.all(promises)
|
|
435
499
|
.then(() => {
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
return
|
|
500
|
+
if (deriveChangedPromise !== null) {
|
|
501
|
+
fragment.setDeriveInputSignature(nextDeriveInputSignature);
|
|
502
|
+
return deriveChangedPromise.then(changed => {
|
|
503
|
+
if (!changed) {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
return Core.reevaluateChildren(fragment);
|
|
507
|
+
});
|
|
439
508
|
}
|
|
440
509
|
return undefined;
|
|
441
510
|
})
|
|
@@ -628,28 +697,90 @@ class Core {
|
|
|
628
697
|
const hasDerive = fragment.hasAttribute(`${Env.prefix}derive`);
|
|
629
698
|
const hasIf = fragment.hasAttribute(`${Env.prefix}if`);
|
|
630
699
|
const hasEach = fragment.hasAttribute(`${Env.prefix}each`);
|
|
700
|
+
const deriveExpression = fragment.getRawAttribute(`${Env.prefix}derive`);
|
|
701
|
+
const deriveName = fragment.getRawAttribute(`${Env.prefix}derive-name`);
|
|
702
|
+
let shouldSkipDerivedSubtree = false;
|
|
703
|
+
let shouldRecordDerivedSubtreeSignature = false;
|
|
704
|
+
let nextDerivedSubtreeSignature = null;
|
|
705
|
+
if (!hasDerive && fragment.getDeriveSubtreeSignature() !== null) {
|
|
706
|
+
fragment.setDeriveSubtreeSignature(null);
|
|
707
|
+
}
|
|
708
|
+
if (!hasDerive && fragment.getDeriveInputSignature() !== null) {
|
|
709
|
+
fragment.setDeriveInputSignature(null);
|
|
710
|
+
}
|
|
631
711
|
if (hasDerive) {
|
|
632
|
-
|
|
712
|
+
const nextDeriveInputSignature = Core.createDeriveInputSignature(fragment, deriveExpression, deriveName);
|
|
713
|
+
if (nextDeriveInputSignature === null) {
|
|
714
|
+
if (fragment.getDeriveInputSignature() !== null) {
|
|
715
|
+
fragment.setDeriveInputSignature(null);
|
|
716
|
+
}
|
|
717
|
+
chain = chain.then(() => Core.evaluateDerive(fragment, deriveExpression, deriveName).then(() => undefined));
|
|
718
|
+
}
|
|
719
|
+
else if (fragment.getDeriveInputSignature() !== nextDeriveInputSignature) {
|
|
720
|
+
chain = chain.then(() => {
|
|
721
|
+
return Core.evaluateDerive(fragment, deriveExpression, deriveName).then(() => {
|
|
722
|
+
fragment.setDeriveInputSignature(nextDeriveInputSignature);
|
|
723
|
+
return undefined;
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
}
|
|
633
727
|
}
|
|
634
728
|
if (hasIf) {
|
|
635
729
|
chain = chain.then(() => Core.evaluateIf(fragment));
|
|
636
730
|
}
|
|
637
731
|
if (hasEach) {
|
|
732
|
+
if (fragment.getDeriveSubtreeSignature() !== null) {
|
|
733
|
+
fragment.setDeriveSubtreeSignature(null);
|
|
734
|
+
}
|
|
638
735
|
return chain.then(() => Core.evaluateEach(fragment));
|
|
639
736
|
}
|
|
640
737
|
if (hasIf) {
|
|
738
|
+
if (fragment.getDeriveSubtreeSignature() !== null) {
|
|
739
|
+
fragment.setDeriveSubtreeSignature(null);
|
|
740
|
+
}
|
|
641
741
|
return chain.then(() => undefined);
|
|
642
742
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
743
|
+
if (hasDerive) {
|
|
744
|
+
chain = chain.then(() => {
|
|
745
|
+
if (!Core.canSkipStableDerivedSubtree(fragment)) {
|
|
746
|
+
fragment.setDeriveSubtreeSignature(null);
|
|
747
|
+
Core.logDerivedSubtreeProfileSnapshot(fragment, 'skip-ineligible');
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
nextDerivedSubtreeSignature = Core.createDescendantBindingSignature(fragment, 'evaluateAll');
|
|
751
|
+
shouldRecordDerivedSubtreeSignature = true;
|
|
752
|
+
shouldSkipDerivedSubtree =
|
|
753
|
+
fragment.getDeriveSubtreeSignature() !== null &&
|
|
754
|
+
fragment.getDeriveSubtreeSignature() === nextDerivedSubtreeSignature;
|
|
755
|
+
Core.logDerivedSubtreeProfileSnapshot(fragment, shouldSkipDerivedSubtree ? 'skip-hit' : 'skip-miss');
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
return chain
|
|
759
|
+
.then(() => {
|
|
760
|
+
if (shouldSkipDerivedSubtree) {
|
|
761
|
+
return undefined;
|
|
647
762
|
}
|
|
648
|
-
|
|
649
|
-
|
|
763
|
+
const promises = [];
|
|
764
|
+
fragment.getChildren().forEach(child => {
|
|
765
|
+
if (child instanceof ElementFragment) {
|
|
766
|
+
if (Core.canSkipUnchangedNestedEach(child)) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
promises.push(Core.evaluateAll(child, skipFragments));
|
|
770
|
+
}
|
|
771
|
+
else if (child instanceof TextFragment) {
|
|
772
|
+
promises.push(Core.evaluateText(child));
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
return Promise.all(promises).then(() => undefined);
|
|
776
|
+
})
|
|
777
|
+
.then(() => {
|
|
778
|
+
if (shouldRecordDerivedSubtreeSignature &&
|
|
779
|
+
nextDerivedSubtreeSignature !== null) {
|
|
780
|
+
fragment.setDeriveSubtreeSignature(nextDerivedSubtreeSignature);
|
|
650
781
|
}
|
|
782
|
+
return undefined;
|
|
651
783
|
});
|
|
652
|
-
return chain.then(() => Promise.all(promises)).then(() => undefined);
|
|
653
784
|
}
|
|
654
785
|
/**
|
|
655
786
|
* data-derive / data-derive-name を評価し、子孫要素向けの派生値を更新します。
|
|
@@ -660,22 +791,32 @@ class Core {
|
|
|
660
791
|
* @returns Promise (評価完了時に解決)
|
|
661
792
|
*/
|
|
662
793
|
static evaluateDerive(fragment, deriveExpression = fragment.getRawAttribute(`${Env.prefix}derive`), deriveName = fragment.getRawAttribute(`${Env.prefix}derive-name`)) {
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
: '';
|
|
794
|
+
const previousDerivedBindingData = fragment.getRawDerivedBindingData();
|
|
795
|
+
const normalizedName = typeof deriveName === 'string' ? deriveName.trim() : '';
|
|
666
796
|
if (!deriveExpression || normalizedName === '') {
|
|
797
|
+
if (previousDerivedBindingData === null) {
|
|
798
|
+
return Promise.resolve(false);
|
|
799
|
+
}
|
|
667
800
|
fragment.setDerivedBindingData(null);
|
|
668
|
-
return Promise.resolve();
|
|
801
|
+
return Promise.resolve(true);
|
|
669
802
|
}
|
|
670
803
|
const result = Expression.evaluateDetailed(deriveExpression, fragment.getBindingData());
|
|
671
804
|
if (result.unresolvedReference) {
|
|
805
|
+
if (previousDerivedBindingData === null) {
|
|
806
|
+
return Promise.resolve(false);
|
|
807
|
+
}
|
|
672
808
|
fragment.setDerivedBindingData(null);
|
|
673
|
-
return Promise.resolve();
|
|
809
|
+
return Promise.resolve(true);
|
|
674
810
|
}
|
|
675
|
-
|
|
811
|
+
const nextDerivedBindingData = {
|
|
676
812
|
[normalizedName]: result.value,
|
|
677
|
-
}
|
|
678
|
-
|
|
813
|
+
};
|
|
814
|
+
if (Core.createBindingSignature(previousDerivedBindingData) ===
|
|
815
|
+
Core.createBindingSignature(nextDerivedBindingData)) {
|
|
816
|
+
return Promise.resolve(false);
|
|
817
|
+
}
|
|
818
|
+
fragment.setDerivedBindingData(nextDerivedBindingData);
|
|
819
|
+
return Promise.resolve(true);
|
|
679
820
|
}
|
|
680
821
|
/**
|
|
681
822
|
* テキストフラグメントを評価します。
|
|
@@ -739,6 +880,11 @@ class Core {
|
|
|
739
880
|
return Promise.reject(new Error('Invalid each attribute.'));
|
|
740
881
|
}
|
|
741
882
|
let template = fragment.getTemplate();
|
|
883
|
+
const keyArg = fragment.getAttribute(`${Env.prefix}each-key`);
|
|
884
|
+
const nextEachInputSignature = Core.createBindingSignature({
|
|
885
|
+
key: keyArg ? String(keyArg) : null,
|
|
886
|
+
items: data,
|
|
887
|
+
});
|
|
742
888
|
if (template === null) {
|
|
743
889
|
// テンプレートの作成
|
|
744
890
|
let found = false;
|
|
@@ -753,6 +899,7 @@ class Core {
|
|
|
753
899
|
}
|
|
754
900
|
// 最初のElementFragmentをテンプレートとして採用
|
|
755
901
|
template = child.clone();
|
|
902
|
+
Core.markFreshInitializationSkippable(template);
|
|
756
903
|
fragment.setTemplate(template);
|
|
757
904
|
found = true;
|
|
758
905
|
// 元のchildはchildrenから除外
|
|
@@ -767,9 +914,16 @@ class Core {
|
|
|
767
914
|
// TextNodeやCommentNodeはテンプレートにならないので無視
|
|
768
915
|
});
|
|
769
916
|
// テンプレートのunmount完了後にupdateDiffを実行
|
|
770
|
-
return this.updateDiff(fragment, data)
|
|
917
|
+
return this.updateDiff(fragment, data).then(() => {
|
|
918
|
+
fragment.setEachInputSignature(nextEachInputSignature);
|
|
919
|
+
});
|
|
771
920
|
}
|
|
772
|
-
|
|
921
|
+
if (fragment.getEachInputSignature() === nextEachInputSignature) {
|
|
922
|
+
return Promise.resolve();
|
|
923
|
+
}
|
|
924
|
+
return this.updateDiff(fragment, data).then(() => {
|
|
925
|
+
fragment.setEachInputSignature(nextEachInputSignature);
|
|
926
|
+
});
|
|
773
927
|
}
|
|
774
928
|
/**
|
|
775
929
|
* data-each 属性値を仕様に従って配列へ正規化します。
|
|
@@ -792,6 +946,307 @@ class Core {
|
|
|
792
946
|
Log.error('[Haori]', 'Invalid each attribute:', data);
|
|
793
947
|
return null;
|
|
794
948
|
}
|
|
949
|
+
/**
|
|
950
|
+
* nested data-each の入力が同値で、要素自身に他の動的要素が無い場合は
|
|
951
|
+
* evaluateAll の子走査を省略できるかどうかを返します。
|
|
952
|
+
*
|
|
953
|
+
* @param fragment 判定対象フラグメント
|
|
954
|
+
* @returns 省略可能なら true
|
|
955
|
+
*/
|
|
956
|
+
static canSkipUnchangedNestedEach(fragment) {
|
|
957
|
+
if (!fragment.hasAttribute(`${Env.prefix}each`)) {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
if (fragment.getEachInputSignature() === null) {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
const parent = fragment.getParent();
|
|
964
|
+
if (parent?.closestByAttribute(`${Env.prefix}derive`) ||
|
|
965
|
+
parent?.closestByAttribute(`${Env.prefix}derive-name`) ||
|
|
966
|
+
parent?.closestByAttribute(`${Env.prefix}if`) ||
|
|
967
|
+
parent?.closestByAttribute(`${Env.prefix}fetch`) ||
|
|
968
|
+
parent?.closestByAttribute(`${Env.prefix}import`)) {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
if (Core.hasNonEachDynamicElementState(fragment)) {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
const data = Core.resolveEachItems(fragment);
|
|
975
|
+
if (data === null) {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
const keyArg = fragment.getAttribute(`${Env.prefix}each-key`);
|
|
979
|
+
const nextEachInputSignature = Core.createBindingSignature({
|
|
980
|
+
key: keyArg ? String(keyArg) : null,
|
|
981
|
+
items: data,
|
|
982
|
+
});
|
|
983
|
+
return fragment.getEachInputSignature() === nextEachInputSignature;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* data-derive subtree の入力が同値で、保守条件も満たす場合に
|
|
987
|
+
* 子走査を省略できるかどうかを返します。
|
|
988
|
+
*
|
|
989
|
+
* @param fragment 判定対象フラグメント
|
|
990
|
+
* @returns 省略可能なら true
|
|
991
|
+
*/
|
|
992
|
+
static canSkipStableDerivedSubtree(fragment) {
|
|
993
|
+
if (!fragment.hasAttribute(`${Env.prefix}derive`)) {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
if (fragment.hasAttribute(`${Env.prefix}if`) ||
|
|
997
|
+
fragment.hasAttribute(`${Env.prefix}each`) ||
|
|
998
|
+
fragment.hasAttribute(`${Env.prefix}fetch`) ||
|
|
999
|
+
fragment.hasAttribute(`${Env.prefix}import`)) {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
return !Core.hasDisallowedDerivedSubtreeDescendant(fragment);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* data-derive subtree skip の初期 PoC で扱わない子孫要素を含むかを返します。
|
|
1006
|
+
*
|
|
1007
|
+
* @param fragment 判定対象フラグメント
|
|
1008
|
+
* @returns 含むなら true
|
|
1009
|
+
*/
|
|
1010
|
+
static hasDisallowedDerivedSubtreeDescendant(fragment) {
|
|
1011
|
+
return fragment.getChildren().some(child => {
|
|
1012
|
+
if (!(child instanceof ElementFragment)) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
if (child.hasAttribute(`${Env.prefix}derive`) ||
|
|
1016
|
+
child.hasAttribute(`${Env.prefix}derive-name`) ||
|
|
1017
|
+
child.hasAttribute(`${Env.prefix}fetch`) ||
|
|
1018
|
+
child.hasAttribute(`${Env.prefix}import`)) {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
return Core.hasDisallowedDerivedSubtreeDescendant(child);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* data-derive host が子孫要素へ公開している binding の署名を返します。
|
|
1026
|
+
*
|
|
1027
|
+
* @param fragment 対象フラグメント
|
|
1028
|
+
* @returns binding 署名
|
|
1029
|
+
*/
|
|
1030
|
+
static createDescendantBindingSignature(fragment, source) {
|
|
1031
|
+
Core.recordDerivedSubtreeSignatureComputation(fragment, source);
|
|
1032
|
+
return Core.createBindingSignature(fragment.getDescendantBindingData());
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* data-derive 実行前の入力署名を返します。
|
|
1036
|
+
*
|
|
1037
|
+
* @param fragment 対象フラグメント
|
|
1038
|
+
* @param deriveExpression 導出式
|
|
1039
|
+
* @param deriveName 導出名
|
|
1040
|
+
* @returns 入力署名。導出が無効なら null
|
|
1041
|
+
*/
|
|
1042
|
+
static createDeriveInputSignature(fragment, deriveExpression, deriveName) {
|
|
1043
|
+
const normalizedName = typeof deriveName === 'string' ? deriveName.trim() : '';
|
|
1044
|
+
if (!deriveExpression || normalizedName === '') {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
return Core.createBindingSignature({
|
|
1048
|
+
expression: deriveExpression,
|
|
1049
|
+
name: normalizedName,
|
|
1050
|
+
scope: fragment.getBindingData(),
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* data-derive subtree skip 用の署名を現在状態で更新します。
|
|
1055
|
+
*
|
|
1056
|
+
* @param fragment 対象フラグメント
|
|
1057
|
+
*/
|
|
1058
|
+
static refreshDerivedSubtreeSignature(fragment) {
|
|
1059
|
+
if (!Core.canSkipStableDerivedSubtree(fragment)) {
|
|
1060
|
+
fragment.setDeriveSubtreeSignature(null);
|
|
1061
|
+
Core.logDerivedSubtreeProfileSnapshot(fragment, 'skip-ineligible');
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
fragment.setDeriveSubtreeSignature(Core.createDescendantBindingSignature(fragment, 'refresh'));
|
|
1065
|
+
Core.logDerivedSubtreeProfileSnapshot(fragment, 'refresh');
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* data-derive subtree skip のプロファイルを取得または初期化します。
|
|
1069
|
+
*
|
|
1070
|
+
* @param fragment 対象フラグメント
|
|
1071
|
+
* @returns プロファイル
|
|
1072
|
+
*/
|
|
1073
|
+
static getOrCreateDerivedSubtreeProfile(fragment) {
|
|
1074
|
+
if (!Dev.isEnabled() || !fragment.hasAttribute(`${Env.prefix}derive`)) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
const existing = Core.DERIVE_SUBTREE_PROFILES.get(fragment);
|
|
1078
|
+
if (existing) {
|
|
1079
|
+
return existing;
|
|
1080
|
+
}
|
|
1081
|
+
const profile = {
|
|
1082
|
+
hostId: Core.createDerivedSubtreeHostId(fragment),
|
|
1083
|
+
signatureComputeTotal: 0,
|
|
1084
|
+
signatureComputeFromEvaluateAll: 0,
|
|
1085
|
+
signatureComputeFromRefresh: 0,
|
|
1086
|
+
skipHitCount: 0,
|
|
1087
|
+
skipMissCount: 0,
|
|
1088
|
+
skipIneligibleCount: 0,
|
|
1089
|
+
};
|
|
1090
|
+
Core.DERIVE_SUBTREE_PROFILES.set(fragment, profile);
|
|
1091
|
+
return profile;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* data-derive subtree host の識別子を作成します。
|
|
1095
|
+
*
|
|
1096
|
+
* @param fragment 対象フラグメント
|
|
1097
|
+
* @returns host 識別子
|
|
1098
|
+
*/
|
|
1099
|
+
static createDerivedSubtreeHostId(fragment) {
|
|
1100
|
+
const segments = [];
|
|
1101
|
+
let current = fragment;
|
|
1102
|
+
while (current) {
|
|
1103
|
+
const target = current.getTarget();
|
|
1104
|
+
if (!(target instanceof HTMLElement)) {
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
let segment = target.tagName.toLowerCase();
|
|
1108
|
+
if (target.id.trim() !== '') {
|
|
1109
|
+
segment += `#${target.id.trim()}`;
|
|
1110
|
+
segments.unshift(segment);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
const deriveName = current.getRawAttribute(`${Env.prefix}derive-name`);
|
|
1114
|
+
if (typeof deriveName === 'string' && deriveName.trim() !== '') {
|
|
1115
|
+
segment += `[${Env.prefix}derive-name="${deriveName.trim()}"]`;
|
|
1116
|
+
}
|
|
1117
|
+
const parent = current.getParent();
|
|
1118
|
+
if (parent) {
|
|
1119
|
+
const siblingIndex = parent
|
|
1120
|
+
.getChildren()
|
|
1121
|
+
.filter(child => child instanceof ElementFragment)
|
|
1122
|
+
.findIndex(child => child === current);
|
|
1123
|
+
segment += `:nth-child(${siblingIndex + 1})`;
|
|
1124
|
+
}
|
|
1125
|
+
segments.unshift(segment);
|
|
1126
|
+
current = parent;
|
|
1127
|
+
}
|
|
1128
|
+
return segments.join(' > ');
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* data-derive subtree の署名計算回数を記録します。
|
|
1132
|
+
*
|
|
1133
|
+
* @param fragment 対象フラグメント
|
|
1134
|
+
* @param source 計算元
|
|
1135
|
+
*/
|
|
1136
|
+
static recordDerivedSubtreeSignatureComputation(fragment, source) {
|
|
1137
|
+
const profile = Core.getOrCreateDerivedSubtreeProfile(fragment);
|
|
1138
|
+
if (profile === null) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
profile.signatureComputeTotal += 1;
|
|
1142
|
+
if (source === 'refresh') {
|
|
1143
|
+
profile.signatureComputeFromRefresh += 1;
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
profile.signatureComputeFromEvaluateAll += 1;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* data-derive subtree の現在プロファイルをログ出力します。
|
|
1150
|
+
*
|
|
1151
|
+
* @param fragment 対象フラグメント
|
|
1152
|
+
* @param reason ログ理由
|
|
1153
|
+
*/
|
|
1154
|
+
static logDerivedSubtreeProfileSnapshot(fragment, reason) {
|
|
1155
|
+
const profile = Core.getOrCreateDerivedSubtreeProfile(fragment);
|
|
1156
|
+
if (profile === null) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (reason === 'skip-hit') {
|
|
1160
|
+
profile.skipHitCount += 1;
|
|
1161
|
+
}
|
|
1162
|
+
else if (reason === 'skip-miss') {
|
|
1163
|
+
profile.skipMissCount += 1;
|
|
1164
|
+
}
|
|
1165
|
+
else if (reason === 'skip-ineligible') {
|
|
1166
|
+
profile.skipIneligibleCount += 1;
|
|
1167
|
+
}
|
|
1168
|
+
Log.info('[Haori][derive-profile]', {
|
|
1169
|
+
reason,
|
|
1170
|
+
hostId: profile.hostId,
|
|
1171
|
+
signatureComputeTotal: profile.signatureComputeTotal,
|
|
1172
|
+
signatureComputeFromEvaluateAll: profile.signatureComputeFromEvaluateAll,
|
|
1173
|
+
signatureComputeFromRefresh: profile.signatureComputeFromRefresh,
|
|
1174
|
+
skipHitCount: profile.skipHitCount,
|
|
1175
|
+
skipMissCount: profile.skipMissCount,
|
|
1176
|
+
skipIneligibleCount: profile.skipIneligibleCount,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* data-each 以外の動的要素状態を持つかどうかを返します。
|
|
1181
|
+
*
|
|
1182
|
+
* @param fragment 判定対象フラグメント
|
|
1183
|
+
* @returns 該当するなら true
|
|
1184
|
+
*/
|
|
1185
|
+
static hasNonEachDynamicElementState(fragment) {
|
|
1186
|
+
const allowedEachAttributes = new Set([
|
|
1187
|
+
`${Env.prefix}each`,
|
|
1188
|
+
`${Env.prefix}each-key`,
|
|
1189
|
+
`${Env.prefix}each-arg`,
|
|
1190
|
+
`${Env.prefix}each-index`,
|
|
1191
|
+
]);
|
|
1192
|
+
const hasDynamicAttributes = fragment.getAttributeNames().some(name => {
|
|
1193
|
+
if (allowedEachAttributes.has(name)) {
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
if (name.startsWith(`${Env.prefix}attr-`)) {
|
|
1197
|
+
return true;
|
|
1198
|
+
}
|
|
1199
|
+
if (name.startsWith(Env.prefix)) {
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
1202
|
+
const value = fragment.getRawAttribute(name);
|
|
1203
|
+
return typeof value === 'string' && value.includes('{{');
|
|
1204
|
+
});
|
|
1205
|
+
if (hasDynamicAttributes) {
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
return fragment.getChildren().some(child => child instanceof TextFragment && child.hasDynamicContent());
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* fresh clone 初期化を subtree ごと省略できるかどうかを事前計算します。
|
|
1212
|
+
*
|
|
1213
|
+
* @param fragment 判定対象フラグメント
|
|
1214
|
+
* @returns subtree 全体を省略可能なら true
|
|
1215
|
+
*/
|
|
1216
|
+
static markFreshInitializationSkippable(fragment) {
|
|
1217
|
+
const hasDynamicAttributes = fragment
|
|
1218
|
+
.getAttributeNames()
|
|
1219
|
+
.some(name => Core.isFreshInitializationDynamicAttribute(fragment, name));
|
|
1220
|
+
const hasDynamicChildren = fragment.getChildren().some(child => {
|
|
1221
|
+
if (child instanceof ElementFragment) {
|
|
1222
|
+
return !Core.markFreshInitializationSkippable(child);
|
|
1223
|
+
}
|
|
1224
|
+
if (child instanceof TextFragment) {
|
|
1225
|
+
return child.hasDynamicContent();
|
|
1226
|
+
}
|
|
1227
|
+
return false;
|
|
1228
|
+
});
|
|
1229
|
+
const skippable = !hasDynamicAttributes && !hasDynamicChildren;
|
|
1230
|
+
fragment.setFreshInitializationSkippable(skippable);
|
|
1231
|
+
return skippable;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* fresh clone 初期化で再評価が必要な属性かどうかを返します。
|
|
1235
|
+
*
|
|
1236
|
+
* @param fragment 判定対象フラグメント
|
|
1237
|
+
* @param name 属性名
|
|
1238
|
+
* @returns 再評価が必要なら true
|
|
1239
|
+
*/
|
|
1240
|
+
static isFreshInitializationDynamicAttribute(fragment, name) {
|
|
1241
|
+
if (name.startsWith(`${Env.prefix}attr-`)) {
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
if (name.startsWith(Env.prefix)) {
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1247
|
+
const value = fragment.getRawAttribute(name);
|
|
1248
|
+
return typeof value === 'string' && value.includes('{{');
|
|
1249
|
+
}
|
|
795
1250
|
/**
|
|
796
1251
|
* 差分を更新します。
|
|
797
1252
|
*
|
|
@@ -817,50 +1272,59 @@ class Core {
|
|
|
817
1272
|
newKeys.push(listKey);
|
|
818
1273
|
keyDataMap.set(listKey, { item, itemIndex });
|
|
819
1274
|
});
|
|
1275
|
+
const newKeySet = new Set(newKeys);
|
|
820
1276
|
const removalPromises = [];
|
|
821
1277
|
let childElements = parent
|
|
822
1278
|
.getChildren()
|
|
823
1279
|
.filter(child => child instanceof ElementFragment)
|
|
824
1280
|
.filter(child => !child.hasAttribute(`${Env.prefix}each-before`) &&
|
|
825
1281
|
!child.hasAttribute(`${Env.prefix}each-after`));
|
|
1282
|
+
const previousKeys = childElements.map(child => child.getListKey());
|
|
826
1283
|
childElements = childElements.filter(child => {
|
|
827
|
-
|
|
828
|
-
if (index === -1) {
|
|
1284
|
+
if (!newKeySet.has(String(child.getListKey()))) {
|
|
829
1285
|
removalPromises.push(child.remove());
|
|
830
1286
|
return false;
|
|
831
1287
|
}
|
|
832
1288
|
return true;
|
|
833
1289
|
});
|
|
834
1290
|
const srcKeys = childElements.map(child => child.getListKey());
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
1291
|
+
const childElementsByKey = new Map();
|
|
1292
|
+
childElements.forEach(child => {
|
|
1293
|
+
const listKey = child.getListKey();
|
|
1294
|
+
if (listKey !== null && !childElementsByKey.has(listKey)) {
|
|
1295
|
+
childElementsByKey.set(listKey, child);
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
const insertTargets = parent.getChildElementFragments().slice();
|
|
1299
|
+
const baseInsertIndex = insertTargets.filter(child => child.hasAttribute(`${Env.prefix}each-before`)).length;
|
|
839
1300
|
let chain = Promise.resolve();
|
|
840
1301
|
newKeys.forEach((newKey, loopIndex) => {
|
|
841
|
-
const srcIndex = srcKeys.indexOf(newKey);
|
|
842
1302
|
const { item, itemIndex } = keyDataMap.get(newKey);
|
|
843
1303
|
let child;
|
|
844
|
-
|
|
1304
|
+
const reusedChild = childElementsByKey.get(newKey);
|
|
1305
|
+
if (reusedChild) {
|
|
845
1306
|
// 既存の要素を再利用
|
|
846
|
-
child =
|
|
847
|
-
//
|
|
848
|
-
chain = chain.then(() => Core.updateRowFragment(child, item, indexKey, itemIndex, itemArg ? String(itemArg) : null, newKey)
|
|
849
|
-
|
|
850
|
-
|
|
1307
|
+
child = reusedChild;
|
|
1308
|
+
// 行の入力が同一なら子孫の再評価をスキップする。
|
|
1309
|
+
chain = chain.then(() => Core.updateRowFragment(child, item, indexKey, itemIndex, itemArg ? String(itemArg) : null, newKey).then(changed => {
|
|
1310
|
+
if (!changed) {
|
|
1311
|
+
return undefined;
|
|
1312
|
+
}
|
|
1313
|
+
return Core.evaluateAll(child);
|
|
1314
|
+
}));
|
|
851
1315
|
}
|
|
852
1316
|
else {
|
|
853
1317
|
// 新しい要素を追加
|
|
854
1318
|
child = template.clone();
|
|
855
1319
|
const currentInsertIndex = baseInsertIndex + loopIndex;
|
|
856
1320
|
chain = chain.then(() => Core.updateRowFragment(child, item, indexKey, itemIndex, itemArg ? String(itemArg) : null, newKey).then(() => {
|
|
857
|
-
const referenceChild =
|
|
858
|
-
.getChildren()
|
|
859
|
-
.filter(currentChild => currentChild instanceof ElementFragment)[currentInsertIndex] || null;
|
|
1321
|
+
const referenceChild = insertTargets[currentInsertIndex] ?? null;
|
|
860
1322
|
return parent
|
|
861
1323
|
.insertBefore(child, referenceChild)
|
|
862
|
-
.then(() =>
|
|
863
|
-
.
|
|
1324
|
+
.then(() => {
|
|
1325
|
+
insertTargets.splice(currentInsertIndex, 0, child);
|
|
1326
|
+
})
|
|
1327
|
+
.then(() => Core.initializeFreshEachRow(child));
|
|
864
1328
|
}));
|
|
865
1329
|
}
|
|
866
1330
|
});
|
|
@@ -870,8 +1334,10 @@ class Core {
|
|
|
870
1334
|
// eachupdateイベントを発火
|
|
871
1335
|
const validNewKeys = newKeys.filter((key) => key !== null);
|
|
872
1336
|
const validSrcKeys = srcKeys.filter((key) => key !== null);
|
|
873
|
-
const
|
|
874
|
-
const
|
|
1337
|
+
const validSrcKeySet = new Set(validSrcKeys);
|
|
1338
|
+
const addedKeys = validNewKeys.filter(key => !validSrcKeySet.has(key));
|
|
1339
|
+
const previousValidKeys = previousKeys.filter((key) => key !== null);
|
|
1340
|
+
const removedKeys = previousValidKeys.filter(key => !newKeySet.has(key));
|
|
875
1341
|
HaoriEvent.eachUpdate(parent.getTarget(), addedKeys, removedKeys, validNewKeys);
|
|
876
1342
|
return undefined;
|
|
877
1343
|
});
|
|
@@ -944,12 +1410,112 @@ class Core {
|
|
|
944
1410
|
}
|
|
945
1411
|
else {
|
|
946
1412
|
Log.error('[Haori]', `Primitive value requires '${Env.prefix}each-arg' attribute: ${data}`);
|
|
947
|
-
return Promise.resolve();
|
|
1413
|
+
return Promise.resolve(false);
|
|
948
1414
|
}
|
|
949
1415
|
}
|
|
1416
|
+
const normalizedBindingData = bindingData;
|
|
1417
|
+
const nextRenderSignature = Core.createBindingSignature({
|
|
1418
|
+
listKey,
|
|
1419
|
+
bindingData: normalizedBindingData,
|
|
1420
|
+
});
|
|
1421
|
+
if (rowFragment.getListKey() === listKey &&
|
|
1422
|
+
rowFragment.getRenderSignature() === nextRenderSignature) {
|
|
1423
|
+
return Promise.resolve(false);
|
|
1424
|
+
}
|
|
950
1425
|
rowFragment.setListKey(listKey);
|
|
951
|
-
rowFragment.
|
|
952
|
-
|
|
1426
|
+
rowFragment.setRenderSignature(nextRenderSignature);
|
|
1427
|
+
rowFragment.setBindingData(normalizedBindingData);
|
|
1428
|
+
return rowFragment
|
|
1429
|
+
.setAttribute(`${Env.prefix}row`, listKey)
|
|
1430
|
+
.then(() => true);
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* 新規挿入行に遅延再評価が必要かどうかを判定します。
|
|
1434
|
+
*
|
|
1435
|
+
* @param fragment 判定対象の行フラグメント
|
|
1436
|
+
* @returns 遅延再評価が必要なら true
|
|
1437
|
+
*/
|
|
1438
|
+
static needsScheduledEvaluateAll(fragment) {
|
|
1439
|
+
const stack = [fragment];
|
|
1440
|
+
while (stack.length > 0) {
|
|
1441
|
+
const current = stack.pop();
|
|
1442
|
+
current.getChildElementFragments().forEach(child => {
|
|
1443
|
+
stack.push(child);
|
|
1444
|
+
});
|
|
1445
|
+
if (current !== fragment &&
|
|
1446
|
+
!current.isMounted() &&
|
|
1447
|
+
Core.hasMountSensitiveAttribute(current)) {
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* mounted 状態に依存して再評価が必要になりやすい属性を持つかどうかを返します。
|
|
1455
|
+
*
|
|
1456
|
+
* @param fragment 判定対象フラグメント
|
|
1457
|
+
* @returns 該当属性を持つなら true
|
|
1458
|
+
*/
|
|
1459
|
+
static hasMountSensitiveAttribute(fragment) {
|
|
1460
|
+
return ['fetch', 'import'].some(suffix => fragment.hasAttribute(`${Env.prefix}${suffix}`));
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* バインド値が同一かどうかを再帰的に判定します。
|
|
1464
|
+
*
|
|
1465
|
+
* @param left 比較元の値
|
|
1466
|
+
* @param right 比較先の値
|
|
1467
|
+
* @param visited 循環参照対策用の訪問済みペア
|
|
1468
|
+
* @returns 同一なら true
|
|
1469
|
+
*/
|
|
1470
|
+
static createBindingSignature(value, seen = new WeakMap(), nextId = { value: 0 }) {
|
|
1471
|
+
if (value === null) {
|
|
1472
|
+
return 'null';
|
|
1473
|
+
}
|
|
1474
|
+
if (value === undefined) {
|
|
1475
|
+
return 'undefined';
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof value === 'string') {
|
|
1478
|
+
return JSON.stringify(value);
|
|
1479
|
+
}
|
|
1480
|
+
if (typeof value === 'number' ||
|
|
1481
|
+
typeof value === 'boolean' ||
|
|
1482
|
+
typeof value === 'bigint') {
|
|
1483
|
+
return String(value);
|
|
1484
|
+
}
|
|
1485
|
+
if (typeof value === 'function') {
|
|
1486
|
+
return `[Function:${value.name || 'anonymous'}]`;
|
|
1487
|
+
}
|
|
1488
|
+
if (typeof value === 'symbol') {
|
|
1489
|
+
return value.toString();
|
|
1490
|
+
}
|
|
1491
|
+
if (value instanceof Date) {
|
|
1492
|
+
return `[Date:${value.toISOString()}]`;
|
|
1493
|
+
}
|
|
1494
|
+
if (Array.isArray(value)) {
|
|
1495
|
+
if (seen.has(value)) {
|
|
1496
|
+
return `[Circular:${seen.get(value)}]`;
|
|
1497
|
+
}
|
|
1498
|
+
const marker = `array-${nextId.value}`;
|
|
1499
|
+
nextId.value += 1;
|
|
1500
|
+
seen.set(value, marker);
|
|
1501
|
+
return `[${value
|
|
1502
|
+
.map(item => Core.createBindingSignature(item, seen, nextId))
|
|
1503
|
+
.join(',')}]`;
|
|
1504
|
+
}
|
|
1505
|
+
if (typeof value === 'object') {
|
|
1506
|
+
if (seen.has(value)) {
|
|
1507
|
+
return `[Circular:${seen.get(value)}]`;
|
|
1508
|
+
}
|
|
1509
|
+
const marker = `object-${nextId.value}`;
|
|
1510
|
+
nextId.value += 1;
|
|
1511
|
+
seen.set(value, marker);
|
|
1512
|
+
const record = value;
|
|
1513
|
+
return `{${Object.keys(record)
|
|
1514
|
+
.sort()
|
|
1515
|
+
.map(key => `${JSON.stringify(key)}:${Core.createBindingSignature(record[key], seen, nextId)}`)
|
|
1516
|
+
.join(',')}}`;
|
|
1517
|
+
}
|
|
1518
|
+
return String(value);
|
|
953
1519
|
}
|
|
954
1520
|
/**
|
|
955
1521
|
* フラグメントの再評価を次のイベントループで実行します。
|
|
@@ -992,5 +1558,7 @@ Core.ATTRIBUTE_PLACEHOLDER_REGEX = /\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/;
|
|
|
992
1558
|
Core.REACTIVE_FETCH_STATES = new WeakMap();
|
|
993
1559
|
/** data-import の自動再評価状態 */
|
|
994
1560
|
Core.REACTIVE_IMPORT_STATES = new WeakMap();
|
|
1561
|
+
/** data-derive subtree skip の開発用プロファイル */
|
|
1562
|
+
Core.DERIVE_SUBTREE_PROFILES = new WeakMap();
|
|
995
1563
|
export default Core;
|
|
996
1564
|
//# sourceMappingURL=core.js.map
|