@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,4727 @@
1
+ import { jqLite, getBooleanAttrName, isTextNode, startingTag } from "../jqLite";
2
+ import { identifierForController } from "./controller";
3
+ import {
4
+ minErr,
5
+ assertArg,
6
+ assertNotHasOwnProperty,
7
+ createMap,
8
+ forEach,
9
+ identity,
10
+ isArray,
11
+ isDefined,
12
+ isFunction,
13
+ isObject,
14
+ isString,
15
+ lowercase,
16
+ extend,
17
+ reverseParams,
18
+ snakeCase,
19
+ isScope,
20
+ valueFn,
21
+ inherit,
22
+ isUndefined,
23
+ arrayRemove,
24
+ nodeName_,
25
+ bind,
26
+ trim,
27
+ isBoolean,
28
+ equals,
29
+ sliceArgs,
30
+ simpleCompare,
31
+ isError,
32
+ directiveNormalize,
33
+ } from "./utils";
34
+
35
+ import { SCE_CONTEXTS } from "./sce";
36
+ import { PREFIX_REGEXP, ALIASED_ATTR } from "../constants";
37
+ import { createEventDirective } from "../directive/events";
38
+
39
+ /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
40
+ *
41
+ * DOM-related variables:
42
+ *
43
+ * - "node" - DOM Node
44
+ * - "element" - DOM Element or Node
45
+ * - "$node" or "$element" - jqLite-wrapped node or element
46
+ *
47
+ *
48
+ * Compiler related stuff:
49
+ *
50
+ * - "linkFn" - linking fn of a single directive
51
+ * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
52
+ * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
53
+ * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
54
+ */
55
+
56
+ /**
57
+ * @ngdoc service
58
+ * @name $compile
59
+ * @kind function
60
+ *
61
+ * @description
62
+ * Compiles an HTML string or DOM into a template and produces a template function, which
63
+ * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
64
+ *
65
+ * The compilation is a process of walking the DOM tree and matching DOM elements to
66
+ * {@link ng.$compileProvider#directive directives}.
67
+ *
68
+ * <div class="alert alert-warning">
69
+ * **Note:** This document is an in-depth reference of all directive options.
70
+ * For a gentle introduction to directives with examples of common use cases,
71
+ * see the {@link guide/directive directive guide}.
72
+ * </div>
73
+ *
74
+ * ## Comprehensive Directive API
75
+ *
76
+ * There are many different options for a directive.
77
+ *
78
+ * The difference resides in the return value of the factory function.
79
+ * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
80
+ * that defines the directive properties, or just the `postLink` function (all other properties will have
81
+ * the default values).
82
+ *
83
+ * <div class="alert alert-success">
84
+ * **Best Practice:** It's recommended to use the "directive definition object" form.
85
+ * </div>
86
+ *
87
+ * Here's an example directive declared with a Directive Definition Object:
88
+ *
89
+ * ```js
90
+ * let myModule = angular.module(...);
91
+ *
92
+ * myModule.directive('directiveName', function factory(injectables) {
93
+ * let directiveDefinitionObject = {
94
+ * {@link $compile#-priority- priority}: 0,
95
+ * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... },
96
+ * // or
97
+ * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... },
98
+ * {@link $compile#-transclude- transclude}: false,
99
+ * {@link $compile#-restrict- restrict}: 'A',
100
+ * {@link $compile#-templatenamespace- templateNamespace}: 'html',
101
+ * {@link $compile#-scope- scope}: false,
102
+ * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
103
+ * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier',
104
+ * {@link $compile#-bindtocontroller- bindToController}: false,
105
+ * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
106
+ * {@link $compile#-multielement- multiElement}: false,
107
+ * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) {
108
+ * return {
109
+ * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
110
+ * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
111
+ * }
112
+ * // or
113
+ * // return function postLink( ... ) { ... }
114
+ * },
115
+ * // or
116
+ * // {@link $compile#-link- link}: {
117
+ * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
118
+ * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
119
+ * // }
120
+ * // or
121
+ * // {@link $compile#-link- link}: function postLink( ... ) { ... }
122
+ * };
123
+ * return directiveDefinitionObject;
124
+ * });
125
+ * ```
126
+ *
127
+ * <div class="alert alert-warning">
128
+ * **Note:** Any unspecified options will use the default value. You can see the default values below.
129
+ * </div>
130
+ *
131
+ * Therefore the above can be simplified as:
132
+ *
133
+ * ```js
134
+ * let myModule = angular.module(...);
135
+ *
136
+ * myModule.directive('directiveName', function factory(injectables) {
137
+ * let directiveDefinitionObject = {
138
+ * link: function postLink(scope, iElement, iAttrs) { ... }
139
+ * };
140
+ * return directiveDefinitionObject;
141
+ * // or
142
+ * // return function postLink(scope, iElement, iAttrs) { ... }
143
+ * });
144
+ * ```
145
+ *
146
+ * ### Life-cycle hooks
147
+ * Directive controllers can provide the following methods that are called by AngularJS at points in the life-cycle of the
148
+ * directive:
149
+ * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
150
+ * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
151
+ * this element). This is a good place to put initialization code for your controller.
152
+ * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
153
+ * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
154
+ * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
155
+ * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
156
+ * also be called when your bindings are initialized.
157
+ * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
158
+ * changes. Any actions that you wish to take in response to the changes that you detect must be
159
+ * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
160
+ * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
161
+ * be detected by AngularJS's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
162
+ * if detecting changes, you must store the previous value(s) for comparison to the current values.
163
+ * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
164
+ * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
165
+ * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
166
+ * components will have their `$onDestroy()` hook called before child components.
167
+ * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
168
+ * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
169
+ * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
170
+ * they are waiting for their template to load asynchronously and their own compilation and linking has been
171
+ * suspended until that occurs.
172
+ *
173
+ * #### Comparison with life-cycle hooks in the new Angular
174
+ * The new Angular also uses life-cycle hooks for its components. While the AngularJS life-cycle hooks are similar there are
175
+ * some differences that you should be aware of, especially when it comes to moving your code from AngularJS to Angular:
176
+ *
177
+ * * AngularJS hooks are prefixed with `$`, such as `$onInit`. Angular hooks are prefixed with `ng`, such as `ngOnInit`.
178
+ * * AngularJS hooks can be defined on the controller prototype or added to the controller inside its constructor.
179
+ * In Angular you can only define hooks on the prototype of the Component class.
180
+ * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in AngularJS than you would to
181
+ * `ngDoCheck` in Angular.
182
+ * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
183
+ * propagated throughout the application.
184
+ * Angular does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
185
+ * error or do nothing depending upon the state of `enableProdMode()`.
186
+ *
187
+ * #### Life-cycle hook examples
188
+ *
189
+ * This example shows how you can check for mutations to a Date object even though the identity of the object
190
+ * has not changed.
191
+ *
192
+ * <example name="doCheckDateExample" module="do-check-module">
193
+ * <file name="app.js">
194
+ * angular.module('do-check-module', [])
195
+ * .component('app', {
196
+ * template:
197
+ * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
198
+ * 'Date: {{ $ctrl.date }}' +
199
+ * '<test date="$ctrl.date"></test>',
200
+ * controller: function() {
201
+ * this.date = new Date();
202
+ * this.month = this.date.getMonth();
203
+ * this.updateDate = function() {
204
+ * this.date.setMonth(this.month);
205
+ * };
206
+ * }
207
+ * })
208
+ * .component('test', {
209
+ * bindings: { date: '<' },
210
+ * template:
211
+ * '<pre>{{ $ctrl.log | json }}</pre>',
212
+ * controller: function() {
213
+ * let previousValue;
214
+ * this.log = [];
215
+ * this.$doCheck = function() {
216
+ * let currentValue = this.date && this.date.valueOf();
217
+ * if (previousValue !== currentValue) {
218
+ * this.log.push('doCheck: date mutated: ' + this.date);
219
+ * previousValue = currentValue;
220
+ * }
221
+ * };
222
+ * }
223
+ * });
224
+ * </file>
225
+ * <file name="index.html">
226
+ * <app></app>
227
+ * </file>
228
+ * </example>
229
+ *
230
+ * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
231
+ * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
232
+ * arrays or objects can have a negative impact on your application performance.)
233
+ *
234
+ * <example name="doCheckArrayExample" module="do-check-module">
235
+ * <file name="index.html">
236
+ * <div ng-init="items = []">
237
+ * <button ng-click="items.push(items.length)">Add Item</button>
238
+ * <button ng-click="items = []">Reset Items</button>
239
+ * <pre>{{ items }}</pre>
240
+ * <test items="items"></test>
241
+ * </div>
242
+ * </file>
243
+ * <file name="app.js">
244
+ * angular.module('do-check-module', [])
245
+ * .component('test', {
246
+ * bindings: { items: '<' },
247
+ * template:
248
+ * '<pre>{{ $ctrl.log | json }}</pre>',
249
+ * controller: function() {
250
+ * this.log = [];
251
+ *
252
+ * this.$doCheck = function() {
253
+ * if (this.items_ref !== this.items) {
254
+ * this.log.push('doCheck: items changed');
255
+ * this.items_ref = this.items;
256
+ * }
257
+ * if (!angular.equals(this.items_clone, this.items)) {
258
+ * this.log.push('doCheck: items mutated');
259
+ * this.items_clone = structuredClone(this.items);
260
+ * }
261
+ * };
262
+ * }
263
+ * });
264
+ * </file>
265
+ * </example>
266
+ *
267
+ *
268
+ * ### Directive Definition Object
269
+ *
270
+ * The directive definition object provides instructions to the {@link ng.$compile
271
+ * compiler}. The attributes are:
272
+ *
273
+ * #### `multiElement`
274
+ * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between
275
+ * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
276
+ * together as the directive elements. It is recommended that this feature be used on directives
277
+ * which are not strictly behavioral (such as {@link ngClick}), and which
278
+ * do not manipulate or replace child nodes (such as {@link ngInclude}).
279
+ *
280
+ * #### `priority`
281
+ * When there are multiple directives defined on a single DOM element, sometimes it
282
+ * is necessary to specify the order in which the directives are applied. The `priority` is used
283
+ * to sort the directives before their `compile` functions get called. Priority is defined as a
284
+ * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
285
+ * are also run in priority order, but post-link functions are run in reverse order. The order
286
+ * of directives with the same priority is undefined. The default priority is `0`.
287
+ *
288
+ * #### `terminal`
289
+ * If set to true then the current `priority` will be the last set of directives
290
+ * which will execute (any directives at the current priority will still execute
291
+ * as the order of execution on same `priority` is undefined). Note that expressions
292
+ * and other directives used in the directive's template will also be excluded from execution.
293
+ *
294
+ * #### `scope`
295
+ * The scope property can be `false`, `true`, or an object:
296
+ *
297
+ * * **`false` (default):** No scope will be created for the directive. The directive will use its
298
+ * parent's scope.
299
+ *
300
+ * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
301
+ * the directive's element. If multiple directives on the same element request a new scope,
302
+ * only one new scope is created.
303
+ *
304
+ * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template.
305
+ * The 'isolate' scope differs from normal scope in that it does not prototypically
306
+ * inherit from its parent scope. This is useful when creating reusable components, which should not
307
+ * accidentally read or modify data in the parent scope. Note that an isolate scope
308
+ * directive without a `template` or `templateUrl` will not apply the isolate scope
309
+ * to its children elements.
310
+ *
311
+ * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
312
+ * directive's element. These local properties are useful for aliasing values for templates. The keys in
313
+ * the object hash map to the name of the property on the isolate scope; the values define how the property
314
+ * is bound to the parent scope, via matching attributes on the directive's element:
315
+ *
316
+ * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
317
+ * always a string since DOM attributes are strings. If no `attr` name is specified then the
318
+ * attribute name is assumed to be the same as the local name. Given `<my-component
319
+ * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
320
+ * the directive's scope property `localName` will reflect the interpolated value of `hello
321
+ * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
322
+ * scope. The `name` is read from the parent scope (not the directive's scope).
323
+ *
324
+ * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
325
+ * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
326
+ * If no `attr` name is specified then the attribute name is assumed to be the same as the local
327
+ * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
328
+ * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
329
+ * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
330
+ * `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute
331
+ * isn't optional and doesn't exist, an exception
332
+ * ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes
333
+ * to the local value, since it will be impossible to sync them back to the parent scope.
334
+ *
335
+ * By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
336
+ * method is used for tracking changes, and the equality check is based on object identity.
337
+ * However, if an object literal or an array literal is passed as the binding expression, the
338
+ * equality check is done by value (using the {@link angular.equals} function). It's also possible
339
+ * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
340
+ * `$watchCollection`}: use `=*` or `=*attr`
341
+ *
342
+ * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
343
+ * expression passed via the attribute `attr`. The expression is evaluated in the context of the
344
+ * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
345
+ * local name.
346
+ *
347
+ * For example, given `<my-component my-attr="parentModel">` and directive definition of
348
+ * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
349
+ * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
350
+ * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
351
+ * two caveats:
352
+ * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
353
+ * sets the same value. That means if your bound value is an object, changes to its properties
354
+ * in the isolated scope will be reflected in the parent scope (because both reference the same object).
355
+ * 2. one-way binding watches changes to the **identity** of the parent value. That means the
356
+ * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
357
+ * to the value has changed. In most cases, this should not be of concern, but can be important
358
+ * to know if you one-way bind to an object, and then replace that object in the isolated scope.
359
+ * If you now change a property of the object in your parent scope, the change will not be
360
+ * propagated to the isolated scope, because the identity of the object on the parent scope
361
+ * has not changed. Instead you must assign a new object.
362
+ *
363
+ * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
364
+ * back to the parent. However, it does not make this completely impossible.
365
+ *
366
+ * By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
367
+ * method is used for tracking changes, and the equality check is based on object identity.
368
+ * It's also possible to watch the evaluated value shallowly with
369
+ * {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr`
370
+ *
371
+ * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
372
+ * no `attr` name is specified then the attribute name is assumed to be the same as the local name.
373
+ * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
374
+ * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
375
+ * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
376
+ * via an expression to the parent scope. This can be done by passing a map of local variable names
377
+ * and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
378
+ * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
379
+ *
380
+ * All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression.
381
+ * The marker must come after the mode and before the attribute name.
382
+ * See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples.
383
+ * This is useful to refine the interface directives provide.
384
+ * One subtle difference between optional and non-optional happens **when the binding attribute is not
385
+ * set**:
386
+ * - the binding is optional: the property will not be defined
387
+ * - the binding is not optional: the property is defined
388
+ *
389
+ * ```js
390
+ *app.directive('testDir', function() {
391
+ return {
392
+ scope: {
393
+ notoptional: '=',
394
+ optional: '=?',
395
+ },
396
+ bindToController: true,
397
+ controller: function() {
398
+ this.$onInit = function() {
399
+ console.log(this.hasOwnProperty('notoptional')) // true
400
+ console.log(this.hasOwnProperty('optional')) // false
401
+ }
402
+ }
403
+ }
404
+ })
405
+ *```
406
+ *
407
+ *
408
+ * ##### Combining directives with different scope defintions
409
+ *
410
+ * In general it's possible to apply more than one directive to one element, but there might be limitations
411
+ * depending on the type of scope required by the directives. The following points will help explain these limitations.
412
+ * For simplicity only two directives are taken into account, but it is also applicable for several directives:
413
+ *
414
+ * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
415
+ * * **child scope** + **no scope** => Both directives will share one single child scope
416
+ * * **child scope** + **child scope** => Both directives will share one single child scope
417
+ * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
418
+ * its parent's scope
419
+ * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
420
+ * be applied to the same element.
421
+ * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
422
+ * cannot be applied to the same element.
423
+ *
424
+ *
425
+ * #### `bindToController`
426
+ * This property is used to bind scope properties directly to the controller. It can be either
427
+ * `true` or an object hash with the same format as the `scope` property.
428
+ *
429
+ * When an isolate scope is used for a directive (see above), `bindToController: true` will
430
+ * allow a component to have its properties bound to the controller, rather than to scope.
431
+ *
432
+ * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
433
+ * properties. You can access these bindings once they have been initialized by providing a controller method called
434
+ * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
435
+ * initialized.
436
+ *
437
+ * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
438
+ * This will set up the scope bindings to the controller directly. Note that `scope` can still be used
439
+ * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
440
+ * scope (useful for component directives).
441
+ *
442
+ * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
443
+ *
444
+ *
445
+ * #### `controller`
446
+ * Controller constructor function. The controller is instantiated before the
447
+ * pre-linking phase and can be accessed by other directives (see
448
+ * `require` attribute). This allows the directives to communicate with each other and augment
449
+ * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
450
+ *
451
+ * * `$scope` - Current scope associated with the element
452
+ * * `$element` - Current element
453
+ * * `$attrs` - Current attributes object for the element
454
+ * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
455
+ * `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
456
+ * * `scope`: (optional) override the scope.
457
+ * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
458
+ * * `futureParentElement` (optional):
459
+ * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
460
+ * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
461
+ * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
462
+ * and when the `cloneLinkingFn` is passed,
463
+ * as those elements need to created and cloned in a special way when they are defined outside their
464
+ * usual containers (e.g. like `<svg>`).
465
+ * * See also the `directive.templateNamespace` property.
466
+ * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
467
+ * then the default transclusion is provided.
468
+ * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
469
+ * `true` if the specified slot contains content (i.e. one or more DOM nodes).
470
+ *
471
+ * #### `require`
472
+ * Require another directive and inject its controller as the fourth argument to the linking function. The
473
+ * `require` property can be a string, an array or an object:
474
+ * * a **string** containing the name of the directive to pass to the linking function
475
+ * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
476
+ * linking function will be an array of controllers in the same order as the names in the `require` property
477
+ * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
478
+ * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
479
+ * controllers.
480
+ *
481
+ * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
482
+ * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
483
+ * have been constructed but before `$onInit` is called.
484
+ * If the name of the required controller is the same as the local name (the key), the name can be
485
+ * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
486
+ * See the {@link $compileProvider#component} helper for an example of how this can be used.
487
+ * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
488
+ * raised (unless no link function is specified and the required controllers are not being bound to the directive
489
+ * controller, in which case error checking is skipped). The name can be prefixed with:
490
+ *
491
+ * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
492
+ * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
493
+ * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
494
+ * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
495
+ * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
496
+ * `null` to the `link` fn if not found.
497
+ * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
498
+ * `null` to the `link` fn if not found.
499
+ *
500
+ *
501
+ * #### `controllerAs`
502
+ * Identifier name for a reference to the controller in the directive's scope.
503
+ * This allows the controller to be referenced from the directive template. This is especially
504
+ * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
505
+ * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
506
+ * `controllerAs` reference might overwrite a property that already exists on the parent scope.
507
+ *
508
+ *
509
+ * #### `restrict`
510
+ * String of subset of `EACM` which restricts the directive to a specific directive
511
+ * declaration style. If omitted, the defaults (elements and attributes) are used.
512
+ *
513
+ * * `E` - Element name (default): `<my-directive></my-directive>`
514
+ * * `A` - Attribute (default): `<div my-directive="exp"></div>`
515
+ * * `C` - Class: `<div class="my-directive: exp;"></div>`
516
+ * * `M` - Comment: `<!-- directive: my-directive exp -->`
517
+ *
518
+ *
519
+ * #### `templateNamespace`
520
+ * String representing the document type used by the markup in the template.
521
+ * AngularJS needs this information as those elements need to be created and cloned
522
+ * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
523
+ *
524
+ * * `html` - All root nodes in the template are HTML. Root nodes may also be
525
+ * top-level elements such as `<svg>` or `<math>`.
526
+ * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
527
+ * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
528
+ *
529
+ * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
530
+ *
531
+ * #### `template`
532
+ * HTML markup that may:
533
+ * * Replace the contents of the directive's element (default).
534
+ * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
535
+ * * Wrap the contents of the directive's element (if `transclude` is true).
536
+ *
537
+ * Value may be:
538
+ *
539
+ * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
540
+ * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
541
+ * function api below) and returns a string value.
542
+ *
543
+ *
544
+ * #### `templateUrl`
545
+ * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
546
+ *
547
+ * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
548
+ * for later when the template has been resolved. In the meantime it will continue to compile and link
549
+ * sibling and parent elements as though this element had not contained any directives.
550
+ *
551
+ * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
552
+ * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
553
+ * case when only one deeply nested directive has `templateUrl`.
554
+ *
555
+ * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}.
556
+ *
557
+ * You can specify `templateUrl` as a string representing the URL or as a function which takes two
558
+ * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
559
+ * a string value representing the url. In either case, the template URL is passed through {@link
560
+ * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
561
+ *
562
+ *
563
+ * #### `replace`
564
+ * <div class="alert alert-danger">
565
+ * **Note:** `replace` is deprecated in AngularJS and has been removed in the new Angular (v2+).
566
+ * </div>
567
+ *
568
+ * Specifies what the template should replace. Defaults to `false`.
569
+ *
570
+ * * `true` - the template will replace the directive's element.
571
+ * * `false` - the template will replace the contents of the directive's element.
572
+ *
573
+ * The replacement process migrates all of the attributes / classes from the old element to the new
574
+ * one. See the {@link guide/directive#template-expanding-directive
575
+ * Directives Guide} for an example.
576
+ *
577
+ * There are very few scenarios where element replacement is required for the application function,
578
+ * the main one being reusable custom components that are used within SVG contexts
579
+ * (because SVG doesn't work with custom elements in the DOM tree).
580
+ *
581
+ * #### `transclude`
582
+ * Extract the contents of the element where the directive appears and make it available to the directive.
583
+ * The contents are compiled and provided to the directive as a **transclusion function**. See the
584
+ * {@link $compile#transclusion Transclusion} section below.
585
+ *
586
+ *
587
+ * #### `compile`
588
+ *
589
+ * ```js
590
+ * function compile(tElement, tAttrs, transclude) { ... }
591
+ * ```
592
+ *
593
+ * The compile function deals with transforming the template DOM. Since most directives do not do
594
+ * template transformation, it is not used often. The compile function takes the following arguments:
595
+ *
596
+ * * `tElement` - template element - The element where the directive has been declared. It is
597
+ * safe to do template transformation on the element and child elements only.
598
+ *
599
+ * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
600
+ * between all directive compile functions.
601
+ *
602
+ * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
603
+ *
604
+ * <div class="alert alert-warning">
605
+ * **Note:** The template instance and the link instance may be different objects if the template has
606
+ * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
607
+ * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
608
+ * should be done in a linking function rather than in a compile function.
609
+ * </div>
610
+
611
+ * <div class="alert alert-warning">
612
+ * **Note:** The compile function cannot handle directives that recursively use themselves in their
613
+ * own templates or compile functions. Compiling these directives results in an infinite loop and
614
+ * stack overflow errors.
615
+ *
616
+ * This can be avoided by manually using `$compile` in the postLink function to imperatively compile
617
+ * a directive's template instead of relying on automatic template compilation via `template` or
618
+ * `templateUrl` declaration or manual compilation inside the compile function.
619
+ * </div>
620
+ *
621
+ * <div class="alert alert-danger">
622
+ * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
623
+ * e.g. does not know about the right outer scope. Please use the transclude function that is passed
624
+ * to the link function instead.
625
+ * </div>
626
+
627
+ * A compile function can have a return value which can be either a function or an object.
628
+ *
629
+ * * returning a (post-link) function - is equivalent to registering the linking function via the
630
+ * `link` property of the config object when the compile function is empty.
631
+ *
632
+ * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
633
+ * control when a linking function should be called during the linking phase. See info about
634
+ * pre-linking and post-linking functions below.
635
+ *
636
+ *
637
+ * #### `link`
638
+ * This property is used only if the `compile` property is not defined.
639
+ *
640
+ * ```js
641
+ * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
642
+ * ```
643
+ *
644
+ * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
645
+ * executed after the template has been cloned. This is where most of the directive logic will be
646
+ * put.
647
+ *
648
+ * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
649
+ * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
650
+ *
651
+ * * `iElement` - instance element - The element where the directive is to be used. It is safe to
652
+ * manipulate the children of the element only in `postLink` function since the children have
653
+ * already been linked.
654
+ *
655
+ * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
656
+ * between all directive linking functions.
657
+ *
658
+ * * `controller` - the directive's required controller instance(s) - Instances are shared
659
+ * among all directives, which allows the directives to use the controllers as a communication
660
+ * channel. The exact value depends on the directive's `require` property:
661
+ * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
662
+ * * `string`: the controller instance
663
+ * * `array`: array of controller instances
664
+ *
665
+ * If a required controller cannot be found, and it is optional, the instance is `null`,
666
+ * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
667
+ *
668
+ * Note that you can also require the directive's own controller - it will be made available like
669
+ * any other controller.
670
+ *
671
+ * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
672
+ * This is the same as the `$transclude` parameter of directive controllers,
673
+ * see {@link ng.$compile#-controller- the controller section for details}.
674
+ * `function([scope], cloneLinkingFn, futureParentElement)`.
675
+ *
676
+ * #### Pre-linking function
677
+ *
678
+ * Executed before the child elements are linked. Not safe to do DOM transformation since the
679
+ * compiler linking function will fail to locate the correct elements for linking.
680
+ *
681
+ * #### Post-linking function
682
+ *
683
+ * Executed after the child elements are linked.
684
+ *
685
+ * Note that child elements that contain `templateUrl` directives will not have been compiled
686
+ * and linked since they are waiting for their template to load asynchronously and their own
687
+ * compilation and linking has been suspended until that occurs.
688
+ *
689
+ * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
690
+ * for their async templates to be resolved.
691
+ *
692
+ *
693
+ * ### Transclusion
694
+ *
695
+ * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
696
+ * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
697
+ * scope from where they were taken.
698
+ *
699
+ * Transclusion is used (often with {@link ngTransclude}) to insert the
700
+ * original contents of a directive's element into a specified place in the template of the directive.
701
+ * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
702
+ * content has access to the properties on the scope from which it was taken, even if the directive
703
+ * has isolated scope.
704
+ * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
705
+ *
706
+ * This makes it possible for the widget to have private state for its template, while the transcluded
707
+ * content has access to its originating scope.
708
+ *
709
+ * <div class="alert alert-warning">
710
+ * **Note:** When testing an element transclude directive you must not place the directive at the root of the
711
+ * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
712
+ * Testing Transclusion Directives}.
713
+ * </div>
714
+ *
715
+ * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
716
+ * directive's element, the entire element or multiple parts of the element contents:
717
+ *
718
+ * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
719
+ * * `'element'` - transclude the whole of the directive's element including any directives on this
720
+ * element that are defined at a lower priority than this directive. When used, the `template`
721
+ * property is ignored.
722
+ * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
723
+ *
724
+ * **Multi-slot transclusion** is declared by providing an object for the `transclude` property.
725
+ *
726
+ * This object is a map where the keys are the name of the slot to fill and the value is an element selector
727
+ * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
728
+ * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
729
+ *
730
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}.
731
+ *
732
+ * If the element selector is prefixed with a `?` then that slot is optional.
733
+ *
734
+ * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
735
+ * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
736
+ *
737
+ * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
738
+ * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
739
+ * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
740
+ * injectable into the directive's controller.
741
+ *
742
+ *
743
+ * #### Transclusion Functions
744
+ *
745
+ * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
746
+ * function** to the directive's `link` function and `controller`. This transclusion function is a special
747
+ * **linking function** that will return the compiled contents linked to a new transclusion scope.
748
+ *
749
+ * <div class="alert alert-info">
750
+ * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
751
+ * ngTransclude will deal with it for us.
752
+ * </div>
753
+ *
754
+ * If you want to manually control the insertion and removal of the transcluded content in your directive
755
+ * then you must use this transclude function. When you call a transclude function it returns a jqLite/JQuery
756
+ * object that contains the compiled DOM, which is linked to the correct transclusion scope.
757
+ *
758
+ * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
759
+ * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
760
+ * content and the `scope` is the newly created transclusion scope, which the clone will be linked to.
761
+ *
762
+ * <div class="alert alert-info">
763
+ * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
764
+ * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
765
+ * </div>
766
+ *
767
+ * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
768
+ * attach function**:
769
+ *
770
+ * ```js
771
+ * let transcludedContent, transclusionScope;
772
+ *
773
+ * $transclude(function(clone, scope) {
774
+ * element.append(clone);
775
+ * transcludedContent = clone;
776
+ * transclusionScope = scope;
777
+ * });
778
+ * ```
779
+ *
780
+ * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
781
+ * associated transclusion scope:
782
+ *
783
+ * ```js
784
+ * transcludedContent.remove();
785
+ * transclusionScope.$destroy();
786
+ * ```
787
+ *
788
+ * <div class="alert alert-info">
789
+ * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
790
+ * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
791
+ * then you are also responsible for calling `$destroy` on the transclusion scope.
792
+ * </div>
793
+ *
794
+ * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
795
+ * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
796
+ * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
797
+ *
798
+ *
799
+ * #### Transclusion Scopes
800
+ *
801
+ * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
802
+ * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
803
+ * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
804
+ * was taken.
805
+ *
806
+ * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
807
+ * like this:
808
+ *
809
+ * ```html
810
+ * <div ng-app>
811
+ * <div isolate>
812
+ * <div transclusion>
813
+ * </div>
814
+ * </div>
815
+ * </div>
816
+ * ```
817
+ *
818
+ * The `$parent` scope hierarchy will look like this:
819
+ *
820
+ ```
821
+ - $rootScope
822
+ - isolate
823
+ - transclusion
824
+ ```
825
+ *
826
+ * but the scopes will inherit prototypically from different scopes to their `$parent`.
827
+ *
828
+ ```
829
+ - $rootScope
830
+ - transclusion
831
+ - isolate
832
+ ```
833
+ *
834
+ *
835
+ * ### Attributes
836
+ *
837
+ * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
838
+ * `link()` or `compile()` functions. It has a variety of uses.
839
+ *
840
+ * * *Accessing normalized attribute names:* Directives like `ngBind` can be expressed in many ways:
841
+ * `ng:bind`, `data-ng-bind`, or `x-ng-bind`. The attributes object allows for normalized access
842
+ * to the attributes.
843
+ *
844
+ * * *Directive inter-communication:* All directives share the same instance of the attributes
845
+ * object which allows the directives to use the attributes object as inter directive
846
+ * communication.
847
+ *
848
+ * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
849
+ * allowing other directives to read the interpolated value.
850
+ *
851
+ * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
852
+ * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
853
+ * the only way to easily get the actual value because during the linking phase the interpolation
854
+ * hasn't been evaluated yet and so the value is at this time set to `undefined`.
855
+ *
856
+ * ```js
857
+ * function linkingFn(scope, elm, attrs, ctrl) {
858
+ * // get the attribute value
859
+ * console.log(attrs.ngModel);
860
+ *
861
+ * // change the attribute
862
+ * attrs.$set('ngModel', 'new value');
863
+ *
864
+ * // observe changes to interpolated attribute
865
+ * attrs.$observe('ngModel', function(value) {
866
+ * console.log('ngModel has changed value to ' + value);
867
+ * });
868
+ * }
869
+ * ```
870
+ *
871
+ *
872
+ *
873
+ * @param {string|Element} element Element or HTML string to compile into a template function.
874
+ * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
875
+ *
876
+ * <div class="alert alert-danger">
877
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
878
+ * e.g. will not use the right outer scope. Please pass the transclude function as a
879
+ * `parentBoundTranscludeFn` to the link function instead.
880
+ * </div>
881
+ *
882
+ * @param {number} maxPriority only apply directives lower than given priority (Only effects the
883
+ * root element(s), not their children)
884
+ * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
885
+ * (a DOM element/tree) to a scope. Where:
886
+ *
887
+ * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
888
+ * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
889
+ * `template` and call the `cloneAttachFn` function allowing the caller to attach the
890
+ * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
891
+ * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
892
+ *
893
+ * * `clonedElement` - is a clone of the original `element` passed into the compiler.
894
+ * * `scope` - is the current scope with which the linking function is working with.
895
+ *
896
+ * * `options` - An optional object hash with linking options. If `options` is provided, then the following
897
+ * keys may be used to control linking behavior:
898
+ *
899
+ * * `parentBoundTranscludeFn` - the transclude function made available to
900
+ * directives; if given, it will be passed through to the link functions of
901
+ * directives found in `element` during compilation.
902
+ * * `transcludeControllers` - an object hash with keys that map controller names
903
+ * to a hash with the key `instance`, which maps to the controller instance;
904
+ * if given, it will make the controllers available to directives on the compileNode:
905
+ * ```
906
+ * {
907
+ * parent: {
908
+ * instance: parentControllerInstance
909
+ * }
910
+ * }
911
+ * ```
912
+ * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
913
+ * the cloned elements; only needed for transcludes that are allowed to contain non HTML
914
+ * elements (e.g. SVG elements). See also the `directive.controller` property.
915
+ *
916
+ * Calling the linking function returns the element of the template. It is either the original
917
+ * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
918
+ *
919
+ * After linking the view is not updated until after a call to `$digest`, which typically is done by
920
+ * AngularJS automatically.
921
+ *
922
+ * If you need access to the bound view, there are two ways to do it:
923
+ *
924
+ * - If you are not asking the linking function to clone the template, create the DOM element(s)
925
+ * before you send them to the compiler and keep this reference around.
926
+ * ```js
927
+ * let element = angular.element('<p>{{total}}</p>');
928
+ * $compile(element)(scope);
929
+ * ```
930
+ *
931
+ * - if on the other hand, you need the element to be cloned, the view reference from the original
932
+ * example would not point to the clone, but rather to the original template that was cloned. In
933
+ * this case, you can access the clone either via the `cloneAttachFn` or the value returned by the
934
+ * linking function:
935
+ * ```js
936
+ * let templateElement = angular.element('<p>{{total}}</p>');
937
+ * let clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
938
+ * // Attach the clone to DOM document at the right place.
939
+ * });
940
+ *
941
+ * // Now we have reference to the cloned DOM via `clonedElement`.
942
+ * // NOTE: The `clonedElement` returned by the linking function is the same as the
943
+ * // `clonedElement` passed to `cloneAttachFn`.
944
+ * ```
945
+ *
946
+ *
947
+ * For information on how the compiler works, see the
948
+ * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide.
949
+ *
950
+ * @knownIssue
951
+ *
952
+ * ### Double Compilation
953
+ *
954
+ Double compilation occurs when an already compiled part of the DOM gets
955
+ compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
956
+ and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
957
+ section on double compilation} for an in-depth explanation and ways to avoid it.
958
+
959
+ * @knownIssue
960
+
961
+ ### Issues with `replace: true`
962
+ *
963
+ * <div class="alert alert-danger">
964
+ * **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use,
965
+ * mainly due to the issues listed here. It has been completely removed in the new Angular.
966
+ * </div>
967
+ *
968
+ * #### Attribute values are not merged
969
+ *
970
+ * When a `replace` directive encounters the same attribute on the original and the replace node,
971
+ * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of
972
+ * the `style` attribute.
973
+ * ```html
974
+ * Original Node: <span class="original" style="color: red;"></span>
975
+ * Replace Template: <span class="replaced" style="background: blue;"></span>
976
+ * Result: <span class="original replaced" style="color: red; background: blue;"></span>
977
+ * ```
978
+ *
979
+ * That means attributes that contain AngularJS expressions will not be merged correctly, e.g.
980
+ * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error:
981
+ *
982
+ * ```html
983
+ * Original Node: <span ng-class="{'something': something}" ng-show="!condition"></span>
984
+ * Replace Template: <span ng-class="{'else': else}" ng-show="otherCondition"></span>
985
+ * Result: <span ng-class="{'something': something} {'else': else}" ng-show="!condition otherCondition"></span>
986
+ * ```
987
+ *
988
+ * See issue [#5695](https://github.com/angular/angular.js/issues/5695).
989
+ *
990
+ * #### Directives are not deduplicated before compilation
991
+ *
992
+ * When the original node and the replace template declare the same directive(s), they will be
993
+ * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler
994
+ * does not deduplicate them. In many cases, this is not noticeable, but e.g. {@link ngModel} will
995
+ * attach `$formatters` and `$parsers` twice.
996
+ *
997
+ * See issue [#2573](https://github.com/angular/angular.js/issues/2573).
998
+ *
999
+ * #### `transclude: element` in the replace template root can have unexpected effects
1000
+ *
1001
+ * When the replace template has a directive at the root node that uses
1002
+ * {@link $compile#-transclude- `transclude: element`}, e.g.
1003
+ * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect.
1004
+ * See the following issues:
1005
+ *
1006
+ * - Incorrect scope on replaced element:
1007
+ * [#9837](https://github.com/angular/angular.js/issues/9837)
1008
+ * - Different DOM between `template` and `templateUrl`:
1009
+ * [#10612](https://github.com/angular/angular.js/issues/14326)
1010
+ *
1011
+ */
1012
+
1013
+ /**
1014
+ * @ngdoc directive
1015
+ * @name ngProp
1016
+ * @restrict A
1017
+ * @element ANY
1018
+ *
1019
+ * @usage
1020
+ *
1021
+ * ```html
1022
+ * <ANY ng-prop-propname="expression">
1023
+ * </ANY>
1024
+ * ```
1025
+ *
1026
+ * or with uppercase letters in property (e.g. "propName"):
1027
+ *
1028
+ *
1029
+ * ```html
1030
+ * <ANY ng-prop-prop_name="expression">
1031
+ * </ANY>
1032
+ * ```
1033
+ *
1034
+ *
1035
+ * @description
1036
+ * The `ngProp` directive binds an expression to a DOM element property.
1037
+ * `ngProp` allows writing to arbitrary properties by including
1038
+ * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to
1039
+ * the `value` property.
1040
+ *
1041
+ * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives
1042
+ * handle the most common use cases (instead of the above example, you would use {@link ngValue}).
1043
+ *
1044
+ * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements)
1045
+ * often use custom properties to hold data, and `ngProp` can be used to provide input to these
1046
+ * custom elements.
1047
+ *
1048
+ * ## Binding to camelCase properties
1049
+ *
1050
+ * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped.
1051
+ * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
1052
+ * `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an
1053
+ * example, and for binding HTML {@link ngBindHtml} should be used.
1054
+ *
1055
+ * ## Security
1056
+ *
1057
+ * Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML`
1058
+ * can insert potentially dangerous HTML into the application, e.g. script tags that execute
1059
+ * malicious code.
1060
+ * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}.
1061
+ * This means vulnerable properties require their content to be "trusted", based on the
1062
+ * context of the property. For example, the `innerHTML` is in the `HTML` context, and the
1063
+ * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to
1064
+ * this property are trusted as a `RESOURCE_URL`.
1065
+ *
1066
+ * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is
1067
+ * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for
1068
+ * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al.
1069
+ *
1070
+ * In some cases you can also rely upon automatic sanitization of untrusted values - see below.
1071
+ *
1072
+ * Based on the context, other options may exist to mark a value as trusted / configure the behavior
1073
+ * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use
1074
+ * the {@link $sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList()}
1075
+ * and {@link $sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList()}.
1076
+ *
1077
+ * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}.
1078
+ *
1079
+ * ### HTML Sanitization
1080
+ *
1081
+ * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the
1082
+ * content.
1083
+ * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the
1084
+ * potentially dangerous HTML, e.g. strip non-trusted tags and attributes when binding to
1085
+ * `innerHTML`.
1086
+ *
1087
+ *
1088
+ */
1089
+
1090
+ /** @ngdoc directive
1091
+ * @name ngOn
1092
+ * @restrict A
1093
+ * @element ANY
1094
+ *
1095
+ * @usage
1096
+ *
1097
+ * ```html
1098
+ * <ANY ng-on-eventname="expression">
1099
+ * </ANY>
1100
+ * ```
1101
+ *
1102
+ * or with uppercase letters in property (e.g. "eventName"):
1103
+ *
1104
+ *
1105
+ * ```html
1106
+ * <ANY ng-on-event_name="expression">
1107
+ * </ANY>
1108
+ * ```
1109
+ *
1110
+ * @description
1111
+ * The `ngOn` directive adds an event listener to a DOM element via
1112
+ * {@link angular.element angular.element().on()}, and evaluates an expression when the event is
1113
+ * fired.
1114
+ * `ngOn` allows adding listeners for arbitrary events by including
1115
+ * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression
1116
+ * when the `drop` event is fired.
1117
+ *
1118
+ * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most
1119
+ * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events
1120
+ * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM
1121
+ * standards.
1122
+ *
1123
+ * Another use-case for `ngOn` is listening to
1124
+ * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
1125
+ * fired by
1126
+ * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements).
1127
+ *
1128
+ * ## Binding to camelCase properties
1129
+ *
1130
+ * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped.
1131
+ * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
1132
+ * `myEvent` must be written as `ng-on-my_event="expression"`.
1133
+ *
1134
+ *
1135
+ */
1136
+
1137
+ const $compileMinErr = minErr("$compile");
1138
+
1139
+ function UNINITIALIZED_VALUE() {}
1140
+ const _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
1141
+ let config = { TTL: 10 };
1142
+
1143
+ /**
1144
+ * @ngdoc provider
1145
+ * @name $compileProvider
1146
+ *
1147
+ * @description
1148
+ */
1149
+ // eslint-disable-next-line no-use-before-define
1150
+ $CompileProvider.$inject = ["$provide", "$$sanitizeUriProvider"];
1151
+
1152
+ export function $CompileProvider($provide, $$sanitizeUriProvider) {
1153
+ const hasDirectives = {};
1154
+ const Suffix = "Directive";
1155
+ const COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/;
1156
+ const CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/;
1157
+ const ALL_OR_NOTHING_ATTRS = {
1158
+ ngSrc: true,
1159
+ ngSrcset: true,
1160
+ src: true,
1161
+ srcset: true,
1162
+ };
1163
+ const REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
1164
+
1165
+ // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
1166
+ // The assumption is that future DOM event attribute names will begin with
1167
+ // 'on' and be composed of only English letters.
1168
+ const EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
1169
+ const bindingCache = createMap();
1170
+
1171
+ function parseIsolateBindings(scope, directiveName, isController) {
1172
+ const LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/;
1173
+
1174
+ const bindings = createMap();
1175
+
1176
+ forEach(scope, (definition, scopeName) => {
1177
+ definition = definition.trim();
1178
+
1179
+ if (definition in bindingCache) {
1180
+ bindings[scopeName] = bindingCache[definition];
1181
+ return;
1182
+ }
1183
+ const match = definition.match(LOCAL_REGEXP);
1184
+
1185
+ if (!match) {
1186
+ throw $compileMinErr(
1187
+ "iscp",
1188
+ "Invalid {3} for directive '{0}'." +
1189
+ " Definition: {... {1}: '{2}' ...}",
1190
+ directiveName,
1191
+ scopeName,
1192
+ definition,
1193
+ isController
1194
+ ? "controller bindings definition"
1195
+ : "isolate scope definition",
1196
+ );
1197
+ }
1198
+
1199
+ bindings[scopeName] = {
1200
+ mode: match[1][0],
1201
+ collection: match[2] === "*",
1202
+ optional: match[3] === "?",
1203
+ attrName: match[4] || scopeName,
1204
+ };
1205
+ if (match[4]) {
1206
+ bindingCache[definition] = bindings[scopeName];
1207
+ }
1208
+ });
1209
+
1210
+ return bindings;
1211
+ }
1212
+
1213
+ function parseDirectiveBindings(directive, directiveName) {
1214
+ const bindings = {
1215
+ isolateScope: null,
1216
+ bindToController: null,
1217
+ };
1218
+ if (isObject(directive.scope)) {
1219
+ if (directive.bindToController === true) {
1220
+ bindings.bindToController = parseIsolateBindings(
1221
+ directive.scope,
1222
+ directiveName,
1223
+ true,
1224
+ );
1225
+ bindings.isolateScope = {};
1226
+ } else {
1227
+ bindings.isolateScope = parseIsolateBindings(
1228
+ directive.scope,
1229
+ directiveName,
1230
+ false,
1231
+ );
1232
+ }
1233
+ }
1234
+ if (isObject(directive.bindToController)) {
1235
+ bindings.bindToController = parseIsolateBindings(
1236
+ directive.bindToController,
1237
+ directiveName,
1238
+ true,
1239
+ );
1240
+ }
1241
+ if (bindings.bindToController && !directive.controller) {
1242
+ // There is no controller
1243
+ throw $compileMinErr(
1244
+ "noctrl",
1245
+ "Cannot bind to controller without directive '{0}'s controller.",
1246
+ directiveName,
1247
+ );
1248
+ }
1249
+ return bindings;
1250
+ }
1251
+
1252
+ function assertValidDirectiveName(name) {
1253
+ const letter = name.charAt(0);
1254
+ if (!letter || letter !== lowercase(letter)) {
1255
+ throw $compileMinErr(
1256
+ "baddir",
1257
+ "Directive/Component name '{0}' is invalid. The first character must be a lowercase letter",
1258
+ name,
1259
+ );
1260
+ }
1261
+ if (name !== name.trim()) {
1262
+ throw $compileMinErr(
1263
+ "baddir",
1264
+ "Directive/Component name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
1265
+ name,
1266
+ );
1267
+ }
1268
+ }
1269
+
1270
+ function getDirectiveRequire(directive) {
1271
+ const require =
1272
+ directive.require || (directive.controller && directive.name);
1273
+
1274
+ if (!isArray(require) && isObject(require)) {
1275
+ forEach(require, (value, key) => {
1276
+ const match = value.match(REQUIRE_PREFIX_REGEXP);
1277
+ const name = value.substring(match[0].length);
1278
+ if (!name) require[key] = match[0] + key;
1279
+ });
1280
+ }
1281
+
1282
+ return require;
1283
+ }
1284
+
1285
+ function getDirectiveRestrict(restrict, name) {
1286
+ if (restrict && !(isString(restrict) && /[EA]/.test(restrict))) {
1287
+ throw $compileMinErr(
1288
+ "badrestrict",
1289
+ "Restrict property '{0}' of directive '{1}' is invalid",
1290
+ restrict,
1291
+ name,
1292
+ );
1293
+ }
1294
+ // Default is element or attribute
1295
+ return restrict || "EA";
1296
+ }
1297
+
1298
+ /**
1299
+ * @ngdoc method
1300
+ * @name $compileProvider#directive
1301
+ * @kind function
1302
+ *
1303
+ * @description
1304
+ * Register a new directive with the compiler.
1305
+ *
1306
+ * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match
1307
+ * as `ng-bind`), or an object map of directives where the keys are the names and the values
1308
+ * are the factories.
1309
+ * @param {Function|Array} directiveFactory An injectable directive factory function. See the
1310
+ * {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
1311
+ * @returns {ng.$compileProvider} Self for chaining.
1312
+ */
1313
+ this.directive = function registerDirective(name, directiveFactory) {
1314
+ assertArg(name, "name");
1315
+ assertNotHasOwnProperty(name, "directive");
1316
+ if (isString(name)) {
1317
+ assertValidDirectiveName(name);
1318
+ assertArg(directiveFactory, "directiveFactory");
1319
+ if (!Object.prototype.hasOwnProperty.call(hasDirectives, name)) {
1320
+ hasDirectives[name] = [];
1321
+ $provide.factory(name + Suffix, [
1322
+ "$injector",
1323
+ "$exceptionHandler",
1324
+ function ($injector, $exceptionHandler) {
1325
+ const directives = [];
1326
+ forEach(hasDirectives[name], (directiveFactory, index) => {
1327
+ try {
1328
+ let directive = $injector.invoke(directiveFactory);
1329
+ if (isFunction(directive)) {
1330
+ directive = { compile: valueFn(directive) };
1331
+ } else if (!directive.compile && directive.link) {
1332
+ directive.compile = valueFn(directive.link);
1333
+ }
1334
+ directive.priority = directive.priority || 0;
1335
+ directive.index = index;
1336
+ directive.name = directive.name || name;
1337
+ directive.require = getDirectiveRequire(directive);
1338
+ directive.restrict = getDirectiveRestrict(
1339
+ directive.restrict,
1340
+ name,
1341
+ );
1342
+ directive.$$moduleName = directiveFactory.$$moduleName;
1343
+ directives.push(directive);
1344
+ } catch (e) {
1345
+ $exceptionHandler(e);
1346
+ }
1347
+ });
1348
+ return directives;
1349
+ },
1350
+ ]);
1351
+ }
1352
+ hasDirectives[name].push(directiveFactory);
1353
+ } else {
1354
+ forEach(name, reverseParams(registerDirective));
1355
+ }
1356
+ return this;
1357
+ };
1358
+
1359
+ /**
1360
+ * @ngdoc method
1361
+ * @name $compileProvider#component
1362
+ * @module ng
1363
+ * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
1364
+ * or an object map of components where the keys are the names and the values are the component definition objects.
1365
+ * @param {Object} options Component definition object (a simplified
1366
+ * {@link ng.$compile#directive-definition-object directive definition object}),
1367
+ * with the following properties (all optional):
1368
+ *
1369
+ * - `controller` – `{(string|function()=}` – controller constructor function that should be
1370
+ * associated with newly created scope or the name of a {@link ng.$compile#-controller-
1371
+ * registered controller} if passed as a string. An empty `noop` function by default.
1372
+ * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
1373
+ * If present, the controller will be published to scope under the `controllerAs` name.
1374
+ * If not present, this will default to be `$ctrl`.
1375
+ * - `template` – `{string=|function()=}` – html template as a string or a function that
1376
+ * returns an html template as a string which should be used as the contents of this component.
1377
+ * Empty string by default.
1378
+ *
1379
+ * If `template` is a function, then it is {@link auto.$injector#invoke injected} with
1380
+ * the following locals:
1381
+ *
1382
+ * - `$element` - Current element
1383
+ * - `$attrs` - Current attributes object for the element
1384
+ *
1385
+ * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
1386
+ * template that should be used as the contents of this component.
1387
+ *
1388
+ * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
1389
+ * the following locals:
1390
+ *
1391
+ * - `$element` - Current element
1392
+ * - `$attrs` - Current attributes object for the element
1393
+ *
1394
+ * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
1395
+ * Component properties are always bound to the component controller and not to the scope.
1396
+ * See {@link ng.$compile#-bindtocontroller- `bindToController`}.
1397
+ * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
1398
+ * Disabled by default.
1399
+ * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to
1400
+ * this component's controller. The object keys specify the property names under which the required
1401
+ * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}.
1402
+ * - `$...` – additional properties to attach to the directive factory function and the controller
1403
+ * constructor function. (This is used by the component router to annotate)
1404
+ *
1405
+ * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
1406
+ * @description
1407
+ * Register a **component definition** with the compiler. This is a shorthand for registering a special
1408
+ * type of directive, which represents a self-contained UI component in your application. Such components
1409
+ * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
1410
+ *
1411
+ * Component definitions are very simple and do not require as much configuration as defining general
1412
+ * directives. Component definitions usually consist only of a template and a controller backing it.
1413
+ *
1414
+ * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
1415
+ * `bindToController`. They always have **isolate scope** and are restricted to elements.
1416
+ *
1417
+ * Here are a few examples of how you would usually define components:
1418
+ *
1419
+ * ```js
1420
+ * let myMod = angular.module(...);
1421
+ * myMod.component('myComp', {
1422
+ * template: '<div>My name is {{$ctrl.name}}</div>',
1423
+ * controller: function() {
1424
+ * this.name = 'shahar';
1425
+ * }
1426
+ * });
1427
+ *
1428
+ * myMod.component('myComp', {
1429
+ * template: '<div>My name is {{$ctrl.name}}</div>',
1430
+ * bindings: {name: '@'}
1431
+ * });
1432
+ *
1433
+ * myMod.component('myComp', {
1434
+ * templateUrl: 'views/my-comp.html',
1435
+ * controller: 'MyCtrl',
1436
+ * controllerAs: 'ctrl',
1437
+ * bindings: {name: '@'}
1438
+ * });
1439
+ *
1440
+ * ```
1441
+ * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
1442
+ *
1443
+ * <br />
1444
+ * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
1445
+ */
1446
+ this.component = function registerComponent(name, options) {
1447
+ if (!isString(name)) {
1448
+ forEach(name, reverseParams(bind(this, registerComponent)));
1449
+ return this;
1450
+ }
1451
+
1452
+ const controller = options.controller || function () {};
1453
+
1454
+ function factory($injector) {
1455
+ function makeInjectable(fn) {
1456
+ if (isFunction(fn) || isArray(fn)) {
1457
+ return function (tElement, tAttrs) {
1458
+ return $injector.invoke(fn, this, {
1459
+ $element: tElement,
1460
+ $attrs: tAttrs,
1461
+ });
1462
+ };
1463
+ }
1464
+ return fn;
1465
+ }
1466
+
1467
+ const template =
1468
+ !options.template && !options.templateUrl ? "" : options.template;
1469
+ const ddo = {
1470
+ controller,
1471
+ controllerAs:
1472
+ identifierForController(options.controller) ||
1473
+ options.controllerAs ||
1474
+ "$ctrl",
1475
+ template: makeInjectable(template),
1476
+ templateUrl: makeInjectable(options.templateUrl),
1477
+ transclude: options.transclude,
1478
+ scope: {},
1479
+ bindToController: options.bindings || {},
1480
+ restrict: "E",
1481
+ require: options.require,
1482
+ };
1483
+
1484
+ // Copy annotations (starting with $) over to the DDO
1485
+ forEach(options, (val, key) => {
1486
+ if (key.charAt(0) === "$") ddo[key] = val;
1487
+ });
1488
+
1489
+ return ddo;
1490
+ }
1491
+
1492
+ // TODO(pete) remove the following `forEach` before we release 1.6.0
1493
+ // The component-router@0.2.0 looks for the annotations on the controller constructor
1494
+ // Nothing in AngularJS looks for annotations on the factory function but we can't remove
1495
+ // it from 1.5.x yet.
1496
+
1497
+ // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
1498
+ // These could be used by libraries such as the new component router
1499
+ forEach(options, (val, key) => {
1500
+ if (key.charAt(0) === "$") {
1501
+ factory[key] = val;
1502
+ // Don't try to copy over annotations to named controller
1503
+ if (isFunction(controller)) controller[key] = val;
1504
+ }
1505
+ });
1506
+
1507
+ factory.$inject = ["$injector"];
1508
+
1509
+ return this.directive(name, factory);
1510
+ };
1511
+
1512
+ /**
1513
+ * @ngdoc method
1514
+ * @name $compileProvider#aHrefSanitizationTrustedUrlList
1515
+ * @kind function
1516
+ *
1517
+ * @description
1518
+ * Retrieves or overrides the default regular expression that is used for determining trusted safe
1519
+ * urls during a[href] sanitization.
1520
+ *
1521
+ * The sanitization is a security measure aimed at preventing XSS attacks via html links.
1522
+ *
1523
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
1524
+ * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationTrustedUrlList`
1525
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
1526
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
1527
+ *
1528
+ * @param {RegExp=} regexp New regexp to trust urls with.
1529
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
1530
+ * chaining otherwise.
1531
+ */
1532
+ this.aHrefSanitizationTrustedUrlList = function (regexp) {
1533
+ if (isDefined(regexp)) {
1534
+ $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(regexp);
1535
+ return this;
1536
+ }
1537
+ return $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList();
1538
+ };
1539
+
1540
+ /**
1541
+ * @ngdoc method
1542
+ * @name $compileProvider#imgSrcSanitizationTrustedUrlList
1543
+ * @kind function
1544
+ *
1545
+ * @description
1546
+ * Retrieves or overrides the default regular expression that is used for determining trusted safe
1547
+ * urls during img[src] sanitization.
1548
+ *
1549
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
1550
+ *
1551
+ * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
1552
+ * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationTrustedUrlList`
1553
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
1554
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
1555
+ *
1556
+ * @param {RegExp=} regexp New regexp to trust urls with.
1557
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
1558
+ * chaining otherwise.
1559
+ */
1560
+ this.imgSrcSanitizationTrustedUrlList = function (regexp) {
1561
+ if (isDefined(regexp)) {
1562
+ $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(regexp);
1563
+ return this;
1564
+ }
1565
+ return $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList();
1566
+ };
1567
+
1568
+ /**
1569
+ * @ngdoc method
1570
+ * @name $compileProvider#debugInfoEnabled
1571
+ *
1572
+ * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
1573
+ * current debugInfoEnabled state
1574
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
1575
+ *
1576
+ * @kind function
1577
+ *
1578
+ * @description
1579
+ * Call this method to enable/disable various debug runtime information in the compiler such as adding
1580
+ * binding information and a reference to the current scope on to DOM elements.
1581
+ * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
1582
+ * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return
1583
+ * the element's scope.
1584
+ * * Placeholder comments will contain information about what directive and binding caused the placeholder.
1585
+ * E.g. `<!-- ngIf: shouldShow() -->`.
1586
+ *
1587
+ * You may want to disable this in production for a significant performance boost. See
1588
+ * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
1589
+ *
1590
+ * The default value is true.
1591
+ */
1592
+ let debugInfoEnabled = true;
1593
+ this.debugInfoEnabled = function (enabled) {
1594
+ if (isDefined(enabled)) {
1595
+ debugInfoEnabled = enabled;
1596
+ return this;
1597
+ }
1598
+ return debugInfoEnabled;
1599
+ };
1600
+
1601
+ /**
1602
+ * @ngdoc method
1603
+ * @name $compileProvider#strictComponentBindingsEnabled
1604
+ *
1605
+ * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided,
1606
+ * otherwise return the current strictComponentBindingsEnabled state.
1607
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
1608
+ *
1609
+ * @kind function
1610
+ *
1611
+ * @description
1612
+ * Call this method to enable / disable the strict component bindings check. If enabled, the
1613
+ * compiler will enforce that all scope / controller bindings of a
1614
+ * {@link $compileProvider#directive directive} / {@link $compileProvider#component component}
1615
+ * that are not set as optional with `?`, must be provided when the directive is instantiated.
1616
+ * If not provided, the compiler will throw the
1617
+ * {@link error/$compile/missingattr $compile:missingattr error}.
1618
+ *
1619
+ * The default value is false.
1620
+ */
1621
+ let strictComponentBindingsEnabled = false;
1622
+ this.strictComponentBindingsEnabled = function (enabled) {
1623
+ if (isDefined(enabled)) {
1624
+ strictComponentBindingsEnabled = enabled;
1625
+ return this;
1626
+ }
1627
+ return strictComponentBindingsEnabled;
1628
+ };
1629
+
1630
+ /**
1631
+ * @ngdoc method
1632
+ * @name $compileProvider#onChangesTtl
1633
+ * @description
1634
+ *
1635
+ * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
1636
+ * assuming that the model is unstable.
1637
+ *
1638
+ * The current default is 10 iterations.
1639
+ *
1640
+ * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
1641
+ * in several iterations of calls to these hooks. However if an application needs more than the default 10
1642
+ * iterations to stabilize then you should investigate what is causing the model to continuously change during
1643
+ * the `$onChanges` hook execution.
1644
+ *
1645
+ * Increasing the TTL could have performance implications, so you should not change it without proper justification.
1646
+ *
1647
+ * @param {number} limit The number of `$onChanges` hook iterations.
1648
+ * @returns {number|object} the current limit (or `this` if called as a setter for chaining)
1649
+ */
1650
+ this.onChangesTtl = function (value) {
1651
+ if (arguments.length) {
1652
+ config.TTL = value;
1653
+ return this;
1654
+ }
1655
+ return config.TTL;
1656
+ };
1657
+
1658
+ let commentDirectivesEnabledConfig = true;
1659
+ /**
1660
+ * @ngdoc method
1661
+ * @name $compileProvider#commentDirectivesEnabled
1662
+ * @description
1663
+ *
1664
+ * It indicates to the compiler
1665
+ * whether or not directives on comments should be compiled.
1666
+ * Defaults to `true`.
1667
+ *
1668
+ * Calling this function with false disables the compilation of directives
1669
+ * on comments for the whole application.
1670
+ * This results in a compilation performance gain,
1671
+ * as the compiler doesn't have to check comments when looking for directives.
1672
+ * This should however only be used if you are sure that no comment directives are used in
1673
+ * the application (including any 3rd party directives).
1674
+ *
1675
+ * @param {boolean} enabled `false` if the compiler may ignore directives on comments
1676
+ * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
1677
+ */
1678
+ this.commentDirectivesEnabled = function (value) {
1679
+ if (arguments.length) {
1680
+ commentDirectivesEnabledConfig = value;
1681
+ return this;
1682
+ }
1683
+ return commentDirectivesEnabledConfig;
1684
+ };
1685
+
1686
+ let cssClassDirectivesEnabledConfig = true;
1687
+ /**
1688
+ * @ngdoc method
1689
+ * @name $compileProvider#cssClassDirectivesEnabled
1690
+ * @description
1691
+ *
1692
+ * It indicates to the compiler
1693
+ * whether or not directives on element classes should be compiled.
1694
+ * Defaults to `true`.
1695
+ *
1696
+ * Calling this function with false disables the compilation of directives
1697
+ * on element classes for the whole application.
1698
+ * This results in a compilation performance gain,
1699
+ * as the compiler doesn't have to check element classes when looking for directives.
1700
+ * This should however only be used if you are sure that no class directives are used in
1701
+ * the application (including any 3rd party directives).
1702
+ *
1703
+ * @param {boolean} enabled `false` if the compiler may ignore directives on element classes
1704
+ * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
1705
+ */
1706
+ this.cssClassDirectivesEnabled = function (value) {
1707
+ if (arguments.length) {
1708
+ cssClassDirectivesEnabledConfig = value;
1709
+ return this;
1710
+ }
1711
+ return cssClassDirectivesEnabledConfig;
1712
+ };
1713
+
1714
+ /**
1715
+ * The security context of DOM Properties.
1716
+ * @private
1717
+ */
1718
+ const PROP_CONTEXTS = createMap();
1719
+
1720
+ /**
1721
+ * @ngdoc method
1722
+ * @name $compileProvider#addPropertySecurityContext
1723
+ * @description
1724
+ *
1725
+ * Defines the security context for DOM properties bound by ng-prop-*.
1726
+ *
1727
+ * @param {string} elementName The element name or '*' to match any element.
1728
+ * @param {string} propertyName The DOM property name.
1729
+ * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL`
1730
+ * @returns {object} `this` for chaining
1731
+ */
1732
+ this.addPropertySecurityContext = function (elementName, propertyName, ctx) {
1733
+ const key = `${elementName.toLowerCase()}|${propertyName.toLowerCase()}`;
1734
+
1735
+ if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) {
1736
+ throw $compileMinErr(
1737
+ "ctxoverride",
1738
+ "Property context '{0}.{1}' already set to '{2}', cannot override to '{3}'.",
1739
+ elementName,
1740
+ propertyName,
1741
+ PROP_CONTEXTS[key],
1742
+ ctx,
1743
+ );
1744
+ }
1745
+
1746
+ PROP_CONTEXTS[key] = ctx;
1747
+ return this;
1748
+ };
1749
+
1750
+ /* Default property contexts.
1751
+ *
1752
+ * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
1753
+ * Changing:
1754
+ * - SecurityContext.* => SCE_CONTEXTS/$sce.*
1755
+ * - STYLE => CSS
1756
+ * - various URL => MEDIA_URL
1757
+ * - *|formAction, form|action URL => RESOURCE_URL (like the attribute)
1758
+ */
1759
+ (function registerNativePropertyContexts() {
1760
+ function registerContext(ctx, values) {
1761
+ forEach(values, (v) => {
1762
+ PROP_CONTEXTS[v.toLowerCase()] = ctx;
1763
+ });
1764
+ }
1765
+
1766
+ registerContext(SCE_CONTEXTS.HTML, [
1767
+ "iframe|srcdoc",
1768
+ "*|innerHTML",
1769
+ "*|outerHTML",
1770
+ ]);
1771
+ registerContext(SCE_CONTEXTS.CSS, ["*|style"]);
1772
+ registerContext(SCE_CONTEXTS.URL, [
1773
+ "area|href",
1774
+ "area|ping",
1775
+ "a|href",
1776
+ "a|ping",
1777
+ "blockquote|cite",
1778
+ "body|background",
1779
+ "del|cite",
1780
+ "input|src",
1781
+ "ins|cite",
1782
+ "q|cite",
1783
+ ]);
1784
+ registerContext(SCE_CONTEXTS.MEDIA_URL, [
1785
+ "audio|src",
1786
+ "img|src",
1787
+ "img|srcset",
1788
+ "source|src",
1789
+ "source|srcset",
1790
+ "track|src",
1791
+ "video|src",
1792
+ "video|poster",
1793
+ ]);
1794
+ registerContext(SCE_CONTEXTS.RESOURCE_URL, [
1795
+ "*|formAction",
1796
+ "applet|code",
1797
+ "applet|codebase",
1798
+ "base|href",
1799
+ "embed|src",
1800
+ "frame|src",
1801
+ "form|action",
1802
+ "head|profile",
1803
+ "html|manifest",
1804
+ "iframe|src",
1805
+ "link|href",
1806
+ "media|src",
1807
+ "object|codebase",
1808
+ "object|data",
1809
+ "script|src",
1810
+ ]);
1811
+ })();
1812
+
1813
+ this.$get = [
1814
+ "$injector",
1815
+ "$interpolate",
1816
+ "$exceptionHandler",
1817
+ "$templateRequest",
1818
+ "$parse",
1819
+ "$controller",
1820
+ "$rootScope",
1821
+ "$sce",
1822
+ "$animate",
1823
+ function (
1824
+ $injector,
1825
+ $interpolate,
1826
+ $exceptionHandler,
1827
+ $templateRequest,
1828
+ $parse,
1829
+ $controller,
1830
+ $rootScope,
1831
+ $sce,
1832
+ $animate,
1833
+ ) {
1834
+ const SIMPLE_ATTR_NAME = /^\w/;
1835
+ const specialAttrHolder = window.document.createElement("div");
1836
+
1837
+ const commentDirectivesEnabled = commentDirectivesEnabledConfig;
1838
+ const cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig;
1839
+
1840
+ // The onChanges hooks should all be run together in a single digest
1841
+ // When changes occur, the call to trigger their hooks will be added to this queue
1842
+ let onChangesQueue;
1843
+
1844
+ // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
1845
+ function flushOnChangesQueue() {
1846
+ try {
1847
+ if (!--config.TTL) {
1848
+ // We have hit the TTL limit so reset everything
1849
+ onChangesQueue = undefined;
1850
+ throw $compileMinErr(
1851
+ "infchng",
1852
+ "{0} $onChanges() iterations reached. Aborting!\n",
1853
+ config.TTL,
1854
+ );
1855
+ }
1856
+ // We must run this hook in an apply since the $$postDigest runs outside apply
1857
+ $rootScope.$apply(() => {
1858
+ for (let i = 0, ii = onChangesQueue.length; i < ii; ++i) {
1859
+ try {
1860
+ onChangesQueue[i]();
1861
+ } catch (e) {
1862
+ $exceptionHandler(e);
1863
+ }
1864
+ }
1865
+ // Reset the queue to trigger a new schedule next time there is a change
1866
+ onChangesQueue = undefined;
1867
+ });
1868
+ } finally {
1869
+ config.TTL++;
1870
+ }
1871
+ }
1872
+
1873
+ function sanitizeSrcset(value, invokeType) {
1874
+ if (!value) {
1875
+ return value;
1876
+ }
1877
+ if (!isString(value)) {
1878
+ throw $compileMinErr(
1879
+ "srcset",
1880
+ 'Can\'t pass trusted values to `{0}`: "{1}"',
1881
+ invokeType,
1882
+ value.toString(),
1883
+ );
1884
+ }
1885
+
1886
+ // Such values are a bit too complex to handle automatically inside $sce.
1887
+ // Instead, we sanitize each of the URIs individually, which works, even dynamically.
1888
+
1889
+ // It's not possible to work around this using `$sce.trustAsMediaUrl`.
1890
+ // If you want to programmatically set explicitly trusted unsafe URLs, you should use
1891
+ // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
1892
+ // `ng-bind-html` directive.
1893
+
1894
+ var result = "";
1895
+
1896
+ // first check if there are spaces because it's not the same pattern
1897
+ var trimmedSrcset = trim(value);
1898
+ // ( 999x ,| 999w ,| ,|, )
1899
+ var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
1900
+ var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
1901
+
1902
+ // split srcset into tuple of uri and descriptor except for the last item
1903
+ var rawUris = trimmedSrcset.split(pattern);
1904
+
1905
+ // for each tuples
1906
+ var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
1907
+ for (var i = 0; i < nbrUrisWith2parts; i++) {
1908
+ var innerIdx = i * 2;
1909
+ // sanitize the uri
1910
+ result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
1911
+ // add the descriptor
1912
+ result += " " + trim(rawUris[innerIdx + 1]);
1913
+ }
1914
+
1915
+ // split the last item into uri and descriptor
1916
+ var lastTuple = trim(rawUris[i * 2]).split(/\s/);
1917
+
1918
+ // sanitize the last uri
1919
+ result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
1920
+
1921
+ // and add the last descriptor if any
1922
+ if (lastTuple.length === 2) {
1923
+ result += " " + trim(lastTuple[1]);
1924
+ }
1925
+ return result;
1926
+ }
1927
+
1928
+ function Attributes(element, attributesToCopy) {
1929
+ if (attributesToCopy) {
1930
+ const keys = Object.keys(attributesToCopy);
1931
+ let i;
1932
+ let l;
1933
+ let key;
1934
+
1935
+ for (i = 0, l = keys.length; i < l; i++) {
1936
+ key = keys[i];
1937
+ this[key] = attributesToCopy[key];
1938
+ }
1939
+ } else {
1940
+ this.$attr = {};
1941
+ }
1942
+
1943
+ this.$$element = element;
1944
+ }
1945
+
1946
+ Attributes.prototype = {
1947
+ /**
1948
+ * @ngdoc method
1949
+ * @name $compile.directive.Attributes#$normalize
1950
+ * @kind function
1951
+ *
1952
+ * @description
1953
+ * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
1954
+ * `data-`) to its normalized, camelCase form.
1955
+ *
1956
+ * Also there is special case for Moz prefix starting with upper case letter.
1957
+ *
1958
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
1959
+ *
1960
+ * @param {string} name Name to normalize
1961
+ */
1962
+ $normalize: directiveNormalize,
1963
+
1964
+ /**
1965
+ * @ngdoc method
1966
+ * @name $compile.directive.Attributes#$addClass
1967
+ * @kind function
1968
+ *
1969
+ * @description
1970
+ * Adds the CSS class value specified by the classVal parameter to the element. If animations
1971
+ * are enabled then an animation will be triggered for the class addition.
1972
+ *
1973
+ * @param {string} classVal The className value that will be added to the element
1974
+ */
1975
+ $addClass(classVal) {
1976
+ if (classVal && classVal.length > 0) {
1977
+ $animate.addClass(this.$$element, classVal);
1978
+ }
1979
+ },
1980
+
1981
+ /**
1982
+ * @ngdoc method
1983
+ * @name $compile.directive.Attributes#$removeClass
1984
+ * @kind function
1985
+ *
1986
+ * @description
1987
+ * Removes the CSS class value specified by the classVal parameter from the element. If
1988
+ * animations are enabled then an animation will be triggered for the class removal.
1989
+ *
1990
+ * @param {string} classVal The className value that will be removed from the element
1991
+ */
1992
+ $removeClass(classVal) {
1993
+ if (classVal && classVal.length > 0) {
1994
+ $animate.removeClass(this.$$element, classVal);
1995
+ }
1996
+ },
1997
+
1998
+ /**
1999
+ * @ngdoc method
2000
+ * @name $compile.directive.Attributes#$updateClass
2001
+ * @kind function
2002
+ *
2003
+ * @description
2004
+ * Adds and removes the appropriate CSS class values to the element based on the difference
2005
+ * between the new and old CSS class values (specified as newClasses and oldClasses).
2006
+ *
2007
+ * @param {string} newClasses The current CSS className value
2008
+ * @param {string} oldClasses The former CSS className value
2009
+ */
2010
+ $updateClass(newClasses, oldClasses) {
2011
+ const toAdd = tokenDifference(newClasses, oldClasses);
2012
+ if (toAdd && toAdd.length) {
2013
+ $animate.addClass(this.$$element, toAdd);
2014
+ }
2015
+
2016
+ const toRemove = tokenDifference(oldClasses, newClasses);
2017
+ if (toRemove && toRemove.length) {
2018
+ $animate.removeClass(this.$$element, toRemove);
2019
+ }
2020
+ },
2021
+
2022
+ /**
2023
+ * Set a normalized attribute on the element in a way such that all directives
2024
+ * can share the attribute. This function properly handles boolean attributes.
2025
+ * @param {string} key Normalized key. (ie ngAttribute)
2026
+ * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
2027
+ * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
2028
+ * Defaults to true.
2029
+ * @param {string=} attrName Optional none normalized name. Defaults to key.
2030
+ */
2031
+ $set(key, value, writeAttr, attrName) {
2032
+ // TODO: decide whether or not to throw an error if "class"
2033
+ // is set through this function since it may cause $updateClass to
2034
+ // become unstable.
2035
+
2036
+ const node = this.$$element[0];
2037
+ const booleanKey = getBooleanAttrName(node, key);
2038
+ const aliasedKey = ALIASED_ATTR[key];
2039
+ let observer = key;
2040
+ let nodeName;
2041
+
2042
+ if (booleanKey) {
2043
+ this.$$element.prop(key, value);
2044
+ attrName = booleanKey;
2045
+ } else if (aliasedKey) {
2046
+ this[aliasedKey] = value;
2047
+ observer = aliasedKey;
2048
+ }
2049
+
2050
+ this[key] = value;
2051
+
2052
+ // translate normalized key to actual key
2053
+ if (attrName) {
2054
+ this.$attr[key] = attrName;
2055
+ } else {
2056
+ attrName = this.$attr[key];
2057
+ if (!attrName) {
2058
+ this.$attr[key] = attrName = snakeCase(key, "-");
2059
+ }
2060
+ }
2061
+
2062
+ nodeName = nodeName_(this.$$element);
2063
+
2064
+ // Sanitize img[srcset] values.
2065
+ if (nodeName === "img" && key === "srcset") {
2066
+ this[key] = value = sanitizeSrcset(value, "$set('srcset', value)");
2067
+ }
2068
+
2069
+ if (writeAttr !== false) {
2070
+ if (value === null || isUndefined(value)) {
2071
+ this.$$element[0].removeAttribute(attrName);
2072
+ } else if (SIMPLE_ATTR_NAME.test(attrName)) {
2073
+ // jQuery skips special boolean attrs treatment in XML nodes for
2074
+ // historical reasons and hence AngularJS cannot freely call
2075
+ // `.attr(attrName, false) with such attributes. To avoid issues
2076
+ // in XHTML, call `removeAttr` in such cases instead.
2077
+ // See https://github.com/jquery/jquery/issues/4249
2078
+ if (booleanKey && value === false) {
2079
+ this.$$element[0].removeAttribute(attrName);
2080
+ } else {
2081
+ this.$$element.attr(attrName, value);
2082
+ }
2083
+ } else {
2084
+ setSpecialAttr(this.$$element[0], attrName, value);
2085
+ }
2086
+ }
2087
+
2088
+ // fire observers
2089
+ const { $$observers } = this;
2090
+ if ($$observers) {
2091
+ forEach($$observers[observer], (fn) => {
2092
+ try {
2093
+ fn(value);
2094
+ } catch (e) {
2095
+ $exceptionHandler(e);
2096
+ }
2097
+ });
2098
+ }
2099
+ },
2100
+
2101
+ /**
2102
+ * @ngdoc method
2103
+ * @name $compile.directive.Attributes#$observe
2104
+ * @kind function
2105
+ *
2106
+ * @description
2107
+ * Observes an interpolated attribute.
2108
+ *
2109
+ * The observer function will be invoked once during the next `$digest` following
2110
+ * compilation. The observer is then invoked whenever the interpolated value
2111
+ * changes.
2112
+ *
2113
+ * @param {string} key Normalized key. (ie ngAttribute) .
2114
+ * @param {function(interpolatedValue)} fn Function that will be called whenever
2115
+ the interpolated value of the attribute changes.
2116
+ * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
2117
+ * guide} for more info.
2118
+ * @returns {function()} Returns a deregistration function for this observer.
2119
+ */
2120
+ $observe(key, fn) {
2121
+ const attrs = this;
2122
+ const $$observers =
2123
+ attrs.$$observers || (attrs.$$observers = createMap());
2124
+ const listeners = $$observers[key] || ($$observers[key] = []);
2125
+
2126
+ listeners.push(fn);
2127
+ $rootScope.$evalAsync(() => {
2128
+ if (
2129
+ !listeners.$$inter &&
2130
+ Object.prototype.hasOwnProperty.call(attrs, key) &&
2131
+ !isUndefined(attrs[key])
2132
+ ) {
2133
+ // no one registered attribute interpolation function, so lets call it manually
2134
+ fn(attrs[key]);
2135
+ }
2136
+ });
2137
+
2138
+ return function () {
2139
+ arrayRemove(listeners, fn);
2140
+ };
2141
+ },
2142
+ };
2143
+
2144
+ function setSpecialAttr(element, attrName, value) {
2145
+ // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
2146
+ // so we have to jump through some hoops to get such an attribute
2147
+ // https://github.com/angular/angular.js/pull/13318
2148
+ specialAttrHolder.innerHTML = `<span ${attrName}>`;
2149
+ const { attributes } = specialAttrHolder.firstChild;
2150
+ const attribute = attributes[0];
2151
+ // We have to remove the attribute from its container element before we can add it to the destination element
2152
+ attributes.removeNamedItem(attribute.name);
2153
+ attribute.value = value;
2154
+ element.attributes.setNamedItem(attribute);
2155
+ }
2156
+
2157
+ function safeAddClass($element, className) {
2158
+ try {
2159
+ $element[0].classList.add(className);
2160
+ } catch (e) {
2161
+ // ignore, since it means that we are trying to set class on
2162
+ // SVG element, where class name is read-only.
2163
+ }
2164
+ }
2165
+
2166
+ const startSymbol = $interpolate.startSymbol();
2167
+ const endSymbol = $interpolate.endSymbol();
2168
+ const denormalizeTemplate =
2169
+ startSymbol === "{{" && endSymbol === "}}"
2170
+ ? identity
2171
+ : function denormalizeTemplate(template) {
2172
+ return template
2173
+ .replace(/\{\{/g, startSymbol)
2174
+ .replace(/}}/g, endSymbol);
2175
+ };
2176
+ const NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/;
2177
+ const MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
2178
+
2179
+ compile.$$addScopeInfo = debugInfoEnabled
2180
+ ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
2181
+ const dataName = isolated
2182
+ ? noTemplate
2183
+ ? "$isolateScopeNoTemplate"
2184
+ : "$isolateScope"
2185
+ : "$scope";
2186
+ $element.data(dataName, scope);
2187
+ }
2188
+ : () => {};
2189
+
2190
+ compile.$$createComment = function (directiveName, comment) {
2191
+ let content = "";
2192
+ if (debugInfoEnabled) {
2193
+ content = ` ${directiveName || ""}: `;
2194
+ if (comment) content += `${comment} `;
2195
+ }
2196
+ return window.document.createComment(content);
2197
+ };
2198
+
2199
+ return compile;
2200
+
2201
+ //= ===============================
2202
+
2203
+ function compile(
2204
+ $compileNodes,
2205
+ transcludeFn,
2206
+ maxPriority,
2207
+ ignoreDirective,
2208
+ previousCompileContext,
2209
+ ) {
2210
+ if (!($compileNodes instanceof jqLite)) {
2211
+ // jquery always rewraps, whereas we need to preserve the original selector so that we can
2212
+ // modify it.
2213
+ $compileNodes = jqLite($compileNodes);
2214
+ }
2215
+ let compositeLinkFn = compileNodes(
2216
+ $compileNodes,
2217
+ transcludeFn,
2218
+ $compileNodes,
2219
+ maxPriority,
2220
+ ignoreDirective,
2221
+ previousCompileContext,
2222
+ );
2223
+
2224
+ let namespace = null;
2225
+ return function publicLinkFn(scope, cloneConnectFn, options) {
2226
+ if (!$compileNodes) {
2227
+ throw $compileMinErr(
2228
+ "multilink",
2229
+ "This element has already been linked.",
2230
+ );
2231
+ }
2232
+ assertArg(scope, "scope");
2233
+
2234
+ if (previousCompileContext && previousCompileContext.needsNewScope) {
2235
+ // A parent directive did a replace and a directive on this element asked
2236
+ // for transclusion, which caused us to lose a layer of element on which
2237
+ // we could hold the new transclusion scope, so we will create it manually
2238
+ // here.
2239
+ scope = scope.$parent.$new();
2240
+ }
2241
+
2242
+ options = options || {};
2243
+ let { parentBoundTranscludeFn } = options;
2244
+ const { transcludeControllers } = options;
2245
+ const { futureParentElement } = options;
2246
+
2247
+ // When `parentBoundTranscludeFn` is passed, it is a
2248
+ // `controllersBoundTransclude` function (it was previously passed
2249
+ // as `transclude` to directive.link) so we must unwrap it to get
2250
+ // its `boundTranscludeFn`
2251
+ if (
2252
+ parentBoundTranscludeFn &&
2253
+ parentBoundTranscludeFn.$$boundTransclude
2254
+ ) {
2255
+ parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
2256
+ }
2257
+
2258
+ if (!namespace) {
2259
+ namespace = detectNamespaceForChildElements(futureParentElement);
2260
+ }
2261
+ let $linkNode;
2262
+ if (namespace !== "html") {
2263
+ // When using a directive with replace:true and templateUrl the $compileNodes
2264
+ // (or a child element inside of them)
2265
+ // might change, so we need to recreate the namespace adapted compileNodes
2266
+ // for call to the link function.
2267
+ // Note: This will already clone the nodes...
2268
+ $linkNode = jqLite(
2269
+ wrapTemplate(
2270
+ namespace,
2271
+ jqLite("<div></div>").append($compileNodes).html(),
2272
+ ),
2273
+ );
2274
+ } else if (cloneConnectFn) {
2275
+ $linkNode = jqLite(
2276
+ Array.from($compileNodes).map((element) =>
2277
+ element.cloneNode(true),
2278
+ ),
2279
+ );
2280
+ } else {
2281
+ $linkNode = $compileNodes;
2282
+ }
2283
+
2284
+ if (transcludeControllers) {
2285
+ for (const controllerName in transcludeControllers) {
2286
+ $linkNode.data(
2287
+ `$${controllerName}Controller`,
2288
+ transcludeControllers[controllerName].instance,
2289
+ );
2290
+ }
2291
+ }
2292
+
2293
+ compile.$$addScopeInfo($linkNode, scope);
2294
+
2295
+ if (cloneConnectFn) cloneConnectFn($linkNode, scope);
2296
+ if (compositeLinkFn)
2297
+ compositeLinkFn(
2298
+ scope,
2299
+ $linkNode,
2300
+ $linkNode,
2301
+ parentBoundTranscludeFn,
2302
+ );
2303
+
2304
+ if (!cloneConnectFn) {
2305
+ $compileNodes = compositeLinkFn = null;
2306
+ }
2307
+ return $linkNode;
2308
+ };
2309
+ }
2310
+
2311
+ function detectNamespaceForChildElements(parentElement) {
2312
+ // TODO: Make this detect MathML as well...
2313
+ const node = parentElement && parentElement[0];
2314
+ if (!node) {
2315
+ return "html";
2316
+ }
2317
+ return nodeName_(node) !== "foreignobject" &&
2318
+ toString.call(node).match(/SVG/)
2319
+ ? "svg"
2320
+ : "html";
2321
+ }
2322
+
2323
+ /**
2324
+ * Compile function matches each node in nodeList against the directives. Once all directives
2325
+ * for a particular node are collected their compile functions are executed. The compile
2326
+ * functions return values - the linking functions - are combined into a composite linking
2327
+ * function, which is the a linking function for the node.
2328
+ *
2329
+ * @param {NodeList} nodeList an array of nodes or NodeList to compile
2330
+ * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
2331
+ * scope argument is auto-generated to the new child of the transcluded parent scope.
2332
+ * @param {Element=} $rootElement If the nodeList is the root of the compilation tree then
2333
+ * the rootElement must be set the jqLite collection of the compile root. This is
2334
+ * needed so that the jqLite collection items can be replaced with widgets.
2335
+ * @param {number=} maxPriority Max directive priority.
2336
+ * @returns {Function} A composite linking function of all of the matched directives or null.
2337
+ */
2338
+ function compileNodes(
2339
+ nodeList,
2340
+ transcludeFn,
2341
+ $rootElement,
2342
+ maxPriority,
2343
+ ignoreDirective,
2344
+ previousCompileContext,
2345
+ ) {
2346
+ const linkFns = [];
2347
+ let attrs;
2348
+ let directives;
2349
+ var nodeLinkFn;
2350
+ let childNodes;
2351
+ let childLinkFn;
2352
+ let linkFnFound;
2353
+ let nodeLinkFnFound;
2354
+
2355
+ for (let i = 0; i < nodeList.length; i++) {
2356
+ attrs = new Attributes();
2357
+
2358
+ // We must always refer to `nodeList[i]` hereafter,
2359
+ // since the nodes can be replaced underneath us.
2360
+ directives = collectDirectives(
2361
+ nodeList[i],
2362
+ [],
2363
+ attrs,
2364
+ i === 0 ? maxPriority : undefined,
2365
+ ignoreDirective,
2366
+ );
2367
+
2368
+ if (directives.length) {
2369
+ nodeLinkFn = applyDirectivesToNode(
2370
+ directives,
2371
+ nodeList[i],
2372
+ attrs,
2373
+ transcludeFn,
2374
+ $rootElement,
2375
+ null,
2376
+ [],
2377
+ [],
2378
+ previousCompileContext,
2379
+ );
2380
+ } else {
2381
+ nodeLinkFn = null;
2382
+ }
2383
+
2384
+ childLinkFn =
2385
+ (nodeLinkFn && nodeLinkFn.terminal) ||
2386
+ !(childNodes = nodeList[i].childNodes) ||
2387
+ !childNodes.length
2388
+ ? null
2389
+ : compileNodes(
2390
+ childNodes,
2391
+ nodeLinkFn
2392
+ ? (nodeLinkFn.transcludeOnThisElement ||
2393
+ !nodeLinkFn.templateOnThisElement) &&
2394
+ nodeLinkFn.transclude
2395
+ : transcludeFn,
2396
+ );
2397
+
2398
+ if (nodeLinkFn || childLinkFn) {
2399
+ linkFns.push(i, nodeLinkFn, childLinkFn);
2400
+ linkFnFound = true;
2401
+ nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
2402
+ }
2403
+
2404
+ // use the previous context only for the first element in the virtual group
2405
+ previousCompileContext = null;
2406
+ }
2407
+
2408
+ // return a linking function if we have found anything, null otherwise
2409
+ return linkFnFound ? compositeLinkFn : null;
2410
+
2411
+ function compositeLinkFn(
2412
+ scope,
2413
+ nodeList,
2414
+ $rootElement,
2415
+ parentBoundTranscludeFn,
2416
+ ) {
2417
+ let nodeLinkFn;
2418
+ let childLinkFn;
2419
+ let node;
2420
+ let childScope;
2421
+ let i;
2422
+ let ii;
2423
+ let idx;
2424
+ let childBoundTranscludeFn;
2425
+ let stableNodeList;
2426
+
2427
+ if (nodeLinkFnFound) {
2428
+ // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
2429
+ // offsets don't get screwed up
2430
+ const nodeListLength = nodeList.length;
2431
+ stableNodeList = new Array(nodeListLength);
2432
+
2433
+ // create a sparse array by only copying the elements which have a linkFn
2434
+ for (i = 0; i < linkFns.length; i += 3) {
2435
+ idx = linkFns[i];
2436
+ stableNodeList[idx] = nodeList[idx];
2437
+ }
2438
+ } else {
2439
+ stableNodeList = nodeList;
2440
+ }
2441
+
2442
+ for (i = 0, ii = linkFns.length; i < ii; ) {
2443
+ node = stableNodeList[linkFns[i++]];
2444
+ nodeLinkFn = linkFns[i++];
2445
+ childLinkFn = linkFns[i++];
2446
+
2447
+ if (nodeLinkFn) {
2448
+ if (nodeLinkFn.scope) {
2449
+ childScope = scope.$new();
2450
+ compile.$$addScopeInfo(jqLite(node), childScope);
2451
+ } else {
2452
+ childScope = scope;
2453
+ }
2454
+
2455
+ if (nodeLinkFn.transcludeOnThisElement) {
2456
+ childBoundTranscludeFn = createBoundTranscludeFn(
2457
+ scope,
2458
+ nodeLinkFn.transclude,
2459
+ parentBoundTranscludeFn,
2460
+ );
2461
+ } else if (
2462
+ !nodeLinkFn.templateOnThisElement &&
2463
+ parentBoundTranscludeFn
2464
+ ) {
2465
+ childBoundTranscludeFn = parentBoundTranscludeFn;
2466
+ } else if (!parentBoundTranscludeFn && transcludeFn) {
2467
+ childBoundTranscludeFn = createBoundTranscludeFn(
2468
+ scope,
2469
+ transcludeFn,
2470
+ );
2471
+ } else {
2472
+ childBoundTranscludeFn = null;
2473
+ }
2474
+
2475
+ nodeLinkFn(
2476
+ childLinkFn,
2477
+ childScope,
2478
+ node,
2479
+ $rootElement,
2480
+ childBoundTranscludeFn,
2481
+ );
2482
+ } else if (childLinkFn) {
2483
+ childLinkFn(
2484
+ scope,
2485
+ node.childNodes,
2486
+ undefined,
2487
+ parentBoundTranscludeFn,
2488
+ );
2489
+ }
2490
+ }
2491
+ }
2492
+ }
2493
+
2494
+ function createBoundTranscludeFn(
2495
+ scope,
2496
+ transcludeFn,
2497
+ previousBoundTranscludeFn,
2498
+ ) {
2499
+ function boundTranscludeFn(
2500
+ transcludedScope,
2501
+ cloneFn,
2502
+ controllers,
2503
+ futureParentElement,
2504
+ containingScope,
2505
+ ) {
2506
+ if (!transcludedScope) {
2507
+ transcludedScope = scope.$new(false, containingScope);
2508
+ transcludedScope.$$transcluded = true;
2509
+ }
2510
+
2511
+ return transcludeFn(transcludedScope, cloneFn, {
2512
+ parentBoundTranscludeFn: previousBoundTranscludeFn,
2513
+ transcludeControllers: controllers,
2514
+ futureParentElement,
2515
+ });
2516
+ }
2517
+
2518
+ // We need to attach the transclusion slots onto the `boundTranscludeFn`
2519
+ // so that they are available inside the `controllersBoundTransclude` function
2520
+ const boundSlots = (boundTranscludeFn.$$slots = createMap());
2521
+ for (const slotName in transcludeFn.$$slots) {
2522
+ if (transcludeFn.$$slots[slotName]) {
2523
+ boundSlots[slotName] = createBoundTranscludeFn(
2524
+ scope,
2525
+ transcludeFn.$$slots[slotName],
2526
+ previousBoundTranscludeFn,
2527
+ );
2528
+ } else {
2529
+ boundSlots[slotName] = null;
2530
+ }
2531
+ }
2532
+
2533
+ return boundTranscludeFn;
2534
+ }
2535
+
2536
+ /**
2537
+ * Looks for directives on the given node and adds them to the directive collection which is
2538
+ * sorted.
2539
+ *
2540
+ * @param node Node to search.
2541
+ * @param directives An array to which the directives are added to. This array is sorted before
2542
+ * the function returns.
2543
+ * @param attrs The shared attrs object which is used to populate the normalized attributes.
2544
+ * @param {number=} maxPriority Max directive priority.
2545
+ */
2546
+ function collectDirectives(
2547
+ node,
2548
+ directives,
2549
+ attrs,
2550
+ maxPriority,
2551
+ ignoreDirective,
2552
+ ) {
2553
+ const { nodeType } = node;
2554
+ const attrsMap = attrs.$attr;
2555
+ let match;
2556
+ let nodeName;
2557
+ let className;
2558
+
2559
+ switch (nodeType) {
2560
+ case Node.ELEMENT_NODE /* Element */:
2561
+ nodeName = nodeName_(node);
2562
+
2563
+ // use the node name: <directive>
2564
+ addDirective(
2565
+ directives,
2566
+ directiveNormalize(nodeName),
2567
+ "E",
2568
+ maxPriority,
2569
+ ignoreDirective,
2570
+ );
2571
+
2572
+ // iterate over the attributes
2573
+ for (
2574
+ var attr,
2575
+ name,
2576
+ nName,
2577
+ value,
2578
+ ngPrefixMatch,
2579
+ nAttrs = node.attributes,
2580
+ j = 0,
2581
+ jj = nAttrs && nAttrs.length;
2582
+ j < jj;
2583
+ j++
2584
+ ) {
2585
+ let attrStartName = false;
2586
+ let attrEndName = false;
2587
+
2588
+ let isNgAttr = false;
2589
+ let isNgProp = false;
2590
+ let isNgEvent = false;
2591
+ let multiElementMatch;
2592
+
2593
+ attr = nAttrs[j];
2594
+ name = attr.name;
2595
+ value = attr.value;
2596
+
2597
+ nName = directiveNormalize(name.toLowerCase());
2598
+
2599
+ // Support ng-attr-*, ng-prop-* and ng-on-*
2600
+ if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) {
2601
+ isNgAttr = ngPrefixMatch[1] === "Attr";
2602
+ isNgProp = ngPrefixMatch[1] === "Prop";
2603
+ isNgEvent = ngPrefixMatch[1] === "On";
2604
+
2605
+ // Normalize the non-prefixed name
2606
+ name = name
2607
+ .replace(PREFIX_REGEXP, "")
2608
+ .toLowerCase()
2609
+ .substr(4 + ngPrefixMatch[1].length)
2610
+ .replace(/_(.)/g, (match, letter) => letter.toUpperCase());
2611
+
2612
+ // Support *-start / *-end multi element directives
2613
+ } else if (
2614
+ (multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) &&
2615
+ directiveIsMultiElement(multiElementMatch[1])
2616
+ ) {
2617
+ attrStartName = name;
2618
+ attrEndName = `${name.substr(0, name.length - 5)}end`;
2619
+ name = name.substr(0, name.length - 6);
2620
+ }
2621
+
2622
+ if (isNgProp || isNgEvent) {
2623
+ attrs[nName] = value;
2624
+ attrsMap[nName] = attr.name;
2625
+
2626
+ if (isNgProp) {
2627
+ addPropertyDirective(node, directives, nName, name);
2628
+ } else {
2629
+ addEventDirective(directives, nName, name);
2630
+ }
2631
+ } else {
2632
+ // Update nName for cases where a prefix was removed
2633
+ // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-*
2634
+ nName = directiveNormalize(name.toLowerCase());
2635
+ attrsMap[nName] = name;
2636
+
2637
+ if (
2638
+ isNgAttr ||
2639
+ !Object.prototype.hasOwnProperty.call(attrs, nName)
2640
+ ) {
2641
+ attrs[nName] = value;
2642
+ if (getBooleanAttrName(node, nName)) {
2643
+ attrs[nName] = true; // presence means true
2644
+ }
2645
+ }
2646
+
2647
+ addAttrInterpolateDirective(
2648
+ node,
2649
+ directives,
2650
+ value,
2651
+ nName,
2652
+ isNgAttr,
2653
+ );
2654
+ addDirective(
2655
+ directives,
2656
+ nName,
2657
+ "A",
2658
+ maxPriority,
2659
+ ignoreDirective,
2660
+ attrStartName,
2661
+ attrEndName,
2662
+ );
2663
+ }
2664
+ }
2665
+
2666
+ if (
2667
+ nodeName === "input" &&
2668
+ node.getAttribute("type") === "hidden"
2669
+ ) {
2670
+ // Hidden input elements can have strange behaviour when navigating back to the page
2671
+ // This tells the browser not to try to cache and reinstate previous values
2672
+ node.setAttribute("autocomplete", "off");
2673
+ }
2674
+
2675
+ // use class as directive
2676
+ if (!cssClassDirectivesEnabled) break;
2677
+ // TODO: migrate to classList
2678
+ className = node.className;
2679
+ if (isObject(className)) {
2680
+ // Maybe SVGAnimatedString
2681
+ className = className.animVal;
2682
+ }
2683
+ if (isString(className) && className !== "") {
2684
+ while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) {
2685
+ nName = directiveNormalize(match[2]);
2686
+ if (
2687
+ addDirective(
2688
+ directives,
2689
+ nName,
2690
+ "C",
2691
+ maxPriority,
2692
+ ignoreDirective,
2693
+ )
2694
+ ) {
2695
+ attrs[nName] = trim(match[3]);
2696
+ }
2697
+ className = className.substr(match.index + match[0].length);
2698
+ }
2699
+ }
2700
+ break;
2701
+ case Node.TEXT_NODE:
2702
+ addTextInterpolateDirective(directives, node.nodeValue);
2703
+ break;
2704
+ case Node.COMMENT_NODE:
2705
+ if (!commentDirectivesEnabled) break;
2706
+ collectCommentDirectives(
2707
+ node,
2708
+ directives,
2709
+ attrs,
2710
+ maxPriority,
2711
+ ignoreDirective,
2712
+ );
2713
+ break;
2714
+ }
2715
+
2716
+ directives.sort(byPriority);
2717
+ return directives;
2718
+ }
2719
+
2720
+ function collectCommentDirectives(
2721
+ node,
2722
+ directives,
2723
+ attrs,
2724
+ maxPriority,
2725
+ ignoreDirective,
2726
+ ) {
2727
+ // function created because of performance, try/catch disables
2728
+ // the optimization of the whole function #14848
2729
+ try {
2730
+ const match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
2731
+ if (match) {
2732
+ const nName = directiveNormalize(match[1]);
2733
+ if (
2734
+ addDirective(directives, nName, "M", maxPriority, ignoreDirective)
2735
+ ) {
2736
+ attrs[nName] = trim(match[2]);
2737
+ }
2738
+ }
2739
+ } catch (e) {
2740
+ // turns out that under some circumstances IE9 throws errors when one attempts to read
2741
+ // comment's node value.
2742
+ // Just ignore it and continue. (Can't seem to reproduce in test case.)
2743
+ }
2744
+ }
2745
+
2746
+ /**
2747
+ * Given a node with a directive-start it collects all of the siblings until it finds
2748
+ * directive-end.
2749
+ * @param node
2750
+ * @param attrStart
2751
+ * @param attrEnd
2752
+ * @returns {*}
2753
+ */
2754
+ function groupScan(node, attrStart, attrEnd) {
2755
+ const nodes = [];
2756
+ let depth = 0;
2757
+ if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
2758
+ do {
2759
+ if (!node) {
2760
+ throw $compileMinErr(
2761
+ "uterdir",
2762
+ "Unterminated attribute, found '{0}' but no matching '{1}' found.",
2763
+ attrStart,
2764
+ attrEnd,
2765
+ );
2766
+ }
2767
+ if (node.nodeType === Node.ELEMENT_NODE) {
2768
+ if (node.hasAttribute(attrStart)) depth++;
2769
+ if (node.hasAttribute(attrEnd)) depth--;
2770
+ }
2771
+ nodes.push(node);
2772
+ node = node.nextSibling;
2773
+ } while (depth > 0);
2774
+ } else {
2775
+ nodes.push(node);
2776
+ }
2777
+
2778
+ return jqLite(nodes);
2779
+ }
2780
+
2781
+ /**
2782
+ * Wrapper for linking function which converts normal linking function into a grouped
2783
+ * linking function.
2784
+ * @param linkFn
2785
+ * @param attrStart
2786
+ * @param attrEnd
2787
+ * @returns {Function}
2788
+ */
2789
+ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
2790
+ return function groupedElementsLink(
2791
+ scope,
2792
+ element,
2793
+ attrs,
2794
+ controllers,
2795
+ transcludeFn,
2796
+ ) {
2797
+ element = groupScan(element[0], attrStart, attrEnd);
2798
+ return linkFn(scope, element, attrs, controllers, transcludeFn);
2799
+ };
2800
+ }
2801
+
2802
+ /**
2803
+ * A function generator that is used to support both eager and lazy compilation
2804
+ * linking function.
2805
+ * @param eager
2806
+ * @param $compileNodes
2807
+ * @param transcludeFn
2808
+ * @param maxPriority
2809
+ * @param ignoreDirective
2810
+ * @param previousCompileContext
2811
+ * @returns {Function}
2812
+ */
2813
+ function compilationGenerator(
2814
+ eager,
2815
+ $compileNodes,
2816
+ transcludeFn,
2817
+ maxPriority,
2818
+ ignoreDirective,
2819
+ previousCompileContext,
2820
+ ) {
2821
+ let compiled;
2822
+
2823
+ if (eager) {
2824
+ return compile(
2825
+ $compileNodes,
2826
+ transcludeFn,
2827
+ maxPriority,
2828
+ ignoreDirective,
2829
+ previousCompileContext,
2830
+ );
2831
+ }
2832
+ return function lazyCompilation() {
2833
+ if (!compiled) {
2834
+ compiled = compile(
2835
+ $compileNodes,
2836
+ transcludeFn,
2837
+ maxPriority,
2838
+ ignoreDirective,
2839
+ previousCompileContext,
2840
+ );
2841
+
2842
+ // Null out all of these references in order to make them eligible for garbage collection
2843
+ // since this is a potentially long lived closure
2844
+ $compileNodes = transcludeFn = previousCompileContext = null;
2845
+ }
2846
+ return compiled.apply(this, arguments);
2847
+ };
2848
+ }
2849
+
2850
+ /**
2851
+ * Once the directives have been collected, their compile functions are executed. This method
2852
+ * is responsible for inlining directive templates as well as terminating the application
2853
+ * of the directives if the terminal directive has been reached.
2854
+ *
2855
+ * @param {Array} directives Array of collected directives to execute their compile function.
2856
+ * this needs to be pre-sorted by priority order.
2857
+ * @param {Node} compileNode The raw DOM node to apply the compile functions to
2858
+ * @param {Object} templateAttrs The shared attribute function
2859
+ * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
2860
+ * scope argument is auto-generated to the new
2861
+ * child of the transcluded parent scope.
2862
+ * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
2863
+ * argument has the root jqLite array so that we can replace nodes
2864
+ * on it.
2865
+ * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
2866
+ * compiling the transclusion.
2867
+ * @param {Array.<Function>} preLinkFns
2868
+ * @param {Array.<Function>} postLinkFns
2869
+ * @param {Object} previousCompileContext Context used for previous compilation of the current
2870
+ * node
2871
+ * @returns {Function} linkFn
2872
+ */
2873
+ function applyDirectivesToNode(
2874
+ directives,
2875
+ compileNode,
2876
+ templateAttrs,
2877
+ transcludeFn,
2878
+ jqCollection,
2879
+ originalReplaceDirective,
2880
+ preLinkFns,
2881
+ postLinkFns,
2882
+ previousCompileContext,
2883
+ ) {
2884
+ previousCompileContext = previousCompileContext || {};
2885
+
2886
+ let terminalPriority = -Number.MAX_VALUE;
2887
+ let { newScopeDirective } = previousCompileContext;
2888
+ let { controllerDirectives } = previousCompileContext;
2889
+ let { newIsolateScopeDirective } = previousCompileContext;
2890
+ let { templateDirective } = previousCompileContext;
2891
+ let { nonTlbTranscludeDirective } = previousCompileContext;
2892
+ let hasTranscludeDirective = false;
2893
+ let hasTemplate = false;
2894
+ let { hasElementTranscludeDirective } = previousCompileContext;
2895
+ let $compileNode = (templateAttrs.$$element = jqLite(compileNode));
2896
+ let directive;
2897
+ let directiveName;
2898
+ let $template;
2899
+ let replaceDirective = originalReplaceDirective;
2900
+ let childTranscludeFn = transcludeFn;
2901
+ let linkFn;
2902
+ let didScanForMultipleTransclusion = false;
2903
+ let mightHaveMultipleTransclusionError = false;
2904
+ let directiveValue;
2905
+
2906
+ // executes all directives on the current element
2907
+ for (let i = 0, ii = directives.length; i < ii; i++) {
2908
+ directive = directives[i];
2909
+ const attrStart = directive.$$start;
2910
+ const attrEnd = directive.$$end;
2911
+
2912
+ // collect multiblock sections
2913
+ if (attrStart) {
2914
+ $compileNode = groupScan(compileNode, attrStart, attrEnd);
2915
+ }
2916
+ $template = undefined;
2917
+
2918
+ if (terminalPriority > directive.priority) {
2919
+ break; // prevent further processing of directives
2920
+ }
2921
+
2922
+ directiveValue = directive.scope;
2923
+
2924
+ if (directiveValue) {
2925
+ // skip the check for directives with async templates, we'll check the derived sync
2926
+ // directive when the template arrives
2927
+ if (!directive.templateUrl) {
2928
+ if (isObject(directiveValue)) {
2929
+ // This directive is trying to add an isolated scope.
2930
+ // Check that there is no scope of any kind already
2931
+ assertNoDuplicate(
2932
+ "new/isolated scope",
2933
+ newIsolateScopeDirective || newScopeDirective,
2934
+ directive,
2935
+ $compileNode,
2936
+ );
2937
+ newIsolateScopeDirective = directive;
2938
+ } else {
2939
+ // This directive is trying to add a child scope.
2940
+ // Check that there is no isolated scope already
2941
+ assertNoDuplicate(
2942
+ "new/isolated scope",
2943
+ newIsolateScopeDirective,
2944
+ directive,
2945
+ $compileNode,
2946
+ );
2947
+ }
2948
+ }
2949
+
2950
+ newScopeDirective = newScopeDirective || directive;
2951
+ }
2952
+
2953
+ directiveName = directive.name;
2954
+
2955
+ // If we encounter a condition that can result in transclusion on the directive,
2956
+ // then scan ahead in the remaining directives for others that may cause a multiple
2957
+ // transclusion error to be thrown during the compilation process. If a matching directive
2958
+ // is found, then we know that when we encounter a transcluded directive, we need to eagerly
2959
+ // compile the `transclude` function rather than doing it lazily in order to throw
2960
+ // exceptions at the correct time
2961
+ if (
2962
+ !didScanForMultipleTransclusion &&
2963
+ ((directive.replace &&
2964
+ (directive.templateUrl || directive.template)) ||
2965
+ (directive.transclude && !directive.$$tlb))
2966
+ ) {
2967
+ let candidateDirective;
2968
+
2969
+ for (
2970
+ let scanningIndex = i + 1;
2971
+ (candidateDirective = directives[scanningIndex++]);
2972
+
2973
+ ) {
2974
+ if (
2975
+ (candidateDirective.transclude && !candidateDirective.$$tlb) ||
2976
+ (candidateDirective.replace &&
2977
+ (candidateDirective.templateUrl ||
2978
+ candidateDirective.template))
2979
+ ) {
2980
+ mightHaveMultipleTransclusionError = true;
2981
+ break;
2982
+ }
2983
+ }
2984
+
2985
+ didScanForMultipleTransclusion = true;
2986
+ }
2987
+
2988
+ if (!directive.templateUrl && directive.controller) {
2989
+ controllerDirectives = controllerDirectives || createMap();
2990
+ assertNoDuplicate(
2991
+ `'${directiveName}' controller`,
2992
+ controllerDirectives[directiveName],
2993
+ directive,
2994
+ $compileNode,
2995
+ );
2996
+ controllerDirectives[directiveName] = directive;
2997
+ }
2998
+
2999
+ directiveValue = directive.transclude;
3000
+
3001
+ if (directiveValue) {
3002
+ hasTranscludeDirective = true;
3003
+
3004
+ // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
3005
+ // This option should only be used by directives that know how to safely handle element transclusion,
3006
+ // where the transcluded nodes are added or replaced after linking.
3007
+ if (!directive.$$tlb) {
3008
+ assertNoDuplicate(
3009
+ "transclusion",
3010
+ nonTlbTranscludeDirective,
3011
+ directive,
3012
+ $compileNode,
3013
+ );
3014
+ nonTlbTranscludeDirective = directive;
3015
+ }
3016
+
3017
+ if (directiveValue === "element") {
3018
+ hasElementTranscludeDirective = true;
3019
+ terminalPriority = directive.priority;
3020
+ $template = $compileNode;
3021
+ $compileNode = templateAttrs.$$element = jqLite(
3022
+ compile.$$createComment(
3023
+ directiveName,
3024
+ templateAttrs[directiveName],
3025
+ ),
3026
+ );
3027
+ compileNode = $compileNode[0];
3028
+ replaceWith(jqCollection, sliceArgs($template), compileNode);
3029
+
3030
+ childTranscludeFn = compilationGenerator(
3031
+ mightHaveMultipleTransclusionError,
3032
+ $template,
3033
+ transcludeFn,
3034
+ terminalPriority,
3035
+ replaceDirective && replaceDirective.name,
3036
+ {
3037
+ // Don't pass in:
3038
+ // - controllerDirectives - otherwise we'll create duplicates controllers
3039
+ // - newIsolateScopeDirective or templateDirective - combining templates with
3040
+ // element transclusion doesn't make sense.
3041
+ //
3042
+ // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
3043
+ // on the same element more than once.
3044
+ nonTlbTranscludeDirective,
3045
+ },
3046
+ );
3047
+ } else {
3048
+ const slots = createMap();
3049
+
3050
+ if (!isObject(directiveValue)) {
3051
+ $template = compileNode.cloneNode(true).childNodes;
3052
+ } else {
3053
+ // We have transclusion slots,
3054
+ // collect them up, compile them and store their transclusion functions
3055
+ $template = window.document.createDocumentFragment();
3056
+
3057
+ const slotMap = createMap();
3058
+ const filledSlots = createMap();
3059
+
3060
+ // Parse the element selectors
3061
+ forEach(directiveValue, (elementSelector, slotName) => {
3062
+ // If an element selector starts with a ? then it is optional
3063
+ const optional = elementSelector.charAt(0) === "?";
3064
+ elementSelector = optional
3065
+ ? elementSelector.substring(1)
3066
+ : elementSelector;
3067
+
3068
+ slotMap[elementSelector] = slotName;
3069
+
3070
+ // We explicitly assign `null` since this implies that a slot was defined but not filled.
3071
+ // Later when calling boundTransclusion functions with a slot name we only error if the
3072
+ // slot is `undefined`
3073
+ slots[slotName] = null;
3074
+
3075
+ // filledSlots contains `true` for all slots that are either optional or have been
3076
+ // filled. This is used to check that we have not missed any required slots
3077
+ filledSlots[slotName] = optional;
3078
+ });
3079
+
3080
+ // Add the matching elements into their slot
3081
+
3082
+ forEach(jqLite($compileNode[0].childNodes), (node) => {
3083
+ const slotName = slotMap[directiveNormalize(nodeName_(node))];
3084
+ if (slotName) {
3085
+ filledSlots[slotName] = true;
3086
+ slots[slotName] =
3087
+ slots[slotName] ||
3088
+ window.document.createDocumentFragment();
3089
+ slots[slotName].appendChild(node);
3090
+ } else {
3091
+ $template.appendChild(node);
3092
+ }
3093
+ });
3094
+
3095
+ // Check for required slots that were not filled
3096
+ forEach(filledSlots, (filled, slotName) => {
3097
+ if (!filled) {
3098
+ throw $compileMinErr(
3099
+ "reqslot",
3100
+ "Required transclusion slot `{0}` was not filled.",
3101
+ slotName,
3102
+ );
3103
+ }
3104
+ });
3105
+
3106
+ for (const slotName in slots) {
3107
+ if (slots[slotName]) {
3108
+ // Only define a transclusion function if the slot was filled
3109
+ const slotCompileNodes = jqLite(slots[slotName].childNodes);
3110
+ slots[slotName] = compilationGenerator(
3111
+ mightHaveMultipleTransclusionError,
3112
+ slotCompileNodes,
3113
+ transcludeFn,
3114
+ );
3115
+ }
3116
+ }
3117
+
3118
+ $template = jqLite($template.childNodes);
3119
+ }
3120
+
3121
+ $compileNode.empty(); // clear contents
3122
+ childTranscludeFn = compilationGenerator(
3123
+ mightHaveMultipleTransclusionError,
3124
+ $template,
3125
+ transcludeFn,
3126
+ undefined,
3127
+ undefined,
3128
+ {
3129
+ needsNewScope:
3130
+ directive.$$isolateScope || directive.$$newScope,
3131
+ },
3132
+ );
3133
+ childTranscludeFn.$$slots = slots;
3134
+ }
3135
+ }
3136
+
3137
+ if (directive.template) {
3138
+ hasTemplate = true;
3139
+ assertNoDuplicate(
3140
+ "template",
3141
+ templateDirective,
3142
+ directive,
3143
+ $compileNode,
3144
+ );
3145
+ templateDirective = directive;
3146
+
3147
+ directiveValue = isFunction(directive.template)
3148
+ ? directive.template($compileNode, templateAttrs)
3149
+ : directive.template;
3150
+
3151
+ directiveValue = denormalizeTemplate(directiveValue);
3152
+
3153
+ if (directive.replace) {
3154
+ replaceDirective = directive;
3155
+ if (isTextNode(directiveValue)) {
3156
+ $template = [];
3157
+ } else {
3158
+ $template = removeComments(
3159
+ wrapTemplate(
3160
+ directive.templateNamespace,
3161
+ trim(directiveValue),
3162
+ ),
3163
+ );
3164
+ }
3165
+ compileNode = $template[0];
3166
+
3167
+ if (
3168
+ $template.length !== 1 ||
3169
+ compileNode.nodeType !== Node.ELEMENT_NODE
3170
+ ) {
3171
+ throw $compileMinErr(
3172
+ "tplrt",
3173
+ "Template for directive '{0}' must have exactly one root element. {1}",
3174
+ directiveName,
3175
+ "",
3176
+ );
3177
+ }
3178
+
3179
+ replaceWith(jqCollection, $compileNode, compileNode);
3180
+
3181
+ const newTemplateAttrs = { $attr: {} };
3182
+
3183
+ // combine directives from the original node and from the template:
3184
+ // - take the array of directives for this element
3185
+ // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
3186
+ // - collect directives from the template and sort them by priority
3187
+ // - combine directives as: processed + template + unprocessed
3188
+ const templateDirectives = collectDirectives(
3189
+ compileNode,
3190
+ [],
3191
+ newTemplateAttrs,
3192
+ );
3193
+ const unprocessedDirectives = directives.splice(
3194
+ i + 1,
3195
+ directives.length - (i + 1),
3196
+ );
3197
+
3198
+ if (newIsolateScopeDirective || newScopeDirective) {
3199
+ // The original directive caused the current element to be replaced but this element
3200
+ // also needs to have a new scope, so we need to tell the template directives
3201
+ // that they would need to get their scope from further up, if they require transclusion
3202
+ markDirectiveScope(
3203
+ templateDirectives,
3204
+ newIsolateScopeDirective,
3205
+ newScopeDirective,
3206
+ );
3207
+ }
3208
+ directives = directives
3209
+ .concat(templateDirectives)
3210
+ .concat(unprocessedDirectives);
3211
+ mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
3212
+
3213
+ ii = directives.length;
3214
+ } else {
3215
+ $compileNode.html(directiveValue);
3216
+ }
3217
+ }
3218
+
3219
+ if (directive.templateUrl) {
3220
+ hasTemplate = true;
3221
+ assertNoDuplicate(
3222
+ "template",
3223
+ templateDirective,
3224
+ directive,
3225
+ $compileNode,
3226
+ );
3227
+ templateDirective = directive;
3228
+
3229
+ if (directive.replace) {
3230
+ replaceDirective = directive;
3231
+ }
3232
+
3233
+ nodeLinkFn = compileTemplateUrl(
3234
+ directives.splice(i, directives.length - i),
3235
+ $compileNode,
3236
+ templateAttrs,
3237
+ jqCollection,
3238
+ hasTranscludeDirective && childTranscludeFn,
3239
+ preLinkFns,
3240
+ postLinkFns,
3241
+ {
3242
+ controllerDirectives,
3243
+ newScopeDirective:
3244
+ newScopeDirective !== directive && newScopeDirective,
3245
+ newIsolateScopeDirective,
3246
+ templateDirective,
3247
+ nonTlbTranscludeDirective,
3248
+ },
3249
+ );
3250
+ ii = directives.length;
3251
+ } else if (directive.compile) {
3252
+ try {
3253
+ linkFn = directive.compile(
3254
+ $compileNode,
3255
+ templateAttrs,
3256
+ childTranscludeFn,
3257
+ );
3258
+ const context = directive.$$originalDirective || directive;
3259
+ if (isFunction(linkFn)) {
3260
+ addLinkFns(null, bind(context, linkFn), attrStart, attrEnd);
3261
+ } else if (linkFn) {
3262
+ addLinkFns(
3263
+ bind(context, linkFn.pre),
3264
+ bind(context, linkFn.post),
3265
+ attrStart,
3266
+ attrEnd,
3267
+ );
3268
+ }
3269
+ } catch (e) {
3270
+ $exceptionHandler(e, startingTag($compileNode));
3271
+ }
3272
+ }
3273
+
3274
+ if (directive.terminal) {
3275
+ nodeLinkFn.terminal = true;
3276
+ terminalPriority = Math.max(terminalPriority, directive.priority);
3277
+ }
3278
+ }
3279
+
3280
+ nodeLinkFn.scope =
3281
+ newScopeDirective && newScopeDirective.scope === true;
3282
+ nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
3283
+ nodeLinkFn.templateOnThisElement = hasTemplate;
3284
+ nodeLinkFn.transclude = childTranscludeFn;
3285
+
3286
+ previousCompileContext.hasElementTranscludeDirective =
3287
+ hasElementTranscludeDirective;
3288
+
3289
+ // might be normal or delayed nodeLinkFn depending on if templateUrl is present
3290
+ return nodeLinkFn;
3291
+
3292
+ /// /////////////////
3293
+
3294
+ function addLinkFns(pre, post, attrStart, attrEnd) {
3295
+ if (pre) {
3296
+ if (attrStart)
3297
+ pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
3298
+ pre.require = directive.require;
3299
+ pre.directiveName = directiveName;
3300
+ if (
3301
+ newIsolateScopeDirective === directive ||
3302
+ directive.$$isolateScope
3303
+ ) {
3304
+ pre = cloneAndAnnotateFn(pre, { isolateScope: true });
3305
+ }
3306
+ preLinkFns.push(pre);
3307
+ }
3308
+ if (post) {
3309
+ if (attrStart)
3310
+ post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
3311
+ post.require = directive.require;
3312
+ post.directiveName = directiveName;
3313
+ if (
3314
+ newIsolateScopeDirective === directive ||
3315
+ directive.$$isolateScope
3316
+ ) {
3317
+ post = cloneAndAnnotateFn(post, { isolateScope: true });
3318
+ }
3319
+ postLinkFns.push(post);
3320
+ }
3321
+ }
3322
+
3323
+ function nodeLinkFn(
3324
+ childLinkFn,
3325
+ scope,
3326
+ linkNode,
3327
+ $rootElement,
3328
+ boundTranscludeFn,
3329
+ ) {
3330
+ let i;
3331
+ let ii;
3332
+ let linkFn;
3333
+ let isolateScope;
3334
+ let controllerScope;
3335
+ let elementControllers;
3336
+ let transcludeFn;
3337
+ let $element;
3338
+ let attrs;
3339
+ let scopeBindingInfo;
3340
+
3341
+ if (compileNode === linkNode) {
3342
+ attrs = templateAttrs;
3343
+ $element = templateAttrs.$$element;
3344
+ } else {
3345
+ $element = jqLite(linkNode);
3346
+ attrs = new Attributes($element, templateAttrs);
3347
+ }
3348
+
3349
+ controllerScope = scope;
3350
+ if (newIsolateScopeDirective) {
3351
+ isolateScope = scope.$new(true);
3352
+ } else if (newScopeDirective) {
3353
+ controllerScope = scope.$parent;
3354
+ }
3355
+
3356
+ if (boundTranscludeFn) {
3357
+ // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
3358
+ // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
3359
+ transcludeFn = controllersBoundTransclude;
3360
+ transcludeFn.$$boundTransclude = boundTranscludeFn;
3361
+ // expose the slots on the `$transclude` function
3362
+ transcludeFn.isSlotFilled = function (slotName) {
3363
+ return !!boundTranscludeFn.$$slots[slotName];
3364
+ };
3365
+ }
3366
+
3367
+ if (controllerDirectives) {
3368
+ elementControllers = setupControllers(
3369
+ $element,
3370
+ attrs,
3371
+ transcludeFn,
3372
+ controllerDirectives,
3373
+ isolateScope,
3374
+ scope,
3375
+ newIsolateScopeDirective,
3376
+ );
3377
+ }
3378
+
3379
+ if (newIsolateScopeDirective) {
3380
+ // Initialize isolate scope bindings for new isolate scope directive.
3381
+ compile.$$addScopeInfo(
3382
+ $element,
3383
+ isolateScope,
3384
+ true,
3385
+ !(
3386
+ templateDirective &&
3387
+ (templateDirective === newIsolateScopeDirective ||
3388
+ templateDirective ===
3389
+ newIsolateScopeDirective.$$originalDirective)
3390
+ ),
3391
+ );
3392
+
3393
+ isolateScope.$$isolateBindings =
3394
+ newIsolateScopeDirective.$$isolateBindings;
3395
+ scopeBindingInfo = initializeDirectiveBindings(
3396
+ scope,
3397
+ attrs,
3398
+ isolateScope,
3399
+ isolateScope.$$isolateBindings,
3400
+ newIsolateScopeDirective,
3401
+ );
3402
+ if (scopeBindingInfo.removeWatches) {
3403
+ isolateScope.$on("$destroy", scopeBindingInfo.removeWatches);
3404
+ }
3405
+ }
3406
+
3407
+ // Initialize bindToController bindings
3408
+ for (const name in elementControllers) {
3409
+ const controllerDirective = controllerDirectives[name];
3410
+ const controller = elementControllers[name];
3411
+ const bindings = controllerDirective.$$bindings.bindToController;
3412
+
3413
+ controller.instance = controller();
3414
+ $element.data(
3415
+ `$${controllerDirective.name}Controller`,
3416
+ controller.instance,
3417
+ );
3418
+ controller.bindingInfo = initializeDirectiveBindings(
3419
+ controllerScope,
3420
+ attrs,
3421
+ controller.instance,
3422
+ bindings,
3423
+ controllerDirective,
3424
+ );
3425
+ }
3426
+
3427
+ // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
3428
+ forEach(controllerDirectives, (controllerDirective, name) => {
3429
+ const { require } = controllerDirective;
3430
+ if (
3431
+ controllerDirective.bindToController &&
3432
+ !isArray(require) &&
3433
+ isObject(require)
3434
+ ) {
3435
+ extend(
3436
+ elementControllers[name].instance,
3437
+ getControllers(name, require, $element, elementControllers),
3438
+ );
3439
+ }
3440
+ });
3441
+
3442
+ // Handle the init and destroy lifecycle hooks on all controllers that have them
3443
+ forEach(elementControllers, (controller) => {
3444
+ const controllerInstance = controller.instance;
3445
+ if (isFunction(controllerInstance.$onChanges)) {
3446
+ try {
3447
+ controllerInstance.$onChanges(
3448
+ controller.bindingInfo.initialChanges,
3449
+ );
3450
+ } catch (e) {
3451
+ $exceptionHandler(e);
3452
+ }
3453
+ }
3454
+ if (isFunction(controllerInstance.$onInit)) {
3455
+ try {
3456
+ controllerInstance.$onInit();
3457
+ } catch (e) {
3458
+ $exceptionHandler(e);
3459
+ }
3460
+ }
3461
+ if (isFunction(controllerInstance.$doCheck)) {
3462
+ controllerScope.$watch(() => {
3463
+ controllerInstance.$doCheck();
3464
+ });
3465
+ controllerInstance.$doCheck();
3466
+ }
3467
+ if (isFunction(controllerInstance.$onDestroy)) {
3468
+ controllerScope.$on("$destroy", () => {
3469
+ controllerInstance.$onDestroy();
3470
+ });
3471
+ }
3472
+ });
3473
+
3474
+ // PRELINKING
3475
+ for (i = 0, ii = preLinkFns.length; i < ii; i++) {
3476
+ linkFn = preLinkFns[i];
3477
+ invokeLinkFn(
3478
+ linkFn,
3479
+ linkFn.isolateScope ? isolateScope : scope,
3480
+ $element,
3481
+ attrs,
3482
+ linkFn.require &&
3483
+ getControllers(
3484
+ linkFn.directiveName,
3485
+ linkFn.require,
3486
+ $element,
3487
+ elementControllers,
3488
+ ),
3489
+ transcludeFn,
3490
+ );
3491
+ }
3492
+
3493
+ // RECURSION
3494
+ // We only pass the isolate scope, if the isolate directive has a template,
3495
+ // otherwise the child elements do not belong to the isolate directive.
3496
+ var scopeToChild = scope;
3497
+ if (
3498
+ newIsolateScopeDirective &&
3499
+ (newIsolateScopeDirective.template ||
3500
+ newIsolateScopeDirective.templateUrl === null)
3501
+ ) {
3502
+ scopeToChild = isolateScope;
3503
+ }
3504
+ if (childLinkFn) {
3505
+ childLinkFn(
3506
+ scopeToChild,
3507
+ linkNode.childNodes,
3508
+ undefined,
3509
+ boundTranscludeFn,
3510
+ );
3511
+ }
3512
+
3513
+ // POSTLINKING
3514
+ for (i = postLinkFns.length - 1; i >= 0; i--) {
3515
+ linkFn = postLinkFns[i];
3516
+ invokeLinkFn(
3517
+ linkFn,
3518
+ linkFn.isolateScope ? isolateScope : scope,
3519
+ $element,
3520
+ attrs,
3521
+ linkFn.require &&
3522
+ getControllers(
3523
+ linkFn.directiveName,
3524
+ linkFn.require,
3525
+ $element,
3526
+ elementControllers,
3527
+ ),
3528
+ transcludeFn,
3529
+ );
3530
+ }
3531
+
3532
+ // Trigger $postLink lifecycle hooks
3533
+ forEach(elementControllers, (controller) => {
3534
+ const controllerInstance = controller.instance;
3535
+ if (isFunction(controllerInstance.$postLink)) {
3536
+ controllerInstance.$postLink();
3537
+ }
3538
+ });
3539
+
3540
+ // This is the function that is injected as `$transclude`.
3541
+ // Note: all arguments are optional!
3542
+ function controllersBoundTransclude(
3543
+ scope,
3544
+ cloneAttachFn,
3545
+ futureParentElement,
3546
+ slotName,
3547
+ ) {
3548
+ let transcludeControllers;
3549
+ // No scope passed in:
3550
+ if (!isScope(scope)) {
3551
+ slotName = futureParentElement;
3552
+ futureParentElement = cloneAttachFn;
3553
+ cloneAttachFn = scope;
3554
+ scope = undefined;
3555
+ }
3556
+
3557
+ if (hasElementTranscludeDirective) {
3558
+ transcludeControllers = elementControllers;
3559
+ }
3560
+ if (!futureParentElement) {
3561
+ futureParentElement = hasElementTranscludeDirective
3562
+ ? $element.parent()
3563
+ : $element;
3564
+ }
3565
+ if (slotName) {
3566
+ // slotTranscludeFn can be one of three things:
3567
+ // * a transclude function - a filled slot
3568
+ // * `null` - an optional slot that was not filled
3569
+ // * `undefined` - a slot that was not declared (i.e. invalid)
3570
+ const slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
3571
+ if (slotTranscludeFn) {
3572
+ return slotTranscludeFn(
3573
+ scope,
3574
+ cloneAttachFn,
3575
+ transcludeControllers,
3576
+ futureParentElement,
3577
+ scopeToChild,
3578
+ );
3579
+ }
3580
+ if (isUndefined(slotTranscludeFn)) {
3581
+ throw $compileMinErr(
3582
+ "noslot",
3583
+ 'No parent directive that requires a transclusion with slot name "{0}". ' +
3584
+ "Element: {1}",
3585
+ slotName,
3586
+ startingTag($element),
3587
+ );
3588
+ }
3589
+ } else {
3590
+ return boundTranscludeFn(
3591
+ scope,
3592
+ cloneAttachFn,
3593
+ transcludeControllers,
3594
+ futureParentElement,
3595
+ scopeToChild,
3596
+ );
3597
+ }
3598
+ }
3599
+ }
3600
+ }
3601
+
3602
+ function getControllers(
3603
+ directiveName,
3604
+ require,
3605
+ $element,
3606
+ elementControllers,
3607
+ ) {
3608
+ let value;
3609
+
3610
+ if (isString(require)) {
3611
+ const match = require.match(REQUIRE_PREFIX_REGEXP);
3612
+ const name = require.substring(match[0].length);
3613
+ const inheritType = match[1] || match[3];
3614
+ const optional = match[2] === "?";
3615
+
3616
+ // If only parents then start at the parent element
3617
+ if (inheritType === "^^") {
3618
+ $element = $element.parent();
3619
+ // Otherwise attempt getting the controller from elementControllers in case
3620
+ // the element is transcluded (and has no data) and to avoid .data if possible
3621
+ } else {
3622
+ value = elementControllers && elementControllers[name];
3623
+ value = value && value.instance;
3624
+ }
3625
+
3626
+ if (!value) {
3627
+ const dataName = `$${name}Controller`;
3628
+
3629
+ if (
3630
+ inheritType === "^^" &&
3631
+ $element[0] &&
3632
+ $element[0].nodeType === Node.DOCUMENT_NODE
3633
+ ) {
3634
+ // inheritedData() uses the documentElement when it finds the document, so we would
3635
+ // require from the element itself.
3636
+ value = null;
3637
+ } else {
3638
+ value = inheritType
3639
+ ? $element.inheritedData(dataName)
3640
+ : $element.data(dataName);
3641
+ }
3642
+ }
3643
+
3644
+ if (!value && !optional) {
3645
+ throw $compileMinErr(
3646
+ "ctreq",
3647
+ "Controller '{0}', required by directive '{1}', can't be found!",
3648
+ name,
3649
+ directiveName,
3650
+ );
3651
+ }
3652
+ } else if (isArray(require)) {
3653
+ value = [];
3654
+ for (let i = 0, ii = require.length; i < ii; i++) {
3655
+ value[i] = getControllers(
3656
+ directiveName,
3657
+ require[i],
3658
+ $element,
3659
+ elementControllers,
3660
+ );
3661
+ }
3662
+ } else if (isObject(require)) {
3663
+ value = {};
3664
+ forEach(require, (controller, property) => {
3665
+ value[property] = getControllers(
3666
+ directiveName,
3667
+ controller,
3668
+ $element,
3669
+ elementControllers,
3670
+ );
3671
+ });
3672
+ }
3673
+
3674
+ return value || null;
3675
+ }
3676
+
3677
+ function setupControllers(
3678
+ $element,
3679
+ attrs,
3680
+ transcludeFn,
3681
+ controllerDirectives,
3682
+ isolateScope,
3683
+ scope,
3684
+ newIsolateScopeDirective,
3685
+ ) {
3686
+ const elementControllers = createMap();
3687
+ for (const controllerKey in controllerDirectives) {
3688
+ const directive = controllerDirectives[controllerKey];
3689
+ const locals = {
3690
+ $scope:
3691
+ directive === newIsolateScopeDirective || directive.$$isolateScope
3692
+ ? isolateScope
3693
+ : scope,
3694
+ $element,
3695
+ $attrs: attrs,
3696
+ $transclude: transcludeFn,
3697
+ };
3698
+
3699
+ let { controller } = directive;
3700
+ if (controller === "@") {
3701
+ controller = attrs[directive.name];
3702
+ }
3703
+
3704
+ const controllerInstance = $controller(
3705
+ controller,
3706
+ locals,
3707
+ true,
3708
+ directive.controllerAs,
3709
+ );
3710
+
3711
+ // For directives with element transclusion the element is a comment.
3712
+ // In this case .data will not attach any data.
3713
+ // Instead, we save the controllers for the element in a local hash and attach to .data
3714
+ // later, once we have the actual element.
3715
+ elementControllers[directive.name] = controllerInstance;
3716
+ $element.data(
3717
+ `$${directive.name}Controller`,
3718
+ controllerInstance.instance,
3719
+ );
3720
+ }
3721
+ return elementControllers;
3722
+ }
3723
+
3724
+ // Depending upon the context in which a directive finds itself it might need to have a new isolated
3725
+ // or child scope created. For instance:
3726
+ // * if the directive has been pulled into a template because another directive with a higher priority
3727
+ // asked for element transclusion
3728
+ // * if the directive itself asks for transclusion but it is at the root of a template and the original
3729
+ // element was replaced. See https://github.com/angular/angular.js/issues/12936
3730
+ function markDirectiveScope(directives, isolateScope, newScope) {
3731
+ for (let j = 0, jj = directives.length; j < jj; j++) {
3732
+ directives[j] = inherit(directives[j], {
3733
+ $$isolateScope: isolateScope,
3734
+ $$newScope: newScope,
3735
+ });
3736
+ }
3737
+ }
3738
+
3739
+ /**
3740
+ * looks up the directive and decorates it with exception handling and proper parameters. We
3741
+ * call this the boundDirective.
3742
+ *
3743
+ * @param {string} name name of the directive to look up.
3744
+ * @param {string} location The directive must be found in specific format.
3745
+ * String containing any of theses characters:
3746
+ *
3747
+ * * `E`: element name
3748
+ * * `A': attribute
3749
+ * * `C`: class
3750
+ * * `M`: comment
3751
+ * @returns {boolean} true if directive was added.
3752
+ */
3753
+ function addDirective(
3754
+ tDirectives,
3755
+ name,
3756
+ location,
3757
+ maxPriority,
3758
+ ignoreDirective,
3759
+ startAttrName,
3760
+ endAttrName,
3761
+ ) {
3762
+ if (name === ignoreDirective) return null;
3763
+ let match = null;
3764
+ if (Object.prototype.hasOwnProperty.call(hasDirectives, name)) {
3765
+ for (
3766
+ let directive,
3767
+ directives = $injector.get(name + Suffix),
3768
+ i = 0,
3769
+ ii = directives.length;
3770
+ i < ii;
3771
+ i++
3772
+ ) {
3773
+ directive = directives[i];
3774
+ if (
3775
+ (isUndefined(maxPriority) || maxPriority > directive.priority) &&
3776
+ directive.restrict.indexOf(location) !== -1
3777
+ ) {
3778
+ if (startAttrName) {
3779
+ directive = inherit(directive, {
3780
+ $$start: startAttrName,
3781
+ $$end: endAttrName,
3782
+ });
3783
+ }
3784
+ if (!directive.$$bindings) {
3785
+ const bindings = (directive.$$bindings = parseDirectiveBindings(
3786
+ directive,
3787
+ directive.name,
3788
+ ));
3789
+ if (isObject(bindings.isolateScope)) {
3790
+ directive.$$isolateBindings = bindings.isolateScope;
3791
+ }
3792
+ }
3793
+ tDirectives.push(directive);
3794
+ match = directive;
3795
+ }
3796
+ }
3797
+ }
3798
+ return match;
3799
+ }
3800
+
3801
+ /**
3802
+ * looks up the directive and returns true if it is a multi-element directive,
3803
+ * and therefore requires DOM nodes between -start and -end markers to be grouped
3804
+ * together. Example: `<div my-directive-start></div><div><div/><div my-directive-end></div>`
3805
+ *
3806
+ * @param {string} name name of the directive to look up.
3807
+ * @returns true if directive was registered as multi-element.
3808
+ */
3809
+ function directiveIsMultiElement(name) {
3810
+ if (Object.prototype.hasOwnProperty.call(hasDirectives, name)) {
3811
+ for (
3812
+ let directive,
3813
+ directives = $injector.get(name + Suffix),
3814
+ i = 0,
3815
+ ii = directives.length;
3816
+ i < ii;
3817
+ i++
3818
+ ) {
3819
+ directive = directives[i];
3820
+ if (directive.multiElement) {
3821
+ return true;
3822
+ }
3823
+ }
3824
+ }
3825
+ return false;
3826
+ }
3827
+
3828
+ /**
3829
+ * When the element is replaced with HTML template then the new attributes
3830
+ * on the template need to be merged with the existing attributes in the DOM.
3831
+ * The desired effect is to have both of the attributes present.
3832
+ *
3833
+ * @param {object} dst destination attributes (original DOM)
3834
+ * @param {object} src source attributes (from the directive template)
3835
+ */
3836
+ function mergeTemplateAttributes(dst, src) {
3837
+ const srcAttr = src.$attr;
3838
+ const dstAttr = dst.$attr;
3839
+
3840
+ // reapply the old attributes to the new element
3841
+ forEach(dst, (value, key) => {
3842
+ if (key.charAt(0) !== "$") {
3843
+ if (src[key] && src[key] !== value) {
3844
+ if (value.length) {
3845
+ value += (key === "style" ? ";" : " ") + src[key];
3846
+ } else {
3847
+ value = src[key];
3848
+ }
3849
+ }
3850
+ dst.$set(key, value, true, srcAttr[key]);
3851
+ }
3852
+ });
3853
+
3854
+ // copy the new attributes on the old attrs object
3855
+ forEach(src, (value, key) => {
3856
+ // Check if we already set this attribute in the loop above.
3857
+ // `dst` will never contain hasOwnProperty as DOM parser won't let it.
3858
+ // You will get an "InvalidCharacterError: DOM Exception 5" error if you
3859
+ // have an attribute like "has-own-property" or "data-has-own-property", etc.
3860
+ if (
3861
+ !Object.prototype.hasOwnProperty.call(dst, key) &&
3862
+ key.charAt(0) !== "$"
3863
+ ) {
3864
+ dst[key] = value;
3865
+
3866
+ if (key !== "class" && key !== "style") {
3867
+ dstAttr[key] = srcAttr[key];
3868
+ }
3869
+ }
3870
+ });
3871
+ }
3872
+
3873
+ function compileTemplateUrl(
3874
+ directives,
3875
+ $compileNode,
3876
+ tAttrs,
3877
+ $rootElement,
3878
+ childTranscludeFn,
3879
+ preLinkFns,
3880
+ postLinkFns,
3881
+ previousCompileContext,
3882
+ ) {
3883
+ let linkQueue = [];
3884
+ let afterTemplateNodeLinkFn;
3885
+ let afterTemplateChildLinkFn;
3886
+ const beforeTemplateCompileNode = $compileNode[0];
3887
+ const origAsyncDirective = directives.shift();
3888
+ const derivedSyncDirective = inherit(origAsyncDirective, {
3889
+ templateUrl: null,
3890
+ transclude: null,
3891
+ replace: null,
3892
+ $$originalDirective: origAsyncDirective,
3893
+ });
3894
+ const templateUrl = isFunction(origAsyncDirective.templateUrl)
3895
+ ? origAsyncDirective.templateUrl($compileNode, tAttrs)
3896
+ : origAsyncDirective.templateUrl;
3897
+ const { templateNamespace } = origAsyncDirective;
3898
+
3899
+ $compileNode.empty();
3900
+
3901
+ $templateRequest(templateUrl)
3902
+ .then((content) => {
3903
+ let compileNode;
3904
+ let tempTemplateAttrs;
3905
+ let $template;
3906
+ let childBoundTranscludeFn;
3907
+
3908
+ content = denormalizeTemplate(content);
3909
+
3910
+ if (origAsyncDirective.replace) {
3911
+ if (isTextNode(content)) {
3912
+ $template = [];
3913
+ } else {
3914
+ $template = removeComments(
3915
+ wrapTemplate(templateNamespace, trim(content)),
3916
+ );
3917
+ }
3918
+ compileNode = $template[0];
3919
+
3920
+ if (
3921
+ $template.length !== 1 ||
3922
+ compileNode.nodeType !== Node.ELEMENT_NODE
3923
+ ) {
3924
+ throw $compileMinErr(
3925
+ "tplrt",
3926
+ "Template for directive '{0}' must have exactly one root element. {1}",
3927
+ origAsyncDirective.name,
3928
+ templateUrl,
3929
+ );
3930
+ }
3931
+
3932
+ tempTemplateAttrs = { $attr: {} };
3933
+ replaceWith($rootElement, $compileNode, compileNode);
3934
+ const templateDirectives = collectDirectives(
3935
+ compileNode,
3936
+ [],
3937
+ tempTemplateAttrs,
3938
+ );
3939
+
3940
+ if (isObject(origAsyncDirective.scope)) {
3941
+ // the original directive that caused the template to be loaded async required
3942
+ // an isolate scope
3943
+ markDirectiveScope(templateDirectives, true);
3944
+ }
3945
+ directives = templateDirectives.concat(directives);
3946
+ mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
3947
+ } else {
3948
+ compileNode = beforeTemplateCompileNode;
3949
+ $compileNode.html(content);
3950
+ }
3951
+
3952
+ directives.unshift(derivedSyncDirective);
3953
+
3954
+ afterTemplateNodeLinkFn = applyDirectivesToNode(
3955
+ directives,
3956
+ compileNode,
3957
+ tAttrs,
3958
+ childTranscludeFn,
3959
+ $compileNode,
3960
+ origAsyncDirective,
3961
+ preLinkFns,
3962
+ postLinkFns,
3963
+ previousCompileContext,
3964
+ );
3965
+ forEach($rootElement, (node, i) => {
3966
+ if (node === compileNode) {
3967
+ $rootElement[i] = $compileNode[0];
3968
+ }
3969
+ });
3970
+ afterTemplateChildLinkFn = compileNodes(
3971
+ $compileNode[0].childNodes,
3972
+ childTranscludeFn,
3973
+ );
3974
+
3975
+ while (linkQueue.length) {
3976
+ const scope = linkQueue.shift();
3977
+ const beforeTemplateLinkNode = linkQueue.shift();
3978
+ const linkRootElement = linkQueue.shift();
3979
+ const boundTranscludeFn = linkQueue.shift();
3980
+ let linkNode = $compileNode[0];
3981
+
3982
+ if (scope.$$destroyed) continue;
3983
+
3984
+ if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
3985
+ const oldClasses = beforeTemplateLinkNode.className;
3986
+
3987
+ if (
3988
+ !(
3989
+ previousCompileContext.hasElementTranscludeDirective &&
3990
+ origAsyncDirective.replace
3991
+ )
3992
+ ) {
3993
+ // it was cloned therefore we have to clone as well.
3994
+ linkNode = compileNode.cloneNode(true);
3995
+ }
3996
+ replaceWith(
3997
+ linkRootElement,
3998
+ jqLite(beforeTemplateLinkNode),
3999
+ linkNode,
4000
+ );
4001
+
4002
+ // Copy in CSS classes from original node
4003
+ safeAddClass(jqLite(linkNode), oldClasses);
4004
+ }
4005
+ if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
4006
+ childBoundTranscludeFn = createBoundTranscludeFn(
4007
+ scope,
4008
+ afterTemplateNodeLinkFn.transclude,
4009
+ boundTranscludeFn,
4010
+ );
4011
+ } else {
4012
+ childBoundTranscludeFn = boundTranscludeFn;
4013
+ }
4014
+ afterTemplateNodeLinkFn(
4015
+ afterTemplateChildLinkFn,
4016
+ scope,
4017
+ linkNode,
4018
+ $rootElement,
4019
+ childBoundTranscludeFn,
4020
+ );
4021
+ }
4022
+ linkQueue = null;
4023
+ })
4024
+ .catch((error) => {
4025
+ if (isError(error)) {
4026
+ $exceptionHandler(error);
4027
+ }
4028
+ });
4029
+
4030
+ return function delayedNodeLinkFn(
4031
+ ignoreChildLinkFn,
4032
+ scope,
4033
+ node,
4034
+ rootElement,
4035
+ boundTranscludeFn,
4036
+ ) {
4037
+ let childBoundTranscludeFn = boundTranscludeFn;
4038
+ if (scope.$$destroyed) return;
4039
+ if (linkQueue) {
4040
+ linkQueue.push(scope, node, rootElement, childBoundTranscludeFn);
4041
+ } else {
4042
+ if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
4043
+ childBoundTranscludeFn = createBoundTranscludeFn(
4044
+ scope,
4045
+ afterTemplateNodeLinkFn.transclude,
4046
+ boundTranscludeFn,
4047
+ );
4048
+ }
4049
+ afterTemplateNodeLinkFn(
4050
+ afterTemplateChildLinkFn,
4051
+ scope,
4052
+ node,
4053
+ rootElement,
4054
+ childBoundTranscludeFn,
4055
+ );
4056
+ }
4057
+ };
4058
+ }
4059
+
4060
+ /**
4061
+ * Sorting function for bound directives.
4062
+ */
4063
+ function byPriority(a, b) {
4064
+ const diff = b.priority - a.priority;
4065
+ if (diff !== 0) return diff;
4066
+ if (a.name !== b.name) return a.name < b.name ? -1 : 1;
4067
+ return a.index - b.index;
4068
+ }
4069
+
4070
+ function assertNoDuplicate(what, previousDirective, directive, element) {
4071
+ function wrapModuleNameIfDefined(moduleName) {
4072
+ return moduleName ? ` (module: ${moduleName})` : "";
4073
+ }
4074
+
4075
+ if (previousDirective) {
4076
+ throw $compileMinErr(
4077
+ "multidir",
4078
+ "Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}",
4079
+ previousDirective.name,
4080
+ wrapModuleNameIfDefined(previousDirective.$$moduleName),
4081
+ directive.name,
4082
+ wrapModuleNameIfDefined(directive.$$moduleName),
4083
+ what,
4084
+ startingTag(element),
4085
+ );
4086
+ }
4087
+ }
4088
+
4089
+ function addTextInterpolateDirective(directives, text) {
4090
+ const interpolateFn = $interpolate(text, true);
4091
+ if (interpolateFn) {
4092
+ directives.push({
4093
+ priority: 0,
4094
+ compile: function textInterpolateCompileFn() {
4095
+ // When transcluding a template that has bindings in the root
4096
+ // we don't have a parent and thus need to add the class during linking fn.
4097
+
4098
+ return function textInterpolateLinkFn(scope, node) {
4099
+ scope.$watch(interpolateFn, (value) => {
4100
+ node[0].nodeValue = value;
4101
+ });
4102
+ };
4103
+ },
4104
+ });
4105
+ }
4106
+ }
4107
+
4108
+ function wrapTemplate(type, template) {
4109
+ type = lowercase(type || "html");
4110
+ switch (type) {
4111
+ case "svg":
4112
+ case "math":
4113
+ var wrapper = window.document.createElement("div");
4114
+ wrapper.innerHTML = `<${type}>${template}</${type}>`;
4115
+ return wrapper.childNodes[0].childNodes;
4116
+ default:
4117
+ return template;
4118
+ }
4119
+ }
4120
+
4121
+ function getTrustedAttrContext(nodeName, attrNormalizedName) {
4122
+ if (attrNormalizedName === "srcdoc") {
4123
+ return $sce.HTML;
4124
+ }
4125
+ // All nodes with src attributes require a RESOURCE_URL value, except for
4126
+ // img and various html5 media nodes, which require the MEDIA_URL context.
4127
+ if (attrNormalizedName === "src" || attrNormalizedName === "ngSrc") {
4128
+ if (
4129
+ ["img", "video", "audio", "source", "track"].indexOf(nodeName) ===
4130
+ -1
4131
+ ) {
4132
+ return $sce.RESOURCE_URL;
4133
+ }
4134
+ return $sce.MEDIA_URL;
4135
+ }
4136
+ if (attrNormalizedName === "xlinkHref") {
4137
+ // Some xlink:href are okay, most aren't
4138
+ if (nodeName === "image") return $sce.MEDIA_URL;
4139
+ if (nodeName === "a") return $sce.URL;
4140
+ return $sce.RESOURCE_URL;
4141
+ }
4142
+ if (
4143
+ // Formaction
4144
+ (nodeName === "form" && attrNormalizedName === "action") ||
4145
+ // If relative URLs can go where they are not expected to, then
4146
+ // all sorts of trust issues can arise.
4147
+ (nodeName === "base" && attrNormalizedName === "href") ||
4148
+ // links can be stylesheets or imports, which can run script in the current origin
4149
+ (nodeName === "link" && attrNormalizedName === "href")
4150
+ ) {
4151
+ return $sce.RESOURCE_URL;
4152
+ }
4153
+ if (
4154
+ nodeName === "a" &&
4155
+ (attrNormalizedName === "href" || attrNormalizedName === "ngHref")
4156
+ ) {
4157
+ return $sce.URL;
4158
+ }
4159
+ }
4160
+
4161
+ function getTrustedPropContext(nodeName, propNormalizedName) {
4162
+ const prop = propNormalizedName.toLowerCase();
4163
+ return (
4164
+ PROP_CONTEXTS[`${nodeName}|${prop}`] || PROP_CONTEXTS[`*|${prop}`]
4165
+ );
4166
+ }
4167
+
4168
+ function sanitizeSrcsetPropertyValue(value) {
4169
+ return sanitizeSrcset($sce.valueOf(value), "ng-prop-srcset");
4170
+ }
4171
+ function addPropertyDirective(node, directives, attrName, propName) {
4172
+ if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) {
4173
+ throw $compileMinErr(
4174
+ "nodomevents",
4175
+ "Property bindings for HTML DOM event properties are disallowed",
4176
+ );
4177
+ }
4178
+
4179
+ const nodeName = nodeName_(node);
4180
+ const trustedContext = getTrustedPropContext(nodeName, propName);
4181
+
4182
+ let sanitizer = identity;
4183
+ // Sanitize img[srcset] + source[srcset] values.
4184
+ if (
4185
+ propName === "srcset" &&
4186
+ (nodeName === "img" || nodeName === "source")
4187
+ ) {
4188
+ sanitizer = sanitizeSrcsetPropertyValue;
4189
+ } else if (trustedContext) {
4190
+ sanitizer = $sce.getTrusted.bind($sce, trustedContext);
4191
+ }
4192
+
4193
+ directives.push({
4194
+ priority: 100,
4195
+ compile: function ngPropCompileFn(_, attr) {
4196
+ const ngPropGetter = $parse(attr[attrName]);
4197
+ const ngPropWatch = $parse(attr[attrName], (val) =>
4198
+ // Unwrap the value to compare the actual inner safe value, not the wrapper object.
4199
+ $sce.valueOf(val),
4200
+ );
4201
+
4202
+ return {
4203
+ pre: function ngPropPreLinkFn(scope, $element) {
4204
+ function applyPropValue() {
4205
+ const propValue = ngPropGetter(scope);
4206
+ $element[0][propName] = sanitizer(propValue);
4207
+ }
4208
+
4209
+ applyPropValue();
4210
+ scope.$watch(ngPropWatch, applyPropValue);
4211
+ },
4212
+ };
4213
+ },
4214
+ });
4215
+ }
4216
+
4217
+ function addEventDirective(directives, attrName, eventName) {
4218
+ directives.push(
4219
+ createEventDirective(
4220
+ $parse,
4221
+ $rootScope,
4222
+ $exceptionHandler,
4223
+ attrName,
4224
+ eventName,
4225
+ /* forceAsync= */ false,
4226
+ ),
4227
+ );
4228
+ }
4229
+
4230
+ function addAttrInterpolateDirective(
4231
+ node,
4232
+ directives,
4233
+ value,
4234
+ name,
4235
+ isNgAttr,
4236
+ ) {
4237
+ const nodeName = nodeName_(node);
4238
+ const trustedContext = getTrustedAttrContext(nodeName, name);
4239
+ const mustHaveExpression = !isNgAttr;
4240
+ const allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
4241
+
4242
+ let interpolateFn = $interpolate(
4243
+ value,
4244
+ mustHaveExpression,
4245
+ trustedContext,
4246
+ allOrNothing,
4247
+ );
4248
+
4249
+ // no interpolation found -> ignore
4250
+ if (!interpolateFn) return;
4251
+
4252
+ if (name === "multiple" && nodeName === "select") {
4253
+ throw $compileMinErr(
4254
+ "selmulti",
4255
+ "Binding to the 'multiple' attribute is not supported. Element: {0}",
4256
+ startingTag(node.outerHTML),
4257
+ );
4258
+ }
4259
+
4260
+ if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
4261
+ throw $compileMinErr(
4262
+ "nodomevents",
4263
+ "Interpolations for HTML DOM event attributes are disallowed",
4264
+ );
4265
+ }
4266
+
4267
+ directives.push({
4268
+ priority: 100,
4269
+ compile() {
4270
+ return {
4271
+ pre: function attrInterpolatePreLinkFn(scope, element, attr) {
4272
+ const $$observers =
4273
+ attr.$$observers || (attr.$$observers = createMap());
4274
+
4275
+ // If the attribute has changed since last $interpolate()ed
4276
+ const newValue = attr[name];
4277
+ if (newValue !== value) {
4278
+ // we need to interpolate again since the attribute value has been updated
4279
+ // (e.g. by another directive's compile function)
4280
+ // ensure unset/empty values make interpolateFn falsy
4281
+ interpolateFn =
4282
+ newValue &&
4283
+ $interpolate(newValue, true, trustedContext, allOrNothing);
4284
+ value = newValue;
4285
+ }
4286
+
4287
+ // if attribute was updated so that there is no interpolation going on we don't want to
4288
+ // register any observers
4289
+ if (!interpolateFn) return;
4290
+
4291
+ // initialize attr object so that it's ready in case we need the value for isolate
4292
+ // scope initialization, otherwise the value would not be available from isolate
4293
+ // directive's linking fn during linking phase
4294
+ attr[name] = interpolateFn(scope);
4295
+
4296
+ ($$observers[name] || ($$observers[name] = [])).$$inter = true;
4297
+ (
4298
+ (attr.$$observers && attr.$$observers[name].$$scope) ||
4299
+ scope
4300
+ ).$watch(interpolateFn, (newValue, oldValue) => {
4301
+ // special case for class attribute addition + removal
4302
+ // so that class changes can tap into the animation
4303
+ // hooks provided by the $animate service. Be sure to
4304
+ // skip animations when the first digest occurs (when
4305
+ // both the new and the old values are the same) since
4306
+ // the CSS classes are the non-interpolated values
4307
+ if (name === "class" && newValue !== oldValue) {
4308
+ attr.$updateClass(newValue, oldValue);
4309
+ } else {
4310
+ attr.$set(name, newValue);
4311
+ }
4312
+ });
4313
+ },
4314
+ };
4315
+ },
4316
+ });
4317
+ }
4318
+
4319
+ /**
4320
+ * This is a special jqLite.replaceWith, which can replace items which
4321
+ * have no parents, provided that the containing jqLite collection is provided.
4322
+ *
4323
+ * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
4324
+ * in the root of the tree.
4325
+ * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
4326
+ * the shell, but replace its DOM node reference.
4327
+ * @param {Node} newNode The new DOM node.
4328
+ */
4329
+ function replaceWith($rootElement, elementsToRemove, newNode) {
4330
+ const firstElementToRemove = elementsToRemove[0];
4331
+ const removeCount = elementsToRemove.length;
4332
+ const parent = firstElementToRemove.parentNode;
4333
+ let i;
4334
+ let ii;
4335
+
4336
+ if ($rootElement) {
4337
+ for (i = 0, ii = $rootElement.length; i < ii; i++) {
4338
+ if ($rootElement[i] === firstElementToRemove) {
4339
+ $rootElement[i++] = newNode;
4340
+ for (
4341
+ let j = i, j2 = j + removeCount - 1, jj = $rootElement.length;
4342
+ j < jj;
4343
+ j++, j2++
4344
+ ) {
4345
+ if (j2 < jj) {
4346
+ $rootElement[j] = $rootElement[j2];
4347
+ } else {
4348
+ delete $rootElement[j];
4349
+ }
4350
+ }
4351
+ $rootElement.length -= removeCount - 1;
4352
+
4353
+ // If the replaced element is also the jQuery .context then replace it
4354
+ // .context is a deprecated jQuery api, so we should set it only when jQuery set it
4355
+ // http://api.jquery.com/context/
4356
+ if ($rootElement.context === firstElementToRemove) {
4357
+ $rootElement.context = newNode;
4358
+ }
4359
+ break;
4360
+ }
4361
+ }
4362
+ }
4363
+
4364
+ if (parent) {
4365
+ parent.replaceChild(newNode, firstElementToRemove);
4366
+ }
4367
+
4368
+ // Append all the `elementsToRemove` to a fragment. This will...
4369
+ // - remove them from the DOM
4370
+ // - allow them to still be traversed with .nextSibling
4371
+ // - allow a single fragment.qSA to fetch all elements being removed
4372
+ const fragment = window.document.createDocumentFragment();
4373
+ for (i = 0; i < removeCount; i++) {
4374
+ fragment.appendChild(elementsToRemove[i]);
4375
+ }
4376
+
4377
+ if (jqLite.hasData(firstElementToRemove)) {
4378
+ // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private
4379
+ // data here because there's no public interface in jQuery to do that and copying over
4380
+ // event listeners (which is the main use of private data) wouldn't work anyway.
4381
+ jqLite.data(newNode, jqLite.data(firstElementToRemove));
4382
+
4383
+ // Remove $destroy event listeners from `firstElementToRemove`
4384
+ jqLite(firstElementToRemove).off("$destroy");
4385
+ }
4386
+
4387
+ // Cleanup any data/listeners on the elements and children.
4388
+ // This includes invoking the $destroy event on any elements with listeners.
4389
+ jqLite.cleanData(fragment.querySelectorAll("*"));
4390
+
4391
+ // Update the jqLite collection to only contain the `newNode`
4392
+ for (i = 1; i < removeCount; i++) {
4393
+ delete elementsToRemove[i];
4394
+ }
4395
+ elementsToRemove[0] = newNode;
4396
+ elementsToRemove.length = 1;
4397
+ }
4398
+
4399
+ function cloneAndAnnotateFn(fn, annotation) {
4400
+ return extend(
4401
+ function () {
4402
+ return fn.apply(null, arguments);
4403
+ },
4404
+ fn,
4405
+ annotation,
4406
+ );
4407
+ }
4408
+
4409
+ function invokeLinkFn(
4410
+ linkFn,
4411
+ scope,
4412
+ $element,
4413
+ attrs,
4414
+ controllers,
4415
+ transcludeFn,
4416
+ ) {
4417
+ try {
4418
+ linkFn(scope, $element, attrs, controllers, transcludeFn);
4419
+ } catch (e) {
4420
+ console.error(e);
4421
+ $exceptionHandler(e, startingTag($element));
4422
+ }
4423
+ }
4424
+
4425
+ function strictBindingsCheck(attrName, directiveName) {
4426
+ if (strictComponentBindingsEnabled) {
4427
+ throw $compileMinErr(
4428
+ "missingattr",
4429
+ "Attribute '{0}' of '{1}' is non-optional and must be set!",
4430
+ attrName,
4431
+ directiveName,
4432
+ );
4433
+ }
4434
+ }
4435
+
4436
+ // Set up $watches for isolate scope and controller bindings.
4437
+ function initializeDirectiveBindings(
4438
+ scope,
4439
+ attrs,
4440
+ destination,
4441
+ bindings,
4442
+ directive,
4443
+ ) {
4444
+ const removeWatchCollection = [];
4445
+ const initialChanges = {};
4446
+ let changes;
4447
+
4448
+ forEach(bindings, (definition, scopeName) => {
4449
+ const { attrName } = definition;
4450
+ const { optional } = definition;
4451
+ const { mode } = definition; // @, =, <, or &
4452
+ let lastValue;
4453
+ let parentGet;
4454
+ let parentSet;
4455
+ let compare;
4456
+ let removeWatch;
4457
+
4458
+ switch (mode) {
4459
+ case "@":
4460
+ if (!optional && !hasOwnProperty.call(attrs, attrName)) {
4461
+ strictBindingsCheck(attrName, directive.name);
4462
+ destination[scopeName] = attrs[attrName] = undefined;
4463
+ }
4464
+ removeWatch = attrs.$observe(attrName, (value) => {
4465
+ if (isString(value) || isBoolean(value)) {
4466
+ const oldValue = destination[scopeName];
4467
+ recordChanges(scopeName, value, oldValue);
4468
+ destination[scopeName] = value;
4469
+ }
4470
+ });
4471
+ attrs.$$observers[attrName].$$scope = scope;
4472
+ lastValue = attrs[attrName];
4473
+ if (isString(lastValue)) {
4474
+ // If the attribute has been provided then we trigger an interpolation to ensure
4475
+ // the value is there for use in the link fn
4476
+ destination[scopeName] = $interpolate(lastValue)(scope);
4477
+ } else if (isBoolean(lastValue)) {
4478
+ // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted
4479
+ // the value to boolean rather than a string, so we special case this situation
4480
+ destination[scopeName] = lastValue;
4481
+ }
4482
+ initialChanges[scopeName] = new SimpleChange(
4483
+ _UNINITIALIZED_VALUE,
4484
+ destination[scopeName],
4485
+ );
4486
+ removeWatchCollection.push(removeWatch);
4487
+ break;
4488
+
4489
+ case "=":
4490
+ if (!Object.hasOwnProperty.call(attrs, attrName)) {
4491
+ if (optional) break;
4492
+ strictBindingsCheck(attrName, directive.name);
4493
+ attrs[attrName] = undefined;
4494
+ }
4495
+ if (optional && !attrs[attrName]) break;
4496
+
4497
+ parentGet = $parse(attrs[attrName]);
4498
+ if (parentGet.literal) {
4499
+ compare = equals;
4500
+ } else {
4501
+ compare = simpleCompare;
4502
+ }
4503
+ parentSet =
4504
+ parentGet.assign ||
4505
+ function () {
4506
+ // reset the change, or we will throw this exception on every $digest
4507
+ lastValue = destination[scopeName] = parentGet(scope);
4508
+ throw $compileMinErr(
4509
+ "nonassign",
4510
+ "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!",
4511
+ attrs[attrName],
4512
+ attrName,
4513
+ directive.name,
4514
+ );
4515
+ };
4516
+ lastValue = destination[scopeName] = parentGet(scope);
4517
+ var parentValueWatch = function parentValueWatch(parentValue) {
4518
+ if (!compare(parentValue, destination[scopeName])) {
4519
+ // we are out of sync and need to copy
4520
+ if (!compare(parentValue, lastValue)) {
4521
+ // parent changed and it has precedence
4522
+ destination[scopeName] = parentValue;
4523
+ } else {
4524
+ // if the parent can be assigned then do so
4525
+ parentSet(scope, (parentValue = destination[scopeName]));
4526
+ }
4527
+ }
4528
+ lastValue = parentValue;
4529
+ return lastValue;
4530
+ };
4531
+ parentValueWatch.$stateful = true;
4532
+ if (definition.collection) {
4533
+ removeWatch = scope.$watchCollection(
4534
+ attrs[attrName],
4535
+ parentValueWatch,
4536
+ );
4537
+ } else {
4538
+ removeWatch = scope.$watch(
4539
+ $parse(attrs[attrName], parentValueWatch),
4540
+ null,
4541
+ parentGet.literal,
4542
+ );
4543
+ }
4544
+ removeWatchCollection.push(removeWatch);
4545
+ break;
4546
+
4547
+ case "<":
4548
+ if (!Object.hasOwnProperty.call(attrs, attrName)) {
4549
+ if (optional) break;
4550
+ strictBindingsCheck(attrName, directive.name);
4551
+ attrs[attrName] = undefined;
4552
+ }
4553
+ if (optional && !attrs[attrName]) break;
4554
+
4555
+ parentGet = $parse(attrs[attrName]);
4556
+ var isLiteral = parentGet.literal;
4557
+
4558
+ var initialValue = (destination[scopeName] = parentGet(scope));
4559
+ initialChanges[scopeName] = new SimpleChange(
4560
+ _UNINITIALIZED_VALUE,
4561
+ destination[scopeName],
4562
+ );
4563
+
4564
+ removeWatch = scope[
4565
+ definition.collection ? "$watchCollection" : "$watch"
4566
+ ](parentGet, (newValue, oldValue) => {
4567
+ if (oldValue === newValue) {
4568
+ if (
4569
+ oldValue === initialValue ||
4570
+ (isLiteral && equals(oldValue, initialValue))
4571
+ ) {
4572
+ return;
4573
+ }
4574
+ oldValue = initialValue;
4575
+ }
4576
+ recordChanges(scopeName, newValue, oldValue);
4577
+ destination[scopeName] = newValue;
4578
+ });
4579
+
4580
+ removeWatchCollection.push(removeWatch);
4581
+ break;
4582
+
4583
+ case "&":
4584
+ if (!optional && !Object.hasOwnProperty.call(attrs, attrName)) {
4585
+ strictBindingsCheck(attrName, directive.name);
4586
+ }
4587
+ // Don't assign Object.prototype method to scope
4588
+ parentGet = Object.prototype.hasOwnProperty.call(attrs, attrName)
4589
+ ? $parse(attrs[attrName])
4590
+ : () => {};
4591
+
4592
+ // Don't assign noop to destination if expression is not valid
4593
+ if (parentGet.toString() === (() => {}).toString() && optional)
4594
+ break;
4595
+
4596
+ destination[scopeName] = function (locals) {
4597
+ return parentGet(scope, locals);
4598
+ };
4599
+ break;
4600
+ }
4601
+ });
4602
+
4603
+ function recordChanges(key, currentValue, previousValue) {
4604
+ if (
4605
+ isFunction(destination.$onChanges) &&
4606
+ !simpleCompare(currentValue, previousValue)
4607
+ ) {
4608
+ // If we have not already scheduled the top level onChangesQueue handler then do so now
4609
+ if (!onChangesQueue) {
4610
+ scope.$$postDigest(flushOnChangesQueue);
4611
+ onChangesQueue = [];
4612
+ }
4613
+ // If we have not already queued a trigger of onChanges for this controller then do so now
4614
+ if (!changes) {
4615
+ changes = {};
4616
+ onChangesQueue.push(triggerOnChangesHook);
4617
+ }
4618
+ // If the has been a change on this property already then we need to reuse the previous value
4619
+ if (changes[key]) {
4620
+ previousValue = changes[key].previousValue;
4621
+ }
4622
+ // Store this change
4623
+ changes[key] = new SimpleChange(previousValue, currentValue);
4624
+ }
4625
+ }
4626
+
4627
+ function triggerOnChangesHook() {
4628
+ destination.$onChanges(changes);
4629
+ // Now clear the changes so that we schedule onChanges when more changes arrive
4630
+ changes = undefined;
4631
+ }
4632
+
4633
+ return {
4634
+ initialChanges,
4635
+ removeWatches:
4636
+ removeWatchCollection.length &&
4637
+ function removeWatches() {
4638
+ for (let i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
4639
+ removeWatchCollection[i]();
4640
+ }
4641
+ },
4642
+ };
4643
+ }
4644
+ },
4645
+ ];
4646
+ }
4647
+
4648
+ function SimpleChange(previous, current) {
4649
+ this.previousValue = previous;
4650
+ this.currentValue = current;
4651
+ }
4652
+ SimpleChange.prototype.isFirstChange = function () {
4653
+ return this.previousValue === _UNINITIALIZED_VALUE;
4654
+ };
4655
+
4656
+ /**
4657
+ * @ngdoc type
4658
+ * @name $compile.directive.Attributes
4659
+ *
4660
+ * @description
4661
+ * A shared object between directive compile / linking functions which contains normalized DOM
4662
+ * element attributes. The values reflect current binding state `{{ }}`. The normalization is
4663
+ * needed since all of these are treated as equivalent in AngularJS:
4664
+ *
4665
+ * ```
4666
+ * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
4667
+ * ```
4668
+ */
4669
+
4670
+ /**
4671
+ * @ngdoc property
4672
+ * @name $compile.directive.Attributes#$attr
4673
+ *
4674
+ * @description
4675
+ * A map of DOM element attribute names to the normalized name. This is
4676
+ * needed to do reverse lookup from normalized name back to actual name.
4677
+ */
4678
+
4679
+ /**
4680
+ * @ngdoc method
4681
+ * @name $compile.directive.Attributes#$set
4682
+ * @kind function
4683
+ *
4684
+ * @description
4685
+ * Set DOM element attribute value.
4686
+ *
4687
+ *
4688
+ * @param {string} name Normalized element attribute name of the property to modify. The name is
4689
+ * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
4690
+ * property to the original name.
4691
+ * @param {string} value Value to set the attribute to. The value can be an interpolated string.
4692
+ */
4693
+
4694
+ function tokenDifference(str1, str2) {
4695
+ let values = "";
4696
+ const tokens1 = str1.split(/\s+/);
4697
+ const tokens2 = str2.split(/\s+/);
4698
+
4699
+ outer: for (let i = 0; i < tokens1.length; i++) {
4700
+ const token = tokens1[i];
4701
+ for (let j = 0; j < tokens2.length; j++) {
4702
+ if (token === tokens2[j]) continue outer;
4703
+ }
4704
+ values += (values.length > 0 ? " " : "") + token;
4705
+ }
4706
+ return values;
4707
+ }
4708
+
4709
+ function removeComments(jqNodes) {
4710
+ jqNodes = jqLite(jqNodes);
4711
+ let i = jqNodes.length;
4712
+
4713
+ if (i <= 1) {
4714
+ return jqNodes;
4715
+ }
4716
+
4717
+ while (i--) {
4718
+ const node = jqNodes[i];
4719
+ if (
4720
+ node.nodeType === Node.COMMENT_NODE ||
4721
+ (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() === "")
4722
+ ) {
4723
+ [].splice.call(jqNodes, i, 1);
4724
+ }
4725
+ }
4726
+ return jqNodes;
4727
+ }