@rescui/select 0.20.1 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
1
  var styles = {
2
- "icon": "_icon_622m6a_4",
3
- "sizeXS": "_sizeXS_622m6a_14",
4
- "sizeS": "_sizeS_622m6a_18",
5
- "sizeM": "_sizeM_622m6a_22",
6
- "sizeL": "_sizeL_622m6a_26",
7
- "light": "_light_622m6a_30",
8
- "dark": "_dark_622m6a_34",
9
- "allVariants": "_allVariants_622m6a_48",
10
- "variant16": "_variant16_622m6a_53",
11
- "variant20": "_variant20_622m6a_54",
12
- "variant24": "_variant24_622m6a_55",
13
- "variant28": "_variant28_622m6a_58"
2
+ "icon": "_icon_g46ng3_4",
3
+ "sizeXS": "_sizeXS_g46ng3_14",
4
+ "sizeS": "_sizeS_g46ng3_18",
5
+ "sizeM": "_sizeM_g46ng3_22",
6
+ "sizeL": "_sizeL_g46ng3_26",
7
+ "light": "_light_g46ng3_30",
8
+ "dark": "_dark_g46ng3_34",
9
+ "allVariants": "_allVariants_g46ng3_48",
10
+ "variant16": "_variant16_g46ng3_53",
11
+ "variant20": "_variant20_g46ng3_54",
12
+ "variant24": "_variant24_g46ng3_55",
13
+ "variant28": "_variant28_g46ng3_58"
14
14
  };
15
15
 
16
16
  export { styles as default };
@@ -3,7 +3,8 @@ import type { SelectOptionOrGroup, SelectOption, ListBoxItem } from '../types';
3
3
  /**
4
4
  * Convert an array of options with possible groups to the flat array of options, stripping them of the groups.
5
5
  * Also creating a list for ListBox component with indexes and keys.
6
- * Supports filtering by returning the setFilterString function.
6
+ * Supports filtering by returning the setFilterString function: options that
7
+ * contain the filter string are kept, the ones starting with it go first.
7
8
  * @param options
8
9
  */
