@nocios/crudify-components 2.0.32 → 2.0.38

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 (178) hide show
  1. package/coverage/base.css +224 -0
  2. package/coverage/block-navigation.js +87 -0
  3. package/coverage/components/CrudiaAutoGenerate.tsx.html +583 -0
  4. package/coverage/components/CrudiaFileField/CrudiaFileField.tsx.html +1276 -0
  5. package/coverage/components/CrudiaFileField/components/DeleteConfirmationDialog.tsx.html +355 -0
  6. package/coverage/components/CrudiaFileField/components/DropZone.tsx.html +433 -0
  7. package/coverage/components/CrudiaFileField/components/FileItemRow.tsx.html +751 -0
  8. package/coverage/components/CrudiaFileField/components/FileThumbnail.tsx.html +1195 -0
  9. package/coverage/components/CrudiaFileField/components/index.html +176 -0
  10. package/coverage/components/CrudiaFileField/components/index.ts.html +115 -0
  11. package/coverage/components/CrudiaFileField/hooks/index.html +146 -0
  12. package/coverage/components/CrudiaFileField/hooks/index.ts.html +112 -0
  13. package/coverage/components/CrudiaFileField/hooks/useDeleteConfirmation.ts.html +421 -0
  14. package/coverage/components/CrudiaFileField/hooks/useDragDrop.ts.html +403 -0
  15. package/coverage/components/CrudiaFileField/index.html +131 -0
  16. package/coverage/components/CrudiaFileField/index.ts.html +112 -0
  17. package/coverage/components/CrudiaFileField/utils/formatters.ts.html +163 -0
  18. package/coverage/components/CrudiaFileField/utils/icons.tsx.html +253 -0
  19. package/coverage/components/CrudiaFileField/utils/index.html +131 -0
  20. package/coverage/components/CrudiaMarkdownField.tsx.html +619 -0
  21. package/coverage/components/CrudifyLogin/Forms/CheckCodeForm.tsx.html +586 -0
  22. package/coverage/components/CrudifyLogin/Forms/ForgotPasswordForm.tsx.html +694 -0
  23. package/coverage/components/CrudifyLogin/Forms/LoginForm.tsx.html +835 -0
  24. package/coverage/components/CrudifyLogin/Forms/ResetPasswordForm.tsx.html +1180 -0
  25. package/coverage/components/CrudifyLogin/Forms/components/CodeInput.tsx.html +283 -0
  26. package/coverage/components/CrudifyLogin/Forms/components/FormAlert.tsx.html +202 -0
  27. package/coverage/components/CrudifyLogin/Forms/components/PasswordInput.tsx.html +340 -0
  28. package/coverage/components/CrudifyLogin/Forms/components/index.html +161 -0
  29. package/coverage/components/CrudifyLogin/Forms/components/index.ts.html +106 -0
  30. package/coverage/components/CrudifyLogin/Forms/index.html +161 -0
  31. package/coverage/components/CrudifyLogin/Forms/utils/errorTranslation.ts.html +268 -0
  32. package/coverage/components/CrudifyLogin/Forms/utils/index.html +161 -0
  33. package/coverage/components/CrudifyLogin/Forms/utils/index.ts.html +106 -0
  34. package/coverage/components/CrudifyLogin/Forms/utils/paramUtils.ts.html +478 -0
  35. package/coverage/components/CrudifyLogin/Forms/utils/validation.ts.html +289 -0
  36. package/coverage/components/CrudifyLogin/components/CrudifyInitializer.tsx.html +262 -0
  37. package/coverage/components/CrudifyLogin/components/index.html +116 -0
  38. package/coverage/components/CrudifyLogin/context/CrudifyProvider.tsx.html +382 -0
  39. package/coverage/components/CrudifyLogin/context/I18nProvider.tsx.html +397 -0
  40. package/coverage/components/CrudifyLogin/context/LoginStateProvider.tsx.html +1249 -0
  41. package/coverage/components/CrudifyLogin/context/index.html +146 -0
  42. package/coverage/components/CrudifyLogin/hooks/index.html +116 -0
  43. package/coverage/components/CrudifyLogin/hooks/useTranslationsFromUrl.ts.html +292 -0
  44. package/coverage/components/CrudifyLogin/index.html +116 -0
  45. package/coverage/components/CrudifyLogin/index.tsx.html +475 -0
  46. package/coverage/components/GlobalNotificationProvider.tsx.html +781 -0
  47. package/coverage/components/LoginComponent.tsx.html +727 -0
  48. package/coverage/components/PasswordRequirements/index.html +116 -0
  49. package/coverage/components/PasswordRequirements/index.tsx.html +226 -0
  50. package/coverage/components/PublicPolicies/FieldSelector/FieldSelector.tsx.html +982 -0
  51. package/coverage/components/PublicPolicies/FieldSelector/index.html +131 -0
  52. package/coverage/components/PublicPolicies/FieldSelector/index.ts.html +85 -0
  53. package/coverage/components/PublicPolicies/Policies.tsx.html +610 -0
  54. package/coverage/components/PublicPolicies/PolicyItem/PolicyItem.tsx.html +856 -0
  55. package/coverage/components/PublicPolicies/PolicyItem/index.html +131 -0
  56. package/coverage/components/PublicPolicies/PolicyItem/index.ts.html +85 -0
  57. package/coverage/components/PublicPolicies/constants.ts.html +127 -0
  58. package/coverage/components/PublicPolicies/index.html +131 -0
  59. package/coverage/components/SessionTimeIndicator/index.html +116 -0
  60. package/coverage/components/SessionTimeIndicator/index.tsx.html +505 -0
  61. package/coverage/components/UserProfile/UserProfileDisplay.tsx.html +826 -0
  62. package/coverage/components/UserProfile/index.html +131 -0
  63. package/coverage/components/UserProfile/index.ts.html +85 -0
  64. package/coverage/components/index.html +176 -0
  65. package/coverage/components/index.ts.html +160 -0
  66. package/coverage/core/CrossTabSyncManager.ts.html +814 -0
  67. package/coverage/core/CrudifyInitializationManager.ts.html +1132 -0
  68. package/coverage/core/SessionManager.ts.html +2764 -0
  69. package/coverage/core/index.html +146 -0
  70. package/coverage/coverage-final.json +87 -0
  71. package/coverage/favicon.png +0 -0
  72. package/coverage/hooks/index.html +131 -0
  73. package/coverage/hooks/useAutoGenerate.ts.html +562 -0
  74. package/coverage/hooks/useFileUpload/index.html +131 -0
  75. package/coverage/hooks/useFileUpload/index.ts.html +112 -0
  76. package/coverage/hooks/useFileUpload/services/index.html +116 -0
  77. package/coverage/hooks/useFileUpload/services/uploadService.ts.html +610 -0
  78. package/coverage/hooks/useFileUpload/useFileUpload.ts.html +1870 -0
  79. package/coverage/hooks/useFileUpload/utils/fileUtils.ts.html +271 -0
  80. package/coverage/hooks/useFileUpload/utils/index.html +146 -0
  81. package/coverage/hooks/useFileUpload/utils/mimeTypes.ts.html +235 -0
  82. package/coverage/hooks/useFileUpload/utils/validation.ts.html +379 -0
  83. package/coverage/hooks/useSession/constants.ts.html +217 -0
  84. package/coverage/hooks/useSession/hooks/index.html +176 -0
  85. package/coverage/hooks/useSession/hooks/useAuthEventSubscriber.ts.html +331 -0
  86. package/coverage/hooks/useSession/hooks/useCrossTabSync.ts.html +433 -0
  87. package/coverage/hooks/useSession/hooks/useSessionActions.ts.html +664 -0
  88. package/coverage/hooks/useSession/hooks/useSessionState.ts.html +295 -0
  89. package/coverage/hooks/useSession/hooks/useTokenRefreshScheduler.ts.html +490 -0
  90. package/coverage/hooks/useSession/index.html +161 -0
  91. package/coverage/hooks/useSession/index.ts.html +127 -0
  92. package/coverage/hooks/useSession/types.ts.html +427 -0
  93. package/coverage/hooks/useSession/useSession.ts.html +526 -0
  94. package/coverage/hooks/useSession/utils/index.html +131 -0
  95. package/coverage/hooks/useSession/utils/initializeSession.ts.html +424 -0
  96. package/coverage/hooks/useSession/utils/tokenUtils.ts.html +280 -0
  97. package/coverage/hooks/useUserProfile.ts.html +658 -0
  98. package/coverage/index.html +566 -0
  99. package/coverage/prettify.css +1 -0
  100. package/coverage/prettify.js +2 -0
  101. package/coverage/providers/SessionProvider.tsx.html +1150 -0
  102. package/coverage/providers/TranslationsProvider.tsx.html +1450 -0
  103. package/coverage/providers/index.html +131 -0
  104. package/coverage/services/credentialsEventBus.ts.html +310 -0
  105. package/coverage/services/index.html +131 -0
  106. package/coverage/services/translationService.ts.html +1318 -0
  107. package/coverage/sort-arrow-sprite.png +0 -0
  108. package/coverage/sorter.js +210 -0
  109. package/coverage/translations/critical.ts.html +1195 -0
  110. package/coverage/translations/index.html +116 -0
  111. package/coverage/types/index.html +116 -0
  112. package/coverage/types/password.ts.html +178 -0
  113. package/coverage/utils/authEventBus.ts.html +454 -0
  114. package/coverage/utils/configResolver.ts.html +460 -0
  115. package/coverage/utils/cookieSync.ts.html +580 -0
  116. package/coverage/utils/errorHandler.ts.html +1264 -0
  117. package/coverage/utils/errorTranslation.ts.html +862 -0
  118. package/coverage/utils/index.html +296 -0
  119. package/coverage/utils/jwtUtils.ts.html +301 -0
  120. package/coverage/utils/logger.ts.html +901 -0
  121. package/coverage/utils/navigationTracker.ts.html +565 -0
  122. package/coverage/utils/passwordValidation.ts.html +259 -0
  123. package/coverage/utils/redirectSecurity.ts.html +715 -0
  124. package/coverage/utils/tenantConfig.ts.html +700 -0
  125. package/coverage/utils/tokenStorage.ts.html +1768 -0
  126. package/coverage/utils/webCrypto.ts.html +472 -0
  127. package/dist/{CrudiaMarkdownField-C54-A_J3.d.mts → CrudiaMarkdownField-BvJn2GL8.d.mts} +17 -7
  128. package/dist/{CrudiaMarkdownField-C8HQh7s5.d.ts → CrudiaMarkdownField-CggOpcBM.d.ts} +17 -7
  129. package/dist/chunk-44VU4TSP.mjs +1 -0
  130. package/dist/chunk-4LMFQECS.js +1 -0
  131. package/dist/chunk-77UKXG5L.mjs +1 -0
  132. package/dist/{chunk-4VN5YRYZ.js → chunk-BXFEQ6KP.js} +1 -1
  133. package/dist/{chunk-6ONAT4QU.js → chunk-H6XXWDUT.js} +1 -1
  134. package/dist/chunk-NIRLP2TC.js +1 -0
  135. package/dist/chunk-O4FUHIQG.mjs +1 -0
  136. package/dist/chunk-SYHNHKFA.mjs +1 -0
  137. package/dist/chunk-W4ZYKPOK.js +1 -0
  138. package/dist/{chunk-RJBX4MWF.mjs → chunk-WMLIOPUC.mjs} +1 -1
  139. package/dist/components.d.mts +1 -1
  140. package/dist/components.d.ts +1 -1
  141. package/dist/components.js +1 -1
  142. package/dist/components.mjs +1 -1
  143. package/dist/errorTranslation-D-Y7uNN_.d.mts +141 -0
  144. package/dist/errorTranslation-DDlAXpMl.d.ts +141 -0
  145. package/dist/hooks.d.mts +2 -2
  146. package/dist/hooks.d.ts +2 -2
  147. package/dist/hooks.js +1 -1
  148. package/dist/hooks.mjs +1 -1
  149. package/dist/{index-DY90WVQQ.d.mts → index-U--xRr8A.d.mts} +225 -191
  150. package/dist/{index-d1vE803G.d.ts → index-dXVRVcEB.d.ts} +225 -191
  151. package/dist/index.d.mts +179 -6
  152. package/dist/index.d.ts +179 -6
  153. package/dist/index.js +2 -2
  154. package/dist/index.mjs +1 -1
  155. package/dist/{errorTranslation-By5Av0tL.d.ts → tenantConfig-DqJqQkoR.d.mts} +116 -139
  156. package/dist/{errorTranslation-DeeDj7Vt.d.mts → tenantConfig-DqJqQkoR.d.ts} +116 -139
  157. package/dist/utils.d.mts +2 -2
  158. package/dist/utils.d.ts +2 -2
  159. package/dist/utils.js +1 -1
  160. package/dist/utils.mjs +1 -1
  161. package/package.json +1 -1
  162. package/coverage/.tmp/coverage-102.json +0 -1
  163. package/coverage/.tmp/coverage-103.json +0 -1
  164. package/coverage/.tmp/coverage-104.json +0 -1
  165. package/coverage/.tmp/coverage-105.json +0 -1
  166. package/coverage/.tmp/coverage-106.json +0 -1
  167. package/coverage/.tmp/coverage-107.json +0 -1
  168. package/dist/api-B4uXiHF0.d.mts +0 -118
  169. package/dist/api-B4uXiHF0.d.ts +0 -118
  170. package/dist/chunk-4ILUXVPW.mjs +0 -1
  171. package/dist/chunk-5XBTRBE5.js +0 -1
  172. package/dist/chunk-CR5KJUST.js +0 -1
  173. package/dist/chunk-H5M2Q6PB.mjs +0 -1
  174. package/dist/chunk-JAPL7EZJ.mjs +0 -1
  175. package/dist/chunk-L7GKP6XY.mjs +0 -1
  176. package/dist/chunk-NSV6ECYO.js +0 -1
  177. package/dist/chunk-PTUSZGL4.mjs +0 -1
  178. package/dist/chunk-T6R65ROU.js +0 -1
