components-svelte 0.1.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 (242) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/components/Search.svelte +320 -0
  4. package/dist/components/Search.svelte.d.ts +31 -0
  5. package/dist/components/SearchButton.svelte +82 -0
  6. package/dist/components/SearchButton.svelte.d.ts +13 -0
  7. package/dist/components/SearchProvider.svelte +87 -0
  8. package/dist/components/SearchProvider.svelte.d.ts +17 -0
  9. package/dist/components/accordion/Accordion.svelte +150 -0
  10. package/dist/components/accordion/Accordion.svelte.d.ts +25 -0
  11. package/dist/components/accordion/AccordionCover.svelte +68 -0
  12. package/dist/components/accordion/AccordionCover.svelte.d.ts +13 -0
  13. package/dist/components/accordion/AccordionGroup.svelte +23 -0
  14. package/dist/components/accordion/AccordionGroup.svelte.d.ts +8 -0
  15. package/dist/components/accordion/accordion-url-utils.d.ts +4 -0
  16. package/dist/components/accordion/accordion-url-utils.js +48 -0
  17. package/dist/components/accordion/index.d.ts +29 -0
  18. package/dist/components/accordion/index.js +6 -0
  19. package/dist/components/badge/Badge.svelte +152 -0
  20. package/dist/components/badge/Badge.svelte.d.ts +23 -0
  21. package/dist/components/badge/index.d.ts +1 -0
  22. package/dist/components/badge/index.js +2 -0
  23. package/dist/components/callout/Callout.svelte +317 -0
  24. package/dist/components/callout/Callout.svelte.d.ts +17 -0
  25. package/dist/components/callout/Check.svelte +9 -0
  26. package/dist/components/callout/Check.svelte.d.ts +4 -0
  27. package/dist/components/callout/Danger.svelte +9 -0
  28. package/dist/components/callout/Danger.svelte.d.ts +4 -0
  29. package/dist/components/callout/Info.svelte +9 -0
  30. package/dist/components/callout/Info.svelte.d.ts +4 -0
  31. package/dist/components/callout/Note.svelte +9 -0
  32. package/dist/components/callout/Note.svelte.d.ts +4 -0
  33. package/dist/components/callout/Tip.svelte +9 -0
  34. package/dist/components/callout/Tip.svelte.d.ts +4 -0
  35. package/dist/components/callout/Warning.svelte +9 -0
  36. package/dist/components/callout/Warning.svelte.d.ts +4 -0
  37. package/dist/components/callout/index.d.ts +8 -0
  38. package/dist/components/callout/index.js +8 -0
  39. package/dist/components/card/Card.svelte +172 -0
  40. package/dist/components/card/Card.svelte.d.ts +21 -0
  41. package/dist/components/card/index.d.ts +3 -0
  42. package/dist/components/card/index.js +2 -0
  43. package/dist/components/code-block/BaseCodeBlock.svelte +249 -0
  44. package/dist/components/code-block/BaseCodeBlock.svelte.d.ts +10 -0
  45. package/dist/components/code-block/CodeBlock.svelte +185 -0
  46. package/dist/components/code-block/CodeBlock.svelte.d.ts +5 -0
  47. package/dist/components/code-block/CodeFooter.svelte +34 -0
  48. package/dist/components/code-block/CodeFooter.svelte.d.ts +8 -0
  49. package/dist/components/code-block/CodeHeader.svelte +52 -0
  50. package/dist/components/code-block/CodeHeader.svelte.d.ts +11 -0
  51. package/dist/components/code-block/CopyToClipboardButton.svelte +124 -0
  52. package/dist/components/code-block/CopyToClipboardButton.svelte.d.ts +4 -0
  53. package/dist/components/code-block/index.d.ts +10 -0
  54. package/dist/components/code-block/index.js +5 -0
  55. package/dist/components/code-block/types.d.ts +85 -0
  56. package/dist/components/code-block/types.js +1 -0
  57. package/dist/components/code-group/CodeGroup.svelte +327 -0
  58. package/dist/components/code-group/CodeGroup.svelte.d.ts +24 -0
  59. package/dist/components/code-group/CodeGroupSelect.svelte +170 -0
  60. package/dist/components/code-group/CodeGroupSelect.svelte.d.ts +25 -0
  61. package/dist/components/code-group/CodeSelectDropdown.svelte +74 -0
  62. package/dist/components/code-group/CodeSelectDropdown.svelte.d.ts +9 -0
  63. package/dist/components/code-group/LanguageDropdown.svelte +134 -0
  64. package/dist/components/code-group/LanguageDropdown.svelte.d.ts +11 -0
  65. package/dist/components/code-group/LanguageIcon.svelte +24 -0
  66. package/dist/components/code-group/LanguageIcon.svelte.d.ts +8 -0
  67. package/dist/components/code-group/context.d.ts +14 -0
  68. package/dist/components/code-group/context.js +2 -0
  69. package/dist/components/code-group/index.d.ts +8 -0
  70. package/dist/components/code-group/index.js +4 -0
  71. package/dist/components/color/Color.svelte +34 -0
  72. package/dist/components/color/Color.svelte.d.ts +10 -0
  73. package/dist/components/color/ColorItem.svelte +140 -0
  74. package/dist/components/color/ColorItem.svelte.d.ts +10 -0
  75. package/dist/components/color/ColorRow.svelte +27 -0
  76. package/dist/components/color/ColorRow.svelte.d.ts +8 -0
  77. package/dist/components/color/index.d.ts +1 -0
  78. package/dist/components/color/index.js +8 -0
  79. package/dist/components/columns/Box.svelte +15 -0
  80. package/dist/components/columns/Box.svelte.d.ts +7 -0
  81. package/dist/components/columns/Columns.svelte +31 -0
  82. package/dist/components/columns/Columns.svelte.d.ts +10 -0
  83. package/dist/components/columns/constants.d.ts +5 -0
  84. package/dist/components/columns/constants.js +3 -0
  85. package/dist/components/columns/index.d.ts +5 -0
  86. package/dist/components/columns/index.js +3 -0
  87. package/dist/components/expandable/Expandable.svelte +182 -0
  88. package/dist/components/expandable/Expandable.svelte.d.ts +22 -0
  89. package/dist/components/expandable/index.d.ts +3 -0
  90. package/dist/components/expandable/index.js +2 -0
  91. package/dist/components/frame/Frame.svelte +118 -0
  92. package/dist/components/frame/Frame.svelte.d.ts +15 -0
  93. package/dist/components/frame/index.d.ts +3 -0
  94. package/dist/components/frame/index.js +2 -0
  95. package/dist/components/icon/Icon.svelte +129 -0
  96. package/dist/components/icon/Icon.svelte.d.ts +18 -0
  97. package/dist/components/icon/index.d.ts +2 -0
  98. package/dist/components/icon/index.js +1 -0
  99. package/dist/components/index.d.ts +22 -0
  100. package/dist/components/index.js +22 -0
  101. package/dist/components/mermaid/Mermaid.svelte +181 -0
  102. package/dist/components/mermaid/Mermaid.svelte.d.ts +11 -0
  103. package/dist/components/mermaid/ZoomControls.svelte +74 -0
  104. package/dist/components/mermaid/ZoomControls.svelte.d.ts +12 -0
  105. package/dist/components/mermaid/index.d.ts +7 -0
  106. package/dist/components/mermaid/index.js +4 -0
  107. package/dist/components/mermaid/use-pan-zoom.svelte.d.ts +11 -0
  108. package/dist/components/mermaid/use-pan-zoom.svelte.js +41 -0
  109. package/dist/components/panel/Panel.svelte +27 -0
  110. package/dist/components/panel/Panel.svelte.d.ts +9 -0
  111. package/dist/components/panel/index.d.ts +3 -0
  112. package/dist/components/panel/index.js +2 -0
  113. package/dist/components/property/DeprecatedPill.svelte +16 -0
  114. package/dist/components/property/DeprecatedPill.svelte.d.ts +6 -0
  115. package/dist/components/property/InfoPill.svelte +32 -0
  116. package/dist/components/property/InfoPill.svelte.d.ts +10 -0
  117. package/dist/components/property/ParamHead.svelte +188 -0
  118. package/dist/components/property/ParamHead.svelte.d.ts +22 -0
  119. package/dist/components/property/Property.svelte +123 -0
  120. package/dist/components/property/Property.svelte.d.ts +24 -0
  121. package/dist/components/property/RequiredPill.svelte +16 -0
  122. package/dist/components/property/RequiredPill.svelte.d.ts +6 -0
  123. package/dist/components/property/index.d.ts +11 -0
  124. package/dist/components/property/index.js +6 -0
  125. package/dist/components/search/index.d.ts +7 -0
  126. package/dist/components/search/index.js +4 -0
  127. package/dist/components/search/use-search.d.ts +9 -0
  128. package/dist/components/search/use-search.js +6 -0
  129. package/dist/components/steps/Step.svelte +314 -0
  130. package/dist/components/steps/Step.svelte.d.ts +26 -0
  131. package/dist/components/steps/Steps.svelte +51 -0
  132. package/dist/components/steps/Steps.svelte.d.ts +11 -0
  133. package/dist/components/steps/constants.d.ts +4 -0
  134. package/dist/components/steps/constants.js +2 -0
  135. package/dist/components/steps/context.d.ts +10 -0
  136. package/dist/components/steps/context.js +2 -0
  137. package/dist/components/steps/index.d.ts +32 -0
  138. package/dist/components/steps/index.js +6 -0
  139. package/dist/components/tabs/Tabs.svelte +254 -0
  140. package/dist/components/tabs/Tabs.svelte.d.ts +16 -0
  141. package/dist/components/tabs/TabsItem.svelte +56 -0
  142. package/dist/components/tabs/TabsItem.svelte.d.ts +15 -0
  143. package/dist/components/tabs/context.d.ts +20 -0
  144. package/dist/components/tabs/context.js +2 -0
  145. package/dist/components/tabs/index.d.ts +27 -0
  146. package/dist/components/tabs/index.js +5 -0
  147. package/dist/components/tile/Tile.svelte +124 -0
  148. package/dist/components/tile/Tile.svelte.d.ts +12 -0
  149. package/dist/components/tile/index.d.ts +2 -0
  150. package/dist/components/tile/index.js +1 -0
  151. package/dist/components/tooltip/Tooltip.svelte +170 -0
  152. package/dist/components/tooltip/Tooltip.svelte.d.ts +23 -0
  153. package/dist/components/tooltip/index.d.ts +2 -0
  154. package/dist/components/tooltip/index.js +1 -0
  155. package/dist/components/tree/Tree.svelte +226 -0
  156. package/dist/components/tree/Tree.svelte.d.ts +9 -0
  157. package/dist/components/tree/TreeFile.svelte +44 -0
  158. package/dist/components/tree/TreeFile.svelte.d.ts +6 -0
  159. package/dist/components/tree/TreeFolder.svelte +107 -0
  160. package/dist/components/tree/TreeFolder.svelte.d.ts +10 -0
  161. package/dist/components/tree/context.d.ts +3 -0
  162. package/dist/components/tree/context.js +7 -0
  163. package/dist/components/tree/index.d.ts +10 -0
  164. package/dist/components/tree/index.js +9 -0
  165. package/dist/components/tree/tree-level-context.d.ts +8 -0
  166. package/dist/components/tree/tree-level-context.js +7 -0
  167. package/dist/components/tree/use-tree-level.d.ts +3 -0
  168. package/dist/components/tree/use-tree-level.js +5 -0
  169. package/dist/components/tree/utils.d.ts +9 -0
  170. package/dist/components/tree/utils.js +86 -0
  171. package/dist/components/update/Update.svelte +210 -0
  172. package/dist/components/update/Update.svelte.d.ts +34 -0
  173. package/dist/components/update/index.d.ts +4 -0
  174. package/dist/components/update/index.js +3 -0
  175. package/dist/components/view/View.svelte +52 -0
  176. package/dist/components/view/View.svelte.d.ts +14 -0
  177. package/dist/components/view/index.d.ts +3 -0
  178. package/dist/components/view/index.js +2 -0
  179. package/dist/constants/brands.d.ts +2 -0
  180. package/dist/constants/brands.js +4 -0
  181. package/dist/constants/index.d.ts +6 -0
  182. package/dist/constants/index.js +6 -0
  183. package/dist/constants/selectors.d.ts +125 -0
  184. package/dist/constants/selectors.js +125 -0
  185. package/dist/hooks/index.d.ts +8 -0
  186. package/dist/hooks/index.js +6 -0
  187. package/dist/hooks/use-expandable-memory.svelte.d.ts +6 -0
  188. package/dist/hooks/use-expandable-memory.svelte.js +89 -0
  189. package/dist/hooks/use-expandable.svelte.d.ts +15 -0
  190. package/dist/hooks/use-expandable.svelte.js +53 -0
  191. package/dist/hooks/use-get-shiki-highlighted-html.svelte.d.ts +8 -0
  192. package/dist/hooks/use-get-shiki-highlighted-html.svelte.js +36 -0
  193. package/dist/hooks/use-has-hover.d.ts +2 -0
  194. package/dist/hooks/use-has-hover.js +10 -0
  195. package/dist/hooks/use-is-dark-theme.svelte.d.ts +3 -0
  196. package/dist/hooks/use-is-dark-theme.svelte.js +25 -0
  197. package/dist/hooks/use-isomorphic-layout-effect.d.ts +3 -0
  198. package/dist/hooks/use-isomorphic-layout-effect.js +6 -0
  199. package/dist/index.d.ts +2 -0
  200. package/dist/index.js +2 -0
  201. package/dist/styles.css +2 -0
  202. package/dist/utils/cn.d.ts +3 -0
  203. package/dist/utils/cn.js +7 -0
  204. package/dist/utils/copy-to-clipboard.d.ts +4 -0
  205. package/dist/utils/copy-to-clipboard.js +50 -0
  206. package/dist/utils/dedent.d.ts +1 -0
  207. package/dist/utils/dedent.js +125 -0
  208. package/dist/utils/enhance-video-props.d.ts +4 -0
  209. package/dist/utils/enhance-video-props.js +25 -0
  210. package/dist/utils/get-node-text.d.ts +2 -0
  211. package/dist/utils/get-node-text.js +26 -0
  212. package/dist/utils/icon-utils.d.ts +18 -0
  213. package/dist/utils/icon-utils.js +30 -0
  214. package/dist/utils/is-absolute-url.d.ts +4 -0
  215. package/dist/utils/is-absolute-url.js +11 -0
  216. package/dist/utils/is-remote-url.d.ts +2 -0
  217. package/dist/utils/is-remote-url.js +12 -0
  218. package/dist/utils/make-marker-ids-unique.d.ts +2 -0
  219. package/dist/utils/make-marker-ids-unique.js +36 -0
  220. package/dist/utils/shiki/code-styling.d.ts +13 -0
  221. package/dist/utils/shiki/code-styling.js +1 -0
  222. package/dist/utils/shiki/constants.d.ts +21 -0
  223. package/dist/utils/shiki/constants.js +116 -0
  224. package/dist/utils/shiki/get-code-styling.d.ts +9 -0
  225. package/dist/utils/shiki/get-code-styling.js +38 -0
  226. package/dist/utils/shiki/get-shiki-background-colors.d.ts +20 -0
  227. package/dist/utils/shiki/get-shiki-background-colors.js +56 -0
  228. package/dist/utils/shiki/index.d.ts +30 -0
  229. package/dist/utils/shiki/index.js +209 -0
  230. package/dist/utils/shiki/lib.d.ts +4 -0
  231. package/dist/utils/shiki/lib.js +49 -0
  232. package/dist/utils/shiki/shiki-themes.d.ts +2 -0
  233. package/dist/utils/shiki/shiki-themes.js +64 -0
  234. package/dist/utils/shiki/snippet-presets.d.ts +27 -0
  235. package/dist/utils/shiki/snippet-presets.js +44 -0
  236. package/dist/utils/shiki/worker-client.d.ts +9 -0
  237. package/dist/utils/shiki/worker-client.js +22 -0
  238. package/dist/utils/shiki/worker.d.ts +1 -0
  239. package/dist/utils/shiki/worker.js +134 -0
  240. package/dist/utils/slugify.d.ts +2 -0
  241. package/dist/utils/slugify.js +9 -0
  242. package/package.json +100 -0
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ import { ChevronRight } from "@lucide/svelte";
3
+ import { Tooltip as TooltipPrimitive } from "bits-ui";
4
+ import type { Snippet } from "svelte";
5
+ import { Classes } from "../../constants/selectors";
6
+ import { cn } from "../../utils/cn";
7
+ import { isRemoteUrl } from "../../utils/is-remote-url";
8
+
9
+ type TooltipSide = "top" | "right" | "bottom" | "left";
10
+ type TooltipAlign = "start" | "center" | "end";
11
+
12
+ export type TooltipProps = {
13
+ description?: string;
14
+ children?: string | Snippet<[Record<string, unknown>?]>;
15
+ title?: string;
16
+ cta?: string;
17
+ href?: string;
18
+ class?: string;
19
+ className?: string;
20
+ side?: TooltipSide;
21
+ align?: TooltipAlign;
22
+ /**
23
+ * Svelte snippets do not expose the element type at runtime. Set this when
24
+ * the child is already an interactive element so Tooltip can render it as
25
+ * the Bits UI trigger, matching React's render-as-child behavior.
26
+ */
27
+ interactive?: boolean;
28
+ };
29
+
30
+ let {
31
+ description,
32
+ children,
33
+ title,
34
+ cta,
35
+ href,
36
+ class: classAttribute,
37
+ className,
38
+ side = "top",
39
+ align = "center",
40
+ interactive = false,
41
+ }: TooltipProps = $props();
42
+
43
+ let open = $state(false);
44
+ const hasHover = getHasHover();
45
+ let triggerClassName = $derived(className ?? classAttribute);
46
+ let ariaLabel = $derived.by(() => {
47
+ if (interactive) {
48
+ return undefined;
49
+ }
50
+
51
+ if (title && description) {
52
+ return `${title}: ${description}`;
53
+ }
54
+
55
+ return title || description;
56
+ });
57
+ let triggerClass = $derived(
58
+ cn(
59
+ triggerClassName,
60
+ !interactive &&
61
+ "rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
62
+ )
63
+ );
64
+
65
+ function getHasHover(): boolean {
66
+ try {
67
+ return matchMedia("(hover: hover)").matches;
68
+ } catch {
69
+ // assume that if the browser is too old to support matchMedia it's likely not a touch device
70
+ return true;
71
+ }
72
+ }
73
+
74
+ const handleClick = hasHover
75
+ ? undefined
76
+ : () => {
77
+ open = !open;
78
+ };
79
+
80
+ function handleOpenChange(nextOpen: boolean) {
81
+ // Base UI provides the reason separately; Bits UI exposes the resulting state.
82
+ // Keeping the controlled state synchronized preserves hover, Escape, and outside-press behavior.
83
+ open = nextOpen;
84
+ }
85
+ </script>
86
+
87
+ {#if children != null}
88
+ <TooltipPrimitive.Provider delayDuration={0}>
89
+ <TooltipPrimitive.Root
90
+ bind:open
91
+ disableCloseOnTriggerClick={!hasHover}
92
+ onOpenChange={handleOpenChange}
93
+ >
94
+ {#if interactive}
95
+ <TooltipPrimitive.Trigger
96
+ aria-label={ariaLabel}
97
+ class={triggerClass}
98
+ onclick={handleClick}
99
+ >
100
+ {#snippet child({ props })}
101
+ {#if typeof children === "string"}
102
+ <span {...props}>{children}</span>
103
+ {:else}
104
+ {@render children(props)}
105
+ {/if}
106
+ {/snippet}
107
+ </TooltipPrimitive.Trigger>
108
+ {:else}
109
+ <TooltipPrimitive.Trigger
110
+ aria-label={ariaLabel}
111
+ class={triggerClass}
112
+ onclick={handleClick}
113
+ >
114
+ {#if typeof children === "string"}
115
+ <span
116
+ class={cn(
117
+ Classes.Tooltip,
118
+ "underline decoration-2 decoration-stone-400 decoration-dotted underline-offset-4 dark:decoration-stone-500"
119
+ )}
120
+ >
121
+ {children}
122
+ </span>
123
+ {:else}
124
+ {@render children()}
125
+ {/if}
126
+ </TooltipPrimitive.Trigger>
127
+ {/if}
128
+
129
+ <TooltipPrimitive.Portal>
130
+ <TooltipPrimitive.Content
131
+ align={align}
132
+ collisionPadding={8}
133
+ data-component-part="tooltip-content"
134
+ side={side}
135
+ sideOffset={4}
136
+ class="z-50 flex max-w-[16rem] flex-col gap-1 rounded-xl border border-stone-200 bg-white px-4 py-3 shadow-[0_10px_16px_-3px_rgb(10_10_10/0.05),0_3px_10px_-2px_rgb(10_10_10/0.02)] dark:border-stone-900 dark:bg-stone-950"
137
+ >
138
+ {#if title}
139
+ <p
140
+ class="font-medium text-stone-900 text-xs leading-4 dark:text-stone-200"
141
+ data-component-part="tooltip-title"
142
+ >
143
+ {title}
144
+ </p>
145
+ {/if}
146
+ {#if description}
147
+ <p
148
+ class="text-stone-600 text-xs leading-4 dark:text-stone-400"
149
+ data-component-part="tooltip-description"
150
+ >
151
+ {description}
152
+ </p>
153
+ {/if}
154
+ {#if cta && href}
155
+ <a
156
+ class="mt-2! flex items-center gap-0.5 rounded-sm font-medium text-stone-600 text-xs leading-4 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 dark:text-stone-400 dark:hover:text-primary-light"
157
+ data-component-part="tooltip-cta"
158
+ href={href}
159
+ rel={isRemoteUrl(href) ? "noreferrer" : undefined}
160
+ target={isRemoteUrl(href) ? "_blank" : undefined}
161
+ >
162
+ {cta}
163
+ <ChevronRight aria-hidden="true" class="size-3" strokeWidth={2.5} />
164
+ </a>
165
+ {/if}
166
+ </TooltipPrimitive.Content>
167
+ </TooltipPrimitive.Portal>
168
+ </TooltipPrimitive.Root>
169
+ </TooltipPrimitive.Provider>
170
+ {/if}
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from "svelte";
2
+ type TooltipSide = "top" | "right" | "bottom" | "left";
3
+ type TooltipAlign = "start" | "center" | "end";
4
+ export type TooltipProps = {
5
+ description?: string;
6
+ children?: string | Snippet<[Record<string, unknown>?]>;
7
+ title?: string;
8
+ cta?: string;
9
+ href?: string;
10
+ class?: string;
11
+ className?: string;
12
+ side?: TooltipSide;
13
+ align?: TooltipAlign;
14
+ /**
15
+ * Svelte snippets do not expose the element type at runtime. Set this when
16
+ * the child is already an interactive element so Tooltip can render it as
17
+ * the Bits UI trigger, matching React's render-as-child behavior.
18
+ */
19
+ interactive?: boolean;
20
+ };
21
+ declare const Tooltip: import("svelte").Component<TooltipProps, {}, "">;
22
+ type Tooltip = ReturnType<typeof Tooltip>;
23
+ export default Tooltip;
@@ -0,0 +1,2 @@
1
+ export type { TooltipProps } from "./Tooltip.svelte";
2
+ export { default as Tooltip } from "./Tooltip.svelte";
@@ -0,0 +1 @@
1
+ export { default as Tooltip } from "./Tooltip.svelte";
@@ -0,0 +1,226 @@
1
+ <script lang="ts">
2
+ // biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: TODO
3
+ import { onMount } from "svelte";
4
+ import type { Snippet } from "svelte";
5
+
6
+ import { Classes } from "../../constants/selectors";
7
+ import { cn } from "../../utils/cn";
8
+
9
+ import { setTreeLevelContext } from "./context";
10
+ import {
11
+ getFirstChildTreeItem,
12
+ getParentTreeItem,
13
+ getSiblingFolders,
14
+ getTreeItemLabel,
15
+ getVisibleTreeItems,
16
+ updateRovingTabindex,
17
+ } from "./utils";
18
+
19
+ const TYPE_AHEAD_TIMEOUT_MS = 500;
20
+
21
+ export type TreeProps = {
22
+ class?: string;
23
+ className?: string;
24
+ children?: Snippet;
25
+ };
26
+
27
+ let {
28
+ class: classAttribute,
29
+ className,
30
+ children,
31
+ }: TreeProps = $props();
32
+
33
+ let treeElement: HTMLDivElement | null = $state(null);
34
+ let typeAheadBuffer = "";
35
+ let typeAheadTimeout: ReturnType<typeof setTimeout> | null = null;
36
+
37
+ // Svelte setContext replaces the React TreeLevelProvider around the root children.
38
+ setTreeLevelContext({ level: 1, parentId: null });
39
+
40
+ onMount(() => {
41
+ if (!treeElement) {
42
+ return;
43
+ }
44
+
45
+ const existingFocusable = treeElement.querySelector<HTMLElement>(
46
+ '[role="treeitem"][tabindex="0"]'
47
+ );
48
+
49
+ if (!existingFocusable) {
50
+ const firstItem = treeElement.querySelector<HTMLElement>('[role="treeitem"]');
51
+ if (firstItem) {
52
+ updateRovingTabindex(treeElement, firstItem);
53
+ }
54
+ }
55
+
56
+ return () => {
57
+ if (typeAheadTimeout) {
58
+ clearTimeout(typeAheadTimeout);
59
+ }
60
+ };
61
+ });
62
+
63
+ function focusItem(item: HTMLElement | null | undefined) {
64
+ if (!(item && treeElement)) {
65
+ return;
66
+ }
67
+
68
+ updateRovingTabindex(treeElement, item);
69
+ item.focus();
70
+ }
71
+
72
+ function handleFocus(event: FocusEvent) {
73
+ const target = event.target;
74
+
75
+ if (
76
+ !(target instanceof HTMLElement) ||
77
+ target.getAttribute("role") !== "treeitem"
78
+ ) {
79
+ return;
80
+ }
81
+
82
+ if (treeElement) {
83
+ updateRovingTabindex(treeElement, target);
84
+ }
85
+ }
86
+
87
+ function handleTreeKeyDown(event: KeyboardEvent) {
88
+ const container = treeElement;
89
+ const target = event.target;
90
+
91
+ if (
92
+ !(container && target instanceof HTMLElement) ||
93
+ target.getAttribute("role") !== "treeitem"
94
+ ) {
95
+ return;
96
+ }
97
+
98
+ const visibleItems = getVisibleTreeItems(container);
99
+ const currentIndex = visibleItems.indexOf(target);
100
+ const isFolder = target.getAttribute("aria-expanded") !== null;
101
+ const isExpanded = target.getAttribute("aria-expanded") === "true";
102
+
103
+ switch (event.key) {
104
+ case "ArrowDown": {
105
+ event.preventDefault();
106
+ focusItem(visibleItems[currentIndex + 1]);
107
+ break;
108
+ }
109
+
110
+ case "ArrowUp": {
111
+ event.preventDefault();
112
+ focusItem(visibleItems[currentIndex - 1]);
113
+ break;
114
+ }
115
+
116
+ case "ArrowRight": {
117
+ event.preventDefault();
118
+ if (isFolder) {
119
+ if (isExpanded) {
120
+ focusItem(getFirstChildTreeItem(target));
121
+ } else {
122
+ target.click();
123
+ }
124
+ }
125
+ break;
126
+ }
127
+
128
+ case "ArrowLeft": {
129
+ event.preventDefault();
130
+ if (isFolder && isExpanded) {
131
+ target.click();
132
+ } else {
133
+ focusItem(getParentTreeItem(target, container));
134
+ }
135
+ break;
136
+ }
137
+
138
+ case "Home": {
139
+ event.preventDefault();
140
+ focusItem(visibleItems[0]);
141
+ break;
142
+ }
143
+
144
+ case "End": {
145
+ event.preventDefault();
146
+ focusItem(visibleItems.at(-1));
147
+ break;
148
+ }
149
+
150
+ case " ":
151
+ case "Enter": {
152
+ event.preventDefault();
153
+ target.click();
154
+ break;
155
+ }
156
+
157
+ case "*": {
158
+ event.preventDefault();
159
+ const siblingFolders = getSiblingFolders(target, container);
160
+ // Preserve the original sibling-folder expansion behavior.
161
+ // biome-ignore lint/complexity/noForEach: TODO
162
+ siblingFolders.forEach((folder) => {
163
+ if (folder.getAttribute("aria-expanded") === "false") {
164
+ folder.click();
165
+ }
166
+ });
167
+ break;
168
+ }
169
+
170
+ default: {
171
+ if (
172
+ event.key.length === 1 &&
173
+ !event.ctrlKey &&
174
+ !event.metaKey &&
175
+ !event.altKey
176
+ ) {
177
+ event.preventDefault();
178
+
179
+ if (typeAheadTimeout) {
180
+ clearTimeout(typeAheadTimeout);
181
+ }
182
+
183
+ typeAheadBuffer += event.key.toLowerCase();
184
+
185
+ const searchString = typeAheadBuffer;
186
+ const startIndex = currentIndex + 1;
187
+ const itemsAfter = visibleItems.slice(startIndex);
188
+ const itemsBefore = visibleItems.slice(0, startIndex);
189
+ const searchOrder = [...itemsAfter, ...itemsBefore];
190
+
191
+ const match = searchOrder.find((item) => {
192
+ const label = getTreeItemLabel(item);
193
+ return label?.toLowerCase().startsWith(searchString);
194
+ });
195
+
196
+ if (match) {
197
+ focusItem(match);
198
+ }
199
+
200
+ typeAheadTimeout = setTimeout(() => {
201
+ typeAheadBuffer = "";
202
+ }, TYPE_AHEAD_TIMEOUT_MS);
203
+ }
204
+ break;
205
+ }
206
+ }
207
+ }
208
+
209
+ let treeClass = $derived(cn(Classes.Tree, classAttribute ?? className));
210
+ </script>
211
+
212
+ <!-- Svelte keeps the React tree root and delegates keyboard behavior from it. -->
213
+ <div
214
+ bind:this={treeElement}
215
+ aria-label="File tree"
216
+ class={treeClass}
217
+ data-component-part="tree-root"
218
+ onfocusin={handleFocus}
219
+ onkeydown={handleTreeKeyDown}
220
+ role="tree"
221
+ tabindex="-1"
222
+ >
223
+ {#if children}
224
+ {@render children()}
225
+ {/if}
226
+ </div>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from "svelte";
2
+ export type TreeProps = {
3
+ class?: string;
4
+ className?: string;
5
+ children?: Snippet;
6
+ };
7
+ declare const Tree: import("svelte").Component<TreeProps, {}, "">;
8
+ type Tree = ReturnType<typeof Tree>;
9
+ export default Tree;
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import { Classes } from "../../constants/selectors";
3
+ import Icon from "../icon/Icon.svelte";
4
+ import { cn } from "../../utils/cn";
5
+
6
+ import { useTreeLevel } from "./use-tree-level";
7
+ import { calculatePaddingLeft } from "./utils";
8
+
9
+ export type TreeFileProps = {
10
+ name: string;
11
+ };
12
+
13
+ let { name }: TreeFileProps = $props();
14
+ const { level } = useTreeLevel();
15
+ </script>
16
+
17
+ <div
18
+ aria-level={level}
19
+ aria-selected={false}
20
+ class={cn(
21
+ "flex cursor-default items-center gap-1.5 rounded-lg py-1 pr-1.5 pl-[calc(var(--padding-left)*1px)] text-stone-700 -outline-offset-1 hover:bg-stone-100 dark:text-stone-400 dark:hover:bg-stone-900",
22
+ Classes.TreeFile
23
+ )}
24
+ data-component-part="tree-file"
25
+ role="treeitem"
26
+ style={`--padding-left:${calculatePaddingLeft(level)}`}
27
+ tabindex="-1"
28
+ >
29
+ <span aria-hidden="true" class="contents" data-component-part="tree-file-icon">
30
+ <Icon
31
+ class="size-4 shrink-0 select-none bg-stone-700 dark:bg-stone-400"
32
+ icon="file"
33
+ iconLibrary="lucide"
34
+ overrideColor={true}
35
+ />
36
+ </span>
37
+ <span
38
+ class="truncate font-medium text-sm leading-5 -tracking-[0.1px]"
39
+ data-component-part="tree-file-title"
40
+ title={name}
41
+ >
42
+ {name}
43
+ </span>
44
+ </div>
@@ -0,0 +1,6 @@
1
+ export type TreeFileProps = {
2
+ name: string;
3
+ };
4
+ declare const TreeFile: import("svelte").Component<TreeFileProps, {}, "">;
5
+ type TreeFile = ReturnType<typeof TreeFile>;
6
+ export default TreeFile;
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ import { Classes } from "../../constants/selectors";
5
+ import Icon from "../icon/Icon.svelte";
6
+ import { cn } from "../../utils/cn";
7
+
8
+ import { setTreeLevelContext } from "./context";
9
+ import { useTreeLevel } from "./use-tree-level";
10
+ import { calculatePaddingLeft, createTreeId } from "./utils";
11
+
12
+ export type TreeFolderProps = {
13
+ name: string;
14
+ defaultOpen?: boolean;
15
+ children?: Snippet;
16
+ openable?: boolean;
17
+ };
18
+
19
+ let {
20
+ name,
21
+ defaultOpen = false,
22
+ children,
23
+ openable: requestedOpenable = true,
24
+ }: TreeFolderProps = $props();
25
+
26
+ const uniqueId = createTreeId();
27
+ const nodeId = `tree-folder-${uniqueId}`;
28
+ const groupId = `tree-group-${uniqueId}`;
29
+ const { level } = useTreeLevel();
30
+ let openable = $derived(requestedOpenable && !!children);
31
+ // React's useState intentionally captures the initial defaultOpen value.
32
+ // svelte-ignore state_referenced_locally
33
+ let open = $state(openable && defaultOpen);
34
+
35
+ // Svelte context replaces the nested React TreeLevelProvider.
36
+ setTreeLevelContext({ level: level + 1, parentId: nodeId });
37
+
38
+ function toggleFolder() {
39
+ if (openable) {
40
+ open = !open;
41
+ }
42
+ }
43
+ </script>
44
+
45
+ <div
46
+ data-component-part="tree-folder"
47
+ role="none"
48
+ style={`--padding-left:${calculatePaddingLeft(level)}`}
49
+ >
50
+ <!-- Preserve the React treeitem click behavior; the tree root supplies keyboard activation. -->
51
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
52
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
53
+ <div
54
+ aria-expanded={openable ? open : undefined}
55
+ aria-level={level}
56
+ aria-owns={openable && open ? groupId : undefined}
57
+ aria-selected={false}
58
+ class={cn(
59
+ "flex items-center gap-1.5 rounded-lg py-1 pr-1.5 pl-[calc(var(--padding-left)*1px)] text-stone-700 -outline-offset-1 hover:bg-stone-100 dark:text-stone-400 dark:hover:bg-stone-900",
60
+ openable ? "cursor-pointer" : "cursor-default",
61
+ Classes.TreeFolder
62
+ )}
63
+ id={nodeId}
64
+ onclick={openable ? toggleFolder : undefined}
65
+ role="treeitem"
66
+ tabindex="-1"
67
+ >
68
+ <span
69
+ aria-hidden="true"
70
+ class="contents"
71
+ data-component-part={openable && open ? "tree-folder-icon-open" : "tree-folder-icon-closed"}
72
+ >
73
+ <Icon
74
+ class="size-4 shrink-0 select-none bg-stone-700 dark:bg-stone-400"
75
+ icon={openable && open ? "folder-open" : "folder"}
76
+ iconLibrary="lucide"
77
+ overrideColor={true}
78
+ />
79
+ </span>
80
+ <span
81
+ class="truncate font-medium text-sm leading-5 -tracking-[0.1px]"
82
+ data-component-part="tree-folder-title"
83
+ title={name}
84
+ >
85
+ {name}
86
+ </span>
87
+ </div>
88
+
89
+ {#if openable && open}
90
+ <!-- Svelte preserves the semantic group wrapper used by the React tree. -->
91
+ <div
92
+ class="relative"
93
+ data-component-part="tree-folder-children-wrapper"
94
+ id={groupId}
95
+ role="group"
96
+ >
97
+ <div
98
+ aria-hidden="true"
99
+ class="absolute left-[calc((var(--padding-left)+8)*1px)] z-10 h-full w-px select-none bg-stone-200 dark:bg-stone-800"
100
+ data-component-part="tree-folder-children-line"
101
+ ></div>
102
+ {#if children}
103
+ {@render children()}
104
+ {/if}
105
+ </div>
106
+ {/if}
107
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from "svelte";
2
+ export type TreeFolderProps = {
3
+ name: string;
4
+ defaultOpen?: boolean;
5
+ children?: Snippet;
6
+ openable?: boolean;
7
+ };
8
+ declare const TreeFolder: import("svelte").Component<TreeFolderProps, {}, "">;
9
+ type TreeFolder = ReturnType<typeof TreeFolder>;
10
+ export default TreeFolder;
@@ -0,0 +1,3 @@
1
+ import { type TreeLevelContextValue } from "./tree-level-context";
2
+ declare const setTreeLevelContext: (value: TreeLevelContextValue) => void;
3
+ export { setTreeLevelContext };
@@ -0,0 +1,7 @@
1
+ import { setContext } from "svelte";
2
+ import { TREE_LEVEL_CONTEXT } from "./tree-level-context";
3
+ // Svelte's setContext replaces the React TreeLevelProvider component.
4
+ const setTreeLevelContext = (value) => {
5
+ setContext(TREE_LEVEL_CONTEXT, value);
6
+ };
7
+ export { setTreeLevelContext };
@@ -0,0 +1,10 @@
1
+ import TreeFile from "./TreeFile.svelte";
2
+ import TreeFolder from "./TreeFolder.svelte";
3
+ export type { TreeFileProps } from "./TreeFile.svelte";
4
+ export type { TreeFolderProps } from "./TreeFolder.svelte";
5
+ export type { TreeProps } from "./Tree.svelte";
6
+ declare const Tree: import("svelte").Component<import("./Tree.svelte").TreeProps, {}, ""> & {
7
+ File: import("svelte").Component<import("./TreeFile.svelte").TreeFileProps, {}, "">;
8
+ Folder: import("svelte").Component<import("./TreeFolder.svelte").TreeFolderProps, {}, "">;
9
+ };
10
+ export { Tree, TreeFile, TreeFolder };
@@ -0,0 +1,9 @@
1
+ import TreeFile from "./TreeFile.svelte";
2
+ import TreeFolder from "./TreeFolder.svelte";
3
+ import TreeRoot from "./Tree.svelte";
4
+ // Preserve the React compound-component export for JavaScript consumers.
5
+ const Tree = Object.assign(TreeRoot, {
6
+ File: TreeFile,
7
+ Folder: TreeFolder,
8
+ });
9
+ export { Tree, TreeFile, TreeFolder };
@@ -0,0 +1,8 @@
1
+ type TreeLevelContextValue = {
2
+ level: number;
3
+ parentId: string | null;
4
+ };
5
+ declare const DEFAULT_VALUE: TreeLevelContextValue;
6
+ declare const TREE_LEVEL_CONTEXT: unique symbol;
7
+ export { DEFAULT_VALUE, TREE_LEVEL_CONTEXT };
8
+ export type { TreeLevelContextValue };
@@ -0,0 +1,7 @@
1
+ const DEFAULT_VALUE = {
2
+ level: 1,
3
+ parentId: null,
4
+ };
5
+ // Svelte uses a shared context key in place of React's createContext value.
6
+ const TREE_LEVEL_CONTEXT = Symbol("tree-level-context");
7
+ export { DEFAULT_VALUE, TREE_LEVEL_CONTEXT };
@@ -0,0 +1,3 @@
1
+ import { type TreeLevelContextValue } from "./tree-level-context";
2
+ declare const useTreeLevel: () => TreeLevelContextValue;
3
+ export { useTreeLevel };
@@ -0,0 +1,5 @@
1
+ import { getContext } from "svelte";
2
+ import { DEFAULT_VALUE, TREE_LEVEL_CONTEXT, } from "./tree-level-context";
3
+ // Svelte's getContext replaces React useTreeLevel/useContext.
4
+ const useTreeLevel = () => getContext(TREE_LEVEL_CONTEXT) ?? DEFAULT_VALUE;
5
+ export { useTreeLevel };
@@ -0,0 +1,9 @@
1
+ declare const getParentTreeItem: (element: HTMLElement, container: HTMLElement) => HTMLElement | null;
2
+ declare const getFirstChildTreeItem: (folder: HTMLElement) => HTMLElement | null;
3
+ declare const getVisibleTreeItems: (container: HTMLElement) => HTMLElement[];
4
+ declare const calculatePaddingLeft: (level: number) => number;
5
+ declare const createTreeId: () => string;
6
+ declare const getTreeItemLabel: (element: HTMLElement) => string | null;
7
+ declare const getSiblingFolders: (element: HTMLElement, container: HTMLElement) => HTMLElement[];
8
+ declare const updateRovingTabindex: (container: HTMLElement, target: HTMLElement) => void;
9
+ export { getParentTreeItem, getFirstChildTreeItem, getVisibleTreeItems, calculatePaddingLeft, createTreeId, getTreeItemLabel, getSiblingFolders, updateRovingTabindex, };