@1024pix/pix-ui 55.18.1 → 55.18.2
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-select-list.hbs +6 -2
- package/addon/components/pix-select.gjs +235 -0
- package/addon/modifiers/on-arrow-down-up-action.js +82 -0
- package/addon/modifiers/on-enter-action.js +28 -0
- package/addon/modifiers/on-escape-action.js +23 -0
- package/addon/modifiers/on-space-action.js +20 -0
- package/addon/modifiers/on-window-resize.js +12 -0
- package/addon/modifiers/trap-focus.js +117 -0
- package/addon/styles/_pix-select-list.scss +5 -0
- package/addon/styles/_pix-select.scss +9 -4
- package/app/modifiers/on-arrow-down-up-action.js +1 -82
- package/app/modifiers/on-enter-action.js +1 -28
- package/app/modifiers/on-escape-action.js +1 -23
- package/app/modifiers/on-space-action.js +1 -20
- package/app/modifiers/on-window-resize.js +1 -12
- package/app/modifiers/trap-focus.js +1 -117
- package/package.json +2 -1
- package/addon/components/pix-select.hbs +0 -91
- package/addon/components/pix-select.js +0 -127
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
/>
|
|
61
61
|
{{/if}}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
<span class="pix-select-list-category__option-label">
|
|
64
|
+
{{option.label}}
|
|
65
|
+
</span>
|
|
64
66
|
|
|
65
67
|
{{#if (eq option.value @value)}}
|
|
66
68
|
<PixIcon
|
|
@@ -97,7 +99,9 @@
|
|
|
97
99
|
/>
|
|
98
100
|
{{/if}}
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
<span class="pix-select-list-category__option-label">
|
|
103
|
+
{{option.label}}
|
|
104
|
+
</span>
|
|
101
105
|
|
|
102
106
|
{{#if (eq option.value @value)}}
|
|
103
107
|
<PixIcon
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { on } from '@ember/modifier';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { guidFor } from '@ember/object/internals';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
5
|
+
import Component from '@glimmer/component';
|
|
6
|
+
import { tracked } from '@glimmer/tracking';
|
|
7
|
+
import onClickOutsideModifier from 'ember-click-outside/modifiers/on-click-outside';
|
|
8
|
+
import { FloatingUI } from 'ember-primitives/floating-ui';
|
|
9
|
+
|
|
10
|
+
import onArrowDownUpAction from '../modifiers/on-arrow-down-up-action';
|
|
11
|
+
import onEscapeAction from '../modifiers/on-escape-action';
|
|
12
|
+
import PixIcon from './pix-icon';
|
|
13
|
+
import PixLabel from './pix-label';
|
|
14
|
+
import PixSelectList from './pix-select-list';
|
|
15
|
+
|
|
16
|
+
export default class PixSelect extends Component {
|
|
17
|
+
@service elementHelper;
|
|
18
|
+
@tracked isExpanded = false;
|
|
19
|
+
@tracked searchValue = null;
|
|
20
|
+
|
|
21
|
+
constructor(...args) {
|
|
22
|
+
super(...args);
|
|
23
|
+
|
|
24
|
+
this.searchId = 'search-input-' + guidFor(this);
|
|
25
|
+
this.selectId = this.args.id ? this.args.id : 'select-' + guidFor(this);
|
|
26
|
+
this.listId = `listbox-${this.selectId}`;
|
|
27
|
+
|
|
28
|
+
if (!this.args.isComputeWidthDisabled) {
|
|
29
|
+
this.elementHelper.waitForElement(this.listId).then((elementList) => {
|
|
30
|
+
const baseFontRemRatio = Number(
|
|
31
|
+
getComputedStyle(document.querySelector('html')).fontSize.match(/\d+(\.\d+)?/)[0],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const listWidth = elementList.parentNode.getBoundingClientRect().width;
|
|
35
|
+
const selectWidth = listWidth / baseFontRemRatio;
|
|
36
|
+
|
|
37
|
+
const element = document.getElementById(`container-${this.selectId}`);
|
|
38
|
+
element.style.setProperty('--pix-select-width', `${selectWidth}rem`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get displayDefaultOption() {
|
|
44
|
+
return !this.searchValue && !this.args.hideDefaultOption;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get className() {
|
|
48
|
+
const classes = ['pix-select-button'];
|
|
49
|
+
if (this.args.className) {
|
|
50
|
+
classes.push(this.args.className);
|
|
51
|
+
}
|
|
52
|
+
if (this.args.errorMessage) {
|
|
53
|
+
classes.push('pix-select-button--error');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return classes.join(' ');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get isAriaExpanded() {
|
|
60
|
+
return this.isExpanded ? 'true' : 'false';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get placeholder() {
|
|
64
|
+
if (!this.args.value) return this.args.placeholder;
|
|
65
|
+
const option = this.args.options.find((option) => option.value === this.args.value);
|
|
66
|
+
return option ? option.label : this.args.placeholder;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get defaultOption() {
|
|
70
|
+
return {
|
|
71
|
+
value: '',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@action
|
|
76
|
+
toggleDropdown(event) {
|
|
77
|
+
if (this.isExpanded) {
|
|
78
|
+
this.hideDropdown(event);
|
|
79
|
+
} else {
|
|
80
|
+
this.showDropdown(event);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@action
|
|
85
|
+
showDropdown(event) {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
if (this.args.isDisabled) return;
|
|
88
|
+
|
|
89
|
+
this.isExpanded = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@action
|
|
93
|
+
hideDropdown(event) {
|
|
94
|
+
if (this.isExpanded) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
|
|
97
|
+
this.isExpanded = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@action
|
|
102
|
+
onChange(option, event) {
|
|
103
|
+
if (this.args.isDisabled) return;
|
|
104
|
+
|
|
105
|
+
this.args.onChange(option.value);
|
|
106
|
+
|
|
107
|
+
this.hideDropdown(event);
|
|
108
|
+
document.getElementById(this.selectId).focus();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@action
|
|
112
|
+
setSearchValue(event) {
|
|
113
|
+
this.searchValue = event.target.value.trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@action
|
|
117
|
+
lockTab(event) {
|
|
118
|
+
if (event.code === 'Tab' && this.isExpanded) {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
if (this.args.isSearchable) document.getElementById(this.searchId).focus();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@action
|
|
125
|
+
focus(event) {
|
|
126
|
+
if (!event.target) return;
|
|
127
|
+
if (!this.isExpanded) return;
|
|
128
|
+
|
|
129
|
+
if (this.args.value) {
|
|
130
|
+
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
131
|
+
} else if (this.args.isSearchable) {
|
|
132
|
+
event.target.querySelector(`#${this.searchId}`)?.focus();
|
|
133
|
+
} else if (this.displayDefaultOption) {
|
|
134
|
+
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
<template>
|
|
139
|
+
<div
|
|
140
|
+
class="pix-select {{if @inlineLabel ' pix-select--inline'}}"
|
|
141
|
+
id="container-{{this.selectId}}"
|
|
142
|
+
{{onClickOutsideModifier this.hideDropdown}}
|
|
143
|
+
{{onArrowDownUpAction this.listId this.showDropdown this.isExpanded}}
|
|
144
|
+
{{onEscapeAction this.hideDropdown this.selectId}}
|
|
145
|
+
{{on "keydown" this.lockTab}}
|
|
146
|
+
...attributes
|
|
147
|
+
>
|
|
148
|
+
{{#if (has-block "label")}}
|
|
149
|
+
<PixLabel
|
|
150
|
+
@for={{this.selectId}}
|
|
151
|
+
@requiredLabel={{@requiredLabel}}
|
|
152
|
+
@subLabel={{@subLabel}}
|
|
153
|
+
@size={{@size}}
|
|
154
|
+
@screenReaderOnly={{@screenReaderOnly}}
|
|
155
|
+
@inlineLabel={{@inlineLabel}}
|
|
156
|
+
>
|
|
157
|
+
{{yield to="label"}}
|
|
158
|
+
</PixLabel>
|
|
159
|
+
{{/if}}
|
|
160
|
+
|
|
161
|
+
<FloatingUI @placement="bottom" as |reference floating|>
|
|
162
|
+
<div>
|
|
163
|
+
<button
|
|
164
|
+
{{reference}}
|
|
165
|
+
type="button"
|
|
166
|
+
id={{this.selectId}}
|
|
167
|
+
class={{this.className}}
|
|
168
|
+
{{on "click" this.toggleDropdown}}
|
|
169
|
+
aria-expanded={{this.isAriaExpanded}}
|
|
170
|
+
aria-controls={{this.listId}}
|
|
171
|
+
aria-disabled={{@isDisabled}}
|
|
172
|
+
>
|
|
173
|
+
{{#if @iconName}}
|
|
174
|
+
<PixIcon
|
|
175
|
+
@name={{@iconName}}
|
|
176
|
+
@plainIcon={{@plainIcon}}
|
|
177
|
+
@ariaHidden={{true}}
|
|
178
|
+
class="pix-select-button__icon"
|
|
179
|
+
/>
|
|
180
|
+
{{/if}}
|
|
181
|
+
|
|
182
|
+
<span class="pix-select-button__text">{{this.placeholder}}</span>
|
|
183
|
+
|
|
184
|
+
<PixIcon
|
|
185
|
+
class="pix-select-button__dropdown-icon"
|
|
186
|
+
@ariaHidden={{true}}
|
|
187
|
+
@name={{if this.isExpanded "chevronTop" "chevronBottom"}}
|
|
188
|
+
/>
|
|
189
|
+
</button>
|
|
190
|
+
|
|
191
|
+
<div class="pix-select__floating-container" {{floating}}>
|
|
192
|
+
<div
|
|
193
|
+
class="pix-select__dropdown
|
|
194
|
+
{{unless this.isExpanded ' pix-select__dropdown--closed'}}"
|
|
195
|
+
{{on "transitionend" this.focus}}
|
|
196
|
+
>
|
|
197
|
+
{{#if @isSearchable}}
|
|
198
|
+
<div class="pix-select__search">
|
|
199
|
+
<PixIcon class="pix-select-search__icon" @name="search" @ariaHidden={{true}} />
|
|
200
|
+
<label class="screen-reader-only" for={{this.searchId}}>{{@searchLabel}}</label>
|
|
201
|
+
<input
|
|
202
|
+
class="pix-select-search__input"
|
|
203
|
+
id={{this.searchId}}
|
|
204
|
+
autocomplete="off"
|
|
205
|
+
tabindex={{if this.isExpanded "0" "-1"}}
|
|
206
|
+
placeholder={{@searchPlaceholder}}
|
|
207
|
+
{{on "input" this.setSearchValue}}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
{{/if}}
|
|
211
|
+
<PixSelectList
|
|
212
|
+
@hideDefaultOption={{@hideDefaultOption}}
|
|
213
|
+
@listId={{this.listId}}
|
|
214
|
+
@value={{@value}}
|
|
215
|
+
@displayDefaultOption={{this.displayDefaultOption}}
|
|
216
|
+
@searchValue={{this.searchValue}}
|
|
217
|
+
@onChange={{this.onChange}}
|
|
218
|
+
@defaultOption={{this.defaultOption}}
|
|
219
|
+
@selectId={{this.selectId}}
|
|
220
|
+
@isExpanded={{this.isExpanded}}
|
|
221
|
+
@options={{@options}}
|
|
222
|
+
@defaultOptionValue={{@placeholder}}
|
|
223
|
+
@emptySearchMessage={{@emptySearchMessage}}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
{{#if @errorMessage}}
|
|
229
|
+
<p class="pix-select__error-message">{{@errorMessage}}</p>
|
|
230
|
+
{{/if}}
|
|
231
|
+
</div>
|
|
232
|
+
</FloatingUI>
|
|
233
|
+
</div>
|
|
234
|
+
</template>
|
|
235
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
export default modifier((element, [elementId, callback, isExpanded]) => {
|
|
4
|
+
const elementToTarget = document.getElementById(elementId);
|
|
5
|
+
|
|
6
|
+
element.addEventListener('keydown', handleKeyDown);
|
|
7
|
+
|
|
8
|
+
return () => {
|
|
9
|
+
element.removeEventListener('keydown', handleKeyDown);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function handleKeyDown(event) {
|
|
13
|
+
const ARROW_UP_KEY = 'ArrowUp';
|
|
14
|
+
const ARROW_DOWN_KEY = 'ArrowDown';
|
|
15
|
+
|
|
16
|
+
if (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
|
|
21
|
+
const focusElement = () => {
|
|
22
|
+
const focusableElements = findFocusableElements(elementToTarget);
|
|
23
|
+
|
|
24
|
+
const [firstFocusableElement] = focusableElements;
|
|
25
|
+
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
26
|
+
|
|
27
|
+
const activeIndexElement = focusableElements.findIndex((elementToTarget) => {
|
|
28
|
+
return document.activeElement === elementToTarget;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const handleArrowDown = () => {
|
|
32
|
+
if (
|
|
33
|
+
!isExpanded ||
|
|
34
|
+
document.activeElement === lastFocusableElement ||
|
|
35
|
+
activeIndexElement === -1
|
|
36
|
+
) {
|
|
37
|
+
firstFocusableElement?.focus();
|
|
38
|
+
} else {
|
|
39
|
+
focusableElements[activeIndexElement + 1].focus();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleArrowUp = () => {
|
|
44
|
+
if (
|
|
45
|
+
!isExpanded ||
|
|
46
|
+
document.activeElement === firstFocusableElement ||
|
|
47
|
+
activeIndexElement === -1
|
|
48
|
+
) {
|
|
49
|
+
lastFocusableElement?.focus();
|
|
50
|
+
} else {
|
|
51
|
+
focusableElements[activeIndexElement - 1].focus();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (ARROW_UP_KEY === event.key) {
|
|
56
|
+
handleArrowUp();
|
|
57
|
+
} else if (ARROW_DOWN_KEY === event.key) {
|
|
58
|
+
handleArrowDown();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (!isExpanded) {
|
|
63
|
+
elementToTarget.addEventListener('transitionend', focusElement);
|
|
64
|
+
|
|
65
|
+
callback(event);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
elementToTarget.removeEventListener('transitionend', focusElement);
|
|
69
|
+
};
|
|
70
|
+
} else {
|
|
71
|
+
focusElement(elementToTarget);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function findFocusableElements(element) {
|
|
77
|
+
return [
|
|
78
|
+
...element.querySelectorAll(
|
|
79
|
+
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
|
|
80
|
+
),
|
|
81
|
+
].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
|
|
82
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
export default modifier((element, [callback = null, focusId = null]) => {
|
|
4
|
+
function handleKeyUp(event) {
|
|
5
|
+
const ENTER_KEY = 'Enter';
|
|
6
|
+
|
|
7
|
+
if (event.key !== ENTER_KEY) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (element.type === 'checkbox') {
|
|
12
|
+
element.checked = !element.checked;
|
|
13
|
+
element.dispatchEvent(new Event('change'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (focusId) {
|
|
17
|
+
document.getElementById(focusId).focus();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (callback) callback(event);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
element.addEventListener('keydown', handleKeyUp);
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
element.removeEventListener('keydown', handleKeyUp);
|
|
27
|
+
};
|
|
28
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
export default modifier((element, [callback, focusId = null]) => {
|
|
4
|
+
function handleKeyUp(event) {
|
|
5
|
+
const ESCAPE_KEY = 'Escape';
|
|
6
|
+
|
|
7
|
+
if (event.key !== ESCAPE_KEY) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
callback(event);
|
|
12
|
+
|
|
13
|
+
if (focusId) {
|
|
14
|
+
document.getElementById(focusId).focus();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
element.addEventListener('keyup', handleKeyUp);
|
|
19
|
+
|
|
20
|
+
return () => {
|
|
21
|
+
element.removeEventListener('keyup', handleKeyUp);
|
|
22
|
+
};
|
|
23
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
export default modifier((element, [callback]) => {
|
|
4
|
+
const listener = (event) => handleKeyUp(event, callback);
|
|
5
|
+
element.addEventListener('keydown', listener);
|
|
6
|
+
|
|
7
|
+
return () => {
|
|
8
|
+
element.removeEventListener('keydown', listener);
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function handleKeyUp(event, callback) {
|
|
13
|
+
const SPACE_KEY = ' ';
|
|
14
|
+
|
|
15
|
+
if (event.key !== SPACE_KEY) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
callback(event);
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
export default modifier(function onWindowResize(element, [action]) {
|
|
4
|
+
const actionWithElement = () => action(element);
|
|
5
|
+
|
|
6
|
+
window.addEventListener('resize', actionWithElement);
|
|
7
|
+
actionWithElement();
|
|
8
|
+
|
|
9
|
+
return () => {
|
|
10
|
+
window.removeEventListener('resize', actionWithElement);
|
|
11
|
+
};
|
|
12
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
let sourceActiveElement = null;
|
|
4
|
+
|
|
5
|
+
export default modifier(function trapFocus(element, [isOpen, focusOnClose = true]) {
|
|
6
|
+
const [firstFocusableElement] = findFocusableElements(element);
|
|
7
|
+
|
|
8
|
+
if (isOpen) {
|
|
9
|
+
preventPageScrolling();
|
|
10
|
+
sourceActiveElement = document.activeElement;
|
|
11
|
+
focusElement(firstFocusableElement, element);
|
|
12
|
+
} else if (sourceActiveElement) {
|
|
13
|
+
allowPageScrolling();
|
|
14
|
+
|
|
15
|
+
if (focusOnClose) {
|
|
16
|
+
focusElement(sourceActiveElement, element);
|
|
17
|
+
}
|
|
18
|
+
sourceActiveElement = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
element.addEventListener('keydown', (event) => {
|
|
22
|
+
handleKeyDown(event, element);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
element.removeEventListener('keydown', (event) => {
|
|
27
|
+
handleKeyDown(event, element);
|
|
28
|
+
});
|
|
29
|
+
allowPageScrolling();
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function findFocusableElements(element) {
|
|
34
|
+
return [
|
|
35
|
+
...element.querySelectorAll(
|
|
36
|
+
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
|
|
37
|
+
),
|
|
38
|
+
].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function focusElement(elementToFocus, element) {
|
|
42
|
+
let focusOnce = false;
|
|
43
|
+
|
|
44
|
+
const handleTransitionEnd = () => {
|
|
45
|
+
if (!focusOnce) {
|
|
46
|
+
elementToFocus.focus();
|
|
47
|
+
focusOnce = true;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (hasTransitionDuration(element)) {
|
|
52
|
+
element.addEventListener('transitionend', handleTransitionEnd);
|
|
53
|
+
} else if (hasAnimationDuration(element)) {
|
|
54
|
+
element.addEventListener('animationend', handleTransitionEnd);
|
|
55
|
+
} else {
|
|
56
|
+
elementToFocus.focus();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
if (hasTransitionDuration(element)) {
|
|
61
|
+
element.removeEventListener('transitionend', handleTransitionEnd);
|
|
62
|
+
} else if (hasAnimationDuration(element)) {
|
|
63
|
+
element.removeEventListener('animationend', handleTransitionEnd);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function preventPageScrolling() {
|
|
69
|
+
document.body.classList.add('body__trap-focus');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function allowPageScrolling() {
|
|
73
|
+
document.body.classList.remove('body__trap-focus');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hasTransitionDuration(element) {
|
|
77
|
+
return hasDurationByKey(element, 'transition-duration');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasAnimationDuration(element) {
|
|
81
|
+
return hasDurationByKey(element, 'animation-duration');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasDurationByKey(element, key) {
|
|
85
|
+
return !['', '0s'].includes(getComputedStyle(element, null).getPropertyValue(key));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleKeyDown(event, element) {
|
|
89
|
+
const TAB_KEY = 'Tab';
|
|
90
|
+
const focusableElements = findFocusableElements(element);
|
|
91
|
+
const [firstFocusableElement] = focusableElements;
|
|
92
|
+
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
93
|
+
|
|
94
|
+
if (event.key !== TAB_KEY) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const handleBackwardTab = () => {
|
|
99
|
+
if (document.activeElement === firstFocusableElement) {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
lastFocusableElement.focus();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleForwardTab = () => {
|
|
106
|
+
if (document.activeElement === lastFocusableElement) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
firstFocusableElement.focus();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (event.shiftKey) {
|
|
113
|
+
handleBackwardTab();
|
|
114
|
+
} else {
|
|
115
|
+
handleForwardTab();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
flex-direction: column;
|
|
9
9
|
gap: var(--pix-spacing-1x);
|
|
10
10
|
width: var(--pix-select-width);
|
|
11
|
+
min-width: fit-content;
|
|
11
12
|
max-width: 100%;
|
|
12
13
|
|
|
13
14
|
&--inline {
|
|
@@ -16,13 +17,15 @@
|
|
|
16
17
|
align-items: center;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
&__floating-container {
|
|
21
|
+
z-index: 200;
|
|
22
|
+
width: min(var(--pix-select-width), 100%);
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
&__dropdown {
|
|
20
26
|
@extend %pix-shadow-xs;
|
|
21
27
|
|
|
22
|
-
position: absolute;
|
|
23
|
-
z-index: 200;
|
|
24
28
|
width: 100%;
|
|
25
|
-
min-width: fit-content;
|
|
26
29
|
max-height: 12rem;
|
|
27
30
|
margin-top: var(--pix-spacing-1x);
|
|
28
31
|
padding: 0;
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
background-color: var(--pix-neutral-0);
|
|
33
36
|
border-top: none;
|
|
34
37
|
border-radius: 0 0 var(--pix-spacing-1x) var(--pix-spacing-1x);
|
|
35
|
-
transition:
|
|
38
|
+
transition: visibility 0.2s ease-in-out, opacity 0.2s ease-in-out, max-height 0s;
|
|
36
39
|
|
|
37
40
|
&::-webkit-scrollbar {
|
|
38
41
|
width: 0.5rem;
|
|
@@ -55,8 +58,10 @@
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
&--closed {
|
|
61
|
+
max-height: 0;
|
|
58
62
|
visibility: hidden;
|
|
59
63
|
opacity: 0;
|
|
64
|
+
transition: visibility 0.2s ease-in-out, opacity 0.2s ease-in-out, max-height 0s 0.2s;
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -1,82 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default modifier((element, [elementId, callback, isExpanded]) => {
|
|
4
|
-
const elementToTarget = document.getElementById(elementId);
|
|
5
|
-
|
|
6
|
-
element.addEventListener('keydown', handleKeyDown);
|
|
7
|
-
|
|
8
|
-
return () => {
|
|
9
|
-
element.removeEventListener('keydown', handleKeyDown);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function handleKeyDown(event) {
|
|
13
|
-
const ARROW_UP_KEY = 'ArrowUp';
|
|
14
|
-
const ARROW_DOWN_KEY = 'ArrowDown';
|
|
15
|
-
|
|
16
|
-
if (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
event.preventDefault();
|
|
20
|
-
|
|
21
|
-
const focusElement = () => {
|
|
22
|
-
const focusableElements = findFocusableElements(elementToTarget);
|
|
23
|
-
|
|
24
|
-
const [firstFocusableElement] = focusableElements;
|
|
25
|
-
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
26
|
-
|
|
27
|
-
const activeIndexElement = focusableElements.findIndex((elementToTarget) => {
|
|
28
|
-
return document.activeElement === elementToTarget;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const handleArrowDown = () => {
|
|
32
|
-
if (
|
|
33
|
-
!isExpanded ||
|
|
34
|
-
document.activeElement === lastFocusableElement ||
|
|
35
|
-
activeIndexElement === -1
|
|
36
|
-
) {
|
|
37
|
-
firstFocusableElement?.focus();
|
|
38
|
-
} else {
|
|
39
|
-
focusableElements[activeIndexElement + 1].focus();
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const handleArrowUp = () => {
|
|
44
|
-
if (
|
|
45
|
-
!isExpanded ||
|
|
46
|
-
document.activeElement === firstFocusableElement ||
|
|
47
|
-
activeIndexElement === -1
|
|
48
|
-
) {
|
|
49
|
-
lastFocusableElement?.focus();
|
|
50
|
-
} else {
|
|
51
|
-
focusableElements[activeIndexElement - 1].focus();
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (ARROW_UP_KEY === event.key) {
|
|
56
|
-
handleArrowUp();
|
|
57
|
-
} else if (ARROW_DOWN_KEY === event.key) {
|
|
58
|
-
handleArrowDown();
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if (!isExpanded) {
|
|
63
|
-
elementToTarget.addEventListener('transitionend', focusElement);
|
|
64
|
-
|
|
65
|
-
callback(event);
|
|
66
|
-
|
|
67
|
-
return () => {
|
|
68
|
-
elementToTarget.removeEventListener('transitionend', focusElement);
|
|
69
|
-
};
|
|
70
|
-
} else {
|
|
71
|
-
focusElement(elementToTarget);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
function findFocusableElements(element) {
|
|
77
|
-
return [
|
|
78
|
-
...element.querySelectorAll(
|
|
79
|
-
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
|
|
80
|
-
),
|
|
81
|
-
].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
|
|
82
|
-
}
|
|
1
|
+
export { default } from '@1024pix/pix-ui/modifiers/on-arrow-down-up-action';
|
|
@@ -1,28 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default modifier((element, [callback = null, focusId = null]) => {
|
|
4
|
-
function handleKeyUp(event) {
|
|
5
|
-
const ENTER_KEY = 'Enter';
|
|
6
|
-
|
|
7
|
-
if (event.key !== ENTER_KEY) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
if (element.type === 'checkbox') {
|
|
12
|
-
element.checked = !element.checked;
|
|
13
|
-
element.dispatchEvent(new Event('change'));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (focusId) {
|
|
17
|
-
document.getElementById(focusId).focus();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (callback) callback(event);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
element.addEventListener('keydown', handleKeyUp);
|
|
24
|
-
|
|
25
|
-
return () => {
|
|
26
|
-
element.removeEventListener('keydown', handleKeyUp);
|
|
27
|
-
};
|
|
28
|
-
});
|
|
1
|
+
export { default } from '@1024pix/pix-ui/modifiers/on-enter-action';
|
|
@@ -1,23 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default modifier((element, [callback, focusId = null]) => {
|
|
4
|
-
function handleKeyUp(event) {
|
|
5
|
-
const ESCAPE_KEY = 'Escape';
|
|
6
|
-
|
|
7
|
-
if (event.key !== ESCAPE_KEY) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
callback(event);
|
|
12
|
-
|
|
13
|
-
if (focusId) {
|
|
14
|
-
document.getElementById(focusId).focus();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
element.addEventListener('keyup', handleKeyUp);
|
|
19
|
-
|
|
20
|
-
return () => {
|
|
21
|
-
element.removeEventListener('keyup', handleKeyUp);
|
|
22
|
-
};
|
|
23
|
-
});
|
|
1
|
+
export { default } from '@1024pix/pix-ui/modifiers/on-escape-action';
|
|
@@ -1,20 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default modifier((element, [callback]) => {
|
|
4
|
-
const listener = (event) => handleKeyUp(event, callback);
|
|
5
|
-
element.addEventListener('keydown', listener);
|
|
6
|
-
|
|
7
|
-
return () => {
|
|
8
|
-
element.removeEventListener('keydown', listener);
|
|
9
|
-
};
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
function handleKeyUp(event, callback) {
|
|
13
|
-
const SPACE_KEY = ' ';
|
|
14
|
-
|
|
15
|
-
if (event.key !== SPACE_KEY) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
callback(event);
|
|
20
|
-
}
|
|
1
|
+
export { default } from '@1024pix/pix-ui/modifiers/on-space-action';
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default modifier(function onWindowResize(element, [action]) {
|
|
4
|
-
const actionWithElement = () => action(element);
|
|
5
|
-
|
|
6
|
-
window.addEventListener('resize', actionWithElement);
|
|
7
|
-
actionWithElement();
|
|
8
|
-
|
|
9
|
-
return () => {
|
|
10
|
-
window.removeEventListener('resize', actionWithElement);
|
|
11
|
-
};
|
|
12
|
-
});
|
|
1
|
+
export { default } from '@1024pix/pix-ui/modifiers/on-window-resize';
|
|
@@ -1,117 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
let sourceActiveElement = null;
|
|
4
|
-
|
|
5
|
-
export default modifier(function trapFocus(element, [isOpen, focusOnClose = true]) {
|
|
6
|
-
const [firstFocusableElement] = findFocusableElements(element);
|
|
7
|
-
|
|
8
|
-
if (isOpen) {
|
|
9
|
-
preventPageScrolling();
|
|
10
|
-
sourceActiveElement = document.activeElement;
|
|
11
|
-
focusElement(firstFocusableElement, element);
|
|
12
|
-
} else if (sourceActiveElement) {
|
|
13
|
-
allowPageScrolling();
|
|
14
|
-
|
|
15
|
-
if (focusOnClose) {
|
|
16
|
-
focusElement(sourceActiveElement, element);
|
|
17
|
-
}
|
|
18
|
-
sourceActiveElement = null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
element.addEventListener('keydown', (event) => {
|
|
22
|
-
handleKeyDown(event, element);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return () => {
|
|
26
|
-
element.removeEventListener('keydown', (event) => {
|
|
27
|
-
handleKeyDown(event, element);
|
|
28
|
-
});
|
|
29
|
-
allowPageScrolling();
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
function findFocusableElements(element) {
|
|
34
|
-
return [
|
|
35
|
-
...element.querySelectorAll(
|
|
36
|
-
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
|
|
37
|
-
),
|
|
38
|
-
].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function focusElement(elementToFocus, element) {
|
|
42
|
-
let focusOnce = false;
|
|
43
|
-
|
|
44
|
-
const handleTransitionEnd = () => {
|
|
45
|
-
if (!focusOnce) {
|
|
46
|
-
elementToFocus.focus();
|
|
47
|
-
focusOnce = true;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (hasTransitionDuration(element)) {
|
|
52
|
-
element.addEventListener('transitionend', handleTransitionEnd);
|
|
53
|
-
} else if (hasAnimationDuration(element)) {
|
|
54
|
-
element.addEventListener('animationend', handleTransitionEnd);
|
|
55
|
-
} else {
|
|
56
|
-
elementToFocus.focus();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return () => {
|
|
60
|
-
if (hasTransitionDuration(element)) {
|
|
61
|
-
element.removeEventListener('transitionend', handleTransitionEnd);
|
|
62
|
-
} else if (hasAnimationDuration(element)) {
|
|
63
|
-
element.removeEventListener('animationend', handleTransitionEnd);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function preventPageScrolling() {
|
|
69
|
-
document.body.classList.add('body__trap-focus');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function allowPageScrolling() {
|
|
73
|
-
document.body.classList.remove('body__trap-focus');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function hasTransitionDuration(element) {
|
|
77
|
-
return hasDurationByKey(element, 'transition-duration');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function hasAnimationDuration(element) {
|
|
81
|
-
return hasDurationByKey(element, 'animation-duration');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function hasDurationByKey(element, key) {
|
|
85
|
-
return !['', '0s'].includes(getComputedStyle(element, null).getPropertyValue(key));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function handleKeyDown(event, element) {
|
|
89
|
-
const TAB_KEY = 'Tab';
|
|
90
|
-
const focusableElements = findFocusableElements(element);
|
|
91
|
-
const [firstFocusableElement] = focusableElements;
|
|
92
|
-
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
93
|
-
|
|
94
|
-
if (event.key !== TAB_KEY) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const handleBackwardTab = () => {
|
|
99
|
-
if (document.activeElement === firstFocusableElement) {
|
|
100
|
-
event.preventDefault();
|
|
101
|
-
lastFocusableElement.focus();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const handleForwardTab = () => {
|
|
106
|
-
if (document.activeElement === lastFocusableElement) {
|
|
107
|
-
event.preventDefault();
|
|
108
|
-
firstFocusableElement.focus();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
if (event.shiftKey) {
|
|
113
|
-
handleBackwardTab();
|
|
114
|
-
} else {
|
|
115
|
-
handleForwardTab();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
1
|
+
export { default } from '@1024pix/pix-ui/modifiers/trap-focus';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1024pix/pix-ui",
|
|
3
|
-
"version": "55.18.
|
|
3
|
+
"version": "55.18.2",
|
|
4
4
|
"description": "Pix-UI is the implementation of Pix design principles and guidelines for its products.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ember-addon"
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"ember-lifeline": "^7.0.0",
|
|
73
73
|
"ember-modifier": "^4.2.0",
|
|
74
74
|
"ember-popperjs": "^3.0.0",
|
|
75
|
+
"ember-primitives": "^0.29.0",
|
|
75
76
|
"ember-template-imports": "^4.3.0",
|
|
76
77
|
"ember-truth-helpers": "^4.0.0"
|
|
77
78
|
},
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
<div
|
|
2
|
-
class="pix-select {{if @inlineLabel ' pix-select--inline'}}"
|
|
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>
|
|
24
|
-
<PopperJS @placement={{or @placement "bottom-start"}} as |reference popover|>
|
|
25
|
-
<button
|
|
26
|
-
{{reference}}
|
|
27
|
-
type="button"
|
|
28
|
-
id={{this.selectId}}
|
|
29
|
-
class={{this.className}}
|
|
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,127 +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}rem`);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get displayDefaultOption() {
|
|
34
|
-
return !this.searchValue && !this.args.hideDefaultOption;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get className() {
|
|
38
|
-
const classes = ['pix-select-button'];
|
|
39
|
-
if (this.args.className) {
|
|
40
|
-
classes.push(this.args.className);
|
|
41
|
-
}
|
|
42
|
-
if (this.args.errorMessage) {
|
|
43
|
-
classes.push('pix-select-button--error');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return classes.join(' ');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
get isAriaExpanded() {
|
|
50
|
-
return this.isExpanded ? 'true' : 'false';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get placeholder() {
|
|
54
|
-
if (!this.args.value) return this.args.placeholder;
|
|
55
|
-
const option = this.args.options.find((option) => option.value === this.args.value);
|
|
56
|
-
return option ? option.label : this.args.placeholder;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get defaultOption() {
|
|
60
|
-
return {
|
|
61
|
-
value: '',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@action
|
|
66
|
-
toggleDropdown(event) {
|
|
67
|
-
if (this.isExpanded) {
|
|
68
|
-
this.hideDropdown(event);
|
|
69
|
-
} else {
|
|
70
|
-
this.showDropdown(event);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@action
|
|
75
|
-
showDropdown(event) {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
if (this.args.isDisabled) return;
|
|
78
|
-
|
|
79
|
-
this.isExpanded = true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@action
|
|
83
|
-
hideDropdown(event) {
|
|
84
|
-
if (this.isExpanded) {
|
|
85
|
-
event.preventDefault();
|
|
86
|
-
|
|
87
|
-
this.isExpanded = false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@action
|
|
92
|
-
onChange(option, event) {
|
|
93
|
-
if (this.args.isDisabled) return;
|
|
94
|
-
|
|
95
|
-
this.args.onChange(option.value);
|
|
96
|
-
|
|
97
|
-
this.hideDropdown(event);
|
|
98
|
-
document.getElementById(this.selectId).focus();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
@action
|
|
102
|
-
setSearchValue(event) {
|
|
103
|
-
this.searchValue = event.target.value.trim();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@action
|
|
107
|
-
lockTab(event) {
|
|
108
|
-
if (event.code === 'Tab' && this.isExpanded) {
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
if (this.args.isSearchable) document.getElementById(this.searchId).focus();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
@action
|
|
115
|
-
focus(event) {
|
|
116
|
-
if (!event.target) return;
|
|
117
|
-
if (!this.isExpanded) return;
|
|
118
|
-
|
|
119
|
-
if (this.args.value) {
|
|
120
|
-
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
121
|
-
} else if (this.args.isSearchable) {
|
|
122
|
-
event.target.querySelector(`#${this.searchId}`)?.focus();
|
|
123
|
-
} else if (this.displayDefaultOption) {
|
|
124
|
-
event.target.querySelector("[aria-selected='true']")?.focus();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|