@angular-wave/angular.ts 0.7.7 → 0.7.8

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 (157) hide show
  1. package/@types/animations/raf-scheduler.d.ts +2 -2
  2. package/@types/animations/shared.d.ts +0 -1
  3. package/@types/core/compile/attributes.d.ts +3 -3
  4. package/@types/core/compile/compile.d.ts +1 -1
  5. package/@types/core/di/injector.d.ts +0 -1
  6. package/@types/core/di/internal-injector.d.ts +1 -0
  7. package/@types/core/di/ng-module.d.ts +5 -0
  8. package/@types/core/filter/filter.d.ts +11 -13
  9. package/@types/core/sanitize/sanitize-uri.d.ts +3 -6
  10. package/@types/core/scope/scope.d.ts +1 -1
  11. package/@types/directive/attrs/attrs.d.ts +7 -1
  12. package/@types/directive/events/events.d.ts +9 -3
  13. package/@types/directive/http/http.d.ts +6 -2
  14. package/@types/directive/include/include.d.ts +2 -2
  15. package/@types/directive/input/input.d.ts +2 -12
  16. package/@types/directive/messages/messages.d.ts +9 -48
  17. package/@types/directive/model/model.d.ts +3 -3
  18. package/@types/directive/options/options.d.ts +13 -20
  19. package/@types/directive/switch/switch.d.ts +1 -0
  20. package/@types/directive/transclude/transclude.d.ts +10 -6
  21. package/@types/interface.d.ts +54 -18
  22. package/@types/router/common/glob.d.ts +5 -1
  23. package/@types/router/directives/view-directive.d.ts +2 -19
  24. package/@types/router/globals.d.ts +1 -2
  25. package/@types/router/state/state-registry.d.ts +1 -2
  26. package/@types/router/url/url-service.d.ts +7 -9
  27. package/@types/services/anchor-scroll.d.ts +1 -1
  28. package/@types/{core → services/exception}/exception-handler.d.ts +4 -4
  29. package/@types/{core/error-handler.d.ts → services/exception/interface.d.ts} +1 -1
  30. package/@types/services/http/http.d.ts +0 -2
  31. package/@types/services/http/interface.d.ts +2 -2
  32. package/@types/services/http-backend/http-backend.d.ts +13 -21
  33. package/@types/services/location/interface.d.ts +8 -0
  34. package/@types/{core → services}/location/location.d.ts +52 -12
  35. package/@types/{core → services}/sce/sce.d.ts +1 -1
  36. package/@types/services/template-cache/interface.d.ts +8 -2
  37. package/@types/services/template-cache/template-cache.d.ts +1 -1
  38. package/@types/services/template-request.d.ts +1 -1
  39. package/@types/shared/cache.d.ts +0 -2
  40. package/@types/shared/dom.d.ts +6 -0
  41. package/@types/shared/test-utils.d.ts +1 -0
  42. package/@types/shared/url-utils/interface.d.ts +47 -0
  43. package/@types/{core → shared}/url-utils/url-utils.d.ts +26 -13
  44. package/@types/shared/utils.d.ts +15 -0
  45. package/Makefile +3 -2
  46. package/dist/angular-ts.esm.js +982 -1190
  47. package/dist/angular-ts.umd.js +982 -1190
  48. package/dist/angular-ts.umd.min.js +1 -1
  49. package/docs/content/docs/directive/bind.md +9 -7
  50. package/docs/content/docs/directive/get.md +203 -0
  51. package/docs/content/docs/provider/templateCacheProvider.md +66 -1
  52. package/docs/content/docs/service/templateCache.md +2 -2
  53. package/docs/layouts/partials/hooks/head-end.html +1 -1
  54. package/docs/layouts/shortcodes/showcss.html +2 -0
  55. package/docs/static/examples/ng-bind/ng-bind.html +2 -2
  56. package/docs/static/typedoc/assets/hierarchy.js +1 -1
  57. package/docs/static/typedoc/assets/navigation.js +1 -1
  58. package/docs/static/typedoc/assets/search.js +1 -1
  59. package/docs/static/typedoc/classes/NgModule.html +32 -0
  60. package/docs/static/typedoc/classes/TemplateCacheProvider.html +1 -1
  61. package/docs/static/typedoc/hierarchy.html +1 -1
  62. package/docs/static/typedoc/index.html +1 -1
  63. package/docs/static/typedoc/interfaces/Directive.html +5 -4
  64. package/docs/static/typedoc/interfaces/HttpProviderDefaults.html +1 -1
  65. package/docs/static/typedoc/interfaces/HttpResponse.html +2 -3
  66. package/docs/static/typedoc/interfaces/Provider.html +15 -10
  67. package/docs/static/typedoc/interfaces/RequestConfig.html +1 -1
  68. package/docs/static/typedoc/interfaces/RequestShortcutConfig.html +1 -1
  69. package/docs/static/typedoc/interfaces/TemplateCache.html +7 -0
  70. package/docs/static/typedoc/types/AnnotatedDirectiveFactory.html +1 -0
  71. package/docs/static/typedoc/types/DirectiveFactory.html +1 -2
  72. package/docs/static/typedoc/types/DirectiveFactoryFn.html +1 -0
  73. package/docs/static/typedoc/types/HttpResponseStatus.html +1 -0
  74. package/docs/static/typedoc/types/{TemplateCache.html → SwapModeType.html} +1 -1
  75. package/docs/static/typedoc/variables/SwapMode.html +11 -0
  76. package/legacy.d.ts +0 -10
  77. package/package.json +1 -3
  78. package/src/animations/animate-children-directive.js +2 -2
  79. package/src/animations/raf-scheduler.js +1 -1
  80. package/src/animations/shared.js +0 -9
  81. package/src/core/compile/attributes.js +1 -1
  82. package/src/core/compile/compile.js +3 -3
  83. package/src/core/di/injector.js +4 -17
  84. package/src/core/di/internal-injector.js +4 -1
  85. package/src/core/di/ng-module.js +12 -27
  86. package/src/core/filter/filter.js +28 -28
  87. package/src/core/parse/interpreter.js +32 -38
  88. package/src/core/sanitize/sanitize-uri.js +3 -3
  89. package/src/core/scope/scope.js +2 -2
  90. package/src/directive/attrs/attrs.js +7 -4
  91. package/src/directive/events/events.js +6 -2
  92. package/src/directive/http/delete.spec.js +2 -0
  93. package/src/directive/http/get.spec.js +280 -3
  94. package/src/directive/http/http.js +100 -12
  95. package/src/directive/http/http.test.js +2 -2
  96. package/src/directive/http/post.spec.js +2 -0
  97. package/src/directive/http/put.spec.js +2 -0
  98. package/src/directive/include/include.js +7 -7
  99. package/src/directive/input/input.js +6 -28
  100. package/src/directive/messages/messages.js +4 -0
  101. package/src/directive/model/model.js +1 -1
  102. package/src/directive/options/options.js +454 -464
  103. package/src/directive/setter/setter.js +12 -14
  104. package/src/directive/setter/setter.spec.js +39 -16
  105. package/src/directive/switch/switch.js +1 -0
  106. package/src/directive/transclude/transclude.js +87 -89
  107. package/src/injection-tokens.js +1 -1
  108. package/src/interface.ts +68 -19
  109. package/src/loader.js +4 -9
  110. package/src/public.js +9 -15
  111. package/src/router/common/glob.js +5 -0
  112. package/src/router/directives/state-directives.spec.js +1 -1
  113. package/src/router/directives/view-directive.js +9 -1
  114. package/src/router/globals.js +0 -1
  115. package/src/router/state/state-registry.js +0 -1
  116. package/src/router/state-filters.js +2 -2
  117. package/src/router/url/url-service.js +5 -9
  118. package/src/services/anchor-scroll.html +0 -7
  119. package/src/services/anchor-scroll.js +1 -1
  120. package/src/{core → services/exception}/exception-handler.js +2 -2
  121. package/src/{core/error-handler.ts → services/exception/interface.ts} +1 -1
  122. package/src/services/http/http.js +2 -13
  123. package/src/services/http/interface.ts +2 -2
  124. package/src/services/http-backend/http-backend.js +4 -14
  125. package/src/services/http-backend/http-backend.spec.js +1 -4
  126. package/src/services/location/interface.ts +8 -0
  127. package/src/{core → services}/location/location.html +4 -1
  128. package/src/{core → services}/location/location.js +128 -26
  129. package/src/{core → services}/location/location.spec.js +2 -2
  130. package/src/{core → services}/location/location.test.js +1 -1
  131. package/src/{core → services}/sce/sce.html +1 -1
  132. package/src/{core → services}/sce/sce.js +9 -3
  133. package/src/{core → services}/sce/sce.spec.js +2 -3
  134. package/src/{core → services}/sce/sce.test.js +1 -1
  135. package/src/services/template-cache/interface.ts +8 -2
  136. package/src/services/template-cache/template-cache.js +3 -1
  137. package/src/services/template-cache/template-cache.spec.js +72 -0
  138. package/src/services/template-request.js +2 -1
  139. package/src/shared/cache.js +0 -2
  140. package/src/shared/dom.js +10 -0
  141. package/src/shared/test-utils.js +1 -0
  142. package/src/shared/url-utils/interface.ts +56 -0
  143. package/src/{core → shared}/url-utils/url-utils.html +4 -1
  144. package/src/{core → shared}/url-utils/url-utils.js +26 -23
  145. package/src/{core → shared}/url-utils/url-utils.spec.js +0 -8
  146. package/src/{core → shared}/url-utils/url-utils.test.js +1 -1
  147. package/src/shared/utils.js +28 -0
  148. package/utils/express.js +9 -1
  149. package/@types/core/task-tracker-factory.d.ts +0 -76
  150. package/@types/services/browser.d.ts +0 -101
  151. package/docs/static/typedoc/types/SwapInsertPosition.html +0 -2
  152. package/jsdoc.json +0 -22
  153. package/src/core/task-tracker-factory.js +0 -145
  154. package/src/services/browser.js +0 -212
  155. /package/src/{core → services}/location/location.md +0 -0
  156. /package/src/{core → services}/sce/sce.md +0 -0
  157. /package/src/{core → shared}/url-utils/url-utils.md +0 -0
