@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.
- package/.eslintignore +1 -0
- package/.eslintrc.cjs +29 -0
- package/.github/workflows/playwright.yml +27 -0
- package/CHANGELOG.md +17974 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +246 -0
- package/DEVELOPERS.md +488 -0
- package/LICENSE +22 -0
- package/Makefile +31 -0
- package/README.md +115 -0
- package/RELEASE.md +98 -0
- package/SECURITY.md +16 -0
- package/TRIAGING.md +135 -0
- package/css/angular.css +22 -0
- package/dist/angular-ts.cjs.js +36843 -0
- package/dist/angular-ts.esm.js +36841 -0
- package/dist/angular-ts.umd.js +36848 -0
- package/dist/build/angular-animate.js +4272 -0
- package/dist/build/angular-aria.js +426 -0
- package/dist/build/angular-message-format.js +1072 -0
- package/dist/build/angular-messages.js +829 -0
- package/dist/build/angular-mocks.js +3757 -0
- package/dist/build/angular-parse-ext.js +1275 -0
- package/dist/build/angular-resource.js +911 -0
- package/dist/build/angular-route.js +1266 -0
- package/dist/build/angular-sanitize.js +891 -0
- package/dist/build/angular-touch.js +368 -0
- package/dist/build/angular.js +36600 -0
- package/e2e/unit.spec.ts +15 -0
- package/images/android-chrome-192x192.png +0 -0
- package/images/android-chrome-512x512.png +0 -0
- package/images/apple-touch-icon.png +0 -0
- package/images/favicon-16x16.png +0 -0
- package/images/favicon-32x32.png +0 -0
- package/images/favicon.ico +0 -0
- package/images/site.webmanifest +1 -0
- package/index.html +104 -0
- package/package.json +47 -0
- package/playwright.config.ts +78 -0
- package/public/circle.html +1 -0
- package/public/my_child_directive.html +1 -0
- package/public/my_directive.html +1 -0
- package/public/my_other_directive.html +1 -0
- package/public/test.html +1 -0
- package/rollup.config.js +31 -0
- package/src/animations/animateCache.js +55 -0
- package/src/animations/animateChildrenDirective.js +105 -0
- package/src/animations/animateCss.js +1139 -0
- package/src/animations/animateCssDriver.js +291 -0
- package/src/animations/animateJs.js +367 -0
- package/src/animations/animateJsDriver.js +67 -0
- package/src/animations/animateQueue.js +851 -0
- package/src/animations/animation.js +506 -0
- package/src/animations/module.js +779 -0
- package/src/animations/ngAnimateSwap.js +119 -0
- package/src/animations/rafScheduler.js +50 -0
- package/src/animations/shared.js +378 -0
- package/src/constants.js +20 -0
- package/src/core/animate.js +845 -0
- package/src/core/animateCss.js +73 -0
- package/src/core/animateRunner.js +195 -0
- package/src/core/attributes.js +199 -0
- package/src/core/cache.js +45 -0
- package/src/core/compile.js +4727 -0
- package/src/core/controller.js +225 -0
- package/src/core/exceptionHandler.js +63 -0
- package/src/core/filter.js +146 -0
- package/src/core/interpolate.js +442 -0
- package/src/core/interval.js +188 -0
- package/src/core/intervalFactory.js +57 -0
- package/src/core/location.js +1086 -0
- package/src/core/parser/parse.js +2562 -0
- package/src/core/parser/parse.md +13 -0
- package/src/core/q.js +746 -0
- package/src/core/rootScope.js +1596 -0
- package/src/core/sanitizeUri.js +85 -0
- package/src/core/sce.js +1161 -0
- package/src/core/taskTrackerFactory.js +125 -0
- package/src/core/timeout.js +121 -0
- package/src/core/urlUtils.js +187 -0
- package/src/core/utils.js +1349 -0
- package/src/directive/a.js +37 -0
- package/src/directive/attrs.js +283 -0
- package/src/directive/bind.js +51 -0
- package/src/directive/bind.md +142 -0
- package/src/directive/change.js +12 -0
- package/src/directive/change.md +25 -0
- package/src/directive/cloak.js +12 -0
- package/src/directive/cloak.md +24 -0
- package/src/directive/events.js +75 -0
- package/src/directive/events.md +166 -0
- package/src/directive/form.js +725 -0
- package/src/directive/init.js +15 -0
- package/src/directive/init.md +41 -0
- package/src/directive/input.js +1783 -0
- package/src/directive/list.js +46 -0
- package/src/directive/list.md +22 -0
- package/src/directive/ngClass.js +249 -0
- package/src/directive/ngController.js +64 -0
- package/src/directive/ngCsp.js +82 -0
- package/src/directive/ngIf.js +134 -0
- package/src/directive/ngInclude.js +217 -0
- package/src/directive/ngModel.js +1356 -0
- package/src/directive/ngModelOptions.js +509 -0
- package/src/directive/ngOptions.js +670 -0
- package/src/directive/ngRef.js +90 -0
- package/src/directive/ngRepeat.js +650 -0
- package/src/directive/ngShowHide.js +255 -0
- package/src/directive/ngSwitch.js +178 -0
- package/src/directive/ngTransclude.js +98 -0
- package/src/directive/non-bindable.js +11 -0
- package/src/directive/non-bindable.md +17 -0
- package/src/directive/script.js +30 -0
- package/src/directive/select.js +624 -0
- package/src/directive/style.js +25 -0
- package/src/directive/style.md +23 -0
- package/src/directive/validators.js +329 -0
- package/src/exts/aria.js +544 -0
- package/src/exts/messages.js +852 -0
- package/src/filters/filter.js +207 -0
- package/src/filters/filter.md +69 -0
- package/src/filters/filters.js +239 -0
- package/src/filters/json.md +16 -0
- package/src/filters/limit-to.js +43 -0
- package/src/filters/limit-to.md +19 -0
- package/src/filters/order-by.js +183 -0
- package/src/filters/order-by.md +83 -0
- package/src/index.js +13 -0
- package/src/injector.js +1034 -0
- package/src/jqLite.js +1117 -0
- package/src/loader.js +1320 -0
- package/src/public.js +215 -0
- package/src/routeToRegExp.js +41 -0
- package/src/services/anchorScroll.js +135 -0
- package/src/services/browser.js +321 -0
- package/src/services/cacheFactory.js +398 -0
- package/src/services/cookieReader.js +72 -0
- package/src/services/document.js +64 -0
- package/src/services/http.js +1537 -0
- package/src/services/httpBackend.js +206 -0
- package/src/services/log.js +160 -0
- package/src/services/templateRequest.js +139 -0
- package/test/angular.spec.js +2153 -0
- package/test/aria/aria.spec.js +1245 -0
- package/test/binding.spec.js +504 -0
- package/test/build-test.html +14 -0
- package/test/injector.spec.js +2327 -0
- package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
- package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
- package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
- package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
- package/test/jasmine/jasmine-browser.json +17 -0
- package/test/jasmine/jasmine.json +9 -0
- package/test/jqlite.spec.js +2133 -0
- package/test/loader.spec.js +219 -0
- package/test/messages/messages.spec.js +1146 -0
- package/test/min-err.spec.js +174 -0
- package/test/mock-test.html +13 -0
- package/test/module-test.html +15 -0
- package/test/ng/anomate.spec.js +606 -0
- package/test/ng/cache-factor.spec.js +334 -0
- package/test/ng/compile.spec.js +17956 -0
- package/test/ng/controller-provider.spec.js +227 -0
- package/test/ng/cookie-reader.spec.js +98 -0
- package/test/ng/directive/a.spec.js +192 -0
- package/test/ng/directive/bind.spec.js +334 -0
- package/test/ng/directive/boolean.spec.js +136 -0
- package/test/ng/directive/change.spec.js +71 -0
- package/test/ng/directive/class.spec.js +858 -0
- package/test/ng/directive/click.spec.js +38 -0
- package/test/ng/directive/cloak.spec.js +44 -0
- package/test/ng/directive/constoller.spec.js +194 -0
- package/test/ng/directive/element-style.spec.js +92 -0
- package/test/ng/directive/event.spec.js +282 -0
- package/test/ng/directive/form.spec.js +1518 -0
- package/test/ng/directive/href.spec.js +143 -0
- package/test/ng/directive/if.spec.js +402 -0
- package/test/ng/directive/include.spec.js +828 -0
- package/test/ng/directive/init.spec.js +68 -0
- package/test/ng/directive/input.spec.js +3810 -0
- package/test/ng/directive/list.spec.js +170 -0
- package/test/ng/directive/model-options.spec.js +1008 -0
- package/test/ng/directive/model.spec.js +1905 -0
- package/test/ng/directive/non-bindable.spec.js +55 -0
- package/test/ng/directive/options.spec.js +3583 -0
- package/test/ng/directive/ref.spec.js +575 -0
- package/test/ng/directive/repeat.spec.js +1675 -0
- package/test/ng/directive/script.spec.js +52 -0
- package/test/ng/directive/scrset.spec.js +67 -0
- package/test/ng/directive/select.spec.js +2541 -0
- package/test/ng/directive/show-hide.spec.js +253 -0
- package/test/ng/directive/src.spec.js +157 -0
- package/test/ng/directive/style.spec.js +178 -0
- package/test/ng/directive/switch.spec.js +647 -0
- package/test/ng/directive/validators.spec.js +717 -0
- package/test/ng/document.spec.js +52 -0
- package/test/ng/filter/filter.spec.js +714 -0
- package/test/ng/filter/filters.spec.js +35 -0
- package/test/ng/filter/limit-to.spec.js +251 -0
- package/test/ng/filter/order-by.spec.js +891 -0
- package/test/ng/filter.spec.js +149 -0
- package/test/ng/http-backend.spec.js +398 -0
- package/test/ng/http.spec.js +4071 -0
- package/test/ng/interpolate.spec.js +642 -0
- package/test/ng/interval.spec.js +343 -0
- package/test/ng/location.spec.js +3488 -0
- package/test/ng/on.spec.js +229 -0
- package/test/ng/parse.spec.js +4655 -0
- package/test/ng/prop.spec.js +805 -0
- package/test/ng/q.spec.js +2904 -0
- package/test/ng/root-element.spec.js +16 -0
- package/test/ng/sanitize-uri.spec.js +249 -0
- package/test/ng/sce.spec.js +660 -0
- package/test/ng/scope.spec.js +3442 -0
- package/test/ng/template-request.spec.js +236 -0
- package/test/ng/timeout.spec.js +351 -0
- package/test/ng/url-utils.spec.js +156 -0
- package/test/ng/utils.spec.js +144 -0
- package/test/original-test.html +21 -0
- package/test/public.spec.js +34 -0
- package/test/sanitize/bing-html.spec.js +36 -0
- package/test/server/express.js +158 -0
- package/test/test-utils.js +11 -0
- package/tsconfig.json +17 -0
- package/types/angular.d.ts +138 -0
- package/types/global.d.ts +9 -0
- package/types/index.d.ts +2357 -0
- package/types/jqlite.d.ts +558 -0
- package/vite.config.js +14 -0
|
@@ -0,0 +1,1783 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isDefined,
|
|
3
|
+
isUndefined,
|
|
4
|
+
isString,
|
|
5
|
+
lowercase,
|
|
6
|
+
trim,
|
|
7
|
+
isObject,
|
|
8
|
+
isNumber,
|
|
9
|
+
isNumberNaN,
|
|
10
|
+
isDate,
|
|
11
|
+
forEach,
|
|
12
|
+
convertTimezoneToLocal,
|
|
13
|
+
addDateMinutes,
|
|
14
|
+
timezoneToOffset,
|
|
15
|
+
nextUid,
|
|
16
|
+
equals,
|
|
17
|
+
} from "../core/utils";
|
|
18
|
+
import { ngModelMinErr } from "./ngModel";
|
|
19
|
+
|
|
20
|
+
// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
|
|
21
|
+
export const ISO_DATE_REGEXP =
|
|
22
|
+
/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
|
|
23
|
+
// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
|
|
24
|
+
// Note: We are being more lenient, because browsers are too.
|
|
25
|
+
// 1. Scheme
|
|
26
|
+
// 2. Slashes
|
|
27
|
+
// 3. Username
|
|
28
|
+
// 4. Password
|
|
29
|
+
// 5. Hostname
|
|
30
|
+
// 6. Port
|
|
31
|
+
// 7. Path
|
|
32
|
+
// 8. Query
|
|
33
|
+
// 9. Fragment
|
|
34
|
+
// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999
|
|
35
|
+
export const URL_REGEXP =
|
|
36
|
+
/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
|
|
37
|
+
// eslint-disable-next-line max-len
|
|
38
|
+
export const EMAIL_REGEXP =
|
|
39
|
+
/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
|
|
40
|
+
const NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
|
|
41
|
+
const DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
|
|
42
|
+
const DATETIMELOCAL_REGEXP =
|
|
43
|
+
/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
|
44
|
+
const WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
|
|
45
|
+
const MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
|
|
46
|
+
const TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
|
47
|
+
// The name of a form control's ValidityState property.
|
|
48
|
+
// This is used so that it's possible for internal tests to create mock ValidityStates.
|
|
49
|
+
export const VALIDITY_STATE_PROPERTY = "validity";
|
|
50
|
+
|
|
51
|
+
const PARTIAL_VALIDATION_EVENTS = "keydown wheel mousedown";
|
|
52
|
+
/**
|
|
53
|
+
* @type {Map<string, boolean>}
|
|
54
|
+
*/
|
|
55
|
+
const PARTIAL_VALIDATION_TYPES = new Map();
|
|
56
|
+
"date,datetime-local,month,time,week".split(",").forEach((type) => {
|
|
57
|
+
PARTIAL_VALIDATION_TYPES.set(type, true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const inputType = {
|
|
61
|
+
/**
|
|
62
|
+
* @ngdoc input
|
|
63
|
+
* @name input[text]
|
|
64
|
+
*
|
|
65
|
+
* @description
|
|
66
|
+
* Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements.
|
|
67
|
+
*
|
|
68
|
+
*
|
|
69
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
70
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
71
|
+
* @param {string=} required Adds `required` validation error key if the value is not entered.
|
|
72
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
73
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
74
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
75
|
+
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
|
76
|
+
* minlength.
|
|
77
|
+
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
|
78
|
+
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
|
79
|
+
* any length.
|
|
80
|
+
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
|
81
|
+
* that contains the regular expression body that will be converted to a regular expression
|
|
82
|
+
* as in the ngPattern directive.
|
|
83
|
+
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
|
84
|
+
* does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
|
|
85
|
+
* If the expression evaluates to a RegExp object, then this is used directly.
|
|
86
|
+
* If the expression evaluates to a string, then it will be converted to a RegExp
|
|
87
|
+
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
|
88
|
+
* `new RegExp('^abc$')`.<br />
|
|
89
|
+
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
|
|
90
|
+
* start at the index of the last search's match, thus not taking the whole input value into
|
|
91
|
+
* account.
|
|
92
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
93
|
+
* interaction with the input element.
|
|
94
|
+
* @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
|
|
95
|
+
* This parameter is ignored for input[type=password] controls, which will never trim the
|
|
96
|
+
* input.
|
|
97
|
+
*/
|
|
98
|
+
text: textInputType,
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @ngdoc input
|
|
102
|
+
* @name input[date]
|
|
103
|
+
*
|
|
104
|
+
* @description
|
|
105
|
+
* Input with date validation and transformation. In browsers that do not yet support
|
|
106
|
+
* the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
|
|
107
|
+
* date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
|
|
108
|
+
* modern browsers do not yet support this input type, it is important to provide cues to users on the
|
|
109
|
+
* expected input format via a placeholder or label.
|
|
110
|
+
*
|
|
111
|
+
* The model must always be a Date object, otherwise AngularJS will throw an error.
|
|
112
|
+
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
|
113
|
+
*
|
|
114
|
+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
|
115
|
+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
118
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
119
|
+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
|
120
|
+
* valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
|
|
121
|
+
* (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
|
|
122
|
+
* constraint validation.
|
|
123
|
+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
|
124
|
+
* a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
|
|
125
|
+
* (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
|
|
126
|
+
* constraint validation.
|
|
127
|
+
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
|
|
128
|
+
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
|
129
|
+
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
|
|
130
|
+
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
|
131
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
132
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
133
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
134
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
135
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
136
|
+
* interaction with the input element.
|
|
137
|
+
*
|
|
138
|
+
*/
|
|
139
|
+
date: createDateInputType(
|
|
140
|
+
"date",
|
|
141
|
+
DATE_REGEXP,
|
|
142
|
+
createDateParser(DATE_REGEXP, ["yyyy", "MM", "dd"]),
|
|
143
|
+
"yyyy-MM-dd",
|
|
144
|
+
),
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @ngdoc input
|
|
148
|
+
* @name input[datetime-local]
|
|
149
|
+
*
|
|
150
|
+
* @description
|
|
151
|
+
* Input with datetime validation and transformation. In browsers that do not yet support
|
|
152
|
+
* the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
|
153
|
+
* local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
|
|
154
|
+
*
|
|
155
|
+
* The model must always be a Date object, otherwise AngularJS will throw an error.
|
|
156
|
+
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
|
157
|
+
*
|
|
158
|
+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
|
159
|
+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
|
160
|
+
*
|
|
161
|
+
* The format of the displayed time can be adjusted with the
|
|
162
|
+
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
|
163
|
+
* and `timeStripZeroSeconds`.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
166
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
167
|
+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
|
168
|
+
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
|
|
169
|
+
* inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
|
|
170
|
+
* Note that `min` will also add native HTML5 constraint validation.
|
|
171
|
+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
|
172
|
+
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
|
|
173
|
+
* inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
|
|
174
|
+
* Note that `max` will also add native HTML5 constraint validation.
|
|
175
|
+
* @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
|
|
176
|
+
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
|
177
|
+
* @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
|
|
178
|
+
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
|
179
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
180
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
181
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
182
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
183
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
184
|
+
* interaction with the input element.
|
|
185
|
+
*
|
|
186
|
+
*/
|
|
187
|
+
"datetime-local": createDateInputType(
|
|
188
|
+
"datetimelocal",
|
|
189
|
+
DATETIMELOCAL_REGEXP,
|
|
190
|
+
createDateParser(DATETIMELOCAL_REGEXP, [
|
|
191
|
+
"yyyy",
|
|
192
|
+
"MM",
|
|
193
|
+
"dd",
|
|
194
|
+
"HH",
|
|
195
|
+
"mm",
|
|
196
|
+
"ss",
|
|
197
|
+
"sss",
|
|
198
|
+
]),
|
|
199
|
+
"yyyy-MM-ddTHH:mm:ss.sss",
|
|
200
|
+
),
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @ngdoc input
|
|
204
|
+
* @name input[time]
|
|
205
|
+
*
|
|
206
|
+
* @description
|
|
207
|
+
* Input with time validation and transformation. In browsers that do not yet support
|
|
208
|
+
* the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
|
209
|
+
* local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
|
|
210
|
+
* Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
|
|
211
|
+
*
|
|
212
|
+
* The model must always be a Date object, otherwise AngularJS will throw an error.
|
|
213
|
+
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
|
214
|
+
*
|
|
215
|
+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
|
216
|
+
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default,
|
|
217
|
+
* this is the timezone of the browser.
|
|
218
|
+
*
|
|
219
|
+
* The format of the displayed time can be adjusted with the
|
|
220
|
+
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
|
221
|
+
* and `timeStripZeroSeconds`.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
224
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
225
|
+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
|
226
|
+
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
|
|
227
|
+
* attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
|
|
228
|
+
* native HTML5 constraint validation.
|
|
229
|
+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
|
230
|
+
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
|
|
231
|
+
* attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
|
|
232
|
+
* native HTML5 constraint validation.
|
|
233
|
+
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
|
|
234
|
+
* `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
|
235
|
+
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
|
|
236
|
+
* `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
|
237
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
238
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
239
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
240
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
241
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
242
|
+
* interaction with the input element.
|
|
243
|
+
*
|
|
244
|
+
*/
|
|
245
|
+
time: createDateInputType(
|
|
246
|
+
"time",
|
|
247
|
+
TIME_REGEXP,
|
|
248
|
+
createDateParser(TIME_REGEXP, ["HH", "mm", "ss", "sss"]),
|
|
249
|
+
"HH:mm:ss.sss",
|
|
250
|
+
),
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @ngdoc input
|
|
254
|
+
* @name input[week]
|
|
255
|
+
*
|
|
256
|
+
* @description
|
|
257
|
+
* Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
|
|
258
|
+
* the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
|
259
|
+
* week format (yyyy-W##), for example: `2013-W02`.
|
|
260
|
+
*
|
|
261
|
+
* The model must always be a Date object, otherwise AngularJS will throw an error.
|
|
262
|
+
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
|
263
|
+
*
|
|
264
|
+
* The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week,
|
|
265
|
+
* due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the
|
|
266
|
+
* year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates
|
|
267
|
+
*
|
|
268
|
+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
|
269
|
+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
|
270
|
+
*
|
|
271
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
272
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
273
|
+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
|
274
|
+
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
|
|
275
|
+
* attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
|
|
276
|
+
* native HTML5 constraint validation.
|
|
277
|
+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
|
278
|
+
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
|
|
279
|
+
* attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
|
|
280
|
+
* native HTML5 constraint validation.
|
|
281
|
+
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
|
|
282
|
+
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
|
283
|
+
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
|
|
284
|
+
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
|
285
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
286
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
287
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
288
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
289
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
290
|
+
* interaction with the input element.
|
|
291
|
+
*/
|
|
292
|
+
week: createDateInputType("week", WEEK_REGEXP, weekParser, "yyyy-Www"),
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @ngdoc input
|
|
296
|
+
* @name input[month]
|
|
297
|
+
*
|
|
298
|
+
* @description
|
|
299
|
+
* Input with month validation and transformation. In browsers that do not yet support
|
|
300
|
+
* the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
|
301
|
+
* month format (yyyy-MM), for example: `2009-01`.
|
|
302
|
+
*
|
|
303
|
+
* The model must always be a Date object, otherwise AngularJS will throw an error.
|
|
304
|
+
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
|
305
|
+
* If the model is not set to the first of the month, the next view to model update will set it
|
|
306
|
+
* to the first of the month.
|
|
307
|
+
*
|
|
308
|
+
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
|
309
|
+
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
|
310
|
+
*
|
|
311
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
312
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
313
|
+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
|
314
|
+
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
|
|
315
|
+
* attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
|
|
316
|
+
* native HTML5 constraint validation.
|
|
317
|
+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
|
318
|
+
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
|
|
319
|
+
* attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
|
|
320
|
+
* native HTML5 constraint validation.
|
|
321
|
+
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
|
|
322
|
+
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
|
323
|
+
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
|
|
324
|
+
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
|
325
|
+
|
|
326
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
327
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
328
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
329
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
330
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
331
|
+
* interaction with the input element.
|
|
332
|
+
*
|
|
333
|
+
*/
|
|
334
|
+
month: createDateInputType(
|
|
335
|
+
"month",
|
|
336
|
+
MONTH_REGEXP,
|
|
337
|
+
createDateParser(MONTH_REGEXP, ["yyyy", "MM"]),
|
|
338
|
+
"yyyy-MM",
|
|
339
|
+
),
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @ngdoc input
|
|
343
|
+
* @name input[number]
|
|
344
|
+
*
|
|
345
|
+
* @description
|
|
346
|
+
* Text input with number validation and transformation. Sets the `number` validation
|
|
347
|
+
* error if not a valid number.
|
|
348
|
+
*
|
|
349
|
+
* <div class="alert alert-warning">
|
|
350
|
+
* The model must always be of type `number` otherwise AngularJS will throw an error.
|
|
351
|
+
* Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
|
|
352
|
+
* error docs for more information and an example of how to convert your model if necessary.
|
|
353
|
+
* </div>
|
|
354
|
+
*
|
|
355
|
+
*
|
|
356
|
+
*
|
|
357
|
+
* @knownIssue
|
|
358
|
+
*
|
|
359
|
+
* ### HTML5 constraint validation and `allowInvalid`
|
|
360
|
+
*
|
|
361
|
+
* In browsers that follow the
|
|
362
|
+
* [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
|
|
363
|
+
* `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
|
|
364
|
+
* If a non-number is entered in the input, the browser will report the value as an empty string,
|
|
365
|
+
* which means the view / model values in `ngModel` and subsequently the scope value
|
|
366
|
+
* will also be an empty string.
|
|
367
|
+
*
|
|
368
|
+
* @knownIssue
|
|
369
|
+
*
|
|
370
|
+
* ### Large numbers and `step` validation
|
|
371
|
+
*
|
|
372
|
+
* The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to
|
|
373
|
+
* Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built
|
|
374
|
+
* libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by
|
|
375
|
+
* {@link guide/forms#modifying-built-in-validators overwriting the validators}
|
|
376
|
+
* for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators}
|
|
377
|
+
* to an `input[text]` element. The source for `input[number]` type can be used as a starting
|
|
378
|
+
* point for both implementations.
|
|
379
|
+
*
|
|
380
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
381
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
382
|
+
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
|
383
|
+
* Can be interpolated.
|
|
384
|
+
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
|
385
|
+
* Can be interpolated.
|
|
386
|
+
* @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`,
|
|
387
|
+
* but does not trigger HTML5 native validation. Takes an expression.
|
|
388
|
+
* @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`,
|
|
389
|
+
* but does not trigger HTML5 native validation. Takes an expression.
|
|
390
|
+
* @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint.
|
|
391
|
+
* Can be interpolated.
|
|
392
|
+
* @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint,
|
|
393
|
+
* but does not trigger HTML5 native validation. Takes an expression.
|
|
394
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
395
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
396
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
397
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
398
|
+
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
|
399
|
+
* minlength.
|
|
400
|
+
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
|
401
|
+
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
|
402
|
+
* any length.
|
|
403
|
+
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
|
404
|
+
* that contains the regular expression body that will be converted to a regular expression
|
|
405
|
+
* as in the ngPattern directive.
|
|
406
|
+
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
|
407
|
+
* does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
|
|
408
|
+
* If the expression evaluates to a RegExp object, then this is used directly.
|
|
409
|
+
* If the expression evaluates to a string, then it will be converted to a RegExp
|
|
410
|
+
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
|
411
|
+
* `new RegExp('^abc$')`.<br />
|
|
412
|
+
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
|
|
413
|
+
* start at the index of the last search's match, thus not taking the whole input value into
|
|
414
|
+
* account.
|
|
415
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
416
|
+
* interaction with the input element.
|
|
417
|
+
*
|
|
418
|
+
*/
|
|
419
|
+
number: numberInputType,
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @ngdoc input
|
|
423
|
+
* @name input[url]
|
|
424
|
+
*
|
|
425
|
+
* @description
|
|
426
|
+
* Text input with URL validation. Sets the `url` validation error key if the content is not a
|
|
427
|
+
* valid URL.
|
|
428
|
+
*
|
|
429
|
+
* <div class="alert alert-warning">
|
|
430
|
+
* **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
|
|
431
|
+
* used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
|
|
432
|
+
* the built-in validators (see the {@link guide/forms Forms guide})
|
|
433
|
+
* </div>
|
|
434
|
+
*
|
|
435
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
436
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
437
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
438
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
439
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
440
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
441
|
+
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
|
442
|
+
* minlength.
|
|
443
|
+
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
|
444
|
+
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
|
445
|
+
* any length.
|
|
446
|
+
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
|
447
|
+
* that contains the regular expression body that will be converted to a regular expression
|
|
448
|
+
* as in the ngPattern directive.
|
|
449
|
+
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
|
450
|
+
* does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
|
|
451
|
+
* If the expression evaluates to a RegExp object, then this is used directly.
|
|
452
|
+
* If the expression evaluates to a string, then it will be converted to a RegExp
|
|
453
|
+
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
|
454
|
+
* `new RegExp('^abc$')`.<br />
|
|
455
|
+
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
|
|
456
|
+
* start at the index of the last search's match, thus not taking the whole input value into
|
|
457
|
+
* account.
|
|
458
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
459
|
+
* interaction with the input element.
|
|
460
|
+
*
|
|
461
|
+
*/
|
|
462
|
+
url: urlInputType,
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @ngdoc input
|
|
466
|
+
* @name input[email]
|
|
467
|
+
*
|
|
468
|
+
* @description
|
|
469
|
+
* Text input with email validation. Sets the `email` validation error key if not a valid email
|
|
470
|
+
* address.
|
|
471
|
+
*
|
|
472
|
+
* <div class="alert alert-warning">
|
|
473
|
+
* **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
|
|
474
|
+
* used in Chromium, which may not fulfill your app's requirements.
|
|
475
|
+
* If you need stricter (e.g. requiring a top-level domain), or more relaxed validation
|
|
476
|
+
* (e.g. allowing IPv6 address literals) you can use `ng-pattern` or
|
|
477
|
+
* modify the built-in validators (see the {@link guide/forms Forms guide}).
|
|
478
|
+
* </div>
|
|
479
|
+
*
|
|
480
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
481
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
482
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
483
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
484
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
485
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
486
|
+
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
|
487
|
+
* minlength.
|
|
488
|
+
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
|
489
|
+
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
|
|
490
|
+
* any length.
|
|
491
|
+
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
|
492
|
+
* that contains the regular expression body that will be converted to a regular expression
|
|
493
|
+
* as in the ngPattern directive.
|
|
494
|
+
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
|
495
|
+
* does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
|
|
496
|
+
* If the expression evaluates to a RegExp object, then this is used directly.
|
|
497
|
+
* If the expression evaluates to a string, then it will be converted to a RegExp
|
|
498
|
+
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
|
499
|
+
* `new RegExp('^abc$')`.<br />
|
|
500
|
+
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
|
|
501
|
+
* start at the index of the last search's match, thus not taking the whole input value into
|
|
502
|
+
* account.
|
|
503
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
504
|
+
* interaction with the input element.
|
|
505
|
+
*
|
|
506
|
+
*/
|
|
507
|
+
email: emailInputType,
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* @ngdoc input
|
|
511
|
+
* @name input[radio]
|
|
512
|
+
*
|
|
513
|
+
* @description
|
|
514
|
+
* HTML radio button.
|
|
515
|
+
*
|
|
516
|
+
* **Note:**<br>
|
|
517
|
+
* All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the
|
|
518
|
+
* value of their `name` attribute to determine the property under which their
|
|
519
|
+
* {@link ngModel.NgModelController NgModelController} will be published on the parent
|
|
520
|
+
* {@link form.FormController FormController}. Thus, if you use the same `name` for multiple
|
|
521
|
+
* inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be
|
|
522
|
+
* published on the parent `FormController` under that name. The rest of the controllers will
|
|
523
|
+
* continue to work as expected, but you won't be able to access them as properties on the parent
|
|
524
|
+
* `FormController`.
|
|
525
|
+
*
|
|
526
|
+
* <div class="alert alert-info">
|
|
527
|
+
* <p>
|
|
528
|
+
* In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so
|
|
529
|
+
* that the browser can manage their state (checked/unchecked) based on the state of other
|
|
530
|
+
* inputs in the same group.
|
|
531
|
+
* </p>
|
|
532
|
+
* <p>
|
|
533
|
+
* In AngularJS forms, this is not necessary. The input's state will be updated based on the
|
|
534
|
+
* value of the underlying model data.
|
|
535
|
+
* </p>
|
|
536
|
+
* </div>
|
|
537
|
+
*
|
|
538
|
+
* <div class="alert alert-success">
|
|
539
|
+
* If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a
|
|
540
|
+
* unique name.
|
|
541
|
+
* </div>
|
|
542
|
+
*
|
|
543
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
544
|
+
* @param {string} value The value to which the `ngModel` expression should be set when selected.
|
|
545
|
+
* Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
|
|
546
|
+
* too. Use `ngValue` if you need complex models (`number`, `object`, ...).
|
|
547
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
548
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
549
|
+
* interaction with the input element.
|
|
550
|
+
* @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio
|
|
551
|
+
* is selected. Should be used instead of the `value` attribute if you need
|
|
552
|
+
* a non-string `ngModel` (`boolean`, `array`, ...).
|
|
553
|
+
*
|
|
554
|
+
*/
|
|
555
|
+
radio: radioInputType,
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* @ngdoc input
|
|
559
|
+
* @name input[range]
|
|
560
|
+
*
|
|
561
|
+
* @description
|
|
562
|
+
* Native range input with validation and transformation.
|
|
563
|
+
*
|
|
564
|
+
* The model for the range input must always be a `Number`.
|
|
565
|
+
*
|
|
566
|
+
* IE9 and other browsers that do not support the `range` type fall back
|
|
567
|
+
* to a text input without any default values for `min`, `max` and `step`. Model binding,
|
|
568
|
+
* validation and number parsing are nevertheless supported.
|
|
569
|
+
*
|
|
570
|
+
* Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
|
|
571
|
+
* in a way that never allows the input to hold an invalid value. That means:
|
|
572
|
+
* - any non-numerical value is set to `(max + min) / 2`.
|
|
573
|
+
* - any numerical value that is less than the current min val, or greater than the current max val
|
|
574
|
+
* is set to the min / max val respectively.
|
|
575
|
+
* - additionally, the current `step` is respected, so the nearest value that satisfies a step
|
|
576
|
+
* is used.
|
|
577
|
+
*
|
|
578
|
+
* See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range))
|
|
579
|
+
* for more info.
|
|
580
|
+
*
|
|
581
|
+
* This has the following consequences for AngularJS:
|
|
582
|
+
*
|
|
583
|
+
* Since the element value should always reflect the current model value, a range input
|
|
584
|
+
* will set the bound ngModel expression to the value that the browser has set for the
|
|
585
|
+
* input element. For example, in the following input `<input type="range" ng-model="model.value">`,
|
|
586
|
+
* if the application sets `model.value = null`, the browser will set the input to `'50'`.
|
|
587
|
+
* AngularJS will then set the model to `50`, to prevent input and model value being out of sync.
|
|
588
|
+
*
|
|
589
|
+
* That means the model for range will immediately be set to `50` after `ngModel` has been
|
|
590
|
+
* initialized. It also means a range input can never have the required error.
|
|
591
|
+
*
|
|
592
|
+
* This does not only affect changes to the model value, but also to the values of the `min`,
|
|
593
|
+
* `max`, and `step` attributes. When these change in a way that will cause the browser to modify
|
|
594
|
+
* the input value, AngularJS will also update the model value.
|
|
595
|
+
*
|
|
596
|
+
* Automatic value adjustment also means that a range input element can never have the `required`,
|
|
597
|
+
* `min`, or `max` errors.
|
|
598
|
+
*
|
|
599
|
+
* However, `step` is currently only fully implemented by Firefox. Other browsers have problems
|
|
600
|
+
* when the step value changes dynamically - they do not adjust the element value correctly, but
|
|
601
|
+
* instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step`
|
|
602
|
+
* error on the input, and set the model to `undefined`.
|
|
603
|
+
*
|
|
604
|
+
* Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
|
|
605
|
+
* not set the `min` and `max` attributes, which means that the browser won't automatically adjust
|
|
606
|
+
* the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
|
|
607
|
+
*
|
|
608
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
609
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
610
|
+
* @param {string=} min Sets the `min` validation to ensure that the value entered is greater
|
|
611
|
+
* than `min`. Can be interpolated.
|
|
612
|
+
* @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
|
|
613
|
+
* Can be interpolated.
|
|
614
|
+
* @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
|
|
615
|
+
* Can be interpolated.
|
|
616
|
+
* @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due
|
|
617
|
+
* to user interaction with the input element.
|
|
618
|
+
* @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the
|
|
619
|
+
* element. **Note** : `ngChecked` should not be used alongside `ngModel`.
|
|
620
|
+
* Checkout {@link ng.directive:ngChecked ngChecked} for usage.
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
<example name="range-input-directive" module="rangeExample">
|
|
624
|
+
<file name="index.html">
|
|
625
|
+
<script>
|
|
626
|
+
angular.module('rangeExample', [])
|
|
627
|
+
.controller('ExampleController', ['$scope', function($scope) {
|
|
628
|
+
$scope.value = 75;
|
|
629
|
+
$scope.min = 10;
|
|
630
|
+
$scope.max = 90;
|
|
631
|
+
}]);
|
|
632
|
+
</script>
|
|
633
|
+
<form name="myForm" ng-controller="ExampleController">
|
|
634
|
+
|
|
635
|
+
Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
|
|
636
|
+
<hr>
|
|
637
|
+
Model as number: <input type="number" ng-model="value"><br>
|
|
638
|
+
Min: <input type="number" ng-model="min"><br>
|
|
639
|
+
Max: <input type="number" ng-model="max"><br>
|
|
640
|
+
value = <code>{{value}}</code><br/>
|
|
641
|
+
myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
|
|
642
|
+
myForm.range.$error = <code>{{myForm.range.$error}}</code>
|
|
643
|
+
</form>
|
|
644
|
+
</file>
|
|
645
|
+
</example>
|
|
646
|
+
|
|
647
|
+
* ## Range Input with ngMin & ngMax attributes
|
|
648
|
+
|
|
649
|
+
* @example
|
|
650
|
+
<example name="range-input-directive-ng" module="rangeExample">
|
|
651
|
+
<file name="index.html">
|
|
652
|
+
<script>
|
|
653
|
+
angular.module('rangeExample', [])
|
|
654
|
+
.controller('ExampleController', ['$scope', function($scope) {
|
|
655
|
+
$scope.value = 75;
|
|
656
|
+
$scope.min = 10;
|
|
657
|
+
$scope.max = 90;
|
|
658
|
+
}]);
|
|
659
|
+
</script>
|
|
660
|
+
<form name="myForm" ng-controller="ExampleController">
|
|
661
|
+
Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
|
|
662
|
+
<hr>
|
|
663
|
+
Model as number: <input type="number" ng-model="value"><br>
|
|
664
|
+
Min: <input type="number" ng-model="min"><br>
|
|
665
|
+
Max: <input type="number" ng-model="max"><br>
|
|
666
|
+
value = <code>{{value}}</code><br/>
|
|
667
|
+
myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
|
|
668
|
+
myForm.range.$error = <code>{{myForm.range.$error}}</code>
|
|
669
|
+
</form>
|
|
670
|
+
</file>
|
|
671
|
+
</example>
|
|
672
|
+
|
|
673
|
+
*/
|
|
674
|
+
range: rangeInputType,
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* @ngdoc input
|
|
678
|
+
* @name input[checkbox]
|
|
679
|
+
*
|
|
680
|
+
* @description
|
|
681
|
+
* HTML checkbox.
|
|
682
|
+
*
|
|
683
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
684
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
685
|
+
* @param {expression=} ngTrueValue The value to which the expression should be set when selected.
|
|
686
|
+
* @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
|
|
687
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
688
|
+
* interaction with the input element.
|
|
689
|
+
*
|
|
690
|
+
*/
|
|
691
|
+
checkbox: checkboxInputType,
|
|
692
|
+
|
|
693
|
+
hidden: () => {},
|
|
694
|
+
button: () => {},
|
|
695
|
+
submit: () => {},
|
|
696
|
+
reset: () => {},
|
|
697
|
+
file: () => {},
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
function stringBasedInputType(ctrl) {
|
|
701
|
+
ctrl.$formatters.push((value) =>
|
|
702
|
+
ctrl.$isEmpty(value) ? value : value.toString(),
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function textInputType(scope, element, attr, ctrl, $browser) {
|
|
707
|
+
baseInputType(scope, element, attr, ctrl, $browser);
|
|
708
|
+
stringBasedInputType(ctrl);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function baseInputType(scope, element, attr, ctrl, $browser) {
|
|
712
|
+
const type = lowercase(element[0].type);
|
|
713
|
+
let composing = false;
|
|
714
|
+
// In composition mode, users are still inputting intermediate text buffer,
|
|
715
|
+
// hold the listener until composition is done.
|
|
716
|
+
// More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
|
|
717
|
+
element.on("compositionstart", () => {
|
|
718
|
+
composing = true;
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
element.on("compositionend", () => {
|
|
722
|
+
composing = false;
|
|
723
|
+
listener();
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
let timeout;
|
|
727
|
+
|
|
728
|
+
let listener = function (ev) {
|
|
729
|
+
if (timeout) {
|
|
730
|
+
$browser.defer.cancel(timeout);
|
|
731
|
+
timeout = null;
|
|
732
|
+
}
|
|
733
|
+
if (composing) return;
|
|
734
|
+
let value = element.val();
|
|
735
|
+
const event = ev && ev.type;
|
|
736
|
+
|
|
737
|
+
// By default we will trim the value
|
|
738
|
+
// If the attribute ng-trim exists we will avoid trimming
|
|
739
|
+
// If input type is 'password', the value is never trimmed
|
|
740
|
+
if (type !== "password" && (!attr.ngTrim || attr.ngTrim !== "false")) {
|
|
741
|
+
value = trim(value);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// If a control is suffering from bad input (due to native validators), browsers discard its
|
|
745
|
+
// value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
|
|
746
|
+
// control's value is the same empty value twice in a row.
|
|
747
|
+
if (
|
|
748
|
+
ctrl.$viewValue !== value ||
|
|
749
|
+
(value === "" && ctrl.$$hasNativeValidators)
|
|
750
|
+
) {
|
|
751
|
+
ctrl.$setViewValue(value, event);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
["input", "change", "paste", "drop", "cut"].forEach((event) => {
|
|
756
|
+
element.on(event, listener);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// Some native input types (date-family) have the ability to change validity without
|
|
760
|
+
// firing any input/change events.
|
|
761
|
+
// For these event types, when native validators are present and the browser supports the type,
|
|
762
|
+
// check for validity changes on various DOM events.
|
|
763
|
+
if (
|
|
764
|
+
PARTIAL_VALIDATION_TYPES[type] &&
|
|
765
|
+
ctrl.$$hasNativeValidators &&
|
|
766
|
+
type === attr.type
|
|
767
|
+
) {
|
|
768
|
+
element.on(PARTIAL_VALIDATION_EVENTS, function (ev) {
|
|
769
|
+
if (!timeout) {
|
|
770
|
+
const validity = this[VALIDITY_STATE_PROPERTY];
|
|
771
|
+
const origBadInput = validity.badInput;
|
|
772
|
+
const origTypeMismatch = validity.typeMismatch;
|
|
773
|
+
timeout = $browser.defer(() => {
|
|
774
|
+
timeout = null;
|
|
775
|
+
if (
|
|
776
|
+
validity.badInput !== origBadInput ||
|
|
777
|
+
validity.typeMismatch !== origTypeMismatch
|
|
778
|
+
) {
|
|
779
|
+
listener(ev);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
ctrl.$render = function () {
|
|
787
|
+
// Workaround for Firefox validation #12102.
|
|
788
|
+
const value = ctrl.$isEmpty(ctrl.$viewValue) ? "" : ctrl.$viewValue;
|
|
789
|
+
if (element.val() !== value) {
|
|
790
|
+
element.val(value);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
export function weekParser(isoWeek, existingDate) {
|
|
796
|
+
if (isDate(isoWeek)) {
|
|
797
|
+
return isoWeek;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function getFirstThursdayOfYear(year) {
|
|
801
|
+
// 0 = index of January
|
|
802
|
+
var dayOfWeekOnFirst = new Date(year, 0, 1).getDay();
|
|
803
|
+
// 4 = index of Thursday (+1 to account for 1st = 5)
|
|
804
|
+
// 11 = index of *next* Thursday (+1 account for 1st = 12)
|
|
805
|
+
return new Date(
|
|
806
|
+
year,
|
|
807
|
+
0,
|
|
808
|
+
(dayOfWeekOnFirst <= 4 ? 5 : 12) - dayOfWeekOnFirst,
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (isString(isoWeek)) {
|
|
813
|
+
WEEK_REGEXP.lastIndex = 0;
|
|
814
|
+
const parts = WEEK_REGEXP.exec(isoWeek);
|
|
815
|
+
if (parts) {
|
|
816
|
+
const year = +parts[1];
|
|
817
|
+
const week = +parts[2];
|
|
818
|
+
let hours = 0;
|
|
819
|
+
let minutes = 0;
|
|
820
|
+
let seconds = 0;
|
|
821
|
+
let milliseconds = 0;
|
|
822
|
+
const firstThurs = getFirstThursdayOfYear(year);
|
|
823
|
+
const addDays = (week - 1) * 7;
|
|
824
|
+
|
|
825
|
+
if (existingDate) {
|
|
826
|
+
hours = existingDate.getHours();
|
|
827
|
+
minutes = existingDate.getMinutes();
|
|
828
|
+
seconds = existingDate.getSeconds();
|
|
829
|
+
milliseconds = existingDate.getMilliseconds();
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return new Date(
|
|
833
|
+
year,
|
|
834
|
+
0,
|
|
835
|
+
firstThurs.getDate() + addDays,
|
|
836
|
+
hours,
|
|
837
|
+
minutes,
|
|
838
|
+
seconds,
|
|
839
|
+
milliseconds,
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return NaN;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export function createDateParser(regexp, mapping) {
|
|
848
|
+
return function (iso, previousDate) {
|
|
849
|
+
let parts;
|
|
850
|
+
let map;
|
|
851
|
+
|
|
852
|
+
if (isDate(iso)) {
|
|
853
|
+
return iso;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (isString(iso)) {
|
|
857
|
+
// When a date is JSON'ified to wraps itself inside of an extra
|
|
858
|
+
// set of double quotes. This makes the date parsing code unable
|
|
859
|
+
// to match the date string and parse it as a date.
|
|
860
|
+
if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') {
|
|
861
|
+
iso = iso.substring(1, iso.length - 1);
|
|
862
|
+
}
|
|
863
|
+
if (ISO_DATE_REGEXP.test(iso)) {
|
|
864
|
+
return new Date(iso);
|
|
865
|
+
}
|
|
866
|
+
regexp.lastIndex = 0;
|
|
867
|
+
parts = regexp.exec(iso);
|
|
868
|
+
|
|
869
|
+
if (parts) {
|
|
870
|
+
parts.shift();
|
|
871
|
+
if (previousDate) {
|
|
872
|
+
map = {
|
|
873
|
+
yyyy: previousDate.getFullYear(),
|
|
874
|
+
MM: previousDate.getMonth() + 1,
|
|
875
|
+
dd: previousDate.getDate(),
|
|
876
|
+
HH: previousDate.getHours(),
|
|
877
|
+
mm: previousDate.getMinutes(),
|
|
878
|
+
ss: previousDate.getSeconds(),
|
|
879
|
+
sss: previousDate.getMilliseconds() / 1000,
|
|
880
|
+
};
|
|
881
|
+
} else {
|
|
882
|
+
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
forEach(parts, (part, index) => {
|
|
886
|
+
if (index < mapping.length) {
|
|
887
|
+
map[mapping[index]] = +part;
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
const date = new Date(
|
|
892
|
+
map.yyyy,
|
|
893
|
+
map.MM - 1,
|
|
894
|
+
map.dd,
|
|
895
|
+
map.HH,
|
|
896
|
+
map.mm,
|
|
897
|
+
map.ss || 0,
|
|
898
|
+
map.sss * 1000 || 0,
|
|
899
|
+
);
|
|
900
|
+
if (map.yyyy < 100) {
|
|
901
|
+
// In the constructor, 2-digit years map to 1900-1999.
|
|
902
|
+
// Use `setFullYear()` to set the correct year.
|
|
903
|
+
date.setFullYear(map.yyyy);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return date;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return NaN;
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const MONTH_INPUT_FORMAT = /\b\d{4}-(0[1-9]|1[0-2])\b/;
|
|
915
|
+
|
|
916
|
+
export function createDateInputType(type, regexp, parseDate) {
|
|
917
|
+
return function dynamicDateInputType(
|
|
918
|
+
scope,
|
|
919
|
+
element,
|
|
920
|
+
attr,
|
|
921
|
+
ctrl,
|
|
922
|
+
$browser,
|
|
923
|
+
$filter,
|
|
924
|
+
$parse,
|
|
925
|
+
) {
|
|
926
|
+
badInputChecker(scope, element, attr, ctrl, type);
|
|
927
|
+
baseInputType(scope, element, attr, ctrl, $browser);
|
|
928
|
+
let previousDate;
|
|
929
|
+
let previousTimezone;
|
|
930
|
+
|
|
931
|
+
ctrl.$parsers.push((value) => {
|
|
932
|
+
if (ctrl.$isEmpty(value)) return null;
|
|
933
|
+
|
|
934
|
+
if (regexp.test(value)) {
|
|
935
|
+
// Do not convert for native HTML
|
|
936
|
+
if (["month", "week", "datetimelocal", "time", "date"].includes(type)) {
|
|
937
|
+
return value;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Note: We cannot read ctrl.$modelValue, as there might be a different
|
|
941
|
+
// parser/formatter in the processing chain so that the model
|
|
942
|
+
// contains some different data format!
|
|
943
|
+
return parseDateAndConvertTimeZoneToLocal(value, previousDate);
|
|
944
|
+
}
|
|
945
|
+
ctrl.$$parserName = type;
|
|
946
|
+
return undefined;
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
ctrl.$formatters.push(function (value) {
|
|
950
|
+
if (value && !isString(value)) {
|
|
951
|
+
throw ngModelMinErr("datefmt", "Expected `{0}` to be a String", value);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (type === "month") {
|
|
955
|
+
if (value == null) {
|
|
956
|
+
return "";
|
|
957
|
+
}
|
|
958
|
+
if (!MONTH_INPUT_FORMAT.test(value)) {
|
|
959
|
+
throw ngModelMinErr(
|
|
960
|
+
"datefmt",
|
|
961
|
+
"Expected month `{0}` to be a 'YYYY-DD'",
|
|
962
|
+
value,
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (type === "week") {
|
|
968
|
+
if (value == null) {
|
|
969
|
+
return "";
|
|
970
|
+
}
|
|
971
|
+
if (!WEEK_REGEXP.test(value)) {
|
|
972
|
+
throw ngModelMinErr(
|
|
973
|
+
"datefmt",
|
|
974
|
+
"Expected week `{0}` to be a 'yyyy-Www'",
|
|
975
|
+
value,
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (type === "datetimelocal") {
|
|
981
|
+
if (value == null) {
|
|
982
|
+
return "";
|
|
983
|
+
}
|
|
984
|
+
if (!DATETIMELOCAL_REGEXP.test(value)) {
|
|
985
|
+
throw ngModelMinErr(
|
|
986
|
+
"datefmt",
|
|
987
|
+
"Expected week `{0}` to be a in date time format. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#local_date_and_time_strings",
|
|
988
|
+
value,
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return value;
|
|
994
|
+
|
|
995
|
+
// if (isValidDate(value)) {
|
|
996
|
+
// previousDate = value;
|
|
997
|
+
// const timezone = ctrl.$options.getOption("timezone");
|
|
998
|
+
|
|
999
|
+
// if (timezone) {
|
|
1000
|
+
// previousTimezone = timezone;
|
|
1001
|
+
// previousDate = convertTimezoneToLocal(previousDate, timezone, true);
|
|
1002
|
+
// }
|
|
1003
|
+
|
|
1004
|
+
// return value;
|
|
1005
|
+
// }
|
|
1006
|
+
// previousDate = null;
|
|
1007
|
+
// previousTimezone = null;
|
|
1008
|
+
// return "";
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
if (isDefined(attr.min) || attr.ngMin) {
|
|
1012
|
+
let minVal = attr.min || $parse(attr.ngMin)(scope);
|
|
1013
|
+
let parsedMinVal = parseObservedDateValue(minVal);
|
|
1014
|
+
|
|
1015
|
+
ctrl.$validators.min = function (value) {
|
|
1016
|
+
if (type === "month") {
|
|
1017
|
+
return (
|
|
1018
|
+
isUndefined(parsedMinVal) ||
|
|
1019
|
+
parseDate(value) >= parseDate(parsedMinVal)
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
return (
|
|
1024
|
+
!isValidDate(value) ||
|
|
1025
|
+
isUndefined(parsedMinVal) ||
|
|
1026
|
+
parseDate(value) >= parsedMinVal
|
|
1027
|
+
);
|
|
1028
|
+
};
|
|
1029
|
+
attr.$observe("min", (val) => {
|
|
1030
|
+
if (val !== minVal) {
|
|
1031
|
+
parsedMinVal = parseObservedDateValue(val);
|
|
1032
|
+
minVal = val;
|
|
1033
|
+
ctrl.$validate();
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (isDefined(attr.max) || attr.ngMax) {
|
|
1039
|
+
let maxVal = attr.max || $parse(attr.ngMax)(scope);
|
|
1040
|
+
let parsedMaxVal = parseObservedDateValue(maxVal);
|
|
1041
|
+
|
|
1042
|
+
ctrl.$validators.max = function (value) {
|
|
1043
|
+
if (type === "month") {
|
|
1044
|
+
return (
|
|
1045
|
+
isUndefined(parsedMaxVal) ||
|
|
1046
|
+
parseDate(value) <= parseDate(parsedMaxVal)
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
return (
|
|
1050
|
+
!isValidDate(value) ||
|
|
1051
|
+
isUndefined(parsedMaxVal) ||
|
|
1052
|
+
parseDate(value) <= parsedMaxVal
|
|
1053
|
+
);
|
|
1054
|
+
};
|
|
1055
|
+
attr.$observe("max", (val) => {
|
|
1056
|
+
if (val !== maxVal) {
|
|
1057
|
+
parsedMaxVal = parseObservedDateValue(val);
|
|
1058
|
+
maxVal = val;
|
|
1059
|
+
ctrl.$validate();
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function isValidDate(value) {
|
|
1065
|
+
// Invalid Date: getTime() returns NaN
|
|
1066
|
+
return value && !(value.getTime && value.getTime() !== value.getTime());
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function parseObservedDateValue(val) {
|
|
1070
|
+
return isDefined(val) && !isDate(val)
|
|
1071
|
+
? parseDateAndConvertTimeZoneToLocal(val) || undefined
|
|
1072
|
+
: val;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function parseDateAndConvertTimeZoneToLocal(value, previousDate) {
|
|
1076
|
+
const timezone = ctrl.$options.getOption("timezone");
|
|
1077
|
+
|
|
1078
|
+
if (previousTimezone && previousTimezone !== timezone) {
|
|
1079
|
+
// If the timezone has changed, adjust the previousDate to the default timezone
|
|
1080
|
+
// so that the new date is converted with the correct timezone offset
|
|
1081
|
+
previousDate = addDateMinutes(
|
|
1082
|
+
previousDate,
|
|
1083
|
+
timezoneToOffset(previousTimezone),
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
let parsedDate = parseDate(value, previousDate);
|
|
1088
|
+
|
|
1089
|
+
if (!Number.isNaN(parsedDate) && timezone) {
|
|
1090
|
+
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
|
|
1091
|
+
}
|
|
1092
|
+
return parsedDate;
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export function badInputChecker(scope, element, attr, ctrl, parserName) {
|
|
1098
|
+
const node = element[0];
|
|
1099
|
+
const nativeValidation = (ctrl.$$hasNativeValidators = isObject(
|
|
1100
|
+
node.validity,
|
|
1101
|
+
));
|
|
1102
|
+
|
|
1103
|
+
if (nativeValidation) {
|
|
1104
|
+
ctrl.$parsers.push((value) => {
|
|
1105
|
+
const validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
|
|
1106
|
+
if (validity.badInput || validity.typeMismatch) {
|
|
1107
|
+
ctrl.$$parserName = parserName;
|
|
1108
|
+
return undefined;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
return value;
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
export function numberFormatterParser(ctrl) {
|
|
1117
|
+
ctrl.$parsers.push((value) => {
|
|
1118
|
+
if (ctrl.$isEmpty(value)) return null;
|
|
1119
|
+
if (NUMBER_REGEXP.test(value)) return parseFloat(value);
|
|
1120
|
+
|
|
1121
|
+
ctrl.$$parserName = "number";
|
|
1122
|
+
return undefined;
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
ctrl.$formatters.push((value) => {
|
|
1126
|
+
if (!ctrl.$isEmpty(value)) {
|
|
1127
|
+
if (!isNumber(value)) {
|
|
1128
|
+
throw ngModelMinErr("numfmt", "Expected `{0}` to be a number", value);
|
|
1129
|
+
}
|
|
1130
|
+
value = value.toString();
|
|
1131
|
+
}
|
|
1132
|
+
return value;
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function parseNumberAttrVal(val) {
|
|
1137
|
+
if (isDefined(val) && !isNumber(val)) {
|
|
1138
|
+
val = parseFloat(val);
|
|
1139
|
+
}
|
|
1140
|
+
return !isNumberNaN(val) ? val : undefined;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
export function isNumberInteger(num) {
|
|
1144
|
+
// See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066
|
|
1145
|
+
// (minus the assumption that `num` is a number)
|
|
1146
|
+
|
|
1147
|
+
// eslint-disable-next-line no-bitwise
|
|
1148
|
+
return (num | 0) === num;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export function countDecimals(num) {
|
|
1152
|
+
const numString = num.toString();
|
|
1153
|
+
const decimalSymbolIndex = numString.indexOf(".");
|
|
1154
|
+
|
|
1155
|
+
if (decimalSymbolIndex === -1) {
|
|
1156
|
+
if (num > -1 && num < 1) {
|
|
1157
|
+
// It may be in the exponential notation format (`1e-X`)
|
|
1158
|
+
const match = /e-(\d+)$/.exec(numString);
|
|
1159
|
+
|
|
1160
|
+
if (match) {
|
|
1161
|
+
return Number(match[1]);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
return 0;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
return numString.length - decimalSymbolIndex - 1;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
export function isValidForStep(viewValue, stepBase, step) {
|
|
1172
|
+
// At this point `stepBase` and `step` are expected to be non-NaN values
|
|
1173
|
+
// and `viewValue` is expected to be a valid stringified number.
|
|
1174
|
+
let value = Number(viewValue);
|
|
1175
|
+
|
|
1176
|
+
const isNonIntegerValue = !isNumberInteger(value);
|
|
1177
|
+
const isNonIntegerStepBase = !isNumberInteger(stepBase);
|
|
1178
|
+
const isNonIntegerStep = !isNumberInteger(step);
|
|
1179
|
+
|
|
1180
|
+
// Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
|
|
1181
|
+
// `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
|
|
1182
|
+
if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
|
|
1183
|
+
const valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
|
|
1184
|
+
const stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
|
|
1185
|
+
const stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;
|
|
1186
|
+
|
|
1187
|
+
const decimalCount = Math.max(
|
|
1188
|
+
valueDecimals,
|
|
1189
|
+
stepBaseDecimals,
|
|
1190
|
+
stepDecimals,
|
|
1191
|
+
);
|
|
1192
|
+
const multiplier = 10 ** decimalCount;
|
|
1193
|
+
|
|
1194
|
+
value *= multiplier;
|
|
1195
|
+
stepBase *= multiplier;
|
|
1196
|
+
step *= multiplier;
|
|
1197
|
+
|
|
1198
|
+
if (isNonIntegerValue) value = Math.round(value);
|
|
1199
|
+
if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
|
|
1200
|
+
if (isNonIntegerStep) step = Math.round(step);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return (value - stepBase) % step === 0;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
export function numberInputType(
|
|
1207
|
+
scope,
|
|
1208
|
+
element,
|
|
1209
|
+
attr,
|
|
1210
|
+
ctrl,
|
|
1211
|
+
$browser,
|
|
1212
|
+
$filter,
|
|
1213
|
+
$parse,
|
|
1214
|
+
) {
|
|
1215
|
+
badInputChecker(scope, element, attr, ctrl, "number");
|
|
1216
|
+
numberFormatterParser(ctrl);
|
|
1217
|
+
baseInputType(scope, element, attr, ctrl, $browser);
|
|
1218
|
+
|
|
1219
|
+
let parsedMinVal;
|
|
1220
|
+
|
|
1221
|
+
if (isDefined(attr.min) || attr.ngMin) {
|
|
1222
|
+
let minVal = attr.min || $parse(attr.ngMin)(scope);
|
|
1223
|
+
parsedMinVal = parseNumberAttrVal(minVal);
|
|
1224
|
+
|
|
1225
|
+
ctrl.$validators.min = function (modelValue, viewValue) {
|
|
1226
|
+
return (
|
|
1227
|
+
ctrl.$isEmpty(viewValue) ||
|
|
1228
|
+
isUndefined(parsedMinVal) ||
|
|
1229
|
+
viewValue >= parsedMinVal
|
|
1230
|
+
);
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
attr.$observe("min", (val) => {
|
|
1234
|
+
if (val !== minVal) {
|
|
1235
|
+
parsedMinVal = parseNumberAttrVal(val);
|
|
1236
|
+
minVal = val;
|
|
1237
|
+
// TODO(matsko): implement validateLater to reduce number of validations
|
|
1238
|
+
ctrl.$validate();
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (isDefined(attr.max) || attr.ngMax) {
|
|
1244
|
+
let maxVal = attr.max || $parse(attr.ngMax)(scope);
|
|
1245
|
+
let parsedMaxVal = parseNumberAttrVal(maxVal);
|
|
1246
|
+
|
|
1247
|
+
ctrl.$validators.max = function (modelValue, viewValue) {
|
|
1248
|
+
return (
|
|
1249
|
+
ctrl.$isEmpty(viewValue) ||
|
|
1250
|
+
isUndefined(parsedMaxVal) ||
|
|
1251
|
+
viewValue <= parsedMaxVal
|
|
1252
|
+
);
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
attr.$observe("max", (val) => {
|
|
1256
|
+
if (val !== maxVal) {
|
|
1257
|
+
parsedMaxVal = parseNumberAttrVal(val);
|
|
1258
|
+
maxVal = val;
|
|
1259
|
+
// TODO(matsko): implement validateLater to reduce number of validations
|
|
1260
|
+
ctrl.$validate();
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (isDefined(attr.step) || attr.ngStep) {
|
|
1266
|
+
let stepVal = attr.step || $parse(attr.ngStep)(scope);
|
|
1267
|
+
let parsedStepVal = parseNumberAttrVal(stepVal);
|
|
1268
|
+
|
|
1269
|
+
ctrl.$validators.step = function (modelValue, viewValue) {
|
|
1270
|
+
return (
|
|
1271
|
+
ctrl.$isEmpty(viewValue) ||
|
|
1272
|
+
isUndefined(parsedStepVal) ||
|
|
1273
|
+
isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal)
|
|
1274
|
+
);
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
attr.$observe("step", (val) => {
|
|
1278
|
+
// TODO(matsko): implement validateLater to reduce number of validations
|
|
1279
|
+
if (val !== stepVal) {
|
|
1280
|
+
parsedStepVal = parseNumberAttrVal(val);
|
|
1281
|
+
stepVal = val;
|
|
1282
|
+
ctrl.$validate();
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
export function rangeInputType(scope, element, attr, ctrl, $browser) {
|
|
1289
|
+
badInputChecker(scope, element, attr, ctrl, "range");
|
|
1290
|
+
numberFormatterParser(ctrl);
|
|
1291
|
+
baseInputType(scope, element, attr, ctrl, $browser);
|
|
1292
|
+
|
|
1293
|
+
const supportsRange =
|
|
1294
|
+
ctrl.$$hasNativeValidators && element[0].type === "range";
|
|
1295
|
+
let minVal = supportsRange ? 0 : undefined;
|
|
1296
|
+
let maxVal = supportsRange ? 100 : undefined;
|
|
1297
|
+
let stepVal = supportsRange ? 1 : undefined;
|
|
1298
|
+
const { validity } = element[0];
|
|
1299
|
+
const hasMinAttr = isDefined(attr.min);
|
|
1300
|
+
const hasMaxAttr = isDefined(attr.max);
|
|
1301
|
+
const hasStepAttr = isDefined(attr.step);
|
|
1302
|
+
|
|
1303
|
+
const originalRender = ctrl.$render;
|
|
1304
|
+
|
|
1305
|
+
ctrl.$render =
|
|
1306
|
+
supportsRange &&
|
|
1307
|
+
isDefined(validity.rangeUnderflow) &&
|
|
1308
|
+
isDefined(validity.rangeOverflow)
|
|
1309
|
+
? // Browsers that implement range will set these values automatically, but reading the adjusted values after
|
|
1310
|
+
// $render would cause the min / max validators to be applied with the wrong value
|
|
1311
|
+
function rangeRender() {
|
|
1312
|
+
originalRender();
|
|
1313
|
+
ctrl.$setViewValue(element.val());
|
|
1314
|
+
}
|
|
1315
|
+
: originalRender;
|
|
1316
|
+
|
|
1317
|
+
if (hasMinAttr) {
|
|
1318
|
+
minVal = parseNumberAttrVal(attr.min);
|
|
1319
|
+
|
|
1320
|
+
ctrl.$validators.min = supportsRange
|
|
1321
|
+
? // Since all browsers set the input to a valid value, we don't need to check validity
|
|
1322
|
+
function noopMinValidator() {
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
: // non-support browsers validate the min val
|
|
1326
|
+
function minValidator(modelValue, viewValue) {
|
|
1327
|
+
return (
|
|
1328
|
+
ctrl.$isEmpty(viewValue) ||
|
|
1329
|
+
isUndefined(minVal) ||
|
|
1330
|
+
viewValue >= minVal
|
|
1331
|
+
);
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
setInitialValueAndObserver("min", minChange);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (hasMaxAttr) {
|
|
1338
|
+
maxVal = parseNumberAttrVal(attr.max);
|
|
1339
|
+
|
|
1340
|
+
ctrl.$validators.max = supportsRange
|
|
1341
|
+
? // Since all browsers set the input to a valid value, we don't need to check validity
|
|
1342
|
+
function noopMaxValidator() {
|
|
1343
|
+
return true;
|
|
1344
|
+
}
|
|
1345
|
+
: // non-support browsers validate the max val
|
|
1346
|
+
function maxValidator(modelValue, viewValue) {
|
|
1347
|
+
return (
|
|
1348
|
+
ctrl.$isEmpty(viewValue) ||
|
|
1349
|
+
isUndefined(maxVal) ||
|
|
1350
|
+
viewValue <= maxVal
|
|
1351
|
+
);
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
setInitialValueAndObserver("max", maxChange);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
if (hasStepAttr) {
|
|
1358
|
+
stepVal = parseNumberAttrVal(attr.step);
|
|
1359
|
+
|
|
1360
|
+
ctrl.$validators.step = supportsRange
|
|
1361
|
+
? function nativeStepValidator() {
|
|
1362
|
+
// Currently, only FF implements the spec on step change correctly (i.e. adjusting the
|
|
1363
|
+
// input element value to a valid value). It's possible that other browsers set the stepMismatch
|
|
1364
|
+
// validity error instead, so we can at least report an error in that case.
|
|
1365
|
+
return !validity.stepMismatch;
|
|
1366
|
+
}
|
|
1367
|
+
: // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
|
|
1368
|
+
function stepValidator(modelValue, viewValue) {
|
|
1369
|
+
return (
|
|
1370
|
+
ctrl.$isEmpty(viewValue) ||
|
|
1371
|
+
isUndefined(stepVal) ||
|
|
1372
|
+
isValidForStep(viewValue, minVal || 0, stepVal)
|
|
1373
|
+
);
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
setInitialValueAndObserver("step", stepChange);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function setInitialValueAndObserver(htmlAttrName, changeFn) {
|
|
1380
|
+
// interpolated attributes set the attribute value only after a digest, but we need the
|
|
1381
|
+
// attribute value when the input is first rendered, so that the browser can adjust the
|
|
1382
|
+
// input value based on the min/max value
|
|
1383
|
+
element.attr(htmlAttrName, attr[htmlAttrName]);
|
|
1384
|
+
let oldVal = attr[htmlAttrName];
|
|
1385
|
+
attr.$observe(htmlAttrName, (val) => {
|
|
1386
|
+
if (val !== oldVal) {
|
|
1387
|
+
oldVal = val;
|
|
1388
|
+
changeFn(val);
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function minChange(val) {
|
|
1394
|
+
minVal = parseNumberAttrVal(val);
|
|
1395
|
+
// ignore changes before model is initialized
|
|
1396
|
+
if (isNumberNaN(ctrl.$modelValue)) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (supportsRange) {
|
|
1401
|
+
let elVal = element.val();
|
|
1402
|
+
// IE11 doesn't set the el val correctly if the minVal is greater than the element value
|
|
1403
|
+
if (minVal > elVal) {
|
|
1404
|
+
elVal = minVal;
|
|
1405
|
+
element.val(elVal);
|
|
1406
|
+
}
|
|
1407
|
+
ctrl.$setViewValue(elVal);
|
|
1408
|
+
} else {
|
|
1409
|
+
// TODO(matsko): implement validateLater to reduce number of validations
|
|
1410
|
+
ctrl.$validate();
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function maxChange(val) {
|
|
1415
|
+
maxVal = parseNumberAttrVal(val);
|
|
1416
|
+
// ignore changes before model is initialized
|
|
1417
|
+
if (isNumberNaN(ctrl.$modelValue)) {
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (supportsRange) {
|
|
1422
|
+
let elVal = element.val();
|
|
1423
|
+
// IE11 doesn't set the el val correctly if the maxVal is less than the element value
|
|
1424
|
+
if (maxVal < elVal) {
|
|
1425
|
+
element.val(maxVal);
|
|
1426
|
+
// IE11 and Chrome don't set the value to the minVal when max < min
|
|
1427
|
+
elVal = maxVal < minVal ? minVal : maxVal;
|
|
1428
|
+
}
|
|
1429
|
+
ctrl.$setViewValue(elVal);
|
|
1430
|
+
} else {
|
|
1431
|
+
// TODO(matsko): implement validateLater to reduce number of validations
|
|
1432
|
+
ctrl.$validate();
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function stepChange(val) {
|
|
1437
|
+
stepVal = parseNumberAttrVal(val);
|
|
1438
|
+
// ignore changes before model is initialized
|
|
1439
|
+
if (isNumberNaN(ctrl.$modelValue)) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Some browsers don't adjust the input value correctly, but set the stepMismatch error
|
|
1444
|
+
if (!supportsRange) {
|
|
1445
|
+
// TODO(matsko): implement validateLater to reduce number of validations
|
|
1446
|
+
ctrl.$validate();
|
|
1447
|
+
} else if (ctrl.$viewValue !== element.val()) {
|
|
1448
|
+
ctrl.$setViewValue(element.val());
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function urlInputType(scope, element, attr, ctrl, $browser) {
|
|
1454
|
+
// Note: no badInputChecker here by purpose as `url` is only a validation
|
|
1455
|
+
// in browsers, i.e. we can always read out input.value even if it is not valid!
|
|
1456
|
+
baseInputType(scope, element, attr, ctrl, $browser);
|
|
1457
|
+
stringBasedInputType(ctrl);
|
|
1458
|
+
|
|
1459
|
+
ctrl.$validators.url = function (modelValue, viewValue) {
|
|
1460
|
+
const value = modelValue || viewValue;
|
|
1461
|
+
return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
function emailInputType(scope, element, attr, ctrl, $browser) {
|
|
1466
|
+
// Note: no badInputChecker here by purpose as `url` is only a validation
|
|
1467
|
+
// in browsers, i.e. we can always read out input.value even if it is not valid!
|
|
1468
|
+
baseInputType(scope, element, attr, ctrl, $browser);
|
|
1469
|
+
stringBasedInputType(ctrl);
|
|
1470
|
+
|
|
1471
|
+
ctrl.$validators.email = function (modelValue, viewValue) {
|
|
1472
|
+
const value = modelValue || viewValue;
|
|
1473
|
+
return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function radioInputType(scope, element, attr, ctrl) {
|
|
1478
|
+
const doTrim = !attr.ngTrim || trim(attr.ngTrim) !== "false";
|
|
1479
|
+
// make the name unique, if not defined
|
|
1480
|
+
if (isUndefined(attr.name)) {
|
|
1481
|
+
element.attr("name", nextUid());
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const listener = function (ev) {
|
|
1485
|
+
let value;
|
|
1486
|
+
if (element[0].checked) {
|
|
1487
|
+
value = attr.value;
|
|
1488
|
+
if (doTrim) {
|
|
1489
|
+
value = trim(value);
|
|
1490
|
+
}
|
|
1491
|
+
ctrl.$setViewValue(value, ev && ev.type);
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
element.on("change", listener);
|
|
1496
|
+
|
|
1497
|
+
ctrl.$render = function () {
|
|
1498
|
+
let { value } = attr;
|
|
1499
|
+
if (doTrim) {
|
|
1500
|
+
value = trim(value);
|
|
1501
|
+
}
|
|
1502
|
+
element[0].checked = value === ctrl.$viewValue;
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
attr.$observe("value", ctrl.$render);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function parseConstantExpr($parse, context, name, expression, fallback) {
|
|
1509
|
+
let parseFn;
|
|
1510
|
+
if (isDefined(expression)) {
|
|
1511
|
+
parseFn = $parse(expression);
|
|
1512
|
+
if (!parseFn.constant) {
|
|
1513
|
+
throw ngModelMinErr(
|
|
1514
|
+
"constexpr",
|
|
1515
|
+
"Expected constant expression for `{0}`, but saw " + "`{1}`.",
|
|
1516
|
+
name,
|
|
1517
|
+
expression,
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
return parseFn(context);
|
|
1521
|
+
}
|
|
1522
|
+
return fallback;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
function checkboxInputType(
|
|
1526
|
+
scope,
|
|
1527
|
+
element,
|
|
1528
|
+
attr,
|
|
1529
|
+
ctrl,
|
|
1530
|
+
$browser,
|
|
1531
|
+
$filter,
|
|
1532
|
+
$parse,
|
|
1533
|
+
) {
|
|
1534
|
+
const trueValue = parseConstantExpr(
|
|
1535
|
+
$parse,
|
|
1536
|
+
scope,
|
|
1537
|
+
"ngTrueValue",
|
|
1538
|
+
attr.ngTrueValue,
|
|
1539
|
+
true,
|
|
1540
|
+
);
|
|
1541
|
+
const falseValue = parseConstantExpr(
|
|
1542
|
+
$parse,
|
|
1543
|
+
scope,
|
|
1544
|
+
"ngFalseValue",
|
|
1545
|
+
attr.ngFalseValue,
|
|
1546
|
+
false,
|
|
1547
|
+
);
|
|
1548
|
+
|
|
1549
|
+
const listener = function (ev) {
|
|
1550
|
+
ctrl.$setViewValue(element[0].checked, ev && ev.type);
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
element.on("change", listener);
|
|
1554
|
+
|
|
1555
|
+
ctrl.$render = function () {
|
|
1556
|
+
element[0].checked = ctrl.$viewValue;
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
// Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
|
|
1560
|
+
// This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
|
|
1561
|
+
// it to a boolean.
|
|
1562
|
+
ctrl.$isEmpty = function (value) {
|
|
1563
|
+
return value === false;
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
ctrl.$formatters.push((value) => equals(value, trueValue));
|
|
1567
|
+
|
|
1568
|
+
ctrl.$parsers.push((value) => (value ? trueValue : falseValue));
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* @ngdoc directive
|
|
1573
|
+
* @name textarea
|
|
1574
|
+
* @restrict E
|
|
1575
|
+
*
|
|
1576
|
+
* @description
|
|
1577
|
+
* HTML textarea element control with AngularJS data-binding. The data-binding and validation
|
|
1578
|
+
* properties of this element are exactly the same as those of the
|
|
1579
|
+
* {@link ng.directive:input input element}.
|
|
1580
|
+
*
|
|
1581
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
1582
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
1583
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
1584
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
1585
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
1586
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
1587
|
+
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
|
1588
|
+
* minlength.
|
|
1589
|
+
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
|
1590
|
+
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
|
1591
|
+
* length.
|
|
1592
|
+
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
|
1593
|
+
* does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
|
|
1594
|
+
* If the expression evaluates to a RegExp object, then this is used directly.
|
|
1595
|
+
* If the expression evaluates to a string, then it will be converted to a RegExp
|
|
1596
|
+
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
|
1597
|
+
* `new RegExp('^abc$')`.<br />
|
|
1598
|
+
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
|
|
1599
|
+
* start at the index of the last search's match, thus not taking the whole input value into
|
|
1600
|
+
* account.
|
|
1601
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
1602
|
+
* interaction with the input element.
|
|
1603
|
+
* @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
|
|
1604
|
+
*
|
|
1605
|
+
* @knownIssue
|
|
1606
|
+
*
|
|
1607
|
+
* When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily
|
|
1608
|
+
* insert the placeholder value as the textarea's content. If the placeholder value contains
|
|
1609
|
+
* interpolation (`{{ ... }}`), an error will be logged in the console when AngularJS tries to update
|
|
1610
|
+
* the value of the by-then-removed text node. This doesn't affect the functionality of the
|
|
1611
|
+
* textarea, but can be undesirable.
|
|
1612
|
+
*
|
|
1613
|
+
* You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of
|
|
1614
|
+
* `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can
|
|
1615
|
+
* find more details on `ngAttr` in the
|
|
1616
|
+
* [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the
|
|
1617
|
+
* Developer Guide.
|
|
1618
|
+
*/
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* @ngdoc directive
|
|
1622
|
+
* @name input
|
|
1623
|
+
* @restrict E
|
|
1624
|
+
*
|
|
1625
|
+
* @description
|
|
1626
|
+
* HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
|
|
1627
|
+
* input state control, and validation.
|
|
1628
|
+
* Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
|
|
1629
|
+
*
|
|
1630
|
+
* <div class="alert alert-warning">
|
|
1631
|
+
* **Note:** Not every feature offered is available for all input types.
|
|
1632
|
+
* Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
|
|
1633
|
+
* </div>
|
|
1634
|
+
*
|
|
1635
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
1636
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
1637
|
+
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
|
1638
|
+
* @param {boolean=} ngRequired Sets `required` attribute if set to true
|
|
1639
|
+
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
|
1640
|
+
* minlength.
|
|
1641
|
+
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
|
1642
|
+
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
|
1643
|
+
* length.
|
|
1644
|
+
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
|
1645
|
+
* value does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
|
|
1646
|
+
* If the expression evaluates to a RegExp object, then this is used directly.
|
|
1647
|
+
* If the expression evaluates to a string, then it will be converted to a RegExp
|
|
1648
|
+
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
|
1649
|
+
* `new RegExp('^abc$')`.<br />
|
|
1650
|
+
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
|
|
1651
|
+
* start at the index of the last search's match, thus not taking the whole input value into
|
|
1652
|
+
* account.
|
|
1653
|
+
* @param {string=} ngChange AngularJS expression to be executed when input changes due to user
|
|
1654
|
+
* interaction with the input element.
|
|
1655
|
+
* @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
|
|
1656
|
+
* This parameter is ignored for input[type=password] controls, which will never trim the
|
|
1657
|
+
* input.
|
|
1658
|
+
*
|
|
1659
|
+
*/
|
|
1660
|
+
export const inputDirective = [
|
|
1661
|
+
"$browser",
|
|
1662
|
+
"$filter",
|
|
1663
|
+
"$parse",
|
|
1664
|
+
function ($browser, $filter, $parse) {
|
|
1665
|
+
return {
|
|
1666
|
+
restrict: "E",
|
|
1667
|
+
require: ["?ngModel"],
|
|
1668
|
+
link: {
|
|
1669
|
+
pre(scope, element, attr, ctrls) {
|
|
1670
|
+
if (ctrls[0]) {
|
|
1671
|
+
(inputType[lowercase(attr.type)] || inputType.text)(
|
|
1672
|
+
scope,
|
|
1673
|
+
element,
|
|
1674
|
+
attr,
|
|
1675
|
+
ctrls[0],
|
|
1676
|
+
$browser,
|
|
1677
|
+
$filter,
|
|
1678
|
+
$parse,
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
},
|
|
1682
|
+
},
|
|
1683
|
+
};
|
|
1684
|
+
},
|
|
1685
|
+
];
|
|
1686
|
+
|
|
1687
|
+
export function hiddenInputBrowserCacheDirective() {
|
|
1688
|
+
const valueProperty = {
|
|
1689
|
+
configurable: true,
|
|
1690
|
+
enumerable: false,
|
|
1691
|
+
get() {
|
|
1692
|
+
return this.getAttribute("value") || "";
|
|
1693
|
+
},
|
|
1694
|
+
set(val) {
|
|
1695
|
+
this.setAttribute("value", val);
|
|
1696
|
+
},
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
return {
|
|
1700
|
+
restrict: "E",
|
|
1701
|
+
priority: 200,
|
|
1702
|
+
compile(_, attr) {
|
|
1703
|
+
if (lowercase(attr.type) !== "hidden") {
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
return {
|
|
1708
|
+
pre(scope, element) {
|
|
1709
|
+
const node = element[0];
|
|
1710
|
+
|
|
1711
|
+
// Support: Edge
|
|
1712
|
+
// Moving the DOM around prevents autofillling
|
|
1713
|
+
if (node.parentNode) {
|
|
1714
|
+
node.parentNode.insertBefore(node, node.nextSibling);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Support: FF, IE
|
|
1718
|
+
// Avoiding direct assignment to .value prevents autofillling
|
|
1719
|
+
if (Object.defineProperty) {
|
|
1720
|
+
Object.defineProperty(node, "value", valueProperty);
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1723
|
+
};
|
|
1724
|
+
},
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
|
|
1729
|
+
/**
|
|
1730
|
+
* @ngdoc directive
|
|
1731
|
+
* @name ngValue
|
|
1732
|
+
* @restrict A
|
|
1733
|
+
* @priority 100
|
|
1734
|
+
*
|
|
1735
|
+
* @description
|
|
1736
|
+
* Binds the given expression to the value of the element.
|
|
1737
|
+
*
|
|
1738
|
+
* It is mainly used on {@link input[radio] `input[radio]`} and option elements,
|
|
1739
|
+
* so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its
|
|
1740
|
+
* {@link select `select`} parent element) is set to the bound value. It is especially useful
|
|
1741
|
+
* for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below.
|
|
1742
|
+
*
|
|
1743
|
+
* It can also be used to achieve one-way binding of a given expression to an input element
|
|
1744
|
+
* such as an `input[text]` or a `textarea`, when that element does not use ngModel.
|
|
1745
|
+
*
|
|
1746
|
+
* @element ANY
|
|
1747
|
+
* @param {string=} ngValue AngularJS expression, whose value will be bound to the `value` attribute
|
|
1748
|
+
* and `value` property of the element.
|
|
1749
|
+
*
|
|
1750
|
+
*/
|
|
1751
|
+
export function ngValueDirective() {
|
|
1752
|
+
/**
|
|
1753
|
+
* inputs use the value attribute as their default value if the value property is not set.
|
|
1754
|
+
* Once the value property has been set (by adding input), it will not react to changes to
|
|
1755
|
+
* the value attribute anymore. Setting both attribute and property fixes this behavior, and
|
|
1756
|
+
* makes it possible to use ngValue as a sort of one-way bind.
|
|
1757
|
+
*/
|
|
1758
|
+
function updateElementValue(element, attr, value) {
|
|
1759
|
+
// Support: IE9 only
|
|
1760
|
+
// In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
|
|
1761
|
+
const propValue = isDefined(value) ? value : null;
|
|
1762
|
+
element.prop("value", propValue);
|
|
1763
|
+
attr.$set("value", value);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
return {
|
|
1767
|
+
restrict: "A",
|
|
1768
|
+
priority: 100,
|
|
1769
|
+
compile(tpl, tplAttr) {
|
|
1770
|
+
if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
|
|
1771
|
+
return function ngValueConstantLink(scope, elm, attr) {
|
|
1772
|
+
const value = scope.$eval(attr.ngValue);
|
|
1773
|
+
updateElementValue(elm, attr, value);
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
return function ngValueLink(scope, elm, attr) {
|
|
1777
|
+
scope.$watch(attr.ngValue, (value) => {
|
|
1778
|
+
updateElementValue(elm, attr, value);
|
|
1779
|
+
});
|
|
1780
|
+
};
|
|
1781
|
+
},
|
|
1782
|
+
};
|
|
1783
|
+
}
|