@1024pix/pix-ui 55.34.0 → 56.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/components/pix-checkbox.js +1 -1
- package/addon/components/pix-filterable-and-searchable-select.hbs +1 -0
- package/addon/components/pix-multi-select.gjs +281 -0
- package/addon/components/pix-select.gjs +256 -0
- package/addon/styles/_pix-multi-select.scss +9 -1
- package/addon/styles/_pix-select.scss +1 -1
- package/addon/translations/en.js +9 -0
- package/addon/translations/es-419.js +22 -0
- package/addon/translations/es.js +22 -0
- package/addon/translations/fr.js +3 -0
- package/addon/translations/index.js +10 -0
- package/addon/translations/nl.js +9 -0
- package/package.json +2 -2
- package/addon/components/pix-multi-select.hbs +0 -100
- package/addon/components/pix-multi-select.js +0 -169
- package/addon/components/pix-select.hbs +0 -91
- package/addon/components/pix-select.js +0 -144
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { warn } from '@ember/debug';
|
|
2
|
+
import { concat } from '@ember/helper';
|
|
3
|
+
import { on } from '@ember/modifier';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { guidFor } from '@ember/object/internals';
|
|
6
|
+
import { service } from '@ember/service';
|
|
7
|
+
import Component from '@glimmer/component';
|
|
8
|
+
import { tracked } from '@glimmer/tracking';
|
|
9
|
+
import onClickOutside from 'ember-click-outside/modifiers/on-click-outside';
|
|
10
|
+
import { PopperJS } from 'ember-popperjs';
|
|
11
|
+
import { gt, or } from 'ember-truth-helpers';
|
|
12
|
+
|
|
13
|
+
import onArrowDownUpAction from '../modifiers/on-arrow-down-up-action';
|
|
14
|
+
import onEnterAction from '../modifiers/on-enter-action';
|
|
15
|
+
import onEscapeAction from '../modifiers/on-escape-action';
|
|
16
|
+
import { formatMessage } from '../translations';
|
|
17
|
+
import PixCheckbox from './pix-checkbox';
|
|
18
|
+
import PixIcon from './pix-icon';
|
|
19
|
+
import PixLabel from './pix-label';
|
|
20
|
+
|
|
21
|
+
function removeCapitalizeAndDiacritics(string) {
|
|
22
|
+
return string
|
|
23
|
+
.normalize('NFD')
|
|
24
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
25
|
+
.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default class PixMultiSelect extends Component {
|
|
29
|
+
@tracked isExpanded = false;
|
|
30
|
+
@tracked searchData;
|
|
31
|
+
@service elementHelper;
|
|
32
|
+
|
|
33
|
+
constructor(...args) {
|
|
34
|
+
super(...args);
|
|
35
|
+
|
|
36
|
+
this.searchId = 'search-input-' + guidFor(this);
|
|
37
|
+
this.multiSelectId = this.args.id ? this.args.id : 'select-' + guidFor(this);
|
|
38
|
+
this.listId = `list-${this.multiSelectId}`;
|
|
39
|
+
|
|
40
|
+
if (!this.args.isComputeWidthDisabled) {
|
|
41
|
+
this.elementHelper.waitForElement(this.listId).then((elementList) => {
|
|
42
|
+
const baseFontRemRatio = Number(
|
|
43
|
+
getComputedStyle(document.querySelector('html')).fontSize.match(/\d+(\.\d+)?/)[0],
|
|
44
|
+
);
|
|
45
|
+
const listWidth = elementList.getBoundingClientRect().width;
|
|
46
|
+
const selectWidth = listWidth / baseFontRemRatio;
|
|
47
|
+
|
|
48
|
+
const element = document.getElementById(`container-${this.multiSelectId}`);
|
|
49
|
+
element.style.setProperty('--pix-multi-select-width', `${selectWidth + 0.5}rem`); // Fix for FF
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
warn(
|
|
54
|
+
`PixMultiSelect: @strictSearch is deprecated in favour of @onSearch`,
|
|
55
|
+
!this.args.strictSearch,
|
|
56
|
+
{
|
|
57
|
+
id: 'pix-ui.pix-multi-select.strictSearch.deprecated',
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get options() {
|
|
63
|
+
return [...(this.args.options || [])];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get mainInputClassName() {
|
|
67
|
+
let classes = 'pix-multi-select-main-input';
|
|
68
|
+
|
|
69
|
+
if (this.args.className) {
|
|
70
|
+
classes += ` ${this.args.className}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return classes;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get isAriaExpanded() {
|
|
77
|
+
return this.isExpanded ? 'true' : 'false';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get results() {
|
|
81
|
+
if (this.args.isSearchable && this.searchData) {
|
|
82
|
+
return this.args.options.filter(({ label }) => this._search(label));
|
|
83
|
+
}
|
|
84
|
+
return this.args.options;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get placeholder() {
|
|
88
|
+
const { values, placeholder } = this.args;
|
|
89
|
+
if (values?.length > 0) {
|
|
90
|
+
const selectedOptionLabels = this.options
|
|
91
|
+
.filter((option) => {
|
|
92
|
+
const hasOption = values.includes(option.value);
|
|
93
|
+
return hasOption && Boolean(option.label);
|
|
94
|
+
})
|
|
95
|
+
.map(({ label }) => label)
|
|
96
|
+
.join(', ');
|
|
97
|
+
return selectedOptionLabels;
|
|
98
|
+
}
|
|
99
|
+
return placeholder;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_search(label) {
|
|
103
|
+
if (this.args.strictSearch) {
|
|
104
|
+
return label.includes(this.searchData);
|
|
105
|
+
}
|
|
106
|
+
return removeCapitalizeAndDiacritics(label).includes(this.searchData);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@action
|
|
110
|
+
onSelect(event) {
|
|
111
|
+
let selected = [...(this.args.values || [])];
|
|
112
|
+
if (event.target.checked) {
|
|
113
|
+
selected.push(event.target.value);
|
|
114
|
+
} else {
|
|
115
|
+
selected = selected.filter((value) => value !== event.target.value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (this.args.onChange) {
|
|
119
|
+
this.args.onChange(selected);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@action
|
|
124
|
+
toggleDropDown() {
|
|
125
|
+
if (this.isExpanded) {
|
|
126
|
+
this.hideDropDown();
|
|
127
|
+
} else {
|
|
128
|
+
this.showDropDown();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@action
|
|
133
|
+
showDropDown() {
|
|
134
|
+
if (this.isExpanded) return;
|
|
135
|
+
this.isExpanded = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@action
|
|
139
|
+
hideDropDown(event) {
|
|
140
|
+
if (!this.isExpanded) return;
|
|
141
|
+
|
|
142
|
+
if (event) {
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
}
|
|
146
|
+
this.isExpanded = false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@action
|
|
150
|
+
updateSearch(event) {
|
|
151
|
+
if (this.args.onSearch) {
|
|
152
|
+
this.args.onSearch(event.target.value);
|
|
153
|
+
} else {
|
|
154
|
+
this.searchData = this.args.strictSearch
|
|
155
|
+
? event.target.value
|
|
156
|
+
: removeCapitalizeAndDiacritics(event.target.value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@action
|
|
161
|
+
isCheckBoxChecked(value) {
|
|
162
|
+
return this.args.values?.includes(value);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get className() {
|
|
166
|
+
const { className } = this.args;
|
|
167
|
+
return ' ' + className;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get selectSearchLabel() {
|
|
171
|
+
return formatMessage(this.args.locale ?? 'fr', 'select.search');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@action
|
|
175
|
+
focus(event) {
|
|
176
|
+
if (!event.target) return;
|
|
177
|
+
if (!this.isExpanded) return;
|
|
178
|
+
|
|
179
|
+
if (this.args.isSearchable) {
|
|
180
|
+
event.target.querySelector(`#${this.searchId}`)?.focus();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
<template>
|
|
185
|
+
<div
|
|
186
|
+
class="pix-multi-select {{if @inlineLabel ' pix-multi-select--inline'}}"
|
|
187
|
+
id="container-{{this.multiSelectId}}"
|
|
188
|
+
...attributes
|
|
189
|
+
{{onClickOutside this.hideDropDown}}
|
|
190
|
+
{{onArrowDownUpAction this.listId this.showDropDown this.isExpanded}}
|
|
191
|
+
{{onEscapeAction this.hideDropDown this.multiSelectId}}
|
|
192
|
+
>
|
|
193
|
+
<PixLabel
|
|
194
|
+
@for={{this.multiSelectId}}
|
|
195
|
+
@requiredLabel={{@requiredLabel}}
|
|
196
|
+
@subLabel={{@subLabel}}
|
|
197
|
+
@size={{@size}}
|
|
198
|
+
@screenReaderOnly={{@screenReaderOnly}}
|
|
199
|
+
@inlineLabel={{@inlineLabel}}
|
|
200
|
+
>
|
|
201
|
+
{{yield to="label"}}
|
|
202
|
+
</PixLabel>
|
|
203
|
+
|
|
204
|
+
<div>
|
|
205
|
+
<PopperJS @placement={{or @placement "bottom-start"}} as |reference popover|>
|
|
206
|
+
<button
|
|
207
|
+
{{reference}}
|
|
208
|
+
id={{this.multiSelectId}}
|
|
209
|
+
type="button"
|
|
210
|
+
aria-expanded={{this.isAriaExpanded}}
|
|
211
|
+
aria-controls={{this.listId}}
|
|
212
|
+
aria-haspopup="menu"
|
|
213
|
+
class={{this.mainInputClassName}}
|
|
214
|
+
disabled={{@isDisabled}}
|
|
215
|
+
{{on "click" this.toggleDropDown}}
|
|
216
|
+
>
|
|
217
|
+
{{#if (has-block "placeholder")}}
|
|
218
|
+
<span class="pix-multi-select__placeholder">{{yield to="placeholder"}}</span>
|
|
219
|
+
{{else if @placeholder}}
|
|
220
|
+
<span class="pix-multi-select__placeholder">{{this.placeholder}}</span>
|
|
221
|
+
{{/if}}
|
|
222
|
+
<PixIcon
|
|
223
|
+
class="pix-multi-select-main-input__dropdown-icon
|
|
224
|
+
{{if this.isExpanded ' pix-multi-select-main-input__dropdown-icon--expand'}}"
|
|
225
|
+
@name={{if this.isExpanded "chevronTop" "chevronBottom"}}
|
|
226
|
+
@ariaHidden={{true}}
|
|
227
|
+
/>
|
|
228
|
+
</button>
|
|
229
|
+
|
|
230
|
+
<ul
|
|
231
|
+
{{popover}}
|
|
232
|
+
class="pix-multi-select-list {{unless this.isExpanded 'pix-multi-select-list--hidden'}}"
|
|
233
|
+
id={{this.listId}}
|
|
234
|
+
role="menu"
|
|
235
|
+
aria-hidden={{this.isExpanded undefined "true"}}
|
|
236
|
+
{{on "transitionend" this.focus}}
|
|
237
|
+
>
|
|
238
|
+
{{#if @isSearchable}}
|
|
239
|
+
<li class="pix-select__search">
|
|
240
|
+
<PixIcon class="pix-select-search__icon" @name="search" @ariaHidden={{true}} />
|
|
241
|
+
<label class="screen-reader-only" for={{this.searchId}}>
|
|
242
|
+
{{this.selectSearchLabel}}
|
|
243
|
+
</label>
|
|
244
|
+
<input
|
|
245
|
+
class="pix-select-search__input"
|
|
246
|
+
id={{this.searchId}}
|
|
247
|
+
autocomplete="off"
|
|
248
|
+
tabindex={{if this.isExpanded "0" "-1"}}
|
|
249
|
+
placeholder={{@searchPlaceholder}}
|
|
250
|
+
{{on "input" this.updateSearch}}
|
|
251
|
+
/>
|
|
252
|
+
</li>
|
|
253
|
+
{{/if}}
|
|
254
|
+
{{#if (gt this.results.length 0)}}
|
|
255
|
+
{{#each this.results as |option|}}
|
|
256
|
+
<li class="pix-multi-select-list__item" role="menuitem">
|
|
257
|
+
<PixCheckbox
|
|
258
|
+
@id={{concat this.multiSelectId "-" option.value}}
|
|
259
|
+
@checked={{this.isCheckBoxChecked option.value}}
|
|
260
|
+
@size="small"
|
|
261
|
+
@class="pix-multi-select-list__item-label"
|
|
262
|
+
value={{option.value}}
|
|
263
|
+
{{on "change" this.onSelect}}
|
|
264
|
+
{{onEnterAction this.hideDropDown this.multiSelectId}}
|
|
265
|
+
tabindex={{if this.isExpanded "0" "-1"}}
|
|
266
|
+
>
|
|
267
|
+
<:label>{{yield option}}</:label>
|
|
268
|
+
</PixCheckbox>
|
|
269
|
+
</li>
|
|
270
|
+
{{/each}}
|
|
271
|
+
{{else}}
|
|
272
|
+
<li
|
|
273
|
+
class="pix-multi-select-list__item pix-multi-select-list__item--no-result"
|
|
274
|
+
>{{@emptyMessage}}</li>
|
|
275
|
+
{{/if}}
|
|
276
|
+
</ul>
|
|
277
|
+
</PopperJS>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</template>
|
|
281
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { on } from '@ember/modifier';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { guidFor } from '@ember/object/internals';
|
|
4
|
+
import { service } from '@ember/service';
|
|
5
|
+
import Component from '@glimmer/component';
|
|
6
|
+
import { tracked } from '@glimmer/tracking';
|
|
7
|
+
import onClickOutside from 'ember-click-outside/modifiers/on-click-outside';
|
|
8
|
+
import { PopperJS } from 'ember-popperjs';
|
|
9
|
+
import { or } from 'ember-truth-helpers';
|
|
10
|
+
|
|
11
|
+
import onArrowDownUpAction from '../modifiers/on-arrow-down-up-action';
|
|
12
|
+
import onEscapeAction from '../modifiers/on-escape-action';
|
|
13
|
+
import { formatMessage } from '../translations';
|
|
14
|
+
import PixIcon from './pix-icon';
|
|
15
|
+
import PixLabel from './pix-label';
|
|
16
|
+
import PixSelectList from './pix-select-list';
|
|
17
|
+
|
|
18
|
+
export default class PixSelect extends Component {
|
|
19
|
+
@service elementHelper;
|
|
20
|
+
@tracked isExpanded = false;
|
|
21
|
+
@tracked searchValue = null;
|
|
22
|
+
|
|
23
|
+
constructor(...args) {
|
|
24
|
+
super(...args);
|
|
25
|
+
|
|
26
|
+
this.searchId = 'search-input-' + guidFor(this);
|
|
27
|
+
this.selectId = this.args.id ? this.args.id : 'select-' + guidFor(this);
|
|
28
|
+
this.listId = `listbox-${this.selectId}`;
|
|
29
|
+
|
|
30
|
+
if (!this.args.isComputeWidthDisabled) {
|
|
31
|
+
this.elementHelper.waitForElement(this.listId).then((elementList) => {
|
|
32
|
+
const baseFontRemRatio = Number(
|
|
33
|
+
getComputedStyle(document.querySelector('html')).fontSize.match(/\d+(\.\d+)?/)[0],
|
|
34
|
+
);
|
|
35
|
+
const listWidth = elementList.getBoundingClientRect().width;
|
|
36
|
+
const selectWidth = listWidth / baseFontRemRatio;
|
|
37
|
+
|
|
38
|
+
const element = document.getElementById(`container-${this.selectId}`);
|
|
39
|
+
element.style.setProperty('--pix-select-width', `${selectWidth + 0.5}rem`); // Fix for FF
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get selectSearchLabel() {
|
|
45
|
+
return formatMessage(this.args.locale ?? 'fr', 'select.search');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get displayDefaultOption() {
|
|
49
|
+
return !this.searchValue && !this.args.hideDefaultOption;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get buttonClassName() {
|
|
53
|
+
const buttonClasses = ['pix-select-button'];
|
|
54
|
+
if (this.args.className) {
|
|
55
|
+
buttonClasses.push(this.args.className);
|
|
56
|
+
}
|
|
57
|
+
if (this.args.errorMessage) {
|
|
58
|
+
buttonClasses.push('pix-select-button--error');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return buttonClasses.join(' ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get rootClassNames() {
|
|
65
|
+
const classes = ['pix-select'];
|
|
66
|
+
if (this.args.inlineLabel) {
|
|
67
|
+
classes.push('pix-select--inline');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this.args.isFullWidth) {
|
|
71
|
+
classes.push('pix-select--full-width');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return classes.join(' ');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get isAriaExpanded() {
|
|
78
|
+
return this.isExpanded ? 'true' : 'false';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get placeholder() {
|
|
82
|
+
if (!this.args.value) return this.args.placeholder;
|
|
83
|
+
const option = this.args.options.find((option) => option.value === this.args.value);
|
|
84
|
+
return option ? option.label : this.args.placeholder;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get defaultOption() {
|
|
88
|
+
return {
|
|
89
|
+
value: '',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@action
|
|
94
|
+
toggleDropdown(event) {
|
|
95
|
+
if (this.isExpanded) {
|
|
96
|
+
this.hideDropdown(event);
|
|
97
|
+
} else {
|
|
98
|
+
this.showDropdown(event);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@action
|
|
103
|
+
showDropdown(event) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
if (this.args.isDisabled) return;
|
|
106
|
+
|
|
107
|
+
this.isExpanded = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@action
|
|
111
|
+
hideDropdown(event) {
|
|
112
|
+
if (this.isExpanded) {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
|
|
115
|
+
this.isExpanded = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@action
|
|
120
|
+
onChange(option, event) {
|
|
121
|
+
if (this.args.isDisabled) return;
|
|
122
|
+
|
|
123
|
+
this.args.onChange(option.value);
|
|
124
|
+
|
|
125
|
+
this.hideDropdown(event);
|
|
126
|
+
document.getElementById(this.selectId).focus();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@action
|
|
130
|
+
setSearchValue(event) {
|
|
131
|
+
if (this.args.onSearch) {
|
|
132
|
+
this.args.onSearch(event.target.value);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.searchValue = event.target.value.trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@action
|
|
139
|
+
lockTab(event) {
|
|
140
|
+
if (event.code === 'Tab' && this.isExpanded) {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
if (this.args.isSearchable) document.getElementById(this.searchId).focus();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@action
|
|
147
|
+
focus(event) {
|
|
148
|
+
if (!event.target) return;
|
|
149
|
+
if (!this.isExpanded) return;
|
|
150
|
+
|
|
151
|
+
if (this.args.value) {
|
|
152
|
+
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
153
|
+
} else if (this.args.isSearchable) {
|
|
154
|
+
event.target.querySelector(`#${this.searchId}`)?.focus();
|
|
155
|
+
} else if (this.displayDefaultOption) {
|
|
156
|
+
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
<template>
|
|
161
|
+
<div
|
|
162
|
+
class={{this.rootClassNames}}
|
|
163
|
+
id="container-{{this.selectId}}"
|
|
164
|
+
{{onClickOutside this.hideDropdown}}
|
|
165
|
+
{{onArrowDownUpAction this.listId this.showDropdown this.isExpanded}}
|
|
166
|
+
{{onEscapeAction this.hideDropdown this.selectId}}
|
|
167
|
+
{{on "keydown" this.lockTab}}
|
|
168
|
+
...attributes
|
|
169
|
+
>
|
|
170
|
+
{{#if (has-block "label")}}
|
|
171
|
+
<PixLabel
|
|
172
|
+
@for={{this.selectId}}
|
|
173
|
+
@requiredLabel={{@requiredLabel}}
|
|
174
|
+
@subLabel={{@subLabel}}
|
|
175
|
+
@size={{@size}}
|
|
176
|
+
@screenReaderOnly={{@screenReaderOnly}}
|
|
177
|
+
@inlineLabel={{@inlineLabel}}
|
|
178
|
+
>
|
|
179
|
+
{{yield to="label"}}
|
|
180
|
+
</PixLabel>
|
|
181
|
+
{{/if}}
|
|
182
|
+
|
|
183
|
+
<div class="pix-select__button-container">
|
|
184
|
+
<PopperJS @placement={{or @placement "bottom-start"}} as |reference popover|>
|
|
185
|
+
<button
|
|
186
|
+
{{reference}}
|
|
187
|
+
type="button"
|
|
188
|
+
id={{this.selectId}}
|
|
189
|
+
class={{this.buttonClassName}}
|
|
190
|
+
{{on "click" this.toggleDropdown}}
|
|
191
|
+
aria-expanded={{this.isAriaExpanded}}
|
|
192
|
+
aria-controls={{this.listId}}
|
|
193
|
+
aria-disabled={{@isDisabled}}
|
|
194
|
+
>
|
|
195
|
+
{{#if @iconName}}
|
|
196
|
+
<PixIcon
|
|
197
|
+
@name={{@iconName}}
|
|
198
|
+
@plainIcon={{@plainIcon}}
|
|
199
|
+
@ariaHidden={{true}}
|
|
200
|
+
class="pix-select-button__icon"
|
|
201
|
+
/>
|
|
202
|
+
{{/if}}
|
|
203
|
+
|
|
204
|
+
<span class="pix-select-button__text">{{this.placeholder}}</span>
|
|
205
|
+
|
|
206
|
+
<PixIcon
|
|
207
|
+
class="pix-select-button__dropdown-icon"
|
|
208
|
+
@ariaHidden={{true}}
|
|
209
|
+
@name={{if this.isExpanded "chevronTop" "chevronBottom"}}
|
|
210
|
+
/>
|
|
211
|
+
</button>
|
|
212
|
+
<div
|
|
213
|
+
{{popover}}
|
|
214
|
+
class="pix-select__dropdown {{unless this.isExpanded ' pix-select__dropdown--closed'}}"
|
|
215
|
+
{{on "transitionend" this.focus}}
|
|
216
|
+
aria-hidden={{this.isExpanded undefined "true"}}
|
|
217
|
+
>
|
|
218
|
+
{{#if @isSearchable}}
|
|
219
|
+
<div class="pix-select__search">
|
|
220
|
+
<PixIcon class="pix-select-search__icon" @name="search" @ariaHidden={{true}} />
|
|
221
|
+
<label class="screen-reader-only" for={{this.searchId}}>
|
|
222
|
+
{{this.selectSearchLabel}}
|
|
223
|
+
</label>
|
|
224
|
+
<input
|
|
225
|
+
class="pix-select-search__input"
|
|
226
|
+
id={{this.searchId}}
|
|
227
|
+
autocomplete="off"
|
|
228
|
+
tabindex={{if this.isExpanded "0" "-1"}}
|
|
229
|
+
placeholder={{@searchPlaceholder}}
|
|
230
|
+
{{on "input" this.setSearchValue}}
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
{{/if}}
|
|
234
|
+
<PixSelectList
|
|
235
|
+
@hideDefaultOption={{@hideDefaultOption}}
|
|
236
|
+
@listId={{this.listId}}
|
|
237
|
+
@value={{@value}}
|
|
238
|
+
@displayDefaultOption={{this.displayDefaultOption}}
|
|
239
|
+
@searchValue={{this.searchValue}}
|
|
240
|
+
@onChange={{this.onChange}}
|
|
241
|
+
@defaultOption={{this.defaultOption}}
|
|
242
|
+
@selectId={{this.selectId}}
|
|
243
|
+
@isExpanded={{this.isExpanded}}
|
|
244
|
+
@options={{@options}}
|
|
245
|
+
@defaultOptionValue={{@placeholder}}
|
|
246
|
+
@emptySearchMessage={{@emptySearchMessage}}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</PopperJS>
|
|
250
|
+
{{#if @errorMessage}}
|
|
251
|
+
<p class="pix-select__error-message">{{@errorMessage}}</p>
|
|
252
|
+
{{/if}}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</template>
|
|
256
|
+
}
|
|
@@ -7,11 +7,19 @@
|
|
|
7
7
|
display: inline-flex;
|
|
8
8
|
flex-direction: column;
|
|
9
9
|
gap: var(--pix-spacing-1x);
|
|
10
|
+
width: var(--pix-multi-select-width);
|
|
11
|
+
max-width: 100%;
|
|
10
12
|
|
|
11
13
|
&--inline {
|
|
12
14
|
flex-direction: row;
|
|
13
15
|
gap: var(--pix-spacing-2x);
|
|
14
|
-
align-items:
|
|
16
|
+
align-items: baseline;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&__placeholder {
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
white-space: nowrap;
|
|
22
|
+
text-overflow: ellipsis;
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
25
|
|
package/addon/translations/en.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
input: {
|
|
3
|
+
state: {
|
|
4
|
+
success: 'Correct selection',
|
|
5
|
+
error: 'Incorrect selection',
|
|
6
|
+
},
|
|
7
|
+
},
|
|
8
|
+
select: {
|
|
9
|
+
search: 'Search',
|
|
10
|
+
},
|
|
2
11
|
pagination: {
|
|
3
12
|
beforeResultsPerPage: 'See',
|
|
4
13
|
selectPageSizeLabel: 'Select the number of items by page',
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
input: {
|
|
3
|
+
state: {
|
|
4
|
+
success: 'Selección correcta',
|
|
5
|
+
error: 'Selección incorrecta',
|
|
6
|
+
},
|
|
7
|
+
},
|
|
8
|
+
select: {
|
|
9
|
+
search: 'Buscar',
|
|
10
|
+
},
|
|
11
|
+
pagination: {
|
|
12
|
+
beforeResultsPerPage: 'Ver',
|
|
13
|
+
selectPageSizeLabel: 'Número de elementos que se mostrarán por página',
|
|
14
|
+
pageResults:
|
|
15
|
+
'{total, plural, =0 {0 elemento} =1 {1 elemento} otros {{total, número} elementos}}',
|
|
16
|
+
pageInfo:
|
|
17
|
+
'{start, número}-{end, número} de {total, plural, =0 {0 elemento} =1 {1 elemento} otros {{total, número} elementos}}',
|
|
18
|
+
previousPageLabel: 'Ir a la página anterior',
|
|
19
|
+
pageNumber: 'Página {current, number} / {total, number}',
|
|
20
|
+
nextPageLabel: 'Ir a la página siguiente',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
input: {
|
|
3
|
+
state: {
|
|
4
|
+
success: 'Selección correcta',
|
|
5
|
+
error: 'Selección incorrecta',
|
|
6
|
+
},
|
|
7
|
+
},
|
|
8
|
+
select: {
|
|
9
|
+
search: 'Buscar',
|
|
10
|
+
},
|
|
11
|
+
pagination: {
|
|
12
|
+
beforeResultsPerPage: 'Ver',
|
|
13
|
+
selectPageSizeLabel: 'Número de elementos que se mostrarán por página',
|
|
14
|
+
pageResults:
|
|
15
|
+
'{total, plural, =0 {0 elemento} =1 {1 elemento} otros {{total, número} elementos}}',
|
|
16
|
+
pageInfo:
|
|
17
|
+
'{start, número}-{end, número} de {total, plural, =0 {0 elemento} =1 {1 elemento} otros {{total, número} elementos}}',
|
|
18
|
+
previousPageLabel: 'Ir a la página anterior',
|
|
19
|
+
pageNumber: 'Página {current, number} / {total, number}',
|
|
20
|
+
nextPageLabel: 'Ir a la página siguiente',
|
|
21
|
+
},
|
|
22
|
+
};
|
package/addon/translations/fr.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createIntl } from '@formatjs/intl';
|
|
2
2
|
|
|
3
3
|
import en from './en';
|
|
4
|
+
import es from './es';
|
|
5
|
+
import es419 from './es-419';
|
|
4
6
|
import fr from './fr';
|
|
5
7
|
import nl from './nl';
|
|
6
8
|
|
|
@@ -22,6 +24,14 @@ const locales = {
|
|
|
22
24
|
locale: 'nl',
|
|
23
25
|
messages: flattenObject(nl),
|
|
24
26
|
}),
|
|
27
|
+
es: createIntl({
|
|
28
|
+
locale: 'es',
|
|
29
|
+
messages: flattenObject(es),
|
|
30
|
+
}),
|
|
31
|
+
'es-419': createIntl({
|
|
32
|
+
locale: 'es-419',
|
|
33
|
+
messages: flattenObject(es419),
|
|
34
|
+
}),
|
|
25
35
|
};
|
|
26
36
|
|
|
27
37
|
export function flattenObject(object) {
|
package/addon/translations/nl.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
input: {
|
|
3
|
+
state: {
|
|
4
|
+
success: 'Correcte selectie',
|
|
5
|
+
error: 'Onjuiste selectie',
|
|
6
|
+
},
|
|
7
|
+
},
|
|
8
|
+
select: {
|
|
9
|
+
search: 'Zoeken',
|
|
10
|
+
},
|
|
2
11
|
pagination: {
|
|
3
12
|
beforeResultsPerPage: 'Zie',
|
|
4
13
|
selectPageSizeLabel: 'Selecteer het aantal items per pagina',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1024pix/pix-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "56.0.0",
|
|
4
4
|
"description": "Pix-UI is the implementation of Pix design principles and guidelines for its products.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ember-addon"
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"ember-modifier": "^4.2.0",
|
|
74
74
|
"ember-popperjs": "^3.0.0",
|
|
75
75
|
"ember-template-imports": "^4.3.0",
|
|
76
|
-
"ember-truth-helpers": "^
|
|
76
|
+
"ember-truth-helpers": "^5.0.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@1024pix/ember-testing-library": "^3.0.21",
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
<div
|
|
2
|
-
class="pix-multi-select {{if @inlineLabel ' pix-multi-select--inline'}}"
|
|
3
|
-
...attributes
|
|
4
|
-
{{on-click-outside this.hideDropDown}}
|
|
5
|
-
{{on-arrow-down-up-action this.listId this.showDropDown this.isExpanded}}
|
|
6
|
-
{{on-escape-action this.hideDropDown this.multiSelectId}}
|
|
7
|
-
>
|
|
8
|
-
<PixLabel
|
|
9
|
-
@for={{this.multiSelectId}}
|
|
10
|
-
@requiredLabel={{@requiredLabel}}
|
|
11
|
-
@subLabel={{@subLabel}}
|
|
12
|
-
@size={{@size}}
|
|
13
|
-
@screenReaderOnly={{@screenReaderOnly}}
|
|
14
|
-
@inlineLabel={{@inlineLabel}}
|
|
15
|
-
>
|
|
16
|
-
{{yield to="label"}}
|
|
17
|
-
</PixLabel>
|
|
18
|
-
|
|
19
|
-
<PopperJS @placement={{or @placement "bottom-start"}} as |reference popover|>
|
|
20
|
-
|
|
21
|
-
{{#if @isSearchable}}
|
|
22
|
-
<span {{reference}} class={{this.mainInputClassName}}>
|
|
23
|
-
<PixIcon
|
|
24
|
-
@name="search"
|
|
25
|
-
class="pix-multi-select-main-input__search-icon"
|
|
26
|
-
@ariaHidden={{true}}
|
|
27
|
-
/>
|
|
28
|
-
|
|
29
|
-
<input
|
|
30
|
-
class="pix-multi-select-main-input__search-input"
|
|
31
|
-
id={{this.multiSelectId}}
|
|
32
|
-
type="text"
|
|
33
|
-
name={{this.multiSelectId}}
|
|
34
|
-
placeholder={{this.placeholder}}
|
|
35
|
-
autocomplete="off"
|
|
36
|
-
{{on "input" this.updateSearch}}
|
|
37
|
-
{{on "click" this.toggleDropDown}}
|
|
38
|
-
aria-expanded={{this.isAriaExpanded}}
|
|
39
|
-
aria-controls={{this.listId}}
|
|
40
|
-
aria-haspopup="menu"
|
|
41
|
-
disabled={{@isDisabled}}
|
|
42
|
-
/>
|
|
43
|
-
</span>
|
|
44
|
-
{{else}}
|
|
45
|
-
<button
|
|
46
|
-
{{reference}}
|
|
47
|
-
id={{this.multiSelectId}}
|
|
48
|
-
type="button"
|
|
49
|
-
aria-expanded={{this.isAriaExpanded}}
|
|
50
|
-
aria-controls={{this.listId}}
|
|
51
|
-
aria-haspopup="menu"
|
|
52
|
-
class={{this.mainInputClassName}}
|
|
53
|
-
disabled={{@isDisabled}}
|
|
54
|
-
{{on "click" this.toggleDropDown}}
|
|
55
|
-
>
|
|
56
|
-
{{#if (has-block "placeholder")}}
|
|
57
|
-
{{yield to="placeholder"}}
|
|
58
|
-
{{else if @placeholder}}
|
|
59
|
-
{{this.placeholder}}
|
|
60
|
-
{{/if}}
|
|
61
|
-
<PixIcon
|
|
62
|
-
class="pix-multi-select-main-input__dropdown-icon
|
|
63
|
-
{{if this.isExpanded ' pix-multi-select-main-input__dropdown-icon--expand'}}"
|
|
64
|
-
@name={{if this.isExpanded "chevronTop" "chevronBottom"}}
|
|
65
|
-
@ariaHidden={{true}}
|
|
66
|
-
/>
|
|
67
|
-
</button>
|
|
68
|
-
{{/if}}
|
|
69
|
-
|
|
70
|
-
<ul
|
|
71
|
-
{{popover}}
|
|
72
|
-
class="pix-multi-select-list {{unless this.isExpanded 'pix-multi-select-list--hidden'}}"
|
|
73
|
-
id={{this.listId}}
|
|
74
|
-
role="menu"
|
|
75
|
-
>
|
|
76
|
-
{{#if (gt this.results.length 0)}}
|
|
77
|
-
{{#each this.results as |option|}}
|
|
78
|
-
<li class="pix-multi-select-list__item" role="menuitem">
|
|
79
|
-
<PixCheckbox
|
|
80
|
-
@id={{concat this.multiSelectId "-" option.value}}
|
|
81
|
-
@checked={{option.checked}}
|
|
82
|
-
@size="small"
|
|
83
|
-
@class="pix-multi-select-list__item-label"
|
|
84
|
-
value={{option.value}}
|
|
85
|
-
{{on "change" this.onSelect}}
|
|
86
|
-
{{on-enter-action this.hideDropDown this.multiSelectId}}
|
|
87
|
-
tabindex={{if this.isExpanded "0" "-1"}}
|
|
88
|
-
>
|
|
89
|
-
<:label>{{yield option}}</:label>
|
|
90
|
-
</PixCheckbox>
|
|
91
|
-
</li>
|
|
92
|
-
{{/each}}
|
|
93
|
-
{{else}}
|
|
94
|
-
<li
|
|
95
|
-
class="pix-multi-select-list__item pix-multi-select-list__item--no-result"
|
|
96
|
-
>{{@emptyMessage}}</li>
|
|
97
|
-
{{/if}}
|
|
98
|
-
</ul>
|
|
99
|
-
</PopperJS>
|
|
100
|
-
</div>
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { warn } from '@ember/debug';
|
|
2
|
-
import { action } from '@ember/object';
|
|
3
|
-
import { guidFor } from '@ember/object/internals';
|
|
4
|
-
import Component from '@glimmer/component';
|
|
5
|
-
import { tracked } from '@glimmer/tracking';
|
|
6
|
-
|
|
7
|
-
function sortOptionsByCheckedFirst(a, b) {
|
|
8
|
-
if (a.checked && b.checked) return 0;
|
|
9
|
-
if (a.checked) return -1;
|
|
10
|
-
if (b.checked) return 1;
|
|
11
|
-
return 0;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function removeCapitalizeAndDiacritics(string) {
|
|
15
|
-
return string
|
|
16
|
-
.normalize('NFD')
|
|
17
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
18
|
-
.toLowerCase();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default class PixMultiSelect extends Component {
|
|
22
|
-
@tracked isExpanded = false;
|
|
23
|
-
@tracked searchData;
|
|
24
|
-
|
|
25
|
-
constructor(...args) {
|
|
26
|
-
super(...args);
|
|
27
|
-
|
|
28
|
-
warn(
|
|
29
|
-
`PixMultiSelect: @strictSearch is deprecated in favour of @onSearch`,
|
|
30
|
-
!this.args.strictSearch,
|
|
31
|
-
{
|
|
32
|
-
id: 'pix-ui.pix-multi-select.strictSearch.deprecated',
|
|
33
|
-
},
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get options() {
|
|
38
|
-
return [...(this.args.options || [])];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get hasValues() {
|
|
42
|
-
return this.args.values && Array.isArray(this.args.values);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
get displayedOptions() {
|
|
46
|
-
const optionsWithCheckedStatus = this.options.map((option) => ({
|
|
47
|
-
...option,
|
|
48
|
-
checked: this.hasValues ? this.args.values.includes(option.value) : false,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
if (this.args.isSearchable) {
|
|
52
|
-
return [...optionsWithCheckedStatus.sort(sortOptionsByCheckedFirst)];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return optionsWithCheckedStatus;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get mainInputClassName() {
|
|
59
|
-
let classes = 'pix-multi-select-main-input';
|
|
60
|
-
|
|
61
|
-
if (this.args.isSearchable) {
|
|
62
|
-
classes += ' pix-multi-select-main-input--is-searchable';
|
|
63
|
-
}
|
|
64
|
-
if (this.args.className) {
|
|
65
|
-
classes += ` ${this.args.className}`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return classes;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
get multiSelectId() {
|
|
72
|
-
if (this.args.id) return this.args.id;
|
|
73
|
-
return 'select-' + guidFor(this);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get listId() {
|
|
77
|
-
return `list-${this.multiSelectId}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
get isAriaExpanded() {
|
|
81
|
-
return this.isExpanded ? 'true' : 'false';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
get results() {
|
|
85
|
-
if (this.args.isSearchable && this.searchData) {
|
|
86
|
-
return this.displayedOptions.filter(({ label }) => this._search(label));
|
|
87
|
-
}
|
|
88
|
-
return this.displayedOptions;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get placeholder() {
|
|
92
|
-
const { values, placeholder } = this.args;
|
|
93
|
-
if (values?.length > 0) {
|
|
94
|
-
const selectedOptionLabels = this.options
|
|
95
|
-
.filter((option) => {
|
|
96
|
-
const hasOption = values.includes(option.value);
|
|
97
|
-
return hasOption && Boolean(option.label);
|
|
98
|
-
})
|
|
99
|
-
.map(({ label }) => label)
|
|
100
|
-
.join(', ');
|
|
101
|
-
return selectedOptionLabels;
|
|
102
|
-
}
|
|
103
|
-
return placeholder;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
_search(label) {
|
|
107
|
-
if (this.args.strictSearch) {
|
|
108
|
-
return label.includes(this.searchData);
|
|
109
|
-
}
|
|
110
|
-
return removeCapitalizeAndDiacritics(label).includes(this.searchData);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@action
|
|
114
|
-
onSelect(event) {
|
|
115
|
-
let selected = [...(this.args.values || [])];
|
|
116
|
-
if (event.target.checked) {
|
|
117
|
-
selected.push(event.target.value);
|
|
118
|
-
} else {
|
|
119
|
-
selected = selected.filter((value) => value !== event.target.value);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (this.args.onChange) {
|
|
123
|
-
this.args.onChange(selected);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@action
|
|
128
|
-
toggleDropDown() {
|
|
129
|
-
if (this.isExpanded) {
|
|
130
|
-
this.hideDropDown();
|
|
131
|
-
} else {
|
|
132
|
-
this.showDropDown();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@action
|
|
137
|
-
showDropDown() {
|
|
138
|
-
if (this.isExpanded) return;
|
|
139
|
-
this.isExpanded = true;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@action
|
|
143
|
-
hideDropDown(event) {
|
|
144
|
-
if (!this.isExpanded) return;
|
|
145
|
-
|
|
146
|
-
if (event) {
|
|
147
|
-
event.stopPropagation();
|
|
148
|
-
event.preventDefault();
|
|
149
|
-
}
|
|
150
|
-
this.isExpanded = false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@action
|
|
154
|
-
updateSearch(event) {
|
|
155
|
-
if (this.args.onSearch) {
|
|
156
|
-
this.args.onSearch(event.target.value);
|
|
157
|
-
} else {
|
|
158
|
-
this.searchData = this.args.strictSearch
|
|
159
|
-
? event.target.value
|
|
160
|
-
: removeCapitalizeAndDiacritics(event.target.value);
|
|
161
|
-
}
|
|
162
|
-
this.isExpanded = true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
get className() {
|
|
166
|
-
const { className } = this.args;
|
|
167
|
-
return ' ' + className;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
<div
|
|
2
|
-
class={{this.rootClassNames}}
|
|
3
|
-
id="container-{{this.selectId}}"
|
|
4
|
-
{{on-click-outside this.hideDropdown}}
|
|
5
|
-
{{on-arrow-down-up-action this.listId this.showDropdown this.isExpanded}}
|
|
6
|
-
{{on-escape-action this.hideDropdown this.selectId}}
|
|
7
|
-
{{on "keydown" this.lockTab}}
|
|
8
|
-
...attributes
|
|
9
|
-
>
|
|
10
|
-
{{#if (has-block "label")}}
|
|
11
|
-
<PixLabel
|
|
12
|
-
@for={{this.selectId}}
|
|
13
|
-
@requiredLabel={{@requiredLabel}}
|
|
14
|
-
@subLabel={{@subLabel}}
|
|
15
|
-
@size={{@size}}
|
|
16
|
-
@screenReaderOnly={{@screenReaderOnly}}
|
|
17
|
-
@inlineLabel={{@inlineLabel}}
|
|
18
|
-
>
|
|
19
|
-
{{yield to="label"}}
|
|
20
|
-
</PixLabel>
|
|
21
|
-
{{/if}}
|
|
22
|
-
|
|
23
|
-
<div class="pix-select__button-container">
|
|
24
|
-
<PopperJS @placement={{or @placement "bottom-start"}} as |reference popover|>
|
|
25
|
-
<button
|
|
26
|
-
{{reference}}
|
|
27
|
-
type="button"
|
|
28
|
-
id={{this.selectId}}
|
|
29
|
-
class={{this.buttonClassName}}
|
|
30
|
-
{{on "click" this.toggleDropdown}}
|
|
31
|
-
aria-expanded={{this.isAriaExpanded}}
|
|
32
|
-
aria-controls={{this.listId}}
|
|
33
|
-
aria-disabled={{@isDisabled}}
|
|
34
|
-
>
|
|
35
|
-
{{#if @iconName}}
|
|
36
|
-
<PixIcon
|
|
37
|
-
@name={{@iconName}}
|
|
38
|
-
@plainIcon={{@plainIcon}}
|
|
39
|
-
@ariaHidden={{true}}
|
|
40
|
-
class="pix-select-button__icon"
|
|
41
|
-
/>
|
|
42
|
-
{{/if}}
|
|
43
|
-
|
|
44
|
-
<span class="pix-select-button__text">{{this.placeholder}}</span>
|
|
45
|
-
|
|
46
|
-
<PixIcon
|
|
47
|
-
class="pix-select-button__dropdown-icon"
|
|
48
|
-
@ariaHidden={{true}}
|
|
49
|
-
@name={{if this.isExpanded "chevronTop" "chevronBottom"}}
|
|
50
|
-
/>
|
|
51
|
-
</button>
|
|
52
|
-
<div
|
|
53
|
-
{{popover}}
|
|
54
|
-
class="pix-select__dropdown {{unless this.isExpanded ' pix-select__dropdown--closed'}}"
|
|
55
|
-
{{on "transitionend" this.focus}}
|
|
56
|
-
>
|
|
57
|
-
{{#if @isSearchable}}
|
|
58
|
-
<div class="pix-select__search">
|
|
59
|
-
<PixIcon class="pix-select-search__icon" @name="search" @ariaHidden={{true}} />
|
|
60
|
-
<label class="screen-reader-only" for={{this.searchId}}>{{@searchLabel}}</label>
|
|
61
|
-
<input
|
|
62
|
-
class="pix-select-search__input"
|
|
63
|
-
id={{this.searchId}}
|
|
64
|
-
autocomplete="off"
|
|
65
|
-
tabindex={{if this.isExpanded "0" "-1"}}
|
|
66
|
-
placeholder={{@searchPlaceholder}}
|
|
67
|
-
{{on "input" this.setSearchValue}}
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
{{/if}}
|
|
71
|
-
<PixSelectList
|
|
72
|
-
@hideDefaultOption={{@hideDefaultOption}}
|
|
73
|
-
@listId={{this.listId}}
|
|
74
|
-
@value={{@value}}
|
|
75
|
-
@displayDefaultOption={{this.displayDefaultOption}}
|
|
76
|
-
@searchValue={{this.searchValue}}
|
|
77
|
-
@onChange={{this.onChange}}
|
|
78
|
-
@defaultOption={{this.defaultOption}}
|
|
79
|
-
@selectId={{this.selectId}}
|
|
80
|
-
@isExpanded={{this.isExpanded}}
|
|
81
|
-
@options={{@options}}
|
|
82
|
-
@defaultOptionValue={{@placeholder}}
|
|
83
|
-
@emptySearchMessage={{@emptySearchMessage}}
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
</PopperJS>
|
|
87
|
-
{{#if @errorMessage}}
|
|
88
|
-
<p class="pix-select__error-message">{{@errorMessage}}</p>
|
|
89
|
-
{{/if}}
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { action } from '@ember/object';
|
|
2
|
-
import { guidFor } from '@ember/object/internals';
|
|
3
|
-
import { inject as service } from '@ember/service';
|
|
4
|
-
import Component from '@glimmer/component';
|
|
5
|
-
import { tracked } from '@glimmer/tracking';
|
|
6
|
-
|
|
7
|
-
export default class PixSelect extends Component {
|
|
8
|
-
@service elementHelper;
|
|
9
|
-
@tracked isExpanded = false;
|
|
10
|
-
@tracked searchValue = null;
|
|
11
|
-
|
|
12
|
-
constructor(...args) {
|
|
13
|
-
super(...args);
|
|
14
|
-
|
|
15
|
-
this.searchId = 'search-input-' + guidFor(this);
|
|
16
|
-
this.selectId = this.args.id ? this.args.id : 'select-' + guidFor(this);
|
|
17
|
-
this.listId = `listbox-${this.selectId}`;
|
|
18
|
-
|
|
19
|
-
if (!this.args.isComputeWidthDisabled) {
|
|
20
|
-
this.elementHelper.waitForElement(this.listId).then((elementList) => {
|
|
21
|
-
const baseFontRemRatio = Number(
|
|
22
|
-
getComputedStyle(document.querySelector('html')).fontSize.match(/\d+(\.\d+)?/)[0],
|
|
23
|
-
);
|
|
24
|
-
const listWidth = elementList.getBoundingClientRect().width;
|
|
25
|
-
const selectWidth = listWidth / baseFontRemRatio;
|
|
26
|
-
|
|
27
|
-
const element = document.getElementById(`container-${this.selectId}`);
|
|
28
|
-
element.style.setProperty('--pix-select-width', `${selectWidth + 0.5}rem`); // Fix for FF
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get displayDefaultOption() {
|
|
34
|
-
return !this.searchValue && !this.args.hideDefaultOption;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get buttonClassName() {
|
|
38
|
-
const buttonClasses = ['pix-select-button'];
|
|
39
|
-
if (this.args.className) {
|
|
40
|
-
buttonClasses.push(this.args.className);
|
|
41
|
-
}
|
|
42
|
-
if (this.args.errorMessage) {
|
|
43
|
-
buttonClasses.push('pix-select-button--error');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return buttonClasses.join(' ');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
get rootClassNames() {
|
|
50
|
-
const classes = ['pix-select'];
|
|
51
|
-
if (this.args.inlineLabel) {
|
|
52
|
-
classes.push('pix-select--inline');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (this.args.isFullWidth) {
|
|
56
|
-
classes.push('pix-select--full-width');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return classes.join(' ');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get isAriaExpanded() {
|
|
63
|
-
return this.isExpanded ? 'true' : 'false';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get placeholder() {
|
|
67
|
-
if (!this.args.value) return this.args.placeholder;
|
|
68
|
-
const option = this.args.options.find((option) => option.value === this.args.value);
|
|
69
|
-
return option ? option.label : this.args.placeholder;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
get defaultOption() {
|
|
73
|
-
return {
|
|
74
|
-
value: '',
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@action
|
|
79
|
-
toggleDropdown(event) {
|
|
80
|
-
if (this.isExpanded) {
|
|
81
|
-
this.hideDropdown(event);
|
|
82
|
-
} else {
|
|
83
|
-
this.showDropdown(event);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@action
|
|
88
|
-
showDropdown(event) {
|
|
89
|
-
event.preventDefault();
|
|
90
|
-
if (this.args.isDisabled) return;
|
|
91
|
-
|
|
92
|
-
this.isExpanded = true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
@action
|
|
96
|
-
hideDropdown(event) {
|
|
97
|
-
if (this.isExpanded) {
|
|
98
|
-
event.preventDefault();
|
|
99
|
-
|
|
100
|
-
this.isExpanded = false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
@action
|
|
105
|
-
onChange(option, event) {
|
|
106
|
-
if (this.args.isDisabled) return;
|
|
107
|
-
|
|
108
|
-
this.args.onChange(option.value);
|
|
109
|
-
|
|
110
|
-
this.hideDropdown(event);
|
|
111
|
-
document.getElementById(this.selectId).focus();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
@action
|
|
115
|
-
setSearchValue(event) {
|
|
116
|
-
if (this.args.onSearch) {
|
|
117
|
-
this.args.onSearch(event.target.value);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
this.searchValue = event.target.value.trim();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
@action
|
|
124
|
-
lockTab(event) {
|
|
125
|
-
if (event.code === 'Tab' && this.isExpanded) {
|
|
126
|
-
event.preventDefault();
|
|
127
|
-
if (this.args.isSearchable) document.getElementById(this.searchId).focus();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
@action
|
|
132
|
-
focus(event) {
|
|
133
|
-
if (!event.target) return;
|
|
134
|
-
if (!this.isExpanded) return;
|
|
135
|
-
|
|
136
|
-
if (this.args.value) {
|
|
137
|
-
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
138
|
-
} else if (this.args.isSearchable) {
|
|
139
|
-
event.target.querySelector(`#${this.searchId}`)?.focus();
|
|
140
|
-
} else if (this.displayDefaultOption) {
|
|
141
|
-
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|