web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

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>