@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.
Files changed (94) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +410 -0
  3. package/bin/lab-cull.mjs +401 -0
  4. package/bin/pin-gallery.mjs +121 -0
  5. package/defaults.mjs +120 -0
  6. package/dist/core/astro-integration.js +130 -0
  7. package/dist/core/index.js +6 -0
  8. package/dist/core/lib-paths.js +29 -0
  9. package/dist/core/options.js +173 -0
  10. package/dist/core/utils/get-exports.js +52 -0
  11. package/dist/core/utils/invariant.js +12 -0
  12. package/dist/core/utils/kebab-case.js +15 -0
  13. package/dist/core/utils/path-builder.js +22 -0
  14. package/dist/core/utils/path.js +53 -0
  15. package/dist/core/virtual-module/get-story-modules.js +60 -0
  16. package/dist/core/virtual-module/story-modules.js +8 -0
  17. package/dist/core/virtual-module/virtual-module-ids.js +22 -0
  18. package/dist/core/virtual-module/virtual-routes.js +83 -0
  19. package/dist/core/virtual-module/vite-plugin.js +98 -0
  20. package/docs/PIN-CONTRACT.md +263 -0
  21. package/docs/PIN.md +429 -0
  22. package/index.d.ts +279 -0
  23. package/index.mjs +347 -0
  24. package/package.json +93 -0
  25. package/src/Empty.astro +4 -0
  26. package/src/Home.astro +298 -0
  27. package/src/LabHead.astro +1102 -0
  28. package/src/core/LICENSE-astrobook +166 -0
  29. package/src/core/astro-integration.ts +166 -0
  30. package/src/core/client.ts +89 -0
  31. package/src/core/index.ts +7 -0
  32. package/src/core/lib/components/empty.astro +1 -0
  33. package/src/core/lib/components/head.astro +1 -0
  34. package/src/core/lib/components/home.astro +8 -0
  35. package/src/core/lib/components/with-decorators.astro +22 -0
  36. package/src/core/lib/pages/app.astro +19 -0
  37. package/src/core/lib/pages/preview.astro +17 -0
  38. package/src/core/lib/pages/story.astro +16 -0
  39. package/src/core/lib-paths.ts +72 -0
  40. package/src/core/options.ts +262 -0
  41. package/src/core/utils/get-exports.ts +59 -0
  42. package/src/core/utils/invariant.ts +13 -0
  43. package/src/core/utils/kebab-case.ts +30 -0
  44. package/src/core/utils/path-builder.ts +45 -0
  45. package/src/core/utils/path.ts +80 -0
  46. package/src/core/virtual-module/get-story-modules.ts +110 -0
  47. package/src/core/virtual-module/story-modules.ts +9 -0
  48. package/src/core/virtual-module/virtual-module-ids.ts +17 -0
  49. package/src/core/virtual-module/virtual-routes.ts +130 -0
  50. package/src/core/virtual-module/vite-plugin.ts +125 -0
  51. package/src/pin/board.mjs +1521 -0
  52. package/src/pin/index.mjs +666 -0
  53. package/src/pin/shot.mjs +427 -0
  54. package/src/pin/source-stamp.mjs +159 -0
  55. package/src/pin/tickets.mjs +697 -0
  56. package/src/pin/toolbar.js +3181 -0
  57. package/src/shell/Browse.astro +371 -0
  58. package/src/shell/CardGrid.astro +297 -0
  59. package/src/shell/Viewport.astro +1330 -0
  60. package/src/shell/index.json.ts +12 -0
  61. package/src/shell/lab-index.ts +344 -0
  62. package/src/shell/lab-params.ts +245 -0
  63. package/src/shell/live-files.mjs +164 -0
  64. package/src/shell/marks.mjs +136 -0
  65. package/src/types/index.ts +6 -0
  66. package/src/types/types.ts +239 -0
  67. package/src/types/virtual.d.ts +29 -0
  68. package/src/ui/components/app.astro +13 -0
  69. package/src/ui/components/build-path.ts +13 -0
  70. package/src/ui/components/build-tree.ts +108 -0
  71. package/src/ui/components/collapse-duration.ts +28 -0
  72. package/src/ui/components/compress-terms.ts +10 -0
  73. package/src/ui/components/dashboard-layout.astro +39 -0
  74. package/src/ui/components/home.astro +65 -0
  75. package/src/ui/components/layout.astro +110 -0
  76. package/src/ui/components/preview-layout.astro +109 -0
  77. package/src/ui/components/sidebar-button-fullscreen.astro +38 -0
  78. package/src/ui/components/sidebar-button-search.astro +23 -0
  79. package/src/ui/components/sidebar-button-theme.astro +9 -0
  80. package/src/ui/components/sidebar-button.astro +24 -0
  81. package/src/ui/components/sidebar-resize-handle.astro +74 -0
  82. package/src/ui/components/sidebar-search-panel.astro +41 -0
  83. package/src/ui/components/sidebar-search-script.ts +103 -0
  84. package/src/ui/components/sidebar-title.astro +17 -0
  85. package/src/ui/components/sidebar-tree-node.astro +143 -0
  86. package/src/ui/components/sidebar-tree.astro +84 -0
  87. package/src/ui/components/sidebar.astro +29 -0
  88. package/src/ui/components/theme-message.ts +26 -0
  89. package/src/ui/components/theme-script.astro +71 -0
  90. package/src/ui/components/theme-toggle.astro +63 -0
  91. package/src/ui/components/theme.ts +32 -0
  92. package/src/ui/index.ts +4 -0
  93. package/src/ui/lab.css +549 -0
  94. package/virtual.d.ts +42 -0
