haori 0.13.0 → 0.13.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 +1 -1
- package/README.md +1 -1
- package/dist/haori.cjs.js +10 -10
- package/dist/haori.es.js +191 -165
- package/dist/haori.iife.js +10 -10
- package/dist/index.d.ts +1 -1
- package/package.json +11 -3
- package/dist/haori.cjs.js.map +0 -1
- package/dist/haori.es.js.map +0 -1
- package/dist/haori.iife.js.map +0 -1
- package/dist/package.json +0 -70
- package/dist/src/builtins.d.ts +0 -100
- package/dist/src/builtins.d.ts.map +0 -1
- package/dist/src/builtins.js +0 -194
- package/dist/src/builtins.js.map +0 -1
- package/dist/src/core.d.ts +0 -505
- package/dist/src/core.d.ts.map +0 -1
- package/dist/src/core.js +0 -1817
- package/dist/src/core.js.map +0 -1
- package/dist/src/dev.d.ts +0 -35
- package/dist/src/dev.d.ts.map +0 -1
- package/dist/src/dev.js +0 -44
- package/dist/src/dev.js.map +0 -1
- package/dist/src/env.d.ts +0 -43
- package/dist/src/env.d.ts.map +0 -1
- package/dist/src/env.js +0 -108
- package/dist/src/env.js.map +0 -1
- package/dist/src/event.d.ts +0 -171
- package/dist/src/event.d.ts.map +0 -1
- package/dist/src/event.js +0 -235
- package/dist/src/event.js.map +0 -1
- package/dist/src/event_dispatcher.d.ts +0 -66
- package/dist/src/event_dispatcher.d.ts.map +0 -1
- package/dist/src/event_dispatcher.js +0 -167
- package/dist/src/event_dispatcher.js.map +0 -1
- package/dist/src/expression.d.ts +0 -260
- package/dist/src/expression.d.ts.map +0 -1
- package/dist/src/expression.js +0 -975
- package/dist/src/expression.js.map +0 -1
- package/dist/src/form.d.ts +0 -148
- package/dist/src/form.d.ts.map +0 -1
- package/dist/src/form.js +0 -428
- package/dist/src/form.js.map +0 -1
- package/dist/src/fragment.d.ts +0 -621
- package/dist/src/fragment.d.ts.map +0 -1
- package/dist/src/fragment.js +0 -1963
- package/dist/src/fragment.js.map +0 -1
- package/dist/src/haori.d.ts +0 -130
- package/dist/src/haori.d.ts.map +0 -1
- package/dist/src/haori.js +0 -215
- package/dist/src/haori.js.map +0 -1
- package/dist/src/import.d.ts +0 -19
- package/dist/src/import.d.ts.map +0 -1
- package/dist/src/import.js +0 -64
- package/dist/src/import.js.map +0 -1
- package/dist/src/index.d.ts +0 -27
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -30
- package/dist/src/index.js.map +0 -1
- package/dist/src/intersect.d.ts +0 -28
- package/dist/src/intersect.d.ts.map +0 -1
- package/dist/src/intersect.js +0 -201
- package/dist/src/intersect.js.map +0 -1
- package/dist/src/log.d.ts +0 -32
- package/dist/src/log.d.ts.map +0 -1
- package/dist/src/log.js +0 -43
- package/dist/src/log.js.map +0 -1
- package/dist/src/observer.d.ts +0 -24
- package/dist/src/observer.d.ts.map +0 -1
- package/dist/src/observer.js +0 -131
- package/dist/src/observer.js.map +0 -1
- package/dist/src/procedure.d.ts +0 -447
- package/dist/src/procedure.d.ts.map +0 -1
- package/dist/src/procedure.js +0 -1921
- package/dist/src/procedure.js.map +0 -1
- package/dist/src/queue.d.ts +0 -35
- package/dist/src/queue.d.ts.map +0 -1
- package/dist/src/queue.js +0 -202
- package/dist/src/queue.js.map +0 -1
- package/dist/src/url.d.ts +0 -14
- package/dist/src/url.d.ts.map +0 -1
- package/dist/src/url.js +0 -22
- package/dist/src/url.js.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.d.ts +0 -2
- package/dist/tests/bind-arg-own-binding.test.d.ts.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.js +0 -63
- package/dist/tests/bind-arg-own-binding.test.js.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.d.ts +0 -2
- package/dist/tests/bindcomplete-and-scope.test.d.ts.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.js +0 -115
- package/dist/tests/bindcomplete-and-scope.test.js.map +0 -1
- package/dist/tests/builtins.test.d.ts +0 -2
- package/dist/tests/builtins.test.d.ts.map +0 -1
- package/dist/tests/builtins.test.js +0 -152
- package/dist/tests/builtins.test.js.map +0 -1
- package/dist/tests/click-attributes.test.d.ts +0 -2
- package/dist/tests/click-attributes.test.d.ts.map +0 -1
- package/dist/tests/click-attributes.test.js +0 -113
- package/dist/tests/click-attributes.test.js.map +0 -1
- package/dist/tests/click-no-disabled.test.d.ts +0 -2
- package/dist/tests/click-no-disabled.test.d.ts.map +0 -1
- package/dist/tests/click-no-disabled.test.js +0 -67
- package/dist/tests/click-no-disabled.test.js.map +0 -1
- package/dist/tests/core.test.d.ts +0 -5
- package/dist/tests/core.test.d.ts.map +0 -1
- package/dist/tests/core.test.js +0 -1056
- package/dist/tests/core.test.js.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.d.ts +0 -2
- package/dist/tests/data-bind-arg-reeval.test.d.ts.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.js +0 -119
- package/dist/tests/data-bind-arg-reeval.test.js.map +0 -1
- package/dist/tests/data-bind-merge.test.d.ts +0 -2
- package/dist/tests/data-bind-merge.test.d.ts.map +0 -1
- package/dist/tests/data-bind-merge.test.js +0 -86
- package/dist/tests/data-bind-merge.test.js.map +0 -1
- package/dist/tests/data-click-defer.test.d.ts +0 -2
- package/dist/tests/data-click-defer.test.d.ts.map +0 -1
- package/dist/tests/data-click-defer.test.js +0 -57
- package/dist/tests/data-click-defer.test.js.map +0 -1
- package/dist/tests/data-click-run.test.d.ts +0 -5
- package/dist/tests/data-click-run.test.d.ts.map +0 -1
- package/dist/tests/data-click-run.test.js +0 -109
- package/dist/tests/data-click-run.test.js.map +0 -1
- package/dist/tests/data-derive.test.d.ts +0 -2
- package/dist/tests/data-derive.test.d.ts.map +0 -1
- package/dist/tests/data-derive.test.js +0 -999
- package/dist/tests/data-derive.test.js.map +0 -1
- package/dist/tests/data-each-browserlike.test.d.ts +0 -2
- package/dist/tests/data-each-browserlike.test.d.ts.map +0 -1
- package/dist/tests/data-each-browserlike.test.js +0 -48
- package/dist/tests/data-each-browserlike.test.js.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.d.ts +0 -2
- package/dist/tests/data-each-fragment-debug.test.d.ts.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.js +0 -119
- package/dist/tests/data-each-fragment-debug.test.js.map +0 -1
- package/dist/tests/data-each-table.test.d.ts +0 -2
- package/dist/tests/data-each-table.test.d.ts.map +0 -1
- package/dist/tests/data-each-table.test.js +0 -131
- package/dist/tests/data-each-table.test.js.map +0 -1
- package/dist/tests/data-each-template-recovery.test.d.ts +0 -2
- package/dist/tests/data-each-template-recovery.test.d.ts.map +0 -1
- package/dist/tests/data-each-template-recovery.test.js +0 -87
- package/dist/tests/data-each-template-recovery.test.js.map +0 -1
- package/dist/tests/data-event-prevent.test.d.ts +0 -2
- package/dist/tests/data-event-prevent.test.d.ts.map +0 -1
- package/dist/tests/data-event-prevent.test.js +0 -76
- package/dist/tests/data-event-prevent.test.js.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.d.ts +0 -2
- package/dist/tests/data-fetch-form-input-binding.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.js +0 -202
- package/dist/tests/data-fetch-form-input-binding.test.js.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.d.ts +0 -2
- package/dist/tests/data-fetch-tbody-dom.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.js +0 -82
- package/dist/tests/data-fetch-tbody-dom.test.js.map +0 -1
- package/dist/tests/data-form-container.test.d.ts +0 -2
- package/dist/tests/data-form-container.test.d.ts.map +0 -1
- package/dist/tests/data-form-container.test.js +0 -88
- package/dist/tests/data-form-container.test.js.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.d.ts +0 -2
- package/dist/tests/data-if-dev-diagnostics.test.d.ts.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.js +0 -55
- package/dist/tests/data-if-dev-diagnostics.test.js.map +0 -1
- package/dist/tests/data-if-falsy.test.d.ts +0 -2
- package/dist/tests/data-if-falsy.test.d.ts.map +0 -1
- package/dist/tests/data-if-falsy.test.js +0 -73
- package/dist/tests/data-if-falsy.test.js.map +0 -1
- package/dist/tests/data-load-on-show.test.d.ts +0 -2
- package/dist/tests/data-load-on-show.test.d.ts.map +0 -1
- package/dist/tests/data-load-on-show.test.js +0 -98
- package/dist/tests/data-load-on-show.test.js.map +0 -1
- package/dist/tests/derive-each-render-wait.test.d.ts +0 -2
- package/dist/tests/derive-each-render-wait.test.d.ts.map +0 -1
- package/dist/tests/derive-each-render-wait.test.js +0 -90
- package/dist/tests/derive-each-render-wait.test.js.map +0 -1
- package/dist/tests/dev.test.d.ts +0 -2
- package/dist/tests/dev.test.d.ts.map +0 -1
- package/dist/tests/dev.test.js +0 -51
- package/dist/tests/dev.test.js.map +0 -1
- package/dist/tests/each-update-event.test.d.ts +0 -2
- package/dist/tests/each-update-event.test.d.ts.map +0 -1
- package/dist/tests/each-update-event.test.js +0 -83
- package/dist/tests/each-update-event.test.js.map +0 -1
- package/dist/tests/each_arg.test.d.ts +0 -2
- package/dist/tests/each_arg.test.d.ts.map +0 -1
- package/dist/tests/each_arg.test.js +0 -41
- package/dist/tests/each_arg.test.js.map +0 -1
- package/dist/tests/env.test.d.ts +0 -2
- package/dist/tests/env.test.d.ts.map +0 -1
- package/dist/tests/env.test.js +0 -124
- package/dist/tests/env.test.js.map +0 -1
- package/dist/tests/evaluation-profile.test.d.ts +0 -2
- package/dist/tests/evaluation-profile.test.d.ts.map +0 -1
- package/dist/tests/evaluation-profile.test.js +0 -92
- package/dist/tests/evaluation-profile.test.js.map +0 -1
- package/dist/tests/event.test.d.ts +0 -2
- package/dist/tests/event.test.d.ts.map +0 -1
- package/dist/tests/event.test.js +0 -300
- package/dist/tests/event.test.js.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts +0 -2
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.js +0 -45
- package/dist/tests/expression-disallowed-keyword-hint.test.js.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.d.ts +0 -2
- package/dist/tests/expression-forbidden-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.js +0 -37
- package/dist/tests/expression-forbidden-hint.test.js.map +0 -1
- package/dist/tests/expression.test.d.ts +0 -2
- package/dist/tests/expression.test.d.ts.map +0 -1
- package/dist/tests/expression.test.js +0 -482
- package/dist/tests/expression.test.js.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts +0 -2
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.js +0 -195
- package/dist/tests/fetch-and-procedure-scenarios.test.js.map +0 -1
- package/dist/tests/form-number-type.test.d.ts +0 -2
- package/dist/tests/form-number-type.test.d.ts.map +0 -1
- package/dist/tests/form-number-type.test.js +0 -83
- package/dist/tests/form-number-type.test.js.map +0 -1
- package/dist/tests/form.test.d.ts +0 -2
- package/dist/tests/form.test.d.ts.map +0 -1
- package/dist/tests/form.test.js +0 -636
- package/dist/tests/form.test.js.map +0 -1
- package/dist/tests/fragment.test.d.ts +0 -2
- package/dist/tests/fragment.test.d.ts.map +0 -1
- package/dist/tests/fragment.test.js +0 -284
- package/dist/tests/fragment.test.js.map +0 -1
- package/dist/tests/haori.test.d.ts +0 -2
- package/dist/tests/haori.test.d.ts.map +0 -1
- package/dist/tests/haori.test.js +0 -149
- package/dist/tests/haori.test.js.map +0 -1
- package/dist/tests/helpers/async.d.ts +0 -20
- package/dist/tests/helpers/async.d.ts.map +0 -1
- package/dist/tests/helpers/async.js +0 -35
- package/dist/tests/helpers/async.js.map +0 -1
- package/dist/tests/import.test.d.ts +0 -2
- package/dist/tests/import.test.d.ts.map +0 -1
- package/dist/tests/import.test.js +0 -148
- package/dist/tests/import.test.js.map +0 -1
- package/dist/tests/intersect.test.d.ts +0 -2
- package/dist/tests/intersect.test.d.ts.map +0 -1
- package/dist/tests/intersect.test.js +0 -173
- package/dist/tests/intersect.test.js.map +0 -1
- package/dist/tests/log.test.d.ts +0 -2
- package/dist/tests/log.test.d.ts.map +0 -1
- package/dist/tests/log.test.js +0 -58
- package/dist/tests/log.test.js.map +0 -1
- package/dist/tests/observer.test.d.ts +0 -2
- package/dist/tests/observer.test.d.ts.map +0 -1
- package/dist/tests/observer.test.js +0 -64
- package/dist/tests/observer.test.js.map +0 -1
- package/dist/tests/placeholder-resolution.test.d.ts +0 -2
- package/dist/tests/placeholder-resolution.test.d.ts.map +0 -1
- package/dist/tests/placeholder-resolution.test.js +0 -463
- package/dist/tests/placeholder-resolution.test.js.map +0 -1
- package/dist/tests/procedure-action-operations.test.d.ts +0 -2
- package/dist/tests/procedure-action-operations.test.d.ts.map +0 -1
- package/dist/tests/procedure-action-operations.test.js +0 -1140
- package/dist/tests/procedure-action-operations.test.js.map +0 -1
- package/dist/tests/procedure-bind-append.test.d.ts +0 -2
- package/dist/tests/procedure-bind-append.test.d.ts.map +0 -1
- package/dist/tests/procedure-bind-append.test.js +0 -80
- package/dist/tests/procedure-bind-append.test.js.map +0 -1
- package/dist/tests/procedure-fetch-options.test.d.ts +0 -2
- package/dist/tests/procedure-fetch-options.test.d.ts.map +0 -1
- package/dist/tests/procedure-fetch-options.test.js +0 -255
- package/dist/tests/procedure-fetch-options.test.js.map +0 -1
- package/dist/tests/procedure.test.d.ts +0 -2
- package/dist/tests/procedure.test.d.ts.map +0 -1
- package/dist/tests/procedure.test.js +0 -137
- package/dist/tests/procedure.test.js.map +0 -1
- package/dist/tests/procedure_events.test.d.ts +0 -7
- package/dist/tests/procedure_events.test.d.ts.map +0 -1
- package/dist/tests/procedure_events.test.js +0 -261
- package/dist/tests/procedure_events.test.js.map +0 -1
- package/dist/tests/render-wait.test.d.ts +0 -2
- package/dist/tests/render-wait.test.d.ts.map +0 -1
- package/dist/tests/render-wait.test.js +0 -68
- package/dist/tests/render-wait.test.js.map +0 -1
- package/dist/tests/reset_each.test.d.ts +0 -2
- package/dist/tests/reset_each.test.d.ts.map +0 -1
- package/dist/tests/reset_each.test.js +0 -215
- package/dist/tests/reset_each.test.js.map +0 -1
- package/dist/tests/row-move.test.d.ts +0 -2
- package/dist/tests/row-move.test.d.ts.map +0 -1
- package/dist/tests/row-move.test.js +0 -220
- package/dist/tests/row-move.test.js.map +0 -1
- package/dist/tests/row-operations.test.d.ts +0 -2
- package/dist/tests/row-operations.test.d.ts.map +0 -1
- package/dist/tests/row-operations.test.js +0 -259
- package/dist/tests/row-operations.test.js.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.d.ts +0 -2
- package/dist/tests/scheduled-evaluate-wait.test.d.ts.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.js +0 -68
- package/dist/tests/scheduled-evaluate-wait.test.js.map +0 -1
- package/dist/tests/url.test.d.ts +0 -2
- package/dist/tests/url.test.d.ts.map +0 -1
- package/dist/tests/url.test.js +0 -150
- package/dist/tests/url.test.js.map +0 -1
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.d.ts.map +0 -1
- package/dist/vite.config.js +0 -28
- package/dist/vite.config.js.map +0 -1
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- package/dist/vitest.config.js +0 -19
- package/dist/vitest.config.js.map +0 -1
package/dist/src/fragment.js
DELETED
|
@@ -1,1963 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview 仮想DOM実装
|
|
3
|
-
*
|
|
4
|
-
* メモリ上にノードツリーを保持し、DOMへの反映を非同期で行います。
|
|
5
|
-
* DOMからの読み込みは行わず、オブザーバーとchangeイベントで更新されます。
|
|
6
|
-
*/
|
|
7
|
-
import Queue from './queue';
|
|
8
|
-
import Log from './log';
|
|
9
|
-
import Expression from './expression';
|
|
10
|
-
import Env from './env';
|
|
11
|
-
import Dev from './dev';
|
|
12
|
-
/**
|
|
13
|
-
* 開発時の属性・テキスト評価回数を集計するレジストリです。
|
|
14
|
-
*/
|
|
15
|
-
class EvaluationProfileRegistry {
|
|
16
|
-
/**
|
|
17
|
-
* 集計状態を初期化します。
|
|
18
|
-
*/
|
|
19
|
-
static reset() {
|
|
20
|
-
EvaluationProfileRegistry.ELEMENT_STORES.clear();
|
|
21
|
-
EvaluationProfileRegistry.ensureGlobalAccess();
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* 現在の集計結果スナップショットを返します。
|
|
25
|
-
*
|
|
26
|
-
* @returns エレメントごとの集計結果
|
|
27
|
-
*/
|
|
28
|
-
static snapshot() {
|
|
29
|
-
EvaluationProfileRegistry.ensureGlobalAccess();
|
|
30
|
-
return [...EvaluationProfileRegistry.ELEMENT_STORES.entries()]
|
|
31
|
-
.map(([elementId, store]) => ({
|
|
32
|
-
elementId,
|
|
33
|
-
tagName: store.tagName,
|
|
34
|
-
attributes: [...store.attributes.entries()]
|
|
35
|
-
.map(([name, counter]) => ({
|
|
36
|
-
name,
|
|
37
|
-
template: counter.template,
|
|
38
|
-
calls: counter.calls,
|
|
39
|
-
totalDurationMs: counter.totalDurationMs,
|
|
40
|
-
maxDurationMs: counter.maxDurationMs,
|
|
41
|
-
placeholders: EvaluationProfileRegistry.sortPlaceholders(counter.placeholders),
|
|
42
|
-
}))
|
|
43
|
-
.sort((left, right) => right.calls - left.calls),
|
|
44
|
-
texts: [...store.texts.entries()]
|
|
45
|
-
.map(([childIndex, counter]) => ({
|
|
46
|
-
childIndex: Number(childIndex),
|
|
47
|
-
template: counter.template,
|
|
48
|
-
calls: counter.calls,
|
|
49
|
-
totalDurationMs: counter.totalDurationMs,
|
|
50
|
-
maxDurationMs: counter.maxDurationMs,
|
|
51
|
-
placeholders: EvaluationProfileRegistry.sortPlaceholders(counter.placeholders),
|
|
52
|
-
}))
|
|
53
|
-
.sort((left, right) => right.calls - left.calls),
|
|
54
|
-
}))
|
|
55
|
-
.sort((left, right) => {
|
|
56
|
-
const leftCalls = left.attributes.reduce((sum, item) => sum + item.calls, 0) +
|
|
57
|
-
left.texts.reduce((sum, item) => sum + item.calls, 0);
|
|
58
|
-
const rightCalls = right.attributes.reduce((sum, item) => sum + item.calls, 0) +
|
|
59
|
-
right.texts.reduce((sum, item) => sum + item.calls, 0);
|
|
60
|
-
return rightCalls - leftCalls;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* 評価呼び出しを記録します。
|
|
65
|
-
*
|
|
66
|
-
* @param context 評価コンテキスト
|
|
67
|
-
* @param expressions 今回評価した式一覧
|
|
68
|
-
*/
|
|
69
|
-
static record(context, expressions, totalDurationMs) {
|
|
70
|
-
if (!Dev.isEnabled() || !context || expressions.length === 0) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
EvaluationProfileRegistry.ensureGlobalAccess();
|
|
74
|
-
const store = EvaluationProfileRegistry.getOrCreateElementStore(context.element);
|
|
75
|
-
if (context.kind === 'attribute') {
|
|
76
|
-
const counter = EvaluationProfileRegistry.getOrCreateCounter(store.attributes, context.rawName, context.template);
|
|
77
|
-
EvaluationProfileRegistry.updateCounter(counter, expressions, totalDurationMs);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const counter = EvaluationProfileRegistry.getOrCreateCounter(store.texts, String(context.childIndex), context.template);
|
|
81
|
-
EvaluationProfileRegistry.updateCounter(counter, expressions, totalDurationMs);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* globalThis から dev-only の取得窓口を参照できるようにします。
|
|
85
|
-
*/
|
|
86
|
-
static ensureGlobalAccess() {
|
|
87
|
-
if (!Dev.isEnabled()) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const globalRecord = globalThis;
|
|
91
|
-
if (globalRecord[EvaluationProfileRegistry.GLOBAL_KEY] !== undefined) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
globalRecord[EvaluationProfileRegistry.GLOBAL_KEY] = {
|
|
95
|
-
reset: () => EvaluationProfileRegistry.reset(),
|
|
96
|
-
snapshot: () => EvaluationProfileRegistry.snapshot(),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* エレメント単位の集計ストアを取得または初期化します。
|
|
101
|
-
*
|
|
102
|
-
* @param element 対象エレメント
|
|
103
|
-
* @returns 集計ストア
|
|
104
|
-
*/
|
|
105
|
-
static getOrCreateElementStore(element) {
|
|
106
|
-
const elementId = EvaluationProfileRegistry.createElementId(element);
|
|
107
|
-
const existing = EvaluationProfileRegistry.ELEMENT_STORES.get(elementId);
|
|
108
|
-
if (existing) {
|
|
109
|
-
return existing;
|
|
110
|
-
}
|
|
111
|
-
const store = {
|
|
112
|
-
tagName: element.tagName.toLowerCase(),
|
|
113
|
-
attributes: new Map(),
|
|
114
|
-
texts: new Map(),
|
|
115
|
-
};
|
|
116
|
-
EvaluationProfileRegistry.ELEMENT_STORES.set(elementId, store);
|
|
117
|
-
return store;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* カウンタを取得または初期化します。
|
|
121
|
-
*
|
|
122
|
-
* @param counters 種別ごとのカウンタマップ
|
|
123
|
-
* @param key カウンタキー
|
|
124
|
-
* @param template 元テンプレート
|
|
125
|
-
* @returns カウンタ
|
|
126
|
-
*/
|
|
127
|
-
static getOrCreateCounter(counters, key, template) {
|
|
128
|
-
const existing = counters.get(key);
|
|
129
|
-
if (existing) {
|
|
130
|
-
return existing;
|
|
131
|
-
}
|
|
132
|
-
const counter = {
|
|
133
|
-
template,
|
|
134
|
-
calls: 0,
|
|
135
|
-
totalDurationMs: 0,
|
|
136
|
-
maxDurationMs: 0,
|
|
137
|
-
placeholders: new Map(),
|
|
138
|
-
};
|
|
139
|
-
counters.set(key, counter);
|
|
140
|
-
return counter;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* プレースホルダカウンタを取得または初期化します。
|
|
144
|
-
*
|
|
145
|
-
* @param placeholders プレースホルダカウンタマップ
|
|
146
|
-
* @param expression 式文字列
|
|
147
|
-
* @returns プレースホルダカウンタ
|
|
148
|
-
*/
|
|
149
|
-
static getOrCreatePlaceholder(placeholders, expression) {
|
|
150
|
-
const existing = placeholders.get(expression);
|
|
151
|
-
if (existing) {
|
|
152
|
-
return existing;
|
|
153
|
-
}
|
|
154
|
-
const counter = {
|
|
155
|
-
calls: 0,
|
|
156
|
-
totalDurationMs: 0,
|
|
157
|
-
maxDurationMs: 0,
|
|
158
|
-
};
|
|
159
|
-
placeholders.set(expression, counter);
|
|
160
|
-
return counter;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* カウンタへ今回の評価結果を加算します。
|
|
164
|
-
*
|
|
165
|
-
* @param counter 更新対象カウンタ
|
|
166
|
-
* @param expressions 今回評価した式一覧
|
|
167
|
-
* @param totalDurationMs 今回の総所要時間
|
|
168
|
-
*/
|
|
169
|
-
static updateCounter(counter, expressions, totalDurationMs) {
|
|
170
|
-
counter.calls += 1;
|
|
171
|
-
counter.totalDurationMs += totalDurationMs;
|
|
172
|
-
counter.maxDurationMs = Math.max(counter.maxDurationMs, totalDurationMs);
|
|
173
|
-
expressions.forEach(expression => {
|
|
174
|
-
const placeholder = EvaluationProfileRegistry.getOrCreatePlaceholder(counter.placeholders, expression.expression);
|
|
175
|
-
placeholder.calls += 1;
|
|
176
|
-
placeholder.totalDurationMs += expression.durationMs;
|
|
177
|
-
placeholder.maxDurationMs = Math.max(placeholder.maxDurationMs, expression.durationMs);
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* プレースホルダ集計を calls 降順で返します。
|
|
182
|
-
*
|
|
183
|
-
* @param placeholders プレースホルダ集計
|
|
184
|
-
* @returns スナップショット
|
|
185
|
-
*/
|
|
186
|
-
static sortPlaceholders(placeholders) {
|
|
187
|
-
return [...placeholders.entries()]
|
|
188
|
-
.map(([expression, counter]) => ({
|
|
189
|
-
expression,
|
|
190
|
-
calls: counter.calls,
|
|
191
|
-
totalDurationMs: counter.totalDurationMs,
|
|
192
|
-
maxDurationMs: counter.maxDurationMs,
|
|
193
|
-
}))
|
|
194
|
-
.sort((left, right) => {
|
|
195
|
-
if (right.calls !== left.calls) {
|
|
196
|
-
return right.calls - left.calls;
|
|
197
|
-
}
|
|
198
|
-
return right.totalDurationMs - left.totalDurationMs;
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* 現在時刻のタイムスタンプを返します。
|
|
203
|
-
*
|
|
204
|
-
* @returns ミリ秒
|
|
205
|
-
*/
|
|
206
|
-
static now() {
|
|
207
|
-
return globalThis.performance?.now() ?? Date.now();
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* 評価処理の所要時間を計測します。
|
|
211
|
-
*
|
|
212
|
-
* @param callback 計測対象
|
|
213
|
-
* @returns 結果と所要時間
|
|
214
|
-
*/
|
|
215
|
-
static measure(callback) {
|
|
216
|
-
const startedAt = EvaluationProfileRegistry.now();
|
|
217
|
-
const value = callback();
|
|
218
|
-
return {
|
|
219
|
-
value,
|
|
220
|
-
durationMs: EvaluationProfileRegistry.now() - startedAt,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* エレメント識別子を生成します。
|
|
225
|
-
*
|
|
226
|
-
* @param element 対象エレメント
|
|
227
|
-
* @returns 識別子
|
|
228
|
-
*/
|
|
229
|
-
static createElementId(element) {
|
|
230
|
-
const segments = [];
|
|
231
|
-
let current = element;
|
|
232
|
-
while (current) {
|
|
233
|
-
let segment = current.tagName.toLowerCase();
|
|
234
|
-
const rawId = current.getAttribute('id') || '';
|
|
235
|
-
if (rawId.trim() !== '') {
|
|
236
|
-
segment += `#${rawId.trim()}`;
|
|
237
|
-
segments.unshift(segment);
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
const deriveName = current.getAttribute(`${Env.prefix}derive-name`);
|
|
241
|
-
if (deriveName && deriveName.trim() !== '') {
|
|
242
|
-
segment += `[${Env.prefix}derive-name="${deriveName.trim()}"]`;
|
|
243
|
-
}
|
|
244
|
-
const parent = current.parentElement;
|
|
245
|
-
if (parent) {
|
|
246
|
-
segment += `:nth-child(${[...parent.children].indexOf(current) + 1})`;
|
|
247
|
-
}
|
|
248
|
-
segments.unshift(segment);
|
|
249
|
-
current = parent;
|
|
250
|
-
}
|
|
251
|
-
return segments.join(' > ');
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
/** globalThis に公開するキー */
|
|
255
|
-
EvaluationProfileRegistry.GLOBAL_KEY = '__HAORI_EVALUATION_PROFILE__';
|
|
256
|
-
/** エレメントごとの集計 */
|
|
257
|
-
EvaluationProfileRegistry.ELEMENT_STORES = new Map();
|
|
258
|
-
/**
|
|
259
|
-
* 仮想DOMのフラグメントの抽象クラス。
|
|
260
|
-
*/
|
|
261
|
-
class Fragment {
|
|
262
|
-
static get(node) {
|
|
263
|
-
if (node == null) {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
if (Fragment.FRAGMENT_CACHE.has(node)) {
|
|
267
|
-
return Fragment.FRAGMENT_CACHE.get(node);
|
|
268
|
-
}
|
|
269
|
-
let fragment;
|
|
270
|
-
switch (node.nodeType) {
|
|
271
|
-
case Node.ELEMENT_NODE:
|
|
272
|
-
fragment = new ElementFragment(node);
|
|
273
|
-
break;
|
|
274
|
-
case Node.TEXT_NODE:
|
|
275
|
-
fragment = new TextFragment(node);
|
|
276
|
-
break;
|
|
277
|
-
case Node.COMMENT_NODE:
|
|
278
|
-
fragment = new CommentFragment(node);
|
|
279
|
-
break;
|
|
280
|
-
default:
|
|
281
|
-
Log.warn('[Haori]', 'Unsupported node type:', node.nodeType);
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
return fragment;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* フラグメントのコンストラクタ。
|
|
288
|
-
*
|
|
289
|
-
* @param target 対象ノード
|
|
290
|
-
*/
|
|
291
|
-
constructor(target) {
|
|
292
|
-
/** 親フラグメント */
|
|
293
|
-
this.parent = null;
|
|
294
|
-
/** フラグメントがDOMにマウントされているかどうか */
|
|
295
|
-
this.mounted = false;
|
|
296
|
-
/** ノード更新スキップフラグ(オブザーバーによる無限ループ対応) */
|
|
297
|
-
this.skipMutationNodes = false;
|
|
298
|
-
this.target = target;
|
|
299
|
-
Fragment.FRAGMENT_CACHE.set(target, this);
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* skipMutationNodesフラグの値を取得します。
|
|
303
|
-
*
|
|
304
|
-
* @returns skipMutationNodesの値
|
|
305
|
-
*/
|
|
306
|
-
isSkipMutationNodes() {
|
|
307
|
-
return this.skipMutationNodes;
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* フラグメントをDOMから除去します。
|
|
311
|
-
*
|
|
312
|
-
* @return 除去のPromise
|
|
313
|
-
*/
|
|
314
|
-
unmount() {
|
|
315
|
-
if (!this.mounted || this.skipMutationNodes) {
|
|
316
|
-
return Promise.resolve();
|
|
317
|
-
}
|
|
318
|
-
if (this.parent) {
|
|
319
|
-
const parent = this.parent;
|
|
320
|
-
const prevSkip = parent.skipMutationNodes;
|
|
321
|
-
return Queue.enqueue(() => {
|
|
322
|
-
parent.skipMutationNodes = true;
|
|
323
|
-
if (this.target.parentNode === parent.getTarget()) {
|
|
324
|
-
parent.getTarget().removeChild(this.target);
|
|
325
|
-
}
|
|
326
|
-
this.mounted = false;
|
|
327
|
-
}).finally(() => {
|
|
328
|
-
parent.skipMutationNodes = prevSkip;
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
// 親フラグメント情報が無くても、DOM 上に親ノードが存在する場合は安全に除去する。
|
|
333
|
-
const host = this.target.parentNode;
|
|
334
|
-
if (host) {
|
|
335
|
-
return Queue.enqueue(() => {
|
|
336
|
-
if (this.target.parentNode === host) {
|
|
337
|
-
host.removeChild(this.target);
|
|
338
|
-
}
|
|
339
|
-
this.mounted = false;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
this.mounted = false;
|
|
343
|
-
}
|
|
344
|
-
return Promise.resolve();
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* フラグメントをDOMに追加します。
|
|
348
|
-
*
|
|
349
|
-
* @return 追加のPromise
|
|
350
|
-
*/
|
|
351
|
-
mount() {
|
|
352
|
-
if (this.mounted || this.skipMutationNodes) {
|
|
353
|
-
return Promise.resolve();
|
|
354
|
-
}
|
|
355
|
-
if (this.parent) {
|
|
356
|
-
const parent = this.parent;
|
|
357
|
-
const prevSkip = parent.skipMutationNodes;
|
|
358
|
-
return Queue.enqueue(() => {
|
|
359
|
-
parent.skipMutationNodes = true;
|
|
360
|
-
if (this.target.parentNode !== parent.getTarget()) {
|
|
361
|
-
// 既に同じ親なら何もしない
|
|
362
|
-
parent.getTarget().appendChild(this.target);
|
|
363
|
-
}
|
|
364
|
-
this.mounted = true;
|
|
365
|
-
}).finally(() => {
|
|
366
|
-
parent.skipMutationNodes = prevSkip;
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
return Promise.resolve();
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* フラグメントのマウント状態を取得します。
|
|
373
|
-
*
|
|
374
|
-
* @returns マウント状態
|
|
375
|
-
*/
|
|
376
|
-
isMounted() {
|
|
377
|
-
return this.mounted;
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* フラグメントのマウント状態を設定します。
|
|
381
|
-
*
|
|
382
|
-
* @param mounted マウント状態
|
|
383
|
-
*/
|
|
384
|
-
setMounted(mounted) {
|
|
385
|
-
this.mounted = mounted;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* フラグメントとノードを削除します。
|
|
389
|
-
*
|
|
390
|
-
* @param unmount DOMからの除去を行うかどうか(内部の子呼び出しの場合のみfalseとする)
|
|
391
|
-
* @return 除去のPromise
|
|
392
|
-
*/
|
|
393
|
-
remove(unmount = true) {
|
|
394
|
-
if (this.parent) {
|
|
395
|
-
this.parent.removeChild(this);
|
|
396
|
-
}
|
|
397
|
-
Fragment.FRAGMENT_CACHE.delete(this.target);
|
|
398
|
-
if (unmount) {
|
|
399
|
-
return this.unmount();
|
|
400
|
-
}
|
|
401
|
-
return Promise.resolve();
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* 対象ノードを取得します。
|
|
405
|
-
*
|
|
406
|
-
* @returns 対象ノード
|
|
407
|
-
*/
|
|
408
|
-
getTarget() {
|
|
409
|
-
return this.target;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* 親フラグメントを取得します。
|
|
413
|
-
*
|
|
414
|
-
* @returns 親フラグメント
|
|
415
|
-
*/
|
|
416
|
-
getParent() {
|
|
417
|
-
return this.parent;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* 親フラグメントを設定します。
|
|
421
|
-
*
|
|
422
|
-
* @param parent 親フラグメント
|
|
423
|
-
*/
|
|
424
|
-
setParent(parent) {
|
|
425
|
-
this.parent = parent;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
/** フラグメントの対象ノードに対するキャッシュ */
|
|
429
|
-
Fragment.FRAGMENT_CACHE = new WeakMap();
|
|
430
|
-
export default Fragment;
|
|
431
|
-
/**
|
|
432
|
-
* エレメントフラグメント。
|
|
433
|
-
* DOM要素を表現し、子ノードを持つことができます。
|
|
434
|
-
*/
|
|
435
|
-
export class ElementFragment extends Fragment {
|
|
436
|
-
/**
|
|
437
|
-
* エレメントフラグメントのコンストラクタ。
|
|
438
|
-
* アトリビュートや子フラグメントの作成も行います。
|
|
439
|
-
*
|
|
440
|
-
* @param target 対象エレメント
|
|
441
|
-
*/
|
|
442
|
-
constructor(target) {
|
|
443
|
-
super(target);
|
|
444
|
-
/** inputイベントを発生させるタイプ */
|
|
445
|
-
this.INPUT_EVENT_TYPES = [
|
|
446
|
-
'text',
|
|
447
|
-
'password',
|
|
448
|
-
'email',
|
|
449
|
-
'url',
|
|
450
|
-
'tel',
|
|
451
|
-
'search',
|
|
452
|
-
'number',
|
|
453
|
-
'range',
|
|
454
|
-
'color',
|
|
455
|
-
'date',
|
|
456
|
-
'datetime-local',
|
|
457
|
-
'month',
|
|
458
|
-
'time',
|
|
459
|
-
'week',
|
|
460
|
-
];
|
|
461
|
-
/** 子フラグメントのリスト */
|
|
462
|
-
this.children = [];
|
|
463
|
-
/** 属性名に対する属性情報のマップ */
|
|
464
|
-
this.attributeMap = new Map();
|
|
465
|
-
/** バインドデータ */
|
|
466
|
-
this.bindingData = null;
|
|
467
|
-
/** 子孫要素へ公開する派生バインドデータ */
|
|
468
|
-
this.derivedBindingData = null;
|
|
469
|
-
/** バインドデータのキャッシュ */
|
|
470
|
-
this.bindingDataCache = null;
|
|
471
|
-
/** 子孫要素向けバインドデータのキャッシュ */
|
|
472
|
-
this.descendantBindingDataCache = null;
|
|
473
|
-
/** 表示状態 */
|
|
474
|
-
this.visible = true;
|
|
475
|
-
/** 元の display 値 */
|
|
476
|
-
this.display = null;
|
|
477
|
-
/** 元の display の優先度 */
|
|
478
|
-
this.displayPriority = null;
|
|
479
|
-
/** each用のテンプレート */
|
|
480
|
-
this.template = null;
|
|
481
|
-
/** each比較用のキー */
|
|
482
|
-
this.listKey = null;
|
|
483
|
-
/** 直近に描画した each 行の入力署名 */
|
|
484
|
-
this.renderSignature = null;
|
|
485
|
-
/** 直近に描画した data-each 全体の入力署名 */
|
|
486
|
-
this.eachInputSignature = null;
|
|
487
|
-
/** 直近に公開した data-derive subtree の入力署名 */
|
|
488
|
-
this.deriveSubtreeSignature = null;
|
|
489
|
-
/** 直近に評価した data-derive の入力署名 */
|
|
490
|
-
this.deriveInputSignature = null;
|
|
491
|
-
/** fresh clone 初期化を subtree ごと省略できるかどうか */
|
|
492
|
-
this.freshInitializationSkippable = false;
|
|
493
|
-
/** valueプロパティの値 */
|
|
494
|
-
this.value = null;
|
|
495
|
-
/** 属性更新スキップフラグ(オブザーバーによる無限ループ対応) */
|
|
496
|
-
this.skipMutationAttributes = false;
|
|
497
|
-
/** 値変更スキップフラグ(更新イベントによる無限ループ対応) */
|
|
498
|
-
this.skipChangeValue = false;
|
|
499
|
-
this.syncValue();
|
|
500
|
-
target.getAttributeNames().forEach(name => {
|
|
501
|
-
const value = target.getAttribute(name);
|
|
502
|
-
if (value !== null && !this.attributeMap.has(name)) {
|
|
503
|
-
const contents = new AttributeContents(name, value);
|
|
504
|
-
this.attributeMap.set(name, contents);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
target.childNodes.forEach(node => {
|
|
508
|
-
const childFragment = Fragment.get(node);
|
|
509
|
-
childFragment.setParent(this);
|
|
510
|
-
this.children.push(childFragment);
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* 子フラグメントのリストを取得します。
|
|
515
|
-
*
|
|
516
|
-
* @returns 子フラグメントのリスト
|
|
517
|
-
*/
|
|
518
|
-
getChildren() {
|
|
519
|
-
return this.children;
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* 子エレメントフラグメントのリストを取得します。
|
|
523
|
-
*
|
|
524
|
-
* @returns 子エレメントフラグメントのリスト
|
|
525
|
-
*/
|
|
526
|
-
getChildElementFragments() {
|
|
527
|
-
return this.children.filter(child => child instanceof ElementFragment);
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* 子フラグメントをリストに追加します。
|
|
531
|
-
* DOMの追加は行いません。
|
|
532
|
-
*
|
|
533
|
-
* @param child 追加する子フラグメント
|
|
534
|
-
*/
|
|
535
|
-
pushChild(child) {
|
|
536
|
-
this.children.push(child);
|
|
537
|
-
child.setParent(this);
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* 子フラグメントをリストから削除します。
|
|
541
|
-
* DOMからの削除は行いません。
|
|
542
|
-
*
|
|
543
|
-
* @param child 削除する子フラグメント
|
|
544
|
-
*/
|
|
545
|
-
removeChild(child) {
|
|
546
|
-
const index = this.children.indexOf(child);
|
|
547
|
-
if (index < 0) {
|
|
548
|
-
Log.warn('[Haori]', 'Child fragment not found.', child);
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
this.children.splice(index, 1);
|
|
552
|
-
child.setParent(null);
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* フラグメントをクローンします。
|
|
556
|
-
*
|
|
557
|
-
* @returns クローンされたフラグメント
|
|
558
|
-
*/
|
|
559
|
-
clone() {
|
|
560
|
-
const clone = new ElementFragment(this.target.cloneNode(false));
|
|
561
|
-
// DOM 属性は評価後の値になっているため、
|
|
562
|
-
// クローンでは attributeMap をコピーしてテンプレート式を保持します。
|
|
563
|
-
this.attributeMap.forEach((contents, name) => {
|
|
564
|
-
clone.attributeMap.set(name, contents);
|
|
565
|
-
});
|
|
566
|
-
this.children.forEach(child => {
|
|
567
|
-
const childClone = child.clone();
|
|
568
|
-
clone.getTarget().appendChild(childClone.getTarget());
|
|
569
|
-
clone.pushChild(childClone);
|
|
570
|
-
});
|
|
571
|
-
clone.mounted = false;
|
|
572
|
-
clone.bindingData = this.bindingData;
|
|
573
|
-
clone.derivedBindingData = this.derivedBindingData;
|
|
574
|
-
clone.clearBindingDataCache();
|
|
575
|
-
clone.visible = true;
|
|
576
|
-
clone.display = this.display;
|
|
577
|
-
clone.displayPriority = this.displayPriority;
|
|
578
|
-
clone.template = this.template;
|
|
579
|
-
clone.renderSignature = this.renderSignature;
|
|
580
|
-
clone.eachInputSignature = this.eachInputSignature;
|
|
581
|
-
clone.deriveSubtreeSignature = null;
|
|
582
|
-
clone.deriveInputSignature = null;
|
|
583
|
-
clone.freshInitializationSkippable = this.freshInitializationSkippable;
|
|
584
|
-
clone.normalizeClonedVisibilityState();
|
|
585
|
-
return clone;
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* clone 時に runtime の hidden 状態だけを落とします。
|
|
589
|
-
*/
|
|
590
|
-
normalizeClonedVisibilityState() {
|
|
591
|
-
if (this.visible === false ||
|
|
592
|
-
this.getTarget().style.display === 'none' ||
|
|
593
|
-
this.getTarget().hasAttribute(`${Env.prefix}if-false`)) {
|
|
594
|
-
this.visible = true;
|
|
595
|
-
this.display = null;
|
|
596
|
-
this.displayPriority = null;
|
|
597
|
-
this.getTarget().style.removeProperty('display');
|
|
598
|
-
this.getTarget().removeAttribute(`${Env.prefix}if-false`);
|
|
599
|
-
}
|
|
600
|
-
this.children.forEach(child => {
|
|
601
|
-
if (child instanceof ElementFragment) {
|
|
602
|
-
child.normalizeClonedVisibilityState();
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* フラグメントとノードを削除します。
|
|
608
|
-
*
|
|
609
|
-
* @param unmount DOMからの除去を行うかどうか(内部の子呼び出しの場合のみfalseとする)
|
|
610
|
-
* @return 除去のPromise
|
|
611
|
-
*/
|
|
612
|
-
remove(unmount = true) {
|
|
613
|
-
const promises = [];
|
|
614
|
-
this.children.forEach(child => {
|
|
615
|
-
promises.push(child.remove(false));
|
|
616
|
-
});
|
|
617
|
-
this.children.length = 0;
|
|
618
|
-
this.attributeMap.clear();
|
|
619
|
-
this.bindingData = null;
|
|
620
|
-
this.bindingDataCache = null;
|
|
621
|
-
this.derivedBindingData = null;
|
|
622
|
-
this.descendantBindingDataCache = null;
|
|
623
|
-
if (this.template) {
|
|
624
|
-
promises.push(this.template.remove(false));
|
|
625
|
-
this.template = null;
|
|
626
|
-
}
|
|
627
|
-
this.eachInputSignature = null;
|
|
628
|
-
this.deriveSubtreeSignature = null;
|
|
629
|
-
this.deriveInputSignature = null;
|
|
630
|
-
promises.push(super.remove(unmount));
|
|
631
|
-
return Promise.all(promises).then(() => undefined);
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* フラグメントの対象エレメントを取得します。
|
|
635
|
-
*
|
|
636
|
-
* @returns フラグメントの対象エレメント
|
|
637
|
-
*/
|
|
638
|
-
getTarget() {
|
|
639
|
-
return this.target;
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* 継承を考慮したバインドデータを取得します。
|
|
643
|
-
*
|
|
644
|
-
* @returns バインドデータのオブジェクト
|
|
645
|
-
*/
|
|
646
|
-
getBindingData() {
|
|
647
|
-
if (this.bindingDataCache) {
|
|
648
|
-
return this.bindingDataCache;
|
|
649
|
-
}
|
|
650
|
-
this.bindingDataCache = {};
|
|
651
|
-
if (this.parent) {
|
|
652
|
-
Object.assign(this.bindingDataCache, this.parent.getDescendantBindingData());
|
|
653
|
-
}
|
|
654
|
-
if (this.bindingData) {
|
|
655
|
-
Object.assign(this.bindingDataCache, this.bindingData);
|
|
656
|
-
}
|
|
657
|
-
return this.bindingDataCache;
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* 子孫要素向けのバインドデータを取得します。
|
|
661
|
-
*
|
|
662
|
-
* @returns 子孫要素向けのバインドデータ
|
|
663
|
-
*/
|
|
664
|
-
getDescendantBindingData() {
|
|
665
|
-
if (this.descendantBindingDataCache) {
|
|
666
|
-
return this.descendantBindingDataCache;
|
|
667
|
-
}
|
|
668
|
-
this.descendantBindingDataCache = { ...this.getBindingData() };
|
|
669
|
-
if (this.derivedBindingData) {
|
|
670
|
-
Object.assign(this.descendantBindingDataCache, this.derivedBindingData);
|
|
671
|
-
}
|
|
672
|
-
return this.descendantBindingDataCache;
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* 生のバインドデータを取得します。
|
|
676
|
-
*
|
|
677
|
-
* @returns 生のバインドデータ
|
|
678
|
-
*/
|
|
679
|
-
getRawBindingData() {
|
|
680
|
-
return this.bindingData;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* 生の派生バインドデータを取得します。
|
|
684
|
-
*
|
|
685
|
-
* @returns 生の派生バインドデータ
|
|
686
|
-
*/
|
|
687
|
-
getRawDerivedBindingData() {
|
|
688
|
-
return this.derivedBindingData;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* バインドデータを設定します。
|
|
692
|
-
*
|
|
693
|
-
* @param data バインドデータ
|
|
694
|
-
*/
|
|
695
|
-
setBindingData(data) {
|
|
696
|
-
this.bindingData = data;
|
|
697
|
-
this.clearBindingDataCache();
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* 子孫要素向けの派生バインドデータを設定します。
|
|
701
|
-
*
|
|
702
|
-
* @param data 派生バインドデータ。解除する場合は null
|
|
703
|
-
*/
|
|
704
|
-
setDerivedBindingData(data) {
|
|
705
|
-
this.derivedBindingData = data;
|
|
706
|
-
this.clearBindingDataCache();
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* 親フラグメントを設定します。バインドデータキャッシュをクリアします。
|
|
710
|
-
*
|
|
711
|
-
* @param parent 親フラグメント
|
|
712
|
-
*/
|
|
713
|
-
setParent(parent) {
|
|
714
|
-
if (this.parent === parent) {
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
this.parent = parent;
|
|
718
|
-
this.clearBindingDataCache();
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* バインドデータのキャッシュをクリアします。
|
|
722
|
-
*/
|
|
723
|
-
clearBindingDataCache() {
|
|
724
|
-
this.bindingDataCache = null;
|
|
725
|
-
this.descendantBindingDataCache = null;
|
|
726
|
-
this.children.forEach(child => {
|
|
727
|
-
if (child instanceof ElementFragment) {
|
|
728
|
-
child.clearBindingDataCache();
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* フラグメントのテンプレートを取得します。
|
|
734
|
-
*
|
|
735
|
-
* @returns テンプレート
|
|
736
|
-
*/
|
|
737
|
-
getTemplate() {
|
|
738
|
-
return this.template;
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* フラグメントのテンプレートを設定します。
|
|
742
|
-
*
|
|
743
|
-
* @param template フラグメントのテンプレート
|
|
744
|
-
*/
|
|
745
|
-
setTemplate(template) {
|
|
746
|
-
this.template = template;
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* 比較用リストキーを設定します。
|
|
750
|
-
*
|
|
751
|
-
* @param key 比較用リストキー
|
|
752
|
-
*/
|
|
753
|
-
setListKey(key) {
|
|
754
|
-
this.listKey = key;
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* 比較用リストキーを取得します。
|
|
758
|
-
*
|
|
759
|
-
* @returns 比較用リストキー
|
|
760
|
-
*/
|
|
761
|
-
getListKey() {
|
|
762
|
-
return this.listKey;
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* 直近に描画した each 行の入力署名を取得します。
|
|
766
|
-
*
|
|
767
|
-
* @returns 入力署名
|
|
768
|
-
*/
|
|
769
|
-
getRenderSignature() {
|
|
770
|
-
return this.renderSignature;
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* 直近に描画した each 行の入力署名を設定します。
|
|
774
|
-
*
|
|
775
|
-
* @param signature 入力署名
|
|
776
|
-
*/
|
|
777
|
-
setRenderSignature(signature) {
|
|
778
|
-
this.renderSignature = signature;
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* 直近に描画した data-each 全体の入力署名を取得します。
|
|
782
|
-
*
|
|
783
|
-
* @returns 入力署名
|
|
784
|
-
*/
|
|
785
|
-
getEachInputSignature() {
|
|
786
|
-
return this.eachInputSignature;
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* 直近に描画した data-each 全体の入力署名を設定します。
|
|
790
|
-
*
|
|
791
|
-
* @param signature 入力署名
|
|
792
|
-
*/
|
|
793
|
-
setEachInputSignature(signature) {
|
|
794
|
-
this.eachInputSignature = signature;
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* 直近に公開した data-derive subtree の入力署名を取得します。
|
|
798
|
-
*
|
|
799
|
-
* @returns 入力署名
|
|
800
|
-
*/
|
|
801
|
-
getDeriveSubtreeSignature() {
|
|
802
|
-
return this.deriveSubtreeSignature;
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* 直近に公開した data-derive subtree の入力署名を設定します。
|
|
806
|
-
*
|
|
807
|
-
* @param signature 入力署名
|
|
808
|
-
*/
|
|
809
|
-
setDeriveSubtreeSignature(signature) {
|
|
810
|
-
this.deriveSubtreeSignature = signature;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* 直近に評価した data-derive の入力署名を取得します。
|
|
814
|
-
*
|
|
815
|
-
* @returns 入力署名
|
|
816
|
-
*/
|
|
817
|
-
getDeriveInputSignature() {
|
|
818
|
-
return this.deriveInputSignature;
|
|
819
|
-
}
|
|
820
|
-
/**
|
|
821
|
-
* 直近に評価した data-derive の入力署名を設定します。
|
|
822
|
-
*
|
|
823
|
-
* @param signature 入力署名
|
|
824
|
-
*/
|
|
825
|
-
setDeriveInputSignature(signature) {
|
|
826
|
-
this.deriveInputSignature = signature;
|
|
827
|
-
}
|
|
828
|
-
/**
|
|
829
|
-
* fresh clone 初期化を subtree ごと省略できるかどうかを返します。
|
|
830
|
-
*
|
|
831
|
-
* @returns 省略可能なら true
|
|
832
|
-
*/
|
|
833
|
-
isFreshInitializationSkippable() {
|
|
834
|
-
return this.freshInitializationSkippable;
|
|
835
|
-
}
|
|
836
|
-
/**
|
|
837
|
-
* fresh clone 初期化を subtree ごと省略できるかどうかを設定します。
|
|
838
|
-
*
|
|
839
|
-
* @param skippable 省略可能なら true
|
|
840
|
-
*/
|
|
841
|
-
setFreshInitializationSkippable(skippable) {
|
|
842
|
-
this.freshInitializationSkippable = skippable;
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* 入力エレメントに値を設定します。
|
|
846
|
-
* チェックボックとラジオボタンの場合は値に一致するかどうかでチェック状態を変更します。
|
|
847
|
-
*
|
|
848
|
-
* @param value 値
|
|
849
|
-
* @returns エレメントの更新のPromise
|
|
850
|
-
*/
|
|
851
|
-
setValue(value) {
|
|
852
|
-
return this.applyValue(value, true);
|
|
853
|
-
}
|
|
854
|
-
/**
|
|
855
|
-
* 入力エレメントに値をイベントなしで設定します。
|
|
856
|
-
* フォームの bindingData 反映時に内部同期として利用します。
|
|
857
|
-
*
|
|
858
|
-
* @param value 値
|
|
859
|
-
* @returns エレメントの更新のPromise
|
|
860
|
-
*/
|
|
861
|
-
syncBindingValue(value) {
|
|
862
|
-
return this.applyValue(value, false);
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* 入力エレメントに値を設定します。
|
|
866
|
-
* 必要に応じて入力系イベントも発火します。
|
|
867
|
-
*
|
|
868
|
-
* @param value 値
|
|
869
|
-
* @param dispatchEvents input/change イベントを発火するかどうか
|
|
870
|
-
* @returns エレメントの更新のPromise
|
|
871
|
-
*/
|
|
872
|
-
applyValue(value, dispatchEvents) {
|
|
873
|
-
if (this.skipChangeValue) {
|
|
874
|
-
return Promise.resolve();
|
|
875
|
-
}
|
|
876
|
-
if (this.value === value) {
|
|
877
|
-
return Promise.resolve();
|
|
878
|
-
}
|
|
879
|
-
const element = this.getTarget();
|
|
880
|
-
if (element instanceof HTMLInputElement &&
|
|
881
|
-
(element.type === 'checkbox' || element.type === 'radio')) {
|
|
882
|
-
const result = this.getAttribute('value');
|
|
883
|
-
const isBooleanCheckbox = element.type === 'checkbox' && result === 'true';
|
|
884
|
-
let newChecked;
|
|
885
|
-
if (isBooleanCheckbox) {
|
|
886
|
-
newChecked = value === true || value === 'true';
|
|
887
|
-
}
|
|
888
|
-
else if (result === 'false') {
|
|
889
|
-
newChecked = value === false;
|
|
890
|
-
}
|
|
891
|
-
else {
|
|
892
|
-
newChecked = result === String(value);
|
|
893
|
-
}
|
|
894
|
-
this.value = isBooleanCheckbox ? newChecked : newChecked ? value : null;
|
|
895
|
-
if (element.checked === newChecked) {
|
|
896
|
-
return Promise.resolve();
|
|
897
|
-
}
|
|
898
|
-
this.skipChangeValue = true;
|
|
899
|
-
return Queue.enqueue(() => {
|
|
900
|
-
element.checked = newChecked;
|
|
901
|
-
if (dispatchEvents) {
|
|
902
|
-
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
903
|
-
}
|
|
904
|
-
}).finally(() => {
|
|
905
|
-
this.skipChangeValue = false;
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
else if (element instanceof HTMLInputElement ||
|
|
909
|
-
element instanceof HTMLTextAreaElement ||
|
|
910
|
-
element instanceof HTMLSelectElement) {
|
|
911
|
-
// type="number" は内部値を数値へ正規化して保持する(送信時に数値型になる)
|
|
912
|
-
this.value = this.normalizeValueForElement(element, value);
|
|
913
|
-
this.skipChangeValue = true;
|
|
914
|
-
return Queue.enqueue(() => {
|
|
915
|
-
element.value = value === null ? '' : String(value);
|
|
916
|
-
if (dispatchEvents) {
|
|
917
|
-
if ((element instanceof HTMLInputElement &&
|
|
918
|
-
this.INPUT_EVENT_TYPES.includes(element.type)) ||
|
|
919
|
-
element instanceof HTMLTextAreaElement) {
|
|
920
|
-
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
921
|
-
}
|
|
922
|
-
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
923
|
-
}
|
|
924
|
-
}).finally(() => {
|
|
925
|
-
this.skipChangeValue = false;
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
else {
|
|
929
|
-
Log.warn('[Haori]', 'setValue is not supported for this element type.', element);
|
|
930
|
-
return Promise.resolve();
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* 入力エレメントの値を取得します。
|
|
935
|
-
* DOM要素の現在の値と同期します。
|
|
936
|
-
*
|
|
937
|
-
* @returns 入力エレメントの値
|
|
938
|
-
*/
|
|
939
|
-
getValue() {
|
|
940
|
-
return this.value;
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* 内部の値をクリアします。エレメントのvalue値は変化しません。
|
|
944
|
-
*/
|
|
945
|
-
clearValue() {
|
|
946
|
-
this.value = null;
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* 入力要素の種別に応じて値を正規化します。
|
|
950
|
-
*
|
|
951
|
-
* `type="number"` の input では、文字列の入力値・バインド値を数値へ変換します
|
|
952
|
-
* (DTO が数値型を期待する場合に文字列で送られるのを防ぐため)。空文字・null・
|
|
953
|
-
* 数値化できない値は `null` を返します。それ以外の要素では値をそのまま返します。
|
|
954
|
-
*
|
|
955
|
-
* @param element 対象の入力要素
|
|
956
|
-
* @param value 正規化する値
|
|
957
|
-
* @returns 正規化後の値(`type="number"` なら数値または null)
|
|
958
|
-
*/
|
|
959
|
-
normalizeValueForElement(element, value) {
|
|
960
|
-
if (element instanceof HTMLInputElement && element.type === 'number') {
|
|
961
|
-
if (value === null || value === '') {
|
|
962
|
-
return null;
|
|
963
|
-
}
|
|
964
|
-
const numeric = typeof value === 'number' ? value : Number(value);
|
|
965
|
-
return Number.isNaN(numeric) ? null : numeric;
|
|
966
|
-
}
|
|
967
|
-
return value;
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* 内部の値をDOMの値と同期します。
|
|
971
|
-
* changeイベント時など、DOM値が変更された後に呼び出されます。
|
|
972
|
-
*/
|
|
973
|
-
syncValue() {
|
|
974
|
-
const element = this.getTarget();
|
|
975
|
-
if (element instanceof HTMLInputElement) {
|
|
976
|
-
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
977
|
-
const isBooleanCheckbox = element.type === 'checkbox' && element.value === 'true';
|
|
978
|
-
if (element.checked) {
|
|
979
|
-
const value = element.value;
|
|
980
|
-
if (isBooleanCheckbox) {
|
|
981
|
-
this.value = true;
|
|
982
|
-
}
|
|
983
|
-
else if (value === 'false') {
|
|
984
|
-
this.value = false;
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
this.value = value;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
// チェックボックスがOFFの場合
|
|
992
|
-
const value = element.value;
|
|
993
|
-
if (isBooleanCheckbox) {
|
|
994
|
-
this.value = false;
|
|
995
|
-
}
|
|
996
|
-
else if (value === 'false') {
|
|
997
|
-
this.value = true;
|
|
998
|
-
}
|
|
999
|
-
else {
|
|
1000
|
-
this.value = null;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
else {
|
|
1005
|
-
// type="number" は数値へ正規化し、それ以外は文字列のまま保持する
|
|
1006
|
-
this.value = this.normalizeValueForElement(element, element.value);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
else if (element instanceof HTMLTextAreaElement) {
|
|
1010
|
-
this.value = element.value;
|
|
1011
|
-
}
|
|
1012
|
-
else if (element instanceof HTMLSelectElement) {
|
|
1013
|
-
this.value = element.value;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
/**
|
|
1017
|
-
* 属性の値を評価して設定します。
|
|
1018
|
-
* 評価値がfalseの場合は属性を削除します。
|
|
1019
|
-
* 矯正評価属性の場合は元の値を設定します。
|
|
1020
|
-
*
|
|
1021
|
-
* @param name 属性名
|
|
1022
|
-
* @param value 属性値
|
|
1023
|
-
* @returns 属性の更新のPromise
|
|
1024
|
-
*/
|
|
1025
|
-
setAttribute(name, value, fromObserver = false) {
|
|
1026
|
-
return this.setAttributeInternal(name, name, value, true, fromObserver);
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* data-attr-* の生値を保持しつつ、別名の属性へ評価結果を反映します。
|
|
1030
|
-
*
|
|
1031
|
-
* @param rawName 生の属性名
|
|
1032
|
-
* @param targetName 反映先の属性名
|
|
1033
|
-
* @param value 生の属性値
|
|
1034
|
-
* @returns 属性更新の Promise
|
|
1035
|
-
*/
|
|
1036
|
-
setAliasedAttribute(rawName, targetName, value, fromObserver = false) {
|
|
1037
|
-
return this.setAttributeInternal(rawName, targetName, value, false, fromObserver);
|
|
1038
|
-
}
|
|
1039
|
-
/**
|
|
1040
|
-
* data-attr-* の生属性と反映先属性を同時に削除します。
|
|
1041
|
-
*
|
|
1042
|
-
* @param rawName 生の属性名
|
|
1043
|
-
* @param targetName 反映先の属性名
|
|
1044
|
-
* @returns 属性削除の Promise
|
|
1045
|
-
*/
|
|
1046
|
-
removeAliasedAttribute(rawName, targetName) {
|
|
1047
|
-
if (this.skipMutationAttributes) {
|
|
1048
|
-
return Promise.resolve();
|
|
1049
|
-
}
|
|
1050
|
-
this.attributeMap.delete(rawName);
|
|
1051
|
-
this.skipMutationAttributes = true;
|
|
1052
|
-
const element = this.getTarget();
|
|
1053
|
-
return Queue.enqueue(() => {
|
|
1054
|
-
element.removeAttribute(rawName);
|
|
1055
|
-
if (targetName !== rawName) {
|
|
1056
|
-
element.removeAttribute(targetName);
|
|
1057
|
-
}
|
|
1058
|
-
}).finally(() => {
|
|
1059
|
-
this.skipMutationAttributes = false;
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
/**
|
|
1063
|
-
* 生の属性値を保持しつつ、必要に応じて別名属性へ評価結果を反映します。
|
|
1064
|
-
*
|
|
1065
|
-
* @param rawName 生の属性名
|
|
1066
|
-
* @param targetName 反映先の属性名
|
|
1067
|
-
* @param value 生の属性値
|
|
1068
|
-
* @param syncValueProperty value 属性更新時に DOM property も同期するかどうか
|
|
1069
|
-
* @returns 属性更新の Promise
|
|
1070
|
-
*/
|
|
1071
|
-
setAttributeInternal(rawName, targetName, value, syncValueProperty, fromObserver = false) {
|
|
1072
|
-
if (this.skipMutationAttributes) {
|
|
1073
|
-
return Promise.resolve();
|
|
1074
|
-
}
|
|
1075
|
-
if (value === null) {
|
|
1076
|
-
if (rawName === targetName) {
|
|
1077
|
-
return this.removeAttribute(rawName);
|
|
1078
|
-
}
|
|
1079
|
-
return this.removeAliasedAttribute(rawName, targetName);
|
|
1080
|
-
}
|
|
1081
|
-
const contents = new AttributeContents(rawName, value);
|
|
1082
|
-
if (fromObserver) {
|
|
1083
|
-
// MutationObserver経由の書き戻し(展開済み値)でテンプレート式を含む既存エントリを上書きしない。
|
|
1084
|
-
// (例: href="...{{customerCode}}..." が展開された後にObserverが展開済み値を再セットするのを防ぐ)
|
|
1085
|
-
const existing = this.attributeMap.get(rawName);
|
|
1086
|
-
if (existing &&
|
|
1087
|
-
(existing.isEvaluate || existing.isForceEvaluation()) &&
|
|
1088
|
-
!contents.isEvaluate &&
|
|
1089
|
-
!contents.isForceEvaluation()) {
|
|
1090
|
-
this.skipMutationAttributes = true;
|
|
1091
|
-
return Queue.enqueue(() => { }).finally(() => {
|
|
1092
|
-
this.skipMutationAttributes = false;
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
this.attributeMap.set(rawName, contents);
|
|
1097
|
-
const element = this.getTarget();
|
|
1098
|
-
const detail = contents.evaluateDetailed(this.getBindingData(), {
|
|
1099
|
-
kind: 'attribute',
|
|
1100
|
-
element,
|
|
1101
|
-
rawName,
|
|
1102
|
-
template: value,
|
|
1103
|
-
});
|
|
1104
|
-
const hasTemplateExpression = contents.isEvaluate || contents.isRawEvaluate;
|
|
1105
|
-
const isBooleanAttribute = rawName === targetName &&
|
|
1106
|
-
ElementFragment.BOOLEAN_ATTRIBUTES.has(targetName.toLowerCase());
|
|
1107
|
-
const isSingleExpression = contents.isSingleExpression();
|
|
1108
|
-
const joinedValue = TextContents.joinEvaluateResults(detail.results);
|
|
1109
|
-
const evaluatedValue = detail.results.length === 1 ? detail.results[0] : joinedValue;
|
|
1110
|
-
const shouldRemoveTarget = !contents.isForceEvaluation() &&
|
|
1111
|
-
(targetName !== rawName
|
|
1112
|
-
? detail.hasUnresolvedReference ||
|
|
1113
|
-
evaluatedValue === null ||
|
|
1114
|
-
evaluatedValue === undefined ||
|
|
1115
|
-
evaluatedValue === false
|
|
1116
|
-
: isBooleanAttribute
|
|
1117
|
-
? detail.hasUnresolvedReference ||
|
|
1118
|
-
evaluatedValue === null ||
|
|
1119
|
-
evaluatedValue === undefined ||
|
|
1120
|
-
evaluatedValue === false
|
|
1121
|
-
: isSingleExpression
|
|
1122
|
-
? detail.hasUnresolvedReference ||
|
|
1123
|
-
evaluatedValue === null ||
|
|
1124
|
-
evaluatedValue === undefined ||
|
|
1125
|
-
evaluatedValue === false
|
|
1126
|
-
: hasTemplateExpression && joinedValue === '');
|
|
1127
|
-
const result = contents.isForceEvaluation()
|
|
1128
|
-
? value
|
|
1129
|
-
: isSingleExpression
|
|
1130
|
-
? evaluatedValue
|
|
1131
|
-
: joinedValue;
|
|
1132
|
-
const shouldSyncValueProperty = syncValueProperty &&
|
|
1133
|
-
contents.isEvaluate &&
|
|
1134
|
-
targetName === 'value' &&
|
|
1135
|
-
((element instanceof HTMLInputElement &&
|
|
1136
|
-
this.INPUT_EVENT_TYPES.includes(element.type)) ||
|
|
1137
|
-
element instanceof HTMLTextAreaElement ||
|
|
1138
|
-
element instanceof HTMLSelectElement);
|
|
1139
|
-
const stringResult = shouldRemoveTarget || result === null || result === false
|
|
1140
|
-
? null
|
|
1141
|
-
: String(result);
|
|
1142
|
-
const requiresRawAttributeWrite = rawName !== targetName && element.getAttribute(rawName) !== value;
|
|
1143
|
-
const requiresTargetAttributeWrite = stringResult === null
|
|
1144
|
-
? element.hasAttribute(targetName)
|
|
1145
|
-
: element.getAttribute(targetName) !== stringResult;
|
|
1146
|
-
const requiresValuePropertyWrite = shouldSyncValueProperty &&
|
|
1147
|
-
stringResult !== null &&
|
|
1148
|
-
element.value !== stringResult;
|
|
1149
|
-
if (!requiresRawAttributeWrite &&
|
|
1150
|
-
!requiresTargetAttributeWrite &&
|
|
1151
|
-
!requiresValuePropertyWrite) {
|
|
1152
|
-
if (shouldSyncValueProperty && stringResult !== null) {
|
|
1153
|
-
this.value = stringResult;
|
|
1154
|
-
}
|
|
1155
|
-
return Promise.resolve();
|
|
1156
|
-
}
|
|
1157
|
-
this.skipMutationAttributes = true;
|
|
1158
|
-
return Queue.enqueue(() => {
|
|
1159
|
-
if (requiresRawAttributeWrite) {
|
|
1160
|
-
element.setAttribute(rawName, value);
|
|
1161
|
-
}
|
|
1162
|
-
if (stringResult === null) {
|
|
1163
|
-
element.removeAttribute(targetName);
|
|
1164
|
-
}
|
|
1165
|
-
else {
|
|
1166
|
-
if (requiresTargetAttributeWrite) {
|
|
1167
|
-
element.setAttribute(targetName, stringResult);
|
|
1168
|
-
}
|
|
1169
|
-
// element.setAttribute('value', ...) は defaultValue のみ更新するため、
|
|
1170
|
-
// setValue と同じ対象には element.value も反映して DOM と内部状態を揃える。
|
|
1171
|
-
if (shouldSyncValueProperty) {
|
|
1172
|
-
this.value = stringResult;
|
|
1173
|
-
if (requiresValuePropertyWrite) {
|
|
1174
|
-
element.value = stringResult;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
}).finally(() => {
|
|
1179
|
-
this.skipMutationAttributes = false;
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* 属性の値を削除します。
|
|
1184
|
-
*
|
|
1185
|
-
* @param name 属性名
|
|
1186
|
-
* @returns 属性の削除のPromise
|
|
1187
|
-
*/
|
|
1188
|
-
removeAttribute(name) {
|
|
1189
|
-
if (this.skipMutationAttributes) {
|
|
1190
|
-
return Promise.resolve();
|
|
1191
|
-
}
|
|
1192
|
-
this.attributeMap.delete(name);
|
|
1193
|
-
this.skipMutationAttributes = true;
|
|
1194
|
-
const element = this.getTarget();
|
|
1195
|
-
return Queue.enqueue(() => {
|
|
1196
|
-
element.removeAttribute(name);
|
|
1197
|
-
}).finally(() => {
|
|
1198
|
-
this.skipMutationAttributes = false;
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
/**
|
|
1202
|
-
* 属性の評価された値を取得します。
|
|
1203
|
-
* 複数の評価値がある場合は結合して返します。
|
|
1204
|
-
*
|
|
1205
|
-
* @param name 属性名
|
|
1206
|
-
* @returns 評価された値
|
|
1207
|
-
*/
|
|
1208
|
-
getAttribute(name) {
|
|
1209
|
-
const detail = this.getAttributeEvaluation(name);
|
|
1210
|
-
return detail?.value ?? null;
|
|
1211
|
-
}
|
|
1212
|
-
/**
|
|
1213
|
-
* 属性の評価値と未解決参照の有無を取得します。
|
|
1214
|
-
*
|
|
1215
|
-
* @param name 属性名
|
|
1216
|
-
* @returns 属性評価の詳細。属性が存在しない場合は null
|
|
1217
|
-
*/
|
|
1218
|
-
getAttributeEvaluation(name) {
|
|
1219
|
-
const contents = this.attributeMap.get(name);
|
|
1220
|
-
if (contents === undefined) {
|
|
1221
|
-
return null;
|
|
1222
|
-
}
|
|
1223
|
-
const detail = contents.evaluateDetailed(this.getBindingData(), {
|
|
1224
|
-
kind: 'attribute',
|
|
1225
|
-
element: this.getTarget(),
|
|
1226
|
-
rawName: name,
|
|
1227
|
-
template: contents.getValue(),
|
|
1228
|
-
});
|
|
1229
|
-
if (detail.results.length === 1) {
|
|
1230
|
-
return {
|
|
1231
|
-
value: detail.results[0],
|
|
1232
|
-
hasUnresolvedReference: detail.hasUnresolvedReference,
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
return {
|
|
1236
|
-
value: TextContents.joinEvaluateResults(detail.results),
|
|
1237
|
-
hasUnresolvedReference: detail.hasUnresolvedReference,
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
/**
|
|
1241
|
-
* 属性の生の値を取得します。
|
|
1242
|
-
*
|
|
1243
|
-
* @param name 属性名
|
|
1244
|
-
* @returns 生の属性値
|
|
1245
|
-
*/
|
|
1246
|
-
getRawAttribute(name) {
|
|
1247
|
-
const contents = this.attributeMap.get(name);
|
|
1248
|
-
if (contents === undefined) {
|
|
1249
|
-
return null;
|
|
1250
|
-
}
|
|
1251
|
-
return contents.getValue();
|
|
1252
|
-
}
|
|
1253
|
-
/**
|
|
1254
|
-
* 属性名のリストを取得します。
|
|
1255
|
-
*
|
|
1256
|
-
* @return 属性名のリスト
|
|
1257
|
-
*/
|
|
1258
|
-
getAttributeNames() {
|
|
1259
|
-
return Array.from(this.attributeMap.keys());
|
|
1260
|
-
}
|
|
1261
|
-
/**
|
|
1262
|
-
* 属性の有無を確認します。
|
|
1263
|
-
*
|
|
1264
|
-
* @param name 属性名
|
|
1265
|
-
* @returns 属性の有無
|
|
1266
|
-
*/
|
|
1267
|
-
hasAttribute(name) {
|
|
1268
|
-
return this.attributeMap.has(name);
|
|
1269
|
-
}
|
|
1270
|
-
/**
|
|
1271
|
-
* DOM上の順序から、参照フラグメントに対応する children 配列の挿入位置を推定します。
|
|
1272
|
-
*
|
|
1273
|
-
* @param referenceChild 参照フラグメント
|
|
1274
|
-
* @param insertAfter 参照位置の後ろに挿入するかどうか
|
|
1275
|
-
* @returns 挿入位置。解決できない場合はnull
|
|
1276
|
-
*/
|
|
1277
|
-
resolveInsertionPointFromDom(referenceChild, insertAfter) {
|
|
1278
|
-
const referenceNode = referenceChild.getTarget();
|
|
1279
|
-
if (referenceNode.parentNode !== this.target) {
|
|
1280
|
-
return null;
|
|
1281
|
-
}
|
|
1282
|
-
const insertionReferenceNode = insertAfter
|
|
1283
|
-
? referenceNode.nextSibling
|
|
1284
|
-
: referenceNode;
|
|
1285
|
-
let nextTrackedNode = insertAfter
|
|
1286
|
-
? referenceNode.nextSibling
|
|
1287
|
-
: referenceNode;
|
|
1288
|
-
while (nextTrackedNode !== null) {
|
|
1289
|
-
const childFragment = Fragment.get(nextTrackedNode);
|
|
1290
|
-
if (childFragment !== null) {
|
|
1291
|
-
const childIndex = this.children.indexOf(childFragment);
|
|
1292
|
-
if (childIndex !== -1) {
|
|
1293
|
-
return { index: childIndex, referenceNode: insertionReferenceNode };
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
nextTrackedNode = nextTrackedNode.nextSibling;
|
|
1297
|
-
}
|
|
1298
|
-
return { index: this.children.length, referenceNode: insertionReferenceNode };
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* 子ノードを参照ノードの前に挿入します。
|
|
1302
|
-
* 参照ノードがnullの場合、親の最後に追加されます。
|
|
1303
|
-
*
|
|
1304
|
-
* @param newChild 新しい子ノード
|
|
1305
|
-
* @param referenceChild 参照ノード
|
|
1306
|
-
* @return 挿入のPromise
|
|
1307
|
-
*/
|
|
1308
|
-
insertBefore(newChild, referenceChild, referenceNodeOverride) {
|
|
1309
|
-
if (this.skipMutationNodes) {
|
|
1310
|
-
return Promise.resolve();
|
|
1311
|
-
}
|
|
1312
|
-
// 循環参照チェック
|
|
1313
|
-
if (newChild === this) {
|
|
1314
|
-
Log.error('[Haori]', 'Cannot insert element as child of itself');
|
|
1315
|
-
return Promise.reject(new Error('Self-insertion not allowed'));
|
|
1316
|
-
}
|
|
1317
|
-
// 祖先チェック
|
|
1318
|
-
const ancestors = new Set();
|
|
1319
|
-
let ancestor = this.parent;
|
|
1320
|
-
while (ancestor) {
|
|
1321
|
-
ancestors.add(ancestor);
|
|
1322
|
-
ancestor = ancestor.getParent();
|
|
1323
|
-
}
|
|
1324
|
-
if (ancestors.has(newChild)) {
|
|
1325
|
-
Log.error('[Haori]', 'Cannot create circular reference');
|
|
1326
|
-
return Promise.reject(new Error('Circular reference detected'));
|
|
1327
|
-
}
|
|
1328
|
-
// 同じ親内での移動かどうかを確認
|
|
1329
|
-
const isSameParent = newChild.getParent() === this;
|
|
1330
|
-
let newChildIndex = -1;
|
|
1331
|
-
let referenceIndex = -1;
|
|
1332
|
-
if (isSameParent) {
|
|
1333
|
-
newChildIndex = this.children.indexOf(newChild);
|
|
1334
|
-
if (referenceChild !== null) {
|
|
1335
|
-
referenceIndex = this.children.indexOf(referenceChild);
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
const newChildParent = newChild.getParent();
|
|
1339
|
-
if (newChildParent !== null) {
|
|
1340
|
-
// 既存の親から削除
|
|
1341
|
-
newChildParent.removeChild(newChild);
|
|
1342
|
-
}
|
|
1343
|
-
let referenceNode = referenceNodeOverride === undefined
|
|
1344
|
-
? referenceChild?.getTarget() || null
|
|
1345
|
-
: referenceNodeOverride;
|
|
1346
|
-
if (referenceChild === null) {
|
|
1347
|
-
this.children.push(newChild);
|
|
1348
|
-
}
|
|
1349
|
-
else {
|
|
1350
|
-
let index;
|
|
1351
|
-
if (isSameParent) {
|
|
1352
|
-
// 同じ親内での移動の場合、削除後のインデックスを調整
|
|
1353
|
-
if (newChildIndex !== -1 && newChildIndex < referenceIndex) {
|
|
1354
|
-
// 削除する要素が参照要素より前にあった場合、インデックスは1つ減る
|
|
1355
|
-
index = referenceIndex - 1;
|
|
1356
|
-
}
|
|
1357
|
-
else {
|
|
1358
|
-
index = referenceIndex;
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
else {
|
|
1362
|
-
index = this.children.indexOf(referenceChild);
|
|
1363
|
-
}
|
|
1364
|
-
if (index === -1) {
|
|
1365
|
-
const insertionPoint = this.resolveInsertionPointFromDom(referenceChild, false);
|
|
1366
|
-
if (insertionPoint === null) {
|
|
1367
|
-
Log.warn('[Haori]', 'Reference child not found in children.', referenceChild);
|
|
1368
|
-
this.children.push(newChild);
|
|
1369
|
-
}
|
|
1370
|
-
else {
|
|
1371
|
-
this.children.splice(insertionPoint.index, 0, newChild);
|
|
1372
|
-
referenceNode = insertionPoint.referenceNode;
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
else {
|
|
1376
|
-
this.children.splice(index, 0, newChild);
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
newChild.setParent(this);
|
|
1380
|
-
newChild.setMounted(this.mounted);
|
|
1381
|
-
const prevSkip = this.skipMutationNodes;
|
|
1382
|
-
this.skipMutationNodes = true;
|
|
1383
|
-
return Queue.enqueue(() => {
|
|
1384
|
-
this.target.insertBefore(newChild.getTarget(), referenceNode);
|
|
1385
|
-
}).finally(() => {
|
|
1386
|
-
this.skipMutationNodes = prevSkip;
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
|
-
/**
|
|
1390
|
-
* 指定した参照ノードの後に子ノードを挿入します。
|
|
1391
|
-
*
|
|
1392
|
-
* @param newChild 子ノード
|
|
1393
|
-
* @param referenceChild 参照ノード
|
|
1394
|
-
* @returns 挿入のPromise
|
|
1395
|
-
*/
|
|
1396
|
-
insertAfter(newChild, referenceChild) {
|
|
1397
|
-
if (referenceChild == null) {
|
|
1398
|
-
return this.insertBefore(newChild, null);
|
|
1399
|
-
}
|
|
1400
|
-
const index = this.children.indexOf(referenceChild);
|
|
1401
|
-
if (index === -1) {
|
|
1402
|
-
const insertionPoint = this.resolveInsertionPointFromDom(referenceChild, true);
|
|
1403
|
-
if (insertionPoint === null) {
|
|
1404
|
-
Log.warn('[Haori]', 'Reference child not found in children.', referenceChild);
|
|
1405
|
-
return this.insertBefore(newChild, null);
|
|
1406
|
-
}
|
|
1407
|
-
return this.insertBefore(newChild, this.children[insertionPoint.index] || null, insertionPoint.referenceNode);
|
|
1408
|
-
}
|
|
1409
|
-
return this.insertBefore(newChild, this.children[index + 1] || null);
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* 前のエレメントフラグメントを取得します。
|
|
1413
|
-
* 存在しない場合はnullを返します。
|
|
1414
|
-
*
|
|
1415
|
-
* @return 前のエレメントフラグメントまたはnull
|
|
1416
|
-
*/
|
|
1417
|
-
getPrevious() {
|
|
1418
|
-
const parent = this.getParent();
|
|
1419
|
-
if (parent === null) {
|
|
1420
|
-
return null;
|
|
1421
|
-
}
|
|
1422
|
-
const siblings = parent.getChildElementFragments();
|
|
1423
|
-
const index = siblings.indexOf(this);
|
|
1424
|
-
if (index <= 0) {
|
|
1425
|
-
return null;
|
|
1426
|
-
}
|
|
1427
|
-
return siblings[index - 1];
|
|
1428
|
-
}
|
|
1429
|
-
/**
|
|
1430
|
-
* 次のエレメントフラグメントを取得します。
|
|
1431
|
-
* 存在しない場合はnullを返します。
|
|
1432
|
-
*
|
|
1433
|
-
* @return 次のエレメントフラグメントまたはnull
|
|
1434
|
-
*/
|
|
1435
|
-
getNext() {
|
|
1436
|
-
const parent = this.getParent();
|
|
1437
|
-
if (parent === null) {
|
|
1438
|
-
return null;
|
|
1439
|
-
}
|
|
1440
|
-
const siblings = parent.getChildElementFragments();
|
|
1441
|
-
const index = siblings.indexOf(this);
|
|
1442
|
-
if (index < 0 || index + 1 >= siblings.length) {
|
|
1443
|
-
return null;
|
|
1444
|
-
}
|
|
1445
|
-
return siblings[index + 1];
|
|
1446
|
-
}
|
|
1447
|
-
/**
|
|
1448
|
-
* 表示状態を返します。
|
|
1449
|
-
*
|
|
1450
|
-
* @returns 表示状態
|
|
1451
|
-
*/
|
|
1452
|
-
isVisible() {
|
|
1453
|
-
return this.visible;
|
|
1454
|
-
}
|
|
1455
|
-
/**
|
|
1456
|
-
* エレメントを非表示にします。
|
|
1457
|
-
*
|
|
1458
|
-
* @returns エレメントの非表示のPromise
|
|
1459
|
-
*/
|
|
1460
|
-
hide() {
|
|
1461
|
-
if (!this.visible) {
|
|
1462
|
-
return Promise.resolve();
|
|
1463
|
-
}
|
|
1464
|
-
this.visible = false;
|
|
1465
|
-
const target = this.getTarget();
|
|
1466
|
-
this.display = target.style.getPropertyValue('display');
|
|
1467
|
-
this.displayPriority = target.style.getPropertyPriority('display');
|
|
1468
|
-
target.style.setProperty('display', 'none', 'important');
|
|
1469
|
-
target.setAttribute(`${Env.prefix}if-false`, '');
|
|
1470
|
-
return Promise.resolve();
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* エレメントを表示します。
|
|
1474
|
-
*
|
|
1475
|
-
* @return エレメントの表示のPromise
|
|
1476
|
-
*/
|
|
1477
|
-
show() {
|
|
1478
|
-
if (this.visible) {
|
|
1479
|
-
return Promise.resolve();
|
|
1480
|
-
}
|
|
1481
|
-
const target = this.getTarget();
|
|
1482
|
-
if (this.display === null || this.display === '') {
|
|
1483
|
-
target.style.removeProperty('display');
|
|
1484
|
-
}
|
|
1485
|
-
else {
|
|
1486
|
-
target.style.setProperty('display', this.display, this.displayPriority ?? '');
|
|
1487
|
-
}
|
|
1488
|
-
this.display = null;
|
|
1489
|
-
this.displayPriority = null;
|
|
1490
|
-
target.removeAttribute(`${Env.prefix}if-false`);
|
|
1491
|
-
this.visible = true;
|
|
1492
|
-
return Promise.resolve();
|
|
1493
|
-
}
|
|
1494
|
-
/**
|
|
1495
|
-
* 指定した属性名を持つ最も近い親要素を返します。
|
|
1496
|
-
* 見つからない場合はnullを返します。
|
|
1497
|
-
*
|
|
1498
|
-
* @param name 属性名
|
|
1499
|
-
* @returns 最も近い親要素またはnull
|
|
1500
|
-
*/
|
|
1501
|
-
closestByAttribute(name) {
|
|
1502
|
-
if (this.hasAttribute(name)) {
|
|
1503
|
-
return this;
|
|
1504
|
-
}
|
|
1505
|
-
const parent = this.getParent();
|
|
1506
|
-
if (parent === null) {
|
|
1507
|
-
return null;
|
|
1508
|
-
}
|
|
1509
|
-
return parent.closestByAttribute(name);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
/** HTML 真偽属性名のセット */
|
|
1513
|
-
ElementFragment.BOOLEAN_ATTRIBUTES = new Set([
|
|
1514
|
-
'allowfullscreen',
|
|
1515
|
-
'async',
|
|
1516
|
-
'autofocus',
|
|
1517
|
-
'autoplay',
|
|
1518
|
-
'checked',
|
|
1519
|
-
'controls',
|
|
1520
|
-
'default',
|
|
1521
|
-
'defer',
|
|
1522
|
-
'disabled',
|
|
1523
|
-
'hidden',
|
|
1524
|
-
'inert',
|
|
1525
|
-
'ismap',
|
|
1526
|
-
'loop',
|
|
1527
|
-
'multiple',
|
|
1528
|
-
'muted',
|
|
1529
|
-
'nomodule',
|
|
1530
|
-
'novalidate',
|
|
1531
|
-
'open',
|
|
1532
|
-
'playsinline',
|
|
1533
|
-
'readonly',
|
|
1534
|
-
'required',
|
|
1535
|
-
'reversed',
|
|
1536
|
-
'selected',
|
|
1537
|
-
]);
|
|
1538
|
-
/**
|
|
1539
|
-
* テキストフラグメント。
|
|
1540
|
-
* テキストノードを表現します。
|
|
1541
|
-
*/
|
|
1542
|
-
export class TextFragment extends Fragment {
|
|
1543
|
-
/**
|
|
1544
|
-
* テキストフラグメントのコンストラクタ。
|
|
1545
|
-
* 対象テキストノードの内容を初期化します。
|
|
1546
|
-
*
|
|
1547
|
-
* @param target 対象テキストノード
|
|
1548
|
-
*/
|
|
1549
|
-
constructor(target) {
|
|
1550
|
-
super(target);
|
|
1551
|
-
/** 更新スキップフラグ(オブザーバーによる無限ループ対応) */
|
|
1552
|
-
this.skipMutation = false;
|
|
1553
|
-
/** 直近に描画した文字列 */
|
|
1554
|
-
this.renderedText = null;
|
|
1555
|
-
this.text = target.textContent || '';
|
|
1556
|
-
this.contents = new TextContents(this.text);
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* フラグメントをクローンします。
|
|
1560
|
-
*
|
|
1561
|
-
* @returns クローンされたフラグメント
|
|
1562
|
-
*/
|
|
1563
|
-
clone() {
|
|
1564
|
-
const clone = new TextFragment(this.target.cloneNode(true));
|
|
1565
|
-
clone.mounted = false;
|
|
1566
|
-
clone.text = this.text;
|
|
1567
|
-
clone.contents = this.contents;
|
|
1568
|
-
clone.renderedText = this.renderedText;
|
|
1569
|
-
return clone;
|
|
1570
|
-
}
|
|
1571
|
-
/**
|
|
1572
|
-
* フラグメントの対象ノードを取得します。
|
|
1573
|
-
*
|
|
1574
|
-
* @returns フラグメントの対象ノード
|
|
1575
|
-
*/
|
|
1576
|
-
getTarget() {
|
|
1577
|
-
return this.target;
|
|
1578
|
-
}
|
|
1579
|
-
/**
|
|
1580
|
-
* テキストに評価式が含まれているかどうかを返します。
|
|
1581
|
-
*
|
|
1582
|
-
* @returns 評価式を含むなら true
|
|
1583
|
-
*/
|
|
1584
|
-
hasDynamicContent() {
|
|
1585
|
-
return this.contents.isEvaluate || this.contents.isRawEvaluate;
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* コンテンツを更新します。
|
|
1589
|
-
*
|
|
1590
|
-
* @param text テキスト
|
|
1591
|
-
* @returns 更新のPromise
|
|
1592
|
-
*/
|
|
1593
|
-
setContent(text) {
|
|
1594
|
-
if (this.skipMutation || this.text === text) {
|
|
1595
|
-
return Promise.resolve();
|
|
1596
|
-
}
|
|
1597
|
-
this.text = text;
|
|
1598
|
-
this.contents = new TextContents(text);
|
|
1599
|
-
return this.evaluate();
|
|
1600
|
-
}
|
|
1601
|
-
/**
|
|
1602
|
-
* フラグメントを評価します。
|
|
1603
|
-
*
|
|
1604
|
-
* @returns 評価結果のPromise
|
|
1605
|
-
*/
|
|
1606
|
-
evaluate() {
|
|
1607
|
-
if (this.contents.isRawEvaluate && this.parent === null) {
|
|
1608
|
-
return Promise.reject(new Error('Parent fragment is required for raw evaluation'));
|
|
1609
|
-
}
|
|
1610
|
-
return Queue.enqueue(() => {
|
|
1611
|
-
this.skipMutation = true;
|
|
1612
|
-
let nextText = this.text;
|
|
1613
|
-
if (this.contents.isRawEvaluate) {
|
|
1614
|
-
nextText = this.contents.evaluate(this.parent.getBindingData(), {
|
|
1615
|
-
kind: 'text',
|
|
1616
|
-
element: this.parent.getTarget(),
|
|
1617
|
-
childIndex: this.parent.getChildren().indexOf(this),
|
|
1618
|
-
template: this.text,
|
|
1619
|
-
})[0];
|
|
1620
|
-
}
|
|
1621
|
-
else if (this.contents.isEvaluate) {
|
|
1622
|
-
nextText = TextContents.joinEvaluateResults(this.contents.evaluate(this.parent.getBindingData(), {
|
|
1623
|
-
kind: 'text',
|
|
1624
|
-
element: this.parent.getTarget(),
|
|
1625
|
-
childIndex: this.parent.getChildren().indexOf(this),
|
|
1626
|
-
template: this.text,
|
|
1627
|
-
}));
|
|
1628
|
-
}
|
|
1629
|
-
const currentText = this.contents.isRawEvaluate
|
|
1630
|
-
? this.parent.getTarget().innerHTML
|
|
1631
|
-
: this.target.textContent || '';
|
|
1632
|
-
if (this.renderedText === nextText && currentText === nextText) {
|
|
1633
|
-
return;
|
|
1634
|
-
}
|
|
1635
|
-
if (this.contents.isRawEvaluate) {
|
|
1636
|
-
this.parent.getTarget().innerHTML = nextText;
|
|
1637
|
-
}
|
|
1638
|
-
else {
|
|
1639
|
-
this.target.textContent = nextText;
|
|
1640
|
-
}
|
|
1641
|
-
this.renderedText = nextText;
|
|
1642
|
-
}).finally(() => {
|
|
1643
|
-
this.skipMutation = false;
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
/**
|
|
1648
|
-
* コメントフラグメント。
|
|
1649
|
-
* コメントノードを表現します。
|
|
1650
|
-
*/
|
|
1651
|
-
export class CommentFragment extends Fragment {
|
|
1652
|
-
/**
|
|
1653
|
-
* コメントフラグメントのコンストラクタ。
|
|
1654
|
-
* 対象コメントノードの内容を初期化します。
|
|
1655
|
-
*
|
|
1656
|
-
* @param target 対象コメントノード
|
|
1657
|
-
*/
|
|
1658
|
-
constructor(target) {
|
|
1659
|
-
super(target);
|
|
1660
|
-
/** 更新スキップフラグ(オブザーバーによる無限ループ対応) */
|
|
1661
|
-
this.skipMutation = false;
|
|
1662
|
-
this.text = target.textContent || '';
|
|
1663
|
-
}
|
|
1664
|
-
/**
|
|
1665
|
-
* フラグメントをクローンします。
|
|
1666
|
-
*
|
|
1667
|
-
* @returns クローンされたフラグメント
|
|
1668
|
-
*/
|
|
1669
|
-
clone() {
|
|
1670
|
-
const clone = new CommentFragment(this.target.cloneNode(true));
|
|
1671
|
-
clone.mounted = false;
|
|
1672
|
-
clone.text = this.text;
|
|
1673
|
-
return clone;
|
|
1674
|
-
}
|
|
1675
|
-
/**
|
|
1676
|
-
* フラグメントの対象ノードを取得します。
|
|
1677
|
-
*
|
|
1678
|
-
* @returns フラグメントの対象ノード
|
|
1679
|
-
*/
|
|
1680
|
-
getTarget() {
|
|
1681
|
-
return this.target;
|
|
1682
|
-
}
|
|
1683
|
-
/**
|
|
1684
|
-
* コンテンツを更新します。
|
|
1685
|
-
*
|
|
1686
|
-
* @param text テキスト
|
|
1687
|
-
* @return 更新のPromise
|
|
1688
|
-
*/
|
|
1689
|
-
setContent(text) {
|
|
1690
|
-
if (this.skipMutation || this.text === text) {
|
|
1691
|
-
return Promise.resolve();
|
|
1692
|
-
}
|
|
1693
|
-
this.text = text;
|
|
1694
|
-
return Queue.enqueue(() => {
|
|
1695
|
-
this.skipMutation = true;
|
|
1696
|
-
this.target.textContent = this.text;
|
|
1697
|
-
}).finally(() => {
|
|
1698
|
-
this.skipMutation = false;
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
/**
|
|
1703
|
-
* 値の種別。
|
|
1704
|
-
*/
|
|
1705
|
-
var ExpressionType;
|
|
1706
|
-
(function (ExpressionType) {
|
|
1707
|
-
/** テキスト */
|
|
1708
|
-
ExpressionType[ExpressionType["TEXT"] = 0] = "TEXT";
|
|
1709
|
-
/** 評価式 */
|
|
1710
|
-
ExpressionType[ExpressionType["EXPRESSION"] = 1] = "EXPRESSION";
|
|
1711
|
-
/** 生の評価式 */
|
|
1712
|
-
ExpressionType[ExpressionType["RAW_EXPRESSION"] = 2] = "RAW_EXPRESSION";
|
|
1713
|
-
})(ExpressionType || (ExpressionType = {}));
|
|
1714
|
-
/**
|
|
1715
|
-
* テキストコンテンツを管理するクラスです。
|
|
1716
|
-
* 一度生成されると内部は変更しません。
|
|
1717
|
-
*/
|
|
1718
|
-
class TextContents {
|
|
1719
|
-
/**
|
|
1720
|
-
* 評価結果を結合して文字列にします。
|
|
1721
|
-
*
|
|
1722
|
-
* @param contents 評価結果の配列
|
|
1723
|
-
* @returns 結合された文字列
|
|
1724
|
-
*/
|
|
1725
|
-
static joinEvaluateResults(contents) {
|
|
1726
|
-
if (contents === null || contents.length === 0) {
|
|
1727
|
-
return '';
|
|
1728
|
-
}
|
|
1729
|
-
return contents
|
|
1730
|
-
.map(c => {
|
|
1731
|
-
if (c === null || c === undefined || c === false || Number.isNaN(c)) {
|
|
1732
|
-
return '';
|
|
1733
|
-
}
|
|
1734
|
-
else if (typeof c !== 'string') {
|
|
1735
|
-
return String(c);
|
|
1736
|
-
}
|
|
1737
|
-
else {
|
|
1738
|
-
return c;
|
|
1739
|
-
}
|
|
1740
|
-
})
|
|
1741
|
-
.join('');
|
|
1742
|
-
}
|
|
1743
|
-
/**
|
|
1744
|
-
* コンストラクタ。
|
|
1745
|
-
*
|
|
1746
|
-
* @param text テキスト
|
|
1747
|
-
*/
|
|
1748
|
-
constructor(text) {
|
|
1749
|
-
/** コンテンツのリスト */
|
|
1750
|
-
this.contents = [];
|
|
1751
|
-
/** 評価式が含まれるかどうか */
|
|
1752
|
-
this.isEvaluate = false;
|
|
1753
|
-
/** 生の評価式が含まれるかどうか */
|
|
1754
|
-
this.isRawEvaluate = false;
|
|
1755
|
-
this.value = text;
|
|
1756
|
-
const matches = [...text.matchAll(TextContents.PLACEHOLDER_REGEX)];
|
|
1757
|
-
let lastIndex = 0;
|
|
1758
|
-
let hasEvaluate = false;
|
|
1759
|
-
let hasRawEvaluate = false;
|
|
1760
|
-
for (const match of matches) {
|
|
1761
|
-
// プレースホルダ前の通常テキスト
|
|
1762
|
-
if (match.index > lastIndex) {
|
|
1763
|
-
this.contents.push({
|
|
1764
|
-
text: text.slice(lastIndex, match.index),
|
|
1765
|
-
type: ExpressionType.TEXT,
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
// プレースホルダ本体
|
|
1769
|
-
const content = {
|
|
1770
|
-
text: match[1] ?? match[2],
|
|
1771
|
-
type: match[1]
|
|
1772
|
-
? ExpressionType.RAW_EXPRESSION
|
|
1773
|
-
: ExpressionType.EXPRESSION,
|
|
1774
|
-
};
|
|
1775
|
-
hasEvaluate = true;
|
|
1776
|
-
hasRawEvaluate =
|
|
1777
|
-
hasRawEvaluate || content.type === ExpressionType.RAW_EXPRESSION;
|
|
1778
|
-
this.contents.push(content);
|
|
1779
|
-
lastIndex = match.index + match[0].length;
|
|
1780
|
-
}
|
|
1781
|
-
// 最後のプレースホルダ以降の通常テキスト
|
|
1782
|
-
if (lastIndex < text.length) {
|
|
1783
|
-
this.contents.push({
|
|
1784
|
-
text: text.slice(lastIndex),
|
|
1785
|
-
type: ExpressionType.TEXT,
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
this.isEvaluate = hasEvaluate;
|
|
1789
|
-
this.isRawEvaluate = hasRawEvaluate;
|
|
1790
|
-
this.checkRawExpressions();
|
|
1791
|
-
}
|
|
1792
|
-
/**
|
|
1793
|
-
* 評価前の値を取得します。
|
|
1794
|
-
*
|
|
1795
|
-
* @returns 評価前の値
|
|
1796
|
-
*/
|
|
1797
|
-
getValue() {
|
|
1798
|
-
return this.value;
|
|
1799
|
-
}
|
|
1800
|
-
/**
|
|
1801
|
-
* 単体プレースホルダのみで構成されているかどうかを返します。
|
|
1802
|
-
*
|
|
1803
|
-
* @returns 単体プレースホルダなら true
|
|
1804
|
-
*/
|
|
1805
|
-
isSingleExpression() {
|
|
1806
|
-
return (this.contents.length === 1 &&
|
|
1807
|
-
(this.contents[0].type === ExpressionType.EXPRESSION ||
|
|
1808
|
-
this.contents[0].type === ExpressionType.RAW_EXPRESSION));
|
|
1809
|
-
}
|
|
1810
|
-
/**
|
|
1811
|
-
* RAW_EXPRESSION のチェックを行います。
|
|
1812
|
-
*/
|
|
1813
|
-
checkRawExpressions() {
|
|
1814
|
-
for (let i = 0; i < this.contents.length; i++) {
|
|
1815
|
-
const content = this.contents[i];
|
|
1816
|
-
if (content.type === ExpressionType.RAW_EXPRESSION &&
|
|
1817
|
-
this.contents.length > 1) {
|
|
1818
|
-
Log.error('[Haori]', 'Raw expressions are not allowed in multi-content expressions.');
|
|
1819
|
-
this.contents[i].type = ExpressionType.EXPRESSION;
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
/**
|
|
1824
|
-
* 式評価を行い、結果を返します。
|
|
1825
|
-
*
|
|
1826
|
-
* @param bindingValues バインディングされた値のオブジェクト
|
|
1827
|
-
* @returns 評価結果のリスト
|
|
1828
|
-
*/
|
|
1829
|
-
evaluate(bindingValues, profileContext) {
|
|
1830
|
-
return this.evaluateDetailed(bindingValues, profileContext).results;
|
|
1831
|
-
}
|
|
1832
|
-
/**
|
|
1833
|
-
* 式評価を行い、未解決参照の有無を含む結果を返します。
|
|
1834
|
-
*
|
|
1835
|
-
* @param bindingValues バインディングされた値のオブジェクト
|
|
1836
|
-
* @returns 評価結果と未解決参照の有無
|
|
1837
|
-
*/
|
|
1838
|
-
evaluateDetailed(bindingValues, profileContext) {
|
|
1839
|
-
if (!this.isEvaluate && !this.isRawEvaluate) {
|
|
1840
|
-
return {
|
|
1841
|
-
results: this.contents.map(c => c.text),
|
|
1842
|
-
hasUnresolvedReference: false,
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
return this.evaluateWithProfile(bindingValues, profileContext, content => content.type === ExpressionType.EXPRESSION ||
|
|
1846
|
-
content.type === ExpressionType.RAW_EXPRESSION, 'text');
|
|
1847
|
-
}
|
|
1848
|
-
/**
|
|
1849
|
-
* 式評価と profiler 記録をまとめて実行します。
|
|
1850
|
-
*
|
|
1851
|
-
* @param bindingValues バインディングされた値のオブジェクト
|
|
1852
|
-
* @param profileContext profiler 用コンテキスト
|
|
1853
|
-
* @param shouldEvaluate 評価対象判定
|
|
1854
|
-
* @param errorKind エラーログ種別
|
|
1855
|
-
* @returns 評価結果と未解決参照の有無
|
|
1856
|
-
*/
|
|
1857
|
-
evaluateWithProfile(bindingValues, profileContext, shouldEvaluate, errorKind) {
|
|
1858
|
-
const results = [];
|
|
1859
|
-
const profileExpressions = [];
|
|
1860
|
-
let totalDurationMs = 0;
|
|
1861
|
-
let hasUnresolvedReference = false;
|
|
1862
|
-
this.contents.forEach(content => {
|
|
1863
|
-
try {
|
|
1864
|
-
if (shouldEvaluate(content)) {
|
|
1865
|
-
const measured = EvaluationProfileRegistry.measure(() => Expression.evaluateDetailed(content.text, bindingValues));
|
|
1866
|
-
const result = measured.value;
|
|
1867
|
-
totalDurationMs += measured.durationMs;
|
|
1868
|
-
profileExpressions.push({
|
|
1869
|
-
expression: content.text,
|
|
1870
|
-
durationMs: measured.durationMs,
|
|
1871
|
-
});
|
|
1872
|
-
hasUnresolvedReference =
|
|
1873
|
-
hasUnresolvedReference || result.unresolvedReference;
|
|
1874
|
-
results.push(result.value);
|
|
1875
|
-
}
|
|
1876
|
-
else {
|
|
1877
|
-
results.push(content.text);
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
catch (error) {
|
|
1881
|
-
Log.error('[Haori]', `Error evaluating ${errorKind} expression: ${content.text}`, error);
|
|
1882
|
-
profileExpressions.push({
|
|
1883
|
-
expression: content.text,
|
|
1884
|
-
durationMs: 0,
|
|
1885
|
-
});
|
|
1886
|
-
results.push('');
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
|
-
EvaluationProfileRegistry.record(profileContext, profileExpressions, totalDurationMs);
|
|
1890
|
-
return { results, hasUnresolvedReference };
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
/** プレースホルダ検出用の正規表現 */
|
|
1894
|
-
TextContents.PLACEHOLDER_REGEX = /\{\{\{([\s\S]+?)\}\}\}|\{\{([\s\S]+?)\}\}/g;
|
|
1895
|
-
/**
|
|
1896
|
-
* 属性のコンテンツを管理するクラスです。
|
|
1897
|
-
* 一度生成されると内部は変更しません。
|
|
1898
|
-
*/
|
|
1899
|
-
class AttributeContents extends TextContents {
|
|
1900
|
-
/**
|
|
1901
|
-
* コンストラクタ。
|
|
1902
|
-
*
|
|
1903
|
-
* @param name 属性名
|
|
1904
|
-
* @param text 属性値
|
|
1905
|
-
*/
|
|
1906
|
-
constructor(name, value) {
|
|
1907
|
-
super(value);
|
|
1908
|
-
this.forceEvaluation =
|
|
1909
|
-
AttributeContents.FORCE_EVALUATION_ATTRIBUTES.includes(name);
|
|
1910
|
-
}
|
|
1911
|
-
/**
|
|
1912
|
-
* 強制評価フラグを取得します。
|
|
1913
|
-
*
|
|
1914
|
-
* @returns 強制評価フラグ
|
|
1915
|
-
*/
|
|
1916
|
-
isForceEvaluation() {
|
|
1917
|
-
return this.forceEvaluation;
|
|
1918
|
-
}
|
|
1919
|
-
/**
|
|
1920
|
-
* 式評価を行い、結果を返します。
|
|
1921
|
-
*
|
|
1922
|
-
* @param bindingValues バインディングされた値のオブジェクト
|
|
1923
|
-
* @returns 評価結果のリスト
|
|
1924
|
-
*/
|
|
1925
|
-
evaluate(bindingValues, profileContext) {
|
|
1926
|
-
return this.evaluateDetailed(bindingValues, profileContext).results;
|
|
1927
|
-
}
|
|
1928
|
-
/**
|
|
1929
|
-
* 式評価を行い、未解決参照の有無を含む結果を返します。
|
|
1930
|
-
*
|
|
1931
|
-
* @param bindingValues バインディングされた値のオブジェクト
|
|
1932
|
-
* @returns 評価結果と未解決参照の有無
|
|
1933
|
-
*/
|
|
1934
|
-
evaluateDetailed(bindingValues, profileContext) {
|
|
1935
|
-
if (!this.isEvaluate && !this.forceEvaluation) {
|
|
1936
|
-
return {
|
|
1937
|
-
results: this.contents.map(c => c.text),
|
|
1938
|
-
hasUnresolvedReference: false,
|
|
1939
|
-
};
|
|
1940
|
-
}
|
|
1941
|
-
const detail = this.evaluateWithProfile(bindingValues, profileContext, content => (this.forceEvaluation && content.type === ExpressionType.TEXT) ||
|
|
1942
|
-
content.type === ExpressionType.EXPRESSION ||
|
|
1943
|
-
content.type === ExpressionType.RAW_EXPRESSION, 'attribute');
|
|
1944
|
-
if (this.forceEvaluation && detail.results.length > 1) {
|
|
1945
|
-
Log.error('[Haori]', 'each or if expressions must have a single content.', detail.results);
|
|
1946
|
-
return {
|
|
1947
|
-
results: [detail.results[0]],
|
|
1948
|
-
hasUnresolvedReference: detail.hasUnresolvedReference,
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
return detail;
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
/** 強制評価する属性名 */
|
|
1955
|
-
AttributeContents.FORCE_EVALUATION_ATTRIBUTES = [
|
|
1956
|
-
'data-if',
|
|
1957
|
-
'hor-if',
|
|
1958
|
-
'data-each',
|
|
1959
|
-
'hor-each',
|
|
1960
|
-
'data-derive',
|
|
1961
|
-
'hor-derive',
|
|
1962
|
-
];
|
|
1963
|
-
//# sourceMappingURL=fragment.js.map
|