@rimelight/ui 0.0.34 → 0.0.36

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 (185) hide show
  1. package/README.md +1 -0
  2. package/package.json +19 -24
  3. package/src/components/astro/RLAAccordion.astro +94 -18
  4. package/src/components/astro/RLAAvatar.astro +135 -68
  5. package/src/components/astro/RLABadge.astro +4 -2
  6. package/src/components/astro/RLABanner.astro +138 -23
  7. package/src/components/astro/RLABreadcrumb.astro +13 -0
  8. package/src/components/astro/RLAButton.astro +8 -4
  9. package/src/components/astro/RLACallout.astro +112 -0
  10. package/src/components/astro/RLACard.astro +6 -5
  11. package/src/components/astro/RLACarousel.astro +104 -75
  12. package/src/components/astro/RLAChart.astro +4 -1
  13. package/src/components/astro/RLAChip.astro +11 -5
  14. package/src/components/astro/RLACollapsible.astro +4 -1
  15. package/src/components/astro/RLAColorArea.astro +432 -0
  16. package/src/components/astro/RLAColorField.astro +300 -0
  17. package/src/components/astro/RLAColorSlider.astro +413 -0
  18. package/src/components/astro/RLAConfirm.astro +13 -11
  19. package/src/components/astro/RLACopyMarkdown.astro +78 -0
  20. package/src/components/astro/RLADate.astro +30 -0
  21. package/src/components/astro/RLADrawer.astro +50 -3
  22. package/src/components/astro/RLADropdownMenu.astro +78 -51
  23. package/src/components/astro/RLAFieldGroup.astro +4 -1
  24. package/src/components/astro/RLAFileUpload.astro +114 -100
  25. package/src/components/astro/RLAFooter.astro +2 -1
  26. package/src/components/astro/RLAFormField.astro +10 -6
  27. package/src/components/astro/RLAGrid.astro +31 -39
  28. package/src/components/astro/RLAHead.astro +139 -39
  29. package/src/components/astro/RLAHeader.astro +2 -1
  30. package/src/components/astro/RLAImage.astro +274 -251
  31. package/src/components/astro/RLAInput.astro +14 -9
  32. package/src/components/astro/RLAInputMenu.astro +119 -83
  33. package/src/components/astro/RLAKbd.astro +5 -5
  34. package/src/components/astro/RLALayoutGrid.astro +395 -0
  35. package/src/components/astro/RLALink.astro +23 -1
  36. package/src/components/astro/RLALinkGroup.astro +99 -99
  37. package/src/components/astro/RLALocaleSelector.astro +34 -41
  38. package/src/components/astro/RLALogo.astro +117 -90
  39. package/src/components/astro/RLAModal.astro +48 -18
  40. package/src/components/astro/RLANavigationMenu.astro +381 -326
  41. package/src/components/astro/{RLADocsPackageManagers.astro → RLAPackageManagers.astro} +81 -87
  42. package/src/components/astro/RLAPageSection.astro +4 -2
  43. package/src/components/astro/RLAPopover.astro +4 -3
  44. package/src/components/astro/RLAPost.astro +11 -2
  45. package/src/components/astro/RLAScrollArea.astro +4 -1
  46. package/src/components/astro/RLAScrollToTop.astro +200 -172
  47. package/src/components/astro/RLASearch.astro +282 -0
  48. package/src/components/astro/RLASelect.astro +78 -55
  49. package/src/components/astro/RLASeparator.astro +11 -6
  50. package/src/components/astro/RLAShare.astro +60 -0
  51. package/src/components/astro/RLAShortcuts.astro +429 -0
  52. package/src/components/astro/RLASidebar.astro +158 -212
  53. package/src/components/astro/RLASlideover.astro +47 -17
  54. package/src/components/astro/RLASplitter.astro +194 -141
  55. package/src/components/astro/RLASteps.astro +118 -71
  56. package/src/components/astro/RLASurround.astro +39 -0
  57. package/src/components/astro/RLATable.astro +4 -1
  58. package/src/components/astro/RLATableOfContents.astro +109 -23
  59. package/src/components/astro/RLATabs.astro +147 -133
  60. package/src/components/astro/RLATextarea.astro +4 -1
  61. package/src/components/astro/RLAThemeSelector.astro +105 -76
  62. package/src/components/astro/RLAToast.astro +35 -21
  63. package/src/components/vue/RLVBanner.vue +230 -0
  64. package/src/components/vue/RLVButton.vue +3 -2
  65. package/src/components/vue/RLVCheckbox.vue +4 -2
  66. package/src/components/vue/RLVFooter.vue +3 -2
  67. package/src/components/vue/RLVHeader.vue +3 -2
  68. package/src/components/vue/RLVIcon.vue +4 -2
  69. package/src/components/vue/RLVInput.vue +5 -4
  70. package/src/components/vue/RLVLogo.vue +25 -3
  71. package/src/components/vue/RLVPlaceholder.vue +3 -2
  72. package/src/components/vue/RLVPost.vue +8 -2
  73. package/src/components/vue/RLVProgress.vue +60 -0
  74. package/src/components/vue/RLVScrollToTop.vue +65 -52
  75. package/src/components/vue/RLVSection.vue +14 -13
  76. package/src/components/vue/RLVToast.vue +17 -4
  77. package/src/composables/index.ts +1 -0
  78. package/src/composables/useHeaderStore.ts +1 -0
  79. package/src/composables/useShortcuts.ts +11 -0
  80. package/src/config/security.ts +43 -36
  81. package/src/config/site.config.ts +1 -0
  82. package/src/docs/components/ApiChangelog.astro +147 -0
  83. package/src/{plugins/starlightDocsApi → docs}/components/ApiEmits.astro +4 -4
  84. package/src/{plugins/starlightDocsApi → docs}/components/ApiProps.astro +4 -4
  85. package/src/{plugins/starlightDocsApi → docs}/components/ApiSlots.astro +4 -4
  86. package/src/docs/components/ApiTheme.astro +100 -0
  87. package/src/docs/extractAll.ts +94 -0
  88. package/src/docs/extractors/extractAstro.ts +69 -0
  89. package/src/docs/extractors/extractTheme.ts +56 -0
  90. package/src/{plugins/starlightDocsApi/extract-vue.ts → docs/extractors/extractVue.ts} +164 -15
  91. package/src/{plugins/starlightDocsApi → docs}/utils.ts +1 -2
  92. package/src/env.d.ts +0 -12
  93. package/src/integrations/ui.ts +35 -19
  94. package/src/middleware/security.ts +75 -19
  95. package/src/presets/index.ts +118 -293
  96. package/src/styles/index.css +0 -2
  97. package/src/themes/accordion.theme.ts +29 -3
  98. package/src/themes/aspect-ratio.theme.ts +1 -1
  99. package/src/themes/avatar-group.theme.ts +1 -1
  100. package/src/themes/avatar.theme.ts +2 -2
  101. package/src/themes/badge.theme.ts +2 -3
  102. package/src/themes/banner.theme.ts +40 -4
  103. package/src/themes/breadcrumb.theme.ts +1 -1
  104. package/src/themes/button.theme.ts +21 -22
  105. package/src/themes/{alert.theme.ts → callout.theme.ts} +12 -39
  106. package/src/themes/card.theme.ts +17 -9
  107. package/src/themes/carousel.theme.ts +1 -1
  108. package/src/themes/checkbox.theme.ts +1 -2
  109. package/src/themes/chip.theme.ts +2 -3
  110. package/src/themes/color-area.theme.ts +33 -0
  111. package/src/themes/color-field.theme.ts +32 -0
  112. package/src/themes/color-slider.theme.ts +34 -0
  113. package/src/themes/confirm.theme.ts +1 -1
  114. package/src/themes/container.theme.ts +1 -1
  115. package/src/themes/drawer.theme.ts +1 -1
  116. package/src/themes/footer.theme.ts +2 -2
  117. package/src/themes/grid.theme.ts +38 -0
  118. package/src/themes/header.theme.ts +4 -3
  119. package/src/themes/hover-card.theme.ts +1 -1
  120. package/src/themes/image.theme.ts +11 -1
  121. package/src/themes/input.theme.ts +7 -1
  122. package/src/themes/kbd.theme.ts +2 -3
  123. package/src/themes/label.theme.ts +1 -1
  124. package/src/themes/layout-grid.theme.ts +16 -0
  125. package/src/themes/link-group.theme.ts +0 -4
  126. package/src/themes/marquee.theme.ts +1 -1
  127. package/src/themes/modal.theme.ts +1 -1
  128. package/src/themes/navigation-menu.theme.ts +2 -2
  129. package/src/themes/page-section.theme.ts +4 -2
  130. package/src/themes/pagination.theme.ts +1 -1
  131. package/src/themes/pin-input.theme.ts +1 -1
  132. package/src/themes/popover.theme.ts +1 -1
  133. package/src/themes/post.theme.ts +23 -3
  134. package/src/themes/progress.theme.ts +1 -2
  135. package/src/themes/scroll-to-top.theme.ts +2 -3
  136. package/src/themes/search.theme.ts +64 -0
  137. package/src/themes/separator.theme.ts +1 -1
  138. package/src/themes/slideover.theme.ts +1 -1
  139. package/src/themes/slider.theme.ts +1 -2
  140. package/src/themes/spinner.theme.ts +1 -2
  141. package/src/themes/{megamenu.theme.ts → steps.theme.ts} +3 -3
  142. package/src/themes/sticky-surface.theme.ts +1 -1
  143. package/src/themes/switch.theme.ts +1 -2
  144. package/src/themes/table-of-contents.theme.ts +21 -2
  145. package/src/themes/timeline.theme.ts +7 -2
  146. package/src/themes/toast.theme.ts +1 -1
  147. package/src/types/components/accordion.ts +35 -2
  148. package/src/types/components/avatar.ts +16 -0
  149. package/src/types/components/banner.ts +42 -0
  150. package/src/types/components/button.ts +3 -4
  151. package/src/types/components/{alert.ts → callout.ts} +6 -6
  152. package/src/types/components/color-area.ts +19 -0
  153. package/src/types/components/color-field.ts +21 -0
  154. package/src/types/components/color-slider.ts +20 -0
  155. package/src/types/components/grid.ts +23 -0
  156. package/src/types/components/input.ts +4 -0
  157. package/src/types/components/layout-grid.ts +145 -0
  158. package/src/types/components/link-group.ts +4 -2
  159. package/src/types/components/link.ts +15 -0
  160. package/src/types/components/logo.ts +3 -3
  161. package/src/types/components/post.ts +12 -0
  162. package/src/types/components/search.ts +29 -0
  163. package/src/types/components/sidebar.ts +2 -6
  164. package/src/types/components/{megamenu.ts → steps.ts} +7 -1
  165. package/src/types/components/table-of-contents.ts +22 -0
  166. package/src/types/components/toast.ts +4 -0
  167. package/src/types/docs.ts +40 -0
  168. package/src/types/index.ts +151 -1
  169. package/src/types/theme.ts +6 -6
  170. package/src/utils/color.ts +165 -0
  171. package/src/utils/docs.ts +260 -110
  172. package/src/utils/headerStack.ts +24 -4
  173. package/src/utils/index.ts +2 -0
  174. package/src/utils/shortcuts.ts +322 -0
  175. package/src/components/TableOfContents.astro +0 -107
  176. package/src/components/astro/RLAAlert.astro +0 -93
  177. package/src/components/astro/RLAChangelog.astro +0 -23
  178. package/src/components/astro/RLAMegamenu.astro +0 -23
  179. package/src/plugins/index.ts +0 -2
  180. package/src/plugins/starlightDocsApi/components/ApiTheme.astro +0 -27
  181. package/src/plugins/starlightDocsApi/index.ts +0 -272
  182. package/src/styles/prism.css +0 -42
  183. package/src/themes/changelog.theme.ts +0 -13
  184. package/src/types/components/index.ts +0 -134
  185. /package/src/{plugins/starlightDocsApi → docs}/types.ts +0 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ import type { ComponentMetaMap } from "../types"
