view-pane.svelte (8883B)
1 <script lang="ts"> 2 import type { Snippet } from "svelte"; 3 import { get_context } from "$lib/utils/app"; 4 import { 5 VIEW_CONTEXT_KEY, 6 type ViewContext, 7 type ViewMode, 8 type ViewPointerEvents, 9 } from "./view-context"; 10 import { writable, type Writable } from "svelte/store"; 11 12 const DEFAULT_TRANSITION_MS = 260; 13 const DEFAULT_OPACITY_INACTIVE = 0; 14 const DEFAULT_SCALE_INACTIVE = 0.98; 15 const DEFAULT_OFFSET_X = "0px"; 16 const DEFAULT_OFFSET_Y = "12px"; 17 const DEFAULT_BLUR_INACTIVE_PX = 0; 18 const DEFAULT_POINTER_EVENTS_INACTIVE: ViewPointerEvents = "none"; 19 const DEFAULT_Z_INDEX_ACTIVE = 2; 20 const DEFAULT_Z_INDEX_INACTIVE = 1; 21 const DEFAULT_DISPLAY = "flex"; 22 const DEFAULT_DIRECTION = "column"; 23 const DEFAULT_ALIGN = "stretch"; 24 const DEFAULT_JUSTIFY = "flex-start"; 25 const DEFAULT_WIDTH = "100%"; 26 const DEFAULT_HEIGHT = "100%"; 27 const DEFAULT_MIN_HEIGHT = "0px"; 28 const DEFAULT_PADDING = "0px"; 29 const DEFAULT_GAP = "0px"; 30 const DEFAULT_OVERFLOW = "hidden"; 31 const DEFAULT_BACKGROUND = "transparent"; 32 const EASE_OUT = "cubic-bezier(0.16, 1, 0.3, 1)"; 33 34 type ViewPaneBasis<T extends string> = { 35 view: T; 36 active?: boolean; 37 active_view?: T; 38 mode?: ViewMode; 39 fade?: boolean; 40 transition_ms?: number; 41 opacity_inactive?: number; 42 scale_inactive?: number; 43 offset_x?: string; 44 offset_y?: string; 45 blur_inactive_px?: number; 46 pointer_events_inactive?: ViewPointerEvents; 47 z_index_active?: number; 48 z_index_inactive?: number; 49 display?: string; 50 direction?: string; 51 align?: string; 52 justify?: string; 53 width?: string; 54 height?: string; 55 min_height?: string; 56 padding?: string; 57 gap?: string; 58 overflow?: string; 59 background?: string; 60 style?: string; 61 style_active?: string; 62 style_inactive?: string; 63 role?: string; 64 aria_label?: string; 65 }; 66 67 let { 68 basis, 69 children, 70 }: { 71 basis: ViewPaneBasis<string>; 72 children: Snippet; 73 } = $props(); 74 75 const view_context_fallback = writable<ViewContext<string>>({ 76 active_view: "", 77 mode: "stack", 78 fade: true, 79 transition_ms: DEFAULT_TRANSITION_MS, 80 opacity_inactive: DEFAULT_OPACITY_INACTIVE, 81 scale_inactive: DEFAULT_SCALE_INACTIVE, 82 offset_x: DEFAULT_OFFSET_X, 83 offset_y: DEFAULT_OFFSET_Y, 84 blur_inactive_px: DEFAULT_BLUR_INACTIVE_PX, 85 pointer_events_inactive: DEFAULT_POINTER_EVENTS_INACTIVE, 86 z_index_active: DEFAULT_Z_INDEX_ACTIVE, 87 z_index_inactive: DEFAULT_Z_INDEX_INACTIVE, 88 }); 89 90 const view_context_store = 91 get_context<Writable<ViewContext<string>> | undefined>( 92 VIEW_CONTEXT_KEY, 93 ) ?? view_context_fallback; 94 const use_fallback = view_context_store === view_context_fallback; 95 96 $effect(() => { 97 if (!use_fallback) return; 98 view_context_fallback.set({ 99 active_view: basis.active_view ?? basis.view, 100 mode: basis.mode ?? "stack", 101 fade: basis.fade ?? true, 102 transition_ms: basis.transition_ms ?? DEFAULT_TRANSITION_MS, 103 opacity_inactive: basis.opacity_inactive ?? DEFAULT_OPACITY_INACTIVE, 104 scale_inactive: basis.scale_inactive ?? DEFAULT_SCALE_INACTIVE, 105 offset_x: basis.offset_x ?? DEFAULT_OFFSET_X, 106 offset_y: basis.offset_y ?? DEFAULT_OFFSET_Y, 107 blur_inactive_px: 108 basis.blur_inactive_px ?? DEFAULT_BLUR_INACTIVE_PX, 109 pointer_events_inactive: 110 basis.pointer_events_inactive ?? DEFAULT_POINTER_EVENTS_INACTIVE, 111 z_index_active: basis.z_index_active ?? DEFAULT_Z_INDEX_ACTIVE, 112 z_index_inactive: basis.z_index_inactive ?? DEFAULT_Z_INDEX_INACTIVE, 113 }); 114 }); 115 116 const view_context_value = $derived($view_context_store); 117 118 const active_view = $derived( 119 basis.active_view ?? view_context_value?.active_view, 120 ); 121 const is_active = $derived( 122 basis.active !== undefined 123 ? basis.active 124 : active_view 125 ? basis.view === active_view 126 : true, 127 ); 128 const mode = $derived(basis.mode ?? view_context_value?.mode ?? "stack"); 129 const fade = $derived(basis.fade ?? view_context_value?.fade ?? true); 130 const transition_ms = $derived( 131 basis.transition_ms ?? 132 view_context_value?.transition_ms ?? 133 DEFAULT_TRANSITION_MS, 134 ); 135 const opacity_inactive = $derived( 136 basis.opacity_inactive ?? 137 view_context_value?.opacity_inactive ?? 138 DEFAULT_OPACITY_INACTIVE, 139 ); 140 const scale_inactive = $derived( 141 basis.scale_inactive ?? 142 view_context_value?.scale_inactive ?? 143 DEFAULT_SCALE_INACTIVE, 144 ); 145 const offset_x = $derived( 146 basis.offset_x ?? view_context_value?.offset_x ?? DEFAULT_OFFSET_X, 147 ); 148 const offset_y = $derived( 149 basis.offset_y ?? view_context_value?.offset_y ?? DEFAULT_OFFSET_Y, 150 ); 151 const blur_inactive_px = $derived( 152 basis.blur_inactive_px ?? 153 view_context_value?.blur_inactive_px ?? 154 DEFAULT_BLUR_INACTIVE_PX, 155 ); 156 const pointer_events_inactive = $derived( 157 basis.pointer_events_inactive ?? 158 view_context_value?.pointer_events_inactive ?? 159 DEFAULT_POINTER_EVENTS_INACTIVE, 160 ); 161 const z_index_active = $derived( 162 basis.z_index_active ?? 163 view_context_value?.z_index_active ?? 164 DEFAULT_Z_INDEX_ACTIVE, 165 ); 166 const z_index_inactive = $derived( 167 basis.z_index_inactive ?? 168 view_context_value?.z_index_inactive ?? 169 DEFAULT_Z_INDEX_INACTIVE, 170 ); 171 const display = $derived(basis.display ?? DEFAULT_DISPLAY); 172 const direction = $derived(basis.direction ?? DEFAULT_DIRECTION); 173 const align = $derived(basis.align ?? DEFAULT_ALIGN); 174 const justify = $derived(basis.justify ?? DEFAULT_JUSTIFY); 175 const width = $derived(basis.width ?? DEFAULT_WIDTH); 176 const height = $derived(basis.height ?? DEFAULT_HEIGHT); 177 const min_height = $derived(basis.min_height ?? DEFAULT_MIN_HEIGHT); 178 const padding = $derived(basis.padding ?? DEFAULT_PADDING); 179 const gap = $derived(basis.gap ?? DEFAULT_GAP); 180 const overflow = $derived(basis.overflow ?? DEFAULT_OVERFLOW); 181 const background = $derived(basis.background ?? DEFAULT_BACKGROUND); 182 183 const opacity = $derived(is_active ? 1 : opacity_inactive); 184 const transform = $derived( 185 `translate3d(${is_active ? "0px" : offset_x}, ${is_active ? "0px" : offset_y}, 0) scale(${is_active ? 1 : scale_inactive})`, 186 ); 187 const blur_val = $derived(is_active ? 0 : blur_inactive_px); 188 const filter = $derived(blur_val ? `blur(${blur_val}px)` : "none"); 189 const pointer_events = $derived(is_active ? "auto" : pointer_events_inactive); 190 const z_index = $derived(is_active ? z_index_active : z_index_inactive); 191 const transition = $derived( 192 fade 193 ? `opacity ${transition_ms}ms ${EASE_OUT}, transform ${transition_ms}ms ${EASE_OUT}, filter ${transition_ms}ms ${EASE_OUT}` 194 : `transform ${transition_ms}ms ${EASE_OUT}, filter ${transition_ms}ms ${EASE_OUT}`, 195 ); 196 const position = $derived(mode === "stack" ? "absolute" : "relative"); 197 const inset = $derived(mode === "stack" ? "0" : "auto"); 198 199 const style = $derived( 200 `position: ${position}; inset: ${inset}; width: ${width}; height: ${height}; min-height: ${min_height}; display: ${display}; flex-direction: ${direction}; align-items: ${align}; justify-content: ${justify}; gap: ${gap}; padding: ${padding}; overflow: ${overflow}; background: ${background}; opacity: ${opacity}; transform: ${transform}; filter: ${filter}; transition: ${transition}; pointer-events: ${pointer_events}; z-index: ${z_index}; box-sizing: border-box; will-change: transform, opacity, filter; backface-visibility: hidden; transform-style: preserve-3d; ${basis.style ?? ""} ${is_active ? basis.style_active ?? "" : basis.style_inactive ?? ""}`, 201 ); 202 203 let pane_el: HTMLDivElement | null = $state(null); 204 205 $effect(() => { 206 if (is_active) return; 207 if (typeof document === "undefined") return; 208 if (!pane_el) return; 209 const active_el = document.activeElement; 210 if (!active_el) return; 211 if (!pane_el.contains(active_el)) return; 212 (active_el as HTMLElement).blur(); 213 }); 214 </script> 215 216 <div 217 bind:this={pane_el} 218 data-view={basis.view} 219 style={style} 220 role={basis.role ?? undefined} 221 aria-label={basis.aria_label ?? undefined} 222 inert={!is_active} 223 > 224 {@render children()} 225 </div>