@@ -0,0 +1,1870 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for hooks/useFileUpload/useFileUpload.ts</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../../prettify.css" />
9
+ <link rel="stylesheet" href="../../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../../index.html">All files</a> / <a href="index.html">hooks/useFileUpload</a> useFileUpload.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">79.32% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>472/595</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">76.38% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>55/72</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">50% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/2</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">79.32% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>472/595</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line medium'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
361
+ <a name='L296'></a><a href='#L296'>296</a>
362
+ <a name='L297'></a><a href='#L297'>297</a>
363
+ <a name='L298'></a><a href='#L298'>298</a>
364
+ <a name='L299'></a><a href='#L299'>299</a>
365
+ <a name='L300'></a><a href='#L300'>300</a>
366
+ <a name='L301'></a><a href='#L301'>301</a>
367
+ <a name='L302'></a><a href='#L302'>302</a>
368
+ <a name='L303'></a><a href='#L303'>303</a>
369
+ <a name='L304'></a><a href='#L304'>304</a>
370
+ <a name='L305'></a><a href='#L305'>305</a>
371
+ <a name='L306'></a><a href='#L306'>306</a>
372
+ <a name='L307'></a><a href='#L307'>307</a>
373
+ <a name='L308'></a><a href='#L308'>308</a>
374
+ <a name='L309'></a><a href='#L309'>309</a>
375
+ <a name='L310'></a><a href='#L310'>310</a>
376
+ <a name='L311'></a><a href='#L311'>311</a>
377
+ <a name='L312'></a><a href='#L312'>312</a>
378
+ <a name='L313'></a><a href='#L313'>313</a>
379
+ <a name='L314'></a><a href='#L314'>314</a>
380
+ <a name='L315'></a><a href='#L315'>315</a>
381
+ <a name='L316'></a><a href='#L316'>316</a>
382
+ <a name='L317'></a><a href='#L317'>317</a>
383
+ <a name='L318'></a><a href='#L318'>318</a>
384
+ <a name='L319'></a><a href='#L319'>319</a>
385
+ <a name='L320'></a><a href='#L320'>320</a>
386
+ <a name='L321'></a><a href='#L321'>321</a>
387
+ <a name='L322'></a><a href='#L322'>322</a>
388
+ <a name='L323'></a><a href='#L323'>323</a>
389
+ <a name='L324'></a><a href='#L324'>324</a>
390
+ <a name='L325'></a><a href='#L325'>325</a>
391
+ <a name='L326'></a><a href='#L326'>326</a>
392
+ <a name='L327'></a><a href='#L327'>327</a>
393
+ <a name='L328'></a><a href='#L328'>328</a>
394
+ <a name='L329'></a><a href='#L329'>329</a>
395
+ <a name='L330'></a><a href='#L330'>330</a>
396
+ <a name='L331'></a><a href='#L331'>331</a>
397
+ <a name='L332'></a><a href='#L332'>332</a>
398
+ <a name='L333'></a><a href='#L333'>333</a>
399
+ <a name='L334'></a><a href='#L334'>334</a>
400
+ <a name='L335'></a><a href='#L335'>335</a>
401
+ <a name='L336'></a><a href='#L336'>336</a>
402
+ <a name='L337'></a><a href='#L337'>337</a>
403
+ <a name='L338'></a><a href='#L338'>338</a>
404
+ <a name='L339'></a><a href='#L339'>339</a>
405
+ <a name='L340'></a><a href='#L340'>340</a>
406
+ <a name='L341'></a><a href='#L341'>341</a>
407
+ <a name='L342'></a><a href='#L342'>342</a>
408
+ <a name='L343'></a><a href='#L343'>343</a>
409
+ <a name='L344'></a><a href='#L344'>344</a>
410
+ <a name='L345'></a><a href='#L345'>345</a>
411
+ <a name='L346'></a><a href='#L346'>346</a>
412
+ <a name='L347'></a><a href='#L347'>347</a>
413
+ <a name='L348'></a><a href='#L348'>348</a>
414
+ <a name='L349'></a><a href='#L349'>349</a>
415
+ <a name='L350'></a><a href='#L350'>350</a>
416
+ <a name='L351'></a><a href='#L351'>351</a>
417
+ <a name='L352'></a><a href='#L352'>352</a>
418
+ <a name='L353'></a><a href='#L353'>353</a>
419
+ <a name='L354'></a><a href='#L354'>354</a>
420
+ <a name='L355'></a><a href='#L355'>355</a>
421
+ <a name='L356'></a><a href='#L356'>356</a>
422
+ <a name='L357'></a><a href='#L357'>357</a>
423
+ <a name='L358'></a><a href='#L358'>358</a>
424
+ <a name='L359'></a><a href='#L359'>359</a>
425
+ <a name='L360'></a><a href='#L360'>360</a>
426
+ <a name='L361'></a><a href='#L361'>361</a>
427
+ <a name='L362'></a><a href='#L362'>362</a>
428
+ <a name='L363'></a><a href='#L363'>363</a>
429
+ <a name='L364'></a><a href='#L364'>364</a>
430
+ <a name='L365'></a><a href='#L365'>365</a>
431
+ <a name='L366'></a><a href='#L366'>366</a>
432
+ <a name='L367'></a><a href='#L367'>367</a>
433
+ <a name='L368'></a><a href='#L368'>368</a>
434
+ <a name='L369'></a><a href='#L369'>369</a>
435
+ <a name='L370'></a><a href='#L370'>370</a>
436
+ <a name='L371'></a><a href='#L371'>371</a>
437
+ <a name='L372'></a><a href='#L372'>372</a>
438
+ <a name='L373'></a><a href='#L373'>373</a>
439
+ <a name='L374'></a><a href='#L374'>374</a>
440
+ <a name='L375'></a><a href='#L375'>375</a>
441
+ <a name='L376'></a><a href='#L376'>376</a>
442
+ <a name='L377'></a><a href='#L377'>377</a>
443
+ <a name='L378'></a><a href='#L378'>378</a>
444
+ <a name='L379'></a><a href='#L379'>379</a>
445
+ <a name='L380'></a><a href='#L380'>380</a>
446
+ <a name='L381'></a><a href='#L381'>381</a>
447
+ <a name='L382'></a><a href='#L382'>382</a>
448
+ <a name='L383'></a><a href='#L383'>383</a>
449
+ <a name='L384'></a><a href='#L384'>384</a>
450
+ <a name='L385'></a><a href='#L385'>385</a>
451
+ <a name='L386'></a><a href='#L386'>386</a>
452
+ <a name='L387'></a><a href='#L387'>387</a>
453
+ <a name='L388'></a><a href='#L388'>388</a>
454
+ <a name='L389'></a><a href='#L389'>389</a>
455
+ <a name='L390'></a><a href='#L390'>390</a>
456
+ <a name='L391'></a><a href='#L391'>391</a>
457
+ <a name='L392'></a><a href='#L392'>392</a>
458
+ <a name='L393'></a><a href='#L393'>393</a>
459
+ <a name='L394'></a><a href='#L394'>394</a>
460
+ <a name='L395'></a><a href='#L395'>395</a>
461
+ <a name='L396'></a><a href='#L396'>396</a>
462
+ <a name='L397'></a><a href='#L397'>397</a>
463
+ <a name='L398'></a><a href='#L398'>398</a>
464
+ <a name='L399'></a><a href='#L399'>399</a>
465
+ <a name='L400'></a><a href='#L400'>400</a>
466
+ <a name='L401'></a><a href='#L401'>401</a>
467
+ <a name='L402'></a><a href='#L402'>402</a>
468
+ <a name='L403'></a><a href='#L403'>403</a>
469
+ <a name='L404'></a><a href='#L404'>404</a>
470
+ <a name='L405'></a><a href='#L405'>405</a>
471
+ <a name='L406'></a><a href='#L406'>406</a>
472
+ <a name='L407'></a><a href='#L407'>407</a>
473
+ <a name='L408'></a><a href='#L408'>408</a>
474
+ <a name='L409'></a><a href='#L409'>409</a>
475
+ <a name='L410'></a><a href='#L410'>410</a>
476
+ <a name='L411'></a><a href='#L411'>411</a>
477
+ <a name='L412'></a><a href='#L412'>412</a>
478
+ <a name='L413'></a><a href='#L413'>413</a>
479
+ <a name='L414'></a><a href='#L414'>414</a>
480
+ <a name='L415'></a><a href='#L415'>415</a>
481
+ <a name='L416'></a><a href='#L416'>416</a>
482
+ <a name='L417'></a><a href='#L417'>417</a>
483
+ <a name='L418'></a><a href='#L418'>418</a>
484
+ <a name='L419'></a><a href='#L419'>419</a>
485
+ <a name='L420'></a><a href='#L420'>420</a>
486
+ <a name='L421'></a><a href='#L421'>421</a>
487
+ <a name='L422'></a><a href='#L422'>422</a>
488
+ <a name='L423'></a><a href='#L423'>423</a>
489
+ <a name='L424'></a><a href='#L424'>424</a>
490
+ <a name='L425'></a><a href='#L425'>425</a>
491
+ <a name='L426'></a><a href='#L426'>426</a>
492
+ <a name='L427'></a><a href='#L427'>427</a>
493
+ <a name='L428'></a><a href='#L428'>428</a>
494
+ <a name='L429'></a><a href='#L429'>429</a>
495
+ <a name='L430'></a><a href='#L430'>430</a>
496
+ <a name='L431'></a><a href='#L431'>431</a>
497
+ <a name='L432'></a><a href='#L432'>432</a>
498
+ <a name='L433'></a><a href='#L433'>433</a>
499
+ <a name='L434'></a><a href='#L434'>434</a>
500
+ <a name='L435'></a><a href='#L435'>435</a>
501
+ <a name='L436'></a><a href='#L436'>436</a>
502
+ <a name='L437'></a><a href='#L437'>437</a>
503
+ <a name='L438'></a><a href='#L438'>438</a>
504
+ <a name='L439'></a><a href='#L439'>439</a>
505
+ <a name='L440'></a><a href='#L440'>440</a>
506
+ <a name='L441'></a><a href='#L441'>441</a>
507
+ <a name='L442'></a><a href='#L442'>442</a>
508
+ <a name='L443'></a><a href='#L443'>443</a>
509
+ <a name='L444'></a><a href='#L444'>444</a>
510
+ <a name='L445'></a><a href='#L445'>445</a>
511
+ <a name='L446'></a><a href='#L446'>446</a>
512
+ <a name='L447'></a><a href='#L447'>447</a>
513
+ <a name='L448'></a><a href='#L448'>448</a>
514
+ <a name='L449'></a><a href='#L449'>449</a>
515
+ <a name='L450'></a><a href='#L450'>450</a>
516
+ <a name='L451'></a><a href='#L451'>451</a>
517
+ <a name='L452'></a><a href='#L452'>452</a>
518
+ <a name='L453'></a><a href='#L453'>453</a>
519
+ <a name='L454'></a><a href='#L454'>454</a>
520
+ <a name='L455'></a><a href='#L455'>455</a>
521
+ <a name='L456'></a><a href='#L456'>456</a>
522
+ <a name='L457'></a><a href='#L457'>457</a>
523
+ <a name='L458'></a><a href='#L458'>458</a>
524
+ <a name='L459'></a><a href='#L459'>459</a>
525
+ <a name='L460'></a><a href='#L460'>460</a>
526
+ <a name='L461'></a><a href='#L461'>461</a>
527
+ <a name='L462'></a><a href='#L462'>462</a>
528
+ <a name='L463'></a><a href='#L463'>463</a>
529
+ <a name='L464'></a><a href='#L464'>464</a>
530
+ <a name='L465'></a><a href='#L465'>465</a>
531
+ <a name='L466'></a><a href='#L466'>466</a>
532
+ <a name='L467'></a><a href='#L467'>467</a>
533
+ <a name='L468'></a><a href='#L468'>468</a>
534
+ <a name='L469'></a><a href='#L469'>469</a>
535
+ <a name='L470'></a><a href='#L470'>470</a>
536
+ <a name='L471'></a><a href='#L471'>471</a>
537
+ <a name='L472'></a><a href='#L472'>472</a>
538
+ <a name='L473'></a><a href='#L473'>473</a>
539
+ <a name='L474'></a><a href='#L474'>474</a>
540
+ <a name='L475'></a><a href='#L475'>475</a>
541
+ <a name='L476'></a><a href='#L476'>476</a>
542
+ <a name='L477'></a><a href='#L477'>477</a>
543
+ <a name='L478'></a><a href='#L478'>478</a>
544
+ <a name='L479'></a><a href='#L479'>479</a>
545
+ <a name='L480'></a><a href='#L480'>480</a>
546
+ <a name='L481'></a><a href='#L481'>481</a>
547
+ <a name='L482'></a><a href='#L482'>482</a>
548
+ <a name='L483'></a><a href='#L483'>483</a>
549
+ <a name='L484'></a><a href='#L484'>484</a>
550
+ <a name='L485'></a><a href='#L485'>485</a>
551
+ <a name='L486'></a><a href='#L486'>486</a>
552
+ <a name='L487'></a><a href='#L487'>487</a>
553
+ <a name='L488'></a><a href='#L488'>488</a>
554
+ <a name='L489'></a><a href='#L489'>489</a>
555
+ <a name='L490'></a><a href='#L490'>490</a>
556
+ <a name='L491'></a><a href='#L491'>491</a>
557
+ <a name='L492'></a><a href='#L492'>492</a>
558
+ <a name='L493'></a><a href='#L493'>493</a>
559
+ <a name='L494'></a><a href='#L494'>494</a>
560
+ <a name='L495'></a><a href='#L495'>495</a>
561
+ <a name='L496'></a><a href='#L496'>496</a>
562
+ <a name='L497'></a><a href='#L497'>497</a>
563
+ <a name='L498'></a><a href='#L498'>498</a>
564
+ <a name='L499'></a><a href='#L499'>499</a>
565
+ <a name='L500'></a><a href='#L500'>500</a>
566
+ <a name='L501'></a><a href='#L501'>501</a>
567
+ <a name='L502'></a><a href='#L502'>502</a>
568
+ <a name='L503'></a><a href='#L503'>503</a>
569
+ <a name='L504'></a><a href='#L504'>504</a>
570
+ <a name='L505'></a><a href='#L505'>505</a>
571
+ <a name='L506'></a><a href='#L506'>506</a>
572
+ <a name='L507'></a><a href='#L507'>507</a>
573
+ <a name='L508'></a><a href='#L508'>508</a>
574
+ <a name='L509'></a><a href='#L509'>509</a>
575
+ <a name='L510'></a><a href='#L510'>510</a>
576
+ <a name='L511'></a><a href='#L511'>511</a>
577
+ <a name='L512'></a><a href='#L512'>512</a>
578
+ <a name='L513'></a><a href='#L513'>513</a>
579
+ <a name='L514'></a><a href='#L514'>514</a>
580
+ <a name='L515'></a><a href='#L515'>515</a>
581
+ <a name='L516'></a><a href='#L516'>516</a>
582
+ <a name='L517'></a><a href='#L517'>517</a>
583
+ <a name='L518'></a><a href='#L518'>518</a>
584
+ <a name='L519'></a><a href='#L519'>519</a>
585
+ <a name='L520'></a><a href='#L520'>520</a>
586
+ <a name='L521'></a><a href='#L521'>521</a>
587
+ <a name='L522'></a><a href='#L522'>522</a>
588
+ <a name='L523'></a><a href='#L523'>523</a>
589
+ <a name='L524'></a><a href='#L524'>524</a>
590
+ <a name='L525'></a><a href='#L525'>525</a>
591
+ <a name='L526'></a><a href='#L526'>526</a>
592
+ <a name='L527'></a><a href='#L527'>527</a>
593
+ <a name='L528'></a><a href='#L528'>528</a>
594
+ <a name='L529'></a><a href='#L529'>529</a>
595
+ <a name='L530'></a><a href='#L530'>530</a>
596
+ <a name='L531'></a><a href='#L531'>531</a>
597
+ <a name='L532'></a><a href='#L532'>532</a>
598
+ <a name='L533'></a><a href='#L533'>533</a>
599
+ <a name='L534'></a><a href='#L534'>534</a>
600
+ <a name='L535'></a><a href='#L535'>535</a>
601
+ <a name='L536'></a><a href='#L536'>536</a>
602
+ <a name='L537'></a><a href='#L537'>537</a>
603
+ <a name='L538'></a><a href='#L538'>538</a>
604
+ <a name='L539'></a><a href='#L539'>539</a>
605
+ <a name='L540'></a><a href='#L540'>540</a>
606
+ <a name='L541'></a><a href='#L541'>541</a>
607
+ <a name='L542'></a><a href='#L542'>542</a>
608
+ <a name='L543'></a><a href='#L543'>543</a>
609
+ <a name='L544'></a><a href='#L544'>544</a>
610
+ <a name='L545'></a><a href='#L545'>545</a>
611
+ <a name='L546'></a><a href='#L546'>546</a>
612
+ <a name='L547'></a><a href='#L547'>547</a>
613
+ <a name='L548'></a><a href='#L548'>548</a>
614
+ <a name='L549'></a><a href='#L549'>549</a>
615
+ <a name='L550'></a><a href='#L550'>550</a>
616
+ <a name='L551'></a><a href='#L551'>551</a>
617
+ <a name='L552'></a><a href='#L552'>552</a>
618
+ <a name='L553'></a><a href='#L553'>553</a>
619
+ <a name='L554'></a><a href='#L554'>554</a>
620
+ <a name='L555'></a><a href='#L555'>555</a>
621
+ <a name='L556'></a><a href='#L556'>556</a>
622
+ <a name='L557'></a><a href='#L557'>557</a>
623
+ <a name='L558'></a><a href='#L558'>558</a>
624
+ <a name='L559'></a><a href='#L559'>559</a>
625
+ <a name='L560'></a><a href='#L560'>560</a>
626
+ <a name='L561'></a><a href='#L561'>561</a>
627
+ <a name='L562'></a><a href='#L562'>562</a>
628
+ <a name='L563'></a><a href='#L563'>563</a>
629
+ <a name='L564'></a><a href='#L564'>564</a>
630
+ <a name='L565'></a><a href='#L565'>565</a>
631
+ <a name='L566'></a><a href='#L566'>566</a>
632
+ <a name='L567'></a><a href='#L567'>567</a>
633
+ <a name='L568'></a><a href='#L568'>568</a>
634
+ <a name='L569'></a><a href='#L569'>569</a>
635
+ <a name='L570'></a><a href='#L570'>570</a>
636
+ <a name='L571'></a><a href='#L571'>571</a>
637
+ <a name='L572'></a><a href='#L572'>572</a>
638
+ <a name='L573'></a><a href='#L573'>573</a>
639
+ <a name='L574'></a><a href='#L574'>574</a>
640
+ <a name='L575'></a><a href='#L575'>575</a>
641
+ <a name='L576'></a><a href='#L576'>576</a>
642
+ <a name='L577'></a><a href='#L577'>577</a>
643
+ <a name='L578'></a><a href='#L578'>578</a>
644
+ <a name='L579'></a><a href='#L579'>579</a>
645
+ <a name='L580'></a><a href='#L580'>580</a>
646
+ <a name='L581'></a><a href='#L581'>581</a>
647
+ <a name='L582'></a><a href='#L582'>582</a>
648
+ <a name='L583'></a><a href='#L583'>583</a>
649
+ <a name='L584'></a><a href='#L584'>584</a>
650
+ <a name='L585'></a><a href='#L585'>585</a>
651
+ <a name='L586'></a><a href='#L586'>586</a>
652
+ <a name='L587'></a><a href='#L587'>587</a>
653
+ <a name='L588'></a><a href='#L588'>588</a>
654
+ <a name='L589'></a><a href='#L589'>589</a>
655
+ <a name='L590'></a><a href='#L590'>590</a>
656
+ <a name='L591'></a><a href='#L591'>591</a>
657
+ <a name='L592'></a><a href='#L592'>592</a>
658
+ <a name='L593'></a><a href='#L593'>593</a>
659
+ <a name='L594'></a><a href='#L594'>594</a>
660
+ <a name='L595'></a><a href='#L595'>595</a>
661
+ <a name='L596'></a><a href='#L596'>596</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
662
+ <span class="cline-any cline-yes">1x</span>
663
+ <span class="cline-any cline-yes">1x</span>
664
+ <span class="cline-any cline-yes">1x</span>
665
+ <span class="cline-any cline-yes">1x</span>
666
+ <span class="cline-any cline-yes">1x</span>
667
+ <span class="cline-any cline-yes">1x</span>
668
+ <span class="cline-any cline-yes">1x</span>
669
+ <span class="cline-any cline-yes">1x</span>
670
+ <span class="cline-any cline-yes">1x</span>
671
+ <span class="cline-any cline-yes">1x</span>
672
+ <span class="cline-any cline-yes">1x</span>
673
+ <span class="cline-any cline-yes">1x</span>
674
+ <span class="cline-any cline-yes">1x</span>
675
+ <span class="cline-any cline-yes">1x</span>
676
+ <span class="cline-any cline-yes">1x</span>
677
+ <span class="cline-any cline-yes">1x</span>
678
+ <span class="cline-any cline-yes">1x</span>
679
+ <span class="cline-any cline-yes">1x</span>
680
+ <span class="cline-any cline-yes">1x</span>
681
+ <span class="cline-any cline-yes">1x</span>
682
+ <span class="cline-any cline-yes">1x</span>
683
+ <span class="cline-any cline-yes">1x</span>
684
+ <span class="cline-any cline-yes">1x</span>
685
+ <span class="cline-any cline-yes">1x</span>
686
+ <span class="cline-any cline-yes">1x</span>
687
+ <span class="cline-any cline-yes">1x</span>
688
+ <span class="cline-any cline-yes">1x</span>
689
+ <span class="cline-any cline-yes">1x</span>
690
+ <span class="cline-any cline-yes">1x</span>
691
+ <span class="cline-any cline-yes">1x</span>
692
+ <span class="cline-any cline-yes">1x</span>
693
+ <span class="cline-any cline-yes">1x</span>
694
+ <span class="cline-any cline-yes">1x</span>
695
+ <span class="cline-any cline-yes">1x</span>
696
+ <span class="cline-any cline-yes">1x</span>
697
+ <span class="cline-any cline-yes">1x</span>
698
+ <span class="cline-any cline-yes">1x</span>
699
+ <span class="cline-any cline-yes">1x</span>
700
+ <span class="cline-any cline-yes">1x</span>
701
+ <span class="cline-any cline-yes">1x</span>
702
+ <span class="cline-any cline-yes">1x</span>
703
+ <span class="cline-any cline-yes">1x</span>
704
+ <span class="cline-any cline-yes">1x</span>
705
+ <span class="cline-any cline-yes">1x</span>
706
+ <span class="cline-any cline-yes">1x</span>
707
+ <span class="cline-any cline-yes">1x</span>
708
+ <span class="cline-any cline-yes">1x</span>
709
+ <span class="cline-any cline-yes">1x</span>
710
+ <span class="cline-any cline-yes">1x</span>
711
+ <span class="cline-any cline-yes">1x</span>
712
+ <span class="cline-any cline-yes">1x</span>
713
+ <span class="cline-any cline-yes">1x</span>
714
+ <span class="cline-any cline-yes">1x</span>
715
+ <span class="cline-any cline-yes">1x</span>
716
+ <span class="cline-any cline-yes">1x</span>
717
+ <span class="cline-any cline-yes">1x</span>
718
+ <span class="cline-any cline-yes">108x</span>
719
+ <span class="cline-any cline-yes">108x</span>
720
+ <span class="cline-any cline-yes">108x</span>
721
+ <span class="cline-any cline-yes">108x</span>
722
+ <span class="cline-any cline-yes">108x</span>
723
+ <span class="cline-any cline-yes">108x</span>
724
+ <span class="cline-any cline-yes">108x</span>
725
+ <span class="cline-any cline-yes">108x</span>
726
+ <span class="cline-any cline-yes">108x</span>
727
+ <span class="cline-any cline-yes">108x</span>
728
+ <span class="cline-any cline-yes">108x</span>
729
+ <span class="cline-any cline-yes">108x</span>
730
+ <span class="cline-any cline-yes">108x</span>
731
+ <span class="cline-any cline-yes">108x</span>
732
+ <span class="cline-any cline-yes">108x</span>
733
+ <span class="cline-any cline-yes">108x</span>
734
+ <span class="cline-any cline-yes">108x</span>
735
+ <span class="cline-any cline-yes">108x</span>
736
+ <span class="cline-any cline-yes">108x</span>
737
+ <span class="cline-any cline-yes">108x</span>
738
+ <span class="cline-any cline-yes">108x</span>
739
+ <span class="cline-any cline-yes">108x</span>
740
+ <span class="cline-any cline-yes">108x</span>
741
+ <span class="cline-any cline-yes">5x</span>
742
+ <span class="cline-any cline-yes">108x</span>
743
+ <span class="cline-any cline-yes">108x</span>
744
+ <span class="cline-any cline-yes">108x</span>
745
+ <span class="cline-any cline-yes">108x</span>
746
+ <span class="cline-any cline-yes">108x</span>
747
+ <span class="cline-any cline-yes">108x</span>
748
+ <span class="cline-any cline-no">&nbsp;</span>
749
+ <span class="cline-any cline-no">&nbsp;</span>
750
+ <span class="cline-any cline-yes">108x</span>
751
+ <span class="cline-any cline-yes">108x</span>
752
+ <span class="cline-any cline-yes">108x</span>
753
+ <span class="cline-any cline-yes">108x</span>
754
+ <span class="cline-any cline-yes">108x</span>
755
+ <span class="cline-any cline-yes">108x</span>
756
+ <span class="cline-any cline-yes">5x</span>
757
+ <span class="cline-any cline-yes">5x</span>
758
+ <span class="cline-any cline-yes">5x</span>
759
+ <span class="cline-any cline-yes">5x</span>
760
+ <span class="cline-any cline-yes">108x</span>
761
+ <span class="cline-any cline-yes">108x</span>
762
+ <span class="cline-any cline-yes">108x</span>
763
+ <span class="cline-any cline-yes">108x</span>
764
+ <span class="cline-any cline-yes">108x</span>
765
+ <span class="cline-any cline-yes">108x</span>
766
+ <span class="cline-any cline-yes">108x</span>
767
+ <span class="cline-any cline-yes">37x</span>
768
+ <span class="cline-any cline-yes">37x</span>
769
+ <span class="cline-any cline-yes">108x</span>
770
+ <span class="cline-any cline-yes">108x</span>
771
+ <span class="cline-any cline-yes">108x</span>
772
+ <span class="cline-any cline-yes">108x</span>
773
+ <span class="cline-any cline-yes">108x</span>
774
+ <span class="cline-any cline-yes">108x</span>
775
+ <span class="cline-any cline-yes">108x</span>
776
+ <span class="cline-any cline-yes">108x</span>
777
+ <span class="cline-any cline-yes">108x</span>
778
+ <span class="cline-any cline-yes">108x</span>
779
+ <span class="cline-any cline-yes">108x</span>
780
+ <span class="cline-any cline-yes">108x</span>
781
+ <span class="cline-any cline-yes">108x</span>
782
+ <span class="cline-any cline-yes">5x</span>
783
+ <span class="cline-any cline-yes">5x</span>
784
+ <span class="cline-any cline-yes">5x</span>
785
+ <span class="cline-any cline-yes">5x</span>
786
+ <span class="cline-any cline-yes">5x</span>
787
+ <span class="cline-any cline-yes">5x</span>
788
+ <span class="cline-any cline-yes">5x</span>
789
+ <span class="cline-any cline-yes">5x</span>
790
+ <span class="cline-any cline-yes">5x</span>
791
+ <span class="cline-any cline-yes">5x</span>
792
+ <span class="cline-any cline-yes">5x</span>
793
+ <span class="cline-any cline-yes">5x</span>
794
+ <span class="cline-any cline-yes">5x</span>
795
+ <span class="cline-any cline-yes">5x</span>
796
+ <span class="cline-any cline-yes">5x</span>
797
+ <span class="cline-any cline-yes">5x</span>
798
+ <span class="cline-any cline-yes">5x</span>
799
+ <span class="cline-any cline-yes">5x</span>
800
+ <span class="cline-any cline-yes">5x</span>
801
+ <span class="cline-any cline-yes">5x</span>
802
+ <span class="cline-any cline-yes">5x</span>
803
+ <span class="cline-any cline-yes">5x</span>
804
+ <span class="cline-any cline-yes">5x</span>
805
+ <span class="cline-any cline-yes">5x</span>
806
+ <span class="cline-any cline-yes">5x</span>
807
+ <span class="cline-any cline-yes">5x</span>
808
+ <span class="cline-any cline-yes">5x</span>
809
+ <span class="cline-any cline-yes">5x</span>
810
+ <span class="cline-any cline-yes">5x</span>
811
+ <span class="cline-any cline-yes">5x</span>
812
+ <span class="cline-any cline-yes">5x</span>
813
+ <span class="cline-any cline-yes">5x</span>
814
+ <span class="cline-any cline-yes">5x</span>
815
+ <span class="cline-any cline-yes">5x</span>
816
+ <span class="cline-any cline-yes">5x</span>
817
+ <span class="cline-any cline-no">&nbsp;</span>
818
+ <span class="cline-any cline-no">&nbsp;</span>
819
+ <span class="cline-any cline-no">&nbsp;</span>
820
+ <span class="cline-any cline-no">&nbsp;</span>
821
+ <span class="cline-any cline-no">&nbsp;</span>
822
+ <span class="cline-any cline-no">&nbsp;</span>
823
+ <span class="cline-any cline-no">&nbsp;</span>
824
+ <span class="cline-any cline-no">&nbsp;</span>
825
+ <span class="cline-any cline-no">&nbsp;</span>
826
+ <span class="cline-any cline-no">&nbsp;</span>
827
+ <span class="cline-any cline-no">&nbsp;</span>
828
+ <span class="cline-any cline-no">&nbsp;</span>
829
+ <span class="cline-any cline-no">&nbsp;</span>
830
+ <span class="cline-any cline-no">&nbsp;</span>
831
+ <span class="cline-any cline-no">&nbsp;</span>
832
+ <span class="cline-any cline-no">&nbsp;</span>
833
+ <span class="cline-any cline-yes">5x</span>
834
+ <span class="cline-any cline-yes">108x</span>
835
+ <span class="cline-any cline-yes">108x</span>
836
+ <span class="cline-any cline-yes">108x</span>
837
+ <span class="cline-any cline-yes">108x</span>
838
+ <span class="cline-any cline-yes">108x</span>
839
+ <span class="cline-any cline-yes">108x</span>
840
+ <span class="cline-any cline-yes">108x</span>
841
+ <span class="cline-any cline-yes">108x</span>
842
+ <span class="cline-any cline-yes">5x</span>
843
+ <span class="cline-any cline-yes">5x</span>
844
+ <span class="cline-any cline-yes">5x</span>
845
+ <span class="cline-any cline-yes">5x</span>
846
+ <span class="cline-any cline-yes">5x</span>
847
+ <span class="cline-any cline-yes">5x</span>
848
+ <span class="cline-any cline-yes">5x</span>
849
+ <span class="cline-any cline-yes">5x</span>
850
+ <span class="cline-any cline-yes">5x</span>
851
+ <span class="cline-any cline-yes">5x</span>
852
+ <span class="cline-any cline-yes">5x</span>
853
+ <span class="cline-any cline-yes">5x</span>
854
+ <span class="cline-any cline-yes">5x</span>
855
+ <span class="cline-any cline-no">&nbsp;</span>
856
+ <span class="cline-any cline-no">&nbsp;</span>
857
+ <span class="cline-any cline-no">&nbsp;</span>
858
+ <span class="cline-any cline-yes">5x</span>
859
+ <span class="cline-any cline-yes">5x</span>
860
+ <span class="cline-any cline-no">&nbsp;</span>
861
+ <span class="cline-any cline-no">&nbsp;</span>
862
+ <span class="cline-any cline-no">&nbsp;</span>
863
+ <span class="cline-any cline-yes">5x</span>
864
+ <span class="cline-any cline-yes">5x</span>
865
+ <span class="cline-any cline-yes">5x</span>
866
+ <span class="cline-any cline-yes">5x</span>
867
+ <span class="cline-any cline-yes">5x</span>
868
+ <span class="cline-any cline-yes">5x</span>
869
+ <span class="cline-any cline-yes">5x</span>
870
+ <span class="cline-any cline-yes">5x</span>
871
+ <span class="cline-any cline-yes">5x</span>
872
+ <span class="cline-any cline-yes">5x</span>
873
+ <span class="cline-any cline-yes">5x</span>
874
+ <span class="cline-any cline-yes">5x</span>
875
+ <span class="cline-any cline-yes">5x</span>
876
+ <span class="cline-any cline-yes">5x</span>
877
+ <span class="cline-any cline-yes">5x</span>
878
+ <span class="cline-any cline-yes">5x</span>
879
+ <span class="cline-any cline-yes">5x</span>
880
+ <span class="cline-any cline-yes">5x</span>
881
+ <span class="cline-any cline-yes">5x</span>
882
+ <span class="cline-any cline-yes">5x</span>
883
+ <span class="cline-any cline-yes">5x</span>
884
+ <span class="cline-any cline-yes">5x</span>
885
+ <span class="cline-any cline-yes">5x</span>
886
+ <span class="cline-any cline-yes">5x</span>
887
+ <span class="cline-any cline-yes">5x</span>
888
+ <span class="cline-any cline-yes">5x</span>
889
+ <span class="cline-any cline-yes">5x</span>
890
+ <span class="cline-any cline-yes">5x</span>
891
+ <span class="cline-any cline-yes">5x</span>
892
+ <span class="cline-any cline-yes">5x</span>
893
+ <span class="cline-any cline-yes">5x</span>
894
+ <span class="cline-any cline-yes">5x</span>
895
+ <span class="cline-any cline-yes">5x</span>
896
+ <span class="cline-any cline-yes">5x</span>
897
+ <span class="cline-any cline-yes">5x</span>
898
+ <span class="cline-any cline-yes">5x</span>
899
+ <span class="cline-any cline-yes">5x</span>
900
+ <span class="cline-any cline-yes">5x</span>
901
+ <span class="cline-any cline-yes">5x</span>
902
+ <span class="cline-any cline-yes">5x</span>
903
+ <span class="cline-any cline-yes">5x</span>
904
+ <span class="cline-any cline-yes">5x</span>
905
+ <span class="cline-any cline-yes">5x</span>
906
+ <span class="cline-any cline-yes">5x</span>
907
+ <span class="cline-any cline-yes">5x</span>
908
+ <span class="cline-any cline-yes">5x</span>
909
+ <span class="cline-any cline-yes">5x</span>
910
+ <span class="cline-any cline-yes">5x</span>
911
+ <span class="cline-any cline-yes">5x</span>
912
+ <span class="cline-any cline-yes">108x</span>
913
+ <span class="cline-any cline-yes">108x</span>
914
+ <span class="cline-any cline-yes">108x</span>
915
+ <span class="cline-any cline-yes">108x</span>
916
+ <span class="cline-any cline-yes">108x</span>
917
+ <span class="cline-any cline-yes">108x</span>
918
+ <span class="cline-any cline-yes">108x</span>
919
+ <span class="cline-any cline-yes">108x</span>
920
+ <span class="cline-any cline-yes">108x</span>
921
+ <span class="cline-any cline-yes">108x</span>
922
+ <span class="cline-any cline-yes">108x</span>
923
+ <span class="cline-any cline-yes">6x</span>
924
+ <span class="cline-any cline-yes">6x</span>
925
+ <span class="cline-any cline-yes">6x</span>
926
+ <span class="cline-any cline-yes">6x</span>
927
+ <span class="cline-any cline-yes">6x</span>
928
+ <span class="cline-any cline-yes">6x</span>
929
+ <span class="cline-any cline-yes">6x</span>
930
+ <span class="cline-any cline-yes">6x</span>
931
+ <span class="cline-any cline-yes">6x</span>
932
+ <span class="cline-any cline-yes">6x</span>
933
+ <span class="cline-any cline-yes">6x</span>
934
+ <span class="cline-any cline-yes">3x</span>
935
+ <span class="cline-any cline-yes">3x</span>
936
+ <span class="cline-any cline-yes">3x</span>
937
+ <span class="cline-any cline-yes">3x</span>
938
+ <span class="cline-any cline-yes">3x</span>
939
+ <span class="cline-any cline-yes">3x</span>
940
+ <span class="cline-any cline-yes">3x</span>
941
+ <span class="cline-any cline-yes">3x</span>
942
+ <span class="cline-any cline-yes">3x</span>
943
+ <span class="cline-any cline-yes">3x</span>
944
+ <span class="cline-any cline-yes">3x</span>
945
+ <span class="cline-any cline-yes">3x</span>
946
+ <span class="cline-any cline-yes">3x</span>
947
+ <span class="cline-any cline-yes">3x</span>
948
+ <span class="cline-any cline-yes">3x</span>
949
+ <span class="cline-any cline-yes">3x</span>
950
+ <span class="cline-any cline-yes">3x</span>
951
+ <span class="cline-any cline-yes">6x</span>
952
+ <span class="cline-any cline-yes">108x</span>
953
+ <span class="cline-any cline-yes">108x</span>
954
+ <span class="cline-any cline-yes">108x</span>
955
+ <span class="cline-any cline-yes">108x</span>
956
+ <span class="cline-any cline-yes">108x</span>
957
+ <span class="cline-any cline-yes">108x</span>
958
+ <span class="cline-any cline-yes">108x</span>
959
+ <span class="cline-any cline-yes">108x</span>
960
+ <span class="cline-any cline-yes">108x</span>
961
+ <span class="cline-any cline-yes">1x</span>
962
+ <span class="cline-any cline-yes">1x</span>
963
+ <span class="cline-any cline-yes">1x</span>
964
+ <span class="cline-any cline-yes">1x</span>
965
+ <span class="cline-any cline-yes">1x</span>
966
+ <span class="cline-any cline-no">&nbsp;</span>
967
+ <span class="cline-any cline-no">&nbsp;</span>
968
+ <span class="cline-any cline-no">&nbsp;</span>
969
+ <span class="cline-any cline-no">&nbsp;</span>
970
+ <span class="cline-any cline-no">&nbsp;</span>
971
+ <span class="cline-any cline-no">&nbsp;</span>
972
+ <span class="cline-any cline-no">&nbsp;</span>
973
+ <span class="cline-any cline-yes">1x</span>
974
+ <span class="cline-any cline-yes">1x</span>
975
+ <span class="cline-any cline-yes">1x</span>
976
+ <span class="cline-any cline-yes">1x</span>
977
+ <span class="cline-any cline-yes">1x</span>
978
+ <span class="cline-any cline-yes">1x</span>
979
+ <span class="cline-any cline-yes">1x</span>
980
+ <span class="cline-any cline-yes">1x</span>
981
+ <span class="cline-any cline-yes">1x</span>
982
+ <span class="cline-any cline-yes">1x</span>
983
+ <span class="cline-any cline-yes">1x</span>
984
+ <span class="cline-any cline-yes">1x</span>
985
+ <span class="cline-any cline-yes">1x</span>
986
+ <span class="cline-any cline-yes">1x</span>
987
+ <span class="cline-any cline-yes">1x</span>
988
+ <span class="cline-any cline-yes">108x</span>
989
+ <span class="cline-any cline-yes">108x</span>
990
+ <span class="cline-any cline-yes">108x</span>
991
+ <span class="cline-any cline-yes">108x</span>
992
+ <span class="cline-any cline-yes">108x</span>
993
+ <span class="cline-any cline-yes">108x</span>
994
+ <span class="cline-any cline-yes">108x</span>
995
+ <span class="cline-any cline-yes">108x</span>
996
+ <span class="cline-any cline-yes">108x</span>
997
+ <span class="cline-any cline-yes">1x</span>
998
+ <span class="cline-any cline-yes">1x</span>
999
+ <span class="cline-any cline-yes">1x</span>
1000
+ <span class="cline-any cline-no">&nbsp;</span>
1001
+ <span class="cline-any cline-no">&nbsp;</span>
1002
+ <span class="cline-any cline-yes">1x</span>
1003
+ <span class="cline-any cline-yes">1x</span>
1004
+ <span class="cline-any cline-yes">1x</span>
1005
+ <span class="cline-any cline-yes">1x</span>
1006
+ <span class="cline-any cline-yes">1x</span>
1007
+ <span class="cline-any cline-yes">1x</span>
1008
+ <span class="cline-any cline-yes">1x</span>
1009
+ <span class="cline-any cline-yes">1x</span>
1010
+ <span class="cline-any cline-yes">1x</span>
1011
+ <span class="cline-any cline-yes">1x</span>
1012
+ <span class="cline-any cline-yes">1x</span>
1013
+ <span class="cline-any cline-yes">1x</span>
1014
+ <span class="cline-any cline-yes">1x</span>
1015
+ <span class="cline-any cline-yes">108x</span>
1016
+ <span class="cline-any cline-yes">108x</span>
1017
+ <span class="cline-any cline-yes">108x</span>
1018
+ <span class="cline-any cline-yes">108x</span>
1019
+ <span class="cline-any cline-yes">108x</span>
1020
+ <span class="cline-any cline-yes">108x</span>
1021
+ <span class="cline-any cline-yes">108x</span>
1022
+ <span class="cline-any cline-no">&nbsp;</span>
1023
+ <span class="cline-any cline-no">&nbsp;</span>
1024
+ <span class="cline-any cline-no">&nbsp;</span>
1025
+ <span class="cline-any cline-no">&nbsp;</span>
1026
+ <span class="cline-any cline-no">&nbsp;</span>
1027
+ <span class="cline-any cline-no">&nbsp;</span>
1028
+ <span class="cline-any cline-no">&nbsp;</span>
1029
+ <span class="cline-any cline-no">&nbsp;</span>
1030
+ <span class="cline-any cline-yes">108x</span>
1031
+ <span class="cline-any cline-yes">108x</span>
1032
+ <span class="cline-any cline-yes">108x</span>
1033
+ <span class="cline-any cline-yes">108x</span>
1034
+ <span class="cline-any cline-yes">108x</span>
1035
+ <span class="cline-any cline-yes">108x</span>
1036
+ <span class="cline-any cline-yes">108x</span>
1037
+ <span class="cline-any cline-no">&nbsp;</span>
1038
+ <span class="cline-any cline-no">&nbsp;</span>
1039
+ <span class="cline-any cline-no">&nbsp;</span>
1040
+ <span class="cline-any cline-no">&nbsp;</span>
1041
+ <span class="cline-any cline-no">&nbsp;</span>
1042
+ <span class="cline-any cline-no">&nbsp;</span>
1043
+ <span class="cline-any cline-no">&nbsp;</span>
1044
+ <span class="cline-any cline-no">&nbsp;</span>
1045
+ <span class="cline-any cline-no">&nbsp;</span>
1046
+ <span class="cline-any cline-no">&nbsp;</span>
1047
+ <span class="cline-any cline-no">&nbsp;</span>
1048
+ <span class="cline-any cline-no">&nbsp;</span>
1049
+ <span class="cline-any cline-no">&nbsp;</span>
1050
+ <span class="cline-any cline-no">&nbsp;</span>
1051
+ <span class="cline-any cline-no">&nbsp;</span>
1052
+ <span class="cline-any cline-no">&nbsp;</span>
1053
+ <span class="cline-any cline-no">&nbsp;</span>
1054
+ <span class="cline-any cline-no">&nbsp;</span>
1055
+ <span class="cline-any cline-no">&nbsp;</span>
1056
+ <span class="cline-any cline-no">&nbsp;</span>
1057
+ <span class="cline-any cline-no">&nbsp;</span>
1058
+ <span class="cline-any cline-no">&nbsp;</span>
1059
+ <span class="cline-any cline-no">&nbsp;</span>
1060
+ <span class="cline-any cline-no">&nbsp;</span>
1061
+ <span class="cline-any cline-no">&nbsp;</span>
1062
+ <span class="cline-any cline-no">&nbsp;</span>
1063
+ <span class="cline-any cline-no">&nbsp;</span>
1064
+ <span class="cline-any cline-no">&nbsp;</span>
1065
+ <span class="cline-any cline-no">&nbsp;</span>
1066
+ <span class="cline-any cline-no">&nbsp;</span>
1067
+ <span class="cline-any cline-no">&nbsp;</span>
1068
+ <span class="cline-any cline-no">&nbsp;</span>
1069
+ <span class="cline-any cline-no">&nbsp;</span>
1070
+ <span class="cline-any cline-no">&nbsp;</span>
1071
+ <span class="cline-any cline-no">&nbsp;</span>
1072
+ <span class="cline-any cline-no">&nbsp;</span>
1073
+ <span class="cline-any cline-yes">108x</span>
1074
+ <span class="cline-any cline-yes">108x</span>
1075
+ <span class="cline-any cline-yes">108x</span>
1076
+ <span class="cline-any cline-yes">108x</span>
1077
+ <span class="cline-any cline-yes">108x</span>
1078
+ <span class="cline-any cline-yes">108x</span>
1079
+ <span class="cline-any cline-no">&nbsp;</span>
1080
+ <span class="cline-any cline-no">&nbsp;</span>
1081
+ <span class="cline-any cline-yes">108x</span>
1082
+ <span class="cline-any cline-yes">108x</span>
1083
+ <span class="cline-any cline-yes">108x</span>
1084
+ <span class="cline-any cline-yes">108x</span>
1085
+ <span class="cline-any cline-yes">108x</span>
1086
+ <span class="cline-any cline-yes">108x</span>
1087
+ <span class="cline-any cline-yes">108x</span>
1088
+ <span class="cline-any cline-no">&nbsp;</span>
1089
+ <span class="cline-any cline-no">&nbsp;</span>
1090
+ <span class="cline-any cline-no">&nbsp;</span>
1091
+ <span class="cline-any cline-no">&nbsp;</span>
1092
+ <span class="cline-any cline-no">&nbsp;</span>
1093
+ <span class="cline-any cline-no">&nbsp;</span>
1094
+ <span class="cline-any cline-no">&nbsp;</span>
1095
+ <span class="cline-any cline-no">&nbsp;</span>
1096
+ <span class="cline-any cline-no">&nbsp;</span>
1097
+ <span class="cline-any cline-no">&nbsp;</span>
1098
+ <span class="cline-any cline-no">&nbsp;</span>
1099
+ <span class="cline-any cline-no">&nbsp;</span>
1100
+ <span class="cline-any cline-no">&nbsp;</span>
1101
+ <span class="cline-any cline-no">&nbsp;</span>
1102
+ <span class="cline-any cline-no">&nbsp;</span>
1103
+ <span class="cline-any cline-no">&nbsp;</span>
1104
+ <span class="cline-any cline-no">&nbsp;</span>
1105
+ <span class="cline-any cline-yes">108x</span>
1106
+ <span class="cline-any cline-yes">108x</span>
1107
+ <span class="cline-any cline-yes">108x</span>
1108
+ <span class="cline-any cline-yes">108x</span>
1109
+ <span class="cline-any cline-yes">108x</span>
1110
+ <span class="cline-any cline-yes">108x</span>
1111
+ <span class="cline-any cline-yes">108x</span>
1112
+ <span class="cline-any cline-yes">108x</span>
1113
+ <span class="cline-any cline-yes">108x</span>
1114
+ <span class="cline-any cline-no">&nbsp;</span>
1115
+ <span class="cline-any cline-no">&nbsp;</span>
1116
+ <span class="cline-any cline-no">&nbsp;</span>
1117
+ <span class="cline-any cline-no">&nbsp;</span>
1118
+ <span class="cline-any cline-no">&nbsp;</span>
1119
+ <span class="cline-any cline-no">&nbsp;</span>
1120
+ <span class="cline-any cline-no">&nbsp;</span>
1121
+ <span class="cline-any cline-no">&nbsp;</span>
1122
+ <span class="cline-any cline-no">&nbsp;</span>
1123
+ <span class="cline-any cline-no">&nbsp;</span>
1124
+ <span class="cline-any cline-no">&nbsp;</span>
1125
+ <span class="cline-any cline-no">&nbsp;</span>
1126
+ <span class="cline-any cline-no">&nbsp;</span>
1127
+ <span class="cline-any cline-no">&nbsp;</span>
1128
+ <span class="cline-any cline-no">&nbsp;</span>
1129
+ <span class="cline-any cline-no">&nbsp;</span>
1130
+ <span class="cline-any cline-yes">108x</span>
1131
+ <span class="cline-any cline-yes">108x</span>
1132
+ <span class="cline-any cline-yes">108x</span>
1133
+ <span class="cline-any cline-yes">108x</span>
1134
+ <span class="cline-any cline-yes">108x</span>
1135
+ <span class="cline-any cline-yes">108x</span>
1136
+ <span class="cline-any cline-yes">108x</span>
1137
+ <span class="cline-any cline-yes">108x</span>
1138
+ <span class="cline-any cline-yes">108x</span>
1139
+ <span class="cline-any cline-yes">22x</span>
1140
+ <span class="cline-any cline-yes">22x</span>
1141
+ <span class="cline-any cline-yes">22x</span>
1142
+ <span class="cline-any cline-yes">22x</span>
1143
+ <span class="cline-any cline-yes">22x</span>
1144
+ <span class="cline-any cline-yes">22x</span>
1145
+ <span class="cline-any cline-yes">22x</span>
1146
+ <span class="cline-any cline-yes">22x</span>
1147
+ <span class="cline-any cline-yes">22x</span>
1148
+ <span class="cline-any cline-yes">22x</span>
1149
+ <span class="cline-any cline-yes">22x</span>
1150
+ <span class="cline-any cline-yes">22x</span>
1151
+ <span class="cline-any cline-yes">22x</span>
1152
+ <span class="cline-any cline-yes">22x</span>
1153
+ <span class="cline-any cline-yes">22x</span>
1154
+ <span class="cline-any cline-yes">22x</span>
1155
+ <span class="cline-any cline-yes">22x</span>
1156
+ <span class="cline-any cline-yes">22x</span>
1157
+ <span class="cline-any cline-yes">22x</span>
1158
+ <span class="cline-any cline-yes">22x</span>
1159
+ <span class="cline-any cline-yes">22x</span>
1160
+ <span class="cline-any cline-yes">22x</span>
1161
+ <span class="cline-any cline-yes">22x</span>
1162
+ <span class="cline-any cline-yes">22x</span>
1163
+ <span class="cline-any cline-yes">22x</span>
1164
+ <span class="cline-any cline-yes">22x</span>
1165
+ <span class="cline-any cline-yes">22x</span>
1166
+ <span class="cline-any cline-yes">22x</span>
1167
+ <span class="cline-any cline-yes">22x</span>
1168
+ <span class="cline-any cline-yes">108x</span>
1169
+ <span class="cline-any cline-yes">108x</span>
1170
+ <span class="cline-any cline-yes">108x</span>
1171
+ <span class="cline-any cline-yes">108x</span>
1172
+ <span class="cline-any cline-yes">108x</span>
1173
+ <span class="cline-any cline-yes">108x</span>
1174
+ <span class="cline-any cline-yes">108x</span>
1175
+ <span class="cline-any cline-yes">108x</span>
1176
+ <span class="cline-any cline-yes">108x</span>
1177
+ <span class="cline-any cline-yes">108x</span>
1178
+ <span class="cline-any cline-no">&nbsp;</span>
1179
+ <span class="cline-any cline-no">&nbsp;</span>
1180
+ <span class="cline-any cline-no">&nbsp;</span>
1181
+ <span class="cline-any cline-no">&nbsp;</span>
1182
+ <span class="cline-any cline-no">&nbsp;</span>
1183
+ <span class="cline-any cline-no">&nbsp;</span>
1184
+ <span class="cline-any cline-no">&nbsp;</span>
1185
+ <span class="cline-any cline-no">&nbsp;</span>
1186
+ <span class="cline-any cline-no">&nbsp;</span>
1187
+ <span class="cline-any cline-no">&nbsp;</span>
1188
+ <span class="cline-any cline-no">&nbsp;</span>
1189
+ <span class="cline-any cline-yes">108x</span>
1190
+ <span class="cline-any cline-yes">108x</span>
1191
+ <span class="cline-any cline-yes">108x</span>
1192
+ <span class="cline-any cline-yes">108x</span>
1193
+ <span class="cline-any cline-yes">108x</span>
1194
+ <span class="cline-any cline-yes">103x</span>
1195
+ <span class="cline-any cline-yes">108x</span>
1196
+ <span class="cline-any cline-yes">108x</span>
1197
+ <span class="cline-any cline-yes">108x</span>
1198
+ <span class="cline-any cline-yes">103x</span>
1199
+ <span class="cline-any cline-yes">108x</span>
1200
+ <span class="cline-any cline-yes">108x</span>
1201
+ <span class="cline-any cline-yes">108x</span>
1202
+ <span class="cline-any cline-yes">103x</span>
1203
+ <span class="cline-any cline-yes">108x</span>
1204
+ <span class="cline-any cline-yes">108x</span>
1205
+ <span class="cline-any cline-yes">108x</span>
1206
+ <span class="cline-any cline-yes">108x</span>
1207
+ <span class="cline-any cline-yes">103x</span>
1208
+ <span class="cline-any cline-yes">108x</span>
1209
+ <span class="cline-any cline-yes">108x</span>
1210
+ <span class="cline-any cline-yes">108x</span>
1211
+ <span class="cline-any cline-yes">108x</span>
1212
+ <span class="cline-any cline-yes">103x</span>
1213
+ <span class="cline-any cline-yes">108x</span>
1214
+ <span class="cline-any cline-yes">108x</span>
1215
+ <span class="cline-any cline-yes">108x</span>
1216
+ <span class="cline-any cline-yes">108x</span>
1217
+ <span class="cline-any cline-yes">108x</span>
1218
+ <span class="cline-any cline-yes">108x</span>
1219
+ <span class="cline-any cline-yes">108x</span>
1220
+ <span class="cline-any cline-yes">108x</span>
1221
+ <span class="cline-any cline-yes">108x</span>
1222
+ <span class="cline-any cline-yes">108x</span>
1223
+ <span class="cline-any cline-yes">108x</span>
1224
+ <span class="cline-any cline-yes">108x</span>
1225
+ <span class="cline-any cline-yes">108x</span>
1226
+ <span class="cline-any cline-yes">108x</span>
1227
+ <span class="cline-any cline-yes">108x</span>
1228
+ <span class="cline-any cline-yes">108x</span>
1229
+ <span class="cline-any cline-yes">108x</span>
1230
+ <span class="cline-any cline-yes">108x</span>
1231
+ <span class="cline-any cline-yes">108x</span>
1232
+ <span class="cline-any cline-yes">108x</span>
1233
+ <span class="cline-any cline-yes">108x</span>
1234
+ <span class="cline-any cline-yes">108x</span>
1235
+ <span class="cline-any cline-yes">108x</span>
1236
+ <span class="cline-any cline-yes">108x</span>
1237
+ <span class="cline-any cline-yes">108x</span>
1238
+ <span class="cline-any cline-yes">108x</span>
1239
+ <span class="cline-any cline-yes">108x</span>
1240
+ <span class="cline-any cline-yes">108x</span>
1241
+ <span class="cline-any cline-yes">108x</span>
1242
+ <span class="cline-any cline-yes">108x</span>
1243
+ <span class="cline-any cline-yes">108x</span>
1244
+ <span class="cline-any cline-yes">108x</span>
1245
+ <span class="cline-any cline-yes">108x</span>
1246
+ <span class="cline-any cline-yes">108x</span>
1247
+ <span class="cline-any cline-yes">108x</span>
1248
+ <span class="cline-any cline-yes">108x</span>
1249
+ <span class="cline-any cline-yes">108x</span>
1250
+ <span class="cline-any cline-yes">108x</span>
1251
+ <span class="cline-any cline-yes">108x</span>
1252
+ <span class="cline-any cline-yes">108x</span>
1253
+ <span class="cline-any cline-yes">108x</span>
1254
+ <span class="cline-any cline-yes">1x</span>
1255
+ <span class="cline-any cline-yes">1x</span>
1256
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
1257
+ * Complete hook for file handling with:
1258
+ * - Progressive upload to S3 with pre-signed URLs
1259
+ * - Per-file progress tracking
1260
+ * - Type and size validation
1261
+ * - Soft delete (disableFile)
1262
+ * - Multiple file support
1263
+ */
1264
+ &nbsp;
1265
+ import { useState, useCallback, useRef, useMemo } from "react";
1266
+ import { logger } from "../../utils/logger";
1267
+ &nbsp;
1268
+ // Import types from dedicated types file
1269
+ import type { FileStatus, FileItem, UseFileUploadOptions, UseFileUploadReturn } from "./types";
1270
+ &nbsp;
1271
+ // Import utilities
1272
+ import { generateFileId, extractRelativePath } from "./utils/fileUtils";
1273
+ import { inferContentType } from "./utils/mimeTypes";
1274
+ import { validateFile as validateFileUtil, validateFileCounts } from "./utils/validation";
1275
+ &nbsp;
1276
+ // Import services
1277
+ import { generateSignedUploadUrl, uploadToSignedUrl, deleteFile as deleteFileService, getSignedPreviewUrl } from "./services/uploadService";
1278
+ &nbsp;
1279
+ // Re-export types for backwards compatibility
1280
+ export type { FileStatus, FileItem, UseFileUploadOptions, UseFileUploadReturn } from "./types";
1281
+ &nbsp;
1282
+ /**
1283
+ * Hook for complete file handling with S3 upload
1284
+ *
1285
+ * @example
1286
+ * ```tsx
1287
+ * const {
1288
+ * files,
1289
+ * addFiles,
1290
+ * removeFile,
1291
+ * isUploading,
1292
+ * isValid,
1293
+ * completedS3Keys
1294
+ * } = useFileUpload({
1295
+ * acceptedTypes: ["image/png", "image/jpeg", "application/pdf"],
1296
+ * maxFileSize: 5 * 1024 * 1024, // 5MB
1297
+ * maxFiles: 3,
1298
+ * minFiles: 1
1299
+ * });
1300
+ *
1301
+ * // In form submit
1302
+ * const handleSubmit = async () =&gt; {
1303
+ * await waitForUploads(); // Wait for pending uploads
1304
+ * if (!isValid) return;
1305
+ *
1306
+ * await crudify.createItem("documents", {
1307
+ * files: completedS3Keys
1308
+ * });
1309
+ * };
1310
+ * ```
1311
+ */
1312
+ export const useFileUpload = (options: UseFileUploadOptions = {}): UseFileUploadReturn =&gt; {
1313
+ const {
1314
+ acceptedTypes,
1315
+ maxFileSize = 10 * 1024 * 1024, // 10MB default
1316
+ maxFiles,
1317
+ minFiles = 0,
1318
+ visibility = "private",
1319
+ onUploadComplete,
1320
+ onUploadError,
1321
+ onFileRemoved,
1322
+ onFilesChange,
1323
+ mode = "edit", // Default to edit mode for backwards compatibility
1324
+ } = options;
1325
+ &nbsp;
1326
+ const [files, setFiles] = useState&lt;FileItem[]&gt;([]);
1327
+ const [isTouched, setIsTouched] = useState(false);
1328
+ const [isSubmitted, setIsSubmitted] = useState(false);
1329
+ const [pendingDeletions, setPendingDeletions] = useState&lt;string[]&gt;([]);
1330
+ const uploadPromisesRef = useRef&lt;Map&lt;string, Promise&lt;void&gt;&gt;&gt;(new Map());
1331
+ &nbsp;
1332
+ /**
1333
+ * Marks the field as touched (to show validation errors)
1334
+ */
1335
+ const markAsTouched = useCallback(() =&gt; {
1336
+ setIsTouched(true);
1337
+ }, []);
1338
+ &nbsp;
1339
+ /**
1340
+ * Marks the form as submitted (to show all validation errors)
1341
+ */
1342
+ const markAsSubmitted = useCallback(() =&gt; {
1343
+ <span class="cstat-no" title="statement not covered" > setIsSubmitted(true);</span>
1344
+ <span class="cstat-no" title="statement not covered" > setIsTouched(true);</span>
1345
+ }, []);
1346
+ &nbsp;
1347
+ /**
1348
+ * Updates a file in the list
1349
+ */
1350
+ const updateFile = useCallback((fileId: string, updates: Partial&lt;FileItem&gt;) =&gt; {
1351
+ setFiles((prev) =&gt; {
1352
+ const updated = prev.map((f) =&gt; (f.id === fileId ? { ...f, ...updates <span class="branch-0 cbranch-no" title="branch not covered" >} : f))</span>;
1353
+ return updated;
1354
+ });
1355
+ }, []);
1356
+ &nbsp;
1357
+ /**
1358
+ * Notifies file changes
1359
+ */
1360
+ const notifyFilesChange = useCallback(
1361
+ (newFiles: FileItem[]) =&gt; {
1362
+ onFilesChange?.(newFiles);
1363
+ },
1364
+ [onFilesChange],
1365
+ );
1366
+ &nbsp;
1367
+ /**
1368
+ * Validates a file before upload - using extracted utility
1369
+ */
1370
+ const validateFile = useCallback((file: File) =&gt; validateFileUtil(file, acceptedTypes, maxFileSize), [acceptedTypes, maxFileSize]);
1371
+ &nbsp;
1372
+ /**
1373
+ * Uploads a single file to S3 - using extracted service
1374
+ */
1375
+ const uploadFile = useCallback(
1376
+ async (fileItem: FileItem, file: File): Promise&lt;void&gt; =&gt; {
1377
+ try {
1378
+ // Get pre-signed URL using service
1379
+ const { uploadUrl, filePath, publicUrl } = await generateSignedUploadUrl(file.name, file.type, visibility);
1380
+ &nbsp;
1381
+ // Update state to uploading
1382
+ updateFile(fileItem.id, { status: "uploading", progress: 0 });
1383
+ &nbsp;
1384
+ // Perform upload using service with progress callback
1385
+ await uploadToSignedUrl(file, uploadUrl, {
1386
+ onProgress: <span class="fstat-no" title="function not covered" >(progress) =&gt; updateFile(fileItem.id, { progress }),</span>
1387
+ });
1388
+ &nbsp;
1389
+ // Mark as completed
1390
+ const completedFile: Partial&lt;FileItem&gt; = {
1391
+ status: "completed",
1392
+ progress: 100,
1393
+ filePath,
1394
+ visibility,
1395
+ publicUrl,
1396
+ file: undefined, // Clear File reference
1397
+ };
1398
+ &nbsp;
1399
+ // Update state and notify changes
1400
+ setFiles((prev) =&gt; {
1401
+ const updated = prev.map((f) =&gt; (f.id === fileItem.id ? { ...f, ...completedFile <span class="branch-0 cbranch-no" title="branch not covered" >} : f))</span>;
1402
+ // Notify file change so the form receives the filePath
1403
+ notifyFilesChange(updated);
1404
+ // Notify upload success
1405
+ const updatedFile = updated.find((f) =&gt; f.id === fileItem.id);
1406
+ if (updatedFile) {
1407
+ <span class="branch-0 cbranch-no" title="branch not covered" > onUploadComplete?.(updatedFile);</span>
1408
+ }
1409
+ return updated;
1410
+ });
1411
+ <span class="branch-0 cbranch-no" title="branch not covered" > } catch (error) {</span>
1412
+ <span class="cstat-no" title="statement not covered" > const errorMessage = error instanceof Error ? error.message : "Unknown error";</span>
1413
+ <span class="cstat-no" title="statement not covered" ></span>
1414
+ <span class="cstat-no" title="statement not covered" > updateFile(fileItem.id, {</span>
1415
+ <span class="cstat-no" title="statement not covered" > status: "error",</span>
1416
+ <span class="cstat-no" title="statement not covered" > progress: 0,</span>
1417
+ <span class="cstat-no" title="statement not covered" > errorMessage,</span>
1418
+ <span class="cstat-no" title="statement not covered" > });</span>
1419
+ <span class="cstat-no" title="statement not covered" ></span>
1420
+ <span class="cstat-no" title="statement not covered" > setFiles((prev) =&gt; {</span>
1421
+ <span class="cstat-no" title="statement not covered" > const updatedFile = prev.find((f) =&gt; f.id === fileItem.id);</span>
1422
+ <span class="cstat-no" title="statement not covered" > if (updatedFile) {</span>
1423
+ <span class="cstat-no" title="statement not covered" > onUploadError?.(updatedFile, errorMessage);</span>
1424
+ <span class="cstat-no" title="statement not covered" > }</span>
1425
+ <span class="cstat-no" title="statement not covered" > return prev;</span>
1426
+ <span class="cstat-no" title="statement not covered" > });</span>
1427
+ <span class="cstat-no" title="statement not covered" > }</span>
1428
+ },
1429
+ [updateFile, onUploadComplete, onUploadError, visibility, notifyFilesChange],
1430
+ );
1431
+ &nbsp;
1432
+ /**
1433
+ * Add files and start upload
1434
+ */
1435
+ const addFiles = useCallback(
1436
+ async (newFiles: FileList | File[]): Promise&lt;void&gt; =&gt; {
1437
+ let fileArray = Array.from(newFiles);
1438
+ &nbsp;
1439
+ // Use functional update to get current file count and avoid stale closure
1440
+ let filesToAdd: FileItem[] = [];
1441
+ &nbsp;
1442
+ setFiles((prevFiles) =&gt; {
1443
+ // Validate file limit using current state
1444
+ // Only count active files (not pendingDeletion or error)
1445
+ if (maxFiles !== undefined) {
1446
+ const activeCount = prevFiles.filter((f) =&gt; f.status !== "pendingDeletion" &amp;&amp; f.status !== "error").length;
1447
+ const remainingSlots = maxFiles - activeCount;
1448
+ &nbsp;
1449
+ if (remainingSlots &lt;= 0) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1450
+ <span class="cstat-no" title="statement not covered" > logger.warn(`File limit of ${maxFiles} already reached`);</span>
1451
+ <span class="cstat-no" title="statement not covered" > return prevFiles;</span>
1452
+ <span class="cstat-no" title="statement not covered" > }</span>
1453
+ &nbsp;
1454
+ if (fileArray.length &gt; remainingSlots) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1455
+ <span class="cstat-no" title="statement not covered" > fileArray = fileArray.slice(0, remainingSlots);</span>
1456
+ <span class="cstat-no" title="statement not covered" > logger.warn(`Only ${remainingSlots} files will be added to not exceed limit`);</span>
1457
+ <span class="cstat-no" title="statement not covered" > }</span>
1458
+ }
1459
+ &nbsp;
1460
+ // Create file items
1461
+ const newFileItems: FileItem[] = [];
1462
+ &nbsp;
1463
+ for (const file of fileArray) {
1464
+ // Validate file
1465
+ const validation = validateFile(file);
1466
+ &nbsp;
1467
+ const fileItem: FileItem = {
1468
+ id: generateFileId(),
1469
+ name: file.name,
1470
+ size: file.size,
1471
+ contentType: file.type,
1472
+ status: validation.valid ? <span class="branch-0 cbranch-no" title="branch not covered" >"pending" : "error",</span>
1473
+ progress: 0,
1474
+ createdAt: Date.now(),
1475
+ file: validation.valid ? <span class="branch-0 cbranch-no" title="branch not covered" >file : undefined,</span>
1476
+ errorMessage: validation.error,
1477
+ isExisting: false, // Mark as new file (not from server)
1478
+ };
1479
+ &nbsp;
1480
+ newFileItems.push(fileItem);
1481
+ }
1482
+ &nbsp;
1483
+ filesToAdd = newFileItems;
1484
+ const updated = [...prevFiles, ...newFileItems];
1485
+ notifyFilesChange(updated);
1486
+ return updated;
1487
+ });
1488
+ &nbsp;
1489
+ // Start uploads in parallel for valid files (after state update)
1490
+ // Use setTimeout to ensure state has been updated
1491
+ setTimeout(() =&gt; {
1492
+ const validFiles = filesToAdd.filter((f) =&gt; f.status === "pending" &amp;&amp; f.file);
1493
+ &nbsp;
1494
+ for (const fileItem of validFiles) {
1495
+ if (fileItem.file) {
1496
+ const uploadPromise = uploadFile(fileItem, fileItem.file);
1497
+ uploadPromisesRef.current.set(fileItem.id, uploadPromise);
1498
+ &nbsp;
1499
+ // Clean from list when finished
1500
+ uploadPromise.finally(() =&gt; {
1501
+ uploadPromisesRef.current.delete(fileItem.id);
1502
+ });
1503
+ }
1504
+ }
1505
+ }, 0);
1506
+ },
1507
+ [maxFiles, validateFile, uploadFile, notifyFilesChange],
1508
+ );
1509
+ &nbsp;
1510
+ /**
1511
+ * Remove file - behavior depends on mode and whether file is existing
1512
+ * - CREATE mode: always needs confirmation, then delete immediately
1513
+ * - EDIT mode + existing file: mark as pendingDeletion (delete on form success)
1514
+ * - EDIT mode + new file: needs confirmation, then delete immediately
1515
+ */
1516
+ const removeFile = useCallback(
1517
+ async (fileId: string): Promise&lt;{ needsConfirmation: boolean; isExisting: boolean }&gt; =&gt; {
1518
+ const file = files.find((f) =&gt; f.id === fileId);
1519
+ if (!file) <span class="branch-0 cbranch-no" title="branch not covered" >return { needsConfirmation: false, isExisting: false };</span>
1520
+ &nbsp;
1521
+ // Determine if this is an existing file (loaded from server)
1522
+ const isExisting = file.isExisting === true;
1523
+ &nbsp;
1524
+ // In CREATE mode: always need confirmation, then delete immediately
1525
+ // In EDIT mode:
1526
+ // - Existing file: mark as pendingDeletion (no confirmation needed, can restore)
1527
+ // - New file: needs confirmation, then delete immediately
1528
+ if (mode === "create" || !isExisting) {
1529
+ // Return that confirmation is needed - component will show popup
1530
+ return { needsConfirmation: true, isExisting };
1531
+ }
1532
+ &nbsp;
1533
+ // EDIT mode with existing file: mark as pendingDeletion
1534
+ setPendingDeletions((prev) =&gt; [...prev, fileId]);
1535
+ &nbsp;
1536
+ // Update file status to pendingDeletion
1537
+ setFiles((prev) =&gt; {
1538
+ const updated = prev.map((f) =&gt; (f.id === fileId ? { ...f, status: "pendingDeletion" as FileStatus <span class="branch-0 cbranch-no" title="branch not covered" >} : f))</span>;
1539
+ // Notify change - exclude files pending deletion from the list
1540
+ const activeFilesFiltered = updated.filter((f) =&gt; f.status !== "pendingDeletion");
1541
+ notifyFilesChange(activeFilesFiltered);
1542
+ return updated;
1543
+ });
1544
+ &nbsp;
1545
+ return { needsConfirmation: false, isExisting };
1546
+ },
1547
+ [files, mode, notifyFilesChange],
1548
+ );
1549
+ &nbsp;
1550
+ /**
1551
+ * Delete a file immediately from the server - using extracted service
1552
+ * Used for new files (not yet saved in DB) or in CREATE mode
1553
+ */
1554
+ const deleteFileImmediately = useCallback(
1555
+ async (fileId: string): Promise&lt;{ success: boolean; error?: string }&gt; =&gt; {
1556
+ const file = files.find((f) =&gt; f.id === fileId);
1557
+ if (!file) <span class="branch-0 cbranch-no" title="branch not covered" >return { success: false, error: "File not found" };</span>
1558
+ &nbsp;
1559
+ // If file has no filePath (never uploaded), just remove from state
1560
+ if (!file.filePath) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1561
+ <span class="cstat-no" title="statement not covered" > setFiles((prev) =&gt; {</span>
1562
+ <span class="cstat-no" title="statement not covered" > const updated = prev.filter((f) =&gt; f.id !== fileId);</span>
1563
+ <span class="cstat-no" title="statement not covered" > notifyFilesChange(updated);</span>
1564
+ <span class="cstat-no" title="statement not covered" > return updated;</span>
1565
+ <span class="cstat-no" title="statement not covered" > });</span>
1566
+ <span class="cstat-no" title="statement not covered" > return { success: true };</span>
1567
+ <span class="cstat-no" title="statement not covered" > }</span>
1568
+ &nbsp;
1569
+ // Delete from server using service
1570
+ const result = await deleteFileService(file.filePath);
1571
+ &nbsp;
1572
+ if (result.success) {
1573
+ setFiles((prev) =&gt; {
1574
+ const updated = prev.filter((f) =&gt; f.id !== fileId);
1575
+ notifyFilesChange(updated);
1576
+ return updated;
1577
+ });
1578
+ <span class="branch-0 cbranch-no" title="branch not covered" > onFileRemoved?.(file);</span>
1579
+ return { success: true };
1580
+ <span class="branch-0 cbranch-no" title="branch not covered" > }</span>
1581
+ return { success: false, error: result.error || "Failed to delete file" };
1582
+ },
1583
+ [files, notifyFilesChange, onFileRemoved],
1584
+ );
1585
+ &nbsp;
1586
+ /**
1587
+ * Restore a single file that was marked for deletion
1588
+ * Only allows restoring existing files (files loaded from server)
1589
+ */
1590
+ const restoreFile = useCallback(
1591
+ (fileId: string): boolean =&gt; {
1592
+ const file = files.find((f) =&gt; f.id === fileId);
1593
+ // Only allow restoring existing files
1594
+ if (!file || !file.isExisting) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1595
+ <span class="cstat-no" title="statement not covered" > return false;</span>
1596
+ <span class="cstat-no" title="statement not covered" > }</span>
1597
+ &nbsp;
1598
+ // Remove from pending deletions
1599
+ setPendingDeletions((prev) =&gt; prev.filter((id) =&gt; id !== fileId));
1600
+ &nbsp;
1601
+ // Restore file status to completed
1602
+ setFiles((prev) =&gt; {
1603
+ const updated = prev.map((f) =&gt; (f.id === fileId ? { ...f, status: "completed" as FileStatus <span class="branch-0 cbranch-no" title="branch not covered" >} : f))</span>;
1604
+ notifyFilesChange(updated);
1605
+ return updated;
1606
+ });
1607
+ &nbsp;
1608
+ return true;
1609
+ },
1610
+ [files, notifyFilesChange],
1611
+ );
1612
+ &nbsp;
1613
+ /**
1614
+ * Restore all files that were marked for deletion
1615
+ */
1616
+ const restorePendingDeletions = useCallback(() =&gt; {
1617
+ <span class="cstat-no" title="statement not covered" > if (pendingDeletions.length === 0) return;</span>
1618
+ <span class="cstat-no" title="statement not covered" ></span>
1619
+ <span class="cstat-no" title="statement not covered" > setFiles((prev) =&gt; {</span>
1620
+ <span class="cstat-no" title="statement not covered" > const updated = prev.map((f) =&gt; (pendingDeletions.includes(f.id) ? { ...f, status: "completed" as FileStatus } : f));</span>
1621
+ <span class="cstat-no" title="statement not covered" > notifyFilesChange(updated);</span>
1622
+ <span class="cstat-no" title="statement not covered" > return updated;</span>
1623
+ <span class="cstat-no" title="statement not covered" > });</span>
1624
+ <span class="cstat-no" title="statement not covered" > setPendingDeletions([]);</span>
1625
+ }, [pendingDeletions, notifyFilesChange]);
1626
+ &nbsp;
1627
+ /**
1628
+ * Execute all pending deletions (call this on form submit) - using extracted service
1629
+ * This actually deletes the files from the server
1630
+ */
1631
+ const commitDeletions = useCallback(async (): Promise&lt;{ success: boolean; errors: string[] }&gt; =&gt; {
1632
+ <span class="cstat-no" title="statement not covered" > if (pendingDeletions.length === 0) {</span>
1633
+ <span class="cstat-no" title="statement not covered" > return { success: true, errors: [] };</span>
1634
+ <span class="cstat-no" title="statement not covered" > }</span>
1635
+ <span class="cstat-no" title="statement not covered" ></span>
1636
+ <span class="cstat-no" title="statement not covered" > const errors: string[] = [];</span>
1637
+ <span class="cstat-no" title="statement not covered" > const successfulDeletions: string[] = [];</span>
1638
+ <span class="cstat-no" title="statement not covered" ></span>
1639
+ <span class="cstat-no" title="statement not covered" > for (const fileId of pendingDeletions) {</span>
1640
+ <span class="cstat-no" title="statement not covered" > const file = files.find((f) =&gt; f.id === fileId);</span>
1641
+ <span class="cstat-no" title="statement not covered" > if (!file?.filePath) {</span>
1642
+ <span class="cstat-no" title="statement not covered" > // File without filePath (never uploaded) - just mark as successful</span>
1643
+ <span class="cstat-no" title="statement not covered" > successfulDeletions.push(fileId);</span>
1644
+ <span class="cstat-no" title="statement not covered" > continue;</span>
1645
+ <span class="cstat-no" title="statement not covered" > }</span>
1646
+ <span class="cstat-no" title="statement not covered" ></span>
1647
+ <span class="cstat-no" title="statement not covered" > // Use service for deletion</span>
1648
+ <span class="cstat-no" title="statement not covered" > const result = await deleteFileService(file.filePath);</span>
1649
+ <span class="cstat-no" title="statement not covered" ></span>
1650
+ <span class="cstat-no" title="statement not covered" > if (result.success) {</span>
1651
+ <span class="cstat-no" title="statement not covered" > successfulDeletions.push(fileId);</span>
1652
+ <span class="cstat-no" title="statement not covered" > onFileRemoved?.(file);</span>
1653
+ <span class="cstat-no" title="statement not covered" > } else {</span>
1654
+ <span class="cstat-no" title="statement not covered" > errors.push(result.error || `Failed to delete ${file.name}`);</span>
1655
+ <span class="cstat-no" title="statement not covered" > }</span>
1656
+ <span class="cstat-no" title="statement not covered" > }</span>
1657
+ <span class="cstat-no" title="statement not covered" ></span>
1658
+ <span class="cstat-no" title="statement not covered" > // Remove successfully deleted files from state</span>
1659
+ <span class="cstat-no" title="statement not covered" > if (successfulDeletions.length &gt; 0) {</span>
1660
+ <span class="cstat-no" title="statement not covered" > setFiles((prev) =&gt; {</span>
1661
+ <span class="cstat-no" title="statement not covered" > const updated = prev.filter((f) =&gt; !successfulDeletions.includes(f.id));</span>
1662
+ <span class="cstat-no" title="statement not covered" > return updated;</span>
1663
+ <span class="cstat-no" title="statement not covered" > });</span>
1664
+ <span class="cstat-no" title="statement not covered" > setPendingDeletions((prev) =&gt; prev.filter((id) =&gt; !successfulDeletions.includes(id)));</span>
1665
+ <span class="cstat-no" title="statement not covered" > }</span>
1666
+ <span class="cstat-no" title="statement not covered" ></span>
1667
+ <span class="cstat-no" title="statement not covered" > return { success: errors.length === 0, errors };</span>
1668
+ }, [files, pendingDeletions, onFileRemoved]);
1669
+ &nbsp;
1670
+ /**
1671
+ * Clear all files
1672
+ */
1673
+ const clearFiles = useCallback(() =&gt; {
1674
+ <span class="cstat-no" title="statement not covered" > setFiles([]);</span>
1675
+ <span class="cstat-no" title="statement not covered" > notifyFilesChange([]);</span>
1676
+ }, [notifyFilesChange]);
1677
+ &nbsp;
1678
+ /**
1679
+ * Retry upload for failed file
1680
+ */
1681
+ const retryUpload = useCallback(
1682
+ async (fileId: string): Promise&lt;void&gt; =&gt; {
1683
+ <span class="cstat-no" title="statement not covered" > const file = files.find((f) =&gt; f.id === fileId);</span>
1684
+ <span class="cstat-no" title="statement not covered" > if (!file || file.status !== "error" || !file.file) {</span>
1685
+ <span class="cstat-no" title="statement not covered" > logger.warn("Cannot retry: file not found or no original file");</span>
1686
+ <span class="cstat-no" title="statement not covered" > return;</span>
1687
+ <span class="cstat-no" title="statement not covered" > }</span>
1688
+ <span class="cstat-no" title="statement not covered" ></span>
1689
+ <span class="cstat-no" title="statement not covered" > // Reset state</span>
1690
+ <span class="cstat-no" title="statement not covered" > updateFile(fileId, { status: "pending", progress: 0, errorMessage: undefined });</span>
1691
+ <span class="cstat-no" title="statement not covered" ></span>
1692
+ <span class="cstat-no" title="statement not covered" > // Restart upload</span>
1693
+ <span class="cstat-no" title="statement not covered" > const uploadPromise = uploadFile(file, file.file);</span>
1694
+ <span class="cstat-no" title="statement not covered" > uploadPromisesRef.current.set(fileId, uploadPromise);</span>
1695
+ <span class="cstat-no" title="statement not covered" ></span>
1696
+ <span class="cstat-no" title="statement not covered" > uploadPromise.finally(() =&gt; {</span>
1697
+ <span class="cstat-no" title="statement not covered" > uploadPromisesRef.current.delete(fileId);</span>
1698
+ <span class="cstat-no" title="statement not covered" > });</span>
1699
+ <span class="cstat-no" title="statement not covered" > },</span>
1700
+ [files, updateFile, uploadFile],
1701
+ );
1702
+ &nbsp;
1703
+ /**
1704
+ * Wait for all uploads to complete
1705
+ * Returns a promise that resolves with the completed file paths
1706
+ * This ensures the caller gets the actual file paths without relying on React state timing
1707
+ */
1708
+ const waitForUploads = useCallback(async (): Promise&lt;string[]&gt; =&gt; {
1709
+ <span class="cstat-no" title="statement not covered" > const promises = Array.from(uploadPromisesRef.current.values());</span>
1710
+ <span class="cstat-no" title="statement not covered" > if (promises.length &gt; 0) {</span>
1711
+ <span class="cstat-no" title="statement not covered" > await Promise.allSettled(promises);</span>
1712
+ <span class="cstat-no" title="statement not covered" > }</span>
1713
+ <span class="cstat-no" title="statement not covered" > // Return current completed file paths directly from files state</span>
1714
+ <span class="cstat-no" title="statement not covered" > // We need to get the latest state, so we use a promise that resolves after state update</span>
1715
+ <span class="cstat-no" title="statement not covered" > return new Promise((resolve) =&gt; {</span>
1716
+ <span class="cstat-no" title="statement not covered" > // Use setTimeout to ensure we get the state after React has processed updates</span>
1717
+ <span class="cstat-no" title="statement not covered" > setTimeout(() =&gt; {</span>
1718
+ <span class="cstat-no" title="statement not covered" > setFiles((currentFiles) =&gt; {</span>
1719
+ <span class="cstat-no" title="statement not covered" > const paths = currentFiles.filter((f) =&gt; f.status === "completed" &amp;&amp; f.filePath).map((f) =&gt; f.filePath as string);</span>
1720
+ <span class="cstat-no" title="statement not covered" > resolve(paths);</span>
1721
+ <span class="cstat-no" title="statement not covered" > return currentFiles; // Don't modify state, just read it</span>
1722
+ <span class="cstat-no" title="statement not covered" > });</span>
1723
+ <span class="cstat-no" title="statement not covered" > }, 0);</span>
1724
+ <span class="cstat-no" title="statement not covered" > });</span>
1725
+ }, []);
1726
+ &nbsp;
1727
+ /**
1728
+ * Initialize with existing files (for editing)
1729
+ * @param existingFiles - Array of files with filePath (can be relative or full URL with baseUrl)
1730
+ * @param baseUrl - Optional baseUrl to strip from file paths
1731
+ */
1732
+ const initializeFiles = useCallback(
1733
+ (existingFiles: Array&lt;{ filePath: string; name: string; size?: number; contentType?: string }&gt;, baseUrl?: string) =&gt; {
1734
+ const fileItems: FileItem[] = existingFiles.map((ef) =&gt; {
1735
+ // IMPORTANT: Extract relative path if it comes with baseUrl
1736
+ // This ensures we always store the relative path (e.g., "public/file.jpg")
1737
+ // and not the full URL (e.g., "https://cdn.example.com/public/file.jpg")
1738
+ const relativePath = extractRelativePath(ef.filePath, baseUrl);
1739
+ &nbsp;
1740
+ // Extract visibility from the relative path
1741
+ const fileVisibility = relativePath.includes("/public/") || relativePath.startsWith("public/"<span class="branch-0 cbranch-no" title="branch not covered" >) ? "public" : "</span>private";
1742
+ &nbsp;
1743
+ // Infer contentType from extension if not provided
1744
+ const contentType = ef.contentType || inferContentType(ef.name);
1745
+ &nbsp;
1746
+ return {
1747
+ id: generateFileId(),
1748
+ name: ef.name,
1749
+ size: ef.size || 0,
1750
+ contentType,
1751
+ status: "completed" as FileStatus,
1752
+ progress: 100,
1753
+ filePath: relativePath, // Always store relative path
1754
+ visibility: fileVisibility,
1755
+ createdAt: Date.now(),
1756
+ isExisting: true, // Mark as existing file (loaded from server)
1757
+ };
1758
+ });
1759
+ &nbsp;
1760
+ setFiles(fileItems);
1761
+ notifyFilesChange(fileItems);
1762
+ },
1763
+ [notifyFilesChange],
1764
+ );
1765
+ &nbsp;
1766
+ /**
1767
+ * Gets URL for file preview - using extracted service
1768
+ * - Public: returns publicUrl directly
1769
+ * - Private: requests signed URL from backend
1770
+ */
1771
+ const getPreviewUrl = useCallback(
1772
+ async (fileId: string): Promise&lt;string | null&gt; =&gt; {
1773
+ <span class="cstat-no" title="statement not covered" > const file = files.find((f) =&gt; f.id === fileId);</span>
1774
+ <span class="cstat-no" title="statement not covered" > if (!file || !file.filePath) return null;</span>
1775
+ <span class="cstat-no" title="statement not covered" ></span>
1776
+ <span class="cstat-no" title="statement not covered" > // If public and already has publicUrl, use it</span>
1777
+ <span class="cstat-no" title="statement not covered" > if (file.visibility === "public" &amp;&amp; file.publicUrl) {</span>
1778
+ <span class="cstat-no" title="statement not covered" > return file.publicUrl;</span>
1779
+ <span class="cstat-no" title="statement not covered" > }</span>
1780
+ <span class="cstat-no" title="statement not covered" ></span>
1781
+ <span class="cstat-no" title="statement not covered" > // Request URL from backend using service</span>
1782
+ <span class="cstat-no" title="statement not covered" > return getSignedPreviewUrl(file.filePath, 3600);</span>
1783
+ <span class="cstat-no" title="statement not covered" > },</span>
1784
+ [files],
1785
+ );
1786
+ &nbsp;
1787
+ // Derived values with memoization
1788
+ const isUploading = useMemo(() =&gt; {
1789
+ return files.some((f) =&gt; f.status === "uploading" || f.status === "pending");
1790
+ }, [files]);
1791
+ &nbsp;
1792
+ const pendingCount = useMemo(() =&gt; {
1793
+ return files.filter((f) =&gt; f.status === "uploading" || f.status === "pending").length;
1794
+ }, [files]);
1795
+ &nbsp;
1796
+ const completedFilePaths = useMemo(() =&gt; {
1797
+ return files.filter((f) =&gt; f.status === "completed" &amp;&amp; f.filePath).map((f) =&gt; f.filePath as string);
1798
+ }, [files]);
1799
+ &nbsp;
1800
+ // Active files (excluding pendingDeletion)
1801
+ const activeFiles = useMemo(() =&gt; {
1802
+ return files.filter((f) =&gt; f.status !== "pendingDeletion");
1803
+ }, [files]);
1804
+ &nbsp;
1805
+ // Count of active files (completed, pending, or uploading)
1806
+ const activeFileCount = useMemo(() =&gt; {
1807
+ return activeFiles.filter((f) =&gt; f.status === "completed" || f.status === "pending" || f.status === "uploading").length;
1808
+ }, [activeFiles]);
1809
+ &nbsp;
1810
+ // Validation with i18n keys - using extracted utility
1811
+ const { isValid, validationError, validationErrorKey, validationErrorParams } = useMemo(
1812
+ () =&gt; validateFileCounts(activeFiles, minFiles, maxFiles),
1813
+ [activeFiles, minFiles, maxFiles],
1814
+ );
1815
+ &nbsp;
1816
+ // Computed value for hasPendingDeletions
1817
+ const hasPendingDeletions = pendingDeletions.length &gt; 0;
1818
+ &nbsp;
1819
+ return {
1820
+ files,
1821
+ activeFiles,
1822
+ activeFileCount,
1823
+ isUploading,
1824
+ pendingCount,
1825
+ addFiles,
1826
+ removeFile,
1827
+ deleteFileImmediately,
1828
+ restoreFile,
1829
+ clearFiles,
1830
+ retryUpload,
1831
+ isValid,
1832
+ validationError,
1833
+ validationErrorKey,
1834
+ validationErrorParams,
1835
+ waitForUploads,
1836
+ completedFilePaths,
1837
+ initializeFiles,
1838
+ isTouched,
1839
+ markAsTouched,
1840
+ isSubmitted,
1841
+ markAsSubmitted,
1842
+ getPreviewUrl,
1843
+ pendingDeletions,
1844
+ hasPendingDeletions,
1845
+ commitDeletions,
1846
+ restorePendingDeletions,
1847
+ };
1848
+ };
1849
+ &nbsp;
1850
+ export default useFileUpload;
1851
+ &nbsp;</pre></td></tr></table></pre>
1852
+
1853
+ <div class='push'></div><!-- for sticky footer -->
1854
+ </div><!-- /wrapper -->
1855
+ <div class='footer quiet pad2 space-top1 center small'>
1856
+ Code coverage generated by
1857
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1858
+ at 2026-01-28T06:22:15.683Z
1859
+ </div>
1860
+ <script src="../../prettify.js"></script>
1861
+ <script>
1862
+ window.onload = function () {
1863
+ prettyPrint();
1864
+ };
1865
+ </script>
1866
+ <script src="../../sorter.js"></script>
1867
+ <script src="../../block-navigation.js"></script>
1868
+ </body>
1869
+ </html>
1870
+