Skip to content

Commit f21bee8

Browse files
authored
FIO-8548: Update choices.js to 11 version (#5976)
* FIO-8548: updated choices.js to 11 version * FIO-8548: updated @formio/choices.js version to latest, fixed issue with tags component * FIO-8548: replaced @formio/choices.js with choices.js
1 parent 553f139 commit f21bee8

File tree

9 files changed

+1142
-1482
lines changed

9 files changed

+1142
-1482
lines changed

gulpfile.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ gulp.task('styles-embed', function embedStyles() {
5555
});
5656
gulp.task('styles-form', function formStyles() {
5757
return compileStyles([
58-
'./node_modules/@formio/choices.js/public/assets/styles/choices.css',
58+
'./node_modules/choices.js/public/assets/styles/choices.css',
5959
'./node_modules/tippy.js/dist/tippy.css',
6060
'./node_modules/dialog-polyfill/dialog-polyfill.css',
6161
'./src/sass/formio.form.scss'
6262
], 'formio.form');
6363
});
6464
gulp.task('styles-builder', function builderStyles() {
6565
return compileStyles([
66-
'./node_modules/@formio/choices.js/public/assets/styles/choices.css',
66+
'./node_modules/choices.js/public/assets/styles/choices.css',
6767
'./node_modules/tippy.js/dist/tippy.css',
6868
'./node_modules/dialog-polyfill/dialog-polyfill.css',
6969
'./node_modules/dragula/dist/dragula.css',
@@ -73,7 +73,7 @@ gulp.task('styles-builder', function builderStyles() {
7373
});
7474
gulp.task('styles-full', gulp.series('builder-fonts', function fullStyles() {
7575
return compileStyles([
76-
'./node_modules/@formio/choices.js/public/assets/styles/choices.css',
76+
'./node_modules/choices.js/public/assets/styles/choices.css',
7777
'./node_modules/tippy.js/dist/tippy.css',
7878
'./node_modules/dialog-polyfill/dialog-polyfill.css',
7979
'./node_modules/dragula/dist/dragula.css',

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
"homepage": "https://git.1-hub.cnformio/formio.js#readme",
8282
"dependencies": {
8383
"@formio/bootstrap": "v3.0.0-dev.121.085d187",
84-
"@formio/choices.js": "^10.2.1",
8584
"@formio/core": "v2.4.0-dev.2",
8685
"@formio/text-mask-addons": "3.8.0-formio.4",
8786
"@formio/vanilla-text-mask": "^5.1.1-formio.1",
@@ -90,6 +89,7 @@
9089
"bootstrap": "^5.3.3",
9190
"browser-cookies": "^1.2.0",
9291
"browser-md5-file": "^1.1.1",
92+
"choices.js": "^11.0.6",
9393
"compare-versions": "^6.1.1",
9494
"core-js": "^3.37.1",
9595
"dialog-polyfill": "^0.5.6",

src/components/select/Select.js

+3-25
Original file line numberDiff line numberDiff line change
@@ -905,8 +905,8 @@ export default class SelectComponent extends ListComponent {
905905
removeItemButton: this.component.disabled ? false : _.get(this.component, 'removeItemButton', true),
906906
itemSelectText: '',
907907
classNames: {
908-
containerOuter: 'choices form-group formio-choices',
909-
containerInner: this.transform('class', 'form-control ui fluid selection dropdown')
908+
containerOuter: ['choices', 'form-group', 'formio-choices'],
909+
containerInner: this.transform('class', 'form-control ui fluid selection dropdown').split(' '),
910910
},
911911
addItemText: false,
912912
allowHTML: true,
@@ -938,6 +938,7 @@ export default class SelectComponent extends ListComponent {
938938
),
939939
valueComparer: _.isEqual,
940940
resetScrollPosition: false,
941+
duplicateItemsAllowed: false,
941942
...customOptions,
942943
};
943944
}
@@ -1097,14 +1098,6 @@ export default class SelectComponent extends ListComponent {
10971098
});
10981099
}
10991100

1100-
if (this.choices && choicesOptions.placeholderValue && this.choices._isSelectOneElement) {
1101-
this.addPlaceholderItem(choicesOptions.placeholderValue);
1102-
1103-
this.addEventListener(input, 'removeItem', () => {
1104-
this.addPlaceholderItem(choicesOptions.placeholderValue);
1105-
});
1106-
}
1107-
11081101
// Add value options.
11091102
this.addValueOptions();
11101103
this.setChoicesValue(this.dataValue);
@@ -1208,21 +1201,6 @@ export default class SelectComponent extends ListComponent {
12081201
}
12091202
}
12101203

1211-
addPlaceholderItem(placeholderValue) {
1212-
const items = this.choices._store.activeItems;
1213-
if (!items.length) {
1214-
this.choices._addItem({
1215-
value: '',
1216-
label: placeholderValue,
1217-
choiceId: 0,
1218-
groupId: -1,
1219-
customProperties: null,
1220-
placeholder: true,
1221-
keyCode: null
1222-
});
1223-
}
1224-
}
1225-
12261204
/* eslint-enable max-statements */
12271205
update() {
12281206
if (this.component.dataSrc === 'custom') {

src/components/tags/Tags.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { componentValueTypes, getComponentSavedTypes } from '../../utils/utils';
22
import Input from '../_classes/input/Input';
3-
import Choices from '@formio/choices.js';
3+
import Choices from 'choices.js';
44

55
export default class TagsComponent extends Input {
66
static schema(...extend) {
@@ -146,7 +146,7 @@ export default class TagsComponent extends Input {
146146
const changed = super.setValue(value, flags);
147147
if (this.choices) {
148148
let dataValue = this.dataValue;
149-
this.choices.removeActiveItems();
149+
this.choices.clearStore();
150150
if (dataValue) {
151151
if (typeof dataValue === 'string') {
152152
dataValue = dataValue.split(this.delimiter).filter(result => result);

src/utils/ChoicesWrapper.js

+26-152
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,8 @@
1-
import Choices from '@formio/choices.js';
2-
3-
/**
4-
* TODO: REMOVE THIS ONCE THE PULL REQUEST HAS BEEN RESOLVED.
5-
*
6-
* https://git.1-hub.cnjshjohnson/Choices/pull/788
7-
*
8-
* This is intentionally not part of the extended class, since other components use Choices and need this fix as well.
9-
* @type {Choices._generatePlaceholderValue}
10-
* @private
11-
*/
12-
Choices.prototype._generatePlaceholderValue = function() {
13-
if (this._isSelectElement && this.passedElement.placeholderOption) {
14-
const { placeholderOption } = this.passedElement;
15-
return placeholderOption ? placeholderOption.text : false;
16-
}
17-
const { placeholder, placeholderValue } = this.config;
18-
const {
19-
element: { dataset },
20-
} = this.passedElement;
21-
22-
if (placeholder) {
23-
if (placeholderValue) {
24-
return placeholderValue;
25-
}
26-
27-
if (dataset.placeholder) {
28-
return dataset.placeholder;
29-
}
30-
}
1+
import Choices, { KeyCodeMap } from 'choices.js';
312

32-
return false;
33-
};
34-
35-
export const KEY_CODES = {
36-
BACK_KEY: 46,
37-
DELETE_KEY: 8,
3+
const ExtendedKeyCodeMap = {
4+
...KeyCodeMap,
385
TAB_KEY: 9,
39-
ENTER_KEY: 13,
40-
A_KEY: 65,
41-
ESC_KEY: 27,
42-
UP_KEY: 38,
43-
DOWN_KEY: 40,
44-
PAGE_UP_KEY: 33,
45-
PAGE_DOWN_KEY: 34,
466
};
477

488
class ChoicesWrapper extends Choices {
@@ -74,29 +34,13 @@ class ChoicesWrapper extends Choices {
7434
this._wasTap = true;
7535
}
7636

77-
_handleButtonAction(activeItems, element) {
78-
if (!this._isSelectOneElement) {
79-
return super._handleButtonAction(activeItems, element);
80-
}
81-
82-
if (
83-
!activeItems ||
84-
!element ||
85-
!this.config.removeItems ||
86-
!this.config.removeItemButton
87-
) {
88-
return;
89-
}
90-
91-
super._handleButtonAction(activeItems, element);
92-
}
93-
94-
_onEnterKey(args) {
37+
_onEnterKey(...args) {
38+
const [event] = args;
9539
// Prevent dropdown form opening when removeItemButton was pressed using 'Enter' on keyboard
96-
if (args.event.target.className === 'choices__button') {
40+
if (event.target.className === 'choices__button') {
9741
this.shouldOpenDropDown = false;
9842
}
99-
super._onEnterKey(args);
43+
super._onEnterKey(...args);
10044
}
10145

10246
_onDirectionKey(...args) {
@@ -116,21 +60,22 @@ class ChoicesWrapper extends Choices {
11660
}, 250);
11761
}
11862

119-
_onTabKey({ activeItems, hasActiveDropdown }) {
120-
if (hasActiveDropdown) {
121-
this._selectHighlightedChoice(activeItems);
63+
_onTabKey() {
64+
if (this.dropdown.isActive) {
65+
this._selectHighlightedChoice();
12266
}
12367
}
12468

12569
_selectHighlightedChoice() {
126-
const highlightedChoice = this.dropdown.getChild(
70+
const highlightedChoice = this.dropdown.element.querySelector(
12771
`.${this.config.classNames.highlightedState}`,
12872
);
12973

13074
if (highlightedChoice) {
13175
const id = highlightedChoice.dataset.id;
132-
const choice = id && this._store.getChoiceById(id);
76+
const choice = id && this._store.getChoiceById(Number(id));
13377
this._addItem({
78+
id: choice.id,
13479
value: choice.value,
13580
label: choice.label,
13681
choiceId: choice.id,
@@ -141,84 +86,18 @@ class ChoicesWrapper extends Choices {
14186
});
14287
this._triggerChange(choice.value);
14388
}
144-
145-
event.preventDefault();
14689
}
14790

14891
_onKeyDown(event) {
149-
if (!this._isSelectOneElement) {
150-
return super._onKeyDown(event);
151-
}
152-
153-
const { target, keyCode, ctrlKey, metaKey } = event;
154-
155-
if (
156-
target !== this.input.element &&
157-
!this.containerOuter.element.contains(target)
158-
) {
159-
return;
160-
}
161-
162-
const activeItems = this._store.activeItems;
163-
const hasFocusedInput = this.input.isFocussed;
164-
const hasActiveDropdown = this.dropdown.isActive;
165-
const hasItems = this.itemList.hasChildren;
166-
const keyString = String.fromCharCode(keyCode);
167-
168-
const {
169-
BACK_KEY,
170-
DELETE_KEY,
171-
TAB_KEY,
172-
ENTER_KEY,
173-
A_KEY,
174-
ESC_KEY,
175-
UP_KEY,
176-
DOWN_KEY,
177-
PAGE_UP_KEY,
178-
PAGE_DOWN_KEY,
179-
} = KEY_CODES;
180-
const hasCtrlDownKeyPressed = ctrlKey || metaKey;
181-
182-
// If a user is typing and the dropdown is not active
183-
if (!hasActiveDropdown && !this._isTextElement && /[a-zA-Z0-9-_ ]/.test(keyString)) {
184-
const currentValue = this.input.element.value;
185-
this.input.element.value = currentValue ? `${currentValue}${keyString}` : keyString;
186-
this.showDropdown();
187-
}
188-
189-
// Map keys to key actions
190-
const keyDownActions = {
191-
[A_KEY]: this._onAKey,
192-
[TAB_KEY]: this._onTabKey,
193-
[ENTER_KEY]: this._onEnterKey,
194-
[ESC_KEY]: this._onEscapeKey,
195-
[UP_KEY]: this._onDirectionKey,
196-
[PAGE_UP_KEY]: this._onDirectionKey,
197-
[DOWN_KEY]: this._onDirectionKey,
198-
[PAGE_DOWN_KEY]: this._onDirectionKey,
199-
[DELETE_KEY]: this._onDeleteKey,
200-
[BACK_KEY]: this._onDeleteKey,
201-
};
202-
203-
// If keycode has a function, run it
204-
if (keyDownActions[keyCode]) {
205-
keyDownActions[keyCode]({
206-
event,
207-
target,
208-
keyCode,
209-
metaKey,
210-
activeItems,
211-
hasFocusedInput,
212-
hasActiveDropdown,
213-
hasItems,
214-
hasCtrlDownKeyPressed,
215-
});
216-
}
92+
const keyCode = event.keyCode;
93+
return this._isSelectOneElement && keyCode === ExtendedKeyCodeMap.TAB_KEY
94+
? this._onTabKey()
95+
: super._onKeyDown(event);
21796
}
21897

219-
onSelectValue({ event, activeItems, hasActiveDropdown }) {
98+
onSelectValue(event, hasActiveDropdown) {
22099
if (hasActiveDropdown) {
221-
this._selectHighlightedChoice(activeItems);
100+
this._selectHighlightedChoice();
222101
}
223102
else if (this._isSelectOneElement) {
224103
this.showDropdown();
@@ -227,12 +106,14 @@ class ChoicesWrapper extends Choices {
227106
}
228107

229108
showDropdown(...args) {
230-
if (!this.shouldOpenDropDown) {
231-
this.shouldOpenDropDown = true;
232-
return;
233-
}
109+
setTimeout(() => {
110+
if (!this.shouldOpenDropDown) {
111+
this.shouldOpenDropDown = true;
112+
return;
113+
}
234114

235-
super.showDropdown(...args);
115+
super.showDropdown(...args);
116+
}, 0);
236117
}
237118

238119
hideDropdown(...args) {
@@ -242,13 +123,6 @@ class ChoicesWrapper extends Choices {
242123

243124
super.hideDropdown(...args);
244125
}
245-
246-
_onBlur(...args) {
247-
if (this._isScrollingOnIe) {
248-
return;
249-
}
250-
super._onBlur(...args);
251-
}
252126
}
253127

254128
export default ChoicesWrapper;

test/unit/Radio.unit.js

-2
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,7 @@ describe('Radio Component', () => {
343343
}, 350);
344344
}).catch(done);
345345
});
346-
});
347346

348-
describe('Radio Component', () => {
349347
it('should have red asterisk left hand side to the options labels if component is required and label is hidden', () => {
350348
return Harness.testCreate(RadioComponent, comp7).then(component => {
351349
const options = component.element.querySelectorAll('.form-check-label');

0 commit comments

Comments
 (0)