@oscarpalmer/oui 0.13.0 → 0.15.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/popover.css +9 -0
- package/dist/css/tooltip.css +9 -3
- package/dist/css/value.css +1 -0
- package/dist/js/floatable.cjs +134 -0
- package/dist/js/floatable.js +134 -0
- 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/tooltip.cjs +83 -126
- package/dist/js/tooltip.js +83 -126
- package/package.json +7 -7
- package/src/css/popover.scss +9 -0
- package/src/css/tooltip.scss +7 -3
- package/src/js/floatable.ts +196 -0
- 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/tooltip.ts +97 -161
- package/types/floatable.d.cts +16 -0
- package/types/floatable.d.ts +12 -0
- 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/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
|
|
@@ -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
|
+
|
package/dist/css/tooltip.css
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
|
|
2
|
+
:where([oui-tooltip]:is(abbr, b, cite, dfn, code, em, i, kbd, mark, q, samp, small, span, strong, sub, sup, time, var)) {
|
|
3
|
+
-webkit-text-decoration: underline dotted;
|
|
4
|
+
text-decoration: underline dotted;
|
|
5
|
+
-webkit-text-decoration-skip: ink;
|
|
6
|
+
text-decoration-skip-ink: auto;
|
|
7
|
+
text-underline-offset: 0.25em;
|
|
8
|
+
cursor: help;
|
|
9
|
+
}
|
|
10
|
+
|
|
2
11
|
:where([oui-tooltip-content]) {
|
|
3
12
|
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
13
|
max-width: 48ch;
|
|
@@ -6,9 +15,6 @@
|
|
|
6
15
|
background-color: var(--oui-absolute);
|
|
7
16
|
border-radius: 0.25rem;
|
|
8
17
|
font-size: 0.875rem;
|
|
9
|
-
position: fixed;
|
|
10
|
-
inset: 0 auto auto 0;
|
|
11
|
-
z-index: 10;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
class Floatable {
|
|
4
|
+
constructor(anchor, content) {
|
|
5
|
+
this.anchor = anchor;
|
|
6
|
+
this.content = content;
|
|
7
|
+
setStyles(content);
|
|
8
|
+
}
|
|
9
|
+
activate;
|
|
10
|
+
active = false;
|
|
11
|
+
deactivate;
|
|
12
|
+
frame;
|
|
13
|
+
destroy() {
|
|
14
|
+
this.activate = void 0;
|
|
15
|
+
this.deactivate = void 0;
|
|
16
|
+
this.frame = void 0;
|
|
17
|
+
this.anchor = void 0;
|
|
18
|
+
this.content = void 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function activate(floatable, active, order, time) {
|
|
22
|
+
stop(floatable);
|
|
23
|
+
if (floatable.active) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (time == null || time === 0) {
|
|
27
|
+
onActivate(floatable, active, order);
|
|
28
|
+
} else {
|
|
29
|
+
floatable.activate = +setTimeout(() => {
|
|
30
|
+
onActivate(floatable, active, order);
|
|
31
|
+
}, time);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function deactivate(floatable, active, order, time) {
|
|
35
|
+
stop(floatable);
|
|
36
|
+
if (!floatable.active) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (time == null || time === 0) {
|
|
40
|
+
onDeactivate(floatable, active, order);
|
|
41
|
+
} else {
|
|
42
|
+
floatable.deactivate = +setTimeout(() => {
|
|
43
|
+
onDeactivate(floatable, active, order);
|
|
44
|
+
}, time);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getX(anchor, content) {
|
|
48
|
+
if (content.width + 2 * margin >= window.innerWidth) {
|
|
49
|
+
return {
|
|
50
|
+
left: margin,
|
|
51
|
+
right: margin
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const left = anchor.left + (anchor.width - content.width) / 2;
|
|
55
|
+
const right = window.innerWidth - margin;
|
|
56
|
+
if (left < margin) {
|
|
57
|
+
return {
|
|
58
|
+
left: margin
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (left + content.width > right) {
|
|
62
|
+
return {
|
|
63
|
+
right: margin
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
left
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function getY(anchor, content) {
|
|
71
|
+
let top = anchor.top - content.height - margin;
|
|
72
|
+
if (top - margin >= 0) {
|
|
73
|
+
return top;
|
|
74
|
+
}
|
|
75
|
+
top = anchor.bottom + margin;
|
|
76
|
+
if (top + content.height + margin <= window.innerHeight) {
|
|
77
|
+
return top;
|
|
78
|
+
}
|
|
79
|
+
return anchor.top + (anchor.height - content.height) / 2;
|
|
80
|
+
}
|
|
81
|
+
function onActivate(floatable, active, order) {
|
|
82
|
+
floatable.active = true;
|
|
83
|
+
active.add(floatable);
|
|
84
|
+
if (order != null) {
|
|
85
|
+
order.push(floatable);
|
|
86
|
+
}
|
|
87
|
+
document.body.append(floatable.content);
|
|
88
|
+
floatable.content.hidden = false;
|
|
89
|
+
update(floatable);
|
|
90
|
+
}
|
|
91
|
+
function onDeactivate(floatable, active, order) {
|
|
92
|
+
floatable.active = false;
|
|
93
|
+
active.delete(floatable);
|
|
94
|
+
const index = order?.indexOf(floatable) ?? -1;
|
|
95
|
+
if (index > -1) {
|
|
96
|
+
order?.splice(index, 1);
|
|
97
|
+
}
|
|
98
|
+
floatable.content.hidden = true;
|
|
99
|
+
floatable.anchor.insertAdjacentElement("afterend", floatable.content);
|
|
100
|
+
}
|
|
101
|
+
function update(floatable) {
|
|
102
|
+
function run() {
|
|
103
|
+
const anchor = floatable.anchor.getBoundingClientRect();
|
|
104
|
+
const content = floatable.content.getBoundingClientRect();
|
|
105
|
+
const { left, right } = getX(anchor, content);
|
|
106
|
+
const top = getY(anchor, content);
|
|
107
|
+
floatable.content.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
|
|
108
|
+
floatable.frame = requestAnimationFrame(run);
|
|
109
|
+
}
|
|
110
|
+
floatable.frame = requestAnimationFrame(run);
|
|
111
|
+
}
|
|
112
|
+
function setStyles(content) {
|
|
113
|
+
content.style.position = "fixed";
|
|
114
|
+
content.style.inset = "0 auto auto 0";
|
|
115
|
+
content.style.zIndex = "10";
|
|
116
|
+
}
|
|
117
|
+
function stop(floatable) {
|
|
118
|
+
if (floatable.frame != null) {
|
|
119
|
+
cancelAnimationFrame(floatable.frame);
|
|
120
|
+
}
|
|
121
|
+
if (floatable.activate != null) {
|
|
122
|
+
clearTimeout(floatable.activate);
|
|
123
|
+
}
|
|
124
|
+
if (floatable.deactivate != null) {
|
|
125
|
+
clearTimeout(floatable.deactivate);
|
|
126
|
+
}
|
|
127
|
+
floatable.activate = void 0;
|
|
128
|
+
floatable.deactivate = void 0;
|
|
129
|
+
floatable.frame = void 0;
|
|
130
|
+
}
|
|
131
|
+
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
|
|
132
|
+
exports.Floatable = Floatable;
|
|
133
|
+
exports.activate = activate;
|
|
134
|
+
exports.deactivate = deactivate;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
class Floatable {
|
|
2
|
+
constructor(anchor, content) {
|
|
3
|
+
this.anchor = anchor;
|
|
4
|
+
this.content = content;
|
|
5
|
+
setStyles(content);
|
|
6
|
+
}
|
|
7
|
+
activate;
|
|
8
|
+
active = false;
|
|
9
|
+
deactivate;
|
|
10
|
+
frame;
|
|
11
|
+
destroy() {
|
|
12
|
+
this.activate = void 0;
|
|
13
|
+
this.deactivate = void 0;
|
|
14
|
+
this.frame = void 0;
|
|
15
|
+
this.anchor = void 0;
|
|
16
|
+
this.content = void 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function activate(floatable, active, order, time) {
|
|
20
|
+
stop(floatable);
|
|
21
|
+
if (floatable.active) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (time == null || time === 0) {
|
|
25
|
+
onActivate(floatable, active, order);
|
|
26
|
+
} else {
|
|
27
|
+
floatable.activate = +setTimeout(() => {
|
|
28
|
+
onActivate(floatable, active, order);
|
|
29
|
+
}, time);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function deactivate(floatable, active, order, time) {
|
|
33
|
+
stop(floatable);
|
|
34
|
+
if (!floatable.active) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (time == null || time === 0) {
|
|
38
|
+
onDeactivate(floatable, active, order);
|
|
39
|
+
} else {
|
|
40
|
+
floatable.deactivate = +setTimeout(() => {
|
|
41
|
+
onDeactivate(floatable, active, order);
|
|
42
|
+
}, time);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getX(anchor, content) {
|
|
46
|
+
if (content.width + 2 * margin >= window.innerWidth) {
|
|
47
|
+
return {
|
|
48
|
+
left: margin,
|
|
49
|
+
right: margin
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const left = anchor.left + (anchor.width - content.width) / 2;
|
|
53
|
+
const right = window.innerWidth - margin;
|
|
54
|
+
if (left < margin) {
|
|
55
|
+
return {
|
|
56
|
+
left: margin
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (left + content.width > right) {
|
|
60
|
+
return {
|
|
61
|
+
right: margin
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
left
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function getY(anchor, content) {
|
|
69
|
+
let top = anchor.top - content.height - margin;
|
|
70
|
+
if (top - margin >= 0) {
|
|
71
|
+
return top;
|
|
72
|
+
}
|
|
73
|
+
top = anchor.bottom + margin;
|
|
74
|
+
if (top + content.height + margin <= window.innerHeight) {
|
|
75
|
+
return top;
|
|
76
|
+
}
|
|
77
|
+
return anchor.top + (anchor.height - content.height) / 2;
|
|
78
|
+
}
|
|
79
|
+
function onActivate(floatable, active, order) {
|
|
80
|
+
floatable.active = true;
|
|
81
|
+
active.add(floatable);
|
|
82
|
+
if (order != null) {
|
|
83
|
+
order.push(floatable);
|
|
84
|
+
}
|
|
85
|
+
document.body.append(floatable.content);
|
|
86
|
+
floatable.content.hidden = false;
|
|
87
|
+
update(floatable);
|
|
88
|
+
}
|
|
89
|
+
function onDeactivate(floatable, active, order) {
|
|
90
|
+
floatable.active = false;
|
|
91
|
+
active.delete(floatable);
|
|
92
|
+
const index = order?.indexOf(floatable) ?? -1;
|
|
93
|
+
if (index > -1) {
|
|
94
|
+
order?.splice(index, 1);
|
|
95
|
+
}
|
|
96
|
+
floatable.content.hidden = true;
|
|
97
|
+
floatable.anchor.insertAdjacentElement("afterend", floatable.content);
|
|
98
|
+
}
|
|
99
|
+
function update(floatable) {
|
|
100
|
+
function run() {
|
|
101
|
+
const anchor = floatable.anchor.getBoundingClientRect();
|
|
102
|
+
const content = floatable.content.getBoundingClientRect();
|
|
103
|
+
const { left, right } = getX(anchor, content);
|
|
104
|
+
const top = getY(anchor, content);
|
|
105
|
+
floatable.content.style.inset = `${top}px ${right == null ? "auto" : `${right}px`} auto ${left == null ? "auto" : `${left}px`}`;
|
|
106
|
+
floatable.frame = requestAnimationFrame(run);
|
|
107
|
+
}
|
|
108
|
+
floatable.frame = requestAnimationFrame(run);
|
|
109
|
+
}
|
|
110
|
+
function setStyles(content) {
|
|
111
|
+
content.style.position = "fixed";
|
|
112
|
+
content.style.inset = "0 auto auto 0";
|
|
113
|
+
content.style.zIndex = "10";
|
|
114
|
+
}
|
|
115
|
+
function stop(floatable) {
|
|
116
|
+
if (floatable.frame != null) {
|
|
117
|
+
cancelAnimationFrame(floatable.frame);
|
|
118
|
+
}
|
|
119
|
+
if (floatable.activate != null) {
|
|
120
|
+
clearTimeout(floatable.activate);
|
|
121
|
+
}
|
|
122
|
+
if (floatable.deactivate != null) {
|
|
123
|
+
clearTimeout(floatable.deactivate);
|
|
124
|
+
}
|
|
125
|
+
floatable.activate = void 0;
|
|
126
|
+
floatable.deactivate = void 0;
|
|
127
|
+
floatable.frame = void 0;
|
|
128
|
+
}
|
|
129
|
+
const margin = Number.parseInt(getComputedStyle(document.documentElement).fontSize, 10) / 4;
|
|
130
|
+
export {
|
|
131
|
+
Floatable,
|
|
132
|
+
activate,
|
|
133
|
+
deactivate
|
|
134
|
+
};
|
|
@@ -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;
|