@oscarpalmer/oui 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/css/button.css +0 -3
- package/dist/css/popover.css +9 -0
- package/dist/css/splitter.css +78 -0
- package/dist/css/tooltip.css +0 -3
- package/dist/js/floatable.cjs +22 -9
- package/dist/js/floatable.js +22 -9
- package/dist/js/focus-trap/embedded.cjs +150 -0
- package/dist/js/focus-trap/embedded.js +150 -0
- package/dist/js/focus-trap/standalone.cjs +8 -0
- package/dist/js/focus-trap/standalone.js +7 -0
- package/dist/js/index.cjs +5 -1
- package/dist/js/index.js +6 -1
- package/dist/js/popover.cjs +160 -0
- package/dist/js/popover.js +160 -0
- package/dist/js/splitter.cjs +211 -0
- package/dist/js/splitter.js +211 -0
- package/dist/js/tooltip.cjs +2 -3
- package/dist/js/tooltip.js +2 -3
- package/package.json +25 -5
- package/src/css/button.scss +0 -3
- package/src/css/popover.scss +9 -0
- package/src/css/splitter.scss +87 -0
- package/src/css/tooltip.scss +0 -3
- package/src/js/floatable.ts +53 -25
- package/src/js/focus-trap/embedded.ts +215 -0
- package/src/js/focus-trap/standalone.ts +9 -0
- package/src/js/index.ts +3 -1
- package/src/js/popover.ts +221 -0
- package/src/js/splitter.ts +317 -0
- package/src/js/tooltip.ts +2 -4
- package/types/floatable.d.cts +2 -2
- package/types/floatable.d.ts +2 -2
- package/types/focus-trap/embedded.d.cts +15 -0
- package/types/focus-trap/embedded.d.ts +11 -0
- package/types/index.d.cts +11 -0
- package/types/index.d.ts +3 -1
- package/types/popover.d.cts +14 -0
- package/types/popover.d.ts +15 -0
- package/types/splitter.d.cts +10 -0
- package/types/splitter.d.ts +11 -0
- package/dist/js/focus-trap.cjs +0 -104
- package/dist/js/focus-trap.js +0 -103
- package/src/js/focus-trap.ts +0 -133
- /package/types/{focus-trap.d.cts → focus-trap/standalone.d.cts} +0 -0
- /package/types/{focus-trap.d.ts → focus-trap/standalone.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Oui
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@oscarpalmer/oui)
|
|
4
|
+
|
|
3
5
|
_Oui_ _(Oscar UI)_ is a collection of styles, components, and related thingamjigs for designing websites and applications, which I often use to build prototypes and proof-of-concepts for myself and at work.
|
|
4
6
|
|
|
5
7
|
## License
|
package/dist/css/button.css
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
:where([oui-popover-content]) {
|
|
3
|
+
box-shadow: inset 0 1px 0 0 transparent, 0 0 0 1px rgba(0, 0, 0, 0.05), 0 1px 2px -0.5px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.05), 0 4px 8px -2px rgba(0, 0, 0, 0.05), 0 8px 16px -4px rgba(0, 0, 0, 0.05), 0 16px 32px -8px rgba(0, 0, 0, 0.05), 0 32px 64px -16px rgba(0, 0, 0, 0.05), 0 64px 128px -32px rgba(0, 0, 0, 0.05);
|
|
4
|
+
padding: 1rem;
|
|
5
|
+
background-color: var(--oui-absolute);
|
|
6
|
+
border-radius: 0.5rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
:where(oui-splitter) {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-flow: row nowrap;
|
|
5
|
+
flex: 1;
|
|
6
|
+
}
|
|
7
|
+
:where(oui-splitter > :first-child) {
|
|
8
|
+
flex: var(--oui-splitter-size, 0.5);
|
|
9
|
+
}
|
|
10
|
+
:where(oui-splitter > :last-child) {
|
|
11
|
+
flex: calc(1 - var(--oui-splitter-size, 0.5));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:where([oui-splitter-separator]) {
|
|
15
|
+
width: 0.25rem;
|
|
16
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
17
|
+
border-radius: 0.25rem;
|
|
18
|
+
position: relative;
|
|
19
|
+
transition: background 0.125s ease-in-out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:where([oui-splitter-separator]:has([oui-splitter-handle]:focus)),
|
|
23
|
+
:where([oui-splitter-separator]:has([oui-splitter-handle]:hover)),
|
|
24
|
+
:where([oui-splitter-separator][oui-splitter-active]) {
|
|
25
|
+
background-color: rgba(0, 0, 255, 0.1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:where([oui-splitter-handle]) {
|
|
29
|
+
width: 2rem;
|
|
30
|
+
height: 4rem;
|
|
31
|
+
padding: 0.875rem;
|
|
32
|
+
position: absolute;
|
|
33
|
+
inset: 50% auto auto 50%;
|
|
34
|
+
transform: translate3d(-50%, -50%, 0);
|
|
35
|
+
cursor: ew-resize;
|
|
36
|
+
}
|
|
37
|
+
:where([oui-splitter-handle]):focus {
|
|
38
|
+
outline: none;
|
|
39
|
+
}
|
|
40
|
+
:where([oui-splitter-handle])::after {
|
|
41
|
+
content: "";
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 100%;
|
|
44
|
+
display: block;
|
|
45
|
+
background-color: rgba(0, 0, 0, 0.25);
|
|
46
|
+
outline: 0.25rem solid transparent;
|
|
47
|
+
outline-offset: 0.25rem;
|
|
48
|
+
border-radius: 0.25rem;
|
|
49
|
+
transition: background 0.125s ease-in-out, outline 0.125s ease-in-out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:where([oui-splitter-handle]:focus)::after,
|
|
53
|
+
:where([oui-splitter-handle]:hover)::after,
|
|
54
|
+
:where([oui-splitter-handle][oui-splitter-active])::after {
|
|
55
|
+
background-color: rgba(0, 0, 255, 0.25);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:where([oui-splitter-handle]:focus-visible)::after {
|
|
59
|
+
outline-color: rgba(0, 0, 255, 0.25);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:where(oui-splitter[type=horizontal i],
|
|
63
|
+
oui-splitter[oui-splitter-horizontal]) {
|
|
64
|
+
flex-flow: column nowrap;
|
|
65
|
+
}
|
|
66
|
+
:where(oui-splitter[type=horizontal i] [oui-splitter-separator],
|
|
67
|
+
oui-splitter[oui-splitter-horizontal] [oui-splitter-separator]) {
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: 0.25rem;
|
|
70
|
+
}
|
|
71
|
+
:where(oui-splitter[type=horizontal i] [oui-splitter-handle],
|
|
72
|
+
oui-splitter[oui-splitter-horizontal] [oui-splitter-handle]) {
|
|
73
|
+
width: 4rem;
|
|
74
|
+
height: 2rem;
|
|
75
|
+
cursor: ns-resize;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
package/dist/css/tooltip.css
CHANGED
package/dist/js/floatable.cjs
CHANGED
|
@@ -4,6 +4,7 @@ class Floatable {
|
|
|
4
4
|
constructor(anchor, content) {
|
|
5
5
|
this.anchor = anchor;
|
|
6
6
|
this.content = content;
|
|
7
|
+
setStyles(content);
|
|
7
8
|
}
|
|
8
9
|
activate;
|
|
9
10
|
active = false;
|
|
@@ -17,29 +18,29 @@ class Floatable {
|
|
|
17
18
|
this.content = void 0;
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
function activate(floatable, active, time) {
|
|
21
|
+
function activate(floatable, active, order, time) {
|
|
21
22
|
stop(floatable);
|
|
22
23
|
if (floatable.active) {
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
if (time == null || time === 0) {
|
|
26
|
-
onActivate(floatable, active);
|
|
27
|
+
onActivate(floatable, active, order);
|
|
27
28
|
} else {
|
|
28
29
|
floatable.activate = +setTimeout(() => {
|
|
29
|
-
onActivate(floatable, active);
|
|
30
|
+
onActivate(floatable, active, order);
|
|
30
31
|
}, time);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
function deactivate(floatable, active, time) {
|
|
34
|
+
function deactivate(floatable, active, order, time) {
|
|
34
35
|
stop(floatable);
|
|
35
36
|
if (!floatable.active) {
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
if (time == null || time === 0) {
|
|
39
|
-
onDeactivate(floatable, active);
|
|
40
|
+
onDeactivate(floatable, active, order);
|
|
40
41
|
} else {
|
|
41
42
|
floatable.deactivate = +setTimeout(() => {
|
|
42
|
-
onDeactivate(floatable, active);
|
|
43
|
+
onDeactivate(floatable, active, order);
|
|
43
44
|
}, time);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -77,16 +78,23 @@ function getY(anchor, content) {
|
|
|
77
78
|
}
|
|
78
79
|
return anchor.top + (anchor.height - content.height) / 2;
|
|
79
80
|
}
|
|
80
|
-
function onActivate(floatable, active) {
|
|
81
|
+
function onActivate(floatable, active, order) {
|
|
81
82
|
floatable.active = true;
|
|
82
83
|
active.add(floatable);
|
|
84
|
+
if (order != null) {
|
|
85
|
+
order.push(floatable);
|
|
86
|
+
}
|
|
83
87
|
document.body.append(floatable.content);
|
|
84
88
|
floatable.content.hidden = false;
|
|
85
89
|
update(floatable);
|
|
86
90
|
}
|
|
87
|
-
function onDeactivate(floatable, active) {
|
|
91
|
+
function onDeactivate(floatable, active, order) {
|
|
88
92
|
floatable.active = false;
|
|
89
93
|
active.delete(floatable);
|
|
94
|
+
const index = order?.indexOf(floatable) ?? -1;
|
|
95
|
+
if (index > -1) {
|
|
96
|
+
order?.splice(index, 1);
|
|
97
|
+
}
|
|
90
98
|
floatable.content.hidden = true;
|
|
91
99
|
floatable.anchor.insertAdjacentElement("afterend", floatable.content);
|
|
92
100
|
}
|
|
@@ -101,6 +109,11 @@ function update(floatable) {
|
|
|
101
109
|
}
|
|
102
110
|
floatable.frame = requestAnimationFrame(run);
|
|
103
111
|
}
|
|
112
|
+
function setStyles(content) {
|
|
113
|
+
content.style.position = "fixed";
|
|
114
|
+
content.style.inset = "0 auto auto 0";
|
|
115
|
+
content.style.zIndex = "10";
|
|
116
|
+
}
|
|
104
117
|
function stop(floatable) {
|
|
105
118
|
if (floatable.frame != null) {
|
|
106
119
|
cancelAnimationFrame(floatable.frame);
|
|
@@ -115,7 +128,7 @@ function stop(floatable) {
|
|
|
115
128
|
floatable.deactivate = void 0;
|
|
116
129
|
floatable.frame = void 0;
|
|
117
130
|
}
|
|
118
|
-
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) /
|
|
131
|
+
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 2;
|
|
119
132
|
exports.Floatable = Floatable;
|
|
120
133
|
exports.activate = activate;
|
|
121
134
|
exports.deactivate = deactivate;
|
package/dist/js/floatable.js
CHANGED
|
@@ -2,6 +2,7 @@ class Floatable {
|
|
|
2
2
|
constructor(anchor, content) {
|
|
3
3
|
this.anchor = anchor;
|
|
4
4
|
this.content = content;
|
|
5
|
+
setStyles(content);
|
|
5
6
|
}
|
|
6
7
|
activate;
|
|
7
8
|
active = false;
|
|
@@ -15,29 +16,29 @@ class Floatable {
|
|
|
15
16
|
this.content = void 0;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
|
-
function activate(floatable, active, time) {
|
|
19
|
+
function activate(floatable, active, order, time) {
|
|
19
20
|
stop(floatable);
|
|
20
21
|
if (floatable.active) {
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
if (time == null || time === 0) {
|
|
24
|
-
onActivate(floatable, active);
|
|
25
|
+
onActivate(floatable, active, order);
|
|
25
26
|
} else {
|
|
26
27
|
floatable.activate = +setTimeout(() => {
|
|
27
|
-
onActivate(floatable, active);
|
|
28
|
+
onActivate(floatable, active, order);
|
|
28
29
|
}, time);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
function deactivate(floatable, active, time) {
|
|
32
|
+
function deactivate(floatable, active, order, time) {
|
|
32
33
|
stop(floatable);
|
|
33
34
|
if (!floatable.active) {
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
if (time == null || time === 0) {
|
|
37
|
-
onDeactivate(floatable, active);
|
|
38
|
+
onDeactivate(floatable, active, order);
|
|
38
39
|
} else {
|
|
39
40
|
floatable.deactivate = +setTimeout(() => {
|
|
40
|
-
onDeactivate(floatable, active);
|
|
41
|
+
onDeactivate(floatable, active, order);
|
|
41
42
|
}, time);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
@@ -75,16 +76,23 @@ function getY(anchor, content) {
|
|
|
75
76
|
}
|
|
76
77
|
return anchor.top + (anchor.height - content.height) / 2;
|
|
77
78
|
}
|
|
78
|
-
function onActivate(floatable, active) {
|
|
79
|
+
function onActivate(floatable, active, order) {
|
|
79
80
|
floatable.active = true;
|
|
80
81
|
active.add(floatable);
|
|
82
|
+
if (order != null) {
|
|
83
|
+
order.push(floatable);
|
|
84
|
+
}
|
|
81
85
|
document.body.append(floatable.content);
|
|
82
86
|
floatable.content.hidden = false;
|
|
83
87
|
update(floatable);
|
|
84
88
|
}
|
|
85
|
-
function onDeactivate(floatable, active) {
|
|
89
|
+
function onDeactivate(floatable, active, order) {
|
|
86
90
|
floatable.active = false;
|
|
87
91
|
active.delete(floatable);
|
|
92
|
+
const index = order?.indexOf(floatable) ?? -1;
|
|
93
|
+
if (index > -1) {
|
|
94
|
+
order?.splice(index, 1);
|
|
95
|
+
}
|
|
88
96
|
floatable.content.hidden = true;
|
|
89
97
|
floatable.anchor.insertAdjacentElement("afterend", floatable.content);
|
|
90
98
|
}
|
|
@@ -99,6 +107,11 @@ function update(floatable) {
|
|
|
99
107
|
}
|
|
100
108
|
floatable.frame = requestAnimationFrame(run);
|
|
101
109
|
}
|
|
110
|
+
function setStyles(content) {
|
|
111
|
+
content.style.position = "fixed";
|
|
112
|
+
content.style.inset = "0 auto auto 0";
|
|
113
|
+
content.style.zIndex = "10";
|
|
114
|
+
}
|
|
102
115
|
function stop(floatable) {
|
|
103
116
|
if (floatable.frame != null) {
|
|
104
117
|
cancelAnimationFrame(floatable.frame);
|
|
@@ -113,7 +126,7 @@ function stop(floatable) {
|
|
|
113
126
|
floatable.deactivate = void 0;
|
|
114
127
|
floatable.frame = void 0;
|
|
115
128
|
}
|
|
116
|
-
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) /
|
|
129
|
+
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 2;
|
|
117
130
|
export {
|
|
118
131
|
Floatable,
|
|
119
132
|
activate,
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const number = require("@oscarpalmer/atoms/number");
|
|
4
|
+
const event = require("@oscarpalmer/toretto/event");
|
|
5
|
+
const find = require("@oscarpalmer/toretto/find");
|
|
6
|
+
const focusable = require("@oscarpalmer/toretto/focusable");
|
|
7
|
+
class FocusTrap {
|
|
8
|
+
constructor(element) {
|
|
9
|
+
this.element = element;
|
|
10
|
+
element.setAttribute("aria-modal", "true");
|
|
11
|
+
element.setAttribute("tabindex", "-1");
|
|
12
|
+
}
|
|
13
|
+
active = true;
|
|
14
|
+
destroy() {
|
|
15
|
+
this.element = void 0;
|
|
16
|
+
}
|
|
17
|
+
disable() {
|
|
18
|
+
if (this.active && !this.element.hasAttribute(`${selector}-noescape`)) {
|
|
19
|
+
setDisabled(this, true);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
enable() {
|
|
23
|
+
if (!this.active) {
|
|
24
|
+
setDisabled(this, false);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function createFocusTrap(element) {
|
|
29
|
+
let focusTrap = focusTraps.get(element);
|
|
30
|
+
if (focusTrap == null) {
|
|
31
|
+
focusTrap = new FocusTrap(element);
|
|
32
|
+
focusTraps.set(element, focusTrap);
|
|
33
|
+
}
|
|
34
|
+
return focusTrap;
|
|
35
|
+
}
|
|
36
|
+
function onEscape(event2) {
|
|
37
|
+
focusTraps.get(
|
|
38
|
+
find.findAncestor(event2.target, `[${selector}]`)
|
|
39
|
+
)?.disable();
|
|
40
|
+
}
|
|
41
|
+
function onFocusIn(event2) {
|
|
42
|
+
lastTarget = find.findAncestor(event2.target, `[${selector}]`) == null ? void 0 : event2.target;
|
|
43
|
+
}
|
|
44
|
+
function onKeydown(event2) {
|
|
45
|
+
switch (event2.key) {
|
|
46
|
+
case "Escape":
|
|
47
|
+
onEscape(event2);
|
|
48
|
+
break;
|
|
49
|
+
case "Tab":
|
|
50
|
+
onTab(event2);
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function onPointerdown(event2) {
|
|
57
|
+
const lastFocusTrap = find.findAncestor(
|
|
58
|
+
lastTarget,
|
|
59
|
+
`[${selector}]`
|
|
60
|
+
);
|
|
61
|
+
const nextFocusTrap = find.findAncestor(
|
|
62
|
+
event2.target,
|
|
63
|
+
`[${selector}]`
|
|
64
|
+
);
|
|
65
|
+
if (lastFocusTrap != null && nextFocusTrap !== lastFocusTrap) {
|
|
66
|
+
event2.preventDefault();
|
|
67
|
+
lastTarget?.focus();
|
|
68
|
+
}
|
|
69
|
+
if (disabled.size === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const focusTrap of disabled) {
|
|
73
|
+
if (focusTrap.element !== nextFocusTrap) {
|
|
74
|
+
focusTrap.enable();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function onTab(event2) {
|
|
80
|
+
const element = find.findAncestor(
|
|
81
|
+
event2.target,
|
|
82
|
+
`[${selector}]`
|
|
83
|
+
);
|
|
84
|
+
const focusTrap = focusTraps.get(element);
|
|
85
|
+
if (focusTrap == null) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (focusTrap.active) {
|
|
89
|
+
event2.preventDefault();
|
|
90
|
+
}
|
|
91
|
+
const elements = focusable.getFocusable(element);
|
|
92
|
+
const index = elements.indexOf(event2.target);
|
|
93
|
+
if (index === -1 || elements.length === 0) {
|
|
94
|
+
tabToTrap(focusTrap, elements, element);
|
|
95
|
+
} else {
|
|
96
|
+
tabToElement(event2, focusTrap, elements, index);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function setDisabled(focusTrap, value) {
|
|
100
|
+
focusTrap.element.setAttribute("aria-modal", String(!value));
|
|
101
|
+
focusTrap.active = !value;
|
|
102
|
+
if (value) {
|
|
103
|
+
disabled.add(focusTrap);
|
|
104
|
+
} else {
|
|
105
|
+
disabled.delete(focusTrap);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function tabToElement(event2, focusTrap, elements, index) {
|
|
109
|
+
const next = number.clamp(
|
|
110
|
+
index + (event2.shiftKey ? -1 : 1),
|
|
111
|
+
0,
|
|
112
|
+
elements.length - 1,
|
|
113
|
+
true
|
|
114
|
+
);
|
|
115
|
+
if (!focusTrap.active) {
|
|
116
|
+
if (index === length - 1 && next === 0 || index === 0 && next === length - 1) {
|
|
117
|
+
lastTarget = void 0;
|
|
118
|
+
focusTrap.enable();
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
elements[next].focus();
|
|
123
|
+
lastTarget = elements[next];
|
|
124
|
+
}
|
|
125
|
+
function tabToTrap(focusTrap, elements, element) {
|
|
126
|
+
if (focusTrap.active) {
|
|
127
|
+
const next = length === 0 ? element : elements[0];
|
|
128
|
+
next.focus();
|
|
129
|
+
lastTarget = next;
|
|
130
|
+
} else {
|
|
131
|
+
lastTarget = void 0;
|
|
132
|
+
focusTrap.enable();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const disabled = /* @__PURE__ */ new Set();
|
|
136
|
+
const eventOptions = {
|
|
137
|
+
capture: true,
|
|
138
|
+
passive: false
|
|
139
|
+
};
|
|
140
|
+
const focusTraps = /* @__PURE__ */ new WeakMap();
|
|
141
|
+
let lastTarget;
|
|
142
|
+
const selector = "oui-focus-trap";
|
|
143
|
+
event.on(document, "focusin", onFocusIn, eventOptions);
|
|
144
|
+
event.on(document, "keydown", onKeydown, eventOptions);
|
|
145
|
+
event.on(document, "mousedown", onPointerdown, eventOptions);
|
|
146
|
+
event.on(document, "touchstart", onPointerdown, eventOptions);
|
|
147
|
+
exports.FocusTrap = FocusTrap;
|
|
148
|
+
exports.createFocusTrap = createFocusTrap;
|
|
149
|
+
exports.focusTraps = focusTraps;
|
|
150
|
+
exports.selector = selector;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { clamp } from "@oscarpalmer/atoms/number";
|
|
2
|
+
import { on } from "@oscarpalmer/toretto/event";
|
|
3
|
+
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
4
|
+
import { getFocusable } from "@oscarpalmer/toretto/focusable";
|
|
5
|
+
class FocusTrap {
|
|
6
|
+
constructor(element) {
|
|
7
|
+
this.element = element;
|
|
8
|
+
element.setAttribute("aria-modal", "true");
|
|
9
|
+
element.setAttribute("tabindex", "-1");
|
|
10
|
+
}
|
|
11
|
+
active = true;
|
|
12
|
+
destroy() {
|
|
13
|
+
this.element = void 0;
|
|
14
|
+
}
|
|
15
|
+
disable() {
|
|
16
|
+
if (this.active && !this.element.hasAttribute(`${selector}-noescape`)) {
|
|
17
|
+
setDisabled(this, true);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
enable() {
|
|
21
|
+
if (!this.active) {
|
|
22
|
+
setDisabled(this, false);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function createFocusTrap(element) {
|
|
27
|
+
let focusTrap = focusTraps.get(element);
|
|
28
|
+
if (focusTrap == null) {
|
|
29
|
+
focusTrap = new FocusTrap(element);
|
|
30
|
+
focusTraps.set(element, focusTrap);
|
|
31
|
+
}
|
|
32
|
+
return focusTrap;
|
|
33
|
+
}
|
|
34
|
+
function onEscape(event) {
|
|
35
|
+
focusTraps.get(
|
|
36
|
+
findAncestor(event.target, `[${selector}]`)
|
|
37
|
+
)?.disable();
|
|
38
|
+
}
|
|
39
|
+
function onFocusIn(event) {
|
|
40
|
+
lastTarget = findAncestor(event.target, `[${selector}]`) == null ? void 0 : event.target;
|
|
41
|
+
}
|
|
42
|
+
function onKeydown(event) {
|
|
43
|
+
switch (event.key) {
|
|
44
|
+
case "Escape":
|
|
45
|
+
onEscape(event);
|
|
46
|
+
break;
|
|
47
|
+
case "Tab":
|
|
48
|
+
onTab(event);
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function onPointerdown(event) {
|
|
55
|
+
const lastFocusTrap = findAncestor(
|
|
56
|
+
lastTarget,
|
|
57
|
+
`[${selector}]`
|
|
58
|
+
);
|
|
59
|
+
const nextFocusTrap = findAncestor(
|
|
60
|
+
event.target,
|
|
61
|
+
`[${selector}]`
|
|
62
|
+
);
|
|
63
|
+
if (lastFocusTrap != null && nextFocusTrap !== lastFocusTrap) {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
lastTarget?.focus();
|
|
66
|
+
}
|
|
67
|
+
if (disabled.size === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const focusTrap of disabled) {
|
|
71
|
+
if (focusTrap.element !== nextFocusTrap) {
|
|
72
|
+
focusTrap.enable();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function onTab(event) {
|
|
78
|
+
const element = findAncestor(
|
|
79
|
+
event.target,
|
|
80
|
+
`[${selector}]`
|
|
81
|
+
);
|
|
82
|
+
const focusTrap = focusTraps.get(element);
|
|
83
|
+
if (focusTrap == null) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (focusTrap.active) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
}
|
|
89
|
+
const elements = getFocusable(element);
|
|
90
|
+
const index = elements.indexOf(event.target);
|
|
91
|
+
if (index === -1 || elements.length === 0) {
|
|
92
|
+
tabToTrap(focusTrap, elements, element);
|
|
93
|
+
} else {
|
|
94
|
+
tabToElement(event, focusTrap, elements, index);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function setDisabled(focusTrap, value) {
|
|
98
|
+
focusTrap.element.setAttribute("aria-modal", String(!value));
|
|
99
|
+
focusTrap.active = !value;
|
|
100
|
+
if (value) {
|
|
101
|
+
disabled.add(focusTrap);
|
|
102
|
+
} else {
|
|
103
|
+
disabled.delete(focusTrap);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function tabToElement(event, focusTrap, elements, index) {
|
|
107
|
+
const next = clamp(
|
|
108
|
+
index + (event.shiftKey ? -1 : 1),
|
|
109
|
+
0,
|
|
110
|
+
elements.length - 1,
|
|
111
|
+
true
|
|
112
|
+
);
|
|
113
|
+
if (!focusTrap.active) {
|
|
114
|
+
if (index === length - 1 && next === 0 || index === 0 && next === length - 1) {
|
|
115
|
+
lastTarget = void 0;
|
|
116
|
+
focusTrap.enable();
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
elements[next].focus();
|
|
121
|
+
lastTarget = elements[next];
|
|
122
|
+
}
|
|
123
|
+
function tabToTrap(focusTrap, elements, element) {
|
|
124
|
+
if (focusTrap.active) {
|
|
125
|
+
const next = length === 0 ? element : elements[0];
|
|
126
|
+
next.focus();
|
|
127
|
+
lastTarget = next;
|
|
128
|
+
} else {
|
|
129
|
+
lastTarget = void 0;
|
|
130
|
+
focusTrap.enable();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const disabled = /* @__PURE__ */ new Set();
|
|
134
|
+
const eventOptions = {
|
|
135
|
+
capture: true,
|
|
136
|
+
passive: false
|
|
137
|
+
};
|
|
138
|
+
const focusTraps = /* @__PURE__ */ new WeakMap();
|
|
139
|
+
let lastTarget;
|
|
140
|
+
const selector = "oui-focus-trap";
|
|
141
|
+
on(document, "focusin", onFocusIn, eventOptions);
|
|
142
|
+
on(document, "keydown", onKeydown, eventOptions);
|
|
143
|
+
on(document, "mousedown", onPointerdown, eventOptions);
|
|
144
|
+
on(document, "touchstart", onPointerdown, eventOptions);
|
|
145
|
+
export {
|
|
146
|
+
FocusTrap,
|
|
147
|
+
createFocusTrap,
|
|
148
|
+
focusTraps,
|
|
149
|
+
selector
|
|
150
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const attributable = require("../attributable.cjs");
|
|
3
|
+
const focusTrap_embedded = require("./embedded.cjs");
|
|
4
|
+
function removeFocusTraps(element) {
|
|
5
|
+
focusTrap_embedded.focusTraps.get(element)?.destroy();
|
|
6
|
+
focusTrap_embedded.focusTraps.delete(element);
|
|
7
|
+
}
|
|
8
|
+
attributable.attributable(focusTrap_embedded.selector, focusTrap_embedded.createFocusTrap, removeFocusTraps);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { attributable } from "../attributable.js";
|
|
2
|
+
import { createFocusTrap, selector, focusTraps } from "./embedded.js";
|
|
3
|
+
function removeFocusTraps(element) {
|
|
4
|
+
focusTraps.get(element)?.destroy();
|
|
5
|
+
focusTraps.delete(element);
|
|
6
|
+
}
|
|
7
|
+
attributable(selector, createFocusTrap, removeFocusTraps);
|
package/dist/js/index.cjs
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
require("./focus-trap/standalone.cjs");
|
|
4
|
+
const popover = require("./popover.cjs");
|
|
5
|
+
require("./tooltip.cjs");
|
|
6
|
+
exports.OuiPopoverElement = popover.OuiPopoverElement;
|