Merge branch 'trunk' into try/individual-test-files

This commit is contained in:
Ron Rennick 2021-04-28 14:28:27 -03:00
commit 6910b2080a
14 changed files with 723 additions and 304 deletions

View File

@ -55,8 +55,11 @@ jQuery( function( $ ) {
var wc_country_select_select2 = function() { var wc_country_select_select2 = function() {
$( 'select.country_select:visible, select.state_select:visible' ).each( function() { $( 'select.country_select:visible, select.state_select:visible' ).each( function() {
var $this = $( this );
var select2_args = $.extend({ var select2_args = $.extend({
placeholder: $( this ).attr( 'data-placeholder' ) || $( this ).attr( 'placeholder' ) || '', placeholder: $this.attr( 'data-placeholder' ) || $this.attr( 'placeholder' ) || '',
label: $this.attr( 'data-label' ) || null,
width: '100%' width: '100%'
}, getEnhancedSelectFormatString() ); }, getEnhancedSelectFormatString() );

View File

@ -1,17 +1,32 @@
/*! /*!
* Select2 4.0.3 * SelectWoo 1.0.9
* https://select2.github.io * https://github.com/woocommerce/selectWoo
* *
* Released under the MIT license * Released under the MIT license
* https://github.com/select2/select2/blob/master/LICENSE.md * https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md
*/ */
(function (factory) { (function (factory) {
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module. // AMD. Register as an anonymous module.
define(['jquery'], factory); define(['jquery'], factory);
} else if (typeof exports === 'object') { } else if (typeof module === 'object' && module.exports) {
// Node/CommonJS // Node/CommonJS
factory(require('jquery')); module.exports = function (root, jQuery) {
if (jQuery === undefined) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if (typeof window !== 'undefined') {
jQuery = require('jquery');
}
else {
jQuery = require('jquery')(root);
}
}
factory(jQuery);
return jQuery;
};
} else { } else {
// Browser globals // Browser globals
factory(jQuery); factory(jQuery);
@ -20,8 +35,7 @@
// This is needed so we can catch the AMD loader configuration and use it // This is needed so we can catch the AMD loader configuration and use it
// The inner file should be wrapped (by `banner.start.js`) in a function that // The inner file should be wrapped (by `banner.start.js`) in a function that
// returns the AMD loader references. // returns the AMD loader references.
var S2 = var S2 =(function () {
(function () {
// Restore the Select2 AMD loader so it can be used // Restore the Select2 AMD loader so it can be used
// Needed mostly in the language files, where the loader is not inserted // Needed mostly in the language files, where the loader is not inserted
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
@ -30,13 +44,11 @@
var S2;(function () { if (!S2 || !S2.requirejs) { var S2;(function () { if (!S2 || !S2.requirejs) {
if (!S2) { S2 = {}; } else { require = S2; } if (!S2) { S2 = {}; } else { require = S2; }
/** /**
* @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Available via the MIT or new BSD license. * Released under MIT license, http://github.com/requirejs/almond/LICENSE
* see: http://github.com/jrburke/almond for details
*/ */
//Going sloppy to avoid 'use strict' string cost, but strict practices should //Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed. //be followed.
/*jslint sloppy: true */
/*global setTimeout: false */ /*global setTimeout: false */
var requirejs, require, define; var requirejs, require, define;
@ -64,46 +76,49 @@ var requirejs, require, define;
*/ */
function normalize(name, baseName) { function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap, lastIndex, var nameParts, nameSegment, mapValue, foundMap, lastIndex,
foundI, foundStarMap, starI, i, j, part, foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
baseParts = baseName && baseName.split("/"), baseParts = baseName && baseName.split("/"),
map = config.map, map = config.map,
starMap = (map && map['*']) || {}; starMap = (map && map['*']) || {};
//Adjust any relative paths. //Adjust any relative paths.
if (name && name.charAt(0) === ".") { if (name) {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
name = name.split('/'); name = name.split('/');
lastIndex = name.length - 1; lastIndex = name.length - 1;
// Node .js allowance: // If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
} }
//Lop off the last part of baseParts, so that . matches the // Starts with a '.' so need the baseName
//"directory" and not name of the baseName's module. For instance, if (name[0].charAt(0) === '.' && baseParts) {
//baseName of "one/two/three", maps to "one/two/three.js", but we //Convert baseName to array, and lop off the last part,
//want the directory, "one/two" for this normalization. //so that . matches that 'directory' and not name of the baseName's
name = baseParts.slice(0, baseParts.length - 1).concat(name); //module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
//start trimDots //start trimDots
for (i = 0; i < name.length; i += 1) { for (i = 0; i < name.length; i++) {
part = name[i]; part = name[i];
if (part === ".") { if (part === '.') {
name.splice(i, 1); name.splice(i, 1);
i -= 1; i -= 1;
} else if (part === "..") { } else if (part === '..') {
if (i === 1 && (name[2] === '..' || name[0] === '..')) { // If at the start, or previous value is still ..,
//End of the line. Keep at least one non-dot // keep them so that when converted to a path it may
//path segment at the front so it can be mapped // still work when converted to a path, even though
//correctly to disk. Otherwise, there is likely // as an ID it is less than ideal. In larger point
//no path mapping for a path starting with '..'. // releases, may be better to just kick out an error.
//This can still fail, but catches the most reasonable if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
//uses of .. continue;
break;
} else if (i > 0) { } else if (i > 0) {
name.splice(i - 1, 2); name.splice(i - 1, 2);
i -= 2; i -= 2;
@ -112,12 +127,7 @@ var requirejs, require, define;
} }
//end trimDots //end trimDots
name = name.join("/"); name = name.join('/');
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
} }
//Apply map config if available. //Apply map config if available.
@ -230,32 +240,39 @@ var requirejs, require, define;
return [prefix, name]; return [prefix, name];
} }
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/** /**
* Makes a name map, normalizing the name, and using a plugin * Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin * for normalization if necessary. Grabs a ref to plugin
* too, as an optimization. * too, as an optimization.
*/ */
makeMap = function (name, relName) { makeMap = function (name, relParts) {
var plugin, var plugin,
parts = splitPrefix(name), parts = splitPrefix(name),
prefix = parts[0]; prefix = parts[0],
relResourceName = relParts[1];
name = parts[1]; name = parts[1];
if (prefix) { if (prefix) {
prefix = normalize(prefix, relName); prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix); plugin = callDep(prefix);
} }
//Normalize according //Normalize according
if (prefix) { if (prefix) {
if (plugin && plugin.normalize) { if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName)); name = plugin.normalize(name, makeNormalize(relResourceName));
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
} }
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
parts = splitPrefix(name); parts = splitPrefix(name);
prefix = parts[0]; prefix = parts[0];
name = parts[1]; name = parts[1];
@ -302,13 +319,14 @@ var requirejs, require, define;
}; };
main = function (name, deps, callback, relName) { main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, var cjsModule, depName, ret, map, i, relParts,
args = [], args = [],
callbackType = typeof callback, callbackType = typeof callback,
usingExports; usingExports;
//Use name if no relName //Use name if no relName
relName = relName || name; relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary. //Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') { if (callbackType === 'undefined' || callbackType === 'function') {
@ -317,7 +335,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps //Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) { for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName); map = makeMap(deps[i], relParts);
depName = map.f; depName = map.f;
//Fast path CommonJS standard dependencies. //Fast path CommonJS standard dependencies.
@ -373,7 +391,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed) //deps arg is the module name, and second arg (if passed)
//is just the relName. //is just the relName.
//Normalize module name, if it contains . or .. //Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f); return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) { } else if (!deps.splice) {
//deps is a config object, not an array. //deps is a config object, not an array.
config = deps; config = deps;
@ -737,6 +755,12 @@ S2.define('select2/utils',[
}); });
}; };
Utils.entityDecode = function (html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
// Append an array of jQuery nodes to a given element. // Append an array of jQuery nodes to a given element.
Utils.appendMany = function ($element, $nodes) { Utils.appendMany = function ($element, $nodes) {
// jQuery 1.7.x does not support $.fn.append() with an array // jQuery 1.7.x does not support $.fn.append() with an array
@ -754,6 +778,14 @@ S2.define('select2/utils',[
$element.append($nodes); $element.append($nodes);
}; };
// Determine whether the browser is on a touchscreen device.
Utils.isTouchscreen = function() {
if ('undefined' === typeof Utils._isTouchscreenCache) {
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
}
return Utils._isTouchscreenCache;
}
return Utils; return Utils;
}); });
@ -773,7 +805,7 @@ S2.define('select2/results',[
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="select2-results__options" role="tree"></ul>' '<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
); );
if (this.options.get('multiple')) { if (this.options.get('multiple')) {
@ -796,7 +828,7 @@ S2.define('select2/results',[
this.hideLoading(); this.hideLoading();
var $message = $( var $message = $(
'<li role="treeitem" aria-live="assertive"' + '<li role="alert" aria-live="assertive"' +
' class="select2-results__option"></li>' ' class="select2-results__option"></li>'
); );
@ -858,9 +890,9 @@ S2.define('select2/results',[
Results.prototype.highlightFirstItem = function () { Results.prototype.highlightFirstItem = function () {
var $options = this.$results var $options = this.$results
.find('.select2-results__option[aria-selected]'); .find('.select2-results__option[data-selected]');
var $selected = $options.filter('[aria-selected=true]'); var $selected = $options.filter('[data-selected=true]');
// Check if there are any selected options // Check if there are any selected options
if ($selected.length > 0) { if ($selected.length > 0) {
@ -884,7 +916,7 @@ S2.define('select2/results',[
}); });
var $options = self.$results var $options = self.$results
.find('.select2-results__option[aria-selected]'); .find('.select2-results__option[data-selected]');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
@ -896,9 +928,9 @@ S2.define('select2/results',[
if ((item.element != null && item.element.selected) || if ((item.element != null && item.element.selected) ||
(item.element == null && $.inArray(id, selectedIds) > -1)) { (item.element == null && $.inArray(id, selectedIds) > -1)) {
$option.attr('aria-selected', 'true'); $option.attr('data-selected', 'true');
} else { } else {
$option.attr('aria-selected', 'false'); $option.attr('data-selected', 'false');
} }
}); });
@ -930,17 +962,18 @@ S2.define('select2/results',[
option.className = 'select2-results__option'; option.className = 'select2-results__option';
var attrs = { var attrs = {
'role': 'treeitem', 'role': 'option',
'aria-selected': 'false' 'data-selected': 'false',
'tabindex': -1
}; };
if (data.disabled) { if (data.disabled) {
delete attrs['aria-selected']; delete attrs['data-selected'];
attrs['aria-disabled'] = 'true'; attrs['aria-disabled'] = 'true';
} }
if (data.id == null) { if (data.id == null) {
delete attrs['aria-selected']; delete attrs['data-selected'];
} }
if (data._resultId != null) { if (data._resultId != null) {
@ -952,9 +985,8 @@ S2.define('select2/results',[
} }
if (data.children) { if (data.children) {
attrs.role = 'group';
attrs['aria-label'] = data.text; attrs['aria-label'] = data.text;
delete attrs['aria-selected']; delete attrs['data-selected'];
} }
for (var attr in attrs) { for (var attr in attrs) {
@ -971,6 +1003,7 @@ S2.define('select2/results',[
var $label = $(label); var $label = $(label);
this.template(data, label); this.template(data, label);
$label.attr('role', 'presentation');
var $children = []; var $children = [];
@ -983,10 +1016,11 @@ S2.define('select2/results',[
} }
var $childrenContainer = $('<ul></ul>', { var $childrenContainer = $('<ul></ul>', {
'class': 'select2-results__options select2-results__options--nested' 'class': 'select2-results__options select2-results__options--nested',
'role': 'listbox'
}); });
$childrenContainer.append($children); $childrenContainer.append($children);
$option.attr('role', 'list');
$option.append(label); $option.append(label);
$option.append($childrenContainer); $option.append($childrenContainer);
@ -1082,7 +1116,7 @@ S2.define('select2/results',[
var data = $highlighted.data('data'); var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') { if ($highlighted.attr('data-selected') == 'true') {
self.trigger('close', {}); self.trigger('close', {});
} else { } else {
self.trigger('select', { self.trigger('select', {
@ -1094,7 +1128,7 @@ S2.define('select2/results',[
container.on('results:previous', function () { container.on('results:previous', function () {
var $highlighted = self.getHighlightedResults(); var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]'); var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted); var currentIndex = $options.index($highlighted);
@ -1128,7 +1162,7 @@ S2.define('select2/results',[
container.on('results:next', function () { container.on('results:next', function () {
var $highlighted = self.getHighlightedResults(); var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]'); var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted); var currentIndex = $options.index($highlighted);
@ -1156,7 +1190,8 @@ S2.define('select2/results',[
}); });
container.on('results:focus', function (params) { container.on('results:focus', function (params) {
params.element.addClass('select2-results__option--highlighted'); params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true');
self.$results.attr('aria-activedescendant', params.element.attr('id'));
}); });
container.on('results:message', function (params) { container.on('results:message', function (params) {
@ -1188,13 +1223,13 @@ S2.define('select2/results',[
}); });
} }
this.$results.on('mouseup', '.select2-results__option[aria-selected]', this.$results.on('mouseup', '.select2-results__option[data-selected]',
function (evt) { function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.attr('aria-selected') === 'true') { if ($this.attr('data-selected') === 'true') {
if (self.options.get('multiple')) { if (self.options.get('multiple')) {
self.trigger('unselect', { self.trigger('unselect', {
originalEvent: evt, originalEvent: evt,
@ -1213,12 +1248,13 @@ S2.define('select2/results',[
}); });
}); });
this.$results.on('mouseenter', '.select2-results__option[aria-selected]', this.$results.on('mouseenter', '.select2-results__option[data-selected]',
function (evt) { function (evt) {
var data = $(this).data('data'); var data = $(this).data('data');
self.getHighlightedResults() self.getHighlightedResults()
.removeClass('select2-results__option--highlighted'); .removeClass('select2-results__option--highlighted')
.attr('aria-selected', 'false');
self.trigger('results:focus', { self.trigger('results:focus', {
data: data, data: data,
@ -1245,7 +1281,7 @@ S2.define('select2/results',[
return; return;
} }
var $options = this.$results.find('[aria-selected]'); var $options = this.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted); var currentIndex = $options.index($highlighted);
@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[
BaseSelection.prototype.render = function () { BaseSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="select2-selection" role="combobox" ' + '<span class="select2-selection" ' +
' aria-haspopup="true" aria-expanded="false">' + ' aria-haspopup="true" aria-expanded="false">' +
'</span>' '</span>'
); );
@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[
var id = container.id + '-container'; var id = container.id + '-container';
var resultsId = container.id + '-results'; var resultsId = container.id + '-results';
var searchHidden = this.options.get('minimumResultsForSearch') === Infinity;
this.container = container; this.container = container;
@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[
self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-activedescendant');
self.$selection.removeAttr('aria-owns'); self.$selection.removeAttr('aria-owns');
// This needs to be delayed as the active element is the body when the
// key is pressed.
window.setTimeout(function () {
self.$selection.focus(); self.$selection.focus();
}, 1);
self._detachCloseHandler(container); self._detachCloseHandler(container);
}); });
@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[
} }
var $element = $this.data('element'); var $element = $this.data('element');
$element.select2('close'); $element.select2('close');
// Remove any focus when dropdown is closed by clicking outside the select area.
// Timeout of 1 required for close to finish wrapping up.
setTimeout(function(){
$this.find('*:focus').blur();
$target.focus();
}, 1);
}); });
}); });
}; };
@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[
var id = container.id + '-container'; var id = container.id + '-container';
this.$selection.find('.select2-selection__rendered').attr('id', id); this.$selection.find('.select2-selection__rendered')
.attr('id', id)
.attr('role', 'textbox')
.attr('aria-readonly', 'true');
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id ); this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox');
this.$selection.on('mousedown', function (evt) { this.$selection.on('mousedown', function (evt) {
// Only respond to left clicks // Only respond to left clicks
@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[
// User focuses on the container // User focuses on the container
}); });
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
this.$selection.on('blur', function (evt) { this.$selection.on('blur', function (evt) {
// User exits the container // User exits the container
}); });
@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[
var selection = data[0]; var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered'); var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered); var formatted = Utils.entityDecode(this.display(selection, $rendered));
$rendered.empty().append(formatted); $rendered.empty().text(formatted);
$rendered.prop('title', selection.title || selection.text); $rendered.prop('title', selection.title || selection.text);
}; };
@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[
$selection.addClass('select2-selection--multiple'); $selection.addClass('select2-selection--multiple');
$selection.html( $selection.html(
'<ul class="select2-selection__rendered"></ul>' '<ul class="select2-selection__rendered" aria-live="polite" aria-relevant="additions removals" aria-atomic="true"></ul>'
); );
return $selection; return $selection;
@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[
}); });
} }
); );
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
// Focus on the search field when the container is focused instead of the main container.
container.on( 'focus', function(){
self.focusOnSearch();
});
}; };
MultipleSelection.prototype.clear = function () { MultipleSelection.prototype.clear = function () {
@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="select2-selection__choice">' + '<li class="select2-selection__choice">' +
'<span class="select2-selection__choice__remove" role="presentation">' + '<span class="select2-selection__choice__remove" role="presentation" aria-hidden="true">' +
'&times;' + '&times;' +
'</span>' + '</span>' +
'</li>' '</li>'
@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[
return $container; return $container;
}; };
/**
* Focus on the search field instead of the main multiselect container.
*/
MultipleSelection.prototype.focusOnSearch = function() {
var self = this;
if ('undefined' !== typeof self.$search) {
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
// Prevent the dropdown opening again when focused from this.
// This gets reset automatically when focus is triggered.
self._keyUpPrevented = true;
self.$search.focus();
}, 1);
}
}
MultipleSelection.prototype.update = function (data) { MultipleSelection.prototype.update = function (data) {
this.clear(); this.clear();
@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[
var selection = data[d]; var selection = data[d];
var $selection = this.selectionContainer(); var $selection = this.selectionContainer();
var removeItemTag = $selection.html();
var formatted = this.display(selection, $selection); var formatted = this.display(selection, $selection);
if ('string' === typeof formatted) {
formatted = Utils.entityDecode(formatted.trim());
}
$selection.append(formatted); $selection.text(formatted);
$selection.prepend(removeItemTag);
$selection.prop('title', selection.title || selection.text); $selection.prop('title', selection.title || selection.text);
$selection.data('data', selection); $selection.data('data', selection);
@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
var $placeholder = this.selectionContainer(); var $placeholder = this.selectionContainer();
$placeholder.html(this.display(placeholder)); $placeholder.text(Utils.entityDecode(this.display(placeholder)));
$placeholder.addClass('select2-selection__placeholder') $placeholder.addClass('select2-selection__placeholder')
.removeClass('select2-selection__choice'); .removeClass('select2-selection__choice');
@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[
Search.prototype.render = function (decorated) { Search.prototype.render = function (decorated) {
var $search = $( var $search = $(
'<li class="select2-search select2-search--inline">' + '<li class="select2-search select2-search--inline">' +
'<input class="select2-search__field" type="search" tabindex="-1"' + '<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' + ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" aria-autocomplete="list" />' + ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
'</li>' '</li>'
); );
@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[
Search.prototype.bind = function (decorated, container, $container) { Search.prototype.bind = function (decorated, container, $container) {
var self = this; var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container); decorated.call(this, container, $container);
container.on('open', function () { container.on('open', function () {
self.$search.attr('aria-owns', resultsId);
self.$search.trigger('focus'); self.$search.trigger('focus');
}); });
container.on('close', function () { container.on('close', function () {
self.$search.val(''); self.$search.val('');
self.$search.removeAttr('aria-activedescendant'); self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.trigger('focus'); self.$search.trigger('focus');
}); });
@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[
}); });
container.on('results:focus', function (params) { container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.id); self.$search.attr('aria-activedescendant', params.data._resultId);
}); });
this.$selection.on('focusin', '.select2-search--inline', function (evt) { this.$selection.on('focusin', '.select2-search--inline', function (evt) {
@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[
evt.preventDefault(); evt.preventDefault();
} }
} else if (evt.which === KEYS.ENTER) {
container.open();
evt.preventDefault();
} }
}); });
@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[
}; };
BaseAdapter.prototype.generateResultId = function (container, data) { BaseAdapter.prototype.generateResultId = function (container, data) {
var id = container.id + '-result-'; var id = '';
if (container != null) {
id += container.id
} else {
id += Utils.generateChars(4);
}
id += '-result-';
id += Utils.generateChars(4); id += Utils.generateChars(4);
if (data.id != null) { if (data.id != null) {
@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[
} }
} }
if (data.id) { if (data.id !== undefined) {
option.value = data.id; option.value = data.id;
} }
@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[
item.text = item.text.toString(); item.text = item.text.toString();
} }
if (item._resultId == null && item.id && this.container != null) { if (item._resultId == null && item.id) {
item._resultId = this.generateResultId(this.container, item); item._resultId = this.generateResultId(this.container, item);
} }
@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[
} }
callback(results); callback(results);
self.container.focusOnActiveElement();
}, function () { }, function () {
// Attempt to detect if a request was aborted // Attempt to detect if a request was aborted
// Only works if the transport exposes a status property // Only works if the transport exposes a status property
@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[
}, true) }, true)
); );
var checkText = option.text === params.term; var optionText = (option.text || '').toUpperCase();
var paramsTerm = (params.term || '').toUpperCase();
var checkText = optionText === paramsTerm;
if (checkText || checkChildren) { if (checkText || checkChildren) {
if (child) { if (child) {
@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[
var $search = $( var $search = $(
'<span class="select2-search select2-search--dropdown">' + '<span class="select2-search select2-search--dropdown">' +
'<input class="select2-search__field" type="search" tabindex="-1"' + '<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' + ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" />' + ' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' +
'</span>' '</span>'
); );
@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[
Search.prototype.bind = function (decorated, container, $container) { Search.prototype.bind = function (decorated, container, $container) {
var self = this; var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container); decorated.call(this, container, $container);
@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[
container.on('open', function () { container.on('open', function () {
self.$search.attr('tabindex', 0); self.$search.attr('tabindex', 0);
self.$search.attr('aria-owns', resultsId);
self.$search.focus(); self.$search.focus();
window.setTimeout(function () { window.setTimeout(function () {
@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[
container.on('close', function () { container.on('close', function () {
self.$search.attr('tabindex', -1); self.$search.attr('tabindex', -1);
self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.val(''); self.$search.val('');
}); });
container.on('focus', function () { container.on('focus', function () {
if (container.isOpen()) { if (!container.isOpen()) {
self.$search.focus(); self.$search.focus();
} }
}); });
@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[
} }
} }
}); });
container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.data._resultId);
});
}; };
Search.prototype.handleSearch = function (evt) { Search.prototype.handleSearch = function (evt) {
@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[
var $option = $( var $option = $(
'<li ' + '<li ' +
'class="select2-results__option select2-results__option--load-more"' + 'class="select2-results__option select2-results__option--load-more"' +
'role="treeitem" aria-disabled="true"></li>' 'role="option" aria-disabled="true"></li>'
); );
var message = this.options.get('translations').get('loadingMore'); var message = this.options.get('translations').get('loadingMore');
@ -5344,16 +5469,22 @@ S2.define('select2/core',[
}); });
}); });
this.on('keypress', function (evt) { this.on('open', function(){
var key = evt.which; // Focus on the active element when opening dropdown.
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
self.focusOnActiveElement();
}, 1);
});
$(document).on('keydown', function (evt) {
var key = evt.which;
if (self.isOpen()) { if (self.isOpen()) {
if (key === KEYS.ESC || key === KEYS.TAB || if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) {
(key === KEYS.UP && evt.altKey)) {
self.close(); self.close();
evt.preventDefault(); evt.preventDefault();
} else if (key === KEYS.ENTER) { } else if (key === KEYS.ENTER || key === KEYS.TAB) {
self.trigger('results:select', {}); self.trigger('results:select', {});
evt.preventDefault(); evt.preventDefault();
@ -5370,17 +5501,42 @@ S2.define('select2/core',[
evt.preventDefault(); evt.preventDefault();
} }
} else {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
(key === KEYS.DOWN && evt.altKey)) {
self.open();
var $searchField = self.$dropdown.find('.select2-search__field');
if (! $searchField.length) {
$searchField = self.$container.find('.select2-search__field');
}
// Move the focus to the selected element on keyboard navigation.
// Required for screen readers to work properly.
if (key === KEYS.DOWN || key === KEYS.UP) {
self.focusOnActiveElement();
} else {
// Focus on the search if user starts typing.
$searchField.focus();
// Focus back to active selection when finished typing.
// Small delay so typed character can be read by screen reader.
setTimeout(function(){
self.focusOnActiveElement();
}, 1000);
}
} else if (self.hasFocus()) {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
key === KEYS.DOWN) {
self.open();
evt.preventDefault(); evt.preventDefault();
} }
} }
}); });
}; };
Select2.prototype.focusOnActiveElement = function () {
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
if (this.isOpen() && ! Utils.isTouchscreen()) {
this.$results.find('li.select2-results__option--highlighted').focus();
}
};
Select2.prototype._syncAttributes = function () { Select2.prototype._syncAttributes = function () {
this.options.set('disabled', this.$element.prop('disabled')); this.options.set('disabled', this.$element.prop('disabled'));
@ -6364,11 +6520,11 @@ S2.define('jquery.select2',[
'./select2/core', './select2/core',
'./select2/defaults' './select2/defaults'
], function ($, _, Select2, Defaults) { ], function ($, _, Select2, Defaults) {
if ($.fn.select2 == null) { if ($.fn.selectWoo == null) {
// All methods that should return the element // All methods that should return the element
var thisMethods = ['open', 'close', 'destroy']; var thisMethods = ['open', 'close', 'destroy'];
$.fn.select2 = function (options) { $.fn.selectWoo = function (options) {
options = options || {}; options = options || {};
if (typeof options === 'object') { if (typeof options === 'object') {
@ -6408,10 +6564,17 @@ S2.define('jquery.select2',[
}; };
} }
if ($.fn.select2.defaults == null) { if ($.fn.select2 != null && $.fn.select2.defaults != null) {
$.fn.select2.defaults = Defaults; $.fn.selectWoo.defaults = $.fn.select2.defaults;
} }
if ($.fn.selectWoo.defaults == null) {
$.fn.selectWoo.defaults = Defaults;
}
// Also register selectWoo under select2 if select2 is not already present.
$.fn.select2 = $.fn.select2 || $.fn.selectWoo;
return Select2; return Select2;
}); });
@ -6430,6 +6593,7 @@ S2.define('jquery.select2',[
// This allows Select2 to use the internal loader outside of this file, such // This allows Select2 to use the internal loader outside of this file, such
// as in the language files. // as in the language files.
jQuery.fn.select2.amd = S2; jQuery.fn.select2.amd = S2;
jQuery.fn.selectWoo.amd = S2;
// Return the Select2 instance for anyone who is importing it. // Return the Select2 instance for anyone who is importing it.
return select2; return select2;

View File

@ -1,17 +1,32 @@
/*! /*!
* Select2 4.0.3 * SelectWoo 1.0.9
* https://select2.github.io * https://github.com/woocommerce/selectWoo
* *
* Released under the MIT license * Released under the MIT license
* https://github.com/select2/select2/blob/master/LICENSE.md * https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md
*/ */
(function (factory) { (function (factory) {
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module. // AMD. Register as an anonymous module.
define(['jquery'], factory); define(['jquery'], factory);
} else if (typeof exports === 'object') { } else if (typeof module === 'object' && module.exports) {
// Node/CommonJS // Node/CommonJS
factory(require('jquery')); module.exports = function (root, jQuery) {
if (jQuery === undefined) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if (typeof window !== 'undefined') {
jQuery = require('jquery');
}
else {
jQuery = require('jquery')(root);
}
}
factory(jQuery);
return jQuery;
};
} else { } else {
// Browser globals // Browser globals
factory(jQuery); factory(jQuery);
@ -20,8 +35,7 @@
// This is needed so we can catch the AMD loader configuration and use it // This is needed so we can catch the AMD loader configuration and use it
// The inner file should be wrapped (by `banner.start.js`) in a function that // The inner file should be wrapped (by `banner.start.js`) in a function that
// returns the AMD loader references. // returns the AMD loader references.
var S2 = var S2 =(function () {
(function () {
// Restore the Select2 AMD loader so it can be used // Restore the Select2 AMD loader so it can be used
// Needed mostly in the language files, where the loader is not inserted // Needed mostly in the language files, where the loader is not inserted
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
@ -30,13 +44,11 @@
var S2;(function () { if (!S2 || !S2.requirejs) { var S2;(function () { if (!S2 || !S2.requirejs) {
if (!S2) { S2 = {}; } else { require = S2; } if (!S2) { S2 = {}; } else { require = S2; }
/** /**
* @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Available via the MIT or new BSD license. * Released under MIT license, http://github.com/requirejs/almond/LICENSE
* see: http://github.com/jrburke/almond for details
*/ */
//Going sloppy to avoid 'use strict' string cost, but strict practices should //Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed. //be followed.
/*jslint sloppy: true */
/*global setTimeout: false */ /*global setTimeout: false */
var requirejs, require, define; var requirejs, require, define;
@ -64,46 +76,49 @@ var requirejs, require, define;
*/ */
function normalize(name, baseName) { function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap, lastIndex, var nameParts, nameSegment, mapValue, foundMap, lastIndex,
foundI, foundStarMap, starI, i, j, part, foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
baseParts = baseName && baseName.split("/"), baseParts = baseName && baseName.split("/"),
map = config.map, map = config.map,
starMap = (map && map['*']) || {}; starMap = (map && map['*']) || {};
//Adjust any relative paths. //Adjust any relative paths.
if (name && name.charAt(0) === ".") { if (name) {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
name = name.split('/'); name = name.split('/');
lastIndex = name.length - 1; lastIndex = name.length - 1;
// Node .js allowance: // If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
} }
//Lop off the last part of baseParts, so that . matches the // Starts with a '.' so need the baseName
//"directory" and not name of the baseName's module. For instance, if (name[0].charAt(0) === '.' && baseParts) {
//baseName of "one/two/three", maps to "one/two/three.js", but we //Convert baseName to array, and lop off the last part,
//want the directory, "one/two" for this normalization. //so that . matches that 'directory' and not name of the baseName's
name = baseParts.slice(0, baseParts.length - 1).concat(name); //module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
//start trimDots //start trimDots
for (i = 0; i < name.length; i += 1) { for (i = 0; i < name.length; i++) {
part = name[i]; part = name[i];
if (part === ".") { if (part === '.') {
name.splice(i, 1); name.splice(i, 1);
i -= 1; i -= 1;
} else if (part === "..") { } else if (part === '..') {
if (i === 1 && (name[2] === '..' || name[0] === '..')) { // If at the start, or previous value is still ..,
//End of the line. Keep at least one non-dot // keep them so that when converted to a path it may
//path segment at the front so it can be mapped // still work when converted to a path, even though
//correctly to disk. Otherwise, there is likely // as an ID it is less than ideal. In larger point
//no path mapping for a path starting with '..'. // releases, may be better to just kick out an error.
//This can still fail, but catches the most reasonable if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
//uses of .. continue;
break;
} else if (i > 0) { } else if (i > 0) {
name.splice(i - 1, 2); name.splice(i - 1, 2);
i -= 2; i -= 2;
@ -112,12 +127,7 @@ var requirejs, require, define;
} }
//end trimDots //end trimDots
name = name.join("/"); name = name.join('/');
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
} }
//Apply map config if available. //Apply map config if available.
@ -230,32 +240,39 @@ var requirejs, require, define;
return [prefix, name]; return [prefix, name];
} }
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/** /**
* Makes a name map, normalizing the name, and using a plugin * Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin * for normalization if necessary. Grabs a ref to plugin
* too, as an optimization. * too, as an optimization.
*/ */
makeMap = function (name, relName) { makeMap = function (name, relParts) {
var plugin, var plugin,
parts = splitPrefix(name), parts = splitPrefix(name),
prefix = parts[0]; prefix = parts[0],
relResourceName = relParts[1];
name = parts[1]; name = parts[1];
if (prefix) { if (prefix) {
prefix = normalize(prefix, relName); prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix); plugin = callDep(prefix);
} }
//Normalize according //Normalize according
if (prefix) { if (prefix) {
if (plugin && plugin.normalize) { if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName)); name = plugin.normalize(name, makeNormalize(relResourceName));
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
} }
} else { } else {
name = normalize(name, relName); name = normalize(name, relResourceName);
parts = splitPrefix(name); parts = splitPrefix(name);
prefix = parts[0]; prefix = parts[0];
name = parts[1]; name = parts[1];
@ -302,13 +319,14 @@ var requirejs, require, define;
}; };
main = function (name, deps, callback, relName) { main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, var cjsModule, depName, ret, map, i, relParts,
args = [], args = [],
callbackType = typeof callback, callbackType = typeof callback,
usingExports; usingExports;
//Use name if no relName //Use name if no relName
relName = relName || name; relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary. //Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') { if (callbackType === 'undefined' || callbackType === 'function') {
@ -317,7 +335,7 @@ var requirejs, require, define;
//Default to [require, exports, module] if no deps //Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) { for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName); map = makeMap(deps[i], relParts);
depName = map.f; depName = map.f;
//Fast path CommonJS standard dependencies. //Fast path CommonJS standard dependencies.
@ -373,7 +391,7 @@ var requirejs, require, define;
//deps arg is the module name, and second arg (if passed) //deps arg is the module name, and second arg (if passed)
//is just the relName. //is just the relName.
//Normalize module name, if it contains . or .. //Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f); return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) { } else if (!deps.splice) {
//deps is a config object, not an array. //deps is a config object, not an array.
config = deps; config = deps;
@ -737,6 +755,12 @@ S2.define('select2/utils',[
}); });
}; };
Utils.entityDecode = function (html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
// Append an array of jQuery nodes to a given element. // Append an array of jQuery nodes to a given element.
Utils.appendMany = function ($element, $nodes) { Utils.appendMany = function ($element, $nodes) {
// jQuery 1.7.x does not support $.fn.append() with an array // jQuery 1.7.x does not support $.fn.append() with an array
@ -754,6 +778,14 @@ S2.define('select2/utils',[
$element.append($nodes); $element.append($nodes);
}; };
// Determine whether the browser is on a touchscreen device.
Utils.isTouchscreen = function() {
if ('undefined' === typeof Utils._isTouchscreenCache) {
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
}
return Utils._isTouchscreenCache;
}
return Utils; return Utils;
}); });
@ -773,7 +805,7 @@ S2.define('select2/results',[
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="select2-results__options" role="tree"></ul>' '<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
); );
if (this.options.get('multiple')) { if (this.options.get('multiple')) {
@ -796,7 +828,7 @@ S2.define('select2/results',[
this.hideLoading(); this.hideLoading();
var $message = $( var $message = $(
'<li role="treeitem" aria-live="assertive"' + '<li role="alert" aria-live="assertive"' +
' class="select2-results__option"></li>' ' class="select2-results__option"></li>'
); );
@ -858,9 +890,9 @@ S2.define('select2/results',[
Results.prototype.highlightFirstItem = function () { Results.prototype.highlightFirstItem = function () {
var $options = this.$results var $options = this.$results
.find('.select2-results__option[aria-selected]'); .find('.select2-results__option[data-selected]');
var $selected = $options.filter('[aria-selected=true]'); var $selected = $options.filter('[data-selected=true]');
// Check if there are any selected options // Check if there are any selected options
if ($selected.length > 0) { if ($selected.length > 0) {
@ -884,7 +916,7 @@ S2.define('select2/results',[
}); });
var $options = self.$results var $options = self.$results
.find('.select2-results__option[aria-selected]'); .find('.select2-results__option[data-selected]');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
@ -896,9 +928,9 @@ S2.define('select2/results',[
if ((item.element != null && item.element.selected) || if ((item.element != null && item.element.selected) ||
(item.element == null && $.inArray(id, selectedIds) > -1)) { (item.element == null && $.inArray(id, selectedIds) > -1)) {
$option.attr('aria-selected', 'true'); $option.attr('data-selected', 'true');
} else { } else {
$option.attr('aria-selected', 'false'); $option.attr('data-selected', 'false');
} }
}); });
@ -930,17 +962,18 @@ S2.define('select2/results',[
option.className = 'select2-results__option'; option.className = 'select2-results__option';
var attrs = { var attrs = {
'role': 'treeitem', 'role': 'option',
'aria-selected': 'false' 'data-selected': 'false',
'tabindex': -1
}; };
if (data.disabled) { if (data.disabled) {
delete attrs['aria-selected']; delete attrs['data-selected'];
attrs['aria-disabled'] = 'true'; attrs['aria-disabled'] = 'true';
} }
if (data.id == null) { if (data.id == null) {
delete attrs['aria-selected']; delete attrs['data-selected'];
} }
if (data._resultId != null) { if (data._resultId != null) {
@ -952,9 +985,8 @@ S2.define('select2/results',[
} }
if (data.children) { if (data.children) {
attrs.role = 'group';
attrs['aria-label'] = data.text; attrs['aria-label'] = data.text;
delete attrs['aria-selected']; delete attrs['data-selected'];
} }
for (var attr in attrs) { for (var attr in attrs) {
@ -971,6 +1003,7 @@ S2.define('select2/results',[
var $label = $(label); var $label = $(label);
this.template(data, label); this.template(data, label);
$label.attr('role', 'presentation');
var $children = []; var $children = [];
@ -983,10 +1016,11 @@ S2.define('select2/results',[
} }
var $childrenContainer = $('<ul></ul>', { var $childrenContainer = $('<ul></ul>', {
'class': 'select2-results__options select2-results__options--nested' 'class': 'select2-results__options select2-results__options--nested',
'role': 'listbox'
}); });
$childrenContainer.append($children); $childrenContainer.append($children);
$option.attr('role', 'list');
$option.append(label); $option.append(label);
$option.append($childrenContainer); $option.append($childrenContainer);
@ -1082,7 +1116,7 @@ S2.define('select2/results',[
var data = $highlighted.data('data'); var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') { if ($highlighted.attr('data-selected') == 'true') {
self.trigger('close', {}); self.trigger('close', {});
} else { } else {
self.trigger('select', { self.trigger('select', {
@ -1094,7 +1128,7 @@ S2.define('select2/results',[
container.on('results:previous', function () { container.on('results:previous', function () {
var $highlighted = self.getHighlightedResults(); var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]'); var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted); var currentIndex = $options.index($highlighted);
@ -1128,7 +1162,7 @@ S2.define('select2/results',[
container.on('results:next', function () { container.on('results:next', function () {
var $highlighted = self.getHighlightedResults(); var $highlighted = self.getHighlightedResults();
var $options = self.$results.find('[aria-selected]'); var $options = self.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted); var currentIndex = $options.index($highlighted);
@ -1156,7 +1190,8 @@ S2.define('select2/results',[
}); });
container.on('results:focus', function (params) { container.on('results:focus', function (params) {
params.element.addClass('select2-results__option--highlighted'); params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true');
self.$results.attr('aria-activedescendant', params.element.attr('id'));
}); });
container.on('results:message', function (params) { container.on('results:message', function (params) {
@ -1188,13 +1223,13 @@ S2.define('select2/results',[
}); });
} }
this.$results.on('mouseup', '.select2-results__option[aria-selected]', this.$results.on('mouseup', '.select2-results__option[data-selected]',
function (evt) { function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.attr('aria-selected') === 'true') { if ($this.attr('data-selected') === 'true') {
if (self.options.get('multiple')) { if (self.options.get('multiple')) {
self.trigger('unselect', { self.trigger('unselect', {
originalEvent: evt, originalEvent: evt,
@ -1213,12 +1248,13 @@ S2.define('select2/results',[
}); });
}); });
this.$results.on('mouseenter', '.select2-results__option[aria-selected]', this.$results.on('mouseenter', '.select2-results__option[data-selected]',
function (evt) { function (evt) {
var data = $(this).data('data'); var data = $(this).data('data');
self.getHighlightedResults() self.getHighlightedResults()
.removeClass('select2-results__option--highlighted'); .removeClass('select2-results__option--highlighted')
.attr('aria-selected', 'false');
self.trigger('results:focus', { self.trigger('results:focus', {
data: data, data: data,
@ -1245,7 +1281,7 @@ S2.define('select2/results',[
return; return;
} }
var $options = this.$results.find('[aria-selected]'); var $options = this.$results.find('[data-selected]');
var currentIndex = $options.index($highlighted); var currentIndex = $options.index($highlighted);
@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[
BaseSelection.prototype.render = function () { BaseSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="select2-selection" role="combobox" ' + '<span class="select2-selection" ' +
' aria-haspopup="true" aria-expanded="false">' + ' aria-haspopup="true" aria-expanded="false">' +
'</span>' '</span>'
); );
@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[
var id = container.id + '-container'; var id = container.id + '-container';
var resultsId = container.id + '-results'; var resultsId = container.id + '-results';
var searchHidden = this.options.get('minimumResultsForSearch') === Infinity;
this.container = container; this.container = container;
@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[
self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-activedescendant');
self.$selection.removeAttr('aria-owns'); self.$selection.removeAttr('aria-owns');
// This needs to be delayed as the active element is the body when the
// key is pressed.
window.setTimeout(function () {
self.$selection.focus(); self.$selection.focus();
}, 1);
self._detachCloseHandler(container); self._detachCloseHandler(container);
}); });
@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[
} }
var $element = $this.data('element'); var $element = $this.data('element');
$element.select2('close'); $element.select2('close');
// Remove any focus when dropdown is closed by clicking outside the select area.
// Timeout of 1 required for close to finish wrapping up.
setTimeout(function(){
$this.find('*:focus').blur();
$target.focus();
}, 1);
}); });
}); });
}; };
@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[
var id = container.id + '-container'; var id = container.id + '-container';
this.$selection.find('.select2-selection__rendered').attr('id', id); this.$selection.find('.select2-selection__rendered')
.attr('id', id)
.attr('role', 'textbox')
.attr('aria-readonly', 'true');
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id ); this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox');
this.$selection.on('mousedown', function (evt) { this.$selection.on('mousedown', function (evt) {
// Only respond to left clicks // Only respond to left clicks
@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[
// User focuses on the container // User focuses on the container
}); });
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
this.$selection.on('blur', function (evt) { this.$selection.on('blur', function (evt) {
// User exits the container // User exits the container
}); });
@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[
var selection = data[0]; var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered'); var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered); var formatted = Utils.entityDecode(this.display(selection, $rendered));
$rendered.empty().append(formatted); $rendered.empty().text(formatted);
$rendered.prop('title', selection.title || selection.text); $rendered.prop('title', selection.title || selection.text);
}; };
@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[
$selection.addClass('select2-selection--multiple'); $selection.addClass('select2-selection--multiple');
$selection.html( $selection.html(
'<ul class="select2-selection__rendered"></ul>' '<ul class="select2-selection__rendered" aria-live="polite" aria-relevant="additions removals" aria-atomic="true"></ul>'
); );
return $selection; return $selection;
@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[
}); });
} }
); );
this.$selection.on('keydown', function (evt) {
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
container.open();
}
});
// Focus on the search field when the container is focused instead of the main container.
container.on( 'focus', function(){
self.focusOnSearch();
});
}; };
MultipleSelection.prototype.clear = function () { MultipleSelection.prototype.clear = function () {
@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="select2-selection__choice">' + '<li class="select2-selection__choice">' +
'<span class="select2-selection__choice__remove" role="presentation">' + '<span class="select2-selection__choice__remove" role="presentation" aria-hidden="true">' +
'&times;' + '&times;' +
'</span>' + '</span>' +
'</li>' '</li>'
@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[
return $container; return $container;
}; };
/**
* Focus on the search field instead of the main multiselect container.
*/
MultipleSelection.prototype.focusOnSearch = function() {
var self = this;
if ('undefined' !== typeof self.$search) {
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
// Prevent the dropdown opening again when focused from this.
// This gets reset automatically when focus is triggered.
self._keyUpPrevented = true;
self.$search.focus();
}, 1);
}
}
MultipleSelection.prototype.update = function (data) { MultipleSelection.prototype.update = function (data) {
this.clear(); this.clear();
@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[
var selection = data[d]; var selection = data[d];
var $selection = this.selectionContainer(); var $selection = this.selectionContainer();
var removeItemTag = $selection.html();
var formatted = this.display(selection, $selection); var formatted = this.display(selection, $selection);
if ('string' === typeof formatted) {
formatted = Utils.entityDecode(formatted.trim());
}
$selection.append(formatted); $selection.text(formatted);
$selection.prepend(removeItemTag);
$selection.prop('title', selection.title || selection.text); $selection.prop('title', selection.title || selection.text);
$selection.data('data', selection); $selection.data('data', selection);
@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
var $placeholder = this.selectionContainer(); var $placeholder = this.selectionContainer();
$placeholder.html(this.display(placeholder)); $placeholder.text(Utils.entityDecode(this.display(placeholder)));
$placeholder.addClass('select2-selection__placeholder') $placeholder.addClass('select2-selection__placeholder')
.removeClass('select2-selection__choice'); .removeClass('select2-selection__choice');
@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[
Search.prototype.render = function (decorated) { Search.prototype.render = function (decorated) {
var $search = $( var $search = $(
'<li class="select2-search select2-search--inline">' + '<li class="select2-search select2-search--inline">' +
'<input class="select2-search__field" type="search" tabindex="-1"' + '<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' + ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" aria-autocomplete="list" />' + ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
'</li>' '</li>'
); );
@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[
Search.prototype.bind = function (decorated, container, $container) { Search.prototype.bind = function (decorated, container, $container) {
var self = this; var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container); decorated.call(this, container, $container);
container.on('open', function () { container.on('open', function () {
self.$search.attr('aria-owns', resultsId);
self.$search.trigger('focus'); self.$search.trigger('focus');
}); });
container.on('close', function () { container.on('close', function () {
self.$search.val(''); self.$search.val('');
self.$search.removeAttr('aria-activedescendant'); self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.trigger('focus'); self.$search.trigger('focus');
}); });
@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[
}); });
container.on('results:focus', function (params) { container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.id); self.$search.attr('aria-activedescendant', params.data._resultId);
}); });
this.$selection.on('focusin', '.select2-search--inline', function (evt) { this.$selection.on('focusin', '.select2-search--inline', function (evt) {
@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[
evt.preventDefault(); evt.preventDefault();
} }
} else if (evt.which === KEYS.ENTER) {
container.open();
evt.preventDefault();
} }
}); });
@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[
}; };
BaseAdapter.prototype.generateResultId = function (container, data) { BaseAdapter.prototype.generateResultId = function (container, data) {
var id = container.id + '-result-'; var id = '';
if (container != null) {
id += container.id
} else {
id += Utils.generateChars(4);
}
id += '-result-';
id += Utils.generateChars(4); id += Utils.generateChars(4);
if (data.id != null) { if (data.id != null) {
@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[
} }
} }
if (data.id) { if (data.id !== undefined) {
option.value = data.id; option.value = data.id;
} }
@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[
item.text = item.text.toString(); item.text = item.text.toString();
} }
if (item._resultId == null && item.id && this.container != null) { if (item._resultId == null && item.id) {
item._resultId = this.generateResultId(this.container, item); item._resultId = this.generateResultId(this.container, item);
} }
@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[
} }
callback(results); callback(results);
self.container.focusOnActiveElement();
}, function () { }, function () {
// Attempt to detect if a request was aborted // Attempt to detect if a request was aborted
// Only works if the transport exposes a status property // Only works if the transport exposes a status property
@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[
}, true) }, true)
); );
var checkText = option.text === params.term; var optionText = (option.text || '').toUpperCase();
var paramsTerm = (params.term || '').toUpperCase();
var checkText = optionText === paramsTerm;
if (checkText || checkChildren) { if (checkText || checkChildren) {
if (child) { if (child) {
@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[
var $search = $( var $search = $(
'<span class="select2-search select2-search--dropdown">' + '<span class="select2-search select2-search--dropdown">' +
'<input class="select2-search__field" type="search" tabindex="-1"' + '<input class="select2-search__field" type="text" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="off"' + ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" />' + ' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' +
'</span>' '</span>'
); );
@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[
Search.prototype.bind = function (decorated, container, $container) { Search.prototype.bind = function (decorated, container, $container) {
var self = this; var self = this;
var resultsId = container.id + '-results';
decorated.call(this, container, $container); decorated.call(this, container, $container);
@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[
container.on('open', function () { container.on('open', function () {
self.$search.attr('tabindex', 0); self.$search.attr('tabindex', 0);
self.$search.attr('aria-owns', resultsId);
self.$search.focus(); self.$search.focus();
window.setTimeout(function () { window.setTimeout(function () {
@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[
container.on('close', function () { container.on('close', function () {
self.$search.attr('tabindex', -1); self.$search.attr('tabindex', -1);
self.$search.removeAttr('aria-activedescendant');
self.$search.removeAttr('aria-owns');
self.$search.val(''); self.$search.val('');
}); });
container.on('focus', function () { container.on('focus', function () {
if (container.isOpen()) { if (!container.isOpen()) {
self.$search.focus(); self.$search.focus();
} }
}); });
@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[
} }
} }
}); });
container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.data._resultId);
});
}; };
Search.prototype.handleSearch = function (evt) { Search.prototype.handleSearch = function (evt) {
@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[
var $option = $( var $option = $(
'<li ' + '<li ' +
'class="select2-results__option select2-results__option--load-more"' + 'class="select2-results__option select2-results__option--load-more"' +
'role="treeitem" aria-disabled="true"></li>' 'role="option" aria-disabled="true"></li>'
); );
var message = this.options.get('translations').get('loadingMore'); var message = this.options.get('translations').get('loadingMore');
@ -5344,16 +5469,22 @@ S2.define('select2/core',[
}); });
}); });
this.on('keypress', function (evt) { this.on('open', function(){
var key = evt.which; // Focus on the active element when opening dropdown.
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
self.focusOnActiveElement();
}, 1);
});
$(document).on('keydown', function (evt) {
var key = evt.which;
if (self.isOpen()) { if (self.isOpen()) {
if (key === KEYS.ESC || key === KEYS.TAB || if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) {
(key === KEYS.UP && evt.altKey)) {
self.close(); self.close();
evt.preventDefault(); evt.preventDefault();
} else if (key === KEYS.ENTER) { } else if (key === KEYS.ENTER || key === KEYS.TAB) {
self.trigger('results:select', {}); self.trigger('results:select', {});
evt.preventDefault(); evt.preventDefault();
@ -5370,17 +5501,42 @@ S2.define('select2/core',[
evt.preventDefault(); evt.preventDefault();
} }
} else {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
(key === KEYS.DOWN && evt.altKey)) {
self.open();
var $searchField = self.$dropdown.find('.select2-search__field');
if (! $searchField.length) {
$searchField = self.$container.find('.select2-search__field');
}
// Move the focus to the selected element on keyboard navigation.
// Required for screen readers to work properly.
if (key === KEYS.DOWN || key === KEYS.UP) {
self.focusOnActiveElement();
} else {
// Focus on the search if user starts typing.
$searchField.focus();
// Focus back to active selection when finished typing.
// Small delay so typed character can be read by screen reader.
setTimeout(function(){
self.focusOnActiveElement();
}, 1000);
}
} else if (self.hasFocus()) {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
key === KEYS.DOWN) {
self.open();
evt.preventDefault(); evt.preventDefault();
} }
} }
}); });
}; };
Select2.prototype.focusOnActiveElement = function () {
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
if (this.isOpen() && ! Utils.isTouchscreen()) {
this.$results.find('li.select2-results__option--highlighted').focus();
}
};
Select2.prototype._syncAttributes = function () { Select2.prototype._syncAttributes = function () {
this.options.set('disabled', this.$element.prop('disabled')); this.options.set('disabled', this.$element.prop('disabled'));
@ -5653,11 +5809,11 @@ S2.define('jquery.select2',[
'./select2/core', './select2/core',
'./select2/defaults' './select2/defaults'
], function ($, _, Select2, Defaults) { ], function ($, _, Select2, Defaults) {
if ($.fn.select2 == null) { if ($.fn.selectWoo == null) {
// All methods that should return the element // All methods that should return the element
var thisMethods = ['open', 'close', 'destroy']; var thisMethods = ['open', 'close', 'destroy'];
$.fn.select2 = function (options) { $.fn.selectWoo = function (options) {
options = options || {}; options = options || {};
if (typeof options === 'object') { if (typeof options === 'object') {
@ -5697,10 +5853,17 @@ S2.define('jquery.select2',[
}; };
} }
if ($.fn.select2.defaults == null) { if ($.fn.select2 != null && $.fn.select2.defaults != null) {
$.fn.select2.defaults = Defaults; $.fn.selectWoo.defaults = $.fn.select2.defaults;
} }
if ($.fn.selectWoo.defaults == null) {
$.fn.selectWoo.defaults = Defaults;
}
// Also register selectWoo under select2 if select2 is not already present.
$.fn.select2 = $.fn.select2 || $.fn.selectWoo;
return Select2; return Select2;
}); });
@ -5719,6 +5882,7 @@ S2.define('jquery.select2',[
// This allows Select2 to use the internal loader outside of this file, such // This allows Select2 to use the internal loader outside of this file, such
// as in the language files. // as in the language files.
jQuery.fn.select2.amd = S2; jQuery.fn.select2.amd = S2;
jQuery.fn.selectWoo.amd = S2;
// Return the Select2 instance for anyone who is importing it. // Return the Select2 instance for anyone who is importing it.
return select2; return select2;

View File

@ -1,5 +1,5 @@
/*! /*!
* SelectWoo 1.0.6 * SelectWoo 1.0.9
* https://github.com/woocommerce/selectWoo * https://github.com/woocommerce/selectWoo
* *
* Released under the MIT license * Released under the MIT license
@ -756,7 +756,7 @@ S2.define('select2/utils',[
}; };
Utils.entityDecode = function (html) { Utils.entityDecode = function (html) {
var txt = document.createElement("textarea"); var txt = document.createElement('textarea');
txt.innerHTML = html; txt.innerHTML = html;
return txt.value; return txt.value;
} }
@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[
.attr('id', id) .attr('id', id)
.attr('role', 'textbox') .attr('role', 'textbox')
.attr('aria-readonly', 'true'); .attr('aria-readonly', 'true');
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id ); this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove. // This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox'); this.$selection.attr('role', 'combobox');
@ -4398,6 +4405,7 @@ S2.define('select2/dropdown/attachBody',[
var parentOffset = $offsetParent.offset(); var parentOffset = $offsetParent.offset();
css.top -= parentOffset.top;
css.left -= parentOffset.left; css.left -= parentOffset.left;
if (!isCurrentlyAbove && !isCurrentlyBelow) { if (!isCurrentlyAbove && !isCurrentlyBelow) {
@ -4412,7 +4420,7 @@ S2.define('select2/dropdown/attachBody',[
if (newDirection == 'above' || if (newDirection == 'above' ||
(isCurrentlyAbove && newDirection !== 'below')) { (isCurrentlyAbove && newDirection !== 'below')) {
css.top = container.top - dropdown.height; css.top = container.top - parentOffset.top - dropdown.height;
} }
if (newDirection != null) { if (newDirection != null) {

View File

@ -1,5 +1,5 @@
/*! /*!
* SelectWoo 1.0.6 * SelectWoo 1.0.9
* https://github.com/woocommerce/selectWoo * https://github.com/woocommerce/selectWoo
* *
* Released under the MIT license * Released under the MIT license
@ -756,7 +756,7 @@ S2.define('select2/utils',[
}; };
Utils.entityDecode = function (html) { Utils.entityDecode = function (html) {
var txt = document.createElement("textarea"); var txt = document.createElement('textarea');
txt.innerHTML = html; txt.innerHTML = html;
return txt.value; return txt.value;
} }
@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[
.attr('id', id) .attr('id', id)
.attr('role', 'textbox') .attr('role', 'textbox')
.attr('aria-readonly', 'true'); .attr('aria-readonly', 'true');
var label = this.options.get( 'label' );
if ( typeof( label ) === 'string' ) {
this.$selection.attr( 'aria-label', label );
} else {
this.$selection.attr( 'aria-labelledby', id ); this.$selection.attr( 'aria-labelledby', id );
}
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove. // This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
this.$selection.attr('role', 'combobox'); this.$selection.attr('role', 'combobox');

View File

@ -430,7 +430,10 @@ class WC_Admin_Post_Types {
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$product->set_manage_stock( $manage_stock ); $product->set_manage_stock( $manage_stock );
if ( 'external' !== $product->get_type() ) {
$product->set_backorders( $backorders ); $product->set_backorders( $backorders );
}
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
$stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : ''; $stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : '';
@ -550,7 +553,10 @@ class WC_Admin_Post_Types {
$stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity(); $stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
$product->set_manage_stock( $manage_stock ); $product->set_manage_stock( $manage_stock );
if ( 'external' !== $product->get_type() ) {
$product->set_backorders( $backorders ); $product->set_backorders( $backorders );
}
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
$change_stock = absint( $request_data['change_stock'] ); $change_stock = absint( $request_data['change_stock'] );

View File

@ -224,7 +224,7 @@ class WC_Frontend_Scripts {
'selectWoo' => array( 'selectWoo' => array(
'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '1.0.6', 'version' => '1.0.9',
), ),
'wc-address-i18n' => array( 'wc-address-i18n' => array(
'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ),

View File

@ -2753,8 +2753,9 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
$field .= '<input type="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . current( array_keys( $countries ) ) . '" ' . implode( ' ', $custom_attributes ) . ' class="country_to_state" readonly="readonly" />'; $field .= '<input type="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . current( array_keys( $countries ) ) . '" ' . implode( ' ', $custom_attributes ) . ' class="country_to_state" readonly="readonly" />';
} else { } else {
$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region&hellip;', 'woocommerce' ) ) . '"><option value="">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>'; $field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region&hellip;', 'woocommerce' ) ) . '" ' . $data_label . '><option value="">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
foreach ( $countries as $ckey => $cvalue ) { foreach ( $countries as $ckey => $cvalue ) {
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>'; $field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';
@ -2779,8 +2780,9 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
$field .= '<input type="hidden" class="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="" ' . implode( ' ', $custom_attributes ) . ' placeholder="' . esc_attr( $args['placeholder'] ) . '" readonly="readonly" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"/>'; $field .= '<input type="hidden" class="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="" ' . implode( ' ', $custom_attributes ) . ' placeholder="' . esc_attr( $args['placeholder'] ) . '" readonly="readonly" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"/>';
} elseif ( ! is_null( $for_country ) && is_array( $states ) ) { } elseif ( ! is_null( $for_country ) && is_array( $states ) ) {
$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';
$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="state_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option&hellip;', 'woocommerce' ) ) . '" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"> $field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="state_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option&hellip;', 'woocommerce' ) ) . '" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . $data_label . '>
<option value="">' . esc_html__( 'Select an option&hellip;', 'woocommerce' ) . '</option>'; <option value="">' . esc_html__( 'Select an option&hellip;', 'woocommerce' ) . '</option>';
foreach ( $states as $ckey => $cvalue ) { foreach ( $states as $ckey => $cvalue ) {

View File

@ -10,36 +10,80 @@ const {
createSimpleProduct, createSimpleProduct,
addProductToOrder, addProductToOrder,
clickUpdateOrder, clickUpdateOrder,
factories,
selectOptionInSelect2,
} = require( '@woocommerce/e2e-utils' ); } = require( '@woocommerce/e2e-utils' );
const searchString = 'John Doe';
const customerBilling = {
first_name: 'John',
last_name: 'Doe',
company: 'Automattic',
country: 'US',
address_1: 'address1',
address_2: 'address2',
city: 'San Francisco',
state: 'CA',
postcode: '94107',
phone: '123456789',
email: 'john.doe@example.com',
};
const customerShipping = {
first_name: 'Tim',
last_name: 'Clark',
company: 'Automattic',
country: 'US',
address_1: 'Oxford Ave',
address_2: 'Linwood Ave',
city: 'Buffalo',
state: 'NY',
postcode: '14201',
phone: '123456789',
email: 'john.doe@example.com',
};
/**
* Set the billing fields for the customer account for this test suite.
*
* @returns {Promise<void>}
*/
const updateCustomerBilling = async () => {
const client = factories.api.withDefaultPermalinks;
const customerEndpoint = 'wc/v3/customers/';
const customers = await client.get( customerEndpoint, {
search: 'Jane',
role: 'all',
} );
if ( ! customers.data | ! customers.data.length ) {
return;
}
const customerId = customers.data[0].id;
const customerData = {
id: customerId,
billing: customerBilling,
shipping: customerShipping,
};
await client.put( customerEndpoint + customerId, customerData );
};
const runOrderSearchingTest = () => { const runOrderSearchingTest = () => {
describe('WooCommerce Orders > Search orders', () => { describe('WooCommerce Orders > Search orders', () => {
let orderId; let orderId;
beforeAll( async () => { beforeAll( async () => {
await merchant.login();
await createSimpleProduct('Wanted Product'); await createSimpleProduct('Wanted Product');
await updateCustomerBilling();
// Create new order for testing // Create new order for testing
await merchant.login();
await merchant.openNewOrder(); await merchant.openNewOrder();
await page.waitForSelector('#order_status'); await page.waitForSelector('#order_status');
await page.click('#customer_user'); await page.click('#customer_user');
await page.click('span.select2-search > input.select2-search__field'); await page.click('span.select2-search > input.select2-search__field');
await page.type('span.select2-search > input.select2-search__field', 'Customer'); await page.type('span.select2-search > input.select2-search__field', 'Jane Smith');
await page.waitFor(2000); // to avoid flakyness await page.waitFor(2000); // to avoid flakyness
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
// Change the shipping data
await page.waitFor(1000); // to avoid flakiness
await page.click('.billing-same-as-shipping');
await page.keyboard.press('Enter');
await page.waitForSelector('#_shipping_first_name');
await clearAndFillInput('#_shipping_first_name', 'Tim');
await clearAndFillInput('#_shipping_last_name', 'Clark');
await clearAndFillInput('#_shipping_address_1', 'Oxford Ave');
await clearAndFillInput('#_shipping_address_2', 'Linwood Ave');
await clearAndFillInput('#_shipping_city', 'Buffalo');
await clearAndFillInput('#_shipping_postcode', '14201');
// Get the post id // Get the post id
const variablePostId = await page.$('#post_ID'); const variablePostId = await page.$('#post_ID');
orderId = (await(await variablePostId.getProperty('value')).jsonValue()); orderId = (await(await variablePostId.getProperty('value')).jsonValue());
@ -53,79 +97,82 @@ const runOrderSearchingTest = () => {
}); });
it('can search for order by order id', async () => { it('can search for order by order id', async () => {
await searchForOrder(orderId, orderId, 'John Doe'); await searchForOrder(orderId, orderId, searchString);
}); });
it('can search for order by billing first name', async () => { it('can search for order by billing first name', async () => {
await searchForOrder('John', orderId, 'John Doe'); await searchForOrder(customerBilling.first_name, orderId, searchString);
}) })
it('can search for order by billing last name', async () => { it('can search for order by billing last name', async () => {
await searchForOrder('Doe', orderId, 'John Doe'); await searchForOrder(customerBilling.last_name, orderId, searchString);
}) })
it('can search for order by billing company name', async () => { it('can search for order by billing company name', async () => {
await searchForOrder('Automattic', orderId, 'John Doe'); await searchForOrder(customerBilling.company, orderId, searchString);
}) })
it('can search for order by billing first address', async () => { it('can search for order by billing first address', async () => {
await searchForOrder('addr 1', orderId, 'John Doe'); await searchForOrder(customerBilling.address_1, orderId, searchString);
}) })
it('can search for order by billing second address', async () => { it('can search for order by billing second address', async () => {
await searchForOrder('addr 2', orderId, 'John Doe'); await searchForOrder(customerBilling.address_2, orderId, searchString);
}) })
it('can search for order by billing city name', async () => { it('can search for order by billing city name', async () => {
await searchForOrder('San Francisco', orderId, 'John Doe'); await searchForOrder(customerBilling.city, orderId, searchString);
}) })
it('can search for order by billing post code', async () => { it('can search for order by billing post code', async () => {
await searchForOrder('94107', orderId, 'John Doe'); await searchForOrder(customerBilling.postcode, orderId, searchString);
}) })
it('can search for order by billing email', async () => { it('can search for order by billing email', async () => {
await searchForOrder('john.doe@example.com', orderId, 'John Doe'); await searchForOrder(customerBilling.email, orderId, searchString);
}) })
it('can search for order by billing phone', async () => { it('can search for order by billing phone', async () => {
await searchForOrder('123456789', orderId, 'John Doe'); await searchForOrder(customerBilling.phone, orderId, searchString);
}) })
it('can search for order by billing state', async () => { it('can search for order by billing state', async () => {
await searchForOrder('CA', orderId, 'John Doe'); await searchForOrder(customerBilling.state, orderId, searchString);
}) })
it('can search for order by shipping first name', async () => { it('can search for order by shipping first name', async () => {
await searchForOrder('Tim', orderId, 'John Doe'); await searchForOrder(customerShipping.first_name, orderId, searchString);
}) })
it('can search for order by shipping last name', async () => { it('can search for order by shipping last name', async () => {
await searchForOrder('Clark', orderId, 'John Doe'); await searchForOrder(customerShipping.last_name, orderId, searchString);
}) })
it('can search for order by shipping first address', async () => { it('can search for order by shipping first address', async () => {
await searchForOrder('Oxford Ave', orderId, 'John Doe'); await searchForOrder(customerShipping.address_1, orderId, searchString);
}) })
it('can search for order by shipping second address', async () => { it('can search for order by shipping second address', async () => {
await searchForOrder('Linwood Ave', orderId, 'John Doe'); await searchForOrder(customerShipping.address_2, orderId, searchString);
}) })
it('can search for order by shipping city name', async () => { it('can search for order by shipping city name', async () => {
await searchForOrder('Buffalo', orderId, 'John Doe'); await searchForOrder(customerShipping.city, orderId, searchString);
}) })
it('can search for order by shipping postcode name', async () => { it('can search for order by shipping postcode name', async () => {
await searchForOrder('14201', orderId, 'John Doe'); await searchForOrder(customerShipping.postcode, orderId, searchString);
}) })
it('can search for order by shipping state name', async () => { /**
await searchForOrder('CA', orderId, 'John Doe'); * shipping state is abbreviated. This test passes if billing and shipping state are the same
*/
it.skip('can search for order by shipping state name', async () => {
await searchForOrder('New York', orderId, searchString);
}) })
it('can search for order by item name', async () => { it('can search for order by item name', async () => {
await searchForOrder('Wanted Product', orderId, 'John Doe'); await searchForOrder('Wanted Product', orderId, searchString);
}) })
}); });
}; };

View File

@ -61,6 +61,8 @@ const runCartCalculateShippingTest = () => {
await page.select('select[name="add_method_id"]', 'local_pickup'); await page.select('select[name="add_method_id"]', 'local_pickup');
await page.click('button#btn-ok'); await page.click('button#btn-ok');
await page.waitForSelector('#zone_locations'); await page.waitForSelector('#zone_locations');
await merchant.logout();
await shopper.emptyCart();
}); });
it('allows customer to calculate Free Shipping if in Germany', async () => { it('allows customer to calculate Free Shipping if in Germany', async () => {

View File

@ -4,7 +4,12 @@ echo "Initializing WooCommerce E2E"
wp plugin activate woocommerce wp plugin activate woocommerce
wp theme install twentynineteen --activate wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=password --role=subscriber --path=/var/www/html wp user create customer customer@woocommercecoree2etestsuite.com \
--user_pass=password \
--role=subscriber \
--first_name='Jane' \
--last_name='Smith' \
--path=/var/www/html
# we cannot create API keys for the API, so we using basic auth, this plugin allows that. # we cannot create API keys for the API, so we using basic auth, this plugin allows that.
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate

View File

@ -43,7 +43,10 @@ if ( appPath ) {
if ( ! fs.existsSync( customInitFile ) ) { if ( ! fs.existsSync( customInitFile ) ) {
customInitFile = ''; customInitFile = '';
} }
} else {
customInitFile = '';
} }
const appInitFile = customInitFile ? customInitFile : path.resolve( appPath, 'tests/e2e/docker/initialize.sh' ); const appInitFile = customInitFile ? customInitFile : path.resolve( appPath, 'tests/e2e/docker/initialize.sh' );
// If found, copy it into the wp-cli Docker context so // If found, copy it into the wp-cli Docker context so
// it gets picked up by the entrypoint script. // it gets picked up by the entrypoint script.

View File

@ -236,6 +236,10 @@ export const searchForOrder = async (value, orderId, customerName) => {
*/ */
export const applyCoupon = async ( couponCode ) => { export const applyCoupon = async ( couponCode ) => {
try { try {
await Promise.all([
page.reload(),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
]);
await expect(page).toClick('a', {text: 'Click here to enter your code'}); await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked(); await uiUnblocked();
await clearAndFillInput('#coupon_code', couponCode); await clearAndFillInput('#coupon_code', couponCode);
@ -255,6 +259,10 @@ export const applyCoupon = async ( couponCode ) => {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export const removeCoupon = async ( couponCode ) => { export const removeCoupon = async ( couponCode ) => {
await Promise.all([
page.reload(),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
]);
await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'}); await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'});
await uiUnblocked(); await uiUnblocked();
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});

View File

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce * Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/ * Plugin URI: https://woocommerce.com/
* Description: An eCommerce toolkit that helps you sell anything. Beautifully. * Description: An eCommerce toolkit that helps you sell anything. Beautifully.
* Version: 5.3.0-dev * Version: 5.4.0-dev
* Author: Automattic * Author: Automattic
* Author URI: https://woocommerce.com * Author URI: https://woocommerce.com
* Text Domain: woocommerce * Text Domain: woocommerce