mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-27 02:27:18 +00:00
improved sticky folders
This commit is contained in:
1 parent
593b43e433
commit
970cf7d1e1
8 files changed
+180
-54
No files matched your search
@@ -5,37 +5,58 @@
|
||||
import { getContext, setContext, type Snippet } from 'svelte';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
import type { CollapsibleTreeState } from './utils.svelte';
|
||||
import { stickyKey, stickyZIndex, trackStuck, type StickyLevel } from './sticky';
|
||||
|
||||
const props: {
|
||||
id: string | number;
|
||||
class?: ClassValue;
|
||||
trigger: Snippet;
|
||||
/** Classes and style of the trigger row (when the trigger holds the content), to make it stick. */
|
||||
triggerClass?: ClassValue;
|
||||
triggerStyle?: string;
|
||||
/** Gets whether the node is open, for contents that are only worth building then. */
|
||||
content: Snippet<[boolean]>;
|
||||
} = $props();
|
||||
|
||||
let state = getContext<CollapsibleTreeState>('collapsible-tree-state');
|
||||
let treeState = getContext<CollapsibleTreeState>('collapsible-tree-state');
|
||||
let side = getContext<'left' | 'right'>('collapsible-tree-side');
|
||||
let nohover = getContext<boolean>('collapsible-tree-nohover');
|
||||
let slotInsideTrigger = getContext<boolean>('collapsible-tree-slot-inside-trigger');
|
||||
let parentId = getContext<string>('collapsible-tree-parent-id');
|
||||
// where the row sticks, under the rows of the nodes this one is in
|
||||
let level = getContext<StickyLevel>(stickyKey);
|
||||
|
||||
let fullId = `${parentId}.${props.id}`;
|
||||
setContext('collapsible-tree-parent-id', fullId);
|
||||
|
||||
let open = state.get(fullId);
|
||||
let open = treeState.get(fullId);
|
||||
|
||||
// the rows of the nodes in this one stick under this row, whatever its height
|
||||
let rowHeight = $state(0);
|
||||
setContext<StickyLevel>(stickyKey, {
|
||||
get enabled() {
|
||||
return level.enabled;
|
||||
},
|
||||
get top() {
|
||||
return level.top + rowHeight;
|
||||
},
|
||||
get depth() {
|
||||
return level.depth + 1;
|
||||
},
|
||||
get stuckRows() {
|
||||
return level.stuckRows;
|
||||
},
|
||||
});
|
||||
// the row is on top of the content that goes under it
|
||||
let stuck = $state(false);
|
||||
// only the deepest row that is stuck has a border: the rows above it run into it
|
||||
let bordered = $derived(stuck && level.stuckRows.deepest === level.depth);
|
||||
|
||||
export function openNode() {
|
||||
open.current = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Collapsible.Root bind:open={open.current} class={props.class}>
|
||||
{#snippet row()}
|
||||
{#if slotInsideTrigger}
|
||||
<Collapsible.Trigger class={['w-full', props.triggerClass]} style={props.triggerStyle}>
|
||||
<Collapsible.Trigger class="w-full">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -91,6 +112,35 @@
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<Collapsible.Root bind:open={open.current} class={props.class}>
|
||||
{#if level.enabled}
|
||||
<!-- sticks while some of the content of the node is in view, with a border when it does -->
|
||||
<div
|
||||
class={['sticky bg-background', bordered && 'border-b']}
|
||||
style="top: {level.top}px; z-index: {stickyZIndex(level.depth)}"
|
||||
bind:clientHeight={rowHeight}
|
||||
{@attach (row) => {
|
||||
const stop = trackStuck(
|
||||
row,
|
||||
() => level.top,
|
||||
(value) => {
|
||||
stuck = value;
|
||||
level.stuckRows.set(fullId, level.depth, value);
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
stop?.();
|
||||
level.stuckRows.set(fullId, level.depth, false);
|
||||
};
|
||||
}}
|
||||
>
|
||||
{@render row()}
|
||||
</div>
|
||||
{:else}
|
||||
{@render row()}
|
||||
{/if}
|
||||
<Collapsible.Content>
|
||||
{@render props.content(open.current)}
|
||||
</Collapsible.Content>
|
||||
|
||||
Reference in new issue
Block a user