@1024pix/pix-ui 55.25.0 → 55.25.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-app-layout.gjs +57 -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 +20 -0
- package/addon/modifiers/trap-focus.js +117 -0
- package/addon/styles/_pix-app-layout.scss +1 -1
- package/addon/styles/_pix-navigation.scss +4 -1
- 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 +1 -1
- package/addon/components/pix-app-layout.hbs +0 -12
- package/addon/components/pix-app-layout.js +0 -46
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { VARIANTS } from '@1024pix/pix-ui/helpers/variants';
|
|
2
|
+
import { warn } from '@ember/debug';
|
|
3
|
+
import Component from '@glimmer/component';
|
|
4
|
+
|
|
5
|
+
import onWindowResize from '../modifiers/on-window-resize';
|
|
6
|
+
|
|
7
|
+
export default class PixAppLayout extends Component {
|
|
8
|
+
#computeMarginTopElement(entries) {
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
if (entry.target.id === 'pix-layout-banner-container') {
|
|
11
|
+
const baseFontRemRatio = Number(
|
|
12
|
+
getComputedStyle(document.querySelector('html')).fontSize.match(/\d+(\.\d+)?/)[0],
|
|
13
|
+
);
|
|
14
|
+
const bannerHeight = entry.target.getBoundingClientRect().height;
|
|
15
|
+
const top = bannerHeight / baseFontRemRatio;
|
|
16
|
+
|
|
17
|
+
const layoutElement = document.querySelector('.pix-app-layout');
|
|
18
|
+
layoutElement.style.setProperty('--pix-app-layout-top', `${top}rem`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
handleMarginContainerNavigation = (element) => {
|
|
24
|
+
this.#computeMarginTopElement(element);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
get variant() {
|
|
28
|
+
const value = this.args.variant ?? 'primary';
|
|
29
|
+
warn(
|
|
30
|
+
`PixAppLayout: @variant "${value}" should be ${VARIANTS.join(', ')}`,
|
|
31
|
+
VARIANTS.includes(value),
|
|
32
|
+
{
|
|
33
|
+
id: 'pix-ui.pix-app-layout.variant.not-valid',
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
get classNames() {
|
|
40
|
+
return ['pix-app-layout', `pix-app-layout--${this.variant}`].join(' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<div class={{this.classNames}} ...attributes>
|
|
45
|
+
<section
|
|
46
|
+
class="pix-app-layout__banner"
|
|
47
|
+
id="pix-layout-banner-container"
|
|
48
|
+
{{onWindowResize this.handleMarginContainerNavigation}}
|
|
49
|
+
>
|
|
50
|
+
{{yield to="banner"}}
|
|
51
|
+
</section>
|
|
52
|
+
<div class="pix-app-layout__navigation">{{yield to="navigation"}}</div>
|
|
53
|
+
<div class="pix-app-layout__main">{{yield to="main"}}</div>
|
|
54
|
+
<div class="pix-app-layout__footer">{{yield to="footer"}}</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
}
|
|
@@ -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,20 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
const observerMap = new WeakMap();
|
|
4
|
+
|
|
5
|
+
export default modifier(function onWindowResize(element, [changeHandler]) {
|
|
6
|
+
let observer;
|
|
7
|
+
|
|
8
|
+
if (observerMap.has(changeHandler)) {
|
|
9
|
+
observer = observerMap.get(changeHandler);
|
|
10
|
+
} else {
|
|
11
|
+
observer = new ResizeObserver(changeHandler);
|
|
12
|
+
observerMap.set(changeHandler, observer);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
observer.observe(element);
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
observer.unobserve(element);
|
|
19
|
+
};
|
|
20
|
+
});
|
|
@@ -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
|
+
}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
&__main, &__footer {
|
|
44
44
|
width: 100%;
|
|
45
|
-
max-width: min(calc(100vw -
|
|
45
|
+
max-width: min(calc(100vw - var(--pix-navigation-width) - 3 * var(--pix-spacing-6x)), 93rem);
|
|
46
46
|
margin: 0 auto;
|
|
47
47
|
padding: 0 var(--pix-spacing-6x);
|
|
48
48
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
@use "pix-design-tokens/breakpoints";
|
|
2
2
|
@use "pix-design-tokens/typography";
|
|
3
3
|
|
|
4
|
+
:root {
|
|
5
|
+
--pix-navigation-width: 15rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
.pix-navigation {
|
|
5
9
|
--bg-color-focus: rgb(var(--pix-neutral-100-inline), 50%);
|
|
6
10
|
--bg-color-active: rgb(var(--pix-neutral-100-inline), 30%);
|
|
7
|
-
--pix-navigation-width: 15rem;
|
|
8
11
|
|
|
9
12
|
display: flex;
|
|
10
13
|
flex-direction: column;
|
|
@@ -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,12 +0,0 @@
|
|
|
1
|
-
<div class={{this.classNames}} ...attributes>
|
|
2
|
-
<section
|
|
3
|
-
class="pix-app-layout__banner"
|
|
4
|
-
id="pix-layout-banner-container"
|
|
5
|
-
{{on-window-resize this.handleMarginContainerNavigation}}
|
|
6
|
-
>
|
|
7
|
-
{{yield to="banner"}}
|
|
8
|
-
</section>
|
|
9
|
-
<div class="pix-app-layout__navigation">{{yield to="navigation"}}</div>
|
|
10
|
-
<div class="pix-app-layout__main">{{yield to="main"}}</div>
|
|
11
|
-
<div class="pix-app-layout__footer">{{yield to="footer"}}</div>
|
|
12
|
-
</div>
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { VARIANTS } from '@1024pix/pix-ui/helpers/variants';
|
|
2
|
-
import { warn } from '@ember/debug';
|
|
3
|
-
import { service } from '@ember/service';
|
|
4
|
-
import Component from '@glimmer/component';
|
|
5
|
-
export default class PixAppLayout extends Component {
|
|
6
|
-
@service elementHelper;
|
|
7
|
-
|
|
8
|
-
constructor(...args) {
|
|
9
|
-
super(...args);
|
|
10
|
-
|
|
11
|
-
this.elementHelper.waitForElement('pix-layout-banner-container').then((elementList) => {
|
|
12
|
-
this.#computeMarginTopElement(elementList);
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
#computeMarginTopElement(bannerContainer) {
|
|
17
|
-
const baseFontRemRatio = Number(
|
|
18
|
-
getComputedStyle(document.querySelector('html')).fontSize.match(/\d+(\.\d+)?/)[0],
|
|
19
|
-
);
|
|
20
|
-
const bannerHeight = bannerContainer.getBoundingClientRect().height;
|
|
21
|
-
const top = bannerHeight / baseFontRemRatio;
|
|
22
|
-
|
|
23
|
-
const layoutElement = document.querySelector('.pix-app-layout');
|
|
24
|
-
layoutElement.style.setProperty('--pix-app-layout-top', `${top}rem`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
handleMarginContainerNavigation = (element) => {
|
|
28
|
-
this.#computeMarginTopElement(element);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
get variant() {
|
|
32
|
-
const value = this.args.variant ?? 'primary';
|
|
33
|
-
warn(
|
|
34
|
-
`PixAppLayout: @variant "${value}" should be ${VARIANTS.join(', ')}`,
|
|
35
|
-
VARIANTS.includes(value),
|
|
36
|
-
{
|
|
37
|
-
id: 'pix-ui.pix-app-layout.variant.not-valid',
|
|
38
|
-
},
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
return value;
|
|
42
|
-
}
|
|
43
|
-
get classNames() {
|
|
44
|
-
return ['pix-app-layout', `pix-app-layout--${this.variant}`].join(' ');
|
|
45
|
-
}
|
|
46
|
-
}
|