@angular-wave/angular.ts 0.0.1

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 (231) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.cjs +29 -0
  3. package/.github/workflows/playwright.yml +27 -0
  4. package/CHANGELOG.md +17974 -0
  5. package/CODE_OF_CONDUCT.md +3 -0
  6. package/CONTRIBUTING.md +246 -0
  7. package/DEVELOPERS.md +488 -0
  8. package/LICENSE +22 -0
  9. package/Makefile +31 -0
  10. package/README.md +115 -0
  11. package/RELEASE.md +98 -0
  12. package/SECURITY.md +16 -0
  13. package/TRIAGING.md +135 -0
  14. package/css/angular.css +22 -0
  15. package/dist/angular-ts.cjs.js +36843 -0
  16. package/dist/angular-ts.esm.js +36841 -0
  17. package/dist/angular-ts.umd.js +36848 -0
  18. package/dist/build/angular-animate.js +4272 -0
  19. package/dist/build/angular-aria.js +426 -0
  20. package/dist/build/angular-message-format.js +1072 -0
  21. package/dist/build/angular-messages.js +829 -0
  22. package/dist/build/angular-mocks.js +3757 -0
  23. package/dist/build/angular-parse-ext.js +1275 -0
  24. package/dist/build/angular-resource.js +911 -0
  25. package/dist/build/angular-route.js +1266 -0
  26. package/dist/build/angular-sanitize.js +891 -0
  27. package/dist/build/angular-touch.js +368 -0
  28. package/dist/build/angular.js +36600 -0
  29. package/e2e/unit.spec.ts +15 -0
  30. package/images/android-chrome-192x192.png +0 -0
  31. package/images/android-chrome-512x512.png +0 -0
  32. package/images/apple-touch-icon.png +0 -0
  33. package/images/favicon-16x16.png +0 -0
  34. package/images/favicon-32x32.png +0 -0
  35. package/images/favicon.ico +0 -0
  36. package/images/site.webmanifest +1 -0
  37. package/index.html +104 -0
  38. package/package.json +47 -0
  39. package/playwright.config.ts +78 -0
  40. package/public/circle.html +1 -0
  41. package/public/my_child_directive.html +1 -0
  42. package/public/my_directive.html +1 -0
  43. package/public/my_other_directive.html +1 -0
  44. package/public/test.html +1 -0
  45. package/rollup.config.js +31 -0
  46. package/src/animations/animateCache.js +55 -0
  47. package/src/animations/animateChildrenDirective.js +105 -0
  48. package/src/animations/animateCss.js +1139 -0
  49. package/src/animations/animateCssDriver.js +291 -0
  50. package/src/animations/animateJs.js +367 -0
  51. package/src/animations/animateJsDriver.js +67 -0
  52. package/src/animations/animateQueue.js +851 -0
  53. package/src/animations/animation.js +506 -0
  54. package/src/animations/module.js +779 -0
  55. package/src/animations/ngAnimateSwap.js +119 -0
  56. package/src/animations/rafScheduler.js +50 -0
  57. package/src/animations/shared.js +378 -0
  58. package/src/constants.js +20 -0
  59. package/src/core/animate.js +845 -0
  60. package/src/core/animateCss.js +73 -0
  61. package/src/core/animateRunner.js +195 -0
  62. package/src/core/attributes.js +199 -0
  63. package/src/core/cache.js +45 -0
  64. package/src/core/compile.js +4727 -0
  65. package/src/core/controller.js +225 -0
  66. package/src/core/exceptionHandler.js +63 -0
  67. package/src/core/filter.js +146 -0
  68. package/src/core/interpolate.js +442 -0
  69. package/src/core/interval.js +188 -0
  70. package/src/core/intervalFactory.js +57 -0
  71. package/src/core/location.js +1086 -0
  72. package/src/core/parser/parse.js +2562 -0
  73. package/src/core/parser/parse.md +13 -0
  74. package/src/core/q.js +746 -0
  75. package/src/core/rootScope.js +1596 -0
  76. package/src/core/sanitizeUri.js +85 -0
  77. package/src/core/sce.js +1161 -0
  78. package/src/core/taskTrackerFactory.js +125 -0
  79. package/src/core/timeout.js +121 -0
  80. package/src/core/urlUtils.js +187 -0
  81. package/src/core/utils.js +1349 -0
  82. package/src/directive/a.js +37 -0
  83. package/src/directive/attrs.js +283 -0
  84. package/src/directive/bind.js +51 -0
  85. package/src/directive/bind.md +142 -0
  86. package/src/directive/change.js +12 -0
  87. package/src/directive/change.md +25 -0
  88. package/src/directive/cloak.js +12 -0
  89. package/src/directive/cloak.md +24 -0
  90. package/src/directive/events.js +75 -0
  91. package/src/directive/events.md +166 -0
  92. package/src/directive/form.js +725 -0
  93. package/src/directive/init.js +15 -0
  94. package/src/directive/init.md +41 -0
  95. package/src/directive/input.js +1783 -0
  96. package/src/directive/list.js +46 -0
  97. package/src/directive/list.md +22 -0
  98. package/src/directive/ngClass.js +249 -0
  99. package/src/directive/ngController.js +64 -0
  100. package/src/directive/ngCsp.js +82 -0
  101. package/src/directive/ngIf.js +134 -0
  102. package/src/directive/ngInclude.js +217 -0
  103. package/src/directive/ngModel.js +1356 -0
  104. package/src/directive/ngModelOptions.js +509 -0
  105. package/src/directive/ngOptions.js +670 -0
  106. package/src/directive/ngRef.js +90 -0
  107. package/src/directive/ngRepeat.js +650 -0
  108. package/src/directive/ngShowHide.js +255 -0
  109. package/src/directive/ngSwitch.js +178 -0
  110. package/src/directive/ngTransclude.js +98 -0
  111. package/src/directive/non-bindable.js +11 -0
  112. package/src/directive/non-bindable.md +17 -0
  113. package/src/directive/script.js +30 -0
  114. package/src/directive/select.js +624 -0
  115. package/src/directive/style.js +25 -0
  116. package/src/directive/style.md +23 -0
  117. package/src/directive/validators.js +329 -0
  118. package/src/exts/aria.js +544 -0
  119. package/src/exts/messages.js +852 -0
  120. package/src/filters/filter.js +207 -0
  121. package/src/filters/filter.md +69 -0
  122. package/src/filters/filters.js +239 -0
  123. package/src/filters/json.md +16 -0
  124. package/src/filters/limit-to.js +43 -0
  125. package/src/filters/limit-to.md +19 -0
  126. package/src/filters/order-by.js +183 -0
  127. package/src/filters/order-by.md +83 -0
  128. package/src/index.js +13 -0
  129. package/src/injector.js +1034 -0
  130. package/src/jqLite.js +1117 -0
  131. package/src/loader.js +1320 -0
  132. package/src/public.js +215 -0
  133. package/src/routeToRegExp.js +41 -0
  134. package/src/services/anchorScroll.js +135 -0
  135. package/src/services/browser.js +321 -0
  136. package/src/services/cacheFactory.js +398 -0
  137. package/src/services/cookieReader.js +72 -0
  138. package/src/services/document.js +64 -0
  139. package/src/services/http.js +1537 -0
  140. package/src/services/httpBackend.js +206 -0
  141. package/src/services/log.js +160 -0
  142. package/src/services/templateRequest.js +139 -0
  143. package/test/angular.spec.js +2153 -0
  144. package/test/aria/aria.spec.js +1245 -0
  145. package/test/binding.spec.js +504 -0
  146. package/test/build-test.html +14 -0
  147. package/test/injector.spec.js +2327 -0
  148. package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
  149. package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
  150. package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
  151. package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
  152. package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
  153. package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
  154. package/test/jasmine/jasmine-browser.json +17 -0
  155. package/test/jasmine/jasmine.json +9 -0
  156. package/test/jqlite.spec.js +2133 -0
  157. package/test/loader.spec.js +219 -0
  158. package/test/messages/messages.spec.js +1146 -0
  159. package/test/min-err.spec.js +174 -0
  160. package/test/mock-test.html +13 -0
  161. package/test/module-test.html +15 -0
  162. package/test/ng/anomate.spec.js +606 -0
  163. package/test/ng/cache-factor.spec.js +334 -0
  164. package/test/ng/compile.spec.js +17956 -0
  165. package/test/ng/controller-provider.spec.js +227 -0
  166. package/test/ng/cookie-reader.spec.js +98 -0
  167. package/test/ng/directive/a.spec.js +192 -0
  168. package/test/ng/directive/bind.spec.js +334 -0
  169. package/test/ng/directive/boolean.spec.js +136 -0
  170. package/test/ng/directive/change.spec.js +71 -0
  171. package/test/ng/directive/class.spec.js +858 -0
  172. package/test/ng/directive/click.spec.js +38 -0
  173. package/test/ng/directive/cloak.spec.js +44 -0
  174. package/test/ng/directive/constoller.spec.js +194 -0
  175. package/test/ng/directive/element-style.spec.js +92 -0
  176. package/test/ng/directive/event.spec.js +282 -0
  177. package/test/ng/directive/form.spec.js +1518 -0
  178. package/test/ng/directive/href.spec.js +143 -0
  179. package/test/ng/directive/if.spec.js +402 -0
  180. package/test/ng/directive/include.spec.js +828 -0
  181. package/test/ng/directive/init.spec.js +68 -0
  182. package/test/ng/directive/input.spec.js +3810 -0
  183. package/test/ng/directive/list.spec.js +170 -0
  184. package/test/ng/directive/model-options.spec.js +1008 -0
  185. package/test/ng/directive/model.spec.js +1905 -0
  186. package/test/ng/directive/non-bindable.spec.js +55 -0
  187. package/test/ng/directive/options.spec.js +3583 -0
  188. package/test/ng/directive/ref.spec.js +575 -0
  189. package/test/ng/directive/repeat.spec.js +1675 -0
  190. package/test/ng/directive/script.spec.js +52 -0
  191. package/test/ng/directive/scrset.spec.js +67 -0
  192. package/test/ng/directive/select.spec.js +2541 -0
  193. package/test/ng/directive/show-hide.spec.js +253 -0
  194. package/test/ng/directive/src.spec.js +157 -0
  195. package/test/ng/directive/style.spec.js +178 -0
  196. package/test/ng/directive/switch.spec.js +647 -0
  197. package/test/ng/directive/validators.spec.js +717 -0
  198. package/test/ng/document.spec.js +52 -0
  199. package/test/ng/filter/filter.spec.js +714 -0
  200. package/test/ng/filter/filters.spec.js +35 -0
  201. package/test/ng/filter/limit-to.spec.js +251 -0
  202. package/test/ng/filter/order-by.spec.js +891 -0
  203. package/test/ng/filter.spec.js +149 -0
  204. package/test/ng/http-backend.spec.js +398 -0
  205. package/test/ng/http.spec.js +4071 -0
  206. package/test/ng/interpolate.spec.js +642 -0
  207. package/test/ng/interval.spec.js +343 -0
  208. package/test/ng/location.spec.js +3488 -0
  209. package/test/ng/on.spec.js +229 -0
  210. package/test/ng/parse.spec.js +4655 -0
  211. package/test/ng/prop.spec.js +805 -0
  212. package/test/ng/q.spec.js +2904 -0
  213. package/test/ng/root-element.spec.js +16 -0
  214. package/test/ng/sanitize-uri.spec.js +249 -0
  215. package/test/ng/sce.spec.js +660 -0
  216. package/test/ng/scope.spec.js +3442 -0
  217. package/test/ng/template-request.spec.js +236 -0
  218. package/test/ng/timeout.spec.js +351 -0
  219. package/test/ng/url-utils.spec.js +156 -0
  220. package/test/ng/utils.spec.js +144 -0
  221. package/test/original-test.html +21 -0
  222. package/test/public.spec.js +34 -0
  223. package/test/sanitize/bing-html.spec.js +36 -0
  224. package/test/server/express.js +158 -0
  225. package/test/test-utils.js +11 -0
  226. package/tsconfig.json +17 -0
  227. package/types/angular.d.ts +138 -0
  228. package/types/global.d.ts +9 -0
  229. package/types/index.d.ts +2357 -0
  230. package/types/jqlite.d.ts +558 -0
  231. package/vite.config.js +14 -0
