// el-layout — Responsive layout engine for el-ui. // // Primitives: // VStack, HStack, ZStack — stack layouts (wrap by default) // GridLayout — responsive grid // ScrollView — scrollable container // Responsive — value that changes by breakpoint // ── Breakpoints ────────────────────────────────────────────────────────────── let BP_XS: String = "xs" // < 640px let BP_SM: String = "sm" // >= 640px let BP_MD: String = "md" // >= 768px let BP_LG: String = "lg" // >= 1024px let BP_XL: String = "xl" // >= 1280px let BP_XXL: String = "xxl" // >= 1536px let BP_SM_PX: Int = 640 let BP_MD_PX: Int = 768 let BP_LG_PX: Int = 1024 let BP_XL_PX: Int = 1280 let BP_XXL_PX: Int = 1536 fn breakpoint_for_width(width_px: Int) -> String { if width_px >= BP_XXL_PX { return BP_XXL } if width_px >= BP_XL_PX { return BP_XL } if width_px >= BP_LG_PX { return BP_LG } if width_px >= BP_MD_PX { return BP_MD } if width_px >= BP_SM_PX { return BP_SM } BP_XS } // Cascade index — used by Responsive to find the most-specific value <= bp. fn breakpoint_index(bp: String) -> Int { if str_eq(bp, "xs") { return 0 } if str_eq(bp, "sm") { return 1 } if str_eq(bp, "md") { return 2 } if str_eq(bp, "lg") { return 3 } if str_eq(bp, "xl") { return 4 } if str_eq(bp, "xxl") { return 5 } 0 } // ── Responsive ──────────────────────────────────────────────────────────── // // Stored as a JSON object: { "xs": v0, "md": v1, "lg": v2 } // resolve(bp) walks down from bp until it finds a defined value. fn responsive_fixed(value: String) -> String { "{\"xs\":" + value + "}" } fn responsive_set(rv: String, bp: String, value: String) -> String { json_set(rv, bp, value) } fn responsive_resolve(rv: String, bp: String) -> String { let order: String = "xxl,xl,lg,md,sm,xs" let target_idx: Int = breakpoint_index(bp) // Try each breakpoint <= target, most-specific first. let probe: String = bp while !str_eq(probe, "") { let v: String = json_get(rv, probe) if !str_eq(v, "") { return v } let idx: Int = breakpoint_index(probe) if idx == 0 { return "" } let probe = breakpoint_step_down(probe) } "" } fn breakpoint_step_down(bp: String) -> String { if str_eq(bp, "xxl") { return "xl" } if str_eq(bp, "xl") { return "lg" } if str_eq(bp, "lg") { return "md" } if str_eq(bp, "md") { return "sm" } if str_eq(bp, "sm") { return "xs" } "" } // ── Constraints / Size ─────────────────────────────────────────────────────── type Size { width: Int height: Int } type LayoutConstraints { min_width: Int max_width: Int min_height: Int max_height: Int } fn constraints_unbounded() -> LayoutConstraints { { "min_width": 0, "max_width": 999999, "min_height": 0, "max_height": 999999 } } fn constraints_tight(w: Int, h: Int) -> LayoutConstraints { { "min_width": w, "max_width": w, "min_height": h, "max_height": h } } // ── Flex axes ──────────────────────────────────────────────────────────────── let FLEX_ROW: String = "row" let FLEX_COLUMN: String = "column" let MAIN_START: String = "start" let MAIN_END: String = "end" let MAIN_CENTER: String = "center" let MAIN_BETWEEN: String = "space-between" let MAIN_AROUND: String = "space-around" let MAIN_EVENLY: String = "space-evenly" let CROSS_START: String = "start" let CROSS_END: String = "end" let CROSS_CENTER: String = "center" let CROSS_STRETCH: String = "stretch" let CROSS_BASELINE: String = "baseline" type FlexLayout { direction: String main_alignment: String cross_alignment: String gap_px: Int wrap: Bool } fn flex_default() -> FlexLayout { { "direction": "row", "main_alignment": "start", "cross_alignment": "stretch", "gap_px": 8, "wrap": true } } // ── Stacks ─────────────────────────────────────────────────────────────────── // // VStack / HStack / ZStack are component classes in the JS runtime; the // El side here exposes their layout descriptor — the bag of values the // el-ui-compiler emits into JSX/HTML attributes. type StackLayout { direction: String // row | column | depth main_alignment: String cross_alignment: String gap_px: Int wrap: Bool spacing_token: String // semantic spacing token (md, lg, ...) } fn vstack(spacing_token: String) -> StackLayout { { "direction": "column", "main_alignment": "start", "cross_alignment": "stretch", "gap_px": 0, "wrap": true, "spacing_token": spacing_token } } fn hstack(spacing_token: String) -> StackLayout { { "direction": "row", "main_alignment": "start", "cross_alignment": "center", "gap_px": 0, "wrap": true, "spacing_token": spacing_token } } fn zstack() -> StackLayout { { "direction": "depth", "main_alignment": "center", "cross_alignment": "center", "gap_px": 0, "wrap": false, "spacing_token": "" } } // ── Grid ───────────────────────────────────────────────────────────────────── type GridLayout { columns: String // "auto" or "1fr 1fr 1fr" etc rows: String gap_px: Int auto_fit_min: Int // for `repeat(auto-fit, minmax(, 1fr))` } fn grid_auto(min_col_px: Int, gap: Int) -> GridLayout { { "columns": "auto-fit", "rows": "auto", "gap_px": gap, "auto_fit_min": min_col_px } } fn grid_fixed(num_cols: Int, gap: Int) -> GridLayout { let cols: String = repeat_str("1fr ", num_cols) { "columns": cols, "rows": "auto", "gap_px": gap, "auto_fit_min": 0 } } fn grid_to_css(g: GridLayout) -> String { let cols: String = g.columns if str_eq(cols, "auto-fit") { let cols = "repeat(auto-fit, minmax(" + int_to_str(g.auto_fit_min) + "px, 1fr))" } "display: grid; grid-template-columns: " + cols + "; gap: " + int_to_str(g.gap_px) + "px;" } // ── ScrollView ────────────────────────────────────────────────────────────── let SCROLL_X: String = "x" let SCROLL_Y: String = "y" let SCROLL_BOTH: String = "both" type ScrollView { axis: String show_indicator: Bool bounce: Bool // iOS-style overscroll } fn scroll_view_y() -> ScrollView { { "axis": "y", "show_indicator": true, "bounce": true } } // ── Platform sizing ────────────────────────────────────────────────────────── let PLATFORM_PHONE: String = "phone" let PLATFORM_TABLET: String = "tablet" let PLATFORM_DESKTOP: String = "desktop" type SafeAreaInsets { top: Int right: Int bottom: Int left: Int } fn safe_area_zero() -> SafeAreaInsets { { "top": 0, "right": 0, "bottom": 0, "left": 0 } } fn platform_for_width(width: Int) -> String { if width < 768 { return "phone" } if width < 1024 { return "tablet" } "desktop" } // ── HTML emit ──────────────────────────────────────────────────────────────── // // Server-side render: layout descriptor → HTML string. // Children is a pre-rendered HTML string passed in by the caller. // class_extra is an optional additional CSS class string (pass "" for none). fn stack_direction_to_css(direction: String) -> String { if str_eq(direction, "column") { return "column" } if str_eq(direction, "depth") { return "unset" } "row" } fn stack_align_to_css(align: String) -> String { if str_eq(align, "center") { return "center" } if str_eq(align, "end") { return "flex-end" } if str_eq(align, "baseline") { return "baseline" } if str_eq(align, "stretch") { return "stretch" } "flex-start" } fn stack_justify_to_css(align: String) -> String { if str_eq(align, "center") { return "center" } if str_eq(align, "end") { return "flex-end" } if str_eq(align, "space-between") { return "space-between" } if str_eq(align, "space-around") { return "space-around" } if str_eq(align, "space-evenly") { return "space-evenly" } "flex-start" } fn stack_to_html(layout: StackLayout, children: String, class_extra: String) -> String { let direction: String = stack_direction_to_css(layout.direction) let justify: String = stack_justify_to_css(layout.main_alignment) let align: String = stack_align_to_css(layout.cross_alignment) let gap: String = int_to_str(layout.gap_px) + "px" let wrap_val: String = if layout.wrap { "wrap" } else { "nowrap" } let style: String = "display:flex;flex-direction:" + direction + ";justify-content:" + justify + ";align-items:" + align + ";gap:" + gap + ";flex-wrap:" + wrap_val let base_class: String = "el-stack el-stack--" + layout.direction let cls: String = if str_eq(class_extra, "") { base_class } else { base_class + " " + class_extra } "
" + children + "
" } fn vstack_to_html(spacing_px: Int, children: String, class_extra: String) -> String { let layout: StackLayout = { "direction": "column", "main_alignment": "start", "cross_alignment": "stretch", "gap_px": spacing_px, "wrap": true, "spacing_token": "" } stack_to_html(layout, children, class_extra) } fn hstack_to_html(spacing_px: Int, children: String, class_extra: String) -> String { let layout: StackLayout = { "direction": "row", "main_alignment": "start", "cross_alignment": "center", "gap_px": spacing_px, "wrap": true, "spacing_token": "" } stack_to_html(layout, children, class_extra) } fn zstack_to_html(children: String, class_extra: String) -> String { let base_class: String = "el-stack el-stack--depth" let cls: String = if str_eq(class_extra, "") { base_class } else { base_class + " " + class_extra } "
" + children + "
" } fn grid_to_html(layout: GridLayout, children: String, class_extra: String) -> String { let css: String = grid_to_css(layout) let base_class: String = "el-grid" let cls: String = if str_eq(class_extra, "") { base_class } else { base_class + " " + class_extra } "
" + children + "
" } fn scroll_to_html(layout: ScrollView, children: String, class_extra: String) -> String { let overflow: String = if str_eq(layout.axis, "x") { "overflow-x:auto;overflow-y:hidden" } else if str_eq(layout.axis, "both") { "overflow:auto" } else { "overflow-x:hidden;overflow-y:auto" } let base_class: String = "el-scroll" let cls: String = if str_eq(class_extra, "") { base_class } else { base_class + " " + class_extra } "
" + children + "
" } // ── Entry — smoke test ────────────────────────────────────────────────────── let r: String = responsive_fixed("1") let r = responsive_set(r, "md", "2") let r = responsive_set(r, "lg", "3") println("[el-layout] cols at lg = " + responsive_resolve(r, "lg")) let v: String = vstack_to_html(16, "

hello

", "") println("[el-layout] vstack = " + v)