haori 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +157 -0
  3. package/README.md +158 -0
  4. package/dist/haori.cjs.js +13 -0
  5. package/dist/haori.cjs.js.map +1 -0
  6. package/dist/haori.es.js +2929 -0
  7. package/dist/haori.es.js.map +1 -0
  8. package/dist/haori.iife.js +13 -0
  9. package/dist/haori.iife.js.map +1 -0
  10. package/dist/index.d.ts +824 -0
  11. package/dist/src/core.d.ts +144 -0
  12. package/dist/src/core.d.ts.map +1 -0
  13. package/dist/src/core.js +605 -0
  14. package/dist/src/core.js.map +1 -0
  15. package/dist/src/dev.d.ts +35 -0
  16. package/dist/src/dev.d.ts.map +1 -0
  17. package/dist/src/dev.js +44 -0
  18. package/dist/src/dev.js.map +1 -0
  19. package/dist/src/env.d.ts +25 -0
  20. package/dist/src/env.d.ts.map +1 -0
  21. package/dist/src/env.js +64 -0
  22. package/dist/src/env.js.map +1 -0
  23. package/dist/src/event.d.ts +144 -0
  24. package/dist/src/event.d.ts.map +1 -0
  25. package/dist/src/event.js +221 -0
  26. package/dist/src/event.js.map +1 -0
  27. package/dist/src/event_dispatcher.d.ts +50 -0
  28. package/dist/src/event_dispatcher.d.ts.map +1 -0
  29. package/dist/src/event_dispatcher.js +99 -0
  30. package/dist/src/event_dispatcher.js.map +1 -0
  31. package/dist/src/expression.d.ts +39 -0
  32. package/dist/src/expression.d.ts.map +1 -0
  33. package/dist/src/expression.js +148 -0
  34. package/dist/src/expression.js.map +1 -0
  35. package/dist/src/form.d.ts +113 -0
  36. package/dist/src/form.d.ts.map +1 -0
  37. package/dist/src/form.js +361 -0
  38. package/dist/src/form.js.map +1 -0
  39. package/dist/src/fragment.d.ts +427 -0
  40. package/dist/src/fragment.d.ts.map +1 -0
  41. package/dist/src/fragment.js +1168 -0
  42. package/dist/src/fragment.js.map +1 -0
  43. package/dist/src/haori.d.ts +54 -0
  44. package/dist/src/haori.d.ts.map +1 -0
  45. package/dist/src/haori.js +120 -0
  46. package/dist/src/haori.js.map +1 -0
  47. package/dist/src/import.d.ts +19 -0
  48. package/dist/src/import.d.ts.map +1 -0
  49. package/dist/src/import.js +64 -0
  50. package/dist/src/import.js.map +1 -0
  51. package/dist/src/index.d.ts +17 -0
  52. package/dist/src/index.d.ts.map +1 -0
  53. package/dist/src/index.js +21 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/log.d.ts +32 -0
  56. package/dist/src/log.d.ts.map +1 -0
  57. package/dist/src/log.js +43 -0
  58. package/dist/src/log.js.map +1 -0
  59. package/dist/src/observer.d.ts +17 -0
  60. package/dist/src/observer.d.ts.map +1 -0
  61. package/dist/src/observer.js +102 -0
  62. package/dist/src/observer.js.map +1 -0
  63. package/dist/src/procedure.d.ts +203 -0
  64. package/dist/src/procedure.d.ts.map +1 -0
  65. package/dist/src/procedure.js +1040 -0
  66. package/dist/src/procedure.js.map +1 -0
  67. package/dist/src/queue.d.ts +28 -0
  68. package/dist/src/queue.d.ts.map +1 -0
  69. package/dist/src/queue.js +150 -0
  70. package/dist/src/queue.js.map +1 -0
  71. package/dist/src/url.d.ts +14 -0
  72. package/dist/src/url.d.ts.map +1 -0
  73. package/dist/src/url.js +22 -0
  74. package/dist/src/url.js.map +1 -0
  75. package/dist/tests/click-attributes.test.d.ts +2 -0
  76. package/dist/tests/click-attributes.test.d.ts.map +1 -0
  77. package/dist/tests/click-attributes.test.js +95 -0
  78. package/dist/tests/click-attributes.test.js.map +1 -0
  79. package/dist/tests/core.test.d.ts +5 -0
  80. package/dist/tests/core.test.d.ts.map +1 -0
  81. package/dist/tests/core.test.js +158 -0
  82. package/dist/tests/core.test.js.map +1 -0
  83. package/dist/tests/data-each-browserlike.test.d.ts +2 -0
  84. package/dist/tests/data-each-browserlike.test.d.ts.map +1 -0
  85. package/dist/tests/data-each-browserlike.test.js +48 -0
  86. package/dist/tests/data-each-browserlike.test.js.map +1 -0
  87. package/dist/tests/data-each-fragment-debug.test.d.ts +2 -0
  88. package/dist/tests/data-each-fragment-debug.test.d.ts.map +1 -0
  89. package/dist/tests/data-each-fragment-debug.test.js +119 -0
  90. package/dist/tests/data-each-fragment-debug.test.js.map +1 -0
  91. package/dist/tests/data-each-table.test.d.ts +2 -0
  92. package/dist/tests/data-each-table.test.d.ts.map +1 -0
  93. package/dist/tests/data-each-table.test.js +63 -0
  94. package/dist/tests/data-each-table.test.js.map +1 -0
  95. package/dist/tests/dev.test.d.ts +2 -0
  96. package/dist/tests/dev.test.d.ts.map +1 -0
  97. package/dist/tests/dev.test.js +51 -0
  98. package/dist/tests/dev.test.js.map +1 -0
  99. package/dist/tests/each_arg.test.d.ts +2 -0
  100. package/dist/tests/each_arg.test.d.ts.map +1 -0
  101. package/dist/tests/each_arg.test.js +41 -0
  102. package/dist/tests/each_arg.test.js.map +1 -0
  103. package/dist/tests/env.test.d.ts +2 -0
  104. package/dist/tests/env.test.d.ts.map +1 -0
  105. package/dist/tests/env.test.js +96 -0
  106. package/dist/tests/env.test.js.map +1 -0
  107. package/dist/tests/event.test.d.ts +2 -0
  108. package/dist/tests/event.test.d.ts.map +1 -0
  109. package/dist/tests/event.test.js +287 -0
  110. package/dist/tests/event.test.js.map +1 -0
  111. package/dist/tests/expression.test.d.ts +2 -0
  112. package/dist/tests/expression.test.d.ts.map +1 -0
  113. package/dist/tests/expression.test.js +281 -0
  114. package/dist/tests/expression.test.js.map +1 -0
  115. package/dist/tests/fetch-and-procedure-scenarios.test.d.ts +2 -0
  116. package/dist/tests/fetch-and-procedure-scenarios.test.d.ts.map +1 -0
  117. package/dist/tests/fetch-and-procedure-scenarios.test.js +133 -0
  118. package/dist/tests/fetch-and-procedure-scenarios.test.js.map +1 -0
  119. package/dist/tests/form.test.d.ts +2 -0
  120. package/dist/tests/form.test.d.ts.map +1 -0
  121. package/dist/tests/form.test.js +607 -0
  122. package/dist/tests/form.test.js.map +1 -0
  123. package/dist/tests/fragment.test.d.ts +2 -0
  124. package/dist/tests/fragment.test.d.ts.map +1 -0
  125. package/dist/tests/fragment.test.js +164 -0
  126. package/dist/tests/fragment.test.js.map +1 -0
  127. package/dist/tests/import.test.d.ts +2 -0
  128. package/dist/tests/import.test.d.ts.map +1 -0
  129. package/dist/tests/import.test.js +148 -0
  130. package/dist/tests/import.test.js.map +1 -0
  131. package/dist/tests/log.test.d.ts +2 -0
  132. package/dist/tests/log.test.d.ts.map +1 -0
  133. package/dist/tests/log.test.js +58 -0
  134. package/dist/tests/log.test.js.map +1 -0
  135. package/dist/tests/procedure-action-operations.test.d.ts +2 -0
  136. package/dist/tests/procedure-action-operations.test.d.ts.map +1 -0
  137. package/dist/tests/procedure-action-operations.test.js +148 -0
  138. package/dist/tests/procedure-action-operations.test.js.map +1 -0
  139. package/dist/tests/procedure-fetch-options.test.d.ts +2 -0
  140. package/dist/tests/procedure-fetch-options.test.d.ts.map +1 -0
  141. package/dist/tests/procedure-fetch-options.test.js +131 -0
  142. package/dist/tests/procedure-fetch-options.test.js.map +1 -0
  143. package/dist/tests/procedure.test.d.ts +2 -0
  144. package/dist/tests/procedure.test.d.ts.map +1 -0
  145. package/dist/tests/procedure.test.js +136 -0
  146. package/dist/tests/procedure.test.js.map +1 -0
  147. package/dist/tests/procedure_events.test.d.ts +7 -0
  148. package/dist/tests/procedure_events.test.d.ts.map +1 -0
  149. package/dist/tests/procedure_events.test.js +96 -0
  150. package/dist/tests/procedure_events.test.js.map +1 -0
  151. package/dist/tests/reset_each.test.d.ts +2 -0
  152. package/dist/tests/reset_each.test.d.ts.map +1 -0
  153. package/dist/tests/reset_each.test.js +215 -0
  154. package/dist/tests/reset_each.test.js.map +1 -0
  155. package/dist/tests/row-move.test.d.ts +2 -0
  156. package/dist/tests/row-move.test.d.ts.map +1 -0
  157. package/dist/tests/row-move.test.js +197 -0
  158. package/dist/tests/row-move.test.js.map +1 -0
  159. package/dist/tests/row-operations.test.d.ts +2 -0
  160. package/dist/tests/row-operations.test.d.ts.map +1 -0
  161. package/dist/tests/row-operations.test.js +238 -0
  162. package/dist/tests/row-operations.test.js.map +1 -0
  163. package/dist/tests/url.test.d.ts +2 -0
  164. package/dist/tests/url.test.d.ts.map +1 -0
  165. package/dist/tests/url.test.js +150 -0
  166. package/dist/tests/url.test.js.map +1 -0
  167. package/dist/vite.config.d.ts +3 -0
  168. package/dist/vite.config.d.ts.map +1 -0
  169. package/dist/vite.config.js +28 -0
  170. package/dist/vite.config.js.map +1 -0
  171. package/dist/vitest.config.d.ts +3 -0
  172. package/dist/vitest.config.d.ts.map +1 -0
  173. package/dist/vitest.config.js +19 -0
  174. package/dist/vitest.config.js.map +1 -0
  175. package/package.json +68 -0