3
+ import { generateThemeCode } from "../utils"
4
+ import meta from "virtual:rimelight-ui-docs-meta"
5
+
6
+ interface Props {
7
+ component: string
8
+ }
9
+
10
+ const { component } = Astro.props
11
+
12
+ const allMeta = meta as unknown as ComponentMetaMap
13
+ const compMeta = allMeta[component]
14
+
15
+ const theme = compMeta?.theme ?? null
16
+ const themeCode = theme ? generateThemeCode(component, theme) : null
17
+ ---
18
+
19
+ {themeCode ? (
20
+ <div class="api-theme-container relative border border-gray-800 rounded-lg overflow-hidden group mb-6" data-component={component}>
21
+ <div class="api-theme-collapse overflow-hidden max-h-[240px] relative transition-[max-height] duration-300 ease-in-out">
22
+ <div class="expressive-code m-0 border-0 rounded-none">
23
+ <figure class="frame m-0 border-0">
24
+ <pre class="language-ts overflow-x-auto m-0 border-0 rounded-none bg-gray-900"><code class="language-ts m-0">{themeCode}</code></pre>
25
+ </figure>
26
+ </div>
27
+
28
+ {/* Gradient Fade Overlay */}
29
+ <div class="api-theme-fade absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-gray-950 to-transparent pointer-events-none transition-opacity duration-300"></div>
30
+ </div>
31
+
32
+ {/* Toggle Button Container */}
33
+ <div class="api-theme-trigger-bar absolute bottom-0 left-0 right-0 flex justify-center pb-3 bg-gradient-to-t from-gray-950/90 via-gray-950/70 to-transparent pt-12">
34
+ <button class="api-theme-btn px-4 py-1.5 text-xs font-semibold text-white bg-gray-800 hover:bg-gray-700 border border-gray-700 hover:border-gray-650 rounded-md shadow-md transition-all duration-200 cursor-pointer">
35
+ Show theme config
36
+ </button>
37
+ </div>
38
+ </div>
39
+ ) : (
40
+ <p class="text-sm italic text-gray-500">No theme data available.</p>
41
+ )}
42
+
43
+ <script>
44
+ function setupThemeCollapser() {
45
+ if (typeof document === 'undefined') return;
46
+ const containers = document.querySelectorAll('.api-theme-container');
47
+ containers.forEach(container => {
48
+ if (container.getAttribute('data-collapser-ready') === 'true') return;
49
+ container.setAttribute('data-collapser-ready', 'true');
50
+
51
+ const collapseWrapper = container.querySelector('.api-theme-collapse') as HTMLElement;
52
+ const fadeOverlay = container.querySelector('.api-theme-fade') as HTMLElement;
53
+ const btn = container.querySelector('.api-theme-btn') as HTMLButtonElement;
54
+ const triggerBar = container.querySelector('.api-theme-trigger-bar') as HTMLElement;
55
+
56
+ if (!collapseWrapper || !btn || !triggerBar || !fadeOverlay) return;
57
+
58
+ // Let code blocks load and measure scrollHeight
59
+ const checkAndSetup = () => {
60
+ const scrollHeight = collapseWrapper.scrollHeight;
61
+ if (scrollHeight <= 240) {
62
+ collapseWrapper.style.maxHeight = 'none';
63
+ fadeOverlay.style.display = 'none';
64
+ triggerBar.style.display = 'none';
65
+ return;
66
+ }
67
+
68
+ let isExpanded = false;
69
+
70
+ btn.addEventListener('click', () => {
71
+ isExpanded = !isExpanded;
72
+ if (isExpanded) {
73
+ collapseWrapper.style.maxHeight = `${collapseWrapper.scrollHeight}px`;
74
+ btn.textContent = 'Hide theme config';
75
+ fadeOverlay.style.opacity = '0';
76
+ triggerBar.classList.remove('absolute', 'bottom-0');
77
+ triggerBar.classList.add('relative', 'bg-gray-950', 'py-3', 'border-t', 'border-gray-800');
78
+ triggerBar.style.paddingTop = '12px';
79
+ } else {
80
+ collapseWrapper.style.maxHeight = '240px';
81
+ btn.textContent = 'Show theme config';
82
+ fadeOverlay.style.opacity = '1';
83
+ triggerBar.classList.add('absolute', 'bottom-0');
84
+ triggerBar.classList.remove('relative', 'bg-gray-950', 'py-3', 'border-t', 'border-gray-800');
85
+ triggerBar.style.paddingTop = '48px';
86
+ container.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
87
+ }
88
+ });
89
+ };
90
+
91
+ // Delay slightly to ensure fonts/expressive code are rendered
92
+ setTimeout(checkAndSetup, 150);
93
+ });
94
+ }
95
+
96
+ if (typeof document !== 'undefined') {
97
+ setupThemeCollapser();
98
+ document.addEventListener('astro:after-swap', setupThemeCollapser);
99
+ }
100
+ </script>
@@ -0,0 +1,94 @@
1
+ import { existsSync, readdirSync } from "node:fs"
2
+ import path from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+ import type { ComponentMeta, PropMeta, SlotMeta, EmitMeta } from "./types"
5
+ import { extractAstroMeta } from "./extractors/extractAstro"
6
+ import { extractVueMeta } from "./extractors/extractVue"
7
+ import { extractThemeMeta } from "./extractors/extractTheme"
8
+
9
+ const pathDirname = path.dirname(fileURLToPath(import.meta.url))
10
+ const uiRoot = path.resolve(pathDirname, "..", "..")
11
+
12
+ function pascalToKebab(name: string): string {
13
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
14
+ }
15
+
16
+ function getComponentNameFromFilename(filename: string): string {
17
+ return filename.replace(/^(RLA|RLV)/, "").replace(/\.(astro|vue)$/, "")
18
+ }
19
+
20
+ export async function extractAllMeta(): Promise<Record<string, ComponentMeta>> {
21
+ const componentsDir = path.join(uiRoot, "src", "components")
22
+ const themesDir = path.join(uiRoot, "src", "themes")
23
+
24
+ const vueDir = path.join(componentsDir, "vue")
25
+ const astroDir = path.join(componentsDir, "astro")
26
+
27
+ const vueFiles = existsSync(vueDir)
28
+ ? readdirSync(vueDir).filter((f: string) => f.endsWith(".vue"))
29
+ : []
30
+ const astroFiles = existsSync(astroDir)
31
+ ? readdirSync(astroDir).filter((f: string) => f.endsWith(".astro") && f !== "Head.astro")
32
+ : []
33
+
34
+ const vueComponents = new Map<string, string>()
35
+ const astroComponents = new Map<string, string>()
36
+
37
+ for (const f of vueFiles) vueComponents.set(getComponentNameFromFilename(f), f)
38
+ for (const f of astroFiles) astroComponents.set(getComponentNameFromFilename(f), f)
39
+
40
+ const allNames = new Set([...vueComponents.keys(), ...astroComponents.keys()])
41
+ const result: Record<string, ComponentMeta> = {}
42
+
43
+ await Promise.all(
44
+ [...allNames].toSorted().map(async (name) => {
45
+ const vueFile = vueComponents.get(name)
46
+ const astroFile = astroComponents.get(name)
47
+
48
+ let props: PropMeta[] = []
49
+ let slots: SlotMeta[] = []
50
+ let emits: EmitMeta[] = []
51
+ const frameworks: ("astro" | "vue")[] = []
52
+
53
+ if (vueFile) {
54
+ frameworks.push("vue")
55
+ try {
56
+ const vueMeta = extractVueMeta(path.join(vueDir, vueFile))
57
+ props = vueMeta.props
58
+ slots = vueMeta.slots
59
+ emits = vueMeta.emits
60
+ } catch (err) {
61
+ console.warn(`[docs] Failed to extract Vue meta for ${vueFile}:`, err)
62
+ }
63
+ }
64
+
65
+ if (astroFile) {
66
+ frameworks.push("astro")
67
+ if (!vueFile) {
68
+ try {
69
+ const astroMeta = extractAstroMeta(path.join(astroDir, astroFile))
70
+ props = astroMeta.props
71
+ slots = astroMeta.slots
72
+ } catch (err) {
73
+ console.warn(`[docs] Failed to extract Astro meta for ${astroFile}:`, err)
74
+ }
75
+ }
76
+ }
77
+
78
+ const theme = await extractThemeMeta(name, themesDir)
79
+
80
+ result[pascalToKebab(name)] = {
81
+ name,
82
+ astroComponent: astroFile ?? null,
83
+ vueComponent: vueFile ?? null,
84
+ frameworks,
85
+ props,
86
+ slots,
87
+ emits,
88
+ theme
89
+ }
90
+ })
91
+ )
92
+
93
+ return result
94
+ }
@@ -0,0 +1,69 @@
1
+ import { readFileSync } from "node:fs"
2
+ import ts from "typescript"
3
+ import type { PropMeta, SlotMeta } from "../types"
4
+
5
+ function extractAstroPropsFromInterface(sourceFile: ts.SourceFile): PropMeta[] {
6
+ const props: PropMeta[] = []
7
+
8
+ function visit(node: ts.Node) {
9
+ if (
10
+ (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) &&
11
+ node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
12
+ ) {
13
+ const name = node.name.text
14
+ if (name.endsWith("Props") || name === "Props") {
15
+ if (ts.isInterfaceDeclaration(node)) {
16
+ for (const member of node.members) {
17
+ if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
18
+ const propName = member.name.text
19
+ if (propName === "class" || propName.startsWith("data-")) continue
20
+ const type = member.type ? member.type.getText().replace(/^[^:]+:\s*/, "") : "unknown"
21
+ const isOptional = member.questionToken !== undefined
22
+ props.push({ name: propName, type, required: !isOptional, description: "" })
23
+ }
24
+ }
25
+ } else if (ts.isTypeAliasDeclaration(node) && ts.isTypeLiteralNode(node.type)) {
26
+ for (const member of node.type.members) {
27
+ if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
28
+ const propName = member.name.text
29
+ if (propName === "class" || propName.startsWith("data-")) continue
30
+ const type = member.type ? member.type.getText().replace(/^[^:]+:\s*/, "") : "unknown"
31
+ const isOptional = member.questionToken !== undefined
32
+ props.push({ name: propName, type, required: !isOptional, description: "" })
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ts.forEachChild(node, visit)
39
+ }
40
+
41
+ visit(sourceFile)
42
+ return props
43
+ }
44
+
45
+ export function extractAstroMeta(filePath: string): { props: PropMeta[]; slots: SlotMeta[] } {
46
+ const content = readFileSync(filePath, "utf-8")
47
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
48
+ if (!frontmatterMatch) return { props: [], slots: [] }
49
+
50
+ const frontmatter = frontmatterMatch[1]
51
+ const sourceFile = ts.createSourceFile(
52
+ "component.ts",
53
+ frontmatter ?? "",
54
+ ts.ScriptTarget.Latest,
55
+ true,
56
+ ts.ScriptKind.TS
57
+ )
58
+
59
+ const props = extractAstroPropsFromInterface(sourceFile)
60
+
61
+ const slots: SlotMeta[] = []
62
+ const slotMatches = content.matchAll(/<slot(?:\s+name=["']([^"']+)["'])?/g)
63
+ for (const match of slotMatches) {
64
+ slots.push({ name: match[1] ?? "default", bindings: {}, description: "" })
65
+ }
66
+ if (!slots.length) slots.push({ name: "default", bindings: {}, description: "" })
67
+
68
+ return { props, slots }
69
+ }
@@ -0,0 +1,56 @@
1
+ import { existsSync, readdirSync } from "node:fs"
2
+ import path from "node:path"
3
+ import { execSync } from "node:child_process"
4
+ import type { ThemeMeta } from "../types"
5
+
6
+ export async function extractThemeMeta(
7
+ componentName: string,
8
+ themesDir: string
9
+ ): Promise<ThemeMeta | null> {
10
+ if (!existsSync(themesDir)) return null
11
+
12
+ const themeFiles = readdirSync(themesDir).filter((f) => f.endsWith(".theme.ts"))
13
+ let themePath: string | null = null
14
+ for (const file of themeFiles) {
15
+ const baseName = file.replace(".theme.ts", "")
16
+ if (baseName === componentName.toLowerCase()) {
17
+ themePath = path.join(themesDir, file)
18
+ break
19
+ }
20
+ }
21
+
22
+ if (!themePath || !existsSync(themePath)) return null
23
+
24
+ try {
25
+ const fileUrl = "file:///" + themePath.replace(/\\/g, "/")
26
+ // Run evaluation in a separate Node process with the tsx loader.
27
+ // This bypasses Vite's ModuleRunner restrictions inside Astro config setups.
28
+ // We pass the loader script via stdin to avoid any command line escaping issues.
29
+ const input = `import('${fileUrl}').then(m => console.log(JSON.stringify(m.default))).catch(err => { console.error(err); process.exit(1); })`
30
+ const output = execSync(`"${process.execPath}" --import tsx -`, {
31
+ input,
32
+ encoding: "utf-8",
33
+ stdio: ["pipe", "pipe", "ignore"]
34
+ })
35
+
36
+ const themeObj = JSON.parse(output.trim())
37
+ if (!themeObj) return null
38
+
39
+ return {
40
+ slots: Array.isArray(themeObj.slots) ? themeObj.slots : [],
41
+ base: typeof themeObj.base === "object" && themeObj.base !== null ? themeObj.base : {},
42
+ variants:
43
+ typeof themeObj.variants === "object" && themeObj.variants !== null
44
+ ? themeObj.variants
45
+ : {},
46
+ compoundVariants: Array.isArray(themeObj.compoundVariants) ? themeObj.compoundVariants : [],
47
+ defaultVariants:
48
+ typeof themeObj.defaultVariants === "object" && themeObj.defaultVariants !== null
49
+ ? themeObj.defaultVariants
50
+ : {}
51
+ }
52
+ } catch (err) {
53
+ console.warn(`[docs] Failed to extract theme via child process for ${componentName}:`, err)
54
+ return null
55
+ }
56
+ }
@@ -1,6 +1,7 @@
1
- import { readFileSync } from "node:fs"
1
+ import { readFileSync, existsSync, statSync } from "node:fs"
2
+ import path from "node:path"
2
3
  import ts from "typescript"
3
- import type { PropMeta, SlotMeta, EmitMeta } from "./types"
4
+ import type { PropMeta, SlotMeta, EmitMeta } from "../types"
4
5
 
5
6
  function extractScriptBlock(content: string): string | null {
6
7
  const match =
@@ -73,7 +74,7 @@ function getTypeRefName(typeArg: ts.TypeNode): string | null {
73
74
  return null
74
75
  }
75
76
 
76
- export function extractVueMetaDirect(filePath: string): {
77
+ export function extractVueMeta(filePath: string): {
77
78
  props: PropMeta[]
78
79
  slots: SlotMeta[]
79
80
  emits: EmitMeta[]
@@ -94,13 +95,31 @@ export function extractVueMetaDirect(filePath: string): {
94
95
  ts.ScriptKind.TS
95
96
  )
96
97
 
97
- const interfaces = new Map<string, ts.InterfaceDeclaration>()
98
+ const interfaces = new Map<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>()
98
99
  const destructuredDefaults: Record<string, string> = {}
100
+ const imports = new Map<string, string>()
99
101
 
100
102
  function collectDeclarations(node: ts.Node) {
101
- if (ts.isInterfaceDeclaration(node)) {
103
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
102
104
  interfaces.set(node.name.text, node)
103
105
  }
106
+ if (ts.isImportDeclaration(node)) {
107
+ const moduleSpecifier = node.moduleSpecifier
108
+ if (ts.isStringLiteral(moduleSpecifier)) {
109
+ const importPath = moduleSpecifier.text
110
+ const importClause = node.importClause
111
+ if (importClause) {
112
+ if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
113
+ for (const element of importClause.namedBindings.elements) {
114
+ imports.set(element.name.text, importPath)
115
+ }
116
+ }
117
+ if (importClause.name) {
118
+ imports.set(importClause.name.text, importPath)
119
+ }
120
+ }
121
+ }
122
+ }
104
123
  if (ts.isVariableStatement(node)) {
105
124
  for (const decl of node.declarationList.declarations) {
106
125
  if (
@@ -138,9 +157,59 @@ export function extractVueMetaDirect(filePath: string): {
138
157
  const typeArg = node.typeArguments[0]!
139
158
  const refName = getTypeRefName(typeArg)
140
159
  if (refName) {
141
- const iface = interfaces.get(refName)
160
+ let iface = interfaces.get(refName)
161
+ if (!iface && imports.has(refName)) {
162
+ const relPath = imports.get(refName)!
163
+ const baseDir = path.dirname(filePath)
164
+ const absolutePath = path.resolve(baseDir, relPath)
165
+ const extensions = [".ts", ".d.ts", "/index.ts", "/index.d.ts"]
166
+ let foundPath = ""
167
+ if (existsSync(absolutePath) && statSync(absolutePath).isFile()) {
168
+ foundPath = absolutePath
169
+ } else {
170
+ for (const ext of extensions) {
171
+ const testPath = absolutePath + ext
172
+ if (existsSync(testPath)) {
173
+ foundPath = testPath
174
+ break
175
+ }
176
+ }
177
+ }
178
+ if (foundPath) {
179
+ try {
180
+ const importContent = readFileSync(foundPath, "utf-8")
181
+ const importSourceFile = ts.createSourceFile(
182
+ "import.ts",
183
+ importContent,
184
+ ts.ScriptTarget.Latest,
185
+ true,
186
+ ts.ScriptKind.TS
187
+ )
188
+ let foundNode: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | null = null
189
+ function findDecl(n: ts.Node) {
190
+ if (
191
+ (ts.isInterfaceDeclaration(n) || ts.isTypeAliasDeclaration(n)) &&
192
+ n.name.text === refName
193
+ ) {
194
+ foundNode = n
195
+ }
196
+ ts.forEachChild(n, findDecl)
197
+ }
198
+ findDecl(importSourceFile)
199
+ if (foundNode) {
200
+ iface = foundNode
201
+ }
202
+ } catch {}
203
+ }
204
+ }
205
+
142
206
  if (iface) {
143
- const extracted = extractPropsFromInterface(iface)
207
+ let extracted: PropMeta[] = []
208
+ if (ts.isInterfaceDeclaration(iface)) {
209
+ extracted = extractPropsFromInterface(iface)
210
+ } else if (ts.isTypeAliasDeclaration(iface) && ts.isTypeLiteralNode(iface.type)) {
211
+ extracted = extractPropsFromTypeLiteral(iface.type)
212
+ }
144
213
  for (const p of extracted) {
145
214
  const d = destructuredDefaults[p.name]
146
215
  if (d !== undefined) p.default = d
@@ -177,9 +246,63 @@ export function extractVueMetaDirect(filePath: string): {
177
246
  const typeArg = callArg.typeArguments[0]!
178
247
  const refName = getTypeRefName(typeArg)
179
248
  if (refName) {
180
- const iface = interfaces.get(refName)
249
+ let iface = interfaces.get(refName)
250
+ if (!iface && imports.has(refName)) {
251
+ const relPath = imports.get(refName)!
252
+ const baseDir = path.dirname(filePath)
253
+ const absolutePath = path.resolve(baseDir, relPath)
254
+ const extensions = [".ts", ".d.ts", "/index.ts", "/index.d.ts"]
255
+ let foundPath = ""
256
+ if (existsSync(absolutePath) && statSync(absolutePath).isFile()) {
257
+ foundPath = absolutePath
258
+ } else {
259
+ for (const ext of extensions) {
260
+ const testPath = absolutePath + ext
261
+ if (existsSync(testPath)) {
262
+ foundPath = testPath
263
+ break
264
+ }
265
+ }
266
+ }
267
+ if (foundPath) {
268
+ try {
269
+ const importContent = readFileSync(foundPath, "utf-8")
270
+ const importSourceFile = ts.createSourceFile(
271
+ "import.ts",
272
+ importContent,
273
+ ts.ScriptTarget.Latest,
274
+ true,
275
+ ts.ScriptKind.TS
276
+ )
277
+ let foundNode: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | null =
278
+ null
279
+ function findDecl(n: ts.Node) {
280
+ if (
281
+ (ts.isInterfaceDeclaration(n) || ts.isTypeAliasDeclaration(n)) &&
282
+ n.name.text === refName
283
+ ) {
284
+ foundNode = n
285
+ }
286
+ ts.forEachChild(n, findDecl)
287
+ }
288
+ findDecl(importSourceFile)
289
+ if (foundNode) {
290
+ iface = foundNode
291
+ }
292
+ } catch {}
293
+ }
294
+ }
295
+
181
296
  if (iface) {
182
- const baseProps = extractPropsFromInterface(iface)
297
+ let baseProps: PropMeta[] = []
298
+ if (ts.isInterfaceDeclaration(iface)) {
299
+ baseProps = extractPropsFromInterface(iface)
300
+ } else if (
301
+ ts.isTypeAliasDeclaration(iface) &&
302
+ ts.isTypeLiteralNode(iface.type)
303
+ ) {
304
+ baseProps = extractPropsFromTypeLiteral(iface.type)
305
+ }
183
306
  for (const p of baseProps) {
184
307
  const d = defaults[p.name]
185
308
  if (d !== undefined) p.default = d
@@ -203,14 +326,40 @@ export function extractVueMetaDirect(filePath: string): {
203
326
  const typeArg = node.typeArguments?.[0]
204
327
  if (typeArg && ts.isTypeLiteralNode(typeArg)) {
205
328
  for (const member of typeArg.members) {
206
- if (ts.isMethodSignature(member) && member.name && ts.isIdentifier(member.name)) {
329
+ if (
330
+ (ts.isMethodSignature(member) || ts.isPropertySignature(member)) &&
331
+ member.name &&
332
+ ts.isIdentifier(member.name)
333
+ ) {
207
334
  const slotName = member.name.text
208
335
  const bindings: Record<string, string> = {}
209
- if (member.parameters.length > 0) {
210
- const param = member.parameters[0]
211
- if (param?.type) {
212
- const typeText = param.type.getText()
213
- bindings["props"] = typeText
336
+
337
+ let params: readonly ts.ParameterDeclaration[] = []
338
+ if (ts.isMethodSignature(member)) {
339
+ params = member.parameters
340
+ } else if (
341
+ ts.isPropertySignature(member) &&
342
+ member.type &&
343
+ ts.isFunctionTypeNode(member.type)
344
+ ) {
345
+ params = member.type.parameters
346
+ }
347
+
348
+ if (params.length > 0) {
349
+ const param = params[0]
350
+ if (param?.type && ts.isTypeLiteralNode(param.type)) {
351
+ for (const pm of param.type.members) {
352
+ if (
353
+ ts.isPropertySignature(pm) &&
354
+ pm.name &&
355
+ ts.isIdentifier(pm.name) &&
356
+ pm.type
357
+ ) {
358
+ bindings[pm.name.text] = pm.type.getText()
359
+ }
360
+ }
361
+ } else if (param?.type) {
362
+ bindings["props"] = param.type.getText()
214
363
  }
215
364
  }
216
365
  slots.push({ name: slotName, bindings, description: "" })
@@ -22,7 +22,6 @@ export function filterProps(
22
22
 
23
23
  export function generateThemeCode(componentName: string, theme: ThemeMeta): string {
24
24
  const componentConfig: Record<string, unknown> = {
25
- ...(theme.slots.length ? { slots: theme.slots } : {}),
26
25
  ...(Object.keys(theme.base).length ? { base: theme.base } : {}),
27
26
  ...(Object.keys(theme.variants).length ? { variants: theme.variants } : {}),
28
27
  ...(theme.compoundVariants.length ? { compoundVariants: theme.compoundVariants } : {}),
@@ -36,7 +35,7 @@ import { defineConfig } from "astro/config"
36
35
  export default defineConfig({
37
36
  integrations: [
38
37
  ui({
39
- components: {
38
+ ui: {
40
39
  ${componentName}: ${JSON.stringify(componentConfig, null, 6).replace(/\n/g, "\n ")}
41
40
  }
42
41
  })
package/src/env.d.ts CHANGED
@@ -17,18 +17,6 @@ declare module "virtual:rimelight-ui-docs-meta" {
17
17
  export default meta
18
18
  }
19
19
 
20
- declare module "turndown" {
21
- class TurndownService {
22
- constructor(options?: Record<string, unknown>)
23
- turndown(html: Node | string): string
24
- use(plugin: () => void): void
25
- }
26
- export default TurndownService
27
- }
28
-
29
- declare module "turndown-plugin-gfm" {
30
- export function gfm(): void
31
- }
32
20
 
33
21
  declare namespace App {
34
22
  interface Locals {