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.
Files changed (307) hide show
  1. package/README.ja.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/haori.cjs.js +10 -10
  4. package/dist/haori.es.js +191 -165
  5. package/dist/haori.iife.js +10 -10
  6. package/dist/index.d.ts +1 -1
  7. package/package.json +11 -3
  8. package/dist/haori.cjs.js.map +0 -1
  9. package/dist/haori.es.js.map +0 -1
  10. package/dist/haori.iife.js.map +0 -1
  11. package/dist/package.json +0 -70
  12. package/dist/src/builtins.d.ts +0 -100
  13. package/dist/src/builtins.d.ts.map +0 -1
  14. package/dist/src/builtins.js +0 -194
  15. package/dist/src/builtins.js.map +0 -1
  16. package/dist/src/core.d.ts +0 -505
  17. package/dist/src/core.d.ts.map +0 -1
  18. package/dist/src/core.js +0 -1817
  19. package/dist/src/core.js.map +0 -1
  20. package/dist/src/dev.d.ts +0 -35
  21. package/dist/src/dev.d.ts.map +0 -1
  22. package/dist/src/dev.js +0 -44
  23. package/dist/src/dev.js.map +0 -1
  24. package/dist/src/env.d.ts +0 -43
  25. package/dist/src/env.d.ts.map +0 -1
  26. package/dist/src/env.js +0 -108
  27. package/dist/src/env.js.map +0 -1
  28. package/dist/src/event.d.ts +0 -171
  29. package/dist/src/event.d.ts.map +0 -1
  30. package/dist/src/event.js +0 -235
  31. package/dist/src/event.js.map +0 -1
  32. package/dist/src/event_dispatcher.d.ts +0 -66
  33. package/dist/src/event_dispatcher.d.ts.map +0 -1
  34. package/dist/src/event_dispatcher.js +0 -167
  35. package/dist/src/event_dispatcher.js.map +0 -1
  36. package/dist/src/expression.d.ts +0 -260
  37. package/dist/src/expression.d.ts.map +0 -1
  38. package/dist/src/expression.js +0 -975
  39. package/dist/src/expression.js.map +0 -1
  40. package/dist/src/form.d.ts +0 -148
  41. package/dist/src/form.d.ts.map +0 -1
  42. package/dist/src/form.js +0 -428
  43. package/dist/src/form.js.map +0 -1
  44. package/dist/src/fragment.d.ts +0 -621
  45. package/dist/src/fragment.d.ts.map +0 -1
  46. package/dist/src/fragment.js +0 -1963
  47. package/dist/src/fragment.js.map +0 -1
  48. package/dist/src/haori.d.ts +0 -130
  49. package/dist/src/haori.d.ts.map +0 -1
  50. package/dist/src/haori.js +0 -215
  51. package/dist/src/haori.js.map +0 -1
  52. package/dist/src/import.d.ts +0 -19
  53. package/dist/src/import.d.ts.map +0 -1
  54. package/dist/src/import.js +0 -64
  55. package/dist/src/import.js.map +0 -1
  56. package/dist/src/index.d.ts +0 -27
  57. package/dist/src/index.d.ts.map +0 -1
  58. package/dist/src/index.js +0 -30
  59. package/dist/src/index.js.map +0 -1
  60. package/dist/src/intersect.d.ts +0 -28
  61. package/dist/src/intersect.d.ts.map +0 -1
  62. package/dist/src/intersect.js +0 -201
  63. package/dist/src/intersect.js.map +0 -1
  64. package/dist/src/log.d.ts +0 -32
  65. package/dist/src/log.d.ts.map +0 -1
  66. package/dist/src/log.js +0 -43
  67. package/dist/src/log.js.map +0 -1
  68. package/dist/src/observer.d.ts +0 -24
  69. package/dist/src/observer.d.ts.map +0 -1
  70. package/dist/src/observer.js +0 -131
  71. package/dist/src/observer.js.map +0 -1
  72. package/dist/src/procedure.d.ts +0 -447
  73. package/dist/src/procedure.d.ts.map +0 -1
  74. package/dist/src/procedure.js +0 -1921
  75. package/dist/src/procedure.js.map +0 -1
  76. package/dist/src/queue.d.ts +0 -35
  77. package/dist/src/queue.d.ts.map +0 -1
  78. package/dist/src/queue.js +0 -202
  79. package/dist/src/queue.js.map +0 -1
  80. package/dist/src/url.d.ts +0 -14
  81. package/dist/src/url.d.ts.map +0 -1
  82. package/dist/src/url.js +0 -22
  83. package/dist/src/url.js.map +0 -1
  84. package/dist/tests/bind-arg-own-binding.test.d.ts +0 -2
  85. package/dist/tests/bind-arg-own-binding.test.d.ts.map +0 -1
  86. package/dist/tests/bind-arg-own-binding.test.js +0 -63
  87. package/dist/tests/bind-arg-own-binding.test.js.map +0 -1
  88. package/dist/tests/bindcomplete-and-scope.test.d.ts +0 -2
  89. package/dist/tests/bindcomplete-and-scope.test.d.ts.map +0 -1
  90. package/dist/tests/bindcomplete-and-scope.test.js +0 -115
  91. package/dist/tests/bindcomplete-and-scope.test.js.map +0 -1
  92. package/dist/tests/builtins.test.d.ts +0 -2
  93. package/dist/tests/builtins.test.d.ts.map +0 -1
  94. package/dist/tests/builtins.test.js +0 -152
  95. package/dist/tests/builtins.test.js.map +0 -1
  96. package/dist/tests/click-attributes.test.d.ts +0 -2
  97. package/dist/tests/click-attributes.test.d.ts.map +0 -1
  98. package/dist/tests/click-attributes.test.js +0 -113
  99. package/dist/tests/click-attributes.test.js.map +0 -1
  100. package/dist/tests/click-no-disabled.test.d.ts +0 -2
  101. package/dist/tests/click-no-disabled.test.d.ts.map +0 -1
  102. package/dist/tests/click-no-disabled.test.js +0 -67
  103. package/dist/tests/click-no-disabled.test.js.map +0 -1
  104. package/dist/tests/core.test.d.ts +0 -5
  105. package/dist/tests/core.test.d.ts.map +0 -1
  106. package/dist/tests/core.test.js +0 -1056
  107. package/dist/tests/core.test.js.map +0 -1
  108. package/dist/tests/data-bind-arg-reeval.test.d.ts +0 -2
  109. package/dist/tests/data-bind-arg-reeval.test.d.ts.map +0 -1
  110. package/dist/tests/data-bind-arg-reeval.test.js +0 -119
  111. package/dist/tests/data-bind-arg-reeval.test.js.map +0 -1
  112. package/dist/tests/data-bind-merge.test.d.ts +0 -2
  113. package/dist/tests/data-bind-merge.test.d.ts.map +0 -1
  114. package/dist/tests/data-bind-merge.test.js +0 -86
  115. package/dist/tests/data-bind-merge.test.js.map +0 -1
  116. package/dist/tests/data-click-defer.test.d.ts +0 -2
  117. package/dist/tests/data-click-defer.test.d.ts.map +0 -1
  118. package/dist/tests/data-click-defer.test.js +0 -57
  119. package/dist/tests/data-click-defer.test.js.map +0 -1
  120. package/dist/tests/data-click-run.test.d.ts +0 -5
  121. package/dist/tests/data-click-run.test.d.ts.map +0 -1
  122. package/dist/tests/data-click-run.test.js +0 -109
  123. package/dist/tests/data-click-run.test.js.map +0 -1
  124. package/dist/tests/data-derive.test.d.ts +0 -2
  125. package/dist/tests/data-derive.test.d.ts.map +0 -1
  126. package/dist/tests/data-derive.test.js +0 -999
  127. package/dist/tests/data-derive.test.js.map +0 -1
  128. package/dist/tests/data-each-browserlike.test.d.ts +0 -2
  129. package/dist/tests/data-each-browserlike.test.d.ts.map +0 -1
  130. package/dist/tests/data-each-browserlike.test.js +0 -48
  131. package/dist/tests/data-each-browserlike.test.js.map +0 -1
  132. package/dist/tests/data-each-fragment-debug.test.d.ts +0 -2
  133. package/dist/tests/data-each-fragment-debug.test.d.ts.map +0 -1
  134. package/dist/tests/data-each-fragment-debug.test.js +0 -119
  135. package/dist/tests/data-each-fragment-debug.test.js.map +0 -1
  136. package/dist/tests/data-each-table.test.d.ts +0 -2
  137. package/dist/tests/data-each-table.test.d.ts.map +0 -1
  138. package/dist/tests/data-each-table.test.js +0 -131
  139. package/dist/tests/data-each-table.test.js.map +0 -1
  140. package/dist/tests/data-each-template-recovery.test.d.ts +0 -2
  141. package/dist/tests/data-each-template-recovery.test.d.ts.map +0 -1
  142. package/dist/tests/data-each-template-recovery.test.js +0 -87
  143. package/dist/tests/data-each-template-recovery.test.js.map +0 -1
  144. package/dist/tests/data-event-prevent.test.d.ts +0 -2
  145. package/dist/tests/data-event-prevent.test.d.ts.map +0 -1
  146. package/dist/tests/data-event-prevent.test.js +0 -76
  147. package/dist/tests/data-event-prevent.test.js.map +0 -1
  148. package/dist/tests/data-fetch-form-input-binding.test.d.ts +0 -2
  149. package/dist/tests/data-fetch-form-input-binding.test.d.ts.map +0 -1
  150. package/dist/tests/data-fetch-form-input-binding.test.js +0 -202
  151. package/dist/tests/data-fetch-form-input-binding.test.js.map +0 -1
  152. package/dist/tests/data-fetch-tbody-dom.test.d.ts +0 -2
  153. package/dist/tests/data-fetch-tbody-dom.test.d.ts.map +0 -1
  154. package/dist/tests/data-fetch-tbody-dom.test.js +0 -82
  155. package/dist/tests/data-fetch-tbody-dom.test.js.map +0 -1
  156. package/dist/tests/data-form-container.test.d.ts +0 -2
  157. package/dist/tests/data-form-container.test.d.ts.map +0 -1
  158. package/dist/tests/data-form-container.test.js +0 -88
  159. package/dist/tests/data-form-container.test.js.map +0 -1
  160. package/dist/tests/data-if-dev-diagnostics.test.d.ts +0 -2
  161. package/dist/tests/data-if-dev-diagnostics.test.d.ts.map +0 -1
  162. package/dist/tests/data-if-dev-diagnostics.test.js +0 -55
  163. package/dist/tests/data-if-dev-diagnostics.test.js.map +0 -1
  164. package/dist/tests/data-if-falsy.test.d.ts +0 -2
  165. package/dist/tests/data-if-falsy.test.d.ts.map +0 -1
  166. package/dist/tests/data-if-falsy.test.js +0 -73
  167. package/dist/tests/data-if-falsy.test.js.map +0 -1
  168. package/dist/tests/data-load-on-show.test.d.ts +0 -2
  169. package/dist/tests/data-load-on-show.test.d.ts.map +0 -1
  170. package/dist/tests/data-load-on-show.test.js +0 -98
  171. package/dist/tests/data-load-on-show.test.js.map +0 -1
  172. package/dist/tests/derive-each-render-wait.test.d.ts +0 -2
  173. package/dist/tests/derive-each-render-wait.test.d.ts.map +0 -1
  174. package/dist/tests/derive-each-render-wait.test.js +0 -90
  175. package/dist/tests/derive-each-render-wait.test.js.map +0 -1
  176. package/dist/tests/dev.test.d.ts +0 -2
  177. package/dist/tests/dev.test.d.ts.map +0 -1
  178. package/dist/tests/dev.test.js +0 -51
  179. package/dist/tests/dev.test.js.map +0 -1
  180. package/dist/tests/each-update-event.test.d.ts +0 -2
  181. package/dist/tests/each-update-event.test.d.ts.map +0 -1
  182. package/dist/tests/each-update-event.test.js +0 -83
  183. package/dist/tests/each-update-event.test.js.map +0 -1
  184. package/dist/tests/each_arg.test.d.ts +0 -2
  185. package/dist/tests/each_arg.test.d.ts.map +0 -1
  186. package/dist/tests/each_arg.test.js +0 -41
  187. package/dist/tests/each_arg.test.js.map +0 -1
  188. package/dist/tests/env.test.d.ts +0 -2
  189. package/dist/tests/env.test.d.ts.map +0 -1
  190. package/dist/tests/env.test.js +0 -124
  191. package/dist/tests/env.test.js.map +0 -1
  192. package/dist/tests/evaluation-profile.test.d.ts +0 -2
  193. package/dist/tests/evaluation-profile.test.d.ts.map +0 -1
  194. package/dist/tests/evaluation-profile.test.js +0 -92
  195. package/dist/tests/evaluation-profile.test.js.map +0 -1
  196. package/dist/tests/event.test.d.ts +0 -2
  197. package/dist/tests/event.test.d.ts.map +0 -1
  198. package/dist/tests/event.test.js +0 -300
  199. package/dist/tests/event.test.js.map +0 -1
  200. package/dist/tests/expression-disallowed-keyword-hint.test.d.ts +0 -2
  201. package/dist/tests/expression-disallowed-keyword-hint.test.d.ts.map +0 -1
  202. package/dist/tests/expression-disallowed-keyword-hint.test.js +0 -45
  203. package/dist/tests/expression-disallowed-keyword-hint.test.js.map +0 -1
  204. package/dist/tests/expression-forbidden-hint.test.d.ts +0 -2
  205. package/dist/tests/expression-forbidden-hint.test.d.ts.map +0 -1
  206. package/dist/tests/expression-forbidden-hint.test.js +0 -37
  207. package/dist/tests/expression-forbidden-hint.test.js.map +0 -1
  208. package/dist/tests/expression.test.d.ts +0 -2
  209. package/dist/tests/expression.test.d.ts.map +0 -1
  210. package/dist/tests/expression.test.js +0 -482
  211. package/dist/tests/expression.test.js.map +0 -1
  212. package/dist/tests/fetch-and-procedure-scenarios.test.d.ts +0 -2
  213. package/dist/tests/fetch-and-procedure-scenarios.test.d.ts.map +0 -1
  214. package/dist/tests/fetch-and-procedure-scenarios.test.js +0 -195
  215. package/dist/tests/fetch-and-procedure-scenarios.test.js.map +0 -1
  216. package/dist/tests/form-number-type.test.d.ts +0 -2
  217. package/dist/tests/form-number-type.test.d.ts.map +0 -1
  218. package/dist/tests/form-number-type.test.js +0 -83
  219. package/dist/tests/form-number-type.test.js.map +0 -1
  220. package/dist/tests/form.test.d.ts +0 -2
  221. package/dist/tests/form.test.d.ts.map +0 -1
  222. package/dist/tests/form.test.js +0 -636
  223. package/dist/tests/form.test.js.map +0 -1
  224. package/dist/tests/fragment.test.d.ts +0 -2
  225. package/dist/tests/fragment.test.d.ts.map +0 -1
  226. package/dist/tests/fragment.test.js +0 -284
  227. package/dist/tests/fragment.test.js.map +0 -1
  228. package/dist/tests/haori.test.d.ts +0 -2
  229. package/dist/tests/haori.test.d.ts.map +0 -1
  230. package/dist/tests/haori.test.js +0 -149
  231. package/dist/tests/haori.test.js.map +0 -1
  232. package/dist/tests/helpers/async.d.ts +0 -20
  233. package/dist/tests/helpers/async.d.ts.map +0 -1
  234. package/dist/tests/helpers/async.js +0 -35
  235. package/dist/tests/helpers/async.js.map +0 -1
  236. package/dist/tests/import.test.d.ts +0 -2
  237. package/dist/tests/import.test.d.ts.map +0 -1
  238. package/dist/tests/import.test.js +0 -148
  239. package/dist/tests/import.test.js.map +0 -1
  240. package/dist/tests/intersect.test.d.ts +0 -2
  241. package/dist/tests/intersect.test.d.ts.map +0 -1
  242. package/dist/tests/intersect.test.js +0 -173
  243. package/dist/tests/intersect.test.js.map +0 -1
  244. package/dist/tests/log.test.d.ts +0 -2
  245. package/dist/tests/log.test.d.ts.map +0 -1
  246. package/dist/tests/log.test.js +0 -58
  247. package/dist/tests/log.test.js.map +0 -1
  248. package/dist/tests/observer.test.d.ts +0 -2
  249. package/dist/tests/observer.test.d.ts.map +0 -1
  250. package/dist/tests/observer.test.js +0 -64
  251. package/dist/tests/observer.test.js.map +0 -1
  252. package/dist/tests/placeholder-resolution.test.d.ts +0 -2
  253. package/dist/tests/placeholder-resolution.test.d.ts.map +0 -1
  254. package/dist/tests/placeholder-resolution.test.js +0 -463
  255. package/dist/tests/placeholder-resolution.test.js.map +0 -1
  256. package/dist/tests/procedure-action-operations.test.d.ts +0 -2
  257. package/dist/tests/procedure-action-operations.test.d.ts.map +0 -1
  258. package/dist/tests/procedure-action-operations.test.js +0 -1140
  259. package/dist/tests/procedure-action-operations.test.js.map +0 -1
  260. package/dist/tests/procedure-bind-append.test.d.ts +0 -2
  261. package/dist/tests/procedure-bind-append.test.d.ts.map +0 -1
  262. package/dist/tests/procedure-bind-append.test.js +0 -80
  263. package/dist/tests/procedure-bind-append.test.js.map +0 -1
  264. package/dist/tests/procedure-fetch-options.test.d.ts +0 -2
  265. package/dist/tests/procedure-fetch-options.test.d.ts.map +0 -1
  266. package/dist/tests/procedure-fetch-options.test.js +0 -255
  267. package/dist/tests/procedure-fetch-options.test.js.map +0 -1
  268. package/dist/tests/procedure.test.d.ts +0 -2
  269. package/dist/tests/procedure.test.d.ts.map +0 -1
  270. package/dist/tests/procedure.test.js +0 -137
  271. package/dist/tests/procedure.test.js.map +0 -1
  272. package/dist/tests/procedure_events.test.d.ts +0 -7
  273. package/dist/tests/procedure_events.test.d.ts.map +0 -1
  274. package/dist/tests/procedure_events.test.js +0 -261
  275. package/dist/tests/procedure_events.test.js.map +0 -1
  276. package/dist/tests/render-wait.test.d.ts +0 -2
  277. package/dist/tests/render-wait.test.d.ts.map +0 -1
  278. package/dist/tests/render-wait.test.js +0 -68
  279. package/dist/tests/render-wait.test.js.map +0 -1
  280. package/dist/tests/reset_each.test.d.ts +0 -2
  281. package/dist/tests/reset_each.test.d.ts.map +0 -1
  282. package/dist/tests/reset_each.test.js +0 -215
  283. package/dist/tests/reset_each.test.js.map +0 -1
  284. package/dist/tests/row-move.test.d.ts +0 -2
  285. package/dist/tests/row-move.test.d.ts.map +0 -1
  286. package/dist/tests/row-move.test.js +0 -220
  287. package/dist/tests/row-move.test.js.map +0 -1
  288. package/dist/tests/row-operations.test.d.ts +0 -2
  289. package/dist/tests/row-operations.test.d.ts.map +0 -1
  290. package/dist/tests/row-operations.test.js +0 -259
  291. package/dist/tests/row-operations.test.js.map +0 -1
  292. package/dist/tests/scheduled-evaluate-wait.test.d.ts +0 -2
  293. package/dist/tests/scheduled-evaluate-wait.test.d.ts.map +0 -1
  294. package/dist/tests/scheduled-evaluate-wait.test.js +0 -68
  295. package/dist/tests/scheduled-evaluate-wait.test.js.map +0 -1
  296. package/dist/tests/url.test.d.ts +0 -2
  297. package/dist/tests/url.test.d.ts.map +0 -1
  298. package/dist/tests/url.test.js +0 -150
  299. package/dist/tests/url.test.js.map +0 -1
  300. package/dist/vite.config.d.ts +0 -3
  301. package/dist/vite.config.d.ts.map +0 -1
  302. package/dist/vite.config.js +0 -28
  303. package/dist/vite.config.js.map +0 -1
  304. package/dist/vitest.config.d.ts +0 -3
  305. package/dist/vitest.config.d.ts.map +0 -1
  306. package/dist/vitest.config.js +0 -19
  307. package/dist/vitest.config.js.map +0 -1
@@ -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