@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.
@@ -68,6 +68,6 @@ export default class PixCheckbox extends Component {
68
68
  }
69
69
 
70
70
  formatMessage(message) {
71
- return formatMessage('fr', `input.${message}`);
71
+ return formatMessage(this.args.locale ?? 'fr', `input.${message}`);
72
72
  }
73
73
  }
@@ -20,6 +20,7 @@
20
20
  @values={{this.selectedCategories}}
21
21
  @options={{this.categories}}
22
22
  @onChange={{this.selectCategories}}
23
+ @isComputeWidthDisabled={{true}}
23
24
  @screenReaderOnly={{true}}
24
25
  @className="pix-filterable-and-searchable-select__pix-multi-select"
25
26
  >
@@ -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: center;
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
 
@@ -13,7 +13,7 @@
13
13
  &--inline {
14
14
  flex-direction: row;
15
15
  gap: var(--pix-spacing-2x);
16
- align-items: center;
16
+ align-items: baseline;
17
17
  }
18
18
 
19
19
  &--full-width {
@@ -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
+ };
@@ -5,6 +5,9 @@ export default {
5
5
  error: 'Sélection incorrecte',
6
6
  },
7
7
  },
8
+ select: {
9
+ search: 'Rechercher',
10
+ },
8
11
  pagination: {
9
12
  beforeResultsPerPage: 'Voir',
10
13
  selectPageSizeLabel: "Nombre d'élément à afficher par page",
@@ -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) {
@@ -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": "55.34.0",
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": "^4.0.0"
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
- }