9
10
  export declare const useOptions: (options: SelectOptionOrGroup[]) => {
@@ -4,20 +4,92 @@ import { isOptionAGroup } from '../utils/is-option-a-group.js';
4
4
  import { getTextFromNodes } from '../utils/get-text-from-nodes.js';
5
5
  import { getOptionValue } from '../utils/get-option-value.js';
6
6
 
7
- const createFilter = substring => {
7
+ const RANK_ANY = 0;
8
+ const RANK_LABEL_START = 0;
9
+ const RANK_WORD_START = 1;
10
+ const RANK_SUBSTRING = 2;
11
+ const WORD_CHAR = /[\p{L}\p{N}]/u;
12
+ const createRankFn = substring => {
8
13
  if (substring.length === 0) {
9
- return () => true;
14
+ return () => RANK_ANY;
10
15
  }
11
16
  const lowerSubstring = substring.toLowerCase();
12
- return string => {
13
- return string.toLowerCase().includes(lowerSubstring);
17
+ return label => {
18
+ const lowerLabel = label.toLowerCase();
19
+ let matchIndex = lowerLabel.indexOf(lowerSubstring);
20
+ if (matchIndex === -1) {
21
+ return null;
22
+ }
23
+ if (matchIndex === 0) {
24
+ return RANK_LABEL_START;
25
+ }
26
+ while (matchIndex !== -1) {
27
+ if (!WORD_CHAR.test(lowerLabel[matchIndex - 1])) {
28
+ return RANK_WORD_START;
29
+ }
30
+ matchIndex = lowerLabel.indexOf(lowerSubstring, matchIndex + 1);
31
+ }
32
+ return RANK_SUBSTRING;
14
33
  };
15
34
  };
16
- const processOptions = (options, filterFn) => {
35
+ const rankOptions = (options, rankFn) => {
36
+ const ranked = [];
37
+ options.forEach(option => {
38
+ const rank = rankFn(getTextFromNodes(option.label));
39
+ if (rank !== null) {
40
+ ranked.push({
41
+ option,
42
+ rank
43
+ });
44
+ }
45
+ });
46
+ // Array.prototype.sort is stable, so the original order is kept within a rank
47
+ return ranked.sort((a, b) => a.rank - b.rank);
48
+ };
49
+ const processOptions = (options, rankFn) => {
17
50
  const flattenedOptions = [];
18
51
  const listBoxItems = [];
52
+ // Filter and rank first: options inside a group are sorted by their rank,
53
+ // top-level entries (options and groups) are sorted by their best rank.
54
+ const rankedEntries = [];
55
+ options.forEach((optionOrGroup, arrayIndex) => {
56
+ if (isOptionAGroup(optionOrGroup)) {
57
+ const group = optionOrGroup;
58
+ if (!group.options || group.options.length === 0) {
59
+ return;
60
+ }
61
+ const rankedGroupOptions = rankOptions(group.options, rankFn);
62
+ if (rankedGroupOptions.length > 0) {
63
+ rankedEntries.push({
64
+ type: 'group',
65
+ group,
66
+ arrayIndex,
67
+ options: rankedGroupOptions.map(_ref => {
68
+ let {
69
+ option
70
+ } = _ref;
71
+ return option;
72
+ }),
73
+ rank: rankedGroupOptions[0].rank
74
+ });
75
+ }
76
+ return;
77
+ }
78
+ const option = optionOrGroup;
79
+ const rank = rankFn(getTextFromNodes(option.label));
80
+ if (rank !== null) {
81
+ rankedEntries.push({
82
+ type: 'option',
83
+ option,
84
+ rank
85
+ });
86
+ }
87
+ });
88
+ rankedEntries.sort((a, b) => a.rank - b.rank);
89
+ // Indexes are assigned after sorting to match the order of flattenedOptions
19
90
  let index = 0;
20
91
  const createOptionItem = option => {
92
+ flattenedOptions.push(option);
21
93
  return {
22
94
  ...option,
23
95
  index: index++,
@@ -25,12 +97,12 @@ const processOptions = (options, filterFn) => {
25
97
  key: getOptionValue(option)
26
98
  };
27
99
  };
28
- options.forEach((optionOrGroup, arrayIndex) => {
29
- if (isOptionAGroup(optionOrGroup)) {
30
- const group = optionOrGroup;
31
- if (!group.options || group.options.length === 0) {
32
- return;
33
- }
100
+ rankedEntries.forEach(entry => {
101
+ if (entry.type === 'group') {
102
+ const {
103
+ group,
104
+ arrayIndex
105
+ } = entry;
34
106
  // Render as Group (with or without label)
35
107
  const groupLabelText = group.label ? getTextFromNodes(group.label) : '';
36
108
  // Use a special prefix that's unlikely to collide with user values
@@ -38,24 +110,13 @@ const processOptions = (options, filterFn) => {
38
110
  const listBoxGroupItem = {
39
111
  ...group,
40
112
  type: 'group',
41
- options: [],
113
+ options: entry.options.map(createOptionItem),
42
114
  key: groupKey
43
115
  };
44
- group.options.forEach(option => {
45
- if (filterFn(getTextFromNodes(option.label))) {
46
- flattenedOptions.push(option);
47
- listBoxGroupItem.options.push(createOptionItem(option));
48
- }
49
- });
50
- if (listBoxGroupItem.options.length > 0) {
51
- listBoxItems.push(listBoxGroupItem);
52
- }
116
+ listBoxItems.push(listBoxGroupItem);
53
117
  return;
54
118
  }
55
- if (filterFn(getTextFromNodes(optionOrGroup.label))) {
56
- flattenedOptions.push(optionOrGroup);
57
- listBoxItems.push(createOptionItem(optionOrGroup));
58
- }
119
+ listBoxItems.push(createOptionItem(entry.option));
59
120
  });
60
121
  return {
61
122
  flattenedOptions,
@@ -65,7 +126,8 @@ const processOptions = (options, filterFn) => {
65
126
  /**
66
127
  * Convert an array of options with possible groups to the flat array of options, stripping them of the groups.
67
128
  * Also creating a list for ListBox component with indexes and keys.
68
- * Supports filtering by returning the setFilterString function.
129
+ * Supports filtering by returning the setFilterString function: options that
130
+ * contain the filter string are kept, the ones starting with it go first.
69
131
  * @param options
70
132
  */
71
133
  const useOptions = options => {
@@ -73,18 +135,18 @@ const useOptions = options => {
73
135
  const [filterString, setFilterString] = useState("");
74
136
  let t0;
75
137
  if ($[0] !== filterString) {
76
- t0 = createFilter(filterString);
138
+ t0 = createRankFn(filterString);
77
139
  $[0] = filterString;
78
140
  $[1] = t0;
79
141
  } else {
80
142
  t0 = $[1];
81
143
  }
82
- const filterFn = t0;
144
+ const rankFn = t0;
83
145
  let t1;
84
- if ($[2] !== filterFn || $[3] !== options) {
85
- t1 = processOptions(options, filterFn);
86
- $[2] = filterFn;
87
- $[3] = options;
146
+ if ($[2] !== options || $[3] !== rankFn) {
147
+ t1 = processOptions(options, rankFn);
148
+ $[2] = options;
149
+ $[3] = rankFn;
88
150
  $[4] = t1;
89
151
  } else {
90
152
  t1 = $[4];