@tko/binding.core 4.0.0-alpha9.0 → 4.0.0-beta1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/attr.js +36 -0
  2. package/dist/attr.js.map +7 -0
  3. package/dist/checked.js +94 -0
  4. package/dist/checked.js.map +7 -0
  5. package/dist/click.js +5 -0
  6. package/dist/click.js.map +7 -0
  7. package/dist/css.js +28 -0
  8. package/dist/css.js.map +7 -0
  9. package/dist/descendantsComplete.js +14 -0
  10. package/dist/descendantsComplete.js.map +7 -0
  11. package/dist/enableDisable.js +21 -0
  12. package/dist/enableDisable.js.map +7 -0
  13. package/dist/event.js +75 -0
  14. package/dist/event.js.map +7 -0
  15. package/dist/hasfocus.js +48 -0
  16. package/dist/hasfocus.js.map +7 -0
  17. package/dist/html.js +15 -0
  18. package/dist/html.js.map +7 -0
  19. package/dist/index.cjs +3956 -0
  20. package/dist/index.cjs.map +7 -0
  21. package/dist/index.js +50 -0
  22. package/dist/index.js.map +7 -0
  23. package/dist/index.mjs +50 -0
  24. package/dist/index.mjs.map +7 -0
  25. package/dist/let.js +12 -0
  26. package/dist/let.js.map +7 -0
  27. package/dist/options.js +130 -0
  28. package/dist/options.js.map +7 -0
  29. package/dist/selectedOptions.js +41 -0
  30. package/dist/selectedOptions.js.map +7 -0
  31. package/dist/style.js +30 -0
  32. package/dist/style.js.map +7 -0
  33. package/dist/submit.js +26 -0
  34. package/dist/submit.js.map +7 -0
  35. package/dist/test-helper.js +14 -0
  36. package/dist/test-helper.js.map +7 -0
  37. package/dist/text.js +13 -0
  38. package/dist/text.js.map +7 -0
  39. package/dist/textInput.js +151 -0
  40. package/dist/textInput.js.map +7 -0
  41. package/dist/uniqueName.js +13 -0
  42. package/dist/uniqueName.js.map +7 -0
  43. package/dist/using.js +12 -0
  44. package/dist/using.js.map +7 -0
  45. package/dist/value.js +103 -0
  46. package/dist/value.js.map +7 -0
  47. package/dist/visible.js +20 -0
  48. package/dist/visible.js.map +7 -0
  49. package/package.json +18 -28
  50. package/dist/binding.core.es6.js +0 -1044
  51. package/dist/binding.core.es6.js.map +0 -1
  52. package/dist/binding.core.js +0 -1146
  53. package/dist/binding.core.js.map +0 -1
