@knadh/oat 0.2.0 → 0.4.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/css/00-base.css +13 -8
- package/css/01-theme.css +28 -48
- package/css/alert.css +15 -19
- package/css/badge.css +12 -17
- package/css/button.css +46 -51
- package/css/dialog.css +1 -2
- package/css/dropdown.css +0 -4
- package/css/form.css +16 -18
- package/css/grid.css +10 -2
- package/css/sidebar.css +68 -53
- package/css/skeleton.css +8 -12
- package/css/spinner.css +34 -9
- package/css/tabs.css +2 -3
- package/css/toast.css +14 -6
- package/css/utilities.css +10 -23
- package/js/base.js +25 -24
- package/js/dropdown.js +18 -18
- package/js/index.js +12 -0
- package/js/sidebar.js +11 -0
- package/js/tabs.js +11 -22
- package/js/toast.js +75 -71
- package/js/tooltip.js +26 -8
- package/oat.min.css +1 -1
- package/oat.min.js +1 -1
- package/package.json +1 -1
package/css/sidebar.css
CHANGED
|
@@ -1,54 +1,49 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
:root {
|
|
3
|
-
--
|
|
4
|
-
--sidebar-width: 14rem;
|
|
3
|
+
--_nh: var(--space-12);
|
|
5
4
|
}
|
|
6
5
|
|
|
7
6
|
[data-sidebar-layout] {
|
|
7
|
+
--topnav-offset: 0px;
|
|
8
|
+
--topnav-scroll-offset: 0px;
|
|
9
|
+
|
|
8
10
|
display: grid;
|
|
9
|
-
grid-template-columns:
|
|
11
|
+
grid-template-columns: 14rem 1fr;
|
|
10
12
|
min-height: 100dvh;
|
|
11
13
|
gap: var(--space-4);
|
|
12
14
|
|
|
13
15
|
&:has(nav[data-topnav]) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
--topnav-offset: var(--_nh);
|
|
17
|
+
--topnav-scroll-offset: calc(var(--_nh) + var(--space-6));
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
margin-block-start: calc(var(--topnav-height));
|
|
20
|
+
> main {
|
|
21
|
+
min-width: 0;
|
|
22
|
+
margin-block-start: var(--topnav-offset);
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
scroll-margin-block-start: calc(var(--topnav-height) + var(--space-6));
|
|
27
|
-
}
|
|
24
|
+
[id] {
|
|
25
|
+
scroll-margin-block-start: var(--topnav-scroll-offset);
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
> aside[data-sidebar] {
|
|
32
30
|
position: sticky;
|
|
33
|
-
top:
|
|
31
|
+
top: var(--topnav-offset);
|
|
34
32
|
z-index: 1;
|
|
35
|
-
height: 100dvh;
|
|
33
|
+
height: calc(100dvh - var(--topnav-offset));
|
|
36
34
|
align-self: start;
|
|
37
35
|
background-color: var(--background);
|
|
38
36
|
border-inline-end: 1px solid var(--border);
|
|
39
37
|
display: flex;
|
|
40
38
|
flex-direction: column;
|
|
41
|
-
width: var(--sidebar-width);
|
|
42
39
|
|
|
43
|
-
> header {
|
|
40
|
+
> :is(header, footer) {
|
|
44
41
|
flex-shrink: 0;
|
|
45
42
|
padding: var(--space-3);
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
> footer {
|
|
49
|
-
flex-shrink: 0;
|
|
50
46
|
margin-block-start: auto;
|
|
51
|
-
padding: var(--space-3);
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
> nav {
|
|
@@ -56,9 +51,7 @@
|
|
|
56
51
|
min-height: 0;
|
|
57
52
|
overflow-y: auto;
|
|
58
53
|
padding: var(--space-3) var(--space-2);
|
|
59
|
-
}
|
|
60
54
|
|
|
61
|
-
nav {
|
|
62
55
|
ul {
|
|
63
56
|
list-style: none;
|
|
64
57
|
padding: 0;
|
|
@@ -82,34 +75,31 @@
|
|
|
82
75
|
border-radius: var(--radius-small);
|
|
83
76
|
transition: background-color var(--transition-fast);
|
|
84
77
|
|
|
85
|
-
&:hover,
|
|
78
|
+
&:is(:hover, [aria-current]) {
|
|
86
79
|
background-color: var(--accent);
|
|
87
80
|
}
|
|
88
81
|
}
|
|
89
|
-
}
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
details {
|
|
84
|
+
border: none;
|
|
85
|
+
overflow: visible;
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
& + details { margin-top: 0; }
|
|
88
|
+
&[open] summary { border-bottom: none; }
|
|
89
|
+
> ul { margin-inline-start: var(--space-4); padding: var(--space-1) 0; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
summary {
|
|
93
|
+
justify-content: flex-start;
|
|
94
|
+
padding: var(--space-2) var(--space-3);
|
|
95
|
+
font-size: var(--text-7);
|
|
96
|
+
border-radius: var(--radius-small);
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
font-weight: var(--font-medium);
|
|
106
|
-
color: var(--foreground);
|
|
107
|
-
border-radius: var(--radius-small);
|
|
108
|
-
|
|
109
|
-
&::after {
|
|
110
|
-
width: 0.75rem;
|
|
111
|
-
height: 0.75rem;
|
|
112
|
-
margin-inline-start: auto;
|
|
98
|
+
&::after {
|
|
99
|
+
width: 0.75rem;
|
|
100
|
+
height: 0.75rem;
|
|
101
|
+
margin-inline-start: auto;
|
|
102
|
+
}
|
|
113
103
|
}
|
|
114
104
|
}
|
|
115
105
|
}
|
|
@@ -117,10 +107,9 @@
|
|
|
117
107
|
|
|
118
108
|
nav[data-topnav] {
|
|
119
109
|
position: fixed;
|
|
120
|
-
inset
|
|
121
|
-
inset-inline: 0;
|
|
110
|
+
inset: 0 0 auto;
|
|
122
111
|
z-index: 5;
|
|
123
|
-
min-height: var(--
|
|
112
|
+
min-height: var(--_nh);
|
|
124
113
|
display: flex;
|
|
125
114
|
align-items: center;
|
|
126
115
|
gap: var(--space-3);
|
|
@@ -130,8 +119,7 @@
|
|
|
130
119
|
box-shadow: var(--shadow-small);
|
|
131
120
|
}
|
|
132
121
|
|
|
133
|
-
[data-sidebar-toggle],
|
|
134
|
-
[data-sidebar-header] {
|
|
122
|
+
:is([data-sidebar-toggle], [data-sidebar-header]) {
|
|
135
123
|
display: none;
|
|
136
124
|
}
|
|
137
125
|
|
|
@@ -143,15 +131,43 @@
|
|
|
143
131
|
cursor: pointer;
|
|
144
132
|
}
|
|
145
133
|
|
|
134
|
+
@media (min-width: 769px) {
|
|
135
|
+
[data-sidebar-layout="always"] [data-sidebar-toggle] {
|
|
136
|
+
display: inline-block;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
[data-sidebar-layout="always"] {
|
|
140
|
+
transition: grid-template-columns var(--transition);
|
|
141
|
+
|
|
142
|
+
> aside[data-sidebar] {
|
|
143
|
+
transform: translateX(0);
|
|
144
|
+
opacity: 1;
|
|
145
|
+
transition: transform var(--transition), opacity var(--transition), visibility var(--transition);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[data-sidebar-layout="always"][data-sidebar-open] {
|
|
150
|
+
grid-template-columns: 0px 1fr;
|
|
151
|
+
gap: 0;
|
|
152
|
+
|
|
153
|
+
> aside[data-sidebar] {
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
min-width: 0;
|
|
156
|
+
transform: translateX(-100%);
|
|
157
|
+
opacity: 0;
|
|
158
|
+
visibility: hidden;
|
|
159
|
+
border-inline-end: none;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
146
164
|
@media (max-width: 768px) {
|
|
147
165
|
[data-sidebar-layout] {
|
|
148
166
|
grid-template-columns: 1fr;
|
|
149
167
|
|
|
150
168
|
> aside[data-sidebar] {
|
|
151
169
|
position: fixed;
|
|
152
|
-
top: 0;
|
|
153
170
|
left: 0;
|
|
154
|
-
height: 100dvh;
|
|
155
171
|
width: 16rem;
|
|
156
172
|
transform: translateX(-100%);
|
|
157
173
|
transition: transform var(--transition);
|
|
@@ -162,7 +178,6 @@
|
|
|
162
178
|
transform: translateX(0);
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
|
-
|
|
166
181
|
[data-sidebar-toggle] { display: inline-block; }
|
|
167
182
|
|
|
168
183
|
[data-sidebar-header] {
|
package/css/skeleton.css
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
[role="status"].skeleton {
|
|
3
|
+
--_c: light-dark(
|
|
4
|
+
color-mix(in srgb, var(--muted) 30%, white),
|
|
5
|
+
color-mix(in srgb, var(--muted) 90%, var(--foreground))
|
|
6
|
+
);
|
|
7
|
+
|
|
3
8
|
margin-block-end: var(--space-3);
|
|
4
9
|
background: var(--muted);
|
|
5
10
|
border-radius: var(--radius-medium);
|
|
6
|
-
animation:
|
|
11
|
+
animation: anim 2s infinite;
|
|
7
12
|
background-size: 200% 100%;
|
|
8
13
|
background-image: linear-gradient(
|
|
9
14
|
90deg,
|
|
10
15
|
var(--muted) 0%,
|
|
11
|
-
|
|
16
|
+
var(--_c) 50%,
|
|
12
17
|
var(--muted) 100%
|
|
13
18
|
);
|
|
14
19
|
|
|
15
|
-
[data-theme="dark"] & {
|
|
16
|
-
background-image: linear-gradient(
|
|
17
|
-
90deg,
|
|
18
|
-
var(--muted) 0%,
|
|
19
|
-
color-mix(in srgb, var(--muted) 90%, var(--foreground)) 90%,
|
|
20
|
-
var(--muted) 100%
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
&.box {
|
|
25
21
|
width: 4rem;
|
|
26
22
|
height: 4rem;
|
|
@@ -36,7 +32,7 @@
|
|
|
36
32
|
margin-block-end: 0;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
@keyframes
|
|
35
|
+
@keyframes anim {
|
|
40
36
|
from { background-position: 200% 0; }
|
|
41
37
|
to { background-position: -200% 0; }
|
|
42
38
|
}
|
package/css/spinner.css
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
@layer components {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
[aria-busy="true"] {
|
|
3
|
+
&::before {
|
|
4
|
+
content: "";
|
|
5
|
+
display: inline-block;
|
|
6
|
+
inset: 0;
|
|
7
|
+
margin: auto;
|
|
8
|
+
width: 1.5rem;
|
|
9
|
+
height: 1.5rem;
|
|
10
|
+
border: 2px solid var(--muted);
|
|
11
|
+
border-top-color: var(--primary);
|
|
12
|
+
border-radius: var(--radius-full);
|
|
13
|
+
animation: spin 1s linear infinite;
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
|
9
16
|
|
|
10
|
-
|
|
17
|
+
&[data-spinner~="small"]::before {
|
|
11
18
|
width: 1rem;
|
|
12
19
|
height: 1rem;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
&[data-spinner~="large"]::before {
|
|
16
23
|
width: 2rem;
|
|
17
24
|
height: 2rem;
|
|
18
25
|
border-width: 3px;
|
|
19
26
|
}
|
|
27
|
+
|
|
28
|
+
&[data-spinner~="overlay"] {
|
|
29
|
+
position: relative;
|
|
30
|
+
|
|
31
|
+
> * {
|
|
32
|
+
opacity: 0.3;
|
|
33
|
+
|
|
34
|
+
/* "disable" all elements in the container while it's busy */
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&::before {
|
|
39
|
+
position: absolute;
|
|
40
|
+
inset: 0;
|
|
41
|
+
margin: auto;
|
|
42
|
+
z-index: 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
20
45
|
}
|
|
21
46
|
|
|
22
47
|
@keyframes spin {
|
package/css/tabs.css
CHANGED
|
@@ -17,19 +17,18 @@
|
|
|
17
17
|
font-weight: var(--font-medium);
|
|
18
18
|
white-space: nowrap;
|
|
19
19
|
background-color: transparent;
|
|
20
|
-
color: var(--
|
|
20
|
+
color: var(--foreground);
|
|
21
21
|
border: none;
|
|
22
22
|
border-radius: calc(var(--radius-medium) - 2px);
|
|
23
23
|
cursor: pointer;
|
|
24
24
|
transition: background-color var(--transition-fast), color var(--transition-fast);
|
|
25
25
|
|
|
26
26
|
&:hover {
|
|
27
|
-
color: var(--foreground);
|
|
27
|
+
color: var(--muted-foreground);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
&[aria-selected="true"] {
|
|
31
31
|
background-color: var(--background);
|
|
32
|
-
color: var(--foreground);
|
|
33
32
|
box-shadow: var(--shadow-small);
|
|
34
33
|
}
|
|
35
34
|
}
|
package/css/toast.css
CHANGED
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.toast {
|
|
49
|
+
--transition: 300ms;
|
|
50
|
+
--transition-in: calc(var(--transition) - 50ms);
|
|
51
|
+
|
|
49
52
|
padding: var(--space-5) var(--space-4);
|
|
50
53
|
max-width: 28rem;
|
|
51
54
|
min-width: 20rem;
|
|
@@ -56,7 +59,7 @@
|
|
|
56
59
|
border-inline-start-style: solid;
|
|
57
60
|
border-radius: var(--radius-medium);
|
|
58
61
|
box-shadow: var(--shadow-small);
|
|
59
|
-
transition: opacity
|
|
62
|
+
transition: opacity var(--transition-in), transform var(--transition-in), margin var(--transition-in);
|
|
60
63
|
line-height: 1;
|
|
61
64
|
|
|
62
65
|
.toast-title {
|
|
@@ -69,18 +72,23 @@
|
|
|
69
72
|
|
|
70
73
|
&[data-variant="success"] {
|
|
71
74
|
border-inline-start-color: var(--success);
|
|
75
|
+
.toast-title {
|
|
76
|
+
color: var(--success);
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
&[data-variant="danger"] {
|
|
75
81
|
border-inline-start-color: var(--danger);
|
|
82
|
+
.toast-title {
|
|
83
|
+
color: var(--danger);
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
&[data-variant="warning"] {
|
|
79
88
|
border-inline-start-color: var(--warning);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
border-inline-start-color: var(--secondary);
|
|
89
|
+
.toast-title {
|
|
90
|
+
color: var(--warning);
|
|
91
|
+
}
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
& > [data-close] {
|
|
@@ -109,7 +117,7 @@
|
|
|
109
117
|
padding-block: 0;
|
|
110
118
|
max-height: 0;
|
|
111
119
|
overflow: hidden;
|
|
112
|
-
transition: opacity
|
|
120
|
+
transition: opacity var(--transition), margin var(--transition), padding var(--transition), max-height var(--transition);
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
123
|
}
|
package/css/utilities.css
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
@layer utilities {
|
|
2
|
-
[hidden] {
|
|
3
|
-
display: none !important;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.sr-only {
|
|
7
|
-
position: absolute;
|
|
8
|
-
width: 1px;
|
|
9
|
-
height: 1px;
|
|
10
|
-
padding: 0;
|
|
11
|
-
margin: -1px;
|
|
12
|
-
overflow: hidden;
|
|
13
|
-
clip: rect(0, 0, 0, 0);
|
|
14
|
-
white-space: nowrap;
|
|
15
|
-
border-width: 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
2
|
.text-left { text-align: start; }
|
|
19
3
|
.text-center { text-align: center; }
|
|
20
4
|
.text-right { text-align: end; }
|
|
21
5
|
.text-light { color: var(--muted-foreground); }
|
|
22
|
-
.text-lighter { color: var(--
|
|
6
|
+
.text-lighter { color: var(--faint-foreground); }
|
|
23
7
|
|
|
24
8
|
.flex { display: flex; }
|
|
25
9
|
.flex-col { flex-direction: column; }
|
|
@@ -32,6 +16,9 @@
|
|
|
32
16
|
display: flex;
|
|
33
17
|
align-items: center;
|
|
34
18
|
gap: var(--space-3);
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
align-content: flex-start;
|
|
21
|
+
height: auto;
|
|
35
22
|
}
|
|
36
23
|
.vstack {
|
|
37
24
|
display: flex;
|
|
@@ -53,12 +40,12 @@
|
|
|
53
40
|
.p-4 { padding: var(--space-4); }
|
|
54
41
|
|
|
55
42
|
.w-100 { width: 100%; }
|
|
56
|
-
}
|
|
57
43
|
|
|
58
|
-
ul, ol {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
ul, ol {
|
|
45
|
+
&.unstyled {
|
|
46
|
+
list-style: none;
|
|
47
|
+
padding-inline-start: 0;
|
|
48
|
+
margin-inline-start: 0;
|
|
49
|
+
}
|
|
63
50
|
}
|
|
64
51
|
}
|
package/js/base.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// oat - Base Web Component Class
|
|
2
2
|
// Provides lifecycle management, event handling, and utilities.
|
|
3
3
|
|
|
4
|
-
class OtBase extends HTMLElement {
|
|
4
|
+
export class OtBase extends HTMLElement {
|
|
5
5
|
#initialized = false;
|
|
6
6
|
|
|
7
7
|
// Called when element is added to DOM.
|
|
@@ -23,9 +23,6 @@ class OtBase extends HTMLElement {
|
|
|
23
23
|
this.init();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// Override in WebComponent subclasses for init logic.
|
|
27
|
-
init() {}
|
|
28
|
-
|
|
29
26
|
// Called when element is removed from DOM.
|
|
30
27
|
disconnectedCallback() {
|
|
31
28
|
this.cleanup();
|
|
@@ -41,6 +38,29 @@ class OtBase extends HTMLElement {
|
|
|
41
38
|
if (handler) handler.call(this, event);
|
|
42
39
|
}
|
|
43
40
|
|
|
41
|
+
// Given a keyboard event (left, right, home, end), the current selection idx
|
|
42
|
+
// total items in a list, return 0-n index of the next/previous item
|
|
43
|
+
// for doing a roving keyboard nav.
|
|
44
|
+
keyNav(event, idx, len, prevKey, nextKey, homeEnd = false) {
|
|
45
|
+
const { key } = event;
|
|
46
|
+
let next = -1;
|
|
47
|
+
|
|
48
|
+
if (key === nextKey) {
|
|
49
|
+
next = (idx + 1) % len;
|
|
50
|
+
} else if (key === prevKey) {
|
|
51
|
+
next = (idx - 1 + len) % len;
|
|
52
|
+
} else if (homeEnd) {
|
|
53
|
+
if (key === 'Home') {
|
|
54
|
+
next = 0;
|
|
55
|
+
} else if (key === 'End') {
|
|
56
|
+
next = len - 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (next >= 0) event.preventDefault();
|
|
61
|
+
return next;
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
// Emit a custom event.
|
|
45
65
|
emit(name, detail = null) {
|
|
46
66
|
return this.dispatchEvent(new CustomEvent(name, {
|
|
@@ -51,20 +71,6 @@ class OtBase extends HTMLElement {
|
|
|
51
71
|
}));
|
|
52
72
|
}
|
|
53
73
|
|
|
54
|
-
// Get boolean attribute value.
|
|
55
|
-
getBool(name) {
|
|
56
|
-
return this.hasAttribute(name);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Set or remove boolean attribute.
|
|
60
|
-
setBool(name, value) {
|
|
61
|
-
if (value) {
|
|
62
|
-
this.setAttribute(name, '');
|
|
63
|
-
} else {
|
|
64
|
-
this.removeAttribute(name);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
74
|
// Query selector within this element.
|
|
69
75
|
$(selector) {
|
|
70
76
|
return this.querySelector(selector);
|
|
@@ -81,15 +87,10 @@ class OtBase extends HTMLElement {
|
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
// Export for use in other files
|
|
85
|
-
if (typeof window !== 'undefined') {
|
|
86
|
-
window.OtBase = OtBase;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
90
|
// Polyfill for command/commandfor (Safari)
|
|
90
91
|
if (!('commandForElement' in HTMLButtonElement.prototype)) {
|
|
91
92
|
document.addEventListener('click', e => {
|
|
92
|
-
const btn = e.target.closest('[commandfor]');
|
|
93
|
+
const btn = e.target.closest('button[commandfor]');
|
|
93
94
|
if (!btn) return;
|
|
94
95
|
|
|
95
96
|
const target = document.getElementById(btn.getAttribute('commandfor'));
|
package/js/dropdown.js
CHANGED
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
* </ot-dropdown>
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { OtBase } from './base.js';
|
|
16
|
+
|
|
15
17
|
class OtDropdown extends OtBase {
|
|
16
18
|
#menu;
|
|
17
19
|
#trigger;
|
|
18
20
|
#position;
|
|
21
|
+
#items;
|
|
19
22
|
|
|
20
23
|
init() {
|
|
21
24
|
this.#menu = this.$('[popover]');
|
|
@@ -29,9 +32,12 @@ class OtDropdown extends OtBase {
|
|
|
29
32
|
this.#position = () => {
|
|
30
33
|
// Position has to be calculated and applied manually because
|
|
31
34
|
// popover positioning is like fixed, relative to the window.
|
|
32
|
-
const
|
|
33
|
-
this.#menu.
|
|
34
|
-
|
|
35
|
+
const r = this.#trigger.getBoundingClientRect();
|
|
36
|
+
const m = this.#menu.getBoundingClientRect();
|
|
37
|
+
|
|
38
|
+
// Flip if menu overflows viewport.
|
|
39
|
+
this.#menu.style.top = `${r.bottom + m.height > window.innerHeight ? r.top - m.height : r.bottom}px`;
|
|
40
|
+
this.#menu.style.left = `${r.left + m.width > window.innerWidth ? r.right - m.width : r.left}px`;
|
|
35
41
|
};
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -39,10 +45,13 @@ class OtDropdown extends OtBase {
|
|
|
39
45
|
if (e.newState === 'open') {
|
|
40
46
|
this.#position();
|
|
41
47
|
window.addEventListener('scroll', this.#position, true);
|
|
42
|
-
|
|
48
|
+
window.addEventListener('resize', this.#position);
|
|
49
|
+
this.#items = this.$$('[role="menuitem"]');
|
|
50
|
+
this.#items[0]?.focus();
|
|
43
51
|
this.#trigger.ariaExpanded = 'true';
|
|
44
52
|
} else {
|
|
45
|
-
|
|
53
|
+
this.cleanup();
|
|
54
|
+
this.#items = null;
|
|
46
55
|
this.#trigger.ariaExpanded = 'false';
|
|
47
56
|
this.#trigger.focus();
|
|
48
57
|
}
|
|
@@ -51,23 +60,14 @@ class OtDropdown extends OtBase {
|
|
|
51
60
|
onkeydown(e) {
|
|
52
61
|
if (!e.target.matches('[role="menuitem"]')) return;
|
|
53
62
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
switch (e.key) {
|
|
58
|
-
case 'ArrowDown':
|
|
59
|
-
e.preventDefault();
|
|
60
|
-
items[(idx + 1) % items.length]?.focus();
|
|
61
|
-
break;
|
|
62
|
-
case 'ArrowUp':
|
|
63
|
-
e.preventDefault();
|
|
64
|
-
items[idx - 1 < 0 ? items.length - 1 : idx - 1]?.focus();
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
63
|
+
const idx = this.#items.indexOf(e.target);
|
|
64
|
+
const next = this.keyNav(e, idx, this.#items.length, 'ArrowUp', 'ArrowDown', true);
|
|
65
|
+
if (next >= 0) this.#items[next].focus();
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
cleanup() {
|
|
70
69
|
window.removeEventListener('scroll', this.#position, true);
|
|
70
|
+
window.removeEventListener('resize', this.#position);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
package/js/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import './base.js';
|
|
2
|
+
import './tabs.js';
|
|
3
|
+
import './dropdown.js';
|
|
4
|
+
import './tooltip.js';
|
|
5
|
+
import './sidebar.js';
|
|
6
|
+
import { toast, toastEl, toastClear } from './toast.js';
|
|
7
|
+
|
|
8
|
+
// Register the global window.ot.* APIs.
|
|
9
|
+
const ot = window.ot || (window.ot = {});
|
|
10
|
+
ot.toast = toast;
|
|
11
|
+
ot.toast.el = toastEl;
|
|
12
|
+
ot.toast.clear = toastClear;
|
package/js/sidebar.js
CHANGED
|
@@ -7,5 +7,16 @@ document.addEventListener('click', (e) => {
|
|
|
7
7
|
if (toggle) {
|
|
8
8
|
const layout = toggle.closest('[data-sidebar-layout]');
|
|
9
9
|
layout?.toggleAttribute('data-sidebar-open');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Dismiss sidebar when clicking outside (when sidebar is not an overlay).
|
|
14
|
+
if (!e.target.closest('[data-sidebar]')) {
|
|
15
|
+
const layout = document.querySelector('[data-sidebar-layout][data-sidebar-open]');
|
|
16
|
+
// Hardcode breakpoint (for now) as there's no way to use a CSS variable in
|
|
17
|
+
// the @media{} query which could've been picked up here.
|
|
18
|
+
if (layout && window.matchMedia('(max-width: 768px)').matches) {
|
|
19
|
+
layout.removeAttribute('data-sidebar-open');
|
|
20
|
+
}
|
|
10
21
|
}
|
|
11
22
|
});
|
package/js/tabs.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* </ot-tabs>
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { OtBase } from './base.js';
|
|
17
|
+
|
|
16
18
|
class OtTabs extends OtBase {
|
|
17
19
|
#tabs = [];
|
|
18
20
|
#panels = [];
|
|
@@ -39,11 +41,11 @@ class OtTabs extends OtBase {
|
|
|
39
41
|
panel.id = panelId;
|
|
40
42
|
tab.setAttribute('aria-controls', panelId);
|
|
41
43
|
panel.setAttribute('aria-labelledby', tabId);
|
|
42
|
-
|
|
43
|
-
tab.addEventListener('click', this);
|
|
44
|
-
tab.addEventListener('keydown', this);
|
|
45
44
|
});
|
|
46
45
|
|
|
46
|
+
tablist.addEventListener('click', this);
|
|
47
|
+
tablist.addEventListener('keydown', this);
|
|
48
|
+
|
|
47
49
|
// Find initially active tab or default to first.
|
|
48
50
|
const activeTab = this.#tabs.findIndex(t => t.ariaSelected === 'true');
|
|
49
51
|
this.#activate(activeTab >= 0 ? activeTab : 0);
|
|
@@ -55,26 +57,13 @@ class OtTabs extends OtBase {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
onkeydown(e) {
|
|
58
|
-
|
|
59
|
-
const idx = this.activeIndex;
|
|
60
|
-
let newIdx = idx;
|
|
61
|
-
|
|
62
|
-
switch (key) {
|
|
63
|
-
case 'ArrowLeft':
|
|
64
|
-
e.preventDefault();
|
|
65
|
-
newIdx = idx - 1;
|
|
66
|
-
if (newIdx < 0) newIdx = this.#tabs.length - 1;
|
|
67
|
-
break;
|
|
68
|
-
case 'ArrowRight':
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
newIdx = (idx + 1) % this.#tabs.length;
|
|
71
|
-
break;
|
|
72
|
-
default:
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
60
|
+
if (!e.target.closest('[role="tab"]')) return;
|
|
75
61
|
|
|
76
|
-
this.#
|
|
77
|
-
|
|
62
|
+
const next = this.keyNav(e, this.activeIndex, this.#tabs.length, 'ArrowLeft', 'ArrowRight');
|
|
63
|
+
if (next >= 0) {
|
|
64
|
+
this.#activate(next);
|
|
65
|
+
this.#tabs[next].focus();
|
|
66
|
+
}
|
|
78
67
|
}
|
|
79
68
|
|
|
80
69
|
#activate(idx) {
|