@@ -0,0 +1,650 @@
1
+ import {
2
+ forEach,
3
+ minErr,
4
+ hashKey,
5
+ createMap,
6
+ isArrayLike,
7
+ } from "../core/utils";
8
+ import { getBlockNodes } from "../jqLite";
9
+
10
+ /**
11
+ * @ngdoc directive
12
+ * @name ngRepeat
13
+ * @multiElement
14
+ * @restrict A
15
+ *
16
+ * @description
17
+ * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
18
+ * instance gets its own scope, where the given loop variable is set to the current collection item,
19
+ * and `$index` is set to the item index or key.
20
+ *
21
+ * Special properties are exposed on the local scope of each template instance, including:
22
+ *
23
+ * | Variable | Type | Details |
24
+ * |-----------|-----------------|-----------------------------------------------------------------------------|
25
+ * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
26
+ * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27
+ * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
28
+ * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
29
+ * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
30
+ * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
31
+ *
32
+ * <div class="alert alert-info">
33
+ * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
34
+ * This may be useful when, for instance, nesting ngRepeats.
35
+ * </div>
36
+ *
37
+ *
38
+ * ## Iterating over object properties
39
+ *
40
+ * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
41
+ * syntax:
42
+ *
43
+ * ```js
44
+ * <div ng-repeat="(key, value) in myObj"> ... </div>
45
+ * ```
46
+ *
47
+ * However, there are a few limitations compared to array iteration:
48
+ *
49
+ * - The JavaScript specification does not define the order of keys
50
+ * returned for an object, so AngularJS relies on the order returned by the browser
51
+ * when running `for key in myObj`. Browsers generally follow the strategy of providing
52
+ * keys in the order in which they were defined, although there are exceptions when keys are deleted
53
+ * and reinstated. See the
54
+ * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
55
+ *
56
+ * - `ngRepeat` will silently *ignore* object keys starting with `$`, because
57
+ * it's a prefix used by AngularJS for public (`$`) and private (`$$`) properties.
58
+ *
59
+ * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
60
+ * objects, and will throw an error if used with one.
61
+ *
62
+ * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array
63
+ * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
64
+ * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
65
+ * or implement a `$watch` on the object yourself.
66
+ *
67
+ *
68
+ * ## Tracking and Duplicates
69
+ *
70
+ * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
71
+ * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM:
72
+ *
73
+ * * When an item is added, a new instance of the template is added to the DOM.
74
+ * * When an item is removed, its template instance is removed from the DOM.
75
+ * * When items are reordered, their respective templates are reordered in the DOM.
76
+ *
77
+ * To minimize creation of DOM elements, `ngRepeat` uses a function
78
+ * to "keep track" of all items in the collection and their corresponding DOM elements.
79
+ * For example, if an item is added to the collection, `ngRepeat` will know that all other items
80
+ * already have DOM elements, and will not re-render them.
81
+ *
82
+ * All different types of tracking functions, their syntax, and their support for duplicate
83
+ * items in collections can be found in the
84
+ * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
85
+ *
86
+ * <div class="alert alert-success">
87
+ * **Best Practice:** If you are working with objects that have a unique identifier property, you
88
+ * should track by this identifier instead of the object instance,
89
+ * e.g. `item in items track by item.id`.
90
+ * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items
91
+ * it has already rendered, even if the JavaScript objects in the collection have been substituted
92
+ * for new ones. For large collections, this significantly improves rendering performance.
93
+ * </div>
94
+ *
95
+ * ### Effects of DOM Element re-use
96
+ *
97
+ * When DOM elements are re-used, ngRepeat updates the scope for the element, which will
98
+ * automatically update any active bindings on the template. However, other
99
+ * functionality will not be updated, because the element is not re-created:
100
+ *
101
+ * - Directives are not re-compiled
102
+ * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not
103
+ * updated if they have stabilized.
104
+ *
105
+ * The above affects all kinds of element re-use due to tracking, but may be especially visible
106
+ * when tracking by `$index` due to the way ngRepeat re-uses elements.
107
+ *
108
+ * The following example shows the effects of different actions with tracking:
109
+
110
+ <example module="ngRepeat" name="ngRepeat-tracking" deps="angular-animate.js" animations="true">
111
+ <file name="script.js">
112
+ angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
113
+ let friends = [
114
+ {name:'John', age:25},
115
+ {name:'Mary', age:40},
116
+ {name:'Peter', age:85}
117
+ ];
118
+
119
+ $scope.removeFirst = function() {
120
+ $scope.friends.shift();
121
+ };
122
+
123
+ $scope.updateAge = function() {
124
+ $scope.friends.forEach(function(el) {
125
+ el.age = el.age + 5;
126
+ });
127
+ };
128
+
129
+ $scope.copy = function() {
130
+ $scope.friends = structuredClone($scope.friends);
131
+ };
132
+
133
+ $scope.reset = function() {
134
+ $scope.friends = structuredClone(friends);
135
+ };
136
+
137
+ $scope.reset();
138
+ });
139
+ </file>
140
+ <file name="index.html">
141
+ <div ng-controller="repeatController">
142
+ <ol>
143
+ <li>When you click "Update Age", only the first list updates the age, because all others have
144
+ a one-time binding on the age property. If you then click "Copy", the current friend list
145
+ is copied, and now the second list updates the age, because the identity of the collection items
146
+ has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the
147
+ items are already known according to their tracking functions.
148
+ </li>
149
+ <li>When you click "Remove First", the 4th list has the wrong age on both remaining items. This is
150
+ due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first
151
+ DOM element for the new first collection item, and so on. Since the age property is one-time
152
+ bound, the value remains from the collection item which was previously at this index.
153
+ </li>
154
+ </ol>
155
+
156
+ <button ng-click="removeFirst()">Remove First</button>
157
+ <button ng-click="updateAge()">Update Age</button>
158
+ <button ng-click="copy()">Copy</button>
159
+ <br><button ng-click="reset()">Reset List</button>
160
+ <br>
161
+ <code>track by $id(friend)</code> (default):
162
+ <ul class="example-animate-container">
163
+ <li class="animate-repeat" ng-repeat="friend in friends">
164
+ {{friend.name}} is {{friend.age}} years old.
165
+ </li>
166
+ </ul>
167
+ <code>track by $id(friend)</code> (default), with age one-time binding:
168
+ <ul class="example-animate-container">
169
+ <li class="animate-repeat" ng-repeat="friend in friends">
170
+ {{friend.name}} is {{::friend.age}} years old.
171
+ </li>
172
+ </ul>
173
+ <code>track by friend.name</code>, with age one-time binding:
174
+ <ul class="example-animate-container">
175
+ <li class="animate-repeat" ng-repeat="friend in friends track by friend.name">
176
+ {{friend.name}} is {{::friend.age}} years old.
177
+ </li>
178
+ </ul>
179
+ <code>track by $index</code>, with age one-time binding:
180
+ <ul class="example-animate-container">
181
+ <li class="animate-repeat" ng-repeat="friend in friends track by $index">
182
+ {{friend.name}} is {{::friend.age}} years old.
183
+ </li>
184
+ </ul>
185
+ </div>
186
+ </file>
187
+ <file name="animations.css">
188
+ .example-animate-container {
189
+ background:white;
190
+ border:1px solid black;
191
+ list-style:none;
192
+ margin:0;
193
+ padding:0 10px;
194
+ }
195
+
196
+ .animate-repeat {
197
+ line-height:30px;
198
+ list-style:none;
199
+ box-sizing:border-box;
200
+ }
201
+
202
+ .animate-repeat.ng-move,
203
+ .animate-repeat.ng-enter,
204
+ .animate-repeat.ng-leave {
205
+ transition:all linear 0.5s;
206
+ }
207
+
208
+ .animate-repeat.ng-leave.ng-leave-active,
209
+ .animate-repeat.ng-move,
210
+ .animate-repeat.ng-enter {
211
+ opacity:0;
212
+ max-height:0;
213
+ }
214
+
215
+ .animate-repeat.ng-leave,
216
+ .animate-repeat.ng-move.ng-move-active,
217
+ .animate-repeat.ng-enter.ng-enter-active {
218
+ opacity:1;
219
+ max-height:30px;
220
+ }
221
+ </file>
222
+ </example>
223
+
224
+ *
225
+ * ## Special repeat start and end points
226
+ * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
227
+ * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
228
+ * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on)
229
+ * up to and including the ending HTML tag where **ng-repeat-end** is placed.
230
+ *
231
+ * The example below makes use of this feature:
232
+ * ```html
233
+ * <header ng-repeat-start="item in items">
234
+ * Header {{ item }}
235
+ * </header>
236
+ * <div class="body">
237
+ * Body {{ item }}
238
+ * </div>
239
+ * <footer ng-repeat-end>
240
+ * Footer {{ item }}
241
+ * </footer>
242
+ * ```
243
+ *
244
+ * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
245
+ * ```html
246
+ * <header>
247
+ * Header A
248
+ * </header>
249
+ * <div class="body">
250
+ * Body A
251
+ * </div>
252
+ * <footer>
253
+ * Footer A
254
+ * </footer>
255
+ * <header>
256
+ * Header B
257
+ * </header>
258
+ * <div class="body">
259
+ * Body B
260
+ * </div>
261
+ * <footer>
262
+ * Footer B
263
+ * </footer>
264
+ * ```
265
+ *
266
+ * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
267
+ * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
268
+ *
269
+ * @animations
270
+ * | Animation | Occurs |
271
+ * |----------------------------------|-------------------------------------|
272
+ * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter |
273
+ * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out |
274
+ * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered |
275
+ *
276
+ * See the example below for defining CSS animations with ngRepeat.
277
+ *
278
+ * @element ANY
279
+ * @scope
280
+ * @priority 1000
281
+ * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
282
+ * formats are currently supported:
283
+ *
284
+ * * `variable in expression` – where variable is the user defined loop variable and `expression`
285
+ * is a scope expression giving the collection to enumerate.
286
+ *
287
+ * For example: `album in artist.albums`.
288
+ *
289
+ * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
290
+ * and `expression` is the scope expression giving the collection to enumerate.
291
+ *
292
+ * For example: `(name, age) in {'adam':10, 'amalie':12}`.
293
+ *
294
+ * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
295
+ * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
296
+ * is specified, ng-repeat associates elements by identity. It is an error to have
297
+ * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
298
+ * mapped to the same DOM element, which is not possible.)
299
+ *
300
+ * *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`.
301
+ * This implies that the DOM elements will be associated by item identity in the collection.
302
+ *
303
+ * The built-in `$id()` function can be used to assign a unique
304
+ * `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements
305
+ * with the corresponding item in the collection by identity. Moving the same object would move
306
+ * the DOM element in the same way in the DOM.
307
+ * Note that the default id function does not support duplicate primitive values (`number`, `string`),
308
+ * but supports duplictae non-primitive values (`object`) that are *equal* in shape.
309
+ *
310
+ * *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking
311
+ * id, for example with a function, or using a property on the collection items.
312
+ * `item in items track by item.id` is a typical pattern when the items have a unique identifier,
313
+ * e.g. database id. In this case the object identity does not matter. Two objects are considered
314
+ * equivalent as long as their `id` property is same.
315
+ * Tracking by unique identifier is the most performant way and should be used whenever possible.
316
+ *
317
+ * *$index*: This special property tracks the collection items by their index, and
318
+ * re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can
319
+ * be used for a performance improvement if no unique identfier is available and the identity of
320
+ * the collection items cannot be easily computed. It also allows duplicates.
321
+ *
322
+ * <div class="alert alert-warning">
323
+ * <strong>Note:</strong> Re-using DOM elements can have unforeseen effects. Read the
324
+ * {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for
325
+ * more info.
326
+ * </div>
327
+ *
328
+ * <div class="alert alert-warning">
329
+ * <strong>Note:</strong> the `track by` expression must come last - after any filters, and the alias expression:
330
+ * `item in items | filter:searchText as results track by item.id`
331
+ * </div>
332
+ *
333
+ * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
334
+ * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
335
+ * when a filter is active on the repeater, but the filtered result set is empty.
336
+ *
337
+ * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
338
+ * the items have been processed through the filter.
339
+ *
340
+ * Please note that `as [variable name]` is not an operator but rather a part of ngRepeat
341
+ * micro-syntax so it can be used only after all filters (and not as operator, inside an expression).
342
+ *
343
+ * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` .
344
+ *
345
+ */
346
+ export const ngRepeatDirective = [
347
+ "$parse",
348
+ "$animate",
349
+ "$compile",
350
+ ($parse, $animate, $compile) => {
351
+ const NG_REMOVED = "$$NG_REMOVED";
352
+ const ngRepeatMinErr = minErr("ngRepeat");
353
+
354
+ const updateScope = function (
355
+ scope,
356
+ index,
357
+ valueIdentifier,
358
+ value,
359
+ keyIdentifier,
360
+ key,
361
+ arrayLength,
362
+ ) {
363
+ // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
364
+ scope[valueIdentifier] = value;
365
+ if (keyIdentifier) scope[keyIdentifier] = key;
366
+ scope.$index = index;
367
+ scope.$first = index === 0;
368
+ scope.$last = index === arrayLength - 1;
369
+ scope.$middle = !(scope.$first || scope.$last);
370
+ scope.$odd = !(scope.$even = (index & 1) === 0);
371
+ };
372
+
373
+ const getBlockStart = function (block) {
374
+ return block.clone[0];
375
+ };
376
+
377
+ const getBlockEnd = function (block) {
378
+ return block.clone[block.clone.length - 1];
379
+ };
380
+
381
+ const trackByIdArrayFn = function ($scope, key, value) {
382
+ return hashKey(value);
383
+ };
384
+
385
+ const trackByIdObjFn = function ($scope, key) {
386
+ return key;
387
+ };
388
+
389
+ return {
390
+ restrict: "A",
391
+ multiElement: true,
392
+ transclude: "element",
393
+ priority: 1000,
394
+ terminal: true,
395
+ $$tlb: true,
396
+ compile: function ngRepeatCompile($element, $attr) {
397
+ const expression = $attr.ngRepeat;
398
+ const ngRepeatEndComment = $compile.$$createComment(
399
+ "end ngRepeat",
400
+ expression,
401
+ );
402
+
403
+ let match = expression.match(
404
+ /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/,
405
+ );
406
+
407
+ if (!match) {
408
+ throw ngRepeatMinErr(
409
+ "iexp",
410
+ "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
411
+ expression,
412
+ );
413
+ }
414
+
415
+ const lhs = match[1];
416
+ const rhs = match[2];
417
+ const aliasAs = match[3];
418
+ const trackByExp = match[4];
419
+
420
+ match = lhs.match(
421
+ /^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/,
422
+ );
423
+
424
+ if (!match) {
425
+ throw ngRepeatMinErr(
426
+ "iidexp",
427
+ "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
428
+ lhs,
429
+ );
430
+ }
431
+ const valueIdentifier = match[3] || match[1];
432
+ const keyIdentifier = match[2];
433
+
434
+ if (
435
+ aliasAs &&
436
+ (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
437
+ /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(
438
+ aliasAs,
439
+ ))
440
+ ) {
441
+ throw ngRepeatMinErr(
442
+ "badident",
443
+ "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
444
+ aliasAs,
445
+ );
446
+ }
447
+
448
+ let trackByIdExpFn;
449
+
450
+ if (trackByExp) {
451
+ var hashFnLocals = { $id: hashKey };
452
+ const trackByExpGetter = $parse(trackByExp);
453
+
454
+ trackByIdExpFn = function ($scope, key, value, index) {
455
+ // assign key, value, and $index to the locals so that they can be used in hash functions
456
+ if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
457
+ hashFnLocals[valueIdentifier] = value;
458
+ hashFnLocals.$index = index;
459
+ return trackByExpGetter($scope, hashFnLocals);
460
+ };
461
+ }
462
+
463
+ return function ngRepeatLink(
464
+ $scope,
465
+ $element,
466
+ $attr,
467
+ ctrl,
468
+ $transclude,
469
+ ) {
470
+ // Store a list of elements from previous run. This is a hash where key is the item from the
471
+ // iterator, and the value is objects with following properties.
472
+ // - scope: bound scope
473
+ // - clone: previous element.
474
+ // - index: position
475
+ //
476
+ // We are using no-proto object so that we don't need to guard against inherited props via
477
+ // hasOwnProperty.
478
+ let lastBlockMap = createMap();
479
+
480
+ // watch props
481
+ //watch props
482
+ $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
483
+ var index,
484
+ length,
485
+ previousNode = $element[0], // node that cloned nodes should be inserted after
486
+ // initialized to the comment node anchor
487
+ nextNode,
488
+ // Same as lastBlockMap but it has the current state. It will become the
489
+ // lastBlockMap on the next iteration.
490
+ nextBlockMap = createMap(),
491
+ collectionLength,
492
+ key,
493
+ value, // key/value of iteration
494
+ trackById,
495
+ trackByIdFn,
496
+ collectionKeys,
497
+ block, // last object information {scope, element, id}
498
+ nextBlockOrder,
499
+ elementsToRemove;
500
+
501
+ if (aliasAs) {
502
+ $scope[aliasAs] = collection;
503
+ }
504
+
505
+ if (isArrayLike(collection)) {
506
+ collectionKeys = collection;
507
+ trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
508
+ } else {
509
+ trackByIdFn = trackByIdExpFn || trackByIdObjFn;
510
+ // if object, extract keys, in enumeration order, unsorted
511
+ collectionKeys = [];
512
+ for (var itemKey in collection) {
513
+ if (
514
+ Object.hasOwnProperty.call(collection, itemKey) &&
515
+ itemKey.charAt(0) !== "$"
516
+ ) {
517
+ collectionKeys.push(itemKey);
518
+ }
519
+ }
520
+ }
521
+
522
+ collectionLength = collectionKeys.length;
523
+ nextBlockOrder = new Array(collectionLength);
524
+
525
+ // locate existing items
526
+ for (index = 0; index < collectionLength; index++) {
527
+ key =
528
+ collection === collectionKeys ? index : collectionKeys[index];
529
+ value = collection[key];
530
+ trackById = trackByIdFn($scope, key, value, index);
531
+ if (lastBlockMap[trackById]) {
532
+ // found previously seen block
533
+ block = lastBlockMap[trackById];
534
+ delete lastBlockMap[trackById];
535
+ nextBlockMap[trackById] = block;
536
+ nextBlockOrder[index] = block;
537
+ } else if (nextBlockMap[trackById]) {
538
+ // if collision detected. restore lastBlockMap and throw an error
539
+ forEach(nextBlockOrder, function (block) {
540
+ if (block && block.scope) lastBlockMap[block.id] = block;
541
+ });
542
+ throw ngRepeatMinErr(
543
+ "dupes",
544
+ "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
545
+ expression,
546
+ trackById,
547
+ value,
548
+ );
549
+ } else {
550
+ // new never before seen block
551
+ nextBlockOrder[index] = {
552
+ id: trackById,
553
+ scope: undefined,
554
+ clone: undefined,
555
+ };
556
+ nextBlockMap[trackById] = true;
557
+ }
558
+ }
559
+
560
+ // Clear the value property from the hashFnLocals object to prevent a reference to the last value
561
+ // being leaked into the ngRepeatCompile function scope
562
+ if (hashFnLocals) {
563
+ hashFnLocals[valueIdentifier] = undefined;
564
+ }
565
+
566
+ // remove leftover items
567
+ for (var blockKey in lastBlockMap) {
568
+ block = lastBlockMap[blockKey];
569
+ elementsToRemove = getBlockNodes(block.clone);
570
+ $animate.leave(elementsToRemove);
571
+ if (elementsToRemove[0].parentNode) {
572
+ // if the element was not removed yet because of pending animation, mark it as deleted
573
+ // so that we can ignore it later
574
+ for (
575
+ index = 0, length = elementsToRemove.length;
576
+ index < length;
577
+ index++
578
+ ) {
579
+ elementsToRemove[index][NG_REMOVED] = true;
580
+ }
581
+ }
582
+ block.scope.$destroy();
583
+ }
584
+
585
+ // we are not using forEach for perf reasons (trying to avoid #call)
586
+ for (index = 0; index < collectionLength; index++) {
587
+ key =
588
+ collection === collectionKeys ? index : collectionKeys[index];
589
+ value = collection[key];
590
+ block = nextBlockOrder[index];
591
+
592
+ if (block.scope) {
593
+ // if we have already seen this object, then we need to reuse the
594
+ // associated scope/element
595
+
596
+ nextNode = previousNode;
597
+
598
+ // skip nodes that are already pending removal via leave animation
599
+ do {
600
+ nextNode = nextNode.nextSibling;
601
+ } while (nextNode && nextNode[NG_REMOVED]);
602
+
603
+ if (getBlockStart(block) !== nextNode) {
604
+ // existing item which got moved
605
+ $animate.move(getBlockNodes(block.clone), null, previousNode);
606
+ }
607
+ previousNode = getBlockEnd(block);
608
+ updateScope(
609
+ block.scope,
610
+ index,
611
+ valueIdentifier,
612
+ value,
613
+ keyIdentifier,
614
+ key,
615
+ collectionLength,
616
+ );
617
+ } else {
618
+ // new item which we don't know about
619
+ $transclude(function ngRepeatTransclude(clone, scope) {
620
+ block.scope = scope;
621
+ // http://jsperf.com/clone-vs-createcomment
622
+ var endNode = ngRepeatEndComment.cloneNode(false);
623
+ clone[clone.length++] = endNode;
624
+
625
+ $animate.enter(clone, null, previousNode);
626
+ previousNode = endNode;
627
+ // Note: We only need the first/last node of the cloned nodes.
628
+ // However, we need to keep the reference to the jqlite wrapper as it might be changed later
629
+ // by a directive with templateUrl when its template arrives.
630
+ block.clone = clone;
631
+ nextBlockMap[block.id] = block;
632
+ updateScope(
633
+ block.scope,
634
+ index,
635
+ valueIdentifier,
636
+ value,
637
+ keyIdentifier,
638
+ key,
639
+ collectionLength,
640
+ );
641
+ });
642
+ }
643
+ }
644
+ lastBlockMap = nextBlockMap;
645
+ });
646
+ };
647
+ },
648
+ };
649
+ },
650
+ ];