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.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/components/Search.svelte +320 -0
- package/dist/components/Search.svelte.d.ts +31 -0
- package/dist/components/SearchButton.svelte +82 -0
- package/dist/components/SearchButton.svelte.d.ts +13 -0
- package/dist/components/SearchProvider.svelte +87 -0
- package/dist/components/SearchProvider.svelte.d.ts +17 -0
- package/dist/components/accordion/Accordion.svelte +150 -0
- package/dist/components/accordion/Accordion.svelte.d.ts +25 -0
- package/dist/components/accordion/AccordionCover.svelte +68 -0
- package/dist/components/accordion/AccordionCover.svelte.d.ts +13 -0
- package/dist/components/accordion/AccordionGroup.svelte +23 -0
- package/dist/components/accordion/AccordionGroup.svelte.d.ts +8 -0
- package/dist/components/accordion/accordion-url-utils.d.ts +4 -0
- package/dist/components/accordion/accordion-url-utils.js +48 -0
- package/dist/components/accordion/index.d.ts +29 -0
- package/dist/components/accordion/index.js +6 -0
- package/dist/components/badge/Badge.svelte +152 -0
- package/dist/components/badge/Badge.svelte.d.ts +23 -0
- package/dist/components/badge/index.d.ts +1 -0
- package/dist/components/badge/index.js +2 -0
- package/dist/components/callout/Callout.svelte +317 -0
- package/dist/components/callout/Callout.svelte.d.ts +17 -0
- package/dist/components/callout/Check.svelte +9 -0
- package/dist/components/callout/Check.svelte.d.ts +4 -0
- package/dist/components/callout/Danger.svelte +9 -0
- package/dist/components/callout/Danger.svelte.d.ts +4 -0
- package/dist/components/callout/Info.svelte +9 -0
- package/dist/components/callout/Info.svelte.d.ts +4 -0
- package/dist/components/callout/Note.svelte +9 -0
- package/dist/components/callout/Note.svelte.d.ts +4 -0
- package/dist/components/callout/Tip.svelte +9 -0
- package/dist/components/callout/Tip.svelte.d.ts +4 -0
- package/dist/components/callout/Warning.svelte +9 -0
- package/dist/components/callout/Warning.svelte.d.ts +4 -0
- package/dist/components/callout/index.d.ts +8 -0
- package/dist/components/callout/index.js +8 -0
- package/dist/components/card/Card.svelte +172 -0
- package/dist/components/card/Card.svelte.d.ts +21 -0
- package/dist/components/card/index.d.ts +3 -0
- package/dist/components/card/index.js +2 -0
- package/dist/components/code-block/BaseCodeBlock.svelte +249 -0
- package/dist/components/code-block/BaseCodeBlock.svelte.d.ts +10 -0
- package/dist/components/code-block/CodeBlock.svelte +185 -0
- package/dist/components/code-block/CodeBlock.svelte.d.ts +5 -0
- package/dist/components/code-block/CodeFooter.svelte +34 -0
- package/dist/components/code-block/CodeFooter.svelte.d.ts +8 -0
- package/dist/components/code-block/CodeHeader.svelte +52 -0
- package/dist/components/code-block/CodeHeader.svelte.d.ts +11 -0
- package/dist/components/code-block/CopyToClipboardButton.svelte +124 -0
- package/dist/components/code-block/CopyToClipboardButton.svelte.d.ts +4 -0
- package/dist/components/code-block/index.d.ts +10 -0
- package/dist/components/code-block/index.js +5 -0
- package/dist/components/code-block/types.d.ts +85 -0
- package/dist/components/code-block/types.js +1 -0
- package/dist/components/code-group/CodeGroup.svelte +327 -0
- package/dist/components/code-group/CodeGroup.svelte.d.ts +24 -0
- package/dist/components/code-group/CodeGroupSelect.svelte +170 -0
- package/dist/components/code-group/CodeGroupSelect.svelte.d.ts +25 -0
- package/dist/components/code-group/CodeSelectDropdown.svelte +74 -0
- package/dist/components/code-group/CodeSelectDropdown.svelte.d.ts +9 -0
- package/dist/components/code-group/LanguageDropdown.svelte +134 -0
- package/dist/components/code-group/LanguageDropdown.svelte.d.ts +11 -0
- package/dist/components/code-group/LanguageIcon.svelte +24 -0
- package/dist/components/code-group/LanguageIcon.svelte.d.ts +8 -0
- package/dist/components/code-group/context.d.ts +14 -0
- package/dist/components/code-group/context.js +2 -0
- package/dist/components/code-group/index.d.ts +8 -0
- package/dist/components/code-group/index.js +4 -0
- package/dist/components/color/Color.svelte +34 -0
- package/dist/components/color/Color.svelte.d.ts +10 -0
- package/dist/components/color/ColorItem.svelte +140 -0
- package/dist/components/color/ColorItem.svelte.d.ts +10 -0
- package/dist/components/color/ColorRow.svelte +27 -0
- package/dist/components/color/ColorRow.svelte.d.ts +8 -0
- package/dist/components/color/index.d.ts +1 -0
- package/dist/components/color/index.js +8 -0
- package/dist/components/columns/Box.svelte +15 -0
- package/dist/components/columns/Box.svelte.d.ts +7 -0
- package/dist/components/columns/Columns.svelte +31 -0
- package/dist/components/columns/Columns.svelte.d.ts +10 -0
- package/dist/components/columns/constants.d.ts +5 -0
- package/dist/components/columns/constants.js +3 -0
- package/dist/components/columns/index.d.ts +5 -0
- package/dist/components/columns/index.js +3 -0
- package/dist/components/expandable/Expandable.svelte +182 -0
- package/dist/components/expandable/Expandable.svelte.d.ts +22 -0
- package/dist/components/expandable/index.d.ts +3 -0
- package/dist/components/expandable/index.js +2 -0
- package/dist/components/frame/Frame.svelte +118 -0
- package/dist/components/frame/Frame.svelte.d.ts +15 -0
- package/dist/components/frame/index.d.ts +3 -0
- package/dist/components/frame/index.js +2 -0
- package/dist/components/icon/Icon.svelte +129 -0
- package/dist/components/icon/Icon.svelte.d.ts +18 -0
- package/dist/components/icon/index.d.ts +2 -0
- package/dist/components/icon/index.js +1 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.js +22 -0
- package/dist/components/mermaid/Mermaid.svelte +181 -0
- package/dist/components/mermaid/Mermaid.svelte.d.ts +11 -0
- package/dist/components/mermaid/ZoomControls.svelte +74 -0
- package/dist/components/mermaid/ZoomControls.svelte.d.ts +12 -0
- package/dist/components/mermaid/index.d.ts +7 -0
- package/dist/components/mermaid/index.js +4 -0
- package/dist/components/mermaid/use-pan-zoom.svelte.d.ts +11 -0
- package/dist/components/mermaid/use-pan-zoom.svelte.js +41 -0
- package/dist/components/panel/Panel.svelte +27 -0
- package/dist/components/panel/Panel.svelte.d.ts +9 -0
- package/dist/components/panel/index.d.ts +3 -0
- package/dist/components/panel/index.js +2 -0
- package/dist/components/property/DeprecatedPill.svelte +16 -0
- package/dist/components/property/DeprecatedPill.svelte.d.ts +6 -0
- package/dist/components/property/InfoPill.svelte +32 -0
- package/dist/components/property/InfoPill.svelte.d.ts +10 -0
- package/dist/components/property/ParamHead.svelte +188 -0
- package/dist/components/property/ParamHead.svelte.d.ts +22 -0
- package/dist/components/property/Property.svelte +123 -0
- package/dist/components/property/Property.svelte.d.ts +24 -0
- package/dist/components/property/RequiredPill.svelte +16 -0
- package/dist/components/property/RequiredPill.svelte.d.ts +6 -0
- package/dist/components/property/index.d.ts +11 -0
- package/dist/components/property/index.js +6 -0
- package/dist/components/search/index.d.ts +7 -0
- package/dist/components/search/index.js +4 -0
- package/dist/components/search/use-search.d.ts +9 -0
- package/dist/components/search/use-search.js +6 -0
- package/dist/components/steps/Step.svelte +314 -0
- package/dist/components/steps/Step.svelte.d.ts +26 -0
- package/dist/components/steps/Steps.svelte +51 -0
- package/dist/components/steps/Steps.svelte.d.ts +11 -0
- package/dist/components/steps/constants.d.ts +4 -0
- package/dist/components/steps/constants.js +2 -0
- package/dist/components/steps/context.d.ts +10 -0
- package/dist/components/steps/context.js +2 -0
- package/dist/components/steps/index.d.ts +32 -0
- package/dist/components/steps/index.js +6 -0
- package/dist/components/tabs/Tabs.svelte +254 -0
- package/dist/components/tabs/Tabs.svelte.d.ts +16 -0
- package/dist/components/tabs/TabsItem.svelte +56 -0
- package/dist/components/tabs/TabsItem.svelte.d.ts +15 -0
- package/dist/components/tabs/context.d.ts +20 -0
- package/dist/components/tabs/context.js +2 -0
- package/dist/components/tabs/index.d.ts +27 -0
- package/dist/components/tabs/index.js +5 -0
- package/dist/components/tile/Tile.svelte +124 -0
- package/dist/components/tile/Tile.svelte.d.ts +12 -0
- package/dist/components/tile/index.d.ts +2 -0
- package/dist/components/tile/index.js +1 -0
- package/dist/components/tooltip/Tooltip.svelte +170 -0
- package/dist/components/tooltip/Tooltip.svelte.d.ts +23 -0
- package/dist/components/tooltip/index.d.ts +2 -0
- package/dist/components/tooltip/index.js +1 -0
- package/dist/components/tree/Tree.svelte +226 -0
- package/dist/components/tree/Tree.svelte.d.ts +9 -0
- package/dist/components/tree/TreeFile.svelte +44 -0
- package/dist/components/tree/TreeFile.svelte.d.ts +6 -0
- package/dist/components/tree/TreeFolder.svelte +107 -0
- package/dist/components/tree/TreeFolder.svelte.d.ts +10 -0
- package/dist/components/tree/context.d.ts +3 -0
- package/dist/components/tree/context.js +7 -0
- package/dist/components/tree/index.d.ts +10 -0
- package/dist/components/tree/index.js +9 -0
- package/dist/components/tree/tree-level-context.d.ts +8 -0
- package/dist/components/tree/tree-level-context.js +7 -0
- package/dist/components/tree/use-tree-level.d.ts +3 -0
- package/dist/components/tree/use-tree-level.js +5 -0
- package/dist/components/tree/utils.d.ts +9 -0
- package/dist/components/tree/utils.js +86 -0
- package/dist/components/update/Update.svelte +210 -0
- package/dist/components/update/Update.svelte.d.ts +34 -0
- package/dist/components/update/index.d.ts +4 -0
- package/dist/components/update/index.js +3 -0
- package/dist/components/view/View.svelte +52 -0
- package/dist/components/view/View.svelte.d.ts +14 -0
- package/dist/components/view/index.d.ts +3 -0
- package/dist/components/view/index.js +2 -0
- package/dist/constants/brands.d.ts +2 -0
- package/dist/constants/brands.js +4 -0
- package/dist/constants/index.d.ts +6 -0
- package/dist/constants/index.js +6 -0
- package/dist/constants/selectors.d.ts +125 -0
- package/dist/constants/selectors.js +125 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/use-expandable-memory.svelte.d.ts +6 -0
- package/dist/hooks/use-expandable-memory.svelte.js +89 -0
- package/dist/hooks/use-expandable.svelte.d.ts +15 -0
- package/dist/hooks/use-expandable.svelte.js +53 -0
- package/dist/hooks/use-get-shiki-highlighted-html.svelte.d.ts +8 -0
- package/dist/hooks/use-get-shiki-highlighted-html.svelte.js +36 -0
- package/dist/hooks/use-has-hover.d.ts +2 -0
- package/dist/hooks/use-has-hover.js +10 -0
- package/dist/hooks/use-is-dark-theme.svelte.d.ts +3 -0
- package/dist/hooks/use-is-dark-theme.svelte.js +25 -0
- package/dist/hooks/use-isomorphic-layout-effect.d.ts +3 -0
- package/dist/hooks/use-isomorphic-layout-effect.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/styles.css +2 -0
- package/dist/utils/cn.d.ts +3 -0
- package/dist/utils/cn.js +7 -0
- package/dist/utils/copy-to-clipboard.d.ts +4 -0
- package/dist/utils/copy-to-clipboard.js +50 -0
- package/dist/utils/dedent.d.ts +1 -0
- package/dist/utils/dedent.js +125 -0
- package/dist/utils/enhance-video-props.d.ts +4 -0
- package/dist/utils/enhance-video-props.js +25 -0
- package/dist/utils/get-node-text.d.ts +2 -0
- package/dist/utils/get-node-text.js +26 -0
- package/dist/utils/icon-utils.d.ts +18 -0
- package/dist/utils/icon-utils.js +30 -0
- package/dist/utils/is-absolute-url.d.ts +4 -0
- package/dist/utils/is-absolute-url.js +11 -0
- package/dist/utils/is-remote-url.d.ts +2 -0
- package/dist/utils/is-remote-url.js +12 -0
- package/dist/utils/make-marker-ids-unique.d.ts +2 -0
- package/dist/utils/make-marker-ids-unique.js +36 -0
- package/dist/utils/shiki/code-styling.d.ts +13 -0
- package/dist/utils/shiki/code-styling.js +1 -0
- package/dist/utils/shiki/constants.d.ts +21 -0
- package/dist/utils/shiki/constants.js +116 -0
- package/dist/utils/shiki/get-code-styling.d.ts +9 -0
- package/dist/utils/shiki/get-code-styling.js +38 -0
- package/dist/utils/shiki/get-shiki-background-colors.d.ts +20 -0
- package/dist/utils/shiki/get-shiki-background-colors.js +56 -0
- package/dist/utils/shiki/index.d.ts +30 -0
- package/dist/utils/shiki/index.js +209 -0
- package/dist/utils/shiki/lib.d.ts +4 -0
- package/dist/utils/shiki/lib.js +49 -0
- package/dist/utils/shiki/shiki-themes.d.ts +2 -0
- package/dist/utils/shiki/shiki-themes.js +64 -0
- package/dist/utils/shiki/snippet-presets.d.ts +27 -0
- package/dist/utils/shiki/snippet-presets.js +44 -0
- package/dist/utils/shiki/worker-client.d.ts +9 -0
- package/dist/utils/shiki/worker-client.js +22 -0
- package/dist/utils/shiki/worker.d.ts +1 -0
- package/dist/utils/shiki/worker.js +134 -0
- package/dist/utils/slugify.d.ts +2 -0
- package/dist/utils/slugify.js +9 -0
- 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 @@
|
|
|
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,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,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,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, };
|