@orbytes/astrolab 0.3.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/LICENSE +37 -0
- package/README.md +410 -0
- package/bin/lab-cull.mjs +401 -0
- package/bin/pin-gallery.mjs +121 -0
- package/defaults.mjs +120 -0
- package/dist/core/astro-integration.js +130 -0
- package/dist/core/index.js +6 -0
- package/dist/core/lib-paths.js +29 -0
- package/dist/core/options.js +173 -0
- package/dist/core/utils/get-exports.js +52 -0
- package/dist/core/utils/invariant.js +12 -0
- package/dist/core/utils/kebab-case.js +15 -0
- package/dist/core/utils/path-builder.js +22 -0
- package/dist/core/utils/path.js +53 -0
- package/dist/core/virtual-module/get-story-modules.js +60 -0
- package/dist/core/virtual-module/story-modules.js +8 -0
- package/dist/core/virtual-module/virtual-module-ids.js +22 -0
- package/dist/core/virtual-module/virtual-routes.js +83 -0
- package/dist/core/virtual-module/vite-plugin.js +98 -0
- package/docs/PIN-CONTRACT.md +263 -0
- package/docs/PIN.md +429 -0
- package/index.d.ts +279 -0
- package/index.mjs +347 -0
- package/package.json +93 -0
- package/src/Empty.astro +4 -0
- package/src/Home.astro +298 -0
- package/src/LabHead.astro +1102 -0
- package/src/core/LICENSE-astrobook +166 -0
- package/src/core/astro-integration.ts +166 -0
- package/src/core/client.ts +89 -0
- package/src/core/index.ts +7 -0
- package/src/core/lib/components/empty.astro +1 -0
- package/src/core/lib/components/head.astro +1 -0
- package/src/core/lib/components/home.astro +8 -0
- package/src/core/lib/components/with-decorators.astro +22 -0
- package/src/core/lib/pages/app.astro +19 -0
- package/src/core/lib/pages/preview.astro +17 -0
- package/src/core/lib/pages/story.astro +16 -0
- package/src/core/lib-paths.ts +72 -0
- package/src/core/options.ts +262 -0
- package/src/core/utils/get-exports.ts +59 -0
- package/src/core/utils/invariant.ts +13 -0
- package/src/core/utils/kebab-case.ts +30 -0
- package/src/core/utils/path-builder.ts +45 -0
- package/src/core/utils/path.ts +80 -0
- package/src/core/virtual-module/get-story-modules.ts +110 -0
- package/src/core/virtual-module/story-modules.ts +9 -0
- package/src/core/virtual-module/virtual-module-ids.ts +17 -0
- package/src/core/virtual-module/virtual-routes.ts +130 -0
- package/src/core/virtual-module/vite-plugin.ts +125 -0
- package/src/pin/board.mjs +1521 -0
- package/src/pin/index.mjs +666 -0
- package/src/pin/shot.mjs +427 -0
- package/src/pin/source-stamp.mjs +159 -0
- package/src/pin/tickets.mjs +697 -0
- package/src/pin/toolbar.js +3181 -0
- package/src/shell/Browse.astro +371 -0
- package/src/shell/CardGrid.astro +297 -0
- package/src/shell/Viewport.astro +1330 -0
- package/src/shell/index.json.ts +12 -0
- package/src/shell/lab-index.ts +344 -0
- package/src/shell/lab-params.ts +245 -0
- package/src/shell/live-files.mjs +164 -0
- package/src/shell/marks.mjs +136 -0
- package/src/types/index.ts +6 -0
- package/src/types/types.ts +239 -0
- package/src/types/virtual.d.ts +29 -0
- package/src/ui/components/app.astro +13 -0
- package/src/ui/components/build-path.ts +13 -0
- package/src/ui/components/build-tree.ts +108 -0
- package/src/ui/components/collapse-duration.ts +28 -0
- package/src/ui/components/compress-terms.ts +10 -0
- package/src/ui/components/dashboard-layout.astro +39 -0
- package/src/ui/components/home.astro +65 -0
- package/src/ui/components/layout.astro +110 -0
- package/src/ui/components/preview-layout.astro +109 -0
- package/src/ui/components/sidebar-button-fullscreen.astro +38 -0
- package/src/ui/components/sidebar-button-search.astro +23 -0
- package/src/ui/components/sidebar-button-theme.astro +9 -0
- package/src/ui/components/sidebar-button.astro +24 -0
- package/src/ui/components/sidebar-resize-handle.astro +74 -0
- package/src/ui/components/sidebar-search-panel.astro +41 -0
- package/src/ui/components/sidebar-search-script.ts +103 -0
- package/src/ui/components/sidebar-title.astro +17 -0
- package/src/ui/components/sidebar-tree-node.astro +143 -0
- package/src/ui/components/sidebar-tree.astro +84 -0
- package/src/ui/components/sidebar.astro +29 -0
- package/src/ui/components/theme-message.ts +26 -0
- package/src/ui/components/theme-script.astro +71 -0
- package/src/ui/components/theme-toggle.astro +63 -0
- package/src/ui/components/theme.ts +32 -0
- package/src/ui/index.ts +4 -0
- package/src/ui/lab.css +549 -0
- package/virtual.d.ts +42 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-search-panel.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility strings and the scoped <style> block are rules in ../lab.css, and
|
|
5
|
+
// the search icon is an inline SVG. The ids are DOM contracts — LabHead.astro and
|
|
6
|
+
// ./sidebar-search-script.ts both bind to them.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div id="astrobook-search-panel">
|
|
10
|
+
<div class="lab-search__row">
|
|
11
|
+
<div class="lab-search__field">
|
|
12
|
+
<svg
|
|
13
|
+
class="lab-search__icon"
|
|
14
|
+
viewBox="0 0 16 16"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
stroke-width="1.5"
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
>
|
|
21
|
+
<circle cx="7" cy="7" r="4.5"></circle>
|
|
22
|
+
<path d="m10.5 10.5 3 3"></path>
|
|
23
|
+
</svg>
|
|
24
|
+
<input
|
|
25
|
+
id="astrobook-search-input"
|
|
26
|
+
type="text"
|
|
27
|
+
placeholder="Search..."
|
|
28
|
+
aria-label="Search stories"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<script>
|
|
35
|
+
import { initSearch } from './sidebar-search-script.ts'
|
|
36
|
+
|
|
37
|
+
initSearch(document)
|
|
38
|
+
document.addEventListener('astro:before-swap', (event) => {
|
|
39
|
+
initSearch((event as unknown as { newDocument: Document }).newDocument)
|
|
40
|
+
})
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-search-script.ts) — see
|
|
2
|
+
// ../../core/LICENSE-astrobook.
|
|
3
|
+
// Divergence: the relative import carries the .ts extension.
|
|
4
|
+
//
|
|
5
|
+
// Every id below is a DOM contract shared with LabHead.astro, which opens this panel on ⌘K and
|
|
6
|
+
// widens a match by appending lower-cased terms to `data-search-text`. Do not rename them.
|
|
7
|
+
import { compressTerms } from './compress-terms.ts'
|
|
8
|
+
|
|
9
|
+
const TREE_ID = 'astrobook-sidebar-tree'
|
|
10
|
+
const SEARCH_INPUT_ID = 'astrobook-search-input'
|
|
11
|
+
const SEARCH_TOGGLE_ID = 'astrobook-search-toggle'
|
|
12
|
+
const SEARCH_PANEL_ID = 'astrobook-search-panel'
|
|
13
|
+
const STYLE_ID = 'astrobook-search-style'
|
|
14
|
+
const OPEN_KEY = 'astrobook-sidebar-search-open'
|
|
15
|
+
const QUERY_KEY = 'astrobook-sidebar-search-query'
|
|
16
|
+
|
|
17
|
+
function getStyleElement(doc: Document): HTMLStyleElement {
|
|
18
|
+
let styleEl = doc.getElementById(STYLE_ID) as HTMLStyleElement | null
|
|
19
|
+
if (!styleEl) {
|
|
20
|
+
styleEl = doc.createElement('style')
|
|
21
|
+
styleEl.id = STYLE_ID
|
|
22
|
+
doc.head.appendChild(styleEl)
|
|
23
|
+
}
|
|
24
|
+
return styleEl
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function updateQueryStyle(doc: Document, query: string): void {
|
|
28
|
+
const trimmed = query.trim()
|
|
29
|
+
const el = getStyleElement(doc)
|
|
30
|
+
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
el.textContent = ''
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const terms = compressTerms(trimmed.split(/[\s\p{P}]+/u))
|
|
37
|
+
const rules = terms.map((term) => {
|
|
38
|
+
return `#${TREE_ID} [data-search-text]:not([data-search-text*="${CSS.escape(term)}"]) { display: none !important; }`
|
|
39
|
+
})
|
|
40
|
+
el.textContent = rules.join('\n')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function saveQuery(query: string): void {
|
|
44
|
+
if (query) {
|
|
45
|
+
sessionStorage.setItem(QUERY_KEY, query)
|
|
46
|
+
} else {
|
|
47
|
+
sessionStorage.removeItem(QUERY_KEY)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function initSearch(doc: Document): void {
|
|
52
|
+
const searchInputElement = doc.getElementById(
|
|
53
|
+
SEARCH_INPUT_ID,
|
|
54
|
+
) as HTMLInputElement | null
|
|
55
|
+
const searchToggleElement = doc.getElementById(SEARCH_TOGGLE_ID)
|
|
56
|
+
const searchPanelElement = doc.getElementById(SEARCH_PANEL_ID)
|
|
57
|
+
if (!searchInputElement || !searchToggleElement || !searchPanelElement) return
|
|
58
|
+
|
|
59
|
+
const searchInput = searchInputElement
|
|
60
|
+
const searchToggle = searchToggleElement
|
|
61
|
+
const searchPanel = searchPanelElement
|
|
62
|
+
|
|
63
|
+
function openPanel(): void {
|
|
64
|
+
searchPanel.setAttribute('data-open', '')
|
|
65
|
+
searchToggle.setAttribute('data-active', '')
|
|
66
|
+
sessionStorage.setItem(OPEN_KEY, '1')
|
|
67
|
+
requestAnimationFrame(() => searchInput.focus())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function closePanel(): void {
|
|
71
|
+
searchInput.value = ''
|
|
72
|
+
saveQuery('')
|
|
73
|
+
updateQueryStyle(doc, '')
|
|
74
|
+
searchPanel.removeAttribute('data-open')
|
|
75
|
+
searchToggle.removeAttribute('data-active')
|
|
76
|
+
sessionStorage.removeItem(OPEN_KEY)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function handleInput(): void {
|
|
80
|
+
saveQuery(searchInput.value)
|
|
81
|
+
updateQueryStyle(document, searchInput.value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
searchToggle.addEventListener('click', () => {
|
|
85
|
+
if (searchPanel.hasAttribute('data-open')) {
|
|
86
|
+
closePanel()
|
|
87
|
+
} else {
|
|
88
|
+
openPanel()
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
searchInput.addEventListener('input', handleInput)
|
|
93
|
+
|
|
94
|
+
// Restore state from sessionStorage after a page navigation
|
|
95
|
+
const isOpen = sessionStorage.getItem(OPEN_KEY) === '1'
|
|
96
|
+
if (isOpen) {
|
|
97
|
+
const query = sessionStorage.getItem(QUERY_KEY) || ''
|
|
98
|
+
searchPanel.setAttribute('data-open', '')
|
|
99
|
+
searchToggle.setAttribute('data-active', '')
|
|
100
|
+
searchInput.value = query
|
|
101
|
+
updateQueryStyle(doc, query)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-title.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility string is the `.lab-sidebar__title` class in ../lab.css, and the
|
|
5
|
+
// `import '@astrobook/types'` side-effect line is gone (ambient declarations now live in
|
|
6
|
+
// ../../types/virtual.d.ts).
|
|
7
|
+
import config from 'virtual:astrobook/global-config.mjs'
|
|
8
|
+
|
|
9
|
+
import { buildPath } from './build-path.ts'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<a
|
|
13
|
+
class:list={['lab-sidebar__title', 'lab-focus-ring']}
|
|
14
|
+
href={buildPath('/', config.astrobookBase)}
|
|
15
|
+
>
|
|
16
|
+
{config.title}
|
|
17
|
+
</a>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-tree-node.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility strings and shortcuts are real classes in ../lab.css, the icons
|
|
5
|
+
// are inline SVGs, and the scoped <style> block moved there with them.
|
|
6
|
+
//
|
|
7
|
+
// EVERY ATTRIBUTE AND STATE CLASS BELOW IS A DOM CONTRACT, shared with LabHead.astro's sidebar
|
|
8
|
+
// decorator and with ./sidebar-search-script.ts — `data-astrobook-collapsible`,
|
|
9
|
+
// `data-id="dir:…"` / `"module:…"`, `data-search-text`, `data-astrobook-story-link`,
|
|
10
|
+
// `data-story-id`, `data-astrobook-chevron`, and the two
|
|
11
|
+
// `astrobook-sidebar-story-link-active|inactive` classes. Do not rename any of them.
|
|
12
|
+
import config from 'virtual:astrobook/global-config.mjs'
|
|
13
|
+
|
|
14
|
+
import type { TreeNode } from './build-tree.ts'
|
|
15
|
+
import { buildPath } from './build-path.ts'
|
|
16
|
+
import { getCollapseDuration } from './collapse-duration.ts'
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
node: TreeNode
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { node } = Astro.props
|
|
23
|
+
|
|
24
|
+
// The inline script in sidebar-tree.astro sets the real active state on the client; the initial
|
|
25
|
+
// state here is always inactive.
|
|
26
|
+
const active = false
|
|
27
|
+
|
|
28
|
+
const collapseDuration = getCollapseDuration(node)
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
node.type === 'directory' && (
|
|
33
|
+
<astrobook-sidebar-node>
|
|
34
|
+
<details
|
|
35
|
+
open
|
|
36
|
+
data-astrobook-collapsible
|
|
37
|
+
data-id={`dir:${node.name}`}
|
|
38
|
+
data-search-text={node.searchText}
|
|
39
|
+
style={`--collapse-duration: ${collapseDuration}ms`}
|
|
40
|
+
>
|
|
41
|
+
<summary
|
|
42
|
+
class:list={[
|
|
43
|
+
'astrobook-sidebar-link',
|
|
44
|
+
'lab-tree-row--group',
|
|
45
|
+
'lab-focus-ring',
|
|
46
|
+
]}
|
|
47
|
+
>
|
|
48
|
+
<svg
|
|
49
|
+
data-astrobook-chevron
|
|
50
|
+
class="lab-tree-icon"
|
|
51
|
+
viewBox="0 0 16 16"
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
stroke-width="1.5"
|
|
55
|
+
stroke-linecap="round"
|
|
56
|
+
stroke-linejoin="round"
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
>
|
|
59
|
+
<path d="m4 6 4 4 4-4" />
|
|
60
|
+
</svg>
|
|
61
|
+
<span>{node.name}</span>
|
|
62
|
+
</summary>
|
|
63
|
+
<div class="lab-tree-children">
|
|
64
|
+
{node.children.map((child) => (
|
|
65
|
+
<Astro.self node={child} />
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
</details>
|
|
69
|
+
</astrobook-sidebar-node>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
node.type === 'module' && (
|
|
75
|
+
<astrobook-sidebar-node>
|
|
76
|
+
<details
|
|
77
|
+
open
|
|
78
|
+
data-astrobook-collapsible
|
|
79
|
+
data-id={`module:${node.id}`}
|
|
80
|
+
data-search-text={node.searchText}
|
|
81
|
+
style={`--collapse-duration: ${collapseDuration}ms`}
|
|
82
|
+
>
|
|
83
|
+
<summary
|
|
84
|
+
class:list={[
|
|
85
|
+
'astrobook-sidebar-link',
|
|
86
|
+
'lab-tree-row--group',
|
|
87
|
+
'lab-focus-ring',
|
|
88
|
+
]}
|
|
89
|
+
>
|
|
90
|
+
<svg
|
|
91
|
+
class="lab-tree-icon"
|
|
92
|
+
viewBox="0 0 16 16"
|
|
93
|
+
fill="none"
|
|
94
|
+
stroke="currentColor"
|
|
95
|
+
stroke-width="1.5"
|
|
96
|
+
stroke-linejoin="round"
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
>
|
|
99
|
+
<rect x="2.5" y="2.5" width="11" height="11" rx="1.5" />
|
|
100
|
+
<path d="M2.5 6.5h11" />
|
|
101
|
+
</svg>
|
|
102
|
+
<span>{node.name}</span>
|
|
103
|
+
</summary>
|
|
104
|
+
<div class="lab-tree-children">
|
|
105
|
+
{node.children.map((child) => (
|
|
106
|
+
<Astro.self node={child} />
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</details>
|
|
110
|
+
</astrobook-sidebar-node>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
{
|
|
115
|
+
node.type === 'story' && (
|
|
116
|
+
<a
|
|
117
|
+
href={buildPath(config.dashboardBase, node.id)}
|
|
118
|
+
data-astrobook-story-link
|
|
119
|
+
data-story-id={node.id}
|
|
120
|
+
data-search-text={node.searchText}
|
|
121
|
+
class:list={[
|
|
122
|
+
'astrobook-sidebar-link',
|
|
123
|
+
'lab-focus-ring',
|
|
124
|
+
active
|
|
125
|
+
? 'astrobook-sidebar-story-link-active'
|
|
126
|
+
: 'astrobook-sidebar-story-link-inactive',
|
|
127
|
+
]}
|
|
128
|
+
>
|
|
129
|
+
<svg
|
|
130
|
+
class="lab-tree-icon"
|
|
131
|
+
viewBox="0 0 16 16"
|
|
132
|
+
fill="none"
|
|
133
|
+
stroke="currentColor"
|
|
134
|
+
stroke-width="1.5"
|
|
135
|
+
stroke-linejoin="round"
|
|
136
|
+
aria-hidden="true"
|
|
137
|
+
>
|
|
138
|
+
<path d="M8 2.5 13.5 8 8 13.5 2.5 8z" />
|
|
139
|
+
</svg>
|
|
140
|
+
<span>{node.name}</span>
|
|
141
|
+
</a>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-tree.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility string is a rule on the `astrobook-sidebar-tree` element
|
|
5
|
+
// selector in ../lab.css, and the two `customElements.define` calls are guarded.
|
|
6
|
+
//
|
|
7
|
+
// `id="astrobook-sidebar-tree"` and `transition:persist` are DOM contracts: LabHead.astro hangs
|
|
8
|
+
// its pills, marks and j/k navigation on this element and relies on it surviving a view
|
|
9
|
+
// transition.
|
|
10
|
+
import modules from 'virtual:astrobook/story-modules.mjs'
|
|
11
|
+
|
|
12
|
+
import { buildTree } from './build-tree.ts'
|
|
13
|
+
import SidebarTreeNode from './sidebar-tree-node.astro'
|
|
14
|
+
|
|
15
|
+
const tree = buildTree(modules)
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<astrobook-sidebar-tree
|
|
19
|
+
id="astrobook-sidebar-tree"
|
|
20
|
+
transition:persist="astrobook-sidebar-tree"
|
|
21
|
+
>
|
|
22
|
+
{tree.map((node) => <SidebarTreeNode node={node} />)}
|
|
23
|
+
</astrobook-sidebar-tree>
|
|
24
|
+
|
|
25
|
+
<script is:inline>
|
|
26
|
+
{
|
|
27
|
+
class AstrobookSidebarTree extends HTMLElement {
|
|
28
|
+
connectedCallback() {
|
|
29
|
+
const activeStory = document.documentElement.dataset.activeStory || ''
|
|
30
|
+
this.querySelectorAll(
|
|
31
|
+
'a[data-astrobook-story-link][data-story-id]',
|
|
32
|
+
).forEach((link) => {
|
|
33
|
+
const active = link.dataset.storyId === activeStory
|
|
34
|
+
link.classList.toggle('astrobook-sidebar-story-link-active', active)
|
|
35
|
+
link.classList.toggle(
|
|
36
|
+
'astrobook-sidebar-story-link-inactive',
|
|
37
|
+
!active,
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const SCROLL_KEY = 'astrobook-sidebar-scroll'
|
|
42
|
+
const scrollPosition = parseFloat(
|
|
43
|
+
sessionStorage.getItem(SCROLL_KEY) || '',
|
|
44
|
+
)
|
|
45
|
+
if (scrollPosition) {
|
|
46
|
+
this.scrollTop = scrollPosition
|
|
47
|
+
}
|
|
48
|
+
this.onscrollend = () => {
|
|
49
|
+
sessionStorage.setItem(SCROLL_KEY, String(this.scrollTop))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!customElements.get('astrobook-sidebar-tree')) {
|
|
54
|
+
customElements.define('astrobook-sidebar-tree', AstrobookSidebarTree)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
{
|
|
58
|
+
class AstrobookSidebarNode extends HTMLElement {
|
|
59
|
+
connectedCallback() {
|
|
60
|
+
const details = this.querySelector('details[data-id]')
|
|
61
|
+
if (!details) return
|
|
62
|
+
const id = details.dataset.id
|
|
63
|
+
if (!id) return
|
|
64
|
+
const key = 'astrobook-sidebar-collapse-' + id
|
|
65
|
+
const collapsed = localStorage.getItem(key) === '1'
|
|
66
|
+
|
|
67
|
+
if (collapsed) {
|
|
68
|
+
details.removeAttribute('open')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
details.addEventListener('toggle', () => {
|
|
72
|
+
if (details.open) {
|
|
73
|
+
localStorage.removeItem(key)
|
|
74
|
+
} else {
|
|
75
|
+
localStorage.setItem(key, '1')
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!customElements.get('astrobook-sidebar-node')) {
|
|
81
|
+
customElements.define('astrobook-sidebar-node', AstrobookSidebarNode)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar.astro) — see
|
|
3
|
+
// ../../core/LICENSE-astrobook.
|
|
4
|
+
// Divergence: the UnoCSS utility string is `.lab-sidebar__nav` in ../lab.css. The header row's
|
|
5
|
+
// shape matters: LabHead.astro appends the collapse and cull buttons to `#astrobook-sidebar > nav
|
|
6
|
+
// > div`, so the header stays the FIRST child <div> of this <nav>.
|
|
7
|
+
import SidebarButtonFullscreen from './sidebar-button-fullscreen.astro'
|
|
8
|
+
import SidebarButtonSearch from './sidebar-button-search.astro'
|
|
9
|
+
import SidebarButtonTheme from './sidebar-button-theme.astro'
|
|
10
|
+
import SidebarSearchPanel from './sidebar-search-panel.astro'
|
|
11
|
+
import SidebarTree from './sidebar-tree.astro'
|
|
12
|
+
import SidebarTitle from './sidebar-title.astro'
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
story?: string
|
|
16
|
+
}
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<nav class="lab-sidebar__nav">
|
|
20
|
+
<div class="lab-sidebar__header">
|
|
21
|
+
<SidebarTitle />
|
|
22
|
+
<span class="lab-sidebar__spacer"></span>
|
|
23
|
+
<SidebarButtonFullscreen story={Astro.props.story} />
|
|
24
|
+
<SidebarButtonSearch />
|
|
25
|
+
<SidebarButtonTheme />
|
|
26
|
+
</div>
|
|
27
|
+
<SidebarSearchPanel />
|
|
28
|
+
<SidebarTree />
|
|
29
|
+
</nav>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/components/theme-message.ts) — see
|
|
2
|
+
// ../../core/LICENSE-astrobook.
|
|
3
|
+
// Divergence: `setTheme` comes from the vendored ./theme.ts rather than `astro-theme-toggle`.
|
|
4
|
+
//
|
|
5
|
+
// The protocol is unchanged and still `astrobook:set-theme`: a page that embeds a story in an
|
|
6
|
+
// iframe (the lab's own thumbnail cards, the viewport configurator) can push its theme into the
|
|
7
|
+
// frame with postMessage.
|
|
8
|
+
import { setTheme } from './theme.ts'
|
|
9
|
+
|
|
10
|
+
export function initThemeMessage(): void {
|
|
11
|
+
if (typeof window === 'undefined') return
|
|
12
|
+
|
|
13
|
+
window.addEventListener('message', (event) => {
|
|
14
|
+
try {
|
|
15
|
+
const { type, theme } = event.data as { type: string; theme: string }
|
|
16
|
+
if (
|
|
17
|
+
type === 'astrobook:set-theme' &&
|
|
18
|
+
(theme === 'dark' || theme === 'light')
|
|
19
|
+
) {
|
|
20
|
+
setTheme(theme)
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// ignore
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The theme, set before paint. Written for this package; it replaces `astro-theme-toggle`.
|
|
3
|
+
//
|
|
4
|
+
// WHY THE DEPENDENCY WENT (2026-09-22). Upstream's UI took the toggle from `astro-theme-toggle`,
|
|
5
|
+
// which ships source and would have kept working. Three reasons it did not survive the vendoring:
|
|
6
|
+
//
|
|
7
|
+
// 1. The whole point of the pass is that the lab depends on nothing but Astro. One npm package
|
|
8
|
+
// left behind for a sun/moon button is the one that would surprise the next reader.
|
|
9
|
+
// 2. `.dark` on <html> is a DOM contract this package leans on hard — LabHead.astro styles the
|
|
10
|
+
// live pill, the cull switch and the row error against it. A contract that load-bearing
|
|
11
|
+
// should be owned here, not read off a third party's minified inline script.
|
|
12
|
+
// 3. What was left after dropping the ripple `startViewTransition` animation — presentation,
|
|
13
|
+
// and phase 2's business if it wants it back — was about forty lines.
|
|
14
|
+
//
|
|
15
|
+
// Everything observable is unchanged: the same `theme-toggle` localStorage key, the same
|
|
16
|
+
// `.dark` class, `data-theme` and `color-scheme` on <html>, the same re-apply on
|
|
17
|
+
// `astro:after-swap`. An existing reader's stored preference carries over untouched.
|
|
18
|
+
//
|
|
19
|
+
// `is:inline` and no bundling on purpose: this must run before the first paint, or the lab
|
|
20
|
+
// flashes light before turning dark.
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<script is:inline>
|
|
24
|
+
(() => {
|
|
25
|
+
const STORAGE_KEY = "theme-toggle";
|
|
26
|
+
let current;
|
|
27
|
+
|
|
28
|
+
const systemTheme = () =>
|
|
29
|
+
window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
30
|
+
|
|
31
|
+
const storedTheme = () => {
|
|
32
|
+
try {
|
|
33
|
+
const theme = localStorage.getItem(STORAGE_KEY);
|
|
34
|
+
return theme === "dark" || theme === "light" ? theme : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getTheme = () => current || storedTheme() || systemTheme();
|
|
41
|
+
|
|
42
|
+
const store = (theme) => {
|
|
43
|
+
try {
|
|
44
|
+
if (theme === systemTheme()) localStorage.removeItem(STORAGE_KEY);
|
|
45
|
+
else localStorage.setItem(STORAGE_KEY, theme);
|
|
46
|
+
} catch {
|
|
47
|
+
/* private mode — the theme still applies, it just does not persist */
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const paint = (theme) => {
|
|
52
|
+
const root = document.documentElement;
|
|
53
|
+
root.classList.toggle("dark", theme === "dark");
|
|
54
|
+
root.dataset.theme = theme;
|
|
55
|
+
root.style.colorScheme = theme;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const setTheme = (theme) => {
|
|
59
|
+
current = theme;
|
|
60
|
+
store(theme);
|
|
61
|
+
paint(theme);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const apply = () => setTheme(getTheme());
|
|
65
|
+
|
|
66
|
+
apply();
|
|
67
|
+
document.addEventListener("astro:after-swap", apply);
|
|
68
|
+
|
|
69
|
+
window.labTheme = { setTheme, getTheme };
|
|
70
|
+
})();
|
|
71
|
+
</script>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The sun/moon button. Written for this package; replaces `astro-theme-toggle`'s Toggle (see
|
|
3
|
+
// ./theme-script.astro for why the dependency went).
|
|
4
|
+
//
|
|
5
|
+
// Divergence from what it replaces: no ripple `startViewTransition` animation. That is
|
|
6
|
+
// presentation, and this pass is function; phase 2 can put it back.
|
|
7
|
+
interface Props {
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { class: className } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<lab-theme-toggle class:list={["lab-theme-toggle", className]} title="Toggle theme">
|
|
15
|
+
<span class="lab-sr-only">Toggle theme</span>
|
|
16
|
+
<svg
|
|
17
|
+
class="lab-theme-icon--light"
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
fill="none"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
stroke-width="2"
|
|
22
|
+
stroke-linecap="round"
|
|
23
|
+
stroke-linejoin="round"
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
>
|
|
26
|
+
<circle cx="12" cy="12" r="4"></circle>
|
|
27
|
+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path>
|
|
28
|
+
</svg>
|
|
29
|
+
<svg
|
|
30
|
+
class="lab-theme-icon--dark"
|
|
31
|
+
viewBox="0 0 24 24"
|
|
32
|
+
fill="none"
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
stroke-width="2"
|
|
35
|
+
stroke-linecap="round"
|
|
36
|
+
stroke-linejoin="round"
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
>
|
|
39
|
+
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path>
|
|
40
|
+
</svg>
|
|
41
|
+
</lab-theme-toggle>
|
|
42
|
+
|
|
43
|
+
<script>
|
|
44
|
+
import { toggleTheme } from "./theme.ts";
|
|
45
|
+
|
|
46
|
+
class LabThemeToggle extends HTMLElement {
|
|
47
|
+
connectedCallback() {
|
|
48
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
|
49
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "button");
|
|
50
|
+
this.addEventListener("click", () => toggleTheme());
|
|
51
|
+
this.addEventListener("keydown", (event) => {
|
|
52
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
toggleTheme();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!customElements.get("lab-theme-toggle")) {
|
|
61
|
+
customElements.define("lab-theme-toggle", LabThemeToggle);
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The theme API, written for this package — it replaces the `astro-theme-toggle` dependency
|
|
2
|
+
// (see ./theme-script.astro for the decision).
|
|
3
|
+
//
|
|
4
|
+
// The inline script in ./theme-script.astro owns the state and publishes this window API before
|
|
5
|
+
// paint; everything else in the lab goes through these two functions. The key, the `.dark` class,
|
|
6
|
+
// `data-theme` and `color-scheme` are all kept exactly as `astro-theme-toggle@0.8` set them, so a
|
|
7
|
+
// reader who already has a preference stored keeps it, and `.dark` — which LabHead.astro's own
|
|
8
|
+
// styles key off — means what it has always meant.
|
|
9
|
+
export type Theme = 'light' | 'dark'
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface Window {
|
|
13
|
+
labTheme?: {
|
|
14
|
+
setTheme: (theme: Theme) => void
|
|
15
|
+
getTheme: () => Theme
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTheme(): Theme {
|
|
21
|
+
if (typeof window === 'undefined') return 'light'
|
|
22
|
+
return window.labTheme?.getTheme() ?? 'light'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function setTheme(theme: Theme): void {
|
|
26
|
+
if (typeof window === 'undefined') return
|
|
27
|
+
window.labTheme?.setTheme(theme)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toggleTheme(): void {
|
|
31
|
+
setTheme(getTheme() === 'light' ? 'dark' : 'light')
|
|
32
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Vendored from astrobook 0.13.3 (packages/ui/src/index.ts) — see ../core/LICENSE-astrobook.
|
|
2
|
+
// Upstream's UI package has no public API: everything is reached by component path. Kept so the
|
|
3
|
+
// mirrored layout still lines up with a future `diff -r` against an upstream tag.
|
|
4
|
+
export {}
|