@@ -1,1044 +0,0 @@
1
- /*!
2
- * TKO Core bindings 🥊 @tko/binding.core@4.0.0-alpha9.0
3
- * (c) The Knockout.js Team - https://tko.io
4
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
- */
6
-
7
- import { setElementName, objectForEach, registerEventHandler, arrayIndexOf, addOrRemoveItem, throttle, debounce, createSymbolOrString, toggleDomNodeCssClass, stringTrim, triggerEvent, setHtml, tagNameLower, arrayFilter, arrayMap, setTextContent, setOptionNodeSelectionState, domData, ensureSelectElementIsRenderedCorrectly, selectExtensions, arrayForEach, options, ieVersion, safeSetTimeout, stringStartsWith } from '@tko/utils';
8
- import { unwrap, dependencyDetection, isWriteableObservable } from '@tko/observable';
9
- import { computed, pureComputed } from '@tko/computed';
10
- import { BindingHandler, applyBindingsToDescendants, setDomNodeChildrenFromArrayMapping, applyBindingAccessorsToNode } from '@tko/bind';
11
-
12
- var attr = {
13
- update: function (element, valueAccessor, allBindings) {
14
- var value = unwrap(valueAccessor()) || {};
15
- objectForEach(value, function (attrName, attrValue) {
16
- attrValue = unwrap(attrValue);
17
-
18
- // Find the namespace of this attribute, if any.
19
- var prefixLen = attrName.indexOf(':');
20
- var namespace = prefixLen > 0 && element.lookupNamespaceURI(attrName.substr(0, prefixLen));
21
-
22
- // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
23
- // when someProp is a "no value"-like value (strictly null, false, or undefined)
24
- // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
25
- const toRemove = attrValue === false || attrValue === null || attrValue === undefined;
26
-
27
- if (toRemove) {
28
- if (namespace) {
29
- element.removeAttributeNS(namespace, attrName);
30
- } else {
31
- element.removeAttribute(attrName);
32
- }
33
- } else {
34
- attrValue = attrValue.toString();
35
- if (namespace) {
36
- element.setAttributeNS(namespace, attrName, attrValue);
37
- } else {
38
- element.setAttribute(attrName, attrValue);
39
- }
40
- }
41
-
42
- // Treat "name" specially - although you can think of it as an attribute, it also needs
43
- // special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
44
- // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
45
- // entirely, and there's no strong reason to allow for such casing in HTML.
46
- if (attrName === 'name') {
47
- setElementName(element, toRemove ? '' : attrValue);
48
- }
49
- });
50
- }
51
- };
52
-
53
- var checked = {
54
- after: ['value', 'attr'],
55
- init: function (element, valueAccessor, allBindings) {
56
- var checkedValue = pureComputed(function () {
57
- // Treat "value" like "checkedValue" when it is included with "checked" binding
58
- if (allBindings.has('checkedValue')) {
59
- return unwrap(allBindings.get('checkedValue'))
60
- } else if (useElementValue) {
61
- if (allBindings.has('value')) {
62
- return unwrap(allBindings.get('value'))
63
- } else {
64
- return element.value
65
- }
66
- }
67
- });
68
-
69
- function updateModel () {
70
- // This updates the model value from the view value.
71
- // It runs in response to DOM events (click) and changes in checkedValue.
72
- var isChecked = element.checked,
73
- elemValue = checkedValue();
74
-
75
- // When we're first setting up this computed, don't change any model state.
76
- if (dependencyDetection.isInitial()) {
77
- return
78
- }
79
-
80
- // We can ignore unchecked radio buttons, because some other radio
81
- // button will be checked, and that one can take care of updating state.
82
- // button will be checked, and that one can take care of updating state
83
- if (!isChecked && (isRadio || dependencyDetection.getDependenciesCount())) {
84
- return
85
- }
86
-
87
- var modelValue = dependencyDetection.ignore(valueAccessor);
88
- if (valueIsArray) {
89
- var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue,
90
- saveOldValue = oldElemValue;
91
- oldElemValue = elemValue;
92
-
93
- if (saveOldValue !== elemValue) {
94
- // When we're responding to the checkedValue changing, and the element is
95
- // currently checked, replace the old elem value with the new elem value
96
- // in the model array.
97
- if (isChecked) {
98
- addOrRemoveItem(writableValue, elemValue, true);
99
- addOrRemoveItem(writableValue, saveOldValue, false);
100
- }
101
-
102
- oldElemValue = elemValue;
103
- } else {
104
- // When we're responding to the user having checked/unchecked a checkbox,
105
- // add/remove the element value to the model array.
106
- addOrRemoveItem(writableValue, elemValue, isChecked);
107
- }
108
- if (rawValueIsNonArrayObservable && isWriteableObservable(modelValue)) {
109
- modelValue(writableValue);
110
- }
111
- } else {
112
- if (isCheckbox) {
113
- if (elemValue === undefined) {
114
- elemValue = isChecked;
115
- } else if (!isChecked) {
116
- elemValue = undefined;
117
- }
118
- }
119
- valueAccessor(elemValue, {onlyIfChanged: true});
120
- }
121
- }
122
- function updateView () {
123
- // This updates the view value from the model value.
124
- // It runs in response to changes in the bound (checked) value.
125
- var modelValue = modelValue = unwrap(valueAccessor());
126
- var elemValue = checkedValue();
127
-
128
- if (valueIsArray) {
129
- // When a checkbox is bound to an array, being checked represents its value being present in that array
130
- element.checked = arrayIndexOf(modelValue, elemValue) >= 0;
131
- oldElemValue = elemValue;
132
- } else if (isCheckbox && elemValue === undefined) {
133
- // When a checkbox is bound to any other value (not an array) and "checkedValue" is not defined,
134
- // being checked represents the value being trueish
135
- element.checked = !!modelValue;
136
- } else {
137
- // Otherwise, being checked means that the checkbox or radio button's value corresponds to the model value
138
- element.checked = (checkedValue() === modelValue);
139
- }
140
- }
141
- var isCheckbox = element.type == 'checkbox',
142
- isRadio = element.type == 'radio';
143
-
144
- // Only bind to check boxes and radio buttons
145
- if (!isCheckbox && !isRadio) {
146
- return
147
- }
148
-
149
- var rawValue = valueAccessor(),
150
- valueIsArray = isCheckbox && (unwrap(rawValue) instanceof Array),
151
- rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
152
- useElementValue = isRadio || valueIsArray,
153
- oldElemValue = valueIsArray ? checkedValue() : undefined;
154
-
155
- // Set up two computeds to update the binding:
156
-
157
- // The first responds to changes in the checkedValue value and to element clicks
158
- computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
159
- registerEventHandler(element, 'click', updateModel);
160
-
161
- // The second responds to changes in the model value (the one associated with the checked binding)
162
- computed(updateView, null, { disposeWhenNodeIsRemoved: element });
163
-
164
- rawValue = undefined;
165
- }
166
- };
167
-
168
- var checkedValue = {
169
- update: function (element, valueAccessor) {
170
- element.value = unwrap(valueAccessor());
171
- }
172
- };
173
-
174
- // For certain common events (currently just 'click'), allow a simplified data-binding syntax
175
- // e.g. click:handler instead of the usual full-length event:{click:handler}
176
- function makeEventHandlerShortcut (eventName) {
177
- return {
178
- init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
179
- var newValueAccessor = function () {
180
- var result = {};
181
- result[eventName] = valueAccessor();
182
- return result
183
- };
184
- eventHandler.init.call(this, element, newValueAccessor, allBindings, viewModel, bindingContext);
185
- }
186
- }
187
- }
188
-
189
- function makeDescriptor (handlerOrObject) {
190
- return typeof handlerOrObject === 'function' ? { handler: handlerOrObject } : handlerOrObject || {}
191
- }
192
-
193
- const eventHandler = {
194
- init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
195
- var eventsToHandle = valueAccessor() || {};
196
- objectForEach(eventsToHandle, function (eventName, descriptor) {
197
- const {passive, capture, once, debounce: debounce$$1, throttle: throttle$$1} = makeDescriptor(descriptor);
198
- const eventOptions = (capture || passive || once) && {capture, passive, once};
199
-
200
- let eventHandlerFn = (event, ...more) => {
201
- var handlerReturnValue;
202
- const {handler, passive, bubble} = makeDescriptor(valueAccessor()[eventName]);
203
-
204
- try {
205
- // Take all the event args, and prefix with the viewmodel
206
- if (handler) {
207
- const possiblyUpdatedViewModel = bindingContext.$data;
208
- const argsForHandler = [possiblyUpdatedViewModel, event, ...more];
209
- handlerReturnValue = handler.apply(possiblyUpdatedViewModel, argsForHandler);
210
- }
211
- } finally {
212
- if (handlerReturnValue !== true) {
213
- // Normally we want to prevent default action. Developer can override this be explicitly returning true.
214
- // preventDefault will throw an error if the event is passive.
215
- if (event.preventDefault) {
216
- if (!passive) { event.preventDefault(); }
217
- } else {
218
- event.returnValue = false;
219
- }
220
- }
221
- }
222
-
223
- const bubbleMark = allBindings.get(eventName + 'Bubble') !== false;
224
- if (bubble === false || !bubbleMark) {
225
- event.cancelBubble = true;
226
- if (event.stopPropagation) { event.stopPropagation(); }
227
- }
228
- };
229
-
230
- if (debounce$$1) { eventHandlerFn = debounce(eventHandlerFn, debounce$$1); }
231
- if (throttle$$1) { eventHandlerFn = throttle(eventHandlerFn, throttle$$1); }
232
-
233
- registerEventHandler(element, eventName, eventHandlerFn, eventOptions || false);
234
- });
235
- }
236
- };
237
-
238
- const onHandler = {
239
- init: eventHandler.init,
240
- preprocess: function (value, key, addBinding) {
241
- addBinding(key.replace('on.', ''), '=>' + value);
242
- }
243
- };
244
-
245
- // 'click' is just a shorthand for the usual full-length event:{click:handler}
246
- var click = makeEventHandlerShortcut('click');
247
-
248
- var css = {
249
- aliases: ['class'],
250
- update: function (element, valueAccessor) {
251
- var value = unwrap(valueAccessor());
252
- if (value !== null && typeof value === 'object') {
253
- objectForEach(value, function (className, shouldHaveClass) {
254
- shouldHaveClass = unwrap(shouldHaveClass);
255
- toggleDomNodeCssClass(element, className, shouldHaveClass);
256
- });
257
- } else {
258
- value = stringTrim(String(value || '')); // Make sure we don't try to store or set a non-string value
259
- toggleDomNodeCssClass(element, element[css.classesWrittenByBindingKey], false);
260
- element[css.classesWrittenByBindingKey] = value;
261
- toggleDomNodeCssClass(element, value, true);
262
- }
263
- },
264
- classesWrittenByBindingKey: createSymbolOrString('__ko__cssValue')
265
- };
266
-
267
- /**
268
- * A simple callback binding.
269
- */
270
-
271
- class DescendantsCompleteHandler extends BindingHandler {
272
- onDescendantsComplete () {
273
- if (typeof this.value === 'function') {
274
- this.value(this.$element);
275
- }
276
- }
277
-
278
- static get allowVirtualElements () { return true }
279
- }
280
-
281
- var enable = {
282
- update: function (element, valueAccessor) {
283
- var value = unwrap(valueAccessor());
284
- if (value && element.disabled) {
285
- element.removeAttribute('disabled');
286
- } else if ((!value) && (!element.disabled)) {
287
- element.disabled = true;
288
- }
289
- }
290
- };
291
-
292
- var disable = {
293
- update: function (element, valueAccessor) {
294
- enable.update(element, function () { return !unwrap(valueAccessor()) });
295
- }
296
- };
297
-
298
- var hasfocusUpdatingProperty = createSymbolOrString('__ko_hasfocusUpdating');
299
- var hasfocusLastValue = createSymbolOrString('__ko_hasfocusLastValue');
300
-
301
- var hasfocus = {
302
- init: function (element, valueAccessor /*, allBindings */) {
303
- var handleElementFocusChange = function (isFocused) {
304
- // Where possible, ignore which event was raised and determine focus state using activeElement,
305
- // as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
306
- // However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
307
- // prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
308
- // from calling 'blur()' on the element when it loses focus.
309
- // Discussion at https://github.com/SteveSanderson/knockout/pull/352
310
- element[hasfocusUpdatingProperty] = true;
311
- var ownerDoc = element.ownerDocument;
312
- if ('activeElement' in ownerDoc) {
313
- var active;
314
- try {
315
- active = ownerDoc.activeElement;
316
- } catch (e) {
317
- // IE9 throws if you access activeElement during page load (see issue #703)
318
- active = ownerDoc.body;
319
- }
320
- isFocused = (active === element);
321
- }
322
- // var modelValue = valueAccessor();
323
- valueAccessor(isFocused, {onlyIfChanged: true});
324
-
325
- // cache the latest value, so we can avoid unnecessarily calling focus/blur in the update function
326
- element[hasfocusLastValue] = isFocused;
327
- element[hasfocusUpdatingProperty] = false;
328
- };
329
- var handleElementFocusIn = handleElementFocusChange.bind(null, true);
330
- var handleElementFocusOut = handleElementFocusChange.bind(null, false);
331
-
332
- registerEventHandler(element, 'focus', handleElementFocusIn);
333
- registerEventHandler(element, 'focusin', handleElementFocusIn); // For IE
334
- registerEventHandler(element, 'blur', handleElementFocusOut);
335
- registerEventHandler(element, 'focusout', handleElementFocusOut); // For IE
336
- },
337
- update: function (element, valueAccessor) {
338
- var value = !!unwrap(valueAccessor());
339
-
340
- if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
341
- value ? element.focus() : element.blur();
342
-
343
- // In IE, the blur method doesn't always cause the element to lose focus (for example, if the window is not in focus).
344
- // Setting focus to the body element does seem to be reliable in IE, but should only be used if we know that the current
345
- // element was focused already.
346
- if (!value && element[hasfocusLastValue]) {
347
- element.ownerDocument.body.focus();
348
- }
349
-
350
- // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
351
- dependencyDetection.ignore(triggerEvent, null, [element, value ? 'focusin' : 'focusout']);
352
- }
353
- }
354
- };
355
-
356
- var html = {
357
- init: function () {
358
- // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
359
- return {
360
- 'controlsDescendantBindings': true
361
- }
362
- },
363
- //
364
- // Modify internal, per ko.punches and :
365
- // http://stackoverflow.com/a/15348139
366
- update: function (element, valueAccessor) {
367
- setHtml(element, valueAccessor());
368
- },
369
- allowVirtualElements: true
370
- };
371
-
372
- var $let = {
373
- init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
374
- // Make a modified binding context, with extra properties, and apply it to descendant elements
375
- var innerContext = bindingContext['extend'](valueAccessor);
376
- applyBindingsToDescendants(innerContext, element);
377
-
378
- return { 'controlsDescendantBindings': true }
379
- },
380
- allowVirtualElements: true
381
- }
382
-
383
- var captionPlaceholder = {};
384
-
385
- var options$1 = {
386
- init: function (element) {
387
- if (tagNameLower(element) !== 'select') { throw new Error('options binding applies only to SELECT elements') }
388
-
389
- // Remove all existing <option>s.
390
- while (element.length > 0) {
391
- element.remove(0);
392
- }
393
-
394
- // Ensures that the binding processor doesn't try to bind the options
395
- return { 'controlsDescendantBindings': true }
396
- },
397
- update: function (element, valueAccessor, allBindings) {
398
- function selectedOptions () {
399
- return arrayFilter(element.options, function (node) { return node.selected })
400
- }
401
-
402
- var selectWasPreviouslyEmpty = element.length == 0,
403
- multiple = element.multiple,
404
- previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null,
405
- unwrappedArray = unwrap(valueAccessor()),
406
- valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'),
407
- includeDestroyed = allBindings.get('optionsIncludeDestroyed'),
408
- arrayToDomNodeChildrenOptions = {},
409
- captionValue,
410
- filteredArray,
411
- previousSelectedValues = [];
412
-
413
- if (!valueAllowUnset) {
414
- if (multiple) {
415
- previousSelectedValues = arrayMap(selectedOptions(), selectExtensions.readValue);
416
- } else if (element.selectedIndex >= 0) {
417
- previousSelectedValues.push(selectExtensions.readValue(element.options[element.selectedIndex]));
418
- }
419
- }
420
-
421
- if (unwrappedArray) {
422
- if (typeof unwrappedArray.length === 'undefined') // Coerce single value into array
423
- { unwrappedArray = [unwrappedArray]; }
424
-
425
- // Filter out any entries marked as destroyed
426
- filteredArray = arrayFilter(unwrappedArray, function (item) {
427
- return includeDestroyed || item === undefined || item === null || !unwrap(item['_destroy'])
428
- });
429
-
430
- // If caption is included, add it to the array
431
- if (allBindings['has']('optionsCaption')) {
432
- captionValue = unwrap(allBindings.get('optionsCaption'));
433
- // If caption value is null or undefined, don't show a caption
434
- if (captionValue !== null && captionValue !== undefined) {
435
- filteredArray.unshift(captionPlaceholder);
436
- }
437
- }
438
- }
439
-
440
- function applyToObject (object, predicate, defaultValue) {
441
- var predicateType = typeof predicate;
442
- if (predicateType == 'function') // Given a function; run it against the data value
443
- { return predicate(object) } else if (predicateType == 'string') // Given a string; treat it as a property name on the data value
444
- { return object[predicate] } else // Given no optionsText arg; use the data value itself
445
- { return defaultValue }
446
- }
447
-
448
- // The following functions can run at two different times:
449
- // The first is when the whole array is being updated directly from this binding handler.
450
- // The second is when an observable value for a specific array entry is updated.
451
- // oldOptions will be empty in the first case, but will be filled with the previously generated option in the second.
452
- var itemUpdate = false;
453
- function optionForArrayItem (arrayEntry, index, oldOptions) {
454
- if (oldOptions.length) {
455
- previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ selectExtensions.readValue(oldOptions[0]) ] : [];
456
- itemUpdate = true;
457
- }
458
- var option = element.ownerDocument.createElement('option');
459
- if (arrayEntry === captionPlaceholder) {
460
- setTextContent(option, allBindings.get('optionsCaption'));
461
- selectExtensions.writeValue(option, undefined);
462
- } else {
463
- // Apply a value to the option element
464
- var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue'), arrayEntry);
465
- selectExtensions.writeValue(option, unwrap(optionValue));
466
-
467
- // Apply some text to the option element
468
- var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue);
469
- setTextContent(option, optionText);
470
- }
471
- return [option]
472
- }
473
-
474
- // By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
475
- // problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
476
- arrayToDomNodeChildrenOptions['beforeRemove'] =
477
- function (option) {
478
- element.removeChild(option);
479
- };
480
-
481
- function setSelectionCallback (arrayEntry, newOptions) {
482
- if (itemUpdate && valueAllowUnset) {
483
- // The model value is authoritative, so make sure its value is the one selected
484
- // There is no need to use dependencyDetection.ignore since setDomNodeChildrenFromArrayMapping does so already.
485
- selectExtensions.writeValue(element, unwrap(allBindings.get('value')), true /* allowUnset */);
486
- } else if (previousSelectedValues.length) {
487
- // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
488
- // That's why we first added them without selection. Now it's time to set the selection.
489
- var isSelected = arrayIndexOf(previousSelectedValues, selectExtensions.readValue(newOptions[0])) >= 0;
490
- setOptionNodeSelectionState(newOptions[0], isSelected);
491
-
492
- // If this option was changed from being selected during a single-item update, notify the change
493
- if (itemUpdate && !isSelected) {
494
- dependencyDetection.ignore(triggerEvent, null, [element, 'change']);
495
- }
496
- }
497
- }
498
-
499
- var callback = setSelectionCallback;
500
- if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') === 'function') {
501
- callback = function (arrayEntry, newOptions) {
502
- setSelectionCallback(arrayEntry, newOptions);
503
- dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
504
- };
505
- }
506
-
507
- setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
508
-
509
- dependencyDetection.ignore(function () {
510
- if (valueAllowUnset) {
511
- // The model value is authoritative, so make sure its value is the one selected
512
- selectExtensions.writeValue(element, unwrap(allBindings.get('value')), true /* allowUnset */);
513
- } else {
514
- // Determine if the selection has changed as a result of updating the options list
515
- var selectionChanged;
516
- if (multiple) {
517
- // For a multiple-select box, compare the new selection count to the previous one
518
- // But if nothing was selected before, the selection can't have changed
519
- selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length;
520
- } else {
521
- // For a single-select box, compare the current value to the previous value
522
- // But if nothing was selected before or nothing is selected now, just look for a change in selection
523
- selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0)
524
- ? (selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
525
- : (previousSelectedValues.length || element.selectedIndex >= 0);
526
- }
527
-
528
- // Ensure consistency between model value and selected option.
529
- // If the dropdown was changed so that selection is no longer the same,
530
- // notify the value or selectedOptions binding.
531
- if (selectionChanged) {
532
- triggerEvent(element, 'change');
533
- }
534
- }
535
- });
536
-
537
- // Workaround for IE bug
538
- ensureSelectElementIsRenderedCorrectly(element);
539
-
540
- if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20) { element.scrollTop = previousScrollTop; }
541
- }
542
- };
543
-
544
- var selectedOptions = {
545
- after: ['options', 'foreach'],
546
-
547
- init: function (element, valueAccessor, allBindings) {
548
- registerEventHandler(element, 'change', function () {
549
- var value = valueAccessor(), valueToWrite = [];
550
- arrayForEach(element.getElementsByTagName('option'), function (node) {
551
- if (node.selected) { valueToWrite.push(selectExtensions.readValue(node)); }
552
- });
553
- valueAccessor(valueToWrite);
554
- });
555
- },
556
-
557
- update: function (element, valueAccessor) {
558
- if (tagNameLower(element) != 'select') { throw new Error('values binding applies only to SELECT elements') }
559
-
560
- var newValue = unwrap(valueAccessor()),
561
- previousScrollTop = element.scrollTop;
562
-
563
- if (newValue && typeof newValue.length === 'number') {
564
- arrayForEach(element.getElementsByTagName('option'), function (node) {
565
- var isSelected = arrayIndexOf(newValue, selectExtensions.readValue(node)) >= 0;
566
- if (node.selected != isSelected) { // This check prevents flashing of the select element in IE
567
- setOptionNodeSelectionState(node, isSelected);
568
- }
569
- });
570
- }
571
-
572
- element.scrollTop = previousScrollTop;
573
- }
574
- };
575
-
576
- const {jQueryInstance} = options;
577
-
578
- var style = {
579
- update: function (element, valueAccessor) {
580
- var value = unwrap(valueAccessor() || {});
581
- objectForEach(value, function (styleName, styleValue) {
582
- styleValue = unwrap(styleValue);
583
-
584
- if (styleValue === null || styleValue === undefined || styleValue === false) {
585
- // Empty string removes the value, whereas null/undefined have no effect
586
- styleValue = '';
587
- }
588
-
589
- if (jQueryInstance) {
590
- jQueryInstance(element).css(styleName, styleValue);
591
- } else {
592
- styleName = styleName.replace(/-(\w)/g, (all, letter) => letter.toUpperCase());
593
- const previousStyle = element.style[styleName];
594
- element.style[styleName] = styleValue;
595
- if (styleValue !== previousStyle && element.style[styleName] === previousStyle && !isNaN(styleValue)) {
596
- element.style[styleName] = styleValue + 'px';
597
- }
598
- }
599
- });
600
- }
601
- };
602
-
603
- var submit = {
604
- init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
605
- if (typeof valueAccessor() !== 'function') { throw new Error('The value for a submit binding must be a function') }
606
- registerEventHandler(element, 'submit', function (event) {
607
- var handlerReturnValue;
608
- var value = valueAccessor();
609
- try { handlerReturnValue = value.call(bindingContext['$data'], element); } finally {
610
- if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
611
- if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; }
612
- }
613
- }
614
- });
615
- }
616
- };
617
-
618
- var text = {
619
- init: function () {
620
- // Prevent binding on the dynamically-injected text node (as developers are unlikely to expect that, and it has security implications).
621
- // It should also make things faster, as we no longer have to consider whether the text node might be bindable.
622
- return { controlsDescendantBindings: true }
623
- },
624
- update: function (element, valueAccessor) {
625
- setTextContent(element, valueAccessor());
626
- },
627
- allowVirtualElements: true
628
- };
629
-
630
- var operaVersion, safariVersion, firefoxVersion;
631
-
632
-
633
- /**
634
- * TextInput binding handler for modern browsers (legacy below).
635
- * @extends BindingHandler
636
- */
637
- class TextInput extends BindingHandler {
638
- get aliases () { return 'textinput' }
639
-
640
- constructor (...args) {
641
- super(...args);
642
- this.previousElementValue = this.$element.value;
643
-
644
- if (options.debug && this.constructor._forceUpdateOn) {
645
- // Provide a way for tests to specify exactly which events are bound
646
- arrayForEach(this.constructor._forceUpdateOn, (eventName) => {
647
- if (eventName.slice(0, 5) === 'after') {
648
- this.addEventListener(eventName.slice(5), 'deferUpdateModel');
649
- } else {
650
- this.addEventListener(eventName, 'updateModel');
651
- }
652
- });
653
- }
654
-
655
- for (const eventName of this.eventsIndicatingSyncValueChange()) {
656
- this.addEventListener(eventName, 'updateModel');
657
- }
658
- for (const eventName of this.eventsIndicatingDeferValueChange()) {
659
- this.addEventListener(eventName, 'deferUpdateModel');
660
- }
661
- this.computed('updateView');
662
- }
663
-
664
- eventsIndicatingSyncValueChange () {
665
- // input: Default, modern handler
666
- // change: Catch programmatic updates of the value that fire this event.
667
- // blur: To deal with browsers that don't notify any kind of event for some changes (IE, Safari, etc.)
668
- return ['input', 'change', 'blur']
669
- }
670
-
671
- eventsIndicatingDeferValueChange () {
672
- return []
673
- }
674
-
675
- updateModel (event) {
676
- const element = this.$element;
677
- clearTimeout(this.timeoutHandle);
678
- this.elementValueBeforeEvent = this.timeoutHandle = undefined;
679
- const elementValue = element.value;
680
- if (this.previousElementValue !== elementValue) {
681
- // Provide a way for tests to know exactly which event was processed
682
- if (options.debug && event) {
683
- element._ko_textInputProcessedEvent = event.type;
684
- }
685
- this.previousElementValue = elementValue;
686
- this.value = elementValue;
687
- }
688
- }
689
-
690
- deferUpdateModel (event) {
691
- const element = this.$element;
692
- if (!this.timeoutHandle) {
693
- // The elementValueBeforeEvent variable is set *only* during the brief gap between an
694
- // event firing and the updateModel function running. This allows us to ignore model
695
- // updates that are from the previous state of the element, usually due to techniques
696
- // such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
697
- this.elementValueBeforeEvent = element.value;
698
- const handler = options.debug ? this.updateModel.bind(this, { type: event.type }) : this.updateModel;
699
- this.timeoutHandle = safeSetTimeout(handler, 4);
700
- }
701
- }
702
-
703
- updateView () {
704
- let modelValue = unwrap(this.value);
705
- if (modelValue === null || modelValue === undefined) {
706
- modelValue = '';
707
- }
708
- if (this.elementValueBeforeEvent !== undefined
709
- && modelValue === this.elementValueBeforeEvent) {
710
- setTimeout(this.updateView.bind(this), 4);
711
- } else if (this.$element.value !== modelValue) {
712
- // Update the element only if the element and model are different. On some browsers, updating the value
713
- // will move the cursor to the end of the input, which would be bad while the user is typing.
714
- this.previousElementValue = modelValue; // Make sure we ignore events (propertychange) that result from updating the value
715
- this.$element.value = modelValue;
716
- this.previousElementValue = this.$element.value; // In case the browser changes the value (see #2281)
717
- }
718
- }
719
- }
720
-
721
- /**
722
- * Legacy Input Classes, below
723
- */
724
- class TextInputIE extends TextInput {
725
- constructor (...args) {
726
- super(...args);
727
-
728
- if (ieVersion < 11) {
729
- // Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
730
- // any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
731
- // but that's an acceptable compromise for this binding. IE 9 and 10 support 'input', but since they don't always
732
- // fire it when using autocomplete, we'll use 'propertychange' for them also.
733
- this.addEventListener('propertychange', event =>
734
- event.propertyName === 'value' && this.updateModel(event)
735
- );
736
- }
737
-
738
- if (ieVersion >= 8 && ieVersion < 10) {
739
- this.watchForSelectionChangeEvent();
740
- this.addEventListener('dragend', 'deferUpdateModel');
741
- }
742
- }
743
-
744
- eventsIndicatingSyncValueChange () {
745
- // keypress: All versions (including 11) of Internet Explorer have a bug that they don't generate an input or propertychange event when ESC is pressed
746
- return [...super.eventsIndicatingValueChange(), 'keypress']
747
- }
748
-
749
- // IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
750
- // But it does fire the 'selectionchange' event on many of those, presumably because the
751
- // cursor is moving and that counts as the selection changing. The 'selectionchange' event is
752
- // fired at the document level only and doesn't directly indicate which element changed. We
753
- // set up just one event handler for the document and use 'activeElement' to determine which
754
- // element was changed.
755
- selectionChangeHandler (event) {
756
- const target = this.activeElement;
757
- const handler = target && domData.get(target, selectionChangeHandlerName);
758
- if (handler) { handler(event); }
759
- }
760
-
761
- watchForSelectionChangeEvent (element, ieUpdateModel) {
762
- const ownerDoc = element.ownerDocument;
763
- if (!domData.get(ownerDoc, selectionChangeRegisteredName)) {
764
- domData.set(ownerDoc, selectionChangeRegisteredName, true);
765
- registerEventHandler(ownerDoc, 'selectionchange', this.selectionChangeHandler.bind(ownerDoc));
766
- }
767
- domData.set(element, selectionChangeHandlerName, handler);
768
- }
769
- }
770
-
771
-
772
- // IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
773
- // But it does fire the 'selectionchange' event on many of those, presumably because the
774
- // cursor is moving and that counts as the selection changing. The 'selectionchange' event is
775
- // fired at the document level only and doesn't directly indicate which element changed. We
776
- // set up just one event handler for the document and use 'activeElement' to determine which
777
- // element was changed.
778
- class TextInputIE9 extends TextInputIE {
779
- updateModel (...args) {
780
- // IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings);
781
- // so we'll make sure all updates are asynchronous
782
- this.deferUpdateModel(...args);
783
- }
784
- }
785
-
786
-
787
- class TextInputIE8 extends TextInputIE {
788
- eventsIndicatingValueChange () {
789
- // IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
790
- // JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
791
- // events too.
792
- // keypress: All versions (including 11) of Internet Explorer have a bug that they don't generate an input or propertychange event when ESC is pressed
793
- // keyup: A single keystoke
794
- // keydown: First character when a key is held down
795
- return [...super.eventsIndicatingValueChange(), 'keyup', 'keydown']
796
- }
797
- }
798
-
799
-
800
- // Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
801
- // but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
802
- class TextInputLegacySafari extends TextInput {
803
- eventsIndicatingDeferValueChange () {
804
- return ['keydown', 'paste', 'cut']
805
- }
806
- }
807
-
808
-
809
- class TextInputLegacyOpera extends TextInput {
810
- eventsIndicatingDeferValueChange () {
811
- // Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
812
- // We can try to catch some of those using 'keydown'.
813
- return ['keydown']
814
- }
815
- }
816
-
817
-
818
- class TextInputLegacyFirefox extends TextInput {
819
- eventsIndicatingValueChange () {
820
- return [
821
- ...super.eventsIndicatingSyncValueChange(),
822
- // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
823
- 'DOMAutoComplete',
824
- // Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
825
- 'dragdrop', // < 3.5
826
- 'drop' // 3.5
827
- ]
828
- }
829
- }
830
-
831
-
832
- const w = options.global; // window / global
833
- if (w.navigator) {
834
- const parseVersion = (matches) => matches && parseFloat(matches[1]);
835
- const userAgent = w.navigator.userAgent;
836
- const isChrome = userAgent.match(/Chrome\/([^ ]+)/);
837
- // Detect various browser versions because some old versions don't fully support the 'input' event
838
- operaVersion = w.opera && w.opera.version && parseInt(w.opera.version());
839
- safariVersion = parseVersion(userAgent.match(/Version\/([^ ]+) Safari/));
840
- firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/));
841
- }
842
-
843
-
844
- const textInput =
845
- ieVersion === 8 ? TextInputIE8
846
- : ieVersion === 9 ? TextInputIE9
847
- : ieVersion ? TextInputIE
848
- : safariVersion && safariVersion < 5 ? TextInputLegacySafari
849
- : operaVersion < 11 ? TextInputLegacyOpera
850
- : firefoxVersion && firefoxVersion < 4 ? TextInputLegacyFirefox
851
- : TextInput;
852
-
853
- var uniqueName = {
854
- init: function (element, valueAccessor) {
855
- if (valueAccessor()) {
856
- var name = 'ko_unique_' + (++uniqueName.currentIndex);
857
- setElementName(element, name);
858
- }
859
- },
860
- currentIndex: 0
861
- };
862
-
863
- class value extends BindingHandler {
864
- static get after () { return ['options', 'foreach', 'template'] }
865
-
866
- constructor (...args) {
867
- super(...args);
868
-
869
- // If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
870
- if (this.isCheckboxOrRadio) {
871
- applyBindingAccessorsToNode(this.$element,
872
- { checkedValue: this.valueAccessor });
873
- return
874
- }
875
-
876
- this.propertyChangedFired = false;
877
- this.elementValueBeforeEvent = null;
878
-
879
- if (this.ieAutoCompleteHackNeeded) {
880
- this.addEventListener('propertyChange', () => this.propertyChangedFired = true);
881
- this.addEventListener('focus', () => this.propertyChangedFired = false);
882
- this.addEventListener('blur', () => this.propertyChangeFired &&
883
- this.valueUpdateHandler());
884
- }
885
-
886
- arrayForEach(this.eventsToCatch, eventName => this.registerEvent(eventName));
887
-
888
- if (this.isInput && this.$element.type === 'file') {
889
- this.updateFromModel = this.updateFromModelForFile;
890
- } else {
891
- this.updateFromModel = this.updateFromModelForValue;
892
- }
893
-
894
- this.computed('updateFromModel');
895
- }
896
-
897
- get eventsToCatch () {
898
- const requestedEventsToCatch = this.allBindings.get('valueUpdate');
899
- const requestedEventsArray = typeof requestedEventsToCatch === 'string' ?
900
- [requestedEventsToCatch] : requestedEventsToCatch || [];
901
- return [...new Set(['change', ...requestedEventsArray])]
902
- }
903
-
904
- get isInput () {
905
- return tagNameLower(this.$element) === 'input'
906
- }
907
-
908
- get isCheckboxOrRadio () {
909
- const e = this.$element;
910
- return this.isInput && (e.type == 'checkbox' || e.type == 'radio')
911
- }
912
-
913
- // Workaround for https://github.com/SteveSanderson/knockout/issues/122
914
- // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
915
- get ieAutoCompleteHackNeeded () {
916
- return ieVersion && isInputElement &&
917
- this.$element.type == 'text' && this.$element.autocomplete != 'off' &&
918
- (!this.$element.form || this.$element.form.autocomplete != 'off')
919
- }
920
-
921
- valueUpdateHandler () {
922
- this.elementValueBeforeEvent = null;
923
- this.propertyChangedFired = false;
924
- this.value = selectExtensions.readValue(this.$element);
925
- }
926
-
927
- registerEvent (eventName) {
928
- // The syntax "after<eventname>" means "run the handler asynchronously after the event"
929
- // This is useful, for example, to catch "keydown" events after the browser has updated the control
930
- // (otherwise, selectExtensions.readValue(this) will receive the control's value *before* the key event)
931
- var handler = this.valueUpdateHandler.bind(this);
932
- if (stringStartsWith(eventName, 'after')) {
933
- handler = () => {
934
- // The elementValueBeforeEvent variable is non-null *only* during the brief gap between
935
- // a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
936
- // at the earliest asynchronous opportunity. We store this temporary information so that
937
- // if, between keyX and valueUpdateHandler, the underlying model value changes separately,
938
- // we can overwrite that model value change with the value the user just typed. Otherwise,
939
- // techniques like rateLimit can trigger model changes at critical moments that will
940
- // override the user's inputs, causing keystrokes to be lost.
941
- this.elementValueBeforeEvent = selectExtensions.readValue(this.$element);
942
- safeSetTimeout(this.valueUpdateHandler.bind(this), 0);
943
- };
944
- eventName = eventName.substring(5 /* 'after'.length */);
945
- }
946
- this.addEventListener(eventName, handler);
947
- }
948
-
949
- updateFromModelForFile () {
950
- // For file input elements, can only write the empty string
951
- var newValue = unwrap(this.value);
952
- if (newValue === null || newValue === undefined || newValue === '') {
953
- this.$element.value = '';
954
- } else {
955
- dependencyDetection.ignore(this.valueUpdateHandler, this); // reset the model to match the element
956
- }
957
- }
958
-
959
- updateFromModelForValue () {
960
- const element = this.$element;
961
- var newValue = unwrap(this.value);
962
- var elementValue = selectExtensions.readValue(element);
963
-
964
- if (this.elementValueBeforeEvent !== null && newValue === this.elementValueBeforeEvent) {
965
- safeSetTimeout(this.updateFromModel.bind(this), 0);
966
- return
967
- }
968
-
969
- if (newValue === elementValue && elementValue !== undefined) { return }
970
-
971
- if (tagNameLower(element) === 'select') {
972
- const allowUnset = this.allBindings.get('valueAllowUnset');
973
- selectExtensions.writeValue(element, newValue, allowUnset);
974
-
975
- if (!allowUnset && newValue !== selectExtensions.readValue(element)) {
976
- // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
977
- // because you're not allowed to have a model value that disagrees with a visible UI selection.
978
- dependencyDetection.ignore(this.valueUpdateHandler, this);
979
- }
980
- } else {
981
- selectExtensions.writeValue(element, newValue);
982
- }
983
- }
984
- }
985
-
986
- var visible = {
987
- update: function (element, valueAccessor) {
988
- var value = unwrap(valueAccessor());
989
- var isCurrentlyVisible = !(element.style.display === 'none');
990
- if (value && !isCurrentlyVisible) {
991
- element.style.display = '';
992
- } else if (!value && isCurrentlyVisible) {
993
- element.style.display = 'none';
994
- }
995
- }
996
- };
997
-
998
- var hidden = {
999
- update: function (element, valueAccessor) {
1000
- visible.update.call(this, element, () => !unwrap(valueAccessor()));
1001
- }
1002
- };
1003
-
1004
- var using = {
1005
- init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
1006
- var innerContext = bindingContext.createChildContext(valueAccessor);
1007
- applyBindingsToDescendants(innerContext, element);
1008
- return { controlsDescendantBindings: true }
1009
- },
1010
- allowVirtualElements: true
1011
- };
1012
-
1013
- var bindings = {
1014
- attr,
1015
- checked,
1016
- checkedValue,
1017
- click,
1018
- css,
1019
- 'class': css,
1020
- descendantsComplete: DescendantsCompleteHandler,
1021
- enable,
1022
- 'event': eventHandler,
1023
- disable,
1024
- hasfocus,
1025
- hasFocus: hasfocus,
1026
- hidden,
1027
- html,
1028
- 'let': $let,
1029
- on: onHandler,
1030
- options: options$1,
1031
- selectedOptions,
1032
- style,
1033
- submit,
1034
- text,
1035
- textInput,
1036
- textinput: textInput,
1037
- uniqueName,
1038
- using,
1039
- value,
1040
- visible
1041
- };
1042
-
1043
- export { bindings };
1044
- //# sourceMappingURL=binding.core.es6.js.map