@@ -30,522 +30,512 @@ const NG_OPTIONS_REGEXP =
30
30
  // 8: collection expression
31
31
  // 9: track by expression
32
32
 
33
- export const ngOptionsDirective = [
34
- "$compile",
35
- "$parse",
33
+ ngOptionsDirective.$inject = ["$compile", "$parse"];
34
+ /**
35
+ *
36
+ * @param {import("../../core/compile/compile.js").CompileFn} $compile
37
+ * @param {import("../../core/parse/interface.ts").ParseService} $parse
38
+ * @returns {import("../../interface.ts").Directive}
39
+ */
40
+ export function ngOptionsDirective($compile, $parse) {
36
41
  /**
37
- *
38
- * @param {import("../../core/compile/compile.js").CompileFn} $compile
39
- * @param {import("../../core/parse/interface.ts").ParseService} $parse
42
+ * @param {import('../../interface.ts').Expression} optionsExp
43
+ * @param {HTMLSelectElement} selectElement
44
+ * @param {import('../../core/scope/scope.js').Scope} scope
40
45
  * @returns
41
46
  */
42
- function ($compile, $parse) {
43
- /**
44
- * @param {import('../../interface.ts').Expression} optionsExp
45
- * @param {HTMLSelectElement} selectElement
46
- * @param {import('../../core/scope/scope.js').Scope} scope
47
- * @returns
48
- */
49
- function parseOptionsExpression(optionsExp, selectElement, scope) {
50
- const match = optionsExp.match(NG_OPTIONS_REGEXP);
51
- if (!match) {
52
- throw ngOptionsMinErr(
53
- "iexp",
54
- "Expected expression in form of " +
55
- "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
56
- " but got '{0}'. Element: {1}",
57
- optionsExp,
58
- startingTag(selectElement),
59
- );
60
- }
61
- // Extract the parts from the ngOptions expression
62
-
63
- // The variable name for the value of the item in the collection
64
- const valueName = match[5] || match[7];
65
- // The variable name for the key of the item in the collection
66
- const keyName = match[6];
67
-
68
- // An expression that generates the viewValue for an option if there is a label expression
69
- const selectAs = / as /.test(match[0]) && match[1];
70
- // An expression that is used to track the id of each object in the options collection
71
- const trackBy = match[9];
72
- // An expression that generates the viewValue for an option if there is no label expression
73
- const valueFn = $parse(match[2] ? match[1] : valueName);
74
- const selectAsFn = selectAs && $parse(selectAs);
75
- const viewValueFn = selectAsFn || valueFn;
76
- const trackByFn = trackBy && $parse(trackBy);
77
-
78
- // Get the value by which we are going to track the option
79
- // if we have a trackFn then use that (passing scope and locals)
80
- // otherwise just hash the given viewValue
81
- const getTrackByValueFn = trackBy
82
- ? function (value, locals) {
83
- return trackByFn(scope, locals);
84
- }
85
- : function getHashOfValue(value) {
86
- return hashKey(value);
87
- };
88
- const getTrackByValue = function (value, key) {
89
- return getTrackByValueFn(value, getLocals(value, key));
90
- };
47
+ function parseOptionsExpression(optionsExp, selectElement, scope) {
48
+ const match = optionsExp.match(NG_OPTIONS_REGEXP);
49
+ if (!match) {
50
+ throw ngOptionsMinErr(
51
+ "iexp",
52
+ "Expected expression in form of " +
53
+ "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
54
+ " but got '{0}'. Element: {1}",
55
+ optionsExp,
56
+ startingTag(selectElement),
57
+ );
58
+ }
59
+ // Extract the parts from the ngOptions expression
60
+
61
+ // The variable name for the value of the item in the collection
62
+ const valueName = match[5] || match[7];
63
+ // The variable name for the key of the item in the collection
64
+ const keyName = match[6];
65
+
66
+ // An expression that generates the viewValue for an option if there is a label expression
67
+ const selectAs = / as /.test(match[0]) && match[1];
68
+ // An expression that is used to track the id of each object in the options collection
69
+ const trackBy = match[9];
70
+ // An expression that generates the viewValue for an option if there is no label expression
71
+ const valueFn = $parse(match[2] ? match[1] : valueName);
72
+ const selectAsFn = selectAs && $parse(selectAs);
73
+ const viewValueFn = selectAsFn || valueFn;
74
+ const trackByFn = trackBy && $parse(trackBy);
75
+
76
+ // Get the value by which we are going to track the option
77
+ // if we have a trackFn then use that (passing scope and locals)
78
+ // otherwise just hash the given viewValue
79
+ const getTrackByValueFn = trackBy
80
+ ? function (value, locals) {
81
+ return trackByFn(scope, locals);
82
+ }
83
+ : function getHashOfValue(value) {
84
+ return hashKey(value);
85
+ };
86
+ const getTrackByValue = function (value, key) {
87
+ return getTrackByValueFn(value, getLocals(value, key));
88
+ };
91
89
 
92
- const displayFn = $parse(match[2] || match[1]);
93
- const groupByFn = $parse(match[3] || "");
94
- const disableWhenFn = $parse(match[4] || "");
95
- const valuesFn = $parse(match[8]);
96
-
97
- const locals = {};
98
- let getLocals = keyName
99
- ? function (value, key) {
100
- locals[keyName] = key;
101
- locals[valueName] = value;
102
- return locals;
103
- }
104
- : function (value) {
105
- locals[valueName] = value;
106
- return locals;
107
- };
108
-
109
- class Option {
110
- constructor(selectValue, viewValue, label, group, disabled) {
111
- this.selectValue = selectValue;
112
- this.viewValue = viewValue;
113
- this.label = label;
114
- this.group = group;
115
- this.disabled = disabled;
90
+ const displayFn = $parse(match[2] || match[1]);
91
+ const groupByFn = $parse(match[3] || "");
92
+ const disableWhenFn = $parse(match[4] || "");
93
+ const valuesFn = $parse(match[8]);
94
+
95
+ const locals = {};
96
+ let getLocals = keyName
97
+ ? function (value, key) {
98
+ locals[keyName] = key;
99
+ locals[valueName] = value;
100
+ return locals;
116
101
  }
102
+ : function (value) {
103
+ locals[valueName] = value;
104
+ return locals;
105
+ };
106
+
107
+ class Option {
108
+ constructor(selectValue, viewValue, label, group, disabled) {
109
+ this.selectValue = selectValue;
110
+ this.viewValue = viewValue;
111
+ this.label = label;
112
+ this.group = group;
113
+ this.disabled = disabled;
117
114
  }
115
+ }
118
116
 
119
- function getOptionValuesKeys(optionValues) {
120
- let optionValuesKeys;
117
+ function getOptionValuesKeys(optionValues) {
118
+ let optionValuesKeys;
121
119
 
122
- if (!keyName && isArrayLike(optionValues)) {
123
- optionValuesKeys = optionValues;
124
- } else {
125
- // if object, extract keys, in enumeration order, unsorted
126
- optionValuesKeys = [];
127
- for (const itemKey in optionValues) {
128
- if (hasOwn(optionValues, itemKey) && itemKey.charAt(0) !== "$") {
129
- optionValuesKeys.push(itemKey);
130
- }
120
+ if (!keyName && isArrayLike(optionValues)) {
121
+ optionValuesKeys = optionValues;
122
+ } else {
123
+ // if object, extract keys, in enumeration order, unsorted
124
+ optionValuesKeys = [];
125
+ for (const itemKey in optionValues) {
126
+ if (hasOwn(optionValues, itemKey) && itemKey.charAt(0) !== "$") {
127
+ optionValuesKeys.push(itemKey);
131
128
  }
132
129
  }
133
- return optionValuesKeys;
134
130
  }
131
+ return optionValuesKeys;
132
+ }
135
133
 
136
- return {
137
- trackBy,
138
- getTrackByValue,
139
- getWatchables: $parse(valuesFn, (optionValues) => {
140
- // Create a collection of things that we would like to watch (watchedArray)
141
- // so that they can all be watched using a single $watchCollection
142
- // that only runs the handler once if anything changes
143
- const watchedArray = [];
144
- optionValues = optionValues || [];
145
-
146
- const optionValuesKeys = getOptionValuesKeys(optionValues);
147
- const optionValuesLength = optionValuesKeys.length;
148
- for (let index = 0; index < optionValuesLength; index++) {
149
- const key =
150
- optionValues === optionValuesKeys
151
- ? index
152
- : optionValuesKeys[index];
153
- const value = optionValues[key];
154
-
155
- const locals = getLocals(value, key);
156
- const selectValue = getTrackByValueFn(value, locals);
157
- watchedArray.push(selectValue);
158
-
159
- // Only need to watch the displayFn if there is a specific label expression
160
- if (match[2] || match[1]) {
161
- const label = displayFn(scope, locals);
162
- watchedArray.push(label);
163
- }
164
-
165
- // Only need to watch the disableWhenFn if there is a specific disable expression
166
- if (match[4]) {
167
- const disableWhen = disableWhenFn(scope, locals);
168
- watchedArray.push(disableWhen);
169
- }
170
- }
171
- return watchedArray;
172
- }),
173
-
174
- getOptions() {
175
- /** @type {Option[]} */
176
- const optionItems = [];
177
- /** @type {Object.<string, Option>} */
178
- const selectValueMap = {};
179
-
180
- // The option values were already computed in the `getWatchables` fn,
181
- // which must have been called to trigger `getOptions`
182
- const optionValues = valuesFn(scope) || [];
183
- const optionValuesKeys = getOptionValuesKeys(optionValues);
184
- const optionValuesLength = optionValuesKeys.length;
185
-
186
- for (let index = 0; index < optionValuesLength; index++) {
187
- const key =
188
- optionValues === optionValuesKeys
189
- ? index
190
- : optionValuesKeys[index];
191
- const value = optionValues[key];
192
- const locals = getLocals(value, key);
193
- const viewValue = viewValueFn(scope, locals);
194
- const selectValue = getTrackByValueFn(viewValue, locals);
134
+ return {
135
+ trackBy,
136
+ getTrackByValue,
137
+ getWatchables: $parse(valuesFn, (optionValues) => {
138
+ // Create a collection of things that we would like to watch (watchedArray)
139
+ // so that they can all be watched using a single $watchCollection
140
+ // that only runs the handler once if anything changes
141
+ const watchedArray = [];
142
+ optionValues = optionValues || [];
143
+
144
+ const optionValuesKeys = getOptionValuesKeys(optionValues);
145
+ const optionValuesLength = optionValuesKeys.length;
146
+ for (let index = 0; index < optionValuesLength; index++) {
147
+ const key =
148
+ optionValues === optionValuesKeys ? index : optionValuesKeys[index];
149
+ const value = optionValues[key];
150
+
151
+ const locals = getLocals(value, key);
152
+ const selectValue = getTrackByValueFn(value, locals);
153
+ watchedArray.push(selectValue);
154
+
155
+ // Only need to watch the displayFn if there is a specific label expression
156
+ if (match[2] || match[1]) {
195
157
  const label = displayFn(scope, locals);
196
- const group = groupByFn(scope, locals);
197
- const disabled = disableWhenFn(scope, locals);
198
- const optionItem = new Option(
199
- selectValue,
200
- viewValue,
201
- label,
202
- group,
203
- disabled,
204
- );
205
-
206
- optionItems.push(optionItem);
207
- selectValueMap[selectValue] = optionItem;
158
+ watchedArray.push(label);
208
159
  }
209
160
 
210
- return {
211
- items: optionItems,
212
- selectValueMap,
213
- getOptionFromViewValue(value) {
214
- return selectValueMap[getTrackByValue(value)];
215
- },
216
- getViewValueFromOption(option) {
217
- // If the viewValue could be an object that may be mutated by the application,
218
- // we need to make a copy and not return the reference to the value on the option.
219
- return trackBy
220
- ? structuredClone(option.viewValue)
221
- : option.viewValue;
222
- },
223
- };
224
- },
225
- };
226
- }
227
-
228
- /**
229
- *
230
- * @param {import("../../core/scope/scope.js").Scope} scope
231
- * @param {HTMLSelectElement} selectElement
232
- * @param {import("../../core/compile/attributes.js").Attributes} attr
233
- * @param {*} ctrls
234
- */
235
- function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
236
- const selectCtrl = ctrls[0];
237
- const ngModelCtrl = ctrls[1];
238
- const multiple = attr["multiple"];
239
-
240
- // The emptyOption allows the application developer to provide their own custom "empty"
241
- // option when the viewValue does not match any of the option values.
242
- for (
243
- let i = 0, children = selectElement.childNodes, ii = children.length;
244
- i < ii;
245
- i++
246
- ) {
247
- if (/** @type {HTMLOptionElement} */ (children[i]).value === "") {
248
- selectCtrl.hasEmptyOption = true;
249
- selectCtrl.emptyOption = children[i];
250
- break;
161
+ // Only need to watch the disableWhenFn if there is a specific disable expression
162
+ if (match[4]) {
163
+ const disableWhen = disableWhenFn(scope, locals);
164
+ watchedArray.push(disableWhen);
165
+ }
251
166
  }
252
- }
253
-
254
- // The empty option will be compiled and rendered before we first generate the options
255
- emptyElement(selectElement);
256
-
257
- const providedEmptyOption = !!selectCtrl.emptyOption;
167
+ return watchedArray;
168
+ }),
169
+
170
+ getOptions() {
171
+ /** @type {Option[]} */
172
+ const optionItems = [];
173
+ /** @type {Object.<string, Option>} */
174
+ const selectValueMap = {};
175
+
176
+ // The option values were already computed in the `getWatchables` fn,
177
+ // which must have been called to trigger `getOptions`
178
+ const optionValues = valuesFn(scope) || [];
179
+ const optionValuesKeys = getOptionValuesKeys(optionValues);
180
+ const optionValuesLength = optionValuesKeys.length;
181
+
182
+ for (let index = 0; index < optionValuesLength; index++) {
183
+ const key =
184
+ optionValues === optionValuesKeys ? index : optionValuesKeys[index];
185
+ const value = optionValues[key];
186
+ const locals = getLocals(value, key);
187
+ const viewValue = viewValueFn(scope, locals);
188
+ const selectValue = getTrackByValueFn(viewValue, locals);
189
+ const label = displayFn(scope, locals);
190
+ const group = groupByFn(scope, locals);
191
+ const disabled = disableWhenFn(scope, locals);
192
+ const optionItem = new Option(
193
+ selectValue,
194
+ viewValue,
195
+ label,
196
+ group,
197
+ disabled,
198
+ );
258
199
 
259
- const unknownOption = optionTemplate.cloneNode(false);
260
- // TODO double check
261
- unknownOption.nodeValue = "?";
200
+ optionItems.push(optionItem);
201
+ selectValueMap[selectValue] = optionItem;
202
+ }
262
203
 
263
- let options;
264
- const ngOptions = parseOptionsExpression(
265
- attr["ngOptions"],
266
- selectElement,
267
- scope,
268
- );
269
- // This stores the newly created options before they are appended to the select.
270
- // Since the contents are removed from the fragment when it is appended,
271
- // we only need to create it once.
272
- const listFragment = document.createDocumentFragment();
273
-
274
- // Overwrite the implementation. ngOptions doesn't use hashes
275
- selectCtrl.generateUnknownOptionValue = () => "?";
276
-
277
- // Update the controller methods for multiple selectable options
278
- if (!multiple) {
279
- selectCtrl.writeValue = function writeNgOptionsValue(value) {
280
- // The options might not be defined yet when ngModel tries to render
281
- if (!options) return;
282
-
283
- const selectedOption =
284
- selectElement.options[selectElement.selectedIndex];
285
- const option = options.getOptionFromViewValue(value);
286
-
287
- // Make sure to remove the selected attribute from the previously selected option
288
- // Otherwise, screen readers might get confused
289
- if (selectedOption) selectedOption.removeAttribute("selected");
290
-
291
- if (option) {
292
- // Don't update the option when it is already selected.
293
- // For example, the browser will select the first option by default. In that case,
294
- // most properties are set automatically - except the `selected` attribute, which we
295
- // set always
296
-
297
- if (selectElement.value !== option.selectValue) {
298
- selectCtrl.removeUnknownOption();
299
-
300
- selectElement.value = option.selectValue;
301
- option.element.selected = true;
302
- }
303
-
304
- option.element.setAttribute("selected", "selected");
305
- } else {
306
- selectCtrl.selectUnknownOrEmptyOption(value);
307
- }
204
+ return {
205
+ items: optionItems,
206
+ selectValueMap,
207
+ getOptionFromViewValue(value) {
208
+ return selectValueMap[getTrackByValue(value)];
209
+ },
210
+ getViewValueFromOption(option) {
211
+ // If the viewValue could be an object that may be mutated by the application,
212
+ // we need to make a copy and not return the reference to the value on the option.
213
+ return trackBy
214
+ ? structuredClone(option.viewValue)
215
+ : option.viewValue;
216
+ },
308
217
  };
218
+ },
219
+ };
220
+ }
309
221
 
310
- selectCtrl.readValue = function readNgOptionsValue() {
311
- const selectedOption = options.selectValueMap[selectElement.value];
222
+ /**
223
+ *
224
+ * @param {import("../../core/scope/scope.js").Scope} scope
225
+ * @param {HTMLSelectElement} selectElement
226
+ * @param {import("../../core/compile/attributes.js").Attributes} attr
227
+ * @param {*} ctrls
228
+ */
229
+ function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
230
+ const selectCtrl = ctrls[0];
231
+ const ngModelCtrl = ctrls[1];
232
+ const multiple = attr["multiple"];
233
+
234
+ // The emptyOption allows the application developer to provide their own custom "empty"
235
+ // option when the viewValue does not match any of the option values.
236
+ for (
237
+ let i = 0, children = selectElement.childNodes, ii = children.length;
238
+ i < ii;
239
+ i++
240
+ ) {
241
+ if (/** @type {HTMLOptionElement} */ (children[i]).value === "") {
242
+ selectCtrl.hasEmptyOption = true;
243
+ selectCtrl.emptyOption = children[i];
244
+ break;
245
+ }
246
+ }
312
247
 
313
- if (selectedOption && !selectedOption.disabled) {
314
- selectCtrl.unselectEmptyOption();
248
+ // The empty option will be compiled and rendered before we first generate the options
249
+ emptyElement(selectElement);
250
+
251
+ const providedEmptyOption = !!selectCtrl.emptyOption;
252
+
253
+ const unknownOption = optionTemplate.cloneNode(false);
254
+ // TODO double check
255
+ unknownOption.nodeValue = "?";
256
+
257
+ let options;
258
+ const ngOptions = parseOptionsExpression(
259
+ attr["ngOptions"],
260
+ selectElement,
261
+ scope,
262
+ );
263
+ // This stores the newly created options before they are appended to the select.
264
+ // Since the contents are removed from the fragment when it is appended,
265
+ // we only need to create it once.
266
+ const listFragment = document.createDocumentFragment();
267
+
268
+ // Overwrite the implementation. ngOptions doesn't use hashes
269
+ selectCtrl.generateUnknownOptionValue = () => "?";
270
+
271
+ // Update the controller methods for multiple selectable options
272
+ if (!multiple) {
273
+ selectCtrl.writeValue = function writeNgOptionsValue(value) {
274
+ // The options might not be defined yet when ngModel tries to render
275
+ if (!options) return;
276
+
277
+ const selectedOption =
278
+ selectElement.options[selectElement.selectedIndex];
279
+ const option = options.getOptionFromViewValue(value);
280
+
281
+ // Make sure to remove the selected attribute from the previously selected option
282
+ // Otherwise, screen readers might get confused
283
+ if (selectedOption) selectedOption.removeAttribute("selected");
284
+
285
+ if (option) {
286
+ // Don't update the option when it is already selected.
287
+ // For example, the browser will select the first option by default. In that case,
288
+ // most properties are set automatically - except the `selected` attribute, which we
289
+ // set always
290
+
291
+ if (selectElement.value !== option.selectValue) {
315
292
  selectCtrl.removeUnknownOption();
316
- return options.getViewValueFromOption(selectedOption);
293
+
294
+ selectElement.value = option.selectValue;
295
+ option.element.selected = true;
317
296
  }
318
- return null;
319
- };
320
297
 
321
- // If we are using `track by` then we must watch the tracked value on the model
322
- // since ngModel only watches for object identity change
323
- // FIXME: When a user selects an option, this watch will fire needlessly
324
- if (ngOptions.trackBy) {
325
- scope.$watch(
326
- ngOptions.getTrackByValue(ngModelCtrl.$viewValue),
327
- () => {
328
- ngModelCtrl.$render();
329
- },
330
- );
298
+ option.element.setAttribute("selected", "selected");
299
+ } else {
300
+ selectCtrl.selectUnknownOrEmptyOption(value);
331
301
  }
332
- } else {
333
- selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
334
- // The options might not be defined yet when ngModel tries to render
335
- if (!options) return;
336
-
337
- // Only set `<option>.selected` if necessary, in order to prevent some browsers from
338
- // scrolling to `<option>` elements that are outside the `<select>` element's viewport.
339
- const selectedOptions =
340
- (values && values.map(getAndUpdateSelectedOption)) || [];
341
-
342
- options.items.forEach((option) => {
343
- if (option.element.selected && !includes(selectedOptions, option)) {
344
- option.element.selected = false;
345
- }
346
- });
347
- };
348
-
349
- selectCtrl.readValue = function readNgOptionsMultiple() {
350
- const selectedValues = selectElement.value || [];
351
- const selections = [];
352
- // @ts-ignore
353
- selectedValues.forEach((value) => {
354
- const option = options.selectValueMap[value];
355
- if (option && !option.disabled)
356
- selections.push(options.getViewValueFromOption(option));
357
- });
358
-
359
- return selections;
360
- };
302
+ };
361
303
 
362
- // If we are using `track by` then we must watch these tracked values on the model
363
- // since ngModel only watches for object identity change
364
- // if (ngOptions.trackBy) {
365
- // scope.$watchCollection(
366
- // () => {
367
- // if (Array.isArray(ngModelCtrl.$viewValue)) {
368
- // return ngModelCtrl.$viewValue.map((value) =>
369
- // ngOptions.getTrackByValue(value),
370
- // );
371
- // }
372
- // },
373
- // () => {
374
- // ngModelCtrl.$render();
375
- // },
376
- // );
377
- // }
378
- }
304
+ selectCtrl.readValue = function readNgOptionsValue() {
305
+ const selectedOption = options.selectValueMap[selectElement.value];
379
306
 
380
- if (providedEmptyOption) {
381
- // compile the element since there might be bindings in it
382
- const linkFn = $compile(selectCtrl.emptyOption);
383
- assertArg(linkFn, "LinkFn required");
384
- selectElement.prepend(selectCtrl.emptyOption);
385
- linkFn(scope);
386
-
387
- if (selectCtrl.emptyOption.nodeType === Node.COMMENT_NODE) {
388
- // This means the empty option has currently no actual DOM node, probably because
389
- // it has been modified by a transclusion directive.
390
- selectCtrl.hasEmptyOption = false;
391
-
392
- // Redefine the registerOption function, which will catch
393
- // options that are added by ngIf etc. (rendering of the node is async because of
394
- // lazy transclusion)
395
- selectCtrl.registerOption = function (optionScope, optionEl) {
396
- if (optionEl.value === "") {
397
- selectCtrl.hasEmptyOption = true;
398
- selectCtrl.emptyOption = optionEl;
399
- // This ensures the new empty option is selected if previously no option was selected
400
- ngModelCtrl.$render();
401
-
402
- optionEl.addEventListener("$destroy", () => {
403
- const needsRerender = selectCtrl.$isEmptyOptionSelected();
404
-
405
- selectCtrl.hasEmptyOption = false;
406
- selectCtrl.emptyOption = undefined;
407
-
408
- if (needsRerender) ngModelCtrl.$render();
409
- });
410
- }
411
- };
307
+ if (selectedOption && !selectedOption.disabled) {
308
+ selectCtrl.unselectEmptyOption();
309
+ selectCtrl.removeUnknownOption();
310
+ return options.getViewValueFromOption(selectedOption);
412
311
  }
312
+ return null;
313
+ };
314
+
315
+ // If we are using `track by` then we must watch the tracked value on the model
316
+ // since ngModel only watches for object identity change
317
+ // FIXME: When a user selects an option, this watch will fire needlessly
318
+ if (ngOptions.trackBy) {
319
+ scope.$watch(ngOptions.getTrackByValue(ngModelCtrl.$viewValue), () => {
320
+ ngModelCtrl.$render();
321
+ });
413
322
  }
323
+ } else {
324
+ selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
325
+ // The options might not be defined yet when ngModel tries to render
326
+ if (!options) return;
414
327
 
415
- // We will re-render the option elements if the option values or labels change
328
+ // Only set `<option>.selected` if necessary, in order to prevent some browsers from
329
+ // scrolling to `<option>` elements that are outside the `<select>` element's viewport.
330
+ const selectedOptions =
331
+ (values && values.map(getAndUpdateSelectedOption)) || [];
416
332
 
417
- // let watchables = ngOptions.getWatchables();
418
- // watchables.forEach((i) => {
419
- // scope.$watch(i, updateOptions);
420
- // });
421
- scope.$watch(
422
- ngOptions.getWatchables.decoratedNode.body[0].expression.name,
423
- updateOptions,
424
- );
333
+ options.items.forEach((option) => {
334
+ if (option.element.selected && !includes(selectedOptions, option)) {
335
+ option.element.selected = false;
336
+ }
337
+ });
338
+ };
425
339
 
426
- // ------------------------------------------------------------------ //
427
-
428
- function addOptionElement(option, parent) {
429
- /**
430
- * @type {HTMLOptionElement}
431
- */
432
- const optionElement = /** @type {HTMLOptionElement} */ (
433
- optionTemplate.cloneNode(false)
434
- );
435
- parent.appendChild(optionElement);
436
- updateOptionElement(option, optionElement);
437
- }
340
+ selectCtrl.readValue = function readNgOptionsMultiple() {
341
+ const selectedValues = selectElement.value || [];
342
+ const selections = [];
343
+ // @ts-ignore
344
+ selectedValues.forEach((value) => {
345
+ const option = options.selectValueMap[value];
346
+ if (option && !option.disabled)
347
+ selections.push(options.getViewValueFromOption(option));
348
+ });
438
349
 
439
- function getAndUpdateSelectedOption(viewValue) {
440
- const option = options.getOptionFromViewValue(viewValue);
441
- const element = option && option.element;
350
+ return selections;
351
+ };
442
352
 
443
- if (element && !element.selected) element.selected = true;
353
+ // If we are using `track by` then we must watch these tracked values on the model
354
+ // since ngModel only watches for object identity change
355
+ // if (ngOptions.trackBy) {
356
+ // scope.$watchCollection(
357
+ // () => {
358
+ // if (Array.isArray(ngModelCtrl.$viewValue)) {
359
+ // return ngModelCtrl.$viewValue.map((value) =>
360
+ // ngOptions.getTrackByValue(value),
361
+ // );
362
+ // }
363
+ // },
364
+ // () => {
365
+ // ngModelCtrl.$render();
366
+ // },
367
+ // );
368
+ // }
369
+ }
444
370
 
445
- return option;
446
- }
371
+ if (providedEmptyOption) {
372
+ // compile the element since there might be bindings in it
373
+ const linkFn = $compile(selectCtrl.emptyOption);
374
+ assertArg(linkFn, "LinkFn required");
375
+ selectElement.prepend(selectCtrl.emptyOption);
376
+ linkFn(scope);
377
+
378
+ if (selectCtrl.emptyOption.nodeType === Node.COMMENT_NODE) {
379
+ // This means the empty option has currently no actual DOM node, probably because
380
+ // it has been modified by a transclusion directive.
381
+ selectCtrl.hasEmptyOption = false;
382
+
383
+ // Redefine the registerOption function, which will catch
384
+ // options that are added by ngIf etc. (rendering of the node is async because of
385
+ // lazy transclusion)
386
+ selectCtrl.registerOption = function (optionScope, optionEl) {
387
+ if (optionEl.value === "") {
388
+ selectCtrl.hasEmptyOption = true;
389
+ selectCtrl.emptyOption = optionEl;
390
+ // This ensures the new empty option is selected if previously no option was selected
391
+ ngModelCtrl.$render();
447
392
 
448
- function updateOptionElement(option, element) {
449
- option.element = element;
450
- element.disabled = option.disabled;
451
- // Support: IE 11 only, Edge 12-13 only
452
- // NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
453
- // selects in certain circumstances when multiple selects are next to each other and display
454
- // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
455
- // See https://github.com/angular/angular.js/issues/11314 for more info.
456
- // This is unfortunately untestable with unit / e2e tests
457
- if (option.label !== element.label) {
458
- element.label = option.label;
459
- element.textContent = option.label;
460
- }
461
- element.value = option.selectValue;
462
- }
393
+ optionEl.addEventListener("$destroy", () => {
394
+ const needsRerender = selectCtrl.$isEmptyOptionSelected();
463
395
 
464
- function updateOptions() {
465
- const previousValue = options && selectCtrl.readValue();
466
-
467
- // We must remove all current options, but cannot simply set innerHTML = null
468
- // since the providedEmptyOption might have an ngIf on it that inserts comments which we
469
- // must preserve.
470
- // Instead, iterate over the current option elements and remove them or their optgroup
471
- // parents
472
- if (options) {
473
- for (let i = options.items.length - 1; i >= 0; i--) {
474
- const option = options.items[i];
475
- if (isDefined(option.group)) {
476
- removeElement(option.element.parentNode);
477
- } else {
478
- removeElement(option.element);
479
- }
396
+ selectCtrl.hasEmptyOption = false;
397
+ selectCtrl.emptyOption = undefined;
398
+
399
+ if (needsRerender) ngModelCtrl.$render();
400
+ });
480
401
  }
481
- }
402
+ };
403
+ }
404
+ }
482
405
 
483
- options = ngOptions.getOptions();
406
+ // We will re-render the option elements if the option values or labels change
407
+
408
+ // let watchables = ngOptions.getWatchables();
409
+ // watchables.forEach((i) => {
410
+ // scope.$watch(i, updateOptions);
411
+ // });
412
+ scope.$watch(
413
+ ngOptions.getWatchables.decoratedNode.body[0].expression.name,
414
+ updateOptions,
415
+ );
416
+
417
+ // ------------------------------------------------------------------ //
418
+
419
+ function addOptionElement(option, parent) {
420
+ /**
421
+ * @type {HTMLOptionElement}
422
+ */
423
+ const optionElement = /** @type {HTMLOptionElement} */ (
424
+ optionTemplate.cloneNode(false)
425
+ );
426
+ parent.appendChild(optionElement);
427
+ updateOptionElement(option, optionElement);
428
+ }
484
429
 
485
- const groupElementMap = {};
430
+ function getAndUpdateSelectedOption(viewValue) {
431
+ const option = options.getOptionFromViewValue(viewValue);
432
+ const element = option && option.element;
486
433
 
487
- options.items.forEach((option) => {
488
- let groupElement;
434
+ if (element && !element.selected) element.selected = true;
489
435
 
436
+ return option;
437
+ }
438
+
439
+ function updateOptionElement(option, element) {
440
+ option.element = element;
441
+ element.disabled = option.disabled;
442
+ // Support: IE 11 only, Edge 12-13 only
443
+ // NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
444
+ // selects in certain circumstances when multiple selects are next to each other and display
445
+ // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
446
+ // See https://github.com/angular/angular.js/issues/11314 for more info.
447
+ // This is unfortunately untestable with unit / e2e tests
448
+ if (option.label !== element.label) {
449
+ element.label = option.label;
450
+ element.textContent = option.label;
451
+ }
452
+ element.value = option.selectValue;
453
+ }
454
+
455
+ function updateOptions() {
456
+ const previousValue = options && selectCtrl.readValue();
457
+
458
+ // We must remove all current options, but cannot simply set innerHTML = null
459
+ // since the providedEmptyOption might have an ngIf on it that inserts comments which we
460
+ // must preserve.
461
+ // Instead, iterate over the current option elements and remove them or their optgroup
462
+ // parents
463
+ if (options) {
464
+ for (let i = options.items.length - 1; i >= 0; i--) {
465
+ const option = options.items[i];
490
466
  if (isDefined(option.group)) {
491
- // This option is to live in a group
492
- // See if we have already created this group
493
- groupElement = groupElementMap[option.group];
467
+ removeElement(option.element.parentNode);
468
+ } else {
469
+ removeElement(option.element);
470
+ }
471
+ }
472
+ }
494
473
 
495
- if (!groupElement) {
496
- groupElement = optGroupTemplate.cloneNode(false);
497
- listFragment.appendChild(groupElement);
474
+ options = ngOptions.getOptions();
498
475
 
499
- // Update the label on the group element
500
- // "null" is special cased because of Safari
501
- /** @type {HTMLOptGroupElement} */
502
- (groupElement).label =
503
- option.group === null ? "null" : option.group;
476
+ const groupElementMap = {};
504
477
 
505
- // Store it for use later
506
- groupElementMap[option.group] = groupElement;
507
- }
478
+ options.items.forEach((option) => {
479
+ let groupElement;
508
480
 
509
- addOptionElement(option, groupElement);
510
- } else {
511
- // This option is not in a group
512
- addOptionElement(option, listFragment);
513
- }
514
- });
481
+ if (isDefined(option.group)) {
482
+ // This option is to live in a group
483
+ // See if we have already created this group
484
+ groupElement = groupElementMap[option.group];
515
485
 
516
- selectElement.appendChild(listFragment);
486
+ if (!groupElement) {
487
+ groupElement = optGroupTemplate.cloneNode(false);
488
+ listFragment.appendChild(groupElement);
517
489
 
518
- ngModelCtrl.$render();
490
+ // Update the label on the group element
491
+ // "null" is special cased because of Safari
492
+ /** @type {HTMLOptGroupElement} */
493
+ (groupElement).label =
494
+ option.group === null ? "null" : option.group;
519
495
 
520
- // Check to see if the value has changed due to the update to the options
521
- if (!ngModelCtrl.$isEmpty(previousValue)) {
522
- const nextValue = selectCtrl.readValue();
523
- const isNotPrimitive = ngOptions.trackBy || multiple;
524
- if (
525
- isNotPrimitive
526
- ? !equals(previousValue, nextValue)
527
- : previousValue !== nextValue
528
- ) {
529
- ngModelCtrl.$setViewValue(nextValue);
530
- ngModelCtrl.$render();
496
+ // Store it for use later
497
+ groupElementMap[option.group] = groupElement;
531
498
  }
499
+
500
+ addOptionElement(option, groupElement);
501
+ } else {
502
+ // This option is not in a group
503
+ addOptionElement(option, listFragment);
504
+ }
505
+ });
506
+
507
+ selectElement.appendChild(listFragment);
508
+
509
+ ngModelCtrl.$render();
510
+
511
+ // Check to see if the value has changed due to the update to the options
512
+ if (!ngModelCtrl.$isEmpty(previousValue)) {
513
+ const nextValue = selectCtrl.readValue();
514
+ const isNotPrimitive = ngOptions.trackBy || multiple;
515
+ if (
516
+ isNotPrimitive
517
+ ? !equals(previousValue, nextValue)
518
+ : previousValue !== nextValue
519
+ ) {
520
+ ngModelCtrl.$setViewValue(nextValue);
521
+ ngModelCtrl.$render();
532
522
  }
533
523
  }
534
524
  }
535
-
536
- return {
537
- restrict: "A",
538
- terminal: true,
539
- require: ["select", "ngModel"],
540
- link: {
541
- pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
542
- // Deactivate the SelectController.register method to prevent
543
- // option directives from accidentally registering themselves
544
- // (and unwanted $destroy handlers etc.)
545
- ctrls[0].registerOption = () => {};
546
- },
547
- post: ngOptionsPostLink,
525
+ }
526
+
527
+ return {
528
+ restrict: "A",
529
+ terminal: true,
530
+ require: ["select", "ngModel"],
531
+ link: {
532
+ pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
533
+ // Deactivate the SelectController.register method to prevent
534
+ // option directives from accidentally registering themselves
535
+ // (and unwanted $destroy handlers etc.)
536
+ ctrls[0].registerOption = () => {};
548
537
  },
549
- };
550
- },
551
- ];
538
+ post: ngOptionsPostLink,
539
+ },
540
+ };
541
+ }