Skip to content

Commit 94e4e87

Browse files
authored
feat(dropdown,search): highlightMatches option
This PR adds a new option highlightMatches to dropdown and search module Customizing the mark (background)-color is prepared in the LESS files for a custom theme, but disabled by default. (as the default would do nothing anyway) I decided to leave this to each browsers defaults as its visuals are most familiar to usual browser search and the mark HTML tag uses the exact same styling. Also fixed some typo inside search.less Also made ignoreSearchCase available for search as we have the same for dropdown already
1 parent 6c16b5d commit 94e4e87

File tree

8 files changed

+125
-21
lines changed

8 files changed

+125
-21
lines changed

src/definitions/modules/dropdown.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -890,11 +890,13 @@
890890
? query
891891
: module.get.query()
892892
),
893-
results = null,
894-
escapedTerm = module.escape.string(searchTerm),
895-
regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm',
893+
results = null,
894+
escapedTerm = module.escape.string(searchTerm),
895+
regExpIgnore = settings.ignoreSearchCase ? 'i' : '',
896+
regExpFlags = regExpIgnore + 'gm',
896897
beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags)
897898
;
899+
module.remove.filteredItem();
898900
// avoid loop if we're matching nothing
899901
if (module.has.query()) {
900902
results = [];
@@ -938,12 +940,34 @@
938940
;
939941
}
940942
module.debug('Showing only matched items', searchTerm);
941-
module.remove.filteredItem();
942943
if (results) {
943944
$item
944945
.not(results)
945946
.addClass(className.filtered)
946947
;
948+
if (settings.highlightMatches && (settings.match === 'both' || settings.match === 'text')) {
949+
var querySplit = query.split(''),
950+
diacriticReg = settings.ignoreDiacritics ? '[\u0300-\u036F]?' : '',
951+
htmlReg = '(?![^<]*>)',
952+
markedRegExp = new RegExp(htmlReg + '(' + querySplit.join(diacriticReg + ')(.*?)' + htmlReg + '(') + diacriticReg + ')', regExpIgnore),
953+
markedReplacer = function () {
954+
var args = [].slice.call(arguments, 1, querySplit.length * 2).map(function (x, i) {
955+
return i & 1 ? x : '<mark>' + x + '</mark>'; // eslint-disable-line no-bitwise
956+
});
957+
958+
return args.join('');
959+
}
960+
;
961+
$.each(results, function (index, result) {
962+
var $result = $(result),
963+
markedHTML = module.get.choiceText($result, true)
964+
;
965+
if (settings.ignoreDiacritics) {
966+
markedHTML = markedHTML.normalize('NFD');
967+
}
968+
$result.html(markedHTML.replace(markedRegExp, markedReplacer));
969+
});
970+
}
947971
}
948972