@@ -0,0 +1,108 @@
1
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/build-tree.ts) — see
2
+ // ../../core/LICENSE-astrobook.
3
+ // Divergence: relative imports carry the .ts extension, and the StoryModule type comes from the
4
+ // vendored types (../../types/) rather than the `@astrobook/types` package.
5
+ import type { StoryModule } from '../../types/types.ts'
6
+
7
+ import { compressTerms } from './compress-terms.ts'
8
+
9
+ export interface DirectoryNode {
10
+ type: 'directory'
11
+ name: string
12
+ searchText: string
13
+ children: TreeNode[]
14
+ }
15
+
16
+ export interface ModuleNode {
17
+ type: 'module'
18
+ id: string
19
+ name: string
20
+ searchText: string
21
+ children: StoryNode[]
22
+ }
23
+
24
+ export interface StoryNode {
25
+ type: 'story'
26
+ id: string
27
+ name: string
28
+ searchText: string
29
+ children?: undefined
30
+ }
31
+
32
+ export type TreeNode = DirectoryNode | ModuleNode | StoryNode
33
+
34
+ export function isSingleStory(module: StoryModule): boolean {
35
+ return module.stories.length === 1 && module.stories[0].name === module.name
36
+ }
37
+
38
+ function findOrCreateDirectory(
39
+ children: TreeNode[],
40
+ segments: string[],
41
+ ): TreeNode[] {
42
+ let current = children
43
+ for (const segment of segments) {
44
+ let dir: DirectoryNode | undefined
45
+ for (const node of current) {
46
+ if (node.type === 'directory' && node.name === segment) {
47
+ dir = node
48
+ break
49
+ }
50
+ }
51
+ if (!dir) {
52
+ dir = { type: 'directory', name: segment, children: [], searchText: '' }
53
+ current.push(dir)
54
+ }
55
+ current = dir.children
56
+ }
57
+ return current
58
+ }
59
+
60
+ export function buildTree(modules: StoryModule[]): TreeNode[] {
61
+ const root: TreeNode[] = []
62
+
63
+ for (const module of modules) {
64
+ const segments = module.directory ? module.directory.split('/') : []
65
+ const children = findOrCreateDirectory(root, segments)
66
+
67
+ const storyNodes: StoryNode[] = module.stories.map((story) => ({
68
+ type: 'story',
69
+ id: story.id,
70
+ name: story.name,
71
+ searchText: '',
72
+ }))
73
+
74
+ if (isSingleStory(module)) {
75
+ children.push(storyNodes[0])
76
+ } else {
77
+ const moduleNode: ModuleNode = {
78
+ type: 'module',
79
+ id: module.id,
80
+ name: module.name,
81
+ searchText: '',
82
+ children: storyNodes,
83
+ }
84
+ children.push(moduleNode)
85
+ }
86
+ }
87
+
88
+ for (const node of root) {
89
+ assignSearchText(node, [])
90
+ }
91
+
92
+ return root
93
+ }
94
+
95
+ function assignSearchText(
96
+ node: TreeNode,
97
+ ancestorTerms: readonly string[],
98
+ ): readonly string[] {
99
+ const currTerms = [...ancestorTerms, node.name]
100
+ const subTreeTerms: Array<readonly string[]> = Array.from(
101
+ node.children || [],
102
+ (child) => assignSearchText(child, currTerms),
103
+ )
104
+
105
+ const mergedTerms = compressTerms([...currTerms, ...subTreeTerms.flat()])
106
+ node.searchText = mergedTerms.join(' ')
107
+ return mergedTerms
108
+ }
@@ -0,0 +1,28 @@
1
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/collapse-duration.ts) — see
2
+ // ../../core/LICENSE-astrobook.
3
+ // Divergence: the relative import carries the .ts extension.
4
+ import type { TreeNode } from './build-tree.ts'
5
+
6
+ function countDescendantRows(node: TreeNode): number {
7
+ if (node.type === 'story') return 0
8
+ let rows = node.children.length
9
+ for (const child of node.children) {
10
+ rows += countDescendantRows(child)
11
+ }
12
+ return rows
13
+ }
14
+
15
+ export function getCollapseDuration(node: TreeNode): number {
16
+ if (node.type === 'story') return 0
17
+ return computeCollapseDuration(countDescendantRows(node))
18
+ }
19
+
20
+ export function computeCollapseDuration(rows: number): number {
21
+ const minMs = 100
22
+ const maxMs = 500
23
+ const scale = 35
24
+ return Math.min(
25
+ maxMs,
26
+ Math.round(minMs + scale * Math.log(Math.max(1, rows))),
27
+ )
28
+ }
@@ -0,0 +1,10 @@
1
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/compress-terms.ts) — see
2
+ // ../../core/LICENSE-astrobook. Unchanged.
3
+ export function compressTerms(terms: readonly string[]): readonly string[] {
4
+ const unique = Array.from(new Set(terms.map((k) => k.toLowerCase())))
5
+ return unique
6
+ .filter(
7
+ (term) => !unique.some((other) => other !== term && other.includes(term)),
8
+ )
9
+ .sort()
10
+ }
@@ -0,0 +1,39 @@
1
+ ---
2
+ // The DASHBOARD document: the lab's chrome with the story rendered inside it. New in this
3
+ // package — upstream had one layout for the dashboard, the bare story and the home page alike,
4
+ // and gave all three the consumer's CSS.
5
+ //
6
+ // This is the one document that legitimately carries BOTH stylesheets, because it is both things
7
+ // at once (ruled 2026-09-22):
8
+ //
9
+ // · the chrome — sidebar, search, toolbar — styled by ../lab.css and by nothing else;
10
+ // · the preview — the story in <main> — which must be rendered exactly as the real site
11
+ // renders it, so it gets the consumer's `css` list and the consumer's head component.
12
+ //
13
+ // Nothing crosses. The chrome reads only `--lab-*` tokens and is anchored on chrome classes and
14
+ // ids, so the consumer's `body`/`a`/`*` rules cannot reach it; the chrome sets no font, colour or
15
+ // size on `body`, so nothing of ours inherits down into the story. The full argument, and what it
16
+ // costs, is at the top of ../lab.css.
17
+ //
18
+ // The consumer's head arrives via `virtual:orbytes-lab/user-head.mjs` — the lab shell's own
19
+ // virtual module (../../../index.mjs), which is how it reached this package before too: the
20
+ // package's LabHead used to mount it on EVERY lab page, which is what carried the site's fonts
21
+ // into the chrome.
22
+ import 'virtual:astrobook/user-css.mjs'
23
+
24
+ import UserHead from 'virtual:orbytes-lab/user-head.mjs'
25
+
26
+ import Layout from './layout.astro'
27
+
28
+ interface Props {
29
+ story: string | undefined
30
+ hasSidebar: boolean
31
+ }
32
+ ---
33
+
34
+ <Layout story={Astro.props.story} hasSidebar={Astro.props.hasSidebar}>
35
+ <Fragment slot="head">
36
+ <UserHead />
37
+ </Fragment>
38
+ <slot />
39
+ </Layout>
@@ -0,0 +1,65 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/home.astro) — see
3
+ // ../../core/LICENSE-astrobook.
4
+ // Divergence: the UnoCSS utility strings (including the `@container` queries, which had no
5
+ // container to query once the chrome stopped shipping Uno's preflight) are the `.lab-splash*`
6
+ // classes in ../lab.css, and the star glyph is an inline SVG.
7
+ //
8
+ // This is Astrobook's BUILT-IN splash. The orbytes lab always passes its own `home`
9
+ // (src/Home.astro — the tier cards and live thumbnails), so this renders only for a consumer
10
+ // that leaves the option at its default. Kept working, not designed.
11
+ import config from 'virtual:astrobook/global-config.mjs'
12
+
13
+ const content = config.homeContent
14
+ const showBadges = !!(content.version || content.repo)
15
+ ---
16
+
17
+ <div class="lab-splash lab-chrome">
18
+ {
19
+ content.title == null ? null : (
20
+ <h1 class="lab-splash__title">{content.title}</h1>
21
+ )
22
+ }
23
+ {
24
+ content.subtitle == null ? null : (
25
+ <p class="lab-splash__subtitle">{content.subtitle}</p>
26
+ )
27
+ }
28
+
29
+ {
30
+ showBadges && (
31
+ <div class="lab-splash__badges">
32
+ {content.version && (
33
+ <a
34
+ href={content.version.href}
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ class="lab-splash__badge"
38
+ >
39
+ {content.version.label}
40
+ </a>
41
+ )}
42
+ {content.repo && (
43
+ <a
44
+ href={content.repo.href}
45
+ target="_blank"
46
+ rel="noopener noreferrer"
47
+ class="lab-splash__badge"
48
+ >
49
+ <svg
50
+ viewBox="0 0 16 16"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ stroke-width="1.5"
54
+ stroke-linejoin="round"
55
+ aria-hidden="true"
56
+ >
57
+ <path d="M8 1.75 10 6l4.25.5-3.1 2.9.85 4.35L8 11.6l-4 2.15.85-4.35L1.75 6.5 6 6z" />
58
+ </svg>
59
+ <span>{content.repo.label}</span>
60
+ </a>
61
+ )}
62
+ </div>
63
+ )
64
+ }
65
+ </div>
@@ -0,0 +1,110 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/layout.astro) — see
3
+ // ../../core/LICENSE-astrobook.
4
+ //
5
+ // THE DIVERGENCE THAT MATTERS (ruled 2026-09-22): this layout no longer imports
6
+ // `virtual:astrobook/user-css.mjs`. Upstream imported the consumer's stylesheets here, last, "so
7
+ // it can override built-in styles" — which is exactly how the lab's chrome ended up wearing the
8
+ // website's brand. The consumer's CSS now reaches the PREVIEW only, through
9
+ // ./dashboard-layout.astro (the story rendered beside the chrome) and ./preview-layout.astro (the
10
+ // bare story route). This layout is pure chrome and is what the `/lab` home page renders through.
11
+ // Full reasoning: ../lab.css.
12
+ //
13
+ // Other divergences: the UnoCSS utility strings are the `.lab-shell` / `.lab-main` classes in
14
+ // ../lab.css; `astro-theme-toggle`'s ThemeScript is ./theme-script.astro, vendored; and a
15
+ // `<slot name="head" />` was added so a wrapping layout can put the consumer's head and CSS into
16
+ // this document without this file importing either.
17
+ //
18
+ // DOM contracts LabHead.astro depends on, all unchanged: `<html data-active-story>`, the `.dark`
19
+ // class (set by the theme script), `#astrobook-sidebar`, the resize-handle element, and
20
+ // `astro:after-swap` / `astro:page-load` from <ClientRouter>.
21
+ import '../lab.css'
22
+
23
+ import { ClientRouter } from 'astro:transitions'
24
+
25
+ import CustomHead from 'virtual:astrobook/components/head.mjs'
26
+ import config from 'virtual:astrobook/global-config.mjs'
27
+
28
+ import Sidebar from './sidebar.astro'
29
+ import SidebarResizeHandle from './sidebar-resize-handle.astro'
30
+
31
+ interface Props {
32
+ story: string | undefined
33
+ hasSidebar: boolean
34
+ }
35
+
36
+ const hasSidebar = Astro.props.hasSidebar ?? true
37
+ const DEFAULT_SIDEBAR_WIDTH = 300
38
+ const DEFAULT_SIDEBAR_RESIZING = false
39
+ ---
40
+
41
+ <html
42
+ lang="en"
43
+ class="lab-shell"
44
+ data-astrobook-root
45
+ data-active-story={Astro.props.story ?? ''}
46
+ data-sidebar-width={DEFAULT_SIDEBAR_WIDTH}
47
+ data-sidebar-resizing={DEFAULT_SIDEBAR_RESIZING ? '' : undefined}
48
+ >
49
+ <head>
50
+ <meta charset="utf-8" />
51
+ <meta name="viewport" content="width=device-width" />
52
+ <meta name="generator" content={Astro.generator} />
53
+ <title>{config.title}</title>
54
+ <script>
55
+ import { initThemeMessage } from './theme-message.ts'
56
+ initThemeMessage()
57
+ </script>
58
+ {/* CustomHead is the lab's own head (src/LabHead.astro), and the THEME SCRIPT comes with it.
59
+ Upstream rendered <ThemeScript /> here instead. It moved because the lab's folder pages and
60
+ viewport configurator are their own documents — they mount LabHead and never this layout —
61
+ so a theme script that lived here left `.dark` unset on them. That was invisible while the
62
+ chrome wore the consumer's dark-only tokens; now that the chrome has a light theme and a
63
+ dark one of its own (../lab.css), those pages rendered light while the dashboard rendered
64
+ dark. Measured 2026-09-22 on a real consumer site. One script per document either way:
65
+ LabHead carries it on every chrome page, and the bare story
66
+ page mounts no LabHead and carries its own (./preview-layout.astro). */}
67
+ <CustomHead />
68
+ <slot name="head" />
69
+ <ClientRouter fallback="none" />
70
+ <script is:inline>
71
+ ;(() => {
72
+ const STORAGE_KEY = 'astrobook-sidebar-width'
73
+ const restoreWidth = () => {
74
+ const width = parseFloat(localStorage.getItem(STORAGE_KEY) || '')
75
+ if (width) {
76
+ document.documentElement.dataset.sidebarWidth = String(width)
77
+ }
78
+ }
79
+
80
+ restoreWidth()
81
+ document.addEventListener('astro:after-swap', restoreWidth)
82
+ })()
83
+ </script>
84
+ </head>
85
+ <body class="lab-shell">
86
+ {
87
+ hasSidebar && (
88
+ <>
89
+ <div id="astrobook-sidebar" class="lab-chrome">
90
+ <Sidebar story={Astro.props.story} />
91
+ </div>
92
+ <SidebarResizeHandle />
93
+ </>
94
+ )
95
+ }
96
+ {
97
+ /*
98
+ Use `transition:animate="none"` here because we don't want the fade animation
99
+ when switching between stories. Sadly this also removes the animation when toggling the light/dark theme.
100
+ */
101
+ }
102
+ <main
103
+ class="lab-main"
104
+ transition:name="astrobook-main"
105
+ transition:animate="none"
106
+ >
107
+ <slot />
108
+ </main>
109
+ </body>
110
+ </html>
@@ -0,0 +1,109 @@
1
+ ---
2
+ // The PREVIEW document: the bare story at <subpath>/stories/<id>, with no chrome at all. New in
3
+ // this package — upstream rendered the bare story through the same layout as the dashboard, so
4
+ // every story page also carried the sidebar's stylesheet, its scripts and the lab's head.
5
+ //
6
+ // THE RULING (2026-09-22) runs both ways, and this file is the second half of it: the
7
+ // story keeps the consumer's `css` list and `head` component, because seeing a component as the
8
+ // real site renders it is the point of the lab — and it gets NOTHING of the lab's own styling.
9
+ // ../lab.css is not imported here, LabHead.astro is not mounted here, and a component styled by
10
+ // lab CSS the real site has never heard of is a lab that lies.
11
+ //
12
+ // What IS here, and why:
13
+ // · the document frame — the same `html { overflow: hidden }` / flex `body` / scrollable
14
+ // `<main>` the story has always rendered inside. It is structure, not styling: the thumbnail
15
+ // cards and the viewport configurator measure stories in fixed-size iframes, and changing
16
+ // which box scrolls would change what they show. The three rules live inline, on
17
+ // `.lab-preview-*` classes a story cannot collide with, rather than in the chrome stylesheet.
18
+ // · the theme script — it sets `.dark` on <html> and answers the `astrobook:set-theme`
19
+ // postMessage the embedding pages send. Behaviour, not chrome styling; a site with no `.dark`
20
+ // rules sees nothing.
21
+ // · the UNCONDITIONAL noindex. The lab is staging-only and never indexable (ruled 2026-09-02)
22
+ // and this is one of the two mechanisms that enforce it — the other is the consumer's build
23
+ // gate. `npm run check:lab-gate` walks every built lab page and fails on any without it, so
24
+ // this meta has to be here as well as in LabHead.astro. Do not make it conditional.
25
+ // · Feedbucket, when the consumer configured a key — a bare story page is a lab page you can
26
+ // open full screen, and it carried the widget before this split.
27
+ import 'virtual:astrobook/user-css.mjs'
28
+
29
+ import { ClientRouter } from 'astro:transitions'
30
+
31
+ import config from 'virtual:astrobook/global-config.mjs'
32
+ import labConfig from 'virtual:orbytes-lab/config.mjs'
33
+ import UserHead from 'virtual:orbytes-lab/user-head.mjs'
34
+
35
+ import ThemeScript from './theme-script.astro'
36
+
37
+ interface Props {
38
+ story: string | undefined
39
+ hasSidebar: boolean
40
+ }
41
+
42
+ const feedbucketKey = labConfig.feedbucketKey
43
+ ---
44
+
45
+ <html
46
+ lang="en"
47
+ class="lab-preview-shell"
48
+ data-astrobook-root
49
+ data-astrobook-preview
50
+ data-active-story={Astro.props.story ?? ''}
51
+ >
52
+ <head>
53
+ <meta charset="utf-8" />
54
+ <meta name="viewport" content="width=device-width" />
55
+ <meta name="generator" content={Astro.generator} />
56
+ <title>{config.title}</title>
57
+ <meta name="robots" content="noindex, nofollow" />
58
+ <ThemeScript />
59
+ <script>
60
+ import { initThemeMessage } from './theme-message.ts'
61
+ initThemeMessage()
62
+ </script>
63
+ <UserHead />
64
+ {
65
+ feedbucketKey && (
66
+ <script
67
+ is:inline
68
+ defer
69
+ src="https://cdn.feedbucket.app/assets/feedbucket.js"
70
+ data-feedbucket={feedbucketKey}
71
+ />
72
+ )
73
+ }
74
+ <ClientRouter fallback="none" />
75
+ <style is:global>
76
+ html.lab-preview-shell {
77
+ height: 100%;
78
+ max-height: 100vh;
79
+ overflow: hidden;
80
+ }
81
+
82
+ body.lab-preview-shell {
83
+ margin: 0;
84
+ padding: 0;
85
+ display: flex;
86
+ flex-direction: row;
87
+ align-items: stretch;
88
+ justify-content: flex-start;
89
+ min-height: 100%;
90
+ max-height: 100%;
91
+ }
92
+
93
+ .lab-preview-main {
94
+ flex: 1;
95
+ min-width: 0;
96
+ overflow: auto;
97
+ }
98
+ </style>
99
+ </head>
100
+ <body class="lab-preview-shell">
101
+ <main
102
+ class="lab-preview-main"
103
+ transition:name="astrobook-main"
104
+ transition:animate="none"
105
+ >
106
+ <slot />
107
+ </main>
108
+ </body>
109
+ </html>
@@ -0,0 +1,38 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button-fullscreen.astro) —
3
+ // see ../../core/LICENSE-astrobook.
4
+ // Divergence: the UnoCSS icon class is an inline SVG (no build step here), and the
5
+ // `import '@astrobook/types'` side-effect line is gone — the ambient declarations it pulled in
6
+ // live in ../../types/virtual.d.ts now.
7
+ import config from 'virtual:astrobook/global-config.mjs'
8
+
9
+ import SidebarButton from './sidebar-button.astro'
10
+ import { buildPath } from './build-path.ts'
11
+
12
+ interface Props {
13
+ story?: string
14
+ }
15
+ ---
16
+
17
+ {
18
+ Astro.props.story ? (
19
+ <SidebarButton
20
+ href={buildPath(config.storyBase, Astro.props.story)}
21
+ title="Go full screen"
22
+ >
23
+ <svg
24
+ viewBox="0 0 16 16"
25
+ fill="none"
26
+ stroke="currentColor"
27
+ stroke-width="1.5"
28
+ stroke-linecap="round"
29
+ stroke-linejoin="round"
30
+ aria-hidden="true"
31
+ >
32
+ <path d="M9.5 2.5H13.5V6.5" />
33
+ <path d="M13.5 2.5 8 8" />
34
+ <path d="M12 9.5v3a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3" />
35
+ </svg>
36
+ </SidebarButton>
37
+ ) : null
38
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button-search.astro) — see
3
+ // ../../core/LICENSE-astrobook.
4
+ // Divergence: the UnoCSS icon class (`i-fluent-search-16-regular`, which presetIcons turned into
5
+ // a background-image at build time) is an inline SVG — this package has no build step.
6
+ // `id="astrobook-search-toggle"` is a DOM contract: sidebar-search-script.ts binds the panel to
7
+ // it, and LabHead.astro clicks it to open the panel on ⌘K.
8
+ import SidebarButton from './sidebar-button.astro'
9
+ ---
10
+
11
+ <SidebarButton id="astrobook-search-toggle" title="Search">
12
+ <svg
13
+ viewBox="0 0 16 16"
14
+ fill="none"
15
+ stroke="currentColor"
16
+ stroke-width="1.5"
17
+ stroke-linecap="round"
18
+ aria-hidden="true"
19
+ >
20
+ <circle cx="7" cy="7" r="4.5"></circle>
21
+ <path d="m10.5 10.5 3 3"></path>
22
+ </svg>
23
+ </SidebarButton>
@@ -0,0 +1,9 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button-theme.astro) — see
3
+ // ../../core/LICENSE-astrobook.
4
+ // Divergence: `astro-theme-toggle`'s Toggle is ./theme-toggle.astro, vendored into this package
5
+ // (why: ./theme-script.astro).
6
+ import Toggle from './theme-toggle.astro'
7
+ ---
8
+
9
+ <Toggle class="astrobook-sidebar-button lab-focus-ring" />
@@ -0,0 +1,24 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-button.astro) — see
3
+ // ../../core/LICENSE-astrobook.
4
+ // Divergence: the UnoCSS `astrobook-sidebar-button` shortcut is now a real class in
5
+ // ../lab.css. The CLASS NAME is unchanged on purpose — LabHead.astro creates the collapse, cull,
6
+ // viewport, staging and code buttons in JavaScript and sets exactly this className on each.
7
+ interface Props {
8
+ id?: string
9
+ href?: string
10
+ title: string
11
+ }
12
+
13
+ const Element = Astro.props.href ? 'a' : 'button'
14
+ ---
15
+
16
+ <Element
17
+ id={Astro.props.id}
18
+ href={Astro.props.href}
19
+ class:list={['astrobook-sidebar-button', 'lab-focus-ring']}
20
+ title={Astro.props.title}
21
+ >
22
+ <span class="lab-sr-only">{Astro.props.title}</span>
23
+ <slot />
24
+ </Element>
@@ -0,0 +1,74 @@
1
+ ---
2
+ // Vendored from astrobook 0.13.3 (packages/ui/src/components/sidebar-resize-handle.astro) — see
3
+ // ../../core/LICENSE-astrobook.
4
+ // Divergence: the UnoCSS utility string is a real rule on the `astrobook-sidebar-resize-handle`
5
+ // element selector in ../lab.css. The ELEMENT NAME is unchanged — LabHead.astro's collapse styles
6
+ // hide it by that name.
7
+ ---
8
+
9
+ <astrobook-sidebar-resize-handle role="separator"></astrobook-sidebar-resize-handle>
10
+
11
+ <script>
12
+ const STORAGE_KEY = 'astrobook-sidebar-width'
13
+ const DEFAULT_WIDTH = 300
14
+ let previousWidth = ''
15
+
16
+ function setSidebarWidth(width: number): void {
17
+ const widthString = width.toFixed(0)
18
+ if (widthString === previousWidth) {
19
+ return
20
+ }
21
+ previousWidth = widthString
22
+ document.documentElement.dataset.sidebarWidth = widthString
23
+ localStorage.setItem(STORAGE_KEY, widthString)
24
+ }
25
+
26
+ class AstrobookSidebarResizeHandle extends HTMLElement {
27
+ #handleCapture = (event: PointerEvent) => {
28
+ event.preventDefault()
29
+ this.setPointerCapture(event.pointerId)
30
+ document.documentElement.dataset.sidebarResizing = ''
31
+
32
+ document.body.addEventListener('pointermove', this.#handleMove)
33
+ document.body.addEventListener('pointerup', this.#handleRelease, {
34
+ once: true,
35
+ })
36
+ }
37
+
38
+ #handleRelease = (event?: PointerEvent) => {
39
+ if (event) {
40
+ event.preventDefault()
41
+ this.releasePointerCapture(event.pointerId)
42
+ }
43
+ delete document.documentElement.dataset.sidebarResizing
44
+ document.body.removeEventListener('pointermove', this.#handleMove)
45
+ document.body.removeEventListener('pointerup', this.#handleRelease)
46
+ }
47
+
48
+ #handleMove = (event: PointerEvent) => {
49
+ setSidebarWidth(event.clientX)
50
+ }
51
+
52
+ #handleReset = () => {
53
+ setSidebarWidth(DEFAULT_WIDTH)
54
+ }
55
+
56
+ connectedCallback() {
57
+ this.addEventListener('pointerdown', this.#handleCapture)
58
+ this.addEventListener('dblclick', this.#handleReset)
59
+ }
60
+
61
+ disconnectedCallback() {
62
+ this.removeEventListener('pointerdown', this.#handleCapture)
63
+ this.removeEventListener('dblclick', this.#handleReset)
64
+ this.#handleRelease()
65
+ }
66
+ }
67
+
68
+ if (!customElements.get('astrobook-sidebar-resize-handle')) {
69
+ customElements.define(
70
+ 'astrobook-sidebar-resize-handle',
71
+ AstrobookSidebarResizeHandle,
72
+ )
73
+ }
74
+ </script>