starlight-theme-bejamas 0.0.0-canary.0138fd3
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/CHANGELOG.md +132 -0
- package/package.json +50 -0
- package/src/config.test.ts +40 -0
- package/src/config.ts +51 -0
- package/src/env.d.ts +16 -0
- package/src/global.d.ts +20 -0
- package/src/index.ts +152 -0
- package/src/lib/theme-choice.test.ts +251 -0
- package/src/lib/theme-choice.ts +346 -0
- package/src/lib/theme-stylesheet-runtime.test.ts +246 -0
- package/src/lib/theme-stylesheet.test.ts +16 -0
- package/src/lib/theme-stylesheet.ts +123 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/vite.ts +62 -0
- package/src/overrides/Footer.astro +143 -0
- package/src/overrides/Header.astro +166 -0
- package/src/overrides/Hero.astro +175 -0
- package/src/overrides/MobileTableOfContents.astro +275 -0
- package/src/overrides/PageFrame.astro +287 -0
- package/src/overrides/PageTitle.astro +142 -0
- package/src/overrides/Pagination.astro +57 -0
- package/src/overrides/Sidebar.astro +54 -0
- package/src/overrides/SiteTitle.astro +59 -0
- package/src/overrides/TableOfContents/TableOfContentsList.astro +88 -0
- package/src/overrides/TableOfContents/starlight-toc.ts +125 -0
- package/src/overrides/ThemeProvider.astro +198 -0
- package/src/overrides/ThemeSelect.astro +110 -0
- package/src/styles/theme.css +776 -0
- package/src/virtual-internal.d.ts +40 -0
- package/src/virtual-modules.d.ts +28 -0
- package/src/virtual.d.ts +12 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from "virtual:starlight/user-config";
|
|
3
|
+
|
|
4
|
+
import LanguageSelect from "virtual:starlight/components/LanguageSelect";
|
|
5
|
+
import Search from "virtual:starlight/components/Search";
|
|
6
|
+
import SiteTitle from "virtual:starlight/components/SiteTitle";
|
|
7
|
+
import SocialIcons from "virtual:starlight/components/SocialIcons";
|
|
8
|
+
import ThemeSelect from "virtual:starlight/components/ThemeSelect";
|
|
9
|
+
|
|
10
|
+
import options from "virtual:starlight-theme-bejamas/user-config";
|
|
11
|
+
|
|
12
|
+
import { Button } from "virtual:starlight-theme-bejamas/components/button";
|
|
13
|
+
/**
|
|
14
|
+
* Render the `Search` component if Pagefind is enabled or the default search component has been overridden.
|
|
15
|
+
*/
|
|
16
|
+
const shouldRenderSearch =
|
|
17
|
+
config.pagefind ||
|
|
18
|
+
config.components.Search !== "@astrojs/starlight/components/Search.astro";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Render the `nav` items if they are defined.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const shouldRenderNav = options.nav && options.nav.length > 0;
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div class="header">
|
|
28
|
+
<div class="sl-bejamas-header-title">
|
|
29
|
+
<SiteTitle />
|
|
30
|
+
{
|
|
31
|
+
shouldRenderNav && (
|
|
32
|
+
<nav class="sl-bejamas-main-nav">
|
|
33
|
+
<ul class="gap-1 flex items-center">
|
|
34
|
+
{options.nav.map((item) => (
|
|
35
|
+
<li>
|
|
36
|
+
<Button as="a" variant="ghost" href={item.href}>
|
|
37
|
+
{item.label}
|
|
38
|
+
</Button>
|
|
39
|
+
</li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
</nav>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
</div>
|
|
46
|
+
<div class="sl-flex sl-bejamas-header-search sl-justify-end print:hidden">
|
|
47
|
+
{shouldRenderSearch && <Search />}
|
|
48
|
+
</div>
|
|
49
|
+
<div class="header-desktop-right print:hidden right-group">
|
|
50
|
+
<div class="sl-flex social-icons">
|
|
51
|
+
<SocialIcons />
|
|
52
|
+
</div>
|
|
53
|
+
<ThemeSelect />
|
|
54
|
+
<LanguageSelect />
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<style>
|
|
59
|
+
@layer bejamas {
|
|
60
|
+
.sl-bejamas-header-title {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 1.25rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sl-bejamas-header-search {
|
|
67
|
+
margin-left: auto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.header {
|
|
71
|
+
display: flex;
|
|
72
|
+
gap: var(--sl-nav-gap);
|
|
73
|
+
justify-content: space-between;
|
|
74
|
+
align-items: center;
|
|
75
|
+
height: 100%;
|
|
76
|
+
position: relative;
|
|
77
|
+
z-index: 10;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sl-bejamas-main-nav {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.header-desktop-right {
|
|
85
|
+
display: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (min-width: 64rem) {
|
|
89
|
+
.sl-bejamas-main-nav {
|
|
90
|
+
display: block;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.header-desktop-right {
|
|
94
|
+
display: flex;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sl-justify-end {
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.title-wrapper {
|
|
103
|
+
/* Prevent long titles overflowing and covering the search and menu buttons on narrow viewports. */
|
|
104
|
+
overflow: clip;
|
|
105
|
+
/* Avoid clipping focus ring around link inside title wrapper. */
|
|
106
|
+
padding: 0.25rem;
|
|
107
|
+
margin: -0.25rem;
|
|
108
|
+
min-width: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.right-group,
|
|
112
|
+
.social-icons {
|
|
113
|
+
gap: 1rem;
|
|
114
|
+
align-items: center;
|
|
115
|
+
}
|
|
116
|
+
.social-icons::after {
|
|
117
|
+
content: "";
|
|
118
|
+
height: 2rem;
|
|
119
|
+
border-inline-end: 1px solid var(--sl-color-gray-5);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (min-width: 64rem) {
|
|
123
|
+
:global(:root[data-has-sidebar]) {
|
|
124
|
+
--__sidebar-pad: calc(2 * var(--sl-nav-pad-x));
|
|
125
|
+
}
|
|
126
|
+
:global(.page-mobile-sidebar-only) .header {
|
|
127
|
+
--__sidebar-pad: 0rem;
|
|
128
|
+
display: flex;
|
|
129
|
+
grid-template-columns: none;
|
|
130
|
+
}
|
|
131
|
+
:global(:root:not([data-has-toc])) {
|
|
132
|
+
--__toc-width: 0rem;
|
|
133
|
+
}
|
|
134
|
+
.header {
|
|
135
|
+
--__sidebar-width: max(
|
|
136
|
+
0rem,
|
|
137
|
+
var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x)
|
|
138
|
+
);
|
|
139
|
+
--__main-column-fr: calc(
|
|
140
|
+
(
|
|
141
|
+
100% + var(--__sidebar-pad, 0rem) -
|
|
142
|
+
var(--__toc-width, var(--sl-sidebar-width)) -
|
|
143
|
+
(2 * var(--__toc-width, var(--sl-nav-pad-x))) -
|
|
144
|
+
var(--sl-content-inline-start, 0rem) - var(--sl-content-width)
|
|
145
|
+
) /
|
|
146
|
+
2
|
|
147
|
+
);
|
|
148
|
+
display: grid;
|
|
149
|
+
grid-template-columns:
|
|
150
|
+
/* 1 (site title): runs up until the main content column’s left edge or the width of the title, whichever is the largest */
|
|
151
|
+
minmax(
|
|
152
|
+
calc(
|
|
153
|
+
var(--__sidebar-width) +
|
|
154
|
+
max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))
|
|
155
|
+
),
|
|
156
|
+
auto
|
|
157
|
+
)
|
|
158
|
+
/* 2 (search box): all free space that is available. */
|
|
159
|
+
1fr
|
|
160
|
+
/* 3 (right items): use the space that these need. */
|
|
161
|
+
auto;
|
|
162
|
+
align-content: center;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
</style>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Image } from "astro:assets";
|
|
3
|
+
import { Button } from "virtual:starlight-theme-bejamas/components/button";
|
|
4
|
+
|
|
5
|
+
const { data } = Astro.locals.starlightRoute.entry;
|
|
6
|
+
const { title = data.title, tagline, image, actions = [] } = data.hero || {};
|
|
7
|
+
|
|
8
|
+
const PAGE_TITLE_ID = "_top";
|
|
9
|
+
|
|
10
|
+
const imageAttrs = {
|
|
11
|
+
loading: "eager" as const,
|
|
12
|
+
decoding: "async" as const,
|
|
13
|
+
width: 400,
|
|
14
|
+
height: 400,
|
|
15
|
+
alt: image?.alt || "",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let darkImage: ImageMetadata | undefined;
|
|
19
|
+
let lightImage: ImageMetadata | undefined;
|
|
20
|
+
let rawHtml: string | undefined;
|
|
21
|
+
if (image) {
|
|
22
|
+
if ("file" in image) {
|
|
23
|
+
darkImage = image.file;
|
|
24
|
+
} else if ("dark" in image) {
|
|
25
|
+
darkImage = image.dark;
|
|
26
|
+
lightImage = image.light;
|
|
27
|
+
} else {
|
|
28
|
+
rawHtml = image.html;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const startlightVariantToBejamasVariant = {
|
|
33
|
+
primary: "default",
|
|
34
|
+
secondary: "secondary",
|
|
35
|
+
minimal: "outline",
|
|
36
|
+
};
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<div class="hero">
|
|
40
|
+
{
|
|
41
|
+
darkImage && (
|
|
42
|
+
<Image
|
|
43
|
+
src={darkImage}
|
|
44
|
+
{...imageAttrs}
|
|
45
|
+
class:list={{ "light:sl-hidden": Boolean(lightImage) }}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
{
|
|
50
|
+
lightImage && (
|
|
51
|
+
<Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" />
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
{rawHtml && <div class="hero-html sl-flex" set:html={rawHtml} />}
|
|
55
|
+
<div class="sl-flex stack">
|
|
56
|
+
<div class="sl-flex copy">
|
|
57
|
+
<h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
|
|
58
|
+
{tagline && <div class="tagline" set:html={tagline} />}
|
|
59
|
+
</div>
|
|
60
|
+
{
|
|
61
|
+
actions.length > 0 && (
|
|
62
|
+
<div class="sl-flex actions">
|
|
63
|
+
{actions.map(
|
|
64
|
+
({
|
|
65
|
+
attrs: { class: className, ...attrs } = {},
|
|
66
|
+
icon,
|
|
67
|
+
link: href,
|
|
68
|
+
text,
|
|
69
|
+
variant,
|
|
70
|
+
}) => (
|
|
71
|
+
<Button
|
|
72
|
+
{href}
|
|
73
|
+
variant={startlightVariantToBejamasVariant[variant]}
|
|
74
|
+
as="a"
|
|
75
|
+
icon={icon?.name}
|
|
76
|
+
class:list={[className]}
|
|
77
|
+
{...attrs}
|
|
78
|
+
>
|
|
79
|
+
{text}
|
|
80
|
+
{icon?.html && <Fragment set:html={icon.html} />}
|
|
81
|
+
</Button>
|
|
82
|
+
),
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
@layer starlight.core {
|
|
92
|
+
.hero {
|
|
93
|
+
display: grid;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 1rem;
|
|
96
|
+
padding-bottom: 1rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.hero > img,
|
|
100
|
+
.hero > .hero-html {
|
|
101
|
+
object-fit: contain;
|
|
102
|
+
width: min(70%, 20rem);
|
|
103
|
+
height: auto;
|
|
104
|
+
margin-inline: auto;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.stack {
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
gap: clamp(1.5rem, calc(1.5rem + 1vw), 2rem);
|
|
110
|
+
text-align: center;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.copy {
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
gap: 1rem;
|
|
116
|
+
align-items: center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.copy > * {
|
|
120
|
+
max-width: 50ch;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
h1 {
|
|
124
|
+
font-size: clamp(
|
|
125
|
+
var(--sl-text-3xl),
|
|
126
|
+
calc(0.25rem + 5vw),
|
|
127
|
+
var(--sl-text-6xl)
|
|
128
|
+
);
|
|
129
|
+
line-height: var(--sl-line-height-headings);
|
|
130
|
+
font-weight: 600;
|
|
131
|
+
color: var(--foreground);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.tagline {
|
|
135
|
+
font-size: clamp(
|
|
136
|
+
var(--sl-text-base),
|
|
137
|
+
calc(0.0625rem + 2vw),
|
|
138
|
+
var(--sl-text-xl)
|
|
139
|
+
);
|
|
140
|
+
color: var(--muted-foreground);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.actions {
|
|
144
|
+
gap: 1rem 0.75rem;
|
|
145
|
+
flex-wrap: wrap;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (min-width: 50rem) {
|
|
150
|
+
.hero {
|
|
151
|
+
grid-template-columns: 7fr 4fr;
|
|
152
|
+
gap: 3%;
|
|
153
|
+
padding-block: clamp(2.5rem, calc(1rem + 10vmin), 10rem);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.hero > img,
|
|
157
|
+
.hero > .hero-html {
|
|
158
|
+
order: 2;
|
|
159
|
+
width: min(100%, 25rem);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.stack {
|
|
163
|
+
text-align: start;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.copy {
|
|
167
|
+
align-items: flex-start;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.actions {
|
|
171
|
+
justify-content: flex-start;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</style>
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
---
|
|
2
|
+
import TableOfContentsList from "./TableOfContents/TableOfContentsList.astro";
|
|
3
|
+
|
|
4
|
+
const { toc } = Astro.locals.starlightRoute;
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
toc && (
|
|
9
|
+
<mobile-starlight-toc
|
|
10
|
+
data-min-h={toc.minHeadingLevel}
|
|
11
|
+
data-max-h={toc.maxHeadingLevel}
|
|
12
|
+
>
|
|
13
|
+
<nav aria-labelledby="starlight__on-this-page--mobile">
|
|
14
|
+
<div class="border-t starlight__separator" />
|
|
15
|
+
<details id="starlight__mobile-toc">
|
|
16
|
+
<summary id="starlight__on-this-page--mobile" class="sl-flex">
|
|
17
|
+
<span class="toggle sl-flex">
|
|
18
|
+
<span class="toggle-label sl-flex">
|
|
19
|
+
<span
|
|
20
|
+
class="toc-progress"
|
|
21
|
+
role="progressbar"
|
|
22
|
+
aria-label="Current section progress"
|
|
23
|
+
aria-valuemin="0"
|
|
24
|
+
aria-valuemax="100"
|
|
25
|
+
aria-valuenow="0"
|
|
26
|
+
>
|
|
27
|
+
<span class="toc-progress__fill" aria-hidden="true" />
|
|
28
|
+
</span>
|
|
29
|
+
<span class="toggle-text">
|
|
30
|
+
{Astro.locals.t("tableOfContents.onThisPage")}
|
|
31
|
+
</span>
|
|
32
|
+
</span>
|
|
33
|
+
</span>
|
|
34
|
+
<span class="display-current" />
|
|
35
|
+
</summary>
|
|
36
|
+
<div class="dropdown">
|
|
37
|
+
<TableOfContentsList toc={toc.items} isMobile />
|
|
38
|
+
</div>
|
|
39
|
+
</details>
|
|
40
|
+
</nav>
|
|
41
|
+
</mobile-starlight-toc>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
<style>
|
|
46
|
+
@layer starlight.core {
|
|
47
|
+
nav {
|
|
48
|
+
position: fixed;
|
|
49
|
+
z-index: var(--sl-z-index-toc);
|
|
50
|
+
top: calc(var(--sl-nav-height) - 1px);
|
|
51
|
+
inset-inline: 0;
|
|
52
|
+
border-top: 1px solid var(--sl-color-gray-5);
|
|
53
|
+
background-color: var(--sl-color-bg-nav);
|
|
54
|
+
}
|
|
55
|
+
@media (min-width: 64rem) {
|
|
56
|
+
nav {
|
|
57
|
+
inset-inline-start: var(--sl-content-inline-start, 0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
summary {
|
|
62
|
+
gap: 0.5rem;
|
|
63
|
+
align-items: center;
|
|
64
|
+
height: var(--sl-mobile-toc-height);
|
|
65
|
+
border-bottom: 1px solid var(--sl-color-hairline-shade);
|
|
66
|
+
padding: 0.5rem calc(var(--sl-content-pad-x) + var(--spacing));
|
|
67
|
+
font-size: var(--sl-text-xs);
|
|
68
|
+
outline-offset: var(--sl-outline-offset-inside);
|
|
69
|
+
}
|
|
70
|
+
summary::marker,
|
|
71
|
+
summary::-webkit-details-marker {
|
|
72
|
+
display: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.starlight__separator {
|
|
76
|
+
margin-inline: calc(var(--spacing) * 6);
|
|
77
|
+
}
|
|
78
|
+
@media (min-width: 64rem) {
|
|
79
|
+
.starlight__separator {
|
|
80
|
+
margin-left: 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.toggle {
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
gap: 1rem;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: space-between;
|
|
89
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
90
|
+
border-radius: calc(var(--radius) - 2px);
|
|
91
|
+
padding-block: 0.5rem;
|
|
92
|
+
padding-inline-start: 0.75rem;
|
|
93
|
+
padding-inline-end: 0.5rem;
|
|
94
|
+
line-height: 1;
|
|
95
|
+
background-color: var(--background);
|
|
96
|
+
user-select: none;
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
font-weight: 500;
|
|
99
|
+
}
|
|
100
|
+
.toggle-label {
|
|
101
|
+
gap: calc(var(--spacing) * 2);
|
|
102
|
+
align-items: center;
|
|
103
|
+
}
|
|
104
|
+
.toc-progress {
|
|
105
|
+
--toc-progress-size: calc(var(--spacing) * 4);
|
|
106
|
+
--toc-progress-fill: var(--accent);
|
|
107
|
+
display: inline-flex;
|
|
108
|
+
width: var(--toc-progress-size);
|
|
109
|
+
height: var(--toc-progress-size);
|
|
110
|
+
border-radius: 999px;
|
|
111
|
+
border: 1px solid var(--toc-progress-fill);
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
/* background: radial-gradient(
|
|
115
|
+
circle,
|
|
116
|
+
rgba(2, 123, 249, 0.25) 0,
|
|
117
|
+
rgba(2, 123, 249, 0.25) 60%,
|
|
118
|
+
transparent 60%
|
|
119
|
+
); */
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
}
|
|
122
|
+
.toc-progress__fill {
|
|
123
|
+
--toc-progress-angle: 0deg;
|
|
124
|
+
width: calc(var(--toc-progress-size) - calc(var(--spacing) * 1.5));
|
|
125
|
+
height: calc(var(--toc-progress-size) - calc(var(--spacing) * 1.5));
|
|
126
|
+
border-radius: inherit;
|
|
127
|
+
background: conic-gradient(
|
|
128
|
+
from -90deg,
|
|
129
|
+
var(--toc-progress-fill) var(--toc-progress-angle),
|
|
130
|
+
var(--background) var(--toc-progress-angle)
|
|
131
|
+
);
|
|
132
|
+
/* box-shadow: 0 0 2px rgba(2, 123, 249, 0.4); */
|
|
133
|
+
transition: background 200ms ease-out;
|
|
134
|
+
}
|
|
135
|
+
details[open] .toggle {
|
|
136
|
+
color: var(--foreground);
|
|
137
|
+
border-color: var(--sl-color-accent);
|
|
138
|
+
}
|
|
139
|
+
details .toggle:hover {
|
|
140
|
+
color: var(--foreground);
|
|
141
|
+
border-color: var(--sl-color-gray-2);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:global([dir="rtl"]) .caret {
|
|
145
|
+
transform: rotateZ(180deg);
|
|
146
|
+
}
|
|
147
|
+
details[open] .caret {
|
|
148
|
+
transform: rotateZ(90deg);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.display-current {
|
|
152
|
+
white-space: nowrap;
|
|
153
|
+
text-overflow: ellipsis;
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
color: var(--foreground);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.dropdown {
|
|
159
|
+
--border-top: 1px;
|
|
160
|
+
margin-top: calc(-1 * var(--border-top));
|
|
161
|
+
border: var(--border-top) solid var(--sl-color-gray-6);
|
|
162
|
+
border-top-color: var(--sl-color-hairline-shade);
|
|
163
|
+
max-height: calc(
|
|
164
|
+
85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height)
|
|
165
|
+
);
|
|
166
|
+
overflow-y: auto;
|
|
167
|
+
background-color: var(--popover);
|
|
168
|
+
box-shadow: var(--sl-shadow-md);
|
|
169
|
+
overscroll-behavior: contain;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
173
|
+
|
|
174
|
+
<script>
|
|
175
|
+
import { StarlightTOC } from "./TableOfContents/starlight-toc";
|
|
176
|
+
|
|
177
|
+
class MobileStarlightTOC extends StarlightTOC {
|
|
178
|
+
private displayCurrent: HTMLSpanElement | null = null;
|
|
179
|
+
private indicator: HTMLSpanElement | null = null;
|
|
180
|
+
private indicatorFill: HTMLSpanElement | null = null;
|
|
181
|
+
private tocLinks: HTMLAnchorElement[] = [];
|
|
182
|
+
|
|
183
|
+
override set current(link: HTMLAnchorElement) {
|
|
184
|
+
super.current = link;
|
|
185
|
+
this.updateDisplay(link);
|
|
186
|
+
this.updateProgress(link);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
constructor() {
|
|
190
|
+
super();
|
|
191
|
+
this.displayCurrent = this.querySelector(".display-current");
|
|
192
|
+
this.indicator = this.querySelector(".toc-progress");
|
|
193
|
+
this.indicatorFill = this.querySelector(".toc-progress__fill");
|
|
194
|
+
|
|
195
|
+
const links = this.getLinks();
|
|
196
|
+
const initial = links.find(
|
|
197
|
+
(a) => a.getAttribute("aria-current") === "true",
|
|
198
|
+
);
|
|
199
|
+
if (initial) {
|
|
200
|
+
this.updateDisplay(initial);
|
|
201
|
+
this.updateProgress(initial);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const details = this.querySelector("details");
|
|
205
|
+
if (!details) return;
|
|
206
|
+
const closeToC = () => {
|
|
207
|
+
details.open = false;
|
|
208
|
+
};
|
|
209
|
+
// Close the table of contents whenever a link is clicked.
|
|
210
|
+
links.forEach((a) => {
|
|
211
|
+
a.addEventListener("click", closeToC);
|
|
212
|
+
});
|
|
213
|
+
// Close the table of contents when a user clicks outside of it.
|
|
214
|
+
window.addEventListener("click", (e) => {
|
|
215
|
+
if (!details.contains(e.target as Node)) closeToC();
|
|
216
|
+
});
|
|
217
|
+
// Or when they press the escape key.
|
|
218
|
+
window.addEventListener("keydown", (e) => {
|
|
219
|
+
if (e.key === "Escape" && details.open) {
|
|
220
|
+
const hasFocus = details.contains(document.activeElement);
|
|
221
|
+
closeToC();
|
|
222
|
+
if (hasFocus) {
|
|
223
|
+
const summary = details.querySelector("summary");
|
|
224
|
+
if (summary) summary.focus();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private getLinks(): HTMLAnchorElement[] {
|
|
231
|
+
if (!this.tocLinks.length) {
|
|
232
|
+
this.tocLinks = Array.from(
|
|
233
|
+
this.querySelectorAll<HTMLAnchorElement>("details a"),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return this.tocLinks;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private updateDisplay(link: HTMLAnchorElement) {
|
|
240
|
+
if (!this.displayCurrent) {
|
|
241
|
+
this.displayCurrent = this.querySelector(
|
|
242
|
+
".display-current",
|
|
243
|
+
) as HTMLSpanElement | null;
|
|
244
|
+
}
|
|
245
|
+
if (!this.displayCurrent) return;
|
|
246
|
+
const text = (link.textContent || "").trim();
|
|
247
|
+
this.displayCurrent.textContent = text;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private updateProgress(link: HTMLAnchorElement) {
|
|
251
|
+
if (!this.indicator) {
|
|
252
|
+
this.indicator = this.querySelector(".toc-progress");
|
|
253
|
+
}
|
|
254
|
+
if (!this.indicatorFill) {
|
|
255
|
+
this.indicatorFill = this.querySelector(
|
|
256
|
+
".toc-progress__fill",
|
|
257
|
+
) as HTMLSpanElement | null;
|
|
258
|
+
}
|
|
259
|
+
const indicator = this.indicator;
|
|
260
|
+
const fill = this.indicatorFill;
|
|
261
|
+
const links = this.getLinks();
|
|
262
|
+
if (!indicator || !fill || !links.length) return;
|
|
263
|
+
const index = links.indexOf(link);
|
|
264
|
+
if (index === -1) return;
|
|
265
|
+
const progress = (index + 1) / links.length;
|
|
266
|
+
const percent = Math.round(progress * 100);
|
|
267
|
+
const angle = `${(progress * 360).toFixed(2)}deg`;
|
|
268
|
+
fill.style.setProperty("--toc-progress-angle", angle);
|
|
269
|
+
indicator.setAttribute("aria-valuenow", percent.toString());
|
|
270
|
+
indicator.setAttribute("aria-valuetext", `${percent}% of sections read`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
customElements.define("mobile-starlight-toc", MobileStarlightTOC);
|
|
275
|
+
</script>
|