949973
if (!module.has.query()) {
@@ -979,8 +1003,10 @@
9791003
termLength = term.length,
9801004
queryLength = query.length
9811005
;
982-
query = settings.ignoreSearchCase ? query.toLowerCase() : query;
983-
term = settings.ignoreSearchCase ? term.toLowerCase() : term;
1006+
if (settings.ignoreSearchCase) {
1007+
query = query.toLowerCase();
1008+
term = term.toLowerCase();
1009+
}
9841010
if (queryLength > termLength) {
9851011
return false;
9861012
}
@@ -3084,6 +3110,12 @@
30843110
$item.removeClass(className.active);
30853111
},
30863112
filteredItem: function () {
3113+
if (settings.highlightMatches) {
3114+
$.each($item, function (index, item) {
3115+
var $markItem = $(item);
3116+
$markItem.html($markItem.html().replace(/<\/?mark>/g, ''));
3117+
});
3118+
}
30873119
if (settings.useLabels && module.has.maxSelections()) {
30883120
return;
30893121
}
@@ -3809,8 +3841,7 @@
38093841
;
38103842
if (shouldEscape.test(string)) {
38113843
string = string.replace(forceAmpersand ? /&/g : /&(?![\d#a-z]{1,12};)/gi, '&amp;');
3812-
3813-
return string.replace(badChars, escapedChar);
3844+
string = string.replace(badChars, escapedChar);
38143845
}
38153846

38163847
return string;
@@ -4015,6 +4046,7 @@
40154046

40164047
match: 'both', // what to match against with search selection (both, text, or label)
40174048
fullTextSearch: 'exact', // search anywhere in value (set to 'exact' to require exact matches)
4049+
highlightMatches: false, // Whether search result should highlight matching strings
40184050
ignoreDiacritics: false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...)
40194051
hideDividers: false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item)
40204052

@@ -4239,8 +4271,7 @@
42394271
;
42404272
if (shouldEscape.test(string)) {
42414273
string = string.replace(/&(?![\d#a-z]{1,12};)/gi, '&amp;');
4242-
4243-
return string.replace(badChars, escapedChar);
4274+
string = string.replace(badChars, escapedChar);
42444275
}
42454276

42464277
return string;

src/definitions/modules/dropdown.less

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,13 @@ select.ui.dropdown {
18601860
});
18611861
}
18621862

1863+
& when (@variationDropdownHighlightMatches) {
1864+
.ui.dropdown .menu > .item mark {
1865+
background: @highlightMatchesBackground;
1866+
color: @highlightMatchesColor;
1867+
}
1868+
}
1869+
18631870
& when (@variationDropdownInverted) {
18641871
/* --------------
18651872
Inverted

src/definitions/modules/search.js

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@
135135
// this makes sure $.extend does not add specified search fields to default fields
136136
// this is the only setting which should not extend defaults
137137
if (parameters && parameters.searchFields !== undefined) {
138-
settings.searchFields = parameters.searchFields;
138+
settings.searchFields = Array.isArray(parameters.searchFields)
139+
? parameters.searchFields
140+
: [parameters.searchFields]
141+
;
139142
}
140143
},
141144
},
@@ -631,7 +634,7 @@
631634
exactResults = [],
632635
fuzzyResults = [],
633636
searchExp = searchTerm.replace(regExp.escape, '\\$&'),
634-
matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'),
637+
matchRegExp = new RegExp(regExp.beginsWith + searchExp, settings.ignoreSearchCase ? 'i' : ''),
635638

636639
// avoid duplicates when pushing results
637640
addResult = function (array, result) {
@@ -667,13 +670,14 @@
667670
var concatenatedContent = [];
668671
$.each(searchFields, function (index, field) {
669672
var
670-
fieldExists = (typeof content[field] === 'string') || (typeof content[field] === 'number')
673+
fieldExists = typeof content[field] === 'string' || typeof content[field] === 'number'
671674
;
672675
if (fieldExists) {
673676
var text;
674677
text = typeof content[field] === 'string'
675678
? module.remove.diacritics(content[field])
676679
: content[field].toString();
680+
text = $('<div/>', { html: text }).text().trim();
677681
if (settings.fullTextSearch === 'all') {
678682
concatenatedContent.push(text);
679683
if (index < lastSearchFieldIndex) {
@@ -704,8 +708,10 @@
704708
},
705709
},
706710
exactSearch: function (query, term) {
707-
query = query.toLowerCase();
708-
term = term.toLowerCase();
711+
if (settings.ignoreSearchCase) {
712+
query = query.toLowerCase();
713+
term = term.toLowerCase();
714+
}
709715

710716
return term.indexOf(query) > -1;
711717
},
@@ -732,8 +738,10 @@
732738
if (typeof query !== 'string') {
733739
return false;
734740
}
735-
query = query.toLowerCase();
736-
term = term.toLowerCase();
741+
if (settings.ignoreSearchCase) {
742+
query = query.toLowerCase();
743+
term = term.toLowerCase();
744+
}
737745
if (queryLength > termLength) {
738746
return false;
739747
}
@@ -1088,6 +1096,39 @@
10881096
response[fields.results] = response[fields.results].slice(0, settings.maxResults);
10891097
}
10901098
}
1099+
if (settings.highlightMatches) {
1100+
var results = response[fields.results],
1101+
regExpIgnore = settings.ignoreSearchCase ? 'i' : '',
1102+
querySplit = module.get.value().split(''),
1103+
diacriticReg = settings.ignoreDiacritics ? '[\u0300-\u036F]?' : '',
1104+
htmlReg = '(?![^<]*>)',
1105+
markedRegExp = new RegExp(htmlReg + '(' + querySplit.join(diacriticReg + ')(.*?)' + htmlReg + '(') + diacriticReg + ')', regExpIgnore),
1106+
markedReplacer = function () {
1107+
var args = [].slice.call(arguments, 1, querySplit.length * 2).map(function (x, i) {
1108+
return i & 1 ? x : '<mark>' + x + '</mark>'; // eslint-disable-line no-bitwise
1109+
});
1110+
1111+
return args.join('');
1112+
}
1113+
;
1114+
$.each(results, function (label, content) {
1115+
$.each(settings.searchFields, function (index, field) {
1116+
var
1117+
fieldExists = typeof content[field] === 'string' || typeof content[field] === 'number'
1118+
;
1119+
if (fieldExists) {
1120+
var markedHTML = typeof content[field] === 'string'
1121+
? content[field]
1122+
: content[field].toString();
1123+
if (settings.ignoreDiacritics) {
1124+
markedHTML = markedHTML.normalize('NFD');
1125+
}
1126+
markedHTML = markedHTML.replace(/<\/?mark>/g, '');
1127+
response[fields.results][label][field] = markedHTML.replace(markedRegExp, markedReplacer);
1128+
}
1129+
});
1130+
});
1131+
}
10911132
if (isFunction(template)) {
10921133
html = template(response, fields, settings.preserveHTML);
10931134
} else {
@@ -1316,9 +1357,15 @@
13161357
// search anywhere in value (set to 'exact' to require exact matches
13171358
fullTextSearch: 'exact',
13181359

1360+
// Whether search result should highlight matching strings
1361+
highlightMatches: false,
1362+
13191363
// match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...)
13201364
ignoreDiacritics: false,
13211365

1366+
// whether to consider case sensitivity on local searching
1367+
ignoreSearchCase: true,
1368+
13221369
// whether to add events to prompt automatically
13231370
automatic: true,
13241371

@@ -1436,8 +1483,9 @@
14361483
};
14371484
if (shouldEscape.test(string)) {
14381485
string = string.replace(/&(?![\d#a-z]{1,12};)/gi, '&amp;');
1439-
1440-
return string.replace(badChars, escapedChar);
1486+
string = string.replace(badChars, escapedChar);
1487+
// FUI controlled HTML is still allowed
1488+
string = string.replace(/&lt;(\/)*mark&gt;/g, '<$1mark>');
14411489
}
14421490

14431491
return string;

src/definitions/modules/search.less

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,15 +565,22 @@
565565
.ui.search {
566566
font-size: @relativeMedium;
567567
}
568-
& when not (@variationFeedSizes = false) {
569-
each(@variationFeedSizes, {
568+
& when not (@variationSearchSizes = false) {
569+
each(@variationSearchSizes, {
570570
@s: @{value}SearchSize;
571571
.ui.@{value}.search {
572572
font-size: @@s;
573573
}
574574
});
575575
}
576576

577+
& when (@variationSearchHighlightMatches) {
578+
.ui.search > .results mark {
579+
background: @highlightMatchesBackground;
580+
color: @highlightMatchesColor;
581+
}
582+
}
583+
577584
/* --------------
578585
Mobile
579586
--------------- */

src/themes/default/globals/site.variables

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,3 +1538,6 @@
15381538

15391539
@inputWarningPlaceholderColor: if(iscolor(@formWarningColor), lighten(@formWarningColor, 40), @formWarningColor);
15401540
@inputWarningPlaceholderFocusColor: if(iscolor(@formWarningColor), lighten(@formWarningColor, 30), @formWarningColor);
1541+
1542+
@defaultHighlightMatchesBackground: revert;
1543+
@defaultHighlightMatchesColor: revert;

src/themes/default/globals/variation.variables

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@
573573
@variationDropdownPointing: true;
574574
@variationDropdownColumnar: true;
575575
@variationDropdownScrollhint: true;
576+
@variationDropdownHighlightMatches: false;
576577
@variationDropdownSizes: @variationAllSizes;
577578

578579
/* Embed */
@@ -678,6 +679,7 @@
678679
@variationSearchVeryLong: true;
679680
@variationSearchResizable: true;
680681
@variationSearchScrolling: true;
682+
@variationSearchHighlightMatches: false;
681683
@variationSearchSizes: @variationAllSizes;
682684

683685
/* Shape */

src/themes/default/modules/dropdown.variables

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,6 @@
480480

481481
/* Resizable */
482482
@resizableDirection: vertical;
483+
484+
@highlightMatchesBackground: @defaultHighlightMatchesBackground;
485+
@highlightMatchesColor: @defaultHighlightMatchesColor;

src/themes/default/modules/search.variables

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,6 @@
177177

178178
/* Resizable */
179179
@resizableDirection: vertical;
180+
181+
@highlightMatchesBackground: @defaultHighlightMatchesBackground;
182+
@highlightMatchesColor: @defaultHighlightMatchesColor;

0 commit comments

Comments
 (0)