@@ -0,0 +1,1168 @@
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
+ /**
12
+ * 仮想DOMのフラグメントの抽象クラス。
13
+ */
14
+ class Fragment {
15
+ static get(node) {
16
+ if (node == null) {
17
+ return null;
18
+ }
19
+ if (Fragment.FRAGMENT_CACHE.has(node)) {
20
+ return Fragment.FRAGMENT_CACHE.get(node);
21
+ }
22
+ let fragment;
23
+ switch (node.nodeType) {
24
+ case Node.ELEMENT_NODE:
25
+ fragment = new ElementFragment(node);
26
+ break;
27
+ case Node.TEXT_NODE:
28
+ fragment = new TextFragment(node);
29
+ break;
30
+ case Node.COMMENT_NODE:
31
+ fragment = new CommentFragment(node);
32
+ break;
33
+ default:
34
+ Log.warn('[Haori]', 'Unsupported node type:', node.nodeType);
35
+ return null;
36
+ }
37
+ return fragment;
38
+ }
39
+ /**
40
+ * フラグメントのコンストラクタ。
41
+ *
42
+ * @param target 対象ノード
43
+ */
44
+ constructor(target) {
45
+ /** 親フラグメント */
46
+ this.parent = null;
47
+ /** フラグメントがDOMにマウントされているかどうか */
48
+ this.mounted = false;
49
+ /** ノード更新スキップフラグ(オブザーバーによる無限ループ対応) */
50
+ this.skipMutationNodes = false;
51
+ this.target = target;
52
+ Fragment.FRAGMENT_CACHE.set(target, this);
53
+ }
54
+ /**
55
+ * skipMutationNodesフラグの値を取得します。
56
+ *
57
+ * @returns skipMutationNodesの値
58
+ */
59
+ isSkipMutationNodes() {
60
+ return this.skipMutationNodes;
61
+ }
62
+ /**
63
+ * フラグメントをDOMから除去します。
64
+ *
65
+ * @return 除去のPromise
66
+ */
67
+ unmount() {
68
+ if (!this.mounted || this.skipMutationNodes) {
69
+ return Promise.resolve();
70
+ }
71
+ if (this.parent) {
72
+ const parent = this.parent;
73
+ const prevSkip = parent.skipMutationNodes;
74
+ return Queue.enqueue(() => {
75
+ parent.skipMutationNodes = true;
76
+ if (this.target.parentNode === parent.getTarget()) {
77
+ parent.getTarget().removeChild(this.target);
78
+ }
79
+ this.mounted = false;
80
+ }).finally(() => {
81
+ parent.skipMutationNodes = prevSkip;
82
+ });
83
+ }
84
+ else {
85
+ // 親フラグメント情報が無くても、DOM 上に親ノードが存在する場合は安全に除去する。
86
+ const host = this.target.parentNode;
87
+ if (host) {
88
+ return Queue.enqueue(() => {
89
+ if (this.target.parentNode === host) {
90
+ host.removeChild(this.target);
91
+ }
92
+ this.mounted = false;
93
+ });
94
+ }
95
+ this.mounted = false;
96
+ }
97
+ return Promise.resolve();
98
+ }
99
+ /**
100
+ * フラグメントをDOMに追加します。
101
+ *
102
+ * @return 追加のPromise
103
+ */
104
+ mount() {
105
+ if (this.mounted || this.skipMutationNodes) {
106
+ return Promise.resolve();
107
+ }
108
+ if (this.parent) {
109
+ const parent = this.parent;
110
+ const prevSkip = parent.skipMutationNodes;
111
+ return Queue.enqueue(() => {
112
+ parent.skipMutationNodes = true;
113
+ if (this.target.parentNode !== parent.getTarget()) {
114
+ // 既に同じ親なら何もしない
115
+ parent.getTarget().appendChild(this.target);
116
+ }
117
+ this.mounted = true;
118
+ }).finally(() => {
119
+ parent.skipMutationNodes = prevSkip;
120
+ });
121
+ }
122
+ return Promise.resolve();
123
+ }
124
+ /**
125
+ * フラグメントのマウント状態を取得します。
126
+ *
127
+ * @returns マウント状態
128
+ */
129
+ isMounted() {
130
+ return this.mounted;
131
+ }
132
+ /**
133
+ * フラグメントのマウント状態を設定します。
134
+ *
135
+ * @param mounted マウント状態
136
+ */
137
+ setMounted(mounted) {
138
+ this.mounted = mounted;
139
+ }
140
+ /**
141
+ * フラグメントとノードを削除します。
142
+ *
143
+ * @param unmount DOMからの除去を行うかどうか(内部の子呼び出しの場合のみfalseとする)
144
+ * @return 除去のPromise
145
+ */
146
+ remove(unmount = true) {
147
+ if (this.parent) {
148
+ this.parent.removeChild(this);
149
+ }
150
+ Fragment.FRAGMENT_CACHE.delete(this.target);
151
+ if (unmount) {
152
+ return this.unmount();
153
+ }
154
+ return Promise.resolve();
155
+ }
156
+ /**
157
+ * 対象ノードを取得します。
158
+ *
159
+ * @returns 対象ノード
160
+ */
161
+ getTarget() {
162
+ return this.target;
163
+ }
164
+ /**
165
+ * 親フラグメントを取得します。
166
+ *
167
+ * @returns 親フラグメント
168
+ */
169
+ getParent() {
170
+ return this.parent;
171
+ }
172
+ /**
173
+ * 親フラグメントを設定します。
174
+ *
175
+ * @param parent 親フラグメント
176
+ */
177
+ setParent(parent) {
178
+ this.parent = parent;
179
+ }
180
+ }
181
+ /** フラグメントの対象ノードに対するキャッシュ */
182
+ Fragment.FRAGMENT_CACHE = new WeakMap();
183
+ export default Fragment;
184
+ /**
185
+ * エレメントフラグメント。
186
+ * DOM要素を表現し、子ノードを持つことができます。
187
+ */
188
+ export class ElementFragment extends Fragment {
189
+ /**
190
+ * エレメントフラグメントのコンストラクタ。
191
+ * アトリビュートや子フラグメントの作成も行います。
192
+ *
193
+ * @param target 対象エレメント
194
+ */
195
+ constructor(target) {
196
+ super(target);
197
+ /** inputイベントを発生させるタイプ */
198
+ this.INPUT_EVENT_TYPES = [
199
+ 'text',
200
+ 'password',
201
+ 'email',
202
+ 'url',
203
+ 'tel',
204
+ 'search',
205
+ 'number',
206
+ 'range',
207
+ 'color',
208
+ 'date',
209
+ 'datetime-local',
210
+ 'month',
211
+ 'time',
212
+ 'week',
213
+ ];
214
+ /** 子フラグメントのリスト */
215
+ this.children = [];
216
+ /** 属性名に対する属性情報のマップ */
217
+ this.attributeMap = new Map();
218
+ /** バインドデータ */
219
+ this.bindingData = null;
220
+ /** バインドデータのキャッシュ */
221
+ this.bindingDataCache = null;
222
+ /** 表示状態 */
223
+ this.visible = true;
224
+ /** 元の display 値 */
225
+ this.display = null;
226
+ /** each用のテンプレート */
227
+ this.template = null;
228
+ /** each比較用のキー */
229
+ this.listKey = null;
230
+ /** valueプロパティの値 */
231
+ this.value = null;
232
+ /** 属性更新スキップフラグ(オブザーバーによる無限ループ対応) */
233
+ this.skipMutationAttributes = false;
234
+ /** 値変更スキップフラグ(更新イベントによる無限ループ対応) */
235
+ this.skipChangeValue = false;
236
+ this.syncValue();
237
+ target.getAttributeNames().forEach(name => {
238
+ const value = target.getAttribute(name);
239
+ if (value !== null && !this.attributeMap.has(name)) {
240
+ const contents = new AttributeContents(name, value);
241
+ this.attributeMap.set(name, contents);
242
+ }
243
+ });
244
+ target.childNodes.forEach(node => {
245
+ const childFragment = Fragment.get(node);
246
+ childFragment.setParent(this);
247
+ this.children.push(childFragment);
248
+ });
249
+ }
250
+ /**
251
+ * 子フラグメントのリストを取得します。
252
+ *
253
+ * @returns 子フラグメントのリスト
254
+ */
255
+ getChildren() {
256
+ return this.children;
257
+ }
258
+ /**
259
+ * 子エレメントフラグメントのリストを取得します。
260
+ *
261
+ * @returns 子エレメントフラグメントのリスト
262
+ */
263
+ getChildElementFragments() {
264
+ return this.children.filter(child => child instanceof ElementFragment);
265
+ }
266
+ /**
267
+ * 子フラグメントをリストに追加します。
268
+ * DOMの追加は行いません。
269
+ *
270
+ * @param child 追加する子フラグメント
271
+ */
272
+ pushChild(child) {
273
+ this.children.push(child);
274
+ child.setParent(this);
275
+ }
276
+ /**
277
+ * 子フラグメントをリストから削除します。
278
+ * DOMからの削除は行いません。
279
+ *
280
+ * @param child 削除する子フラグメント
281
+ */
282
+ removeChild(child) {
283
+ const index = this.children.indexOf(child);
284
+ if (index < 0) {
285
+ Log.warn('[Haori]', 'Child fragment not found.', child);
286
+ return;
287
+ }
288
+ this.children.splice(index, 1);
289
+ child.setParent(null);
290
+ }
291
+ /**
292
+ * フラグメントをクローンします。
293
+ *
294
+ * @returns クローンされたフラグメント
295
+ */
296
+ clone() {
297
+ const clone = new ElementFragment(this.target.cloneNode(false));
298
+ this.children.forEach(child => {
299
+ const childClone = child.clone();
300
+ clone.getTarget().appendChild(childClone.getTarget());
301
+ clone.pushChild(childClone);
302
+ });
303
+ clone.mounted = false;
304
+ clone.bindingData = this.bindingData;
305
+ clone.clearBindingDataCache();
306
+ clone.visible = this.visible;
307
+ clone.display = this.display;
308
+ clone.template = this.template;
309
+ return clone;
310
+ }
311
+ /**
312
+ * フラグメントとノードを削除します。
313
+ *
314
+ * @param unmount DOMからの除去を行うかどうか(内部の子呼び出しの場合のみfalseとする)
315
+ * @return 除去のPromise
316
+ */
317
+ remove(unmount = true) {
318
+ const promises = [];
319
+ this.children.forEach(child => {
320
+ promises.push(child.remove(false));
321
+ });
322
+ this.children.length = 0;
323
+ this.attributeMap.clear();
324
+ this.bindingData = null;
325
+ this.bindingDataCache = null;
326
+ if (this.template) {
327
+ promises.push(this.template.remove(false));
328
+ this.template = null;
329
+ }
330
+ promises.push(super.remove(unmount));
331
+ return Promise.all(promises).then(() => undefined);
332
+ }
333
+ /**
334
+ * フラグメントの対象エレメントを取得します。
335
+ *
336
+ * @returns フラグメントの対象エレメント
337
+ */
338
+ getTarget() {
339
+ return this.target;
340
+ }
341
+ /**
342
+ * 継承を考慮したバインドデータを取得します。
343
+ *
344
+ * @returns バインドデータのオブジェクト
345
+ */
346
+ getBindingData() {
347
+ if (this.bindingDataCache) {
348
+ return this.bindingDataCache;
349
+ }
350
+ this.bindingDataCache = {};
351
+ if (this.parent) {
352
+ Object.assign(this.bindingDataCache, this.parent.getBindingData());
353
+ }
354
+ if (this.bindingData) {
355
+ Object.assign(this.bindingDataCache, this.bindingData);
356
+ }
357
+ return this.bindingDataCache;
358
+ }
359
+ /**
360
+ * 生のバインドデータを取得します。
361
+ *
362
+ * @returns 生のバインドデータ
363
+ */
364
+ getRawBindingData() {
365
+ return this.bindingData;
366
+ }
367
+ /**
368
+ * バインドデータを設定します。
369
+ *
370
+ * @param data バインドデータ
371
+ */
372
+ setBindingData(data) {
373
+ this.bindingData = data;
374
+ this.clearBindingDataCache();
375
+ }
376
+ /**
377
+ * バインドデータのキャッシュをクリアします。
378
+ */
379
+ clearBindingDataCache() {
380
+ this.bindingDataCache = null;
381
+ this.children.forEach(child => {
382
+ if (child instanceof ElementFragment) {
383
+ child.clearBindingDataCache();
384
+ }
385
+ });
386
+ }
387
+ /**
388
+ * フラグメントのテンプレートを取得します。
389
+ *
390
+ * @returns テンプレート
391
+ */
392
+ getTemplate() {
393
+ return this.template;
394
+ }
395
+ /**
396
+ * フラグメントのテンプレートを設定します。
397
+ *
398
+ * @param template フラグメントのテンプレート
399
+ */
400
+ setTemplate(template) {
401
+ this.template = template;
402
+ }
403
+ /**
404
+ * 比較用リストキーを設定します。
405
+ *
406
+ * @param key 比較用リストキー
407
+ */
408
+ setListKey(key) {
409
+ this.listKey = key;
410
+ }
411
+ /**
412
+ * 比較用リストキーを取得します。
413
+ *
414
+ * @returns 比較用リストキー
415
+ */
416
+ getListKey() {
417
+ return this.listKey;
418
+ }
419
+ /**
420
+ * 入力エレメントに値を設定します。
421
+ * チェックボックとラジオボタンの場合は値に一致するかどうかでチェック状態を変更します。
422
+ *
423
+ * @param value 値
424
+ * @returns エレメントの更新のPromise
425
+ */
426
+ setValue(value) {
427
+ if (this.skipChangeValue) {
428
+ return Promise.resolve();
429
+ }
430
+ if (this.value === value) {
431
+ return Promise.resolve();
432
+ }
433
+ const element = this.getTarget();
434
+ if (element instanceof HTMLInputElement &&
435
+ (element.type === 'checkbox' || element.type === 'radio')) {
436
+ const result = this.getAttribute('value');
437
+ let newChecked;
438
+ if (result === 'true') {
439
+ newChecked = value === true;
440
+ }
441
+ else if (result === 'false') {
442
+ newChecked = value === false;
443
+ }
444
+ else {
445
+ newChecked = result === String(value);
446
+ }
447
+ this.value = newChecked ? value : null;
448
+ if (element.checked === newChecked) {
449
+ return Promise.resolve();
450
+ }
451
+ this.skipChangeValue = true;
452
+ return Queue.enqueue(() => {
453
+ element.checked = newChecked;
454
+ element.dispatchEvent(new Event('change', { bubbles: true }));
455
+ }).finally(() => {
456
+ this.skipChangeValue = false;
457
+ });
458
+ }
459
+ else if (element instanceof HTMLInputElement ||
460
+ element instanceof HTMLTextAreaElement ||
461
+ element instanceof HTMLSelectElement) {
462
+ this.value = value;
463
+ this.skipChangeValue = true;
464
+ return Queue.enqueue(() => {
465
+ element.value = value === null ? '' : String(value);
466
+ if ((element instanceof HTMLInputElement &&
467
+ this.INPUT_EVENT_TYPES.includes(element.type)) ||
468
+ element instanceof HTMLTextAreaElement) {
469
+ element.dispatchEvent(new Event('input', { bubbles: true }));
470
+ }
471
+ element.dispatchEvent(new Event('change', { bubbles: true }));
472
+ }).finally(() => {
473
+ this.skipChangeValue = false;
474
+ });
475
+ }
476
+ else {
477
+ Log.warn('[Haori]', 'setValue is not supported for this element type.', element);
478
+ return Promise.resolve();
479
+ }
480
+ }
481
+ /**
482
+ * 入力エレメントの値を取得します。
483
+ * DOM要素の現在の値と同期します。
484
+ *
485
+ * @returns 入力エレメントの値
486
+ */
487
+ getValue() {
488
+ return this.value;
489
+ }
490
+ /**
491
+ * 内部の値をクリアします。エレメントのvalue値は変化しません。
492
+ */
493
+ clearValue() {
494
+ this.value = null;
495
+ }
496
+ /**
497
+ * 内部の値をDOMの値と同期します。
498
+ * changeイベント時など、DOM値が変更された後に呼び出されます。
499
+ */
500
+ syncValue() {
501
+ const element = this.getTarget();
502
+ if (element instanceof HTMLInputElement) {
503
+ if (element.type === 'checkbox' || element.type === 'radio') {
504
+ if (element.checked) {
505
+ const value = element.value;
506
+ if (value === 'true') {
507
+ this.value = true;
508
+ }
509
+ else if (value === 'false') {
510
+ this.value = false;
511
+ }
512
+ else {
513
+ this.value = value;
514
+ }
515
+ }
516
+ else {
517
+ // チェックボックスがOFFの場合
518
+ const value = element.value;
519
+ if (value === 'true') {
520
+ this.value = false;
521
+ }
522
+ else if (value === 'false') {
523
+ this.value = true;
524
+ }
525
+ else {
526
+ this.value = null;
527
+ }
528
+ }
529
+ }
530
+ else {
531
+ this.value = element.value;
532
+ }
533
+ }
534
+ else if (element instanceof HTMLTextAreaElement) {
535
+ this.value = element.value;
536
+ }
537
+ else if (element instanceof HTMLSelectElement) {
538
+ this.value = element.value;
539
+ }
540
+ }
541
+ /**
542
+ * 属性の値を評価して設定します。
543
+ * 評価値がfalseの場合は属性を削除します。
544
+ * 矯正評価属性の場合は元の値を設定します。
545
+ *
546
+ * @param name 属性名
547
+ * @param value 属性値
548
+ * @returns 属性の更新のPromise
549
+ */
550
+ setAttribute(name, value) {
551
+ if (this.skipMutationAttributes) {
552
+ return Promise.resolve();
553
+ }
554
+ if (value === null) {
555
+ return this.removeAttribute(name);
556
+ }
557
+ const contents = new AttributeContents(name, value);
558
+ this.attributeMap.set(name, contents);
559
+ this.skipMutationAttributes = true;
560
+ const element = this.getTarget();
561
+ const result = contents.isForceEvaluation()
562
+ ? value
563
+ : this.getAttribute(name);
564
+ return Queue.enqueue(() => {
565
+ if (result === null || result === false) {
566
+ element.removeAttribute(name);
567
+ }
568
+ else {
569
+ const string = String(result);
570
+ if (element.getAttribute(name) !== string) {
571
+ element.setAttribute(name, string);
572
+ }
573
+ }
574
+ }).finally(() => {
575
+ this.skipMutationAttributes = false;
576
+ });
577
+ }
578
+ /**
579
+ * 属性の値を削除します。
580
+ *
581
+ * @param name 属性名
582
+ * @returns 属性の削除のPromise
583
+ */
584
+ removeAttribute(name) {
585
+ if (this.skipMutationAttributes) {
586
+ return Promise.resolve();
587
+ }
588
+ this.attributeMap.delete(name);
589
+ this.skipMutationAttributes = true;
590
+ const element = this.getTarget();
591
+ return Queue.enqueue(() => {
592
+ element.removeAttribute(name);
593
+ }).finally(() => {
594
+ this.skipMutationAttributes = false;
595
+ });
596
+ }
597
+ /**
598
+ * 属性の評価された値を取得します。
599
+ * 複数の評価値がある場合は結合して返します。
600
+ *
601
+ * @param name 属性名
602
+ * @returns 評価された値
603
+ */
604
+ getAttribute(name) {
605
+ const contents = this.attributeMap.get(name);
606
+ if (contents === undefined) {
607
+ return null;
608
+ }
609
+ const results = contents.evaluate(this.getBindingData());
610
+ if (results.length === 1) {
611
+ return results[0];
612
+ }
613
+ return TextContents.joinEvaluateResults(results);
614
+ }
615
+ /**
616
+ * 属性の生の値を取得します。
617
+ *
618
+ * @param name 属性名
619
+ * @returns 生の属性値
620
+ */
621
+ getRawAttribute(name) {
622
+ const contents = this.attributeMap.get(name);
623
+ if (contents === undefined) {
624
+ return null;
625
+ }
626
+ return contents.getValue();
627
+ }
628
+ /**
629
+ * 属性名のリストを取得します。
630
+ *
631
+ * @return 属性名のリスト
632
+ */
633
+ getAttributeNames() {
634
+ return Array.from(this.attributeMap.keys());
635
+ }
636
+ /**
637
+ * 属性の有無を確認します。
638
+ *
639
+ * @param name 属性名
640
+ * @returns 属性の有無
641
+ */
642
+ hasAttribute(name) {
643
+ return this.attributeMap.has(name);
644
+ }
645
+ /**
646
+ * 子ノードを参照ノードの前に挿入します。
647
+ * 参照ノードがnullの場合、親の最後に追加されます。
648
+ *
649
+ * @param newChild 新しい子ノード
650
+ * @param referenceChild 参照ノード
651
+ * @return 挿入のPromise
652
+ */
653
+ insertBefore(newChild, referenceChild) {
654
+ if (this.skipMutationNodes) {
655
+ return Promise.resolve();
656
+ }
657
+ // 循環参照チェック
658
+ if (newChild === this) {
659
+ Log.error('[Haori]', 'Cannot insert element as child of itself');
660
+ return Promise.reject(new Error('Self-insertion not allowed'));
661
+ }
662
+ // 祖先チェック
663
+ const ancestors = new Set();
664
+ let ancestor = this.parent;
665
+ while (ancestor) {
666
+ ancestors.add(ancestor);
667
+ ancestor = ancestor.getParent();
668
+ }
669
+ if (ancestors.has(newChild)) {
670
+ Log.error('[Haori]', 'Cannot create circular reference');
671
+ return Promise.reject(new Error('Circular reference detected'));
672
+ }
673
+ // 同じ親内での移動かどうかを確認
674
+ const isSameParent = newChild.getParent() === this;
675
+ let newChildIndex = -1;
676
+ let referenceIndex = -1;
677
+ if (isSameParent) {
678
+ newChildIndex = this.children.indexOf(newChild);
679
+ if (referenceChild !== null) {
680
+ referenceIndex = this.children.indexOf(referenceChild);
681
+ }
682
+ }
683
+ const newChildParent = newChild.getParent();
684
+ if (newChildParent !== null) {
685
+ // 既存の親から削除
686
+ newChildParent.removeChild(newChild);
687
+ }
688
+ if (referenceChild === null) {
689
+ this.children.push(newChild);
690
+ }
691
+ else {
692
+ let index;
693
+ if (isSameParent) {
694
+ // 同じ親内での移動の場合、削除後のインデックスを調整
695
+ if (newChildIndex !== -1 && newChildIndex < referenceIndex) {
696
+ // 削除する要素が参照要素より前にあった場合、インデックスは1つ減る
697
+ index = referenceIndex - 1;
698
+ }
699
+ else {
700
+ index = referenceIndex;
701
+ }
702
+ }
703
+ else {
704
+ index = this.children.indexOf(referenceChild);
705
+ }
706
+ if (index === -1) {
707
+ Log.warn('[Haori]', 'Reference child not found in children.', referenceChild);
708
+ this.children.push(newChild);
709
+ }
710
+ else {
711
+ this.children.splice(index, 0, newChild);
712
+ }
713
+ }
714
+ newChild.setParent(this);
715
+ newChild.setMounted(this.mounted);
716
+ const prevSkip = this.skipMutationNodes;
717
+ this.skipMutationNodes = true;
718
+ return Queue.enqueue(() => {
719
+ this.target.insertBefore(newChild.getTarget(), referenceChild?.getTarget() || null);
720
+ }).finally(() => {
721
+ this.skipMutationNodes = prevSkip;
722
+ });
723
+ }
724
+ /**
725
+ * 指定した参照ノードの後に子ノードを挿入します。
726
+ *
727
+ * @param newChild 子ノード
728
+ * @param referenceChild 参照ノード
729
+ * @returns 挿入のPromise
730
+ */
731
+ insertAfter(newChild, referenceChild) {
732
+ if (referenceChild == null) {
733
+ return this.insertBefore(newChild, null);
734
+ }
735
+ const index = this.children.indexOf(referenceChild);
736
+ if (index === -1) {
737
+ Log.warn('[Haori]', 'Reference child not found in children.', referenceChild);
738
+ return this.insertBefore(newChild, null);
739
+ }
740
+ return this.insertBefore(newChild, this.children[index + 1] || null);
741
+ }
742
+ /**
743
+ * 前のエレメントフラグメントを取得します。
744
+ * 存在しない場合はnullを返します。
745
+ *
746
+ * @return 前のエレメントフラグメントまたはnull
747
+ */
748
+ getPrevious() {
749
+ const parent = this.getParent();
750
+ if (parent === null) {
751
+ return null;
752
+ }
753
+ const siblings = parent.getChildElementFragments();
754
+ const index = siblings.indexOf(this);
755
+ if (index <= 0) {
756
+ return null;
757
+ }
758
+ return siblings[index - 1];
759
+ }
760
+ /**
761
+ * 次のエレメントフラグメントを取得します。
762
+ * 存在しない場合はnullを返します。
763
+ *
764
+ * @return 次のエレメントフラグメントまたはnull
765
+ */
766
+ getNext() {
767
+ const parent = this.getParent();
768
+ if (parent === null) {
769
+ return null;
770
+ }
771
+ const siblings = parent.getChildElementFragments();
772
+ const index = siblings.indexOf(this);
773
+ if (index < 0 || index + 1 >= siblings.length) {
774
+ return null;
775
+ }
776
+ return siblings[index + 1];
777
+ }
778
+ /**
779
+ * 表示状態を返します。
780
+ *
781
+ * @returns 表示状態
782
+ */
783
+ isVisible() {
784
+ return this.visible;
785
+ }
786
+ /**
787
+ * エレメントを非表示にします。
788
+ *
789
+ * @returns エレメントの非表示のPromise
790
+ */
791
+ hide() {
792
+ this.visible = false;
793
+ this.display = this.getTarget().style.display;
794
+ this.getTarget().style.display = 'none';
795
+ this.getTarget().setAttribute(`${Env.prefix}if-false`, '');
796
+ return Promise.resolve();
797
+ }
798
+ /**
799
+ * エレメントを表示します。
800
+ *
801
+ * @return エレメントの表示のPromise
802
+ */
803
+ show() {
804
+ this.getTarget().style.display = this.display ?? '';
805
+ this.getTarget().removeAttribute(`${Env.prefix}if-false`);
806
+ this.visible = true;
807
+ return Promise.resolve();
808
+ }
809
+ /**
810
+ * 指定した属性名を持つ最も近い親要素を返します。
811
+ * 見つからない場合はnullを返します。
812
+ *
813
+ * @param name 属性名
814
+ * @returns 最も近い親要素またはnull
815
+ */
816
+ closestByAttribute(name) {
817
+ if (this.hasAttribute(name)) {
818
+ return this;
819
+ }
820
+ const parent = this.getParent();
821
+ if (parent === null) {
822
+ return null;
823
+ }
824
+ return parent.closestByAttribute(name);
825
+ }
826
+ }
827
+ /**
828
+ * テキストフラグメント。
829
+ * テキストノードを表現します。
830
+ */
831
+ export class TextFragment extends Fragment {
832
+ /**
833
+ * テキストフラグメントのコンストラクタ。
834
+ * 対象テキストノードの内容を初期化します。
835
+ *
836
+ * @param target 対象テキストノード
837
+ */
838
+ constructor(target) {
839
+ super(target);
840
+ /** 更新スキップフラグ(オブザーバーによる無限ループ対応) */
841
+ this.skipMutation = false;
842
+ this.text = target.textContent || '';
843
+ this.contents = new TextContents(this.text);
844
+ }
845
+ /**
846
+ * フラグメントをクローンします。
847
+ *
848
+ * @returns クローンされたフラグメント
849
+ */
850
+ clone() {
851
+ const clone = new TextFragment(this.target.cloneNode(true));
852
+ clone.mounted = false;
853
+ clone.text = this.text;
854
+ clone.contents = this.contents;
855
+ return clone;
856
+ }
857
+ /**
858
+ * フラグメントの対象ノードを取得します。
859
+ *
860
+ * @returns フラグメントの対象ノード
861
+ */
862
+ getTarget() {
863
+ return this.target;
864
+ }
865
+ /**
866
+ * コンテンツを更新します。
867
+ *
868
+ * @param text テキスト
869
+ * @returns 更新のPromise
870
+ */
871
+ setContent(text) {
872
+ if (this.skipMutation || this.text === text) {
873
+ return Promise.resolve();
874
+ }
875
+ this.text = text;
876
+ this.contents = new TextContents(text);
877
+ return this.evaluate();
878
+ }
879
+ /**
880
+ * フラグメントを評価します。
881
+ *
882
+ * @returns 評価結果のPromise
883
+ */
884
+ evaluate() {
885
+ if (this.contents.isRawEvaluate && this.parent === null) {
886
+ return Promise.reject(new Error('Parent fragment is required for raw evaluation'));
887
+ }
888
+ return Queue.enqueue(() => {
889
+ this.skipMutation = true;
890
+ if (this.contents.isRawEvaluate) {
891
+ this.parent.getTarget().innerHTML = this.contents.evaluate(this.parent.getBindingData())[0];
892
+ }
893
+ else if (this.contents.isEvaluate) {
894
+ this.target.textContent = TextContents.joinEvaluateResults(this.contents.evaluate(this.parent.getBindingData()));
895
+ }
896
+ else {
897
+ this.target.textContent = this.text;
898
+ }
899
+ }).finally(() => {
900
+ this.skipMutation = false;
901
+ });
902
+ }
903
+ }
904
+ /**
905
+ * コメントフラグメント。
906
+ * コメントノードを表現します。
907
+ */
908
+ export class CommentFragment extends Fragment {
909
+ /**
910
+ * コメントフラグメントのコンストラクタ。
911
+ * 対象コメントノードの内容を初期化します。
912
+ *
913
+ * @param target 対象コメントノード
914
+ */
915
+ constructor(target) {
916
+ super(target);
917
+ /** 更新スキップフラグ(オブザーバーによる無限ループ対応) */
918
+ this.skipMutation = false;
919
+ this.text = target.textContent || '';
920
+ }
921
+ /**
922
+ * フラグメントをクローンします。
923
+ *
924
+ * @returns クローンされたフラグメント
925
+ */
926
+ clone() {
927
+ const clone = new CommentFragment(this.target.cloneNode(true));
928
+ clone.mounted = false;
929
+ clone.text = this.text;
930
+ return clone;
931
+ }
932
+ /**
933
+ * フラグメントの対象ノードを取得します。
934
+ *
935
+ * @returns フラグメントの対象ノード
936
+ */
937
+ getTarget() {
938
+ return this.target;
939
+ }
940
+ /**
941
+ * コンテンツを更新します。
942
+ *
943
+ * @param text テキスト
944
+ * @return 更新のPromise
945
+ */
946
+ setContent(text) {
947
+ if (this.skipMutation || this.text === text) {
948
+ return Promise.resolve();
949
+ }
950
+ this.text = text;
951
+ return Queue.enqueue(() => {
952
+ this.skipMutation = true;
953
+ this.target.textContent = this.text;
954
+ }).finally(() => {
955
+ this.skipMutation = false;
956
+ });
957
+ }
958
+ }
959
+ /**
960
+ * 値の種別。
961
+ */
962
+ var ExpressionType;
963
+ (function (ExpressionType) {
964
+ /** テキスト */
965
+ ExpressionType[ExpressionType["TEXT"] = 0] = "TEXT";
966
+ /** 評価式 */
967
+ ExpressionType[ExpressionType["EXPRESSION"] = 1] = "EXPRESSION";
968
+ /** 生の評価式 */
969
+ ExpressionType[ExpressionType["RAW_EXPRESSION"] = 2] = "RAW_EXPRESSION";
970
+ })(ExpressionType || (ExpressionType = {}));
971
+ /**
972
+ * テキストコンテンツを管理するクラスです。
973
+ * 一度生成されると内部は変更しません。
974
+ */
975
+ class TextContents {
976
+ /**
977
+ * 評価結果を結合して文字列にします。
978
+ *
979
+ * @param contents 評価結果の配列
980
+ * @returns 結合された文字列
981
+ */
982
+ static joinEvaluateResults(contents) {
983
+ if (contents === null || contents.length === 0) {
984
+ return '';
985
+ }
986
+ return contents
987
+ .map(c => {
988
+ if (c === null || c === undefined || c === false || Number.isNaN(c)) {
989
+ return '';
990
+ }
991
+ else if (typeof c !== 'string') {
992
+ return String(c);
993
+ }
994
+ else {
995
+ return c;
996
+ }
997
+ })
998
+ .join('');
999
+ }
1000
+ /**
1001
+ * コンストラクタ。
1002
+ *
1003
+ * @param text テキスト
1004
+ */
1005
+ constructor(text) {
1006
+ /** コンテンツのリスト */
1007
+ this.contents = [];
1008
+ /** 評価式が含まれるかどうか */
1009
+ this.isEvaluate = false;
1010
+ /** 生の評価式が含まれるかどうか */
1011
+ this.isRawEvaluate = false;
1012
+ this.value = text;
1013
+ const matches = [...text.matchAll(TextContents.PLACEHOLDER_REGEX)];
1014
+ let lastIndex = 0;
1015
+ let hasEvaluate = false;
1016
+ let hasRawEvaluate = false;
1017
+ for (const match of matches) {
1018
+ // プレースホルダ前の通常テキスト
1019
+ if (match.index > lastIndex) {
1020
+ this.contents.push({
1021
+ text: text.slice(lastIndex, match.index),
1022
+ type: ExpressionType.TEXT,
1023
+ });
1024
+ }
1025
+ // プレースホルダ本体
1026
+ const content = {
1027
+ text: match[1] ?? match[2],
1028
+ type: match[1]
1029
+ ? ExpressionType.RAW_EXPRESSION
1030
+ : ExpressionType.EXPRESSION,
1031
+ };
1032
+ hasEvaluate = true;
1033
+ hasRawEvaluate =
1034
+ hasRawEvaluate || content.type === ExpressionType.RAW_EXPRESSION;
1035
+ this.contents.push(content);
1036
+ lastIndex = match.index + match[0].length;
1037
+ }
1038
+ // 最後のプレースホルダ以降の通常テキスト
1039
+ if (lastIndex < text.length) {
1040
+ this.contents.push({
1041
+ text: text.slice(lastIndex),
1042
+ type: ExpressionType.TEXT,
1043
+ });
1044
+ }
1045
+ this.isEvaluate = hasEvaluate;
1046
+ this.isRawEvaluate = hasRawEvaluate;
1047
+ this.checkRawExpressions();
1048
+ }
1049
+ /**
1050
+ * 評価前の値を取得します。
1051
+ *
1052
+ * @returns 評価前の値
1053
+ */
1054
+ getValue() {
1055
+ return this.value;
1056
+ }
1057
+ /**
1058
+ * RAW_EXPRESSION のチェックを行います。
1059
+ */
1060
+ checkRawExpressions() {
1061
+ for (let i = 0; i < this.contents.length; i++) {
1062
+ const content = this.contents[i];
1063
+ if (content.type === ExpressionType.RAW_EXPRESSION &&
1064
+ this.contents.length > 1) {
1065
+ Log.error('[Haori]', 'Raw expressions are not allowed in multi-content expressions.');
1066
+ this.contents[i].type = ExpressionType.EXPRESSION;
1067
+ }
1068
+ }
1069
+ }
1070
+ /**
1071
+ * 式評価を行い、結果を返します。
1072
+ *
1073
+ * @param bindingValues バインディングされた値のオブジェクト
1074
+ * @returns 評価結果のリスト
1075
+ */
1076
+ evaluate(bindingValues) {
1077
+ if (!this.isEvaluate && !this.isRawEvaluate) {
1078
+ return this.contents.map(c => c.text);
1079
+ }
1080
+ const results = [];
1081
+ this.contents.forEach(c => {
1082
+ try {
1083
+ if (c.type === ExpressionType.EXPRESSION ||
1084
+ c.type === ExpressionType.RAW_EXPRESSION) {
1085
+ const result = Expression.evaluate(c.text, bindingValues);
1086
+ results.push(result);
1087
+ }
1088
+ else {
1089
+ results.push(c.text);
1090
+ }
1091
+ }
1092
+ catch (error) {
1093
+ Log.error('[Haori]', `Error evaluating text expression: ${c.text}`, error);
1094
+ results.push('');
1095
+ }
1096
+ });
1097
+ return results;
1098
+ }
1099
+ }
1100
+ /** プレースホルダ検出用の正規表現 */
1101
+ TextContents.PLACEHOLDER_REGEX = /\{\{\{([\s\S]+?)\}\}\}|\{\{([\s\S]+?)\}\}/g;
1102
+ /**
1103
+ * 属性のコンテンツを管理するクラスです。
1104
+ * 一度生成されると内部は変更しません。
1105
+ */
1106
+ class AttributeContents extends TextContents {
1107
+ /**
1108
+ * コンストラクタ。
1109
+ *
1110
+ * @param name 属性名
1111
+ * @param text 属性値
1112
+ */
1113
+ constructor(name, value) {
1114
+ super(value);
1115
+ this.forceEvaluation =
1116
+ AttributeContents.FORCE_EVALUATION_ATTRIBUTES.includes(name);
1117
+ }
1118
+ /**
1119
+ * 強制評価フラグを取得します。
1120
+ *
1121
+ * @returns 強制評価フラグ
1122
+ */
1123
+ isForceEvaluation() {
1124
+ return this.forceEvaluation;
1125
+ }
1126
+ /**
1127
+ * 式評価を行い、結果を返します。
1128
+ *
1129
+ * @param bindingValues バインディングされた値のオブジェクト
1130
+ * @returns 評価結果のリスト
1131
+ */
1132
+ evaluate(bindingValues) {
1133
+ if (!this.isEvaluate && !this.forceEvaluation) {
1134
+ return this.contents.map(c => c.text);
1135
+ }
1136
+ const results = [];
1137
+ this.contents.forEach(c => {
1138
+ try {
1139
+ if ((this.forceEvaluation && c.type === ExpressionType.TEXT) ||
1140
+ c.type === ExpressionType.EXPRESSION ||
1141
+ c.type === ExpressionType.RAW_EXPRESSION) {
1142
+ const result = Expression.evaluate(c.text, bindingValues);
1143
+ results.push(result);
1144
+ }
1145
+ else {
1146
+ results.push(c.text);
1147
+ }
1148
+ }
1149
+ catch (error) {
1150
+ Log.error('[Haori]', `Error evaluating attribute expression: ${c.text}`, error);
1151
+ results.push('');
1152
+ }
1153
+ });
1154
+ if (this.forceEvaluation && results.length > 1) {
1155
+ Log.error('[Haori]', 'each or if expressions must have a single content.', results);
1156
+ return [results[0]];
1157
+ }
1158
+ return results;
1159
+ }
1160
+ }
1161
+ /** 強制評価する属性名 */
1162
+ AttributeContents.FORCE_EVALUATION_ATTRIBUTES = [
1163
+ 'data-if',
1164
+ 'hor-if',
1165
+ 'data-each',
1166
+ 'hor-each',
1167
+ ];
1168
+ //# sourceMappingURL=fragment.js.map