starlight-theme-bejamas 0.2.0 → 0.2.3
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 +22 -0
- package/package.json +7 -8
- package/src/index.ts +0 -7
- package/src/lib/theme-stylesheet-runtime.test.ts +246 -0
- package/src/lib/theme-stylesheet.ts +112 -2
- package/src/overrides/PageFrame.astro +40 -83
- package/src/overrides/PageTitle.astro +1 -1
- package/src/overrides/ThemeProvider.astro +7 -58
- package/src/styles/theme.css +38 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# starlight-theme-bejamas
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`d8abe23`](https://github.com/bejamas/ui/commit/d8abe237f179cc6a5be73823a134a6221c62cca5)]:
|
|
8
|
+
- @bejamas/semantic-icons@0.2.0
|
|
9
|
+
|
|
10
|
+
## 0.2.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#159](https://github.com/bejamas/ui/pull/159) [`d9fb503`](https://github.com/bejamas/ui/commit/d9fb50331b2692cad08f1653611b952312e6d7fe) Thanks [@Tadzik-2](https://github.com/Tadzik-2)! - Point sidebar group carets down when collapsed and up when expanded.
|
|
15
|
+
|
|
16
|
+
## 0.2.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- [#143](https://github.com/bejamas/ui/pull/143) [`94b6e69`](https://github.com/bejamas/ui/commit/94b6e698d22920f974d263709828cb9cfaa1bce5) Thanks [@Tadzik-2](https://github.com/Tadzik-2)! - Publish semantic icons publicly and validate packed release manifests so the theme installs without workspace protocol or private runtime dependencies.
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [[`94b6e69`](https://github.com/bejamas/ui/commit/94b6e698d22920f974d263709828cb9cfaa1bce5)]:
|
|
23
|
+
- @bejamas/semantic-icons@0.1.1
|
|
24
|
+
|
|
3
25
|
## 0.2.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-theme-bejamas",
|
|
3
3
|
"author": "Bejamas",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A Starlight theme using bejamas/ui",
|
|
7
7
|
"type": "module",
|
|
@@ -38,14 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"bugs": "https://github.com/bejamas/ui/issues",
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@astrojs/starlight": "
|
|
42
|
-
"astro": "
|
|
41
|
+
"@astrojs/starlight": "^0.42.3",
|
|
42
|
+
"astro": "^7.3.4"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@bejamas/semantic-icons": "
|
|
46
|
-
"@
|
|
47
|
-
"@fontsource
|
|
48
|
-
"@
|
|
49
|
-
"@lucide/astro": "^0.545.0"
|
|
45
|
+
"@bejamas/semantic-icons": "0.2.0",
|
|
46
|
+
"@fontsource-variable/inter": "^5.3.0",
|
|
47
|
+
"@fontsource/inter": "^5.3.0",
|
|
48
|
+
"@lucide/astro": "^1.47.0"
|
|
50
49
|
}
|
|
51
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
type StarlightThemeBejamasUserConfig,
|
|
12
12
|
} from "./config";
|
|
13
13
|
import { vitePluginStarlightThemeBejamas } from "./lib/vite";
|
|
14
|
-
// import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
|
|
15
14
|
|
|
16
15
|
export default function starlightThemeBejamas(
|
|
17
16
|
userConfig: StarlightThemeBejamasUserConfig,
|
|
@@ -48,12 +47,6 @@ export default function starlightThemeBejamas(
|
|
|
48
47
|
? {}
|
|
49
48
|
: starlightConfig.expressiveCode;
|
|
50
49
|
|
|
51
|
-
// starlightConfig.plugins?.push(
|
|
52
|
-
// pluginLineNumbers({
|
|
53
|
-
// showLineNumbers: true,
|
|
54
|
-
// }),
|
|
55
|
-
// );
|
|
56
|
-
|
|
57
50
|
const componentOverrides: typeof config.components = {};
|
|
58
51
|
|
|
59
52
|
type OverridableComponent =
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { refreshCurrentThemeStylesheet } from "./theme-stylesheet";
|
|
3
|
+
|
|
4
|
+
const ACTIVE_SELECTOR = "link[data-current-theme-stylesheet]";
|
|
5
|
+
const PENDING_SELECTOR = "link[data-pending-current-theme-stylesheet]";
|
|
6
|
+
|
|
7
|
+
type Listener = (event: Event) => void;
|
|
8
|
+
|
|
9
|
+
class FakeLinkElement {
|
|
10
|
+
href = "";
|
|
11
|
+
rel = "stylesheet";
|
|
12
|
+
private attributes = new Map<string, string>();
|
|
13
|
+
private listeners = new Map<string, Set<Listener>>();
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly document: FakeDocument,
|
|
17
|
+
href = "",
|
|
18
|
+
) {
|
|
19
|
+
this.href = href;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
cloneNode() {
|
|
23
|
+
const clone = new FakeLinkElement(this.document, this.href);
|
|
24
|
+
clone.rel = this.rel;
|
|
25
|
+
|
|
26
|
+
for (const [name, value] of this.attributes) {
|
|
27
|
+
clone.attributes.set(name, value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return clone;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setAttribute(name: string, value: string) {
|
|
34
|
+
this.attributes.set(name, value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
removeAttribute(name: string) {
|
|
38
|
+
this.attributes.delete(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
hasAttribute(name: string) {
|
|
42
|
+
return this.attributes.has(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
addEventListener(type: string, listener: Listener) {
|
|
46
|
+
const listeners = this.listeners.get(type) ?? new Set<Listener>();
|
|
47
|
+
listeners.add(listener);
|
|
48
|
+
this.listeners.set(type, listeners);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
removeEventListener(type: string, listener: Listener) {
|
|
52
|
+
this.listeners.get(type)?.delete(listener);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dispatchEvent(event: Event) {
|
|
56
|
+
this.listeners.get(event.type)?.forEach((listener) => listener(event));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
insertAdjacentElement(position: InsertPosition, element: FakeLinkElement) {
|
|
61
|
+
if (position !== "afterend") {
|
|
62
|
+
throw new Error(`Unsupported insert position: ${position}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.document.insertAfter(this, element);
|
|
66
|
+
return element;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
remove() {
|
|
70
|
+
this.document.remove(this);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class FakeDocument {
|
|
75
|
+
links: FakeLinkElement[] = [];
|
|
76
|
+
|
|
77
|
+
querySelector<T extends FakeLinkElement>(selector: string) {
|
|
78
|
+
return (this.querySelectorAll(selector)[0] ?? null) as T | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
querySelectorAll<T extends FakeLinkElement>(selector: string) {
|
|
82
|
+
if (selector === ACTIVE_SELECTOR) {
|
|
83
|
+
return this.links.filter((link) =>
|
|
84
|
+
link.hasAttribute("data-current-theme-stylesheet"),
|
|
85
|
+
) as T[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (selector === PENDING_SELECTOR) {
|
|
89
|
+
return this.links.filter((link) =>
|
|
90
|
+
link.hasAttribute("data-pending-current-theme-stylesheet"),
|
|
91
|
+
) as T[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (selector === "link") {
|
|
95
|
+
return [...this.links] as T[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [] as T[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
append(link: FakeLinkElement) {
|
|
102
|
+
this.links.push(link);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
insertAfter(target: FakeLinkElement, link: FakeLinkElement) {
|
|
106
|
+
const index = this.links.indexOf(target);
|
|
107
|
+
if (index === -1) {
|
|
108
|
+
this.links.push(link);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.links.splice(index + 1, 0, link);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
remove(link: FakeLinkElement) {
|
|
116
|
+
this.links = this.links.filter((entry) => entry !== link);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const originalDocument = globalThis.document;
|
|
121
|
+
const originalWindow = globalThis.window;
|
|
122
|
+
|
|
123
|
+
function createEnvironment() {
|
|
124
|
+
const document = new FakeDocument();
|
|
125
|
+
const window = {
|
|
126
|
+
location: { origin: "https://ui.bejamas.com" },
|
|
127
|
+
requestAnimationFrame: (callback: FrameRequestCallback) => {
|
|
128
|
+
callback(0);
|
|
129
|
+
return 1;
|
|
130
|
+
},
|
|
131
|
+
setTimeout,
|
|
132
|
+
} as unknown as Window & typeof globalThis;
|
|
133
|
+
|
|
134
|
+
globalThis.document = document as unknown as Document;
|
|
135
|
+
globalThis.window = window;
|
|
136
|
+
|
|
137
|
+
return { document };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function appendActiveStylesheet(document: FakeDocument, href?: string) {
|
|
141
|
+
const stylesheet = new FakeLinkElement(
|
|
142
|
+
document,
|
|
143
|
+
href ?? "https://ui.bejamas.com/r/themes/current-theme.css",
|
|
144
|
+
);
|
|
145
|
+
stylesheet.setAttribute("data-current-theme-stylesheet", "");
|
|
146
|
+
document.append(stylesheet);
|
|
147
|
+
return stylesheet;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
describe("refreshCurrentThemeStylesheet", () => {
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
createEnvironment();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
afterEach(() => {
|
|
156
|
+
globalThis.document = originalDocument;
|
|
157
|
+
globalThis.window = originalWindow;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("keeps the current stylesheet active until the replacement loads", async () => {
|
|
161
|
+
const document = globalThis.document as unknown as FakeDocument;
|
|
162
|
+
const currentStylesheet = appendActiveStylesheet(document);
|
|
163
|
+
|
|
164
|
+
const refreshPromise = refreshCurrentThemeStylesheet();
|
|
165
|
+
const pendingStylesheet =
|
|
166
|
+
document.querySelector<FakeLinkElement>(PENDING_SELECTOR);
|
|
167
|
+
|
|
168
|
+
expect(refreshPromise).toBeInstanceOf(Promise);
|
|
169
|
+
expect(document.querySelectorAll("link")).toHaveLength(2);
|
|
170
|
+
expect(document.querySelector(ACTIVE_SELECTOR)).toBe(currentStylesheet);
|
|
171
|
+
expect(pendingStylesheet).not.toBeNull();
|
|
172
|
+
expect(pendingStylesheet?.href).not.toBe(currentStylesheet.href);
|
|
173
|
+
|
|
174
|
+
pendingStylesheet?.dispatchEvent(new Event("load"));
|
|
175
|
+
await refreshPromise;
|
|
176
|
+
|
|
177
|
+
const activeStylesheet =
|
|
178
|
+
document.querySelector<FakeLinkElement>(ACTIVE_SELECTOR);
|
|
179
|
+
expect(document.querySelectorAll("link")).toHaveLength(1);
|
|
180
|
+
expect(document.querySelector(PENDING_SELECTOR)).toBeNull();
|
|
181
|
+
expect(activeStylesheet).toBe(pendingStylesheet);
|
|
182
|
+
expect(activeStylesheet?.href).toContain("/r/themes/current-theme.css?v=");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("keeps the current stylesheet when the replacement fails to load", async () => {
|
|
186
|
+
const document = globalThis.document as unknown as FakeDocument;
|
|
187
|
+
const currentStylesheet = appendActiveStylesheet(document);
|
|
188
|
+
|
|
189
|
+
const refreshPromise = refreshCurrentThemeStylesheet();
|
|
190
|
+
const pendingStylesheet =
|
|
191
|
+
document.querySelector<FakeLinkElement>(PENDING_SELECTOR);
|
|
192
|
+
|
|
193
|
+
pendingStylesheet?.dispatchEvent(new Event("error"));
|
|
194
|
+
await refreshPromise;
|
|
195
|
+
|
|
196
|
+
expect(document.querySelectorAll("link")).toHaveLength(1);
|
|
197
|
+
expect(document.querySelector(ACTIVE_SELECTOR)).toBe(currentStylesheet);
|
|
198
|
+
expect(document.querySelector(PENDING_SELECTOR)).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("uses a unique cache-busting version for each refresh", async () => {
|
|
202
|
+
const document = globalThis.document as unknown as FakeDocument;
|
|
203
|
+
appendActiveStylesheet(document);
|
|
204
|
+
|
|
205
|
+
const firstRefresh = refreshCurrentThemeStylesheet();
|
|
206
|
+
const firstHref =
|
|
207
|
+
document.querySelector<FakeLinkElement>(PENDING_SELECTOR)?.href;
|
|
208
|
+
|
|
209
|
+
// A second refresh in the same tick (same Date.now()) must still produce a
|
|
210
|
+
// distinct URL, otherwise the browser can reuse a stale cached response.
|
|
211
|
+
const secondRefresh = refreshCurrentThemeStylesheet();
|
|
212
|
+
const secondPending =
|
|
213
|
+
document.querySelector<FakeLinkElement>(PENDING_SELECTOR);
|
|
214
|
+
|
|
215
|
+
expect(secondPending?.href).toContain("/r/themes/current-theme.css?v=");
|
|
216
|
+
expect(secondPending?.href).not.toBe(firstHref);
|
|
217
|
+
|
|
218
|
+
secondPending?.dispatchEvent(new Event("load"));
|
|
219
|
+
await Promise.all([firstRefresh, secondRefresh]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("cancels an older pending swap when a newer one starts", async () => {
|
|
223
|
+
const document = globalThis.document as unknown as FakeDocument;
|
|
224
|
+
appendActiveStylesheet(document);
|
|
225
|
+
|
|
226
|
+
const firstRefresh = refreshCurrentThemeStylesheet();
|
|
227
|
+
const firstPendingStylesheet =
|
|
228
|
+
document.querySelector<FakeLinkElement>(PENDING_SELECTOR);
|
|
229
|
+
|
|
230
|
+
const secondRefresh = refreshCurrentThemeStylesheet();
|
|
231
|
+
const secondPendingStylesheet =
|
|
232
|
+
document.querySelector<FakeLinkElement>(PENDING_SELECTOR);
|
|
233
|
+
|
|
234
|
+
expect(secondPendingStylesheet).not.toBe(firstPendingStylesheet);
|
|
235
|
+
expect(document.querySelectorAll(PENDING_SELECTOR)).toHaveLength(1);
|
|
236
|
+
|
|
237
|
+
secondPendingStylesheet?.dispatchEvent(new Event("load"));
|
|
238
|
+
await Promise.all([firstRefresh, secondRefresh]);
|
|
239
|
+
|
|
240
|
+
expect(document.querySelectorAll("link")).toHaveLength(1);
|
|
241
|
+
expect(document.querySelector(ACTIVE_SELECTOR)).toBe(
|
|
242
|
+
secondPendingStylesheet,
|
|
243
|
+
);
|
|
244
|
+
expect(document.querySelector(PENDING_SELECTOR)).toBeNull();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const CURRENT_THEME_STYLESHEET_SELECTOR =
|
|
2
|
-
|
|
2
|
+
"link[data-current-theme-stylesheet]";
|
|
3
3
|
|
|
4
4
|
export function buildCurrentThemeStylesheetHref(
|
|
5
5
|
href: string,
|
|
@@ -9,5 +9,115 @@ export function buildCurrentThemeStylesheetHref(
|
|
|
9
9
|
url.searchParams.set("v", String(version));
|
|
10
10
|
|
|
11
11
|
const isAbsolute = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href);
|
|
12
|
-
return isAbsolute
|
|
12
|
+
return isAbsolute
|
|
13
|
+
? url.toString()
|
|
14
|
+
: `${url.pathname}${url.search}${url.hash}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PENDING_THEME_STYLESHEET_SELECTOR =
|
|
18
|
+
"link[data-pending-current-theme-stylesheet]";
|
|
19
|
+
|
|
20
|
+
let stylesheetSwapToken = 0;
|
|
21
|
+
let stylesheetVersionCounter = 0;
|
|
22
|
+
let cancelPendingThemeStylesheetSwap: (() => void) | null = null;
|
|
23
|
+
|
|
24
|
+
function getAnimationFrame() {
|
|
25
|
+
if (typeof window !== "undefined" && window.requestAnimationFrame) {
|
|
26
|
+
return window.requestAnimationFrame.bind(window);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (callback: FrameRequestCallback) =>
|
|
30
|
+
window.setTimeout(() => callback(performance.now()), 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function refreshCurrentThemeStylesheet() {
|
|
34
|
+
const currentStylesheet = document.querySelector<HTMLLinkElement>(
|
|
35
|
+
"link[data-current-theme-stylesheet]",
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!currentStylesheet) {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cancelPendingThemeStylesheetSwap?.();
|
|
43
|
+
|
|
44
|
+
// Strictly-monotonic version so two refreshes in the same millisecond can
|
|
45
|
+
// never collide on a cached response (which would swap in a stale theme).
|
|
46
|
+
const nextHref = buildCurrentThemeStylesheetHref(
|
|
47
|
+
currentStylesheet.href,
|
|
48
|
+
`${Date.now()}-${++stylesheetVersionCounter}`,
|
|
49
|
+
);
|
|
50
|
+
const nextStylesheet = currentStylesheet.cloneNode(true) as HTMLLinkElement;
|
|
51
|
+
const swapToken = ++stylesheetSwapToken;
|
|
52
|
+
const requestAnimationFrame = getAnimationFrame();
|
|
53
|
+
|
|
54
|
+
nextStylesheet.href = nextHref;
|
|
55
|
+
nextStylesheet.removeAttribute("data-current-theme-stylesheet");
|
|
56
|
+
nextStylesheet.setAttribute("data-pending-current-theme-stylesheet", "");
|
|
57
|
+
|
|
58
|
+
document
|
|
59
|
+
.querySelectorAll<HTMLLinkElement>(PENDING_THEME_STYLESHEET_SELECTOR)
|
|
60
|
+
.forEach((link) => {
|
|
61
|
+
if (link !== nextStylesheet) {
|
|
62
|
+
link.remove();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return new Promise<void>((resolve) => {
|
|
67
|
+
let settled = false;
|
|
68
|
+
|
|
69
|
+
const settle = () => {
|
|
70
|
+
if (settled) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
settled = true;
|
|
75
|
+
if (cancelPendingThemeStylesheetSwap === cancel) {
|
|
76
|
+
cancelPendingThemeStylesheetSwap = null;
|
|
77
|
+
}
|
|
78
|
+
resolve();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const cleanup = () => {
|
|
82
|
+
nextStylesheet.removeEventListener("load", onLoad);
|
|
83
|
+
nextStylesheet.removeEventListener("error", onError);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const cancel = () => {
|
|
87
|
+
cleanup();
|
|
88
|
+
nextStylesheet.remove();
|
|
89
|
+
settle();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onError = () => {
|
|
93
|
+
cancel();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const onLoad = () => {
|
|
97
|
+
cleanup();
|
|
98
|
+
|
|
99
|
+
if (swapToken !== stylesheetSwapToken) {
|
|
100
|
+
nextStylesheet.remove();
|
|
101
|
+
settle();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
currentStylesheet.removeAttribute("data-current-theme-stylesheet");
|
|
106
|
+
nextStylesheet.removeAttribute("data-pending-current-theme-stylesheet");
|
|
107
|
+
nextStylesheet.setAttribute("data-current-theme-stylesheet", "");
|
|
108
|
+
|
|
109
|
+
requestAnimationFrame(() => {
|
|
110
|
+
if (swapToken === stylesheetSwapToken) {
|
|
111
|
+
currentStylesheet.remove();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
settle();
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
cancelPendingThemeStylesheetSwap = cancel;
|
|
119
|
+
nextStylesheet.addEventListener("load", onLoad, { once: true });
|
|
120
|
+
nextStylesheet.addEventListener("error", onError, { once: true });
|
|
121
|
+
currentStylesheet.insertAdjacentElement("afterend", nextStylesheet);
|
|
122
|
+
});
|
|
13
123
|
}
|
|
@@ -28,83 +28,44 @@ const isSplashTemplate = template === "splash";
|
|
|
28
28
|
aria-label={Astro.locals.t("sidebarNav.accessibleLabel")}
|
|
29
29
|
>
|
|
30
30
|
<MobileMenuToggle />
|
|
31
|
-
<
|
|
31
|
+
<script>
|
|
32
|
+
if (!customElements.get("sl-sidebar-pane")) {
|
|
33
|
+
class StarlightSidebarPane extends HTMLElement {
|
|
34
|
+
constructor() {
|
|
35
|
+
super();
|
|
36
|
+
|
|
37
|
+
const desktopMediaQuery = matchMedia("(min-width: 64rem)");
|
|
38
|
+
const updatePopover = () => {
|
|
39
|
+
this.popover = desktopMediaQuery.matches ? null : "auto";
|
|
40
|
+
};
|
|
41
|
+
updatePopover();
|
|
42
|
+
desktopMediaQuery.addEventListener("change", updatePopover);
|
|
43
|
+
|
|
44
|
+
this.addEventListener("toggle", () => {
|
|
45
|
+
const isOpen = this.matches(":popover-open");
|
|
46
|
+
document
|
|
47
|
+
.querySelectorAll<HTMLElement>(".main-frame, .sl-skip-link")
|
|
48
|
+
.forEach((element) => element.toggleAttribute("inert", isOpen));
|
|
49
|
+
|
|
50
|
+
if (isOpen && this.closest(".sidebar-splash")) {
|
|
51
|
+
this.scrollTop = 0;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
customElements.define("sl-sidebar-pane", StarlightSidebarPane);
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
<sl-sidebar-pane popover="" id="starlight__sidebar" class="sidebar-pane">
|
|
32
61
|
<div class="sidebar-content sl-flex">
|
|
33
62
|
<slot name="sidebar" />
|
|
34
63
|
</div>
|
|
35
|
-
</
|
|
64
|
+
</sl-sidebar-pane>
|
|
36
65
|
</nav>
|
|
37
66
|
)
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
{
|
|
41
|
-
isSplashTemplate && (
|
|
42
|
-
<script is:inline>
|
|
43
|
-
(() => {
|
|
44
|
-
const installReset = () => {
|
|
45
|
-
const nav = document.querySelector("nav.sidebar-splash");
|
|
46
|
-
if (!(nav instanceof HTMLElement)) return () => {};
|
|
47
|
-
|
|
48
|
-
const pane = nav.querySelector("#starlight__sidebar");
|
|
49
|
-
const toggle = nav.querySelector("starlight-menu-button");
|
|
50
|
-
|
|
51
|
-
if (!(pane instanceof HTMLElement)) return () => {};
|
|
52
|
-
|
|
53
|
-
const resetToTop = () => {
|
|
54
|
-
pane.scrollTop = 0;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Avoid opening splash menus at a persisted offset.
|
|
58
|
-
resetToTop();
|
|
59
|
-
|
|
60
|
-
if (!(toggle instanceof HTMLElement)) return () => {};
|
|
61
|
-
|
|
62
|
-
const onToggleChange = () => {
|
|
63
|
-
if (toggle.getAttribute("aria-expanded") === "true") {
|
|
64
|
-
requestAnimationFrame(resetToTop);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const onToggleClick = () => {
|
|
69
|
-
requestAnimationFrame(onToggleChange);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
toggle.addEventListener("click", onToggleClick);
|
|
73
|
-
|
|
74
|
-
const observer = new MutationObserver(onToggleChange);
|
|
75
|
-
observer.observe(toggle, {
|
|
76
|
-
attributes: true,
|
|
77
|
-
attributeFilter: ["aria-expanded"],
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return () => {
|
|
81
|
-
toggle.removeEventListener("click", onToggleClick);
|
|
82
|
-
observer.disconnect();
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
let cleanup = installReset();
|
|
87
|
-
|
|
88
|
-
const onAstroPageLoad = () => {
|
|
89
|
-
cleanup();
|
|
90
|
-
cleanup = installReset();
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
document.addEventListener("astro:page-load", onAstroPageLoad);
|
|
94
|
-
|
|
95
|
-
addEventListener(
|
|
96
|
-
"pagehide",
|
|
97
|
-
() => {
|
|
98
|
-
cleanup();
|
|
99
|
-
document.removeEventListener("astro:page-load", onAstroPageLoad);
|
|
100
|
-
},
|
|
101
|
-
{ once: true },
|
|
102
|
-
);
|
|
103
|
-
})();
|
|
104
|
-
</script>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
69
|
<div class:list={["main-frame", isSplashTemplate && "main-frame-splash"]}>
|
|
109
70
|
{
|
|
110
71
|
template === "splash" ? (
|
|
@@ -182,15 +143,17 @@ const isSplashTemplate = template === "splash";
|
|
|
182
143
|
}
|
|
183
144
|
|
|
184
145
|
.sidebar-pane {
|
|
185
|
-
|
|
146
|
+
border: 0;
|
|
147
|
+
margin: 0;
|
|
148
|
+
color: inherit;
|
|
149
|
+
display: none;
|
|
186
150
|
position: fixed;
|
|
187
151
|
z-index: var(--sl-z-index-menu);
|
|
188
152
|
inset-block: var(--sl-nav-height) 0;
|
|
189
153
|
inset-inline-start: 0;
|
|
190
154
|
width: 100%;
|
|
191
155
|
background-color: var(--background);
|
|
192
|
-
height: 100svh;
|
|
193
|
-
overflow: scroll;
|
|
156
|
+
height: calc(100svh - var(--sl-nav-height));
|
|
194
157
|
overflow-y: scroll;
|
|
195
158
|
overflow-x: hidden;
|
|
196
159
|
scrollbar-width: none;
|
|
@@ -198,12 +161,11 @@ const isSplashTemplate = template === "splash";
|
|
|
198
161
|
-webkit-overflow-scrolling: touch;
|
|
199
162
|
overscroll-behavior: contain;
|
|
200
163
|
overscroll-behavior-y: contain;
|
|
201
|
-
|
|
202
|
-
padding-top: calc(var(--spacing) * 18);
|
|
164
|
+
padding: 0;
|
|
203
165
|
}
|
|
204
166
|
|
|
205
|
-
|
|
206
|
-
|
|
167
|
+
.sidebar-pane:popover-open {
|
|
168
|
+
display: block;
|
|
207
169
|
}
|
|
208
170
|
|
|
209
171
|
.sidebar-content {
|
|
@@ -219,11 +181,6 @@ const isSplashTemplate = template === "splash";
|
|
|
219
181
|
content: "";
|
|
220
182
|
padding-bottom: 1px;
|
|
221
183
|
}
|
|
222
|
-
|
|
223
|
-
.sidebar-pane {
|
|
224
|
-
padding-top: 0;
|
|
225
|
-
margin-top: calc(var(--spacing) * 18);
|
|
226
|
-
}
|
|
227
184
|
}
|
|
228
185
|
|
|
229
186
|
.main-frame {
|
|
@@ -315,7 +272,7 @@ const isSplashTemplate = template === "splash";
|
|
|
315
272
|
padding-inline-end: var(--sl-nav-pad-x);
|
|
316
273
|
}
|
|
317
274
|
.sidebar-pane {
|
|
318
|
-
|
|
275
|
+
display: block;
|
|
319
276
|
width: var(--sl-sidebar-width);
|
|
320
277
|
background-color: var(--sl-color-bg-sidebar);
|
|
321
278
|
border-inline-end: 1px solid var(--sl-color-hairline-shade);
|
|
@@ -128,15 +128,11 @@ const themeChoiceBootstrapScript = buildThemeChoiceBootstrapInlineScript();
|
|
|
128
128
|
|
|
129
129
|
<script>
|
|
130
130
|
import { syncSemanticIconsInRoot } from "@bejamas/semantic-icons/browser";
|
|
131
|
-
import {
|
|
132
|
-
buildCurrentThemeStylesheetHref,
|
|
133
|
-
CURRENT_THEME_STYLESHEET_SELECTOR,
|
|
134
|
-
} from "../lib/theme-stylesheet";
|
|
131
|
+
import { refreshCurrentThemeStylesheet } from "../lib/theme-stylesheet";
|
|
135
132
|
|
|
136
133
|
const PRESET_CHANGE_EVENT = "bejamas:preset-change";
|
|
137
134
|
const SETUP_KEY = "__bejamasCurrentThemeStylesheetSync";
|
|
138
135
|
let reloadFrame = 0;
|
|
139
|
-
let reloadVersion = 0;
|
|
140
136
|
let pendingIconLibrary = null;
|
|
141
137
|
|
|
142
138
|
function syncDocumentIconLibrary() {
|
|
@@ -152,68 +148,21 @@ const themeChoiceBootstrapScript = buildThemeChoiceBootstrapInlineScript();
|
|
|
152
148
|
}
|
|
153
149
|
}
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
document.head.querySelectorAll(CURRENT_THEME_STYLESHEET_SELECTOR),
|
|
158
|
-
).filter(
|
|
159
|
-
(link): link is HTMLLinkElement => link instanceof HTMLLinkElement,
|
|
160
|
-
);
|
|
161
|
-
const currentLink = links.at(-1);
|
|
162
|
-
|
|
163
|
-
if (!currentLink) {
|
|
164
|
-
window.StarlightThemeProvider?.refreshThemeColors?.();
|
|
151
|
+
const scheduleCurrentThemeStylesheetRefresh = () => {
|
|
152
|
+
if (reloadFrame) {
|
|
165
153
|
return;
|
|
166
154
|
}
|
|
167
155
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const sourceHref = currentLink.getAttribute("href") ?? currentLink.href;
|
|
172
|
-
replacement.href = buildCurrentThemeStylesheetHref(sourceHref, `${Date.now()}-${reloadVersion}`);
|
|
173
|
-
|
|
174
|
-
replacement.addEventListener(
|
|
175
|
-
"load",
|
|
176
|
-
() => {
|
|
177
|
-
links.forEach((link) => {
|
|
178
|
-
if (link !== replacement) {
|
|
179
|
-
link.remove();
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
if (pendingIconLibrary) {
|
|
183
|
-
document.documentElement.dataset.iconLibrary = pendingIconLibrary;
|
|
184
|
-
syncSemanticIconsInRoot(document, pendingIconLibrary);
|
|
185
|
-
pendingIconLibrary = null;
|
|
186
|
-
}
|
|
187
|
-
window.StarlightThemeProvider?.refreshThemeColors?.();
|
|
188
|
-
},
|
|
189
|
-
{ once: true },
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
replacement.addEventListener(
|
|
193
|
-
"error",
|
|
194
|
-
() => {
|
|
195
|
-
replacement.remove();
|
|
156
|
+
reloadFrame = requestAnimationFrame(() => {
|
|
157
|
+
reloadFrame = 0;
|
|
158
|
+
void refreshCurrentThemeStylesheet().then(() => {
|
|
196
159
|
if (pendingIconLibrary) {
|
|
197
160
|
document.documentElement.dataset.iconLibrary = pendingIconLibrary;
|
|
198
161
|
syncSemanticIconsInRoot(document, pendingIconLibrary);
|
|
199
162
|
pendingIconLibrary = null;
|
|
200
163
|
}
|
|
201
164
|
window.StarlightThemeProvider?.refreshThemeColors?.();
|
|
202
|
-
}
|
|
203
|
-
{ once: true },
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
currentLink.after(replacement);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const scheduleCurrentThemeStylesheetRefresh = () => {
|
|
210
|
-
if (reloadFrame) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
reloadFrame = requestAnimationFrame(() => {
|
|
215
|
-
reloadFrame = 0;
|
|
216
|
-
refreshCurrentThemeStylesheet();
|
|
165
|
+
});
|
|
217
166
|
});
|
|
218
167
|
};
|
|
219
168
|
|
package/src/styles/theme.css
CHANGED
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
/* --sl-nav-height: auto; */
|
|
90
90
|
--sl-nav-pad-y: calc(var(--spacing) * 4.5);
|
|
91
91
|
--sl-content-pad-x: calc(var(--spacing) * 5);
|
|
92
|
+
--sl-docs-top-space: calc(var(--spacing) * 12);
|
|
92
93
|
--sl-nav-pad-x: calc(var(--spacing) * 1 + var(--sl-content-pad-x));
|
|
93
94
|
--sl-main-frame-inner-background: var(--background);
|
|
94
95
|
--sl-main-frame-inner-border: var(--border);
|
|
@@ -150,7 +151,7 @@
|
|
|
150
151
|
|
|
151
152
|
.right-sidebar-panel {
|
|
152
153
|
--sl-sidebar-item-padding-inline: 1rem;
|
|
153
|
-
padding-top:
|
|
154
|
+
padding-top: var(--sl-docs-top-space);
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
.right-sidebar-panel a {
|
|
@@ -195,6 +196,14 @@
|
|
|
195
196
|
font-size: 1rem;
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
.sidebar-content details > summary .caret {
|
|
200
|
+
transform: rotate(90deg);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.sidebar-content details[open] > summary .caret {
|
|
204
|
+
transform: rotate(-90deg);
|
|
205
|
+
}
|
|
206
|
+
|
|
198
207
|
.sidebar-content a > span {
|
|
199
208
|
color: var(--foreground);
|
|
200
209
|
padding-inline: 0.5rem;
|
|
@@ -224,6 +233,16 @@
|
|
|
224
233
|
margin-bottom: 1px;
|
|
225
234
|
}
|
|
226
235
|
|
|
236
|
+
@media (min-width: 64rem) {
|
|
237
|
+
.sidebar-pane .sidebar-content {
|
|
238
|
+
padding-top: var(--sl-docs-top-space);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.sidebar-content::before {
|
|
242
|
+
display: none;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
227
246
|
@media (max-width: 49.99rem) {
|
|
228
247
|
.sidebar-pane .sidebar-content {
|
|
229
248
|
--sl-sidebar-item-padding-inline: 0.5rem;
|
|
@@ -271,7 +290,7 @@
|
|
|
271
290
|
|
|
272
291
|
@media (min-width: 50rem) {
|
|
273
292
|
.content-panel:first-child {
|
|
274
|
-
padding-top:
|
|
293
|
+
padding-top: var(--sl-docs-top-space);
|
|
275
294
|
}
|
|
276
295
|
}
|
|
277
296
|
|
|
@@ -453,6 +472,19 @@
|
|
|
453
472
|
z-index: 10;
|
|
454
473
|
}
|
|
455
474
|
|
|
475
|
+
/* Draw the border on the wrapper so the collapse overlay and expanded
|
|
476
|
+
scrolling don't cover or scroll away the pre's own border. */
|
|
477
|
+
.expressive-code:has(> .ec-collapse) {
|
|
478
|
+
border: 1px solid var(--border);
|
|
479
|
+
border-radius: var(--radius-lg);
|
|
480
|
+
overflow: hidden;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.expressive-code:has(> .ec-collapse) pre {
|
|
484
|
+
border: 0;
|
|
485
|
+
border-radius: 0;
|
|
486
|
+
}
|
|
487
|
+
|
|
456
488
|
.sl-bejamas-component-preview + .expressive-code {
|
|
457
489
|
margin-top: 0;
|
|
458
490
|
}
|
|
@@ -704,7 +736,7 @@
|
|
|
704
736
|
max-width: calc(var(--spacing) * 150);
|
|
705
737
|
}
|
|
706
738
|
|
|
707
|
-
|
|
739
|
+
.sl-menu-button {
|
|
708
740
|
box-shadow: none;
|
|
709
741
|
padding: 0;
|
|
710
742
|
width: calc(var(--spacing) * 6);
|
|
@@ -712,18 +744,18 @@
|
|
|
712
744
|
color: var(--foreground);
|
|
713
745
|
}
|
|
714
746
|
|
|
715
|
-
|
|
747
|
+
.sl-menu-button svg {
|
|
716
748
|
width: calc(var(--spacing) * 8);
|
|
717
749
|
height: calc(var(--spacing) * 8);
|
|
718
750
|
font-size: calc(var(--spacing) * 8);
|
|
719
751
|
}
|
|
720
752
|
|
|
721
753
|
@media (min-width: 50rem) and (max-width: calc(64rem - 0.01rem)) {
|
|
722
|
-
|
|
754
|
+
.sl-menu-button {
|
|
723
755
|
display: flex;
|
|
724
756
|
}
|
|
725
757
|
|
|
726
|
-
|
|
758
|
+
body:has(#starlight__sidebar:popover-open) {
|
|
727
759
|
overflow: hidden;
|
|
728
760
|
}
|
|
729
761
|
}
|