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,1040 @@
1
+ /**
2
+ * @fileoverview 手続き的処理管理機能
3
+ *
4
+ * イベントに基づく手続き的な処理を提供します。
5
+ */
6
+ import Core from './core';
7
+ import Env from './env';
8
+ import Form from './form';
9
+ import Fragment from './fragment';
10
+ import Haori from './haori';
11
+ import Log from './log';
12
+ import HaoriEvent from './event';
13
+ /**
14
+ * 手続き的処理管理クラスです。
15
+ */
16
+ export default class Procedure {
17
+ /**
18
+ * イベント属性名を正しく生成します。
19
+ * 例: ("click", "fetch") => "data-click-fetch"
20
+ * (null, "fetch") => "data-fetch"
21
+ * ("change", "bind-arg") => "data-change-bind-arg"
22
+ * 非イベント変種が "data-fetch-xxx" として存在するものについては、event が null の場合にそちらを返します。
23
+ */
24
+ static attrName(event, key, hasFetchFallback = false) {
25
+ if (event) {
26
+ return `${Env.prefix}${event}-${key}`;
27
+ }
28
+ return hasFetchFallback
29
+ ? `${Env.prefix}fetch-${key}`
30
+ : `${Env.prefix}${key}`;
31
+ }
32
+ /**
33
+ * オプションをフラグメントの属性から構築します。
34
+ *
35
+ * @param fragment フラグメント
36
+ * @param event イベント名
37
+ * @return 構築されたオプション
38
+ */
39
+ static buildOptions(fragment, event) {
40
+ const options = {
41
+ targetFragment: fragment,
42
+ };
43
+ if (event) {
44
+ // validate(spec: data-???-validate)
45
+ if (fragment.hasAttribute(Procedure.attrName(event, 'validate'))) {
46
+ options.valid = true;
47
+ }
48
+ // confirm
49
+ if (fragment.hasAttribute(Procedure.attrName(event, 'confirm'))) {
50
+ options.confirmMessage = fragment.getAttribute(Procedure.attrName(event, 'confirm'));
51
+ }
52
+ // data(イベント)
53
+ if (fragment.hasAttribute(Procedure.attrName(event, 'data'))) {
54
+ options.data = Core.parseDataBind(fragment.getRawAttribute(Procedure.attrName(event, 'data')));
55
+ }
56
+ // form(イベント)
57
+ if (fragment.hasAttribute(Procedure.attrName(event, 'form'))) {
58
+ const formSelector = fragment.getRawAttribute(Procedure.attrName(event, 'form'));
59
+ if (formSelector) {
60
+ const formElement = document.body.querySelector(formSelector);
61
+ if (formElement !== null) {
62
+ options.formFragment = Form.getFormFragment(Fragment.get(formElement));
63
+ }
64
+ else {
65
+ Log.error('Haori', `Form element not found: ${formSelector}` +
66
+ ` (${Procedure.attrName(event, 'form')})`);
67
+ }
68
+ }
69
+ else {
70
+ // 属性はあるが値が省略された場合は自要素もしくは先祖の form を対象
71
+ options.formFragment = Form.getFormFragment(fragment);
72
+ }
73
+ }
74
+ else if (event === 'change') {
75
+ // changeイベントの場合、data-change-form属性がなくても自動的にフォームを検索
76
+ options.formFragment = Form.getFormFragment(fragment);
77
+ }
78
+ if (fragment.hasAttribute(`${Env.prefix}${event}-before-run`)) {
79
+ const body = fragment.getRawAttribute(`${Env.prefix}${event}-before-run`);
80
+ try {
81
+ options.beforeCallback = new Function('fetchUrl', 'fetchOptions', `
82
+ "use strict";
83
+ ${body}
84
+ `);
85
+ }
86
+ catch (e) {
87
+ Log.error('Haori', `Invalid before script: ${e}`);
88
+ }
89
+ }
90
+ }
91
+ // fetch URL(イベントあり/なし)
92
+ const fetchAttrName = Procedure.attrName(event, 'fetch');
93
+ const hasFetchAttr = fragment.hasAttribute(fetchAttrName);
94
+ if (hasFetchAttr) {
95
+ options.fetchUrl = fragment.getAttribute(fetchAttrName);
96
+ }
97
+ const fetchOptions = {};
98
+ // fetch-method(イベントあり/なし)
99
+ // event: data-{event}-fetch-method, non-event: data-fetch-method
100
+ if (event) {
101
+ const fetchMethodAttrEvent = Procedure.attrName(event, 'fetch-method');
102
+ if (fragment.hasAttribute(fetchMethodAttrEvent)) {
103
+ fetchOptions.method = fragment.getAttribute(fetchMethodAttrEvent);
104
+ }
105
+ }
106
+ else {
107
+ const fetchMethodAttrNonEvent = Procedure.attrName(null, 'method', true);
108
+ if (fragment.hasAttribute(fetchMethodAttrNonEvent)) {
109
+ fetchOptions.method = fragment.getAttribute(fetchMethodAttrNonEvent);
110
+ }
111
+ }
112
+ // fetch-headers(イベントあり/なし)
113
+ // event: data-{event}-fetch-headers, non-event: data-fetch-headers
114
+ if (event) {
115
+ const fetchHeadersAttrEvent = Procedure.attrName(event, 'fetch-headers');
116
+ if (fragment.hasAttribute(fetchHeadersAttrEvent)) {
117
+ const headersString = fragment.getRawAttribute(fetchHeadersAttrEvent);
118
+ try {
119
+ fetchOptions.headers = Core.parseDataBind(headersString);
120
+ }
121
+ catch (e) {
122
+ Log.error('Haori', `Invalid fetch headers: ${e}`);
123
+ }
124
+ }
125
+ }
126
+ else {
127
+ const fetchHeadersAttrNonEvent = Procedure.attrName(null, 'headers', true);
128
+ if (fragment.hasAttribute(fetchHeadersAttrNonEvent)) {
129
+ const headersString = fragment.getRawAttribute(fetchHeadersAttrNonEvent);
130
+ try {
131
+ fetchOptions.headers = Core.parseDataBind(headersString);
132
+ }
133
+ catch (e) {
134
+ Log.error('Haori', `Invalid fetch headers: ${e}`);
135
+ }
136
+ }
137
+ }
138
+ // fetch-content-type(イベントあり/なし)
139
+ // event: data-{event}-fetch-content-type
140
+ // non-event: data-fetch-content-type
141
+ if (event) {
142
+ const fetchCTAttrEvent = Procedure.attrName(event, 'fetch-content-type');
143
+ if (fragment.hasAttribute(fetchCTAttrEvent)) {
144
+ fetchOptions.headers = {
145
+ ...fetchOptions.headers,
146
+ 'Content-Type': fragment.getAttribute(fetchCTAttrEvent),
147
+ };
148
+ }
149
+ else if (fetchOptions.method &&
150
+ fetchOptions.method !== 'GET' &&
151
+ fetchOptions.method !== 'HEAD' &&
152
+ fetchOptions.method !== 'OPTIONS') {
153
+ // only set default Content-Type when one is not already provided
154
+ let hasContentType = false;
155
+ if (fetchOptions.headers && typeof fetchOptions.headers === 'object') {
156
+ const headersObj = fetchOptions.headers;
157
+ hasContentType = 'Content-Type' in headersObj;
158
+ }
159
+ if (!hasContentType) {
160
+ fetchOptions.headers = {
161
+ ...fetchOptions.headers,
162
+ 'Content-Type': 'application/json',
163
+ };
164
+ }
165
+ }
166
+ else if (fetchOptions.method &&
167
+ (fetchOptions.method === 'GET' ||
168
+ fetchOptions.method === 'HEAD' ||
169
+ fetchOptions.method === 'OPTIONS')) {
170
+ // 仕様: GET/HEAD/OPTIONS 既定は application/x-www-form-urlencoded
171
+ fetchOptions.headers = {
172
+ ...fetchOptions.headers,
173
+ 'Content-Type': 'application/x-www-form-urlencoded',
174
+ };
175
+ }
176
+ }
177
+ else {
178
+ const fetchCTAttrNonEvent = Procedure.attrName(null, 'content-type', true);
179
+ if (fragment.hasAttribute(fetchCTAttrNonEvent)) {
180
+ fetchOptions.headers = {
181
+ ...fetchOptions.headers,
182
+ 'Content-Type': fragment.getAttribute(fetchCTAttrNonEvent),
183
+ };
184
+ }
185
+ else if (fetchOptions.method &&
186
+ fetchOptions.method !== 'GET' &&
187
+ fetchOptions.method !== 'HEAD' &&
188
+ fetchOptions.method !== 'OPTIONS') {
189
+ // only set default Content-Type when one is not already provided
190
+ let hasContentType = false;
191
+ if (fetchOptions.headers && typeof fetchOptions.headers === 'object') {
192
+ const headersObj = fetchOptions.headers;
193
+ hasContentType = 'Content-Type' in headersObj;
194
+ }
195
+ if (!hasContentType) {
196
+ fetchOptions.headers = {
197
+ ...fetchOptions.headers,
198
+ 'Content-Type': 'application/json',
199
+ };
200
+ }
201
+ }
202
+ else if (fetchOptions.method &&
203
+ (fetchOptions.method === 'GET' ||
204
+ fetchOptions.method === 'HEAD' ||
205
+ fetchOptions.method === 'OPTIONS')) {
206
+ // 仕様: GET/HEAD/OPTIONS 既定は application/x-www-form-urlencoded
207
+ fetchOptions.headers = {
208
+ ...fetchOptions.headers,
209
+ 'Content-Type': 'application/x-www-form-urlencoded',
210
+ };
211
+ }
212
+ }
213
+ if (Object.keys(fetchOptions).length > 0) {
214
+ options.fetchOptions = fetchOptions;
215
+ }
216
+ // bind(イベントあり/なし: 非イベントは data-fetch-bind)
217
+ const bindAttr = event
218
+ ? Procedure.attrName(event, 'bind')
219
+ : Procedure.attrName(null, 'bind', true);
220
+ if (fragment.hasAttribute(bindAttr)) {
221
+ const bindSelector = fragment.getRawAttribute(bindAttr);
222
+ if (bindSelector) {
223
+ const bindElements = document.body.querySelectorAll(bindSelector);
224
+ if (bindElements.length > 0) {
225
+ options.bindFragments = [];
226
+ bindElements.forEach(element => {
227
+ const fragment = Fragment.get(element);
228
+ if (fragment) {
229
+ options.bindFragments.push(fragment);
230
+ }
231
+ });
232
+ }
233
+ else {
234
+ Log.error('Haori', `Bind element not found: ${bindSelector} (${bindAttr})`);
235
+ }
236
+ }
237
+ }
238
+ const bindArgAttrEvent = Procedure.attrName(event, 'bind-arg');
239
+ const bindArgAttrNonEventLegacy = Procedure.attrName(null, 'arg', true); // data-fetch-arg
240
+ const bindArgAttrNonEventNew = Procedure.attrName(null, 'bind-arg', true); // data-fetch-bind-arg (less common)
241
+ if (event) {
242
+ if (fragment.hasAttribute(bindArgAttrEvent)) {
243
+ options.bindArg = fragment.getRawAttribute(bindArgAttrEvent);
244
+ }
245
+ }
246
+ else {
247
+ // Prefer legacy `data-fetch-arg` for non-event usage.
248
+ // Fallback to `data-fetch-bind-arg` if legacy is not present.
249
+ if (fragment.hasAttribute(bindArgAttrNonEventLegacy)) {
250
+ options.bindArg = fragment.getRawAttribute(bindArgAttrNonEventLegacy);
251
+ }
252
+ else if (fragment.hasAttribute(bindArgAttrNonEventNew)) {
253
+ options.bindArg = fragment.getRawAttribute(bindArgAttrNonEventNew);
254
+ }
255
+ }
256
+ const bindParamsAttr = event
257
+ ? Procedure.attrName(event, 'bind-params')
258
+ : Procedure.attrName(null, 'bind-params', true);
259
+ if (fragment.hasAttribute(bindParamsAttr)) {
260
+ const paramsString = fragment.getRawAttribute(bindParamsAttr);
261
+ options.bindParams = paramsString.split('&').map(p => p.trim());
262
+ }
263
+ if (event) {
264
+ if (fragment.hasAttribute(Procedure.attrName(event, 'adjust'))) {
265
+ const adjustSelector = fragment.getRawAttribute(Procedure.attrName(event, 'adjust'));
266
+ if (adjustSelector) {
267
+ const adjustElements = document.body.querySelectorAll(adjustSelector);
268
+ if (adjustElements.length > 0) {
269
+ options.adjustFragments = [];
270
+ adjustElements.forEach(element => {
271
+ const fragment = Fragment.get(element);
272
+ if (fragment) {
273
+ options.adjustFragments.push(fragment);
274
+ }
275
+ });
276
+ }
277
+ else {
278
+ Log.error('Haori', `Adjust element not found: ${adjustSelector}` +
279
+ ` (${Procedure.attrName(event, 'adjust')})`);
280
+ }
281
+ }
282
+ if (fragment.hasAttribute(Procedure.attrName(event, 'adjust-value'))) {
283
+ const valueString = fragment.getRawAttribute(Procedure.attrName(event, 'adjust-value'));
284
+ const value = Number(valueString);
285
+ if (!isNaN(value)) {
286
+ options.adjustValue = value;
287
+ }
288
+ }
289
+ }
290
+ if (fragment.hasAttribute(Procedure.attrName(event, 'row-add'))) {
291
+ options.rowAdd = true;
292
+ }
293
+ if (fragment.hasAttribute(Procedure.attrName(event, 'row-remove'))) {
294
+ options.rowRemove = true;
295
+ }
296
+ if (fragment.hasAttribute(Procedure.attrName(event, 'row-prev'))) {
297
+ options.rowMovePrev = true;
298
+ }
299
+ if (fragment.hasAttribute(Procedure.attrName(event, 'row-next'))) {
300
+ options.rowMoveNext = true;
301
+ }
302
+ if (fragment.hasAttribute(`${Env.prefix}${event}-after-run`)) {
303
+ const body = fragment.getRawAttribute(`${Env.prefix}${event}-after-run`);
304
+ try {
305
+ options.afterCallback = new Function('response', `
306
+ "use strict";
307
+ ${body}
308
+ `);
309
+ }
310
+ catch (e) {
311
+ Log.error('Haori', `Invalid after script: ${e}`);
312
+ }
313
+ }
314
+ if (fragment.hasAttribute(Procedure.attrName(event, 'dialog'))) {
315
+ options.dialogMessage = fragment.getAttribute(Procedure.attrName(event, 'dialog'));
316
+ }
317
+ if (fragment.hasAttribute(Procedure.attrName(event, 'toast'))) {
318
+ options.toastMessage = fragment.getAttribute(Procedure.attrName(event, 'toast'));
319
+ }
320
+ if (fragment.hasAttribute(Procedure.attrName(event, 'redirect'))) {
321
+ options.redirectUrl = fragment.getAttribute(Procedure.attrName(event, 'redirect'));
322
+ }
323
+ // reset/refetch/click/open/close(イベント、CSSセレクタ)
324
+ const selectorAttrs = [
325
+ 'reset',
326
+ 'refetch',
327
+ 'click',
328
+ 'open',
329
+ 'close',
330
+ ];
331
+ selectorAttrs.forEach(attrKey => {
332
+ const attrName = Procedure.attrName(event, attrKey);
333
+ if (!fragment.hasAttribute(attrName)) {
334
+ return;
335
+ }
336
+ const selector = fragment.getRawAttribute(attrName);
337
+ const list = [];
338
+ if (selector) {
339
+ const elements = document.body.querySelectorAll(selector);
340
+ elements.forEach(el => {
341
+ const frag = Fragment.get(el);
342
+ if (frag) {
343
+ list.push(frag);
344
+ }
345
+ });
346
+ if (list.length === 0) {
347
+ Log.error('Haori', `Element not found: ${selector} (${attrName})`);
348
+ }
349
+ }
350
+ else {
351
+ // 値が省略されている場合は自要素を対象
352
+ list.push(fragment);
353
+ }
354
+ if (list.length > 0) {
355
+ switch (attrKey) {
356
+ case 'reset':
357
+ options.resetFragments = list;
358
+ break;
359
+ case 'refetch':
360
+ options.refetchFragments = list;
361
+ break;
362
+ case 'click':
363
+ options.clickFragments = list;
364
+ break;
365
+ case 'open':
366
+ options.openFragments = list;
367
+ break;
368
+ case 'close':
369
+ options.closeFragments = list;
370
+ break;
371
+ }
372
+ }
373
+ });
374
+ }
375
+ // 非イベントの data / form(data-fetch-data / data-fetch-form)も取り込む
376
+ if (!event) {
377
+ if (fragment.hasAttribute(Procedure.attrName(null, 'data', true))) {
378
+ const raw = fragment.getRawAttribute(Procedure.attrName(null, 'data', true));
379
+ options.data = Core.parseDataBind(raw);
380
+ }
381
+ if (fragment.hasAttribute(Procedure.attrName(null, 'form', true))) {
382
+ const formSelector = fragment.getRawAttribute(Procedure.attrName(null, 'form', true));
383
+ if (formSelector) {
384
+ const formElement = document.body.querySelector(formSelector);
385
+ if (formElement !== null) {
386
+ options.formFragment = Form.getFormFragment(Fragment.get(formElement));
387
+ }
388
+ else {
389
+ Log.error('Haori', `Form element not found: ${formSelector} (` +
390
+ `${Procedure.attrName(null, 'fetch-form', true)})`);
391
+ }
392
+ }
393
+ else {
394
+ // 属性はあるが値が省略された場合は自要素もしくは先祖の form を対象
395
+ options.formFragment = Form.getFormFragment(fragment);
396
+ }
397
+ }
398
+ }
399
+ // fetch が指定されているのにバインド先が無い場合、デフォルトで自要素にバインド
400
+ if (hasFetchAttr &&
401
+ (!options.bindFragments || options.bindFragments.length === 0)) {
402
+ options.bindFragments = [fragment];
403
+ }
404
+ return options;
405
+ }
406
+ /**
407
+ * ElementFragment の構造的タイプガード。
408
+ *
409
+ * @param value チェックする値
410
+ * @returns ElementFragment である場合は true、それ以外は false
411
+ */
412
+ static isElementFragment(value) {
413
+ if (typeof value !== 'object' || value === null) {
414
+ return false;
415
+ }
416
+ const obj = value;
417
+ return (typeof obj.getTarget === 'function' &&
418
+ typeof obj.getChildElementFragments === 'function');
419
+ }
420
+ /**
421
+ * コンストラクタ。
422
+ *
423
+ * @param arg1 オプションもしくはフラグメント
424
+ * @param arg2 イベント名
425
+ */
426
+ constructor(arg1, arg2 = null) {
427
+ if (Procedure.isElementFragment(arg1)) {
428
+ this.options = Procedure.buildOptions(arg1, arg2);
429
+ }
430
+ else {
431
+ this.options = arg1;
432
+ }
433
+ }
434
+ /**
435
+ * 一連の処理を実行します。オプションが空の場合は即座にresolveされます。
436
+ *
437
+ * @returns 実行結果のPromise
438
+ */
439
+ run() {
440
+ if (Object.keys(this.options).length === 0) {
441
+ return Promise.resolve();
442
+ }
443
+ if (this.options.formFragment &&
444
+ this.validate(this.options.formFragment) === false) {
445
+ return Promise.resolve();
446
+ }
447
+ return this.confirm().then(confirmed => {
448
+ if (!confirmed) {
449
+ return Promise.resolve();
450
+ }
451
+ let fetchUrl = this.options.fetchUrl;
452
+ let fetchOptions = this.options.fetchOptions;
453
+ if (this.options.beforeCallback) {
454
+ const result = this.options.beforeCallback(fetchUrl || null, fetchOptions || null);
455
+ if (result !== undefined && result !== null) {
456
+ if (result === false || (typeof result === 'object' && result.stop)) {
457
+ return Promise.resolve();
458
+ }
459
+ if (typeof result === 'object') {
460
+ fetchUrl = ('fetchUrl' in result ? result.fetchUrl : fetchUrl);
461
+ fetchOptions = ('fetchOptions' in result ? result.fetchOptions : fetchOptions);
462
+ }
463
+ }
464
+ }
465
+ // フォーム値と data を統合してペイロードを作成
466
+ const payload = {};
467
+ if (this.options.formFragment) {
468
+ const formValues = Form.getValues(this.options.formFragment);
469
+ Object.assign(payload, formValues);
470
+ }
471
+ if (this.options.data && typeof this.options.data === 'object') {
472
+ Object.assign(payload, this.options.data);
473
+ }
474
+ const hasPayload = Object.keys(payload).length > 0;
475
+ if (fetchUrl) {
476
+ const finalOptions = { ...(fetchOptions || {}) };
477
+ const headers = new Headers(finalOptions.headers || undefined);
478
+ const method = (finalOptions.method || 'GET').toUpperCase();
479
+ if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') {
480
+ if (hasPayload) {
481
+ const url = new URL(fetchUrl, window.location.href);
482
+ const params = new URLSearchParams(url.search);
483
+ for (const [k, v] of Object.entries(payload)) {
484
+ if (v === undefined) {
485
+ continue;
486
+ }
487
+ if (v === null) {
488
+ params.append(k, '');
489
+ }
490
+ else if (Array.isArray(v)) {
491
+ v.forEach(item => {
492
+ params.append(k, String(item));
493
+ });
494
+ }
495
+ else if (typeof v === 'object' || typeof v === 'function') {
496
+ params.append(k, JSON.stringify(v));
497
+ }
498
+ else {
499
+ params.append(k, String(v));
500
+ }
501
+ }
502
+ url.search = params.toString();
503
+ fetchUrl = url.toString();
504
+ }
505
+ }
506
+ else if (hasPayload) {
507
+ const contentType = headers.get('Content-Type') || '';
508
+ if (/multipart\/form-data/i.test(contentType)) {
509
+ headers.delete('Content-Type');
510
+ const formData = new FormData();
511
+ for (const [k, v] of Object.entries(payload)) {
512
+ if (v === undefined || v === null) {
513
+ formData.append(k, '');
514
+ }
515
+ else if (v instanceof Blob) {
516
+ formData.append(k, v);
517
+ }
518
+ else if (Array.isArray(v)) {
519
+ v.forEach(item => formData.append(k, String(item)));
520
+ }
521
+ else if (typeof v === 'object') {
522
+ formData.append(k, JSON.stringify(v));
523
+ }
524
+ else {
525
+ formData.append(k, String(v));
526
+ }
527
+ }
528
+ finalOptions.body = formData;
529
+ }
530
+ else if (/application\/x-www-form-urlencoded/i.test(contentType)) {
531
+ const params = new URLSearchParams();
532
+ for (const [k, v] of Object.entries(payload)) {
533
+ if (v === undefined) {
534
+ continue;
535
+ }
536
+ if (v === null) {
537
+ params.append(k, '');
538
+ }
539
+ else if (Array.isArray(v)) {
540
+ v.forEach(item => params.append(k, String(item)));
541
+ }
542
+ else if (typeof v === 'object') {
543
+ params.append(k, JSON.stringify(v));
544
+ }
545
+ else {
546
+ params.append(k, String(v));
547
+ }
548
+ }
549
+ finalOptions.body = params;
550
+ }
551
+ else {
552
+ headers.set('Content-Type', 'application/json');
553
+ finalOptions.body = JSON.stringify(payload);
554
+ }
555
+ }
556
+ finalOptions.headers = headers;
557
+ // fetchstartイベントを発火
558
+ if (this.options.targetFragment && fetchUrl) {
559
+ const startedAt = performance.now();
560
+ HaoriEvent.fetchStart(this.options.targetFragment.getTarget(), fetchUrl, finalOptions, hasPayload ? payload : undefined);
561
+ return fetch(fetchUrl, finalOptions)
562
+ .then(response => {
563
+ return this.handleFetchResult(response, fetchUrl || undefined, startedAt);
564
+ })
565
+ .catch(error => {
566
+ if (fetchUrl) {
567
+ HaoriEvent.fetchError(this.options.targetFragment.getTarget(), fetchUrl, error);
568
+ }
569
+ throw error;
570
+ });
571
+ }
572
+ else if (fetchUrl) {
573
+ return fetch(fetchUrl, finalOptions).then(response => {
574
+ return this.handleFetchResult(response, fetchUrl || undefined);
575
+ });
576
+ }
577
+ else {
578
+ return Promise.resolve();
579
+ }
580
+ }
581
+ else {
582
+ // fetchUrlが無い場合(changeイベント等)、bindFragmentsが無ければformFragmentにバインド
583
+ if ((!this.options.bindFragments ||
584
+ this.options.bindFragments.length === 0) &&
585
+ this.options.formFragment &&
586
+ hasPayload) {
587
+ // 双方向バインディング: フォーム値を自動的にバインディングデータに反映
588
+ const formFragment = this.options.formFragment;
589
+ const formElement = formFragment.getTarget();
590
+ formElement.setAttribute(`${Env.prefix}bind`, JSON.stringify(payload));
591
+ const bindingData = formFragment.getBindingData();
592
+ Object.assign(bindingData, payload);
593
+ return Core.setBindingData(formElement, bindingData);
594
+ }
595
+ const merged = hasPayload ? payload : {};
596
+ const response = new Response(JSON.stringify(merged), {
597
+ headers: { 'Content-Type': 'application/json' },
598
+ });
599
+ return this.handleFetchResult(response);
600
+ }
601
+ });
602
+ }
603
+ /**
604
+ * フェッチ後の処理を実行します。
605
+ */
606
+ handleFetchResult(response, url, startedAt) {
607
+ // エラー応答時は以後の処理を停止し、メッセージを伝播
608
+ if (!response.ok) {
609
+ if (this.options.targetFragment && url) {
610
+ HaoriEvent.fetchError(this.options.targetFragment.getTarget(), url, new Error(`${response.status} ${response.statusText}`), response.status, startedAt);
611
+ }
612
+ return this.handleFetchError(response);
613
+ }
614
+ // fetchendイベントを発火
615
+ if (this.options.targetFragment && url && startedAt) {
616
+ HaoriEvent.fetchEnd(this.options.targetFragment.getTarget(), url, response.status, startedAt);
617
+ }
618
+ if (this.options.afterCallback) {
619
+ const result = this.options.afterCallback(response);
620
+ if (result !== undefined && result !== null) {
621
+ if (result === false || (typeof result === 'object' && result.stop)) {
622
+ return Promise.resolve();
623
+ }
624
+ if (typeof result === 'object' && 'response' in result) {
625
+ response = ('response' in result ? result.response : response);
626
+ }
627
+ }
628
+ }
629
+ const promises = [];
630
+ promises.push(this.bindResult(response));
631
+ promises.push(this.adjust());
632
+ promises.push(this.addRow());
633
+ promises.push(this.removeRow());
634
+ promises.push(this.movePrevRow());
635
+ promises.push(this.moveNextRow());
636
+ if (this.options.resetFragments && this.options.resetFragments.length > 0) {
637
+ this.options.resetFragments.forEach(fragment => {
638
+ promises.push(Form.reset(fragment));
639
+ });
640
+ }
641
+ if (this.options.refetchFragments &&
642
+ this.options.refetchFragments.length > 0) {
643
+ this.options.refetchFragments.forEach(fragment => {
644
+ promises.push(new Procedure(fragment, null).run());
645
+ });
646
+ }
647
+ if (this.options.clickFragments && this.options.clickFragments.length > 0) {
648
+ this.options.clickFragments.forEach(fragment => {
649
+ const target = fragment.getTarget();
650
+ if (typeof target.click === 'function') {
651
+ target.click();
652
+ }
653
+ else {
654
+ target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
655
+ }
656
+ });
657
+ }
658
+ if (this.options.openFragments && this.options.openFragments.length > 0) {
659
+ this.options.openFragments.forEach(fragment => {
660
+ const target = fragment.getTarget();
661
+ if (target instanceof HTMLDialogElement) {
662
+ promises.push(Haori.openDialog(target));
663
+ }
664
+ else {
665
+ Log.error('Haori', 'Element is not a dialog: ', target);
666
+ }
667
+ });
668
+ }
669
+ if (this.options.closeFragments && this.options.closeFragments.length > 0) {
670
+ this.options.closeFragments.forEach(fragment => {
671
+ const target = fragment.getTarget();
672
+ if (target instanceof HTMLDialogElement) {
673
+ promises.push(Haori.closeDialog(target));
674
+ }
675
+ else {
676
+ Log.error('Haori', 'Element is not a dialog: ', target);
677
+ }
678
+ });
679
+ }
680
+ // 仕様順序: 先に各種操作(bind/adjust/row/reset/refetch/click/open/close)を完了
681
+ return Promise.all(promises)
682
+ .then(() => {
683
+ // その後にダイアログ/トーストを表示
684
+ if (this.options.dialogMessage) {
685
+ return Haori.dialog(this.options.dialogMessage);
686
+ }
687
+ return Promise.resolve();
688
+ })
689
+ .then(() => {
690
+ if (this.options.toastMessage) {
691
+ return Haori.toast(this.options.toastMessage, 'info');
692
+ }
693
+ return Promise.resolve();
694
+ })
695
+ .then(() => {
696
+ if (this.options.redirectUrl) {
697
+ window.location.href = this.options.redirectUrl;
698
+ }
699
+ return Promise.resolve();
700
+ });
701
+ }
702
+ /**
703
+ * フェッチエラー応答のメッセージを適切な要素へ伝播します。
704
+ */
705
+ async handleFetchError(response) {
706
+ // ベースとなるフォーム/フラグメントを決定
707
+ let baseFragment = null;
708
+ if (this.options.formFragment) {
709
+ baseFragment = this.options.formFragment;
710
+ }
711
+ else if (this.options.targetFragment) {
712
+ baseFragment =
713
+ Form.getFormFragment(this.options.targetFragment) ||
714
+ this.options.targetFragment;
715
+ }
716
+ const addGeneralMessage = async (message) => {
717
+ const targetEl = baseFragment ? baseFragment.getTarget() : document.body;
718
+ await Haori.addErrorMessage(targetEl, message);
719
+ };
720
+ // コンテンツタイプに応じて解析
721
+ const contentType = response.headers.get('Content-Type') || '';
722
+ if (contentType.includes('application/json')) {
723
+ try {
724
+ const data = await response.json();
725
+ // 代表的な形式に対応
726
+ const entries = [];
727
+ if (data && typeof data === 'object') {
728
+ if (typeof data.message === 'string') {
729
+ entries.push({ message: data.message });
730
+ }
731
+ if (Array.isArray(data.messages)) {
732
+ for (const m of data.messages) {
733
+ if (typeof m === 'string') {
734
+ entries.push({ message: m });
735
+ }
736
+ }
737
+ }
738
+ if (data.errors && typeof data.errors === 'object') {
739
+ for (const [k, v] of Object.entries(data.errors)) {
740
+ if (Array.isArray(v)) {
741
+ entries.push({ key: k, message: v.join('\n') });
742
+ }
743
+ else if (typeof v === 'string') {
744
+ entries.push({ key: k, message: v });
745
+ }
746
+ else if (v != null) {
747
+ entries.push({ key: k, message: String(v) });
748
+ }
749
+ }
750
+ }
751
+ // キー: 値(文字列/配列)形式にフォールバック
752
+ if (entries.length === 0) {
753
+ for (const [k, v] of Object.entries(data)) {
754
+ if (k === 'message' || k === 'messages' || k === 'errors') {
755
+ continue;
756
+ }
757
+ if (Array.isArray(v)) {
758
+ entries.push({ key: k, message: v.join('\n') });
759
+ }
760
+ else if (typeof v === 'string') {
761
+ entries.push({ key: k, message: v });
762
+ }
763
+ }
764
+ }
765
+ }
766
+ if (entries.length === 0) {
767
+ // 汎用メッセージ
768
+ await addGeneralMessage(`${response.status} ${response.statusText}`);
769
+ return;
770
+ }
771
+ // メッセージを反映
772
+ for (const e of entries) {
773
+ if (e.key && baseFragment) {
774
+ await Form.addErrorMessage(baseFragment, e.key, e.message);
775
+ }
776
+ else {
777
+ await addGeneralMessage(e.message);
778
+ }
779
+ }
780
+ return;
781
+ }
782
+ catch {
783
+ // JSON 解析失敗時はテキストにフォールバック
784
+ }
785
+ }
786
+ // テキストとして処理
787
+ try {
788
+ const text = await response.text();
789
+ if (text && text.trim().length > 0) {
790
+ await addGeneralMessage(text.trim());
791
+ }
792
+ else {
793
+ await addGeneralMessage(`${response.status} ${response.statusText}`);
794
+ }
795
+ }
796
+ catch {
797
+ await addGeneralMessage(`${response.status} ${response.statusText}`);
798
+ }
799
+ }
800
+ /**
801
+ * 対象のフラグメント以下の入力要素に対してバリデーションを実行します。
802
+ * バリデーションエラーがある場合は、最初のエラー要素にフォーカスを移動します。
803
+ *
804
+ * @param fragment 対象のフラグメント
805
+ * @returns バリデーション結果(true: 成功, false: 失敗)
806
+ */
807
+ validate(fragment) {
808
+ if (this.options.valid !== true) {
809
+ return true;
810
+ }
811
+ const target = fragment.getTarget();
812
+ let result = this.validateOne(fragment);
813
+ if (!result) {
814
+ target.focus();
815
+ }
816
+ // エラー要素のフォーカスを最上部に移動するため、子要素は逆順で処理
817
+ fragment
818
+ .getChildElementFragments()
819
+ .reverse()
820
+ .forEach(child => {
821
+ result && (result = this.validate(child));
822
+ });
823
+ return result;
824
+ }
825
+ /**
826
+ * 対象のフラグメントに対してバリデーションを実行します。
827
+ *
828
+ * @param fragment 対象のフラグメント
829
+ * @returns バリデーション結果(true: 成功, false: 失敗)
830
+ */
831
+ validateOne(fragment) {
832
+ const target = fragment.getTarget();
833
+ if (target instanceof HTMLInputElement) {
834
+ return target.reportValidity();
835
+ }
836
+ if (target instanceof HTMLSelectElement) {
837
+ return target.reportValidity();
838
+ }
839
+ if (target instanceof HTMLTextAreaElement) {
840
+ return target.reportValidity();
841
+ }
842
+ return true;
843
+ }
844
+ /**
845
+ * 確認メッセージを表示し、ユーザーの確認を求めます。
846
+ * メッセージが設定されていない場合は、即座に成功とみなします。
847
+ *
848
+ * @returns ユーザーの確認結果を含むPromise(true: 確認, false: キャンセル)
849
+ */
850
+ confirm() {
851
+ const message = this.options.confirmMessage;
852
+ if (message === null || message === undefined) {
853
+ return Promise.resolve(true);
854
+ }
855
+ return Haori.confirm(message);
856
+ }
857
+ /**
858
+ * 結果データを対象のフラグメントにバインドします。
859
+ *
860
+ * @param response フェッチのレスポンスオブジェクト
861
+ */
862
+ bindResult(response) {
863
+ if (!this.options.bindFragments ||
864
+ this.options.bindFragments.length === 0) {
865
+ return Promise.resolve();
866
+ }
867
+ const promise = response.headers
868
+ .get('Content-Type')
869
+ ?.includes('application/json')
870
+ ? response.json()
871
+ : response.text();
872
+ return promise.then(data => {
873
+ if (this.options.bindParams) {
874
+ const newData = {};
875
+ this.options.bindParams.forEach(param => {
876
+ if (data && typeof data === 'object' && param in data) {
877
+ newData[param] = data[param];
878
+ }
879
+ });
880
+ data = newData;
881
+ }
882
+ const promises = [];
883
+ if (this.options.bindArg) {
884
+ this.options.bindFragments.forEach(fragment => {
885
+ const bindingData = fragment.getBindingData();
886
+ bindingData[this.options.bindArg] = data;
887
+ promises.push(Core.setBindingData(fragment.getTarget(), bindingData));
888
+ });
889
+ }
890
+ else if (typeof data === 'string') {
891
+ Log.error('Haori', 'string data cannot be bound without a bindArg.');
892
+ return Promise.reject(new Error('string data cannot be bound without a bindArg.'));
893
+ }
894
+ else {
895
+ this.options.bindFragments.forEach(fragment => {
896
+ promises.push(Core.setBindingData(fragment.getTarget(), data));
897
+ });
898
+ }
899
+ return Promise.all(promises).then(() => undefined);
900
+ });
901
+ }
902
+ /**
903
+ * 値の増減を行います。
904
+ */
905
+ adjust() {
906
+ if (!this.options.adjustFragments ||
907
+ this.options.adjustFragments.length === 0) {
908
+ return Promise.resolve();
909
+ }
910
+ const adjustValue = this.options.adjustValue ?? 0;
911
+ const promises = [];
912
+ for (const fragment of this.options.adjustFragments) {
913
+ let valueString = fragment.getValue();
914
+ if (valueString === null ||
915
+ valueString === undefined ||
916
+ valueString === '') {
917
+ valueString = '0';
918
+ }
919
+ let value = Number(valueString);
920
+ if (isNaN(value)) {
921
+ value = 0;
922
+ }
923
+ value += adjustValue;
924
+ promises.push(fragment.setValue(String(value)));
925
+ }
926
+ return Promise.all(promises).then(() => undefined);
927
+ }
928
+ /**
929
+ * 行フラグメントを取得します。
930
+ *
931
+ * @returns 行フラグメントまたはnull
932
+ */
933
+ getRowFragment() {
934
+ if (!this.options.targetFragment) {
935
+ Log.error('Haori', 'Target fragment is not specified for row operation.');
936
+ return null;
937
+ }
938
+ const rowFragment = this.options.targetFragment.closestByAttribute(`${Env.prefix}row`);
939
+ if (!rowFragment) {
940
+ Log.error('Haori', 'Row fragment not found.');
941
+ return null;
942
+ }
943
+ return rowFragment;
944
+ }
945
+ /**
946
+ * 行を追加します。
947
+ *
948
+ * @returns 処理結果のPromise
949
+ */
950
+ addRow() {
951
+ if (this.options.rowAdd !== true) {
952
+ return Promise.resolve();
953
+ }
954
+ const rowFragment = this.getRowFragment();
955
+ if (!rowFragment) {
956
+ return Promise.reject(new Error('Row fragment not found.'));
957
+ }
958
+ const promises = [];
959
+ const newFragment = rowFragment.clone();
960
+ promises.push(rowFragment.getParent().insertAfter(newFragment, rowFragment));
961
+ promises.push(Core.evaluateAll(newFragment));
962
+ // 追加された行のフォーム要素をリセット
963
+ promises.push(Form.reset(newFragment));
964
+ return Promise.all(promises).then(() => undefined);
965
+ }
966
+ /**
967
+ * 行を削除します。
968
+ *
969
+ * @returns 処理結果のPromise
970
+ */
971
+ removeRow() {
972
+ if (this.options.rowRemove !== true) {
973
+ return Promise.resolve();
974
+ }
975
+ const rowFragment = this.getRowFragment();
976
+ if (!rowFragment) {
977
+ return Promise.reject(new Error('Row fragment not found.'));
978
+ }
979
+ // 1行だった場合は削除しない
980
+ const parent = rowFragment.getParent();
981
+ if (parent) {
982
+ const siblings = parent.getChildElementFragments().filter(child => {
983
+ // data-each-before と data-each-after を除外
984
+ return (!child.hasAttribute(`${Env.prefix}each-before`) &&
985
+ !child.hasAttribute(`${Env.prefix}each-after`));
986
+ });
987
+ if (siblings.length <= 1) {
988
+ return Promise.resolve();
989
+ }
990
+ }
991
+ return rowFragment.remove();
992
+ }
993
+ /**
994
+ * 前の行へ移動します。
995
+ *
996
+ * @returns 処理結果のPromise
997
+ */
998
+ movePrevRow() {
999
+ if (this.options.rowMovePrev !== true) {
1000
+ return Promise.resolve();
1001
+ }
1002
+ const rowFragment = this.getRowFragment();
1003
+ if (!rowFragment) {
1004
+ return Promise.reject(new Error('Row fragment not found.'));
1005
+ }
1006
+ const prevFragment = rowFragment.getPrevious();
1007
+ if (!prevFragment) {
1008
+ return Promise.resolve();
1009
+ }
1010
+ const parent = rowFragment.getParent();
1011
+ if (!parent) {
1012
+ return Promise.resolve();
1013
+ }
1014
+ return parent.insertBefore(rowFragment, prevFragment);
1015
+ }
1016
+ /**
1017
+ * 次の行へ移動します。
1018
+ *
1019
+ * @returns 処理結果のPromise
1020
+ */
1021
+ moveNextRow() {
1022
+ if (this.options.rowMoveNext !== true) {
1023
+ return Promise.resolve();
1024
+ }
1025
+ const rowFragment = this.getRowFragment();
1026
+ if (!rowFragment) {
1027
+ return Promise.reject(new Error('Row fragment not found.'));
1028
+ }
1029
+ const nextFragment = rowFragment.getNext();
1030
+ if (!nextFragment) {
1031
+ return Promise.resolve();
1032
+ }
1033
+ const parent = rowFragment.getParent();
1034
+ if (!parent) {
1035
+ return Promise.resolve();
1036
+ }
1037
+ return parent.insertAfter(rowFragment, nextFragment);
1038
+ }
1039
+ }
1040
+ //# sourceMappingURL=procedure.js.map