From c6f18c263697ea6e7b0c3f05ad6a36d0d058802d Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Sat, 7 Nov 2026 20:29:59 -0800 Subject: [PATCH] feat: add independent flex shrink for Web and MyGo --- docs/api-migrations.md | 28 +++++++++++ .../src/main/kotlin/dev/lui/LuiKindRules.kt | 1 + .../src/main/kotlin/dev/lui/LuiWireSchema.kt | 6 ++- .../LUIAppleBackend/LUIWireProtocol.swift | 4 ++ .../LUIAppleBackend/LUIWireSchema.swift | 6 ++- platform/mygo/SUPPORT.md | 7 +-- platform/mygo/capabilities.json | 12 +++-- platform/mygo/schema_generated.go | 9 ++-- platform/mygo/style.go | 2 +- platform/mygo/style_test.go | 13 ++++- platform/web/melange/render/lui_web_props.ml | 3 ++ schema/components.json | 14 +++++- schema/mygo-manifest.json | 22 ++++----- src/lui_elements.ml | 15 +++--- src/lui_elements.mli | 4 ++ src/lui_protocol.ml | 4 +- src/lui_protocol.mli | 1 + src/lui_runtime.ml | 5 ++ src/lui_wire_schema.ml | 14 +++--- test/test_lui.ml | 49 +++++++++++++++++++ 20 files changed, 176 insertions(+), 43 deletions(-) diff --git a/docs/api-migrations.md b/docs/api-migrations.md index e2b2da4b..38f21eb6 100644 --- a/docs/api-migrations.md +++ b/docs/api-migrations.md @@ -1,5 +1,33 @@ # LUI API migration guide +## Unreleased: independent flex shrink on Web and MyGo + +`row`, `column` and `box` accept `?shrink:float`. The typed `ShrinkValue` +property uses wire name `shrink` and accepts only finite, nonnegative floats. +Only these three standard kinds admit it. Web maps it to CSS `flex-shrink`; +MyGo uses the existing `Element.Shrink` without an SDK or dependency update. +Generic, SwiftUI and Kotlin profiles reject explicit shrink values; Apple and +Android also reject direct standard wire patches rather than ignoring them. +Extensions/raw markup do not gain a shrink property or escape hatch. + +Omitting or removing shrink preserves/restores the existing default factor 1. +Setting or removing it does not change `grow` or flex basis. Existing callers +need no changes. New callers must use a host supporting this property and a +LUI version that knows the new wire name; older hosts reject that opt-in. + +For a bounded, clipped single-line strip, opt in on each direct pill container: + +```ocaml +row ~flex_wrap:`nowrap + [row ~shrink:0. [text ~value:"First" []]; + row ~shrink:0. [text ~value:"Second" []]] +``` + +Use the existing typed overflow property on the strip for clipping. Shrink on +the strip itself does not disable shrinking its children. Remove unnecessary +child `grow` props to keep intrinsic sizing; do not substitute `grow:0` for +`shrink:0`, because MyGo's `Grow` also changes an auto basis to zero percent. + ## Unreleased: MyGo links in text flow Text-content `link` nodes inside a native text flow now use MyGo's inline Link diff --git a/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt b/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt index 4b1d84f6..c531ef9c 100644 --- a/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt +++ b/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt @@ -315,6 +315,7 @@ internal object LuiKindRules { /** Ported from the Flutter backend's `_supports`: whether a `set-prop` * op is admissible for the node's kind. */ fun supports(kind: LuiNodeKind, property: String, value: LuiWireValue): Boolean { + if (property == "shrink") return false when (property) { "virtual-total-count", "virtual-generation", "virtual-indices", "virtual-scroll-index" -> return kind == LuiNodeKind.virtualList && schemaValueSupported(property, value) diff --git a/platform/android/lui/src/main/kotlin/dev/lui/LuiWireSchema.kt b/platform/android/lui/src/main/kotlin/dev/lui/LuiWireSchema.kt index d0cb2750..df69c397 100644 --- a/platform/android/lui/src/main/kotlin/dev/lui/LuiWireSchema.kt +++ b/platform/android/lui/src/main/kotlin/dev/lui/LuiWireSchema.kt @@ -295,6 +295,7 @@ enum class LuiProperty(val wireName: String) { pointerCapture("pointer-capture"), indeterminate("indeterminate"), toggleDetailEnabled("toggle-detail-enabled"), + shrink("shrink"), ; companion object { @@ -337,12 +338,12 @@ object LuiSchemaMatrix { LuiNodeKind.checkbox to setOf(LuiProperty.gap, LuiProperty.labelOpacity, LuiProperty.indicatorBorderColor, LuiProperty.checkedIndicatorBorderColor), LuiNodeKind.radio to setOf(LuiProperty.gap, LuiProperty.labelOpacity), LuiNodeKind.grid to setOf(LuiProperty.main, LuiProperty.cross), - LuiNodeKind.row to setOf(LuiProperty.orientation), + LuiNodeKind.row to setOf(LuiProperty.orientation, LuiProperty.shrink), LuiNodeKind.listItem to setOf(LuiProperty.display), LuiNodeKind.dialog to setOf(LuiProperty.description, LuiProperty.background, LuiProperty.foreground, LuiProperty.borderColor, LuiProperty.borderWidth, LuiProperty.cornerRadius, LuiProperty.gap, LuiProperty.transition), LuiNodeKind.drawer to setOf(LuiProperty.transition), LuiNodeKind.sheet to setOf(LuiProperty.transition), - LuiNodeKind.box to setOf(LuiProperty.inert), + LuiNodeKind.box to setOf(LuiProperty.inert, LuiProperty.shrink), LuiNodeKind.popover to setOf(LuiProperty.collisionPadding), LuiNodeKind.dropdownMenu to setOf(LuiProperty.collisionPadding), LuiNodeKind.tooltip to setOf(LuiProperty.collisionPadding), @@ -354,5 +355,6 @@ object LuiSchemaMatrix { LuiNodeKind.searchField to setOf(LuiProperty.readOnly), LuiNodeKind.textarea to setOf(LuiProperty.readOnly), LuiNodeKind.combobox to setOf(LuiProperty.readOnly), + LuiNodeKind.column to setOf(LuiProperty.shrink), ) } diff --git a/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift b/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift index 5c0cbf3a..5630959b 100644 --- a/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift +++ b/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift @@ -298,6 +298,8 @@ enum LUIWireValue: Decodable, Equatable { case .grow: guard let value = doubleValue else { return false } return value.isFinite && value >= 0 + case .shrink: + return false case .edge: guard let value = stringValue else { return false } return Self.edges.contains(value) @@ -1086,6 +1088,7 @@ struct LUIRetainedTree { } private static func supports(_ property: LUIProperty, on kind: LUINodeKind) -> Bool { + if property == .shrink { return false } switch property { case .virtualTotalCount, .virtualGeneration, .virtualIndices, .virtualScrollIndex: return kind == .virtualList @@ -1120,6 +1123,7 @@ struct LUIRetainedTree { kind == .popover || kind == .link || isHorizontalGroup(kind) case .grow: kind != .avatar && kind != .tooltip && !isModalSurface(kind) + case .shrink: false case .columns: kind == .grid case .gridColumnSpan: kind != .root && kind != .dialog && kind != .drawer && kind != .sheet diff --git a/platform/apple/Sources/LUIAppleBackend/LUIWireSchema.swift b/platform/apple/Sources/LUIAppleBackend/LUIWireSchema.swift index 5e0d087c..2dbe2306 100644 --- a/platform/apple/Sources/LUIAppleBackend/LUIWireSchema.swift +++ b/platform/apple/Sources/LUIAppleBackend/LUIWireSchema.swift @@ -288,6 +288,7 @@ enum LUIProperty: String, Decodable, Hashable { case pointerCapture = "pointer-capture" case indeterminate = "indeterminate" case toggleDetailEnabled = "toggle-detail-enabled" + case shrink = "shrink" } enum LUISchemaMatrix { @@ -317,12 +318,12 @@ enum LUISchemaMatrix { .checkbox: [.gap, .labelOpacity, .indicatorBorderColor, .checkedIndicatorBorderColor], .radio: [.gap, .labelOpacity], .grid: [.main, .cross], - .row: [.orientation], + .row: [.orientation, .shrink], .listItem: [.display], .dialog: [.description, .background, .foreground, .borderColor, .borderWidth, .cornerRadius, .gap, .transition], .drawer: [.transition], .sheet: [.transition], - .box: [.inert], + .box: [.inert, .shrink], .popover: [.collisionPadding], .dropdownMenu: [.collisionPadding], .tooltip: [.collisionPadding], @@ -334,5 +335,6 @@ enum LUISchemaMatrix { .searchField: [.readOnly], .textarea: [.readOnly], .combobox: [.readOnly], + .column: [.shrink], ] } diff --git a/platform/mygo/SUPPORT.md b/platform/mygo/SUPPORT.md index 044aefdd..31ca8361 100644 --- a/platform/mygo/SUPPORT.md +++ b/platform/mygo/SUPPORT.md @@ -21,8 +21,8 @@ Contract counts and hashes come from the canonical OCaml export in [the manifest | Kind | Built-in route | Properties | Allowed child kinds | Events | Behavior evidence | | --- | --- | --- | --- | --- | --- | | `root` | render.go; theme.go | 3 | 86 | 0 | [TestRenderAndEvents](backend_test.go), [TestThemeScopeAndMode](extensions_test.go) | -| `row` | render.go; style.go | 95 | 81 | 11 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | -| `column` | render.go; style.go | 92 | 81 | 8 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | +| `row` | render.go; style.go | 96 | 81 | 11 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | +| `column` | render.go; style.go | 93 | 81 | 8 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | | `grid` | render.go; style.go | 82 | 81 | 3 | [TestLayoutGeometry](extensions_test.go), [TestLayoutGridSpansAndPropertyRemovalChangeGeometry](layout_test.go) | | `stack` | layout.go; style.go | 79 | 81 | 3 | [TestLayoutStackUsesLargestIntrinsicChildAndRetainsOrder](layout_test.go), [TestLayoutStyleZOrderChangesPaintAndPressWithoutChangingLayout](style_test.go) | | `edge-inset` | layout.go; style.go | 81 | 81 | 3 | [TestLayoutEdgeInsetIncludesEveryPinnedChildAndVisibility](layout_test.go), [TestLayoutEdgeInsetSurfaceBelongsOnlyToPinnedRegion](layout_test.go) | @@ -32,7 +32,7 @@ Contract counts and hashes come from the canonical OCaml export in [the manifest | `card` | render.go; style.go | 89 | 81 | 3 | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go), [TestLayoutStyleShadowOpacityStatesAndRemoval](style_test.go) | | `alert` | render.go; style.go | 83 | 81 | 3 | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go), [TestLayoutStyleShadowOpacityStatesAndRemoval](style_test.go) | | `bubble` | render.go; style.go | 84 | 81 | 3 | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go), [TestLayoutStyleShadowOpacityStatesAndRemoval](style_test.go) | -| `box` | render.go; style.go | 94 | 81 | 11 | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go), [TestLayoutStyleShadowOpacityStatesAndRemoval](style_test.go) | +| `box` | render.go; style.go | 95 | 81 | 11 | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go), [TestLayoutStyleShadowOpacityStatesAndRemoval](style_test.go) | | `text` | render.go; style.go | 86 | 86 | 9 | [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go), [TestLayoutSharedTextWrappingAndRemoval](layout_test.go), [TestLayoutStyleLogicalAndUnitlessLineHeightAndDecoration](style_test.go) | | `heading` | render.go; style.go | 85 | 81 | 3 | [TestLayoutStyleAccessibleHeadingAndNavigationSemantics](style_test.go) | | `paragraph` | render.go; style.go | 85 | 81 | 3 | [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go), [TestLayoutSharedTextWrappingAndRemoval](layout_test.go), [TestLayoutStyleLogicalAndUnitlessLineHeightAndDecoration](style_test.go) | @@ -308,6 +308,7 @@ Contract counts and hashes come from the canonical OCaml export in [the manifest | `pointer-capture` | events.go | — | [TestEventsCapturedPointerCancel](events_test.go) | | `indeterminate` | controls.go | — | [TestControlsCheckboxMixedDetail](controls_test.go) | | `toggle-detail-enabled` | controls.go; events.go | — | [TestControlsCheckboxMixedDetail](controls_test.go) | +| `shrink` | style.go; MyGo Element.Shrink | — | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go) | ## Event bridge diff --git a/platform/mygo/capabilities.json b/platform/mygo/capabilities.json index 56cd9fb1..f838d51c 100644 --- a/platform/mygo/capabilities.json +++ b/platform/mygo/capabilities.json @@ -30,7 +30,7 @@ "extensions_test.go:TestLayoutGeometry", "layout_test.go:TestLayoutSharedTypographyAndSpacingRestoreInheritedValues" ], - "contractHash": "8607888dd0b74341aaf93b9a2007d4400d8654129b0a6bca393b82eb27b53b13" + "contractHash": "b3c4590cc5c33a3413f78e85e4c27c4f3e60a976a89a8b9d9b8a8d3c56f2a6a9" }, "column": { "route": "render.go; style.go", @@ -38,7 +38,7 @@ "extensions_test.go:TestLayoutGeometry", "layout_test.go:TestLayoutSharedTypographyAndSpacingRestoreInheritedValues" ], - "contractHash": "d1625463275eac1a39011cec61b6bf07997f116c26977792579c7fdb6f2a1507" + "contractHash": "ce1c29d86837e82dd55f36b7b7dd8a6e1642807c51a8059c17e10fd0848b2222" }, "grid": { "route": "render.go; style.go", @@ -117,7 +117,7 @@ "style_test.go:TestLayoutStyleViewportFractionsAndContainerFrameRemoval", "style_test.go:TestLayoutStyleShadowOpacityStatesAndRemoval" ], - "contractHash": "53c3f165be3a92e51235e68b433156fc4b1594d4ee6845cc2ab1d4dc1a270b64" + "contractHash": "5171f2d9830dfa0251db34196a53df327ade6d4428410352b8902eb0c062f185" }, "text": { "route": "render.go; style.go", @@ -2334,6 +2334,12 @@ "evidence": [ "controls_test.go:TestControlsCheckboxMixedDetail" ] + }, + "shrink": { + "route": "style.go; MyGo Element.Shrink", + "evidence": [ + "style_test.go:TestLayoutStyleViewportFractionsAndContainerFrameRemoval" + ] } }, "events": { diff --git a/platform/mygo/schema_generated.go b/platform/mygo/schema_generated.go index ecd8e1d8..19083cf5 100644 --- a/platform/mygo/schema_generated.go +++ b/platform/mygo/schema_generated.go @@ -290,12 +290,13 @@ var wireProperties = map[string]uint8{ "pointer-capture": scalarBool, "indeterminate": scalarBool, "toggle-detail-enabled": scalarBool, + "shrink": scalarFloat, } var contractKindProperties = map[string]map[string]bool{ "root": {"accessibility-identifier": true, "theme": true, "theme-mode": true}, - "row": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "orientation": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "flex-wrap": true, "hover-foreground": true, "hover-border": true, "transition": true, "navigate-enabled": true, "grid-column-span": true, "pointer-capture": true}, - "column": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "flex-wrap": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, + "row": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "orientation": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "flex-wrap": true, "hover-foreground": true, "hover-border": true, "transition": true, "navigate-enabled": true, "grid-column-span": true, "pointer-capture": true, "shrink": true}, + "column": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "flex-wrap": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true, "shrink": true}, "grid": {"gap": true, "main": true, "cross": true, "grow": true, "columns": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-identifier": true, "style-class": true, "appear-enabled": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, "stack": {"grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-identifier": true, "style-class": true, "appear-enabled": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, "edge-inset": {"gap": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-identifier": true, "style-class": true, "appear-enabled": true, "theme": true, "theme-mode": true, "edge": true, "visible": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, @@ -305,7 +306,7 @@ var contractKindProperties = map[string]map[string]bool{ "card": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, "alert": {"text": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "variant": true, "appear-enabled": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "white-space": true, "text-overflow": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, "bubble": {"text": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "variant": true, "appear-enabled": true, "text-alignment": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "white-space": true, "text-overflow": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true}, - "box": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "flex-wrap": true, "hover-foreground": true, "hover-border": true, "transition": true, "inert": true, "navigate-enabled": true, "grid-column-span": true, "pointer-capture": true}, + "box": {"gap": true, "main": true, "cross": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-label": true, "accessibility-identifier": true, "style-class": true, "selected": true, "change-enabled": true, "toggle-enabled": true, "press-enabled": true, "appear-enabled": true, "role": true, "tree-level": true, "expanded": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "flex-wrap": true, "hover-foreground": true, "hover-border": true, "transition": true, "inert": true, "navigate-enabled": true, "grid-column-span": true, "pointer-capture": true, "shrink": true}, "text": {"text": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-identifier": true, "style-class": true, "press-enabled": true, "appear-enabled": true, "text-alignment": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "white-space": true, "text-overflow": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true, "line-clamp": true}, "heading": {"text": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-identifier": true, "style-class": true, "heading-level": true, "appear-enabled": true, "text-alignment": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "white-space": true, "text-overflow": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true, "line-clamp": true}, "paragraph": {"text": true, "grow": true, "padding": true, "padding-horizontal": true, "padding-vertical": true, "padding-top": true, "padding-right": true, "padding-bottom": true, "padding-left": true, "margin": true, "margin-horizontal": true, "margin-vertical": true, "margin-top": true, "margin-right": true, "margin-bottom": true, "margin-left": true, "background": true, "foreground": true, "border-color": true, "border-width": true, "corner-radius": true, "width": true, "height": true, "min-width": true, "max-width": true, "min-height": true, "max-height": true, "container-relative-frame": true, "container-relative-frame-inset": true, "accessibility-identifier": true, "style-class": true, "appear-enabled": true, "text-alignment": true, "theme": true, "theme-mode": true, "alignment": true, "pointer-enabled": true, "data-attrs": true, "as": true, "opacity": true, "display": true, "font-size": true, "font-weight": true, "font-style": true, "line-height": true, "letter-spacing": true, "position": true, "inset": true, "inset-top": true, "inset-right": true, "inset-bottom": true, "inset-left": true, "z-index": true, "white-space": true, "text-overflow": true, "overflow": true, "user-select": true, "cursor": true, "shadow": true, "hover-background": true, "hover-opacity": true, "hover-shadow": true, "pressed-background": true, "pressed-opacity": true, "pressed-shadow": true, "focus-shadow": true, "focus-within-background": true, "focus-within-border": true, "selected-background": true, "selected-shadow": true, "selected-hover-shadow": true, "disabled-opacity": true, "width-viewport": true, "height-viewport": true, "min-width-viewport": true, "max-width-viewport": true, "min-height-viewport": true, "max-height-viewport": true, "font-family": true, "text-decoration": true, "hover-foreground": true, "hover-border": true, "transition": true, "grid-column-span": true, "line-clamp": true}, @@ -943,7 +944,7 @@ func contract_property_value_supported(property string, value contractWireValue) value := contractMatch11.value.(string) return contract_cross_alignment_supported(value) } - if contractMatch10 == "grow" && (contractMatch11.kind == scalarFloat && (true)) { + if (contractMatch10 == "grow" || contractMatch10 == "shrink") && (contractMatch11.kind == scalarFloat && (true)) { value := contractMatch11.value.(float64) return (contract_is_finite(value) && (value >= float64(0.0))) } diff --git a/platform/mygo/style.go b/platform/mygo/style.go index 15d31395..3443ec4c 100644 --- a/platform/mygo/style.go +++ b/platform/mygo/style.go @@ -41,7 +41,7 @@ func (b *Backend) style(c *ui.Context, n Node, e ui.Element) { e.Label(label) } numeric := map[string]func(float32) ui.Element{ - "grow": e.Grow, "width": e.Width, "height": e.Height, + "grow": e.Grow, "shrink": e.Shrink, "width": e.Width, "height": e.Height, "min-width": e.MinWidth, "max-width": e.MaxWidth, "min-height": e.MinHeight, "max-height": e.MaxHeight, "opacity": e.Opacity, "border-width": func(v float32) ui.Element { return e.BorderWidth(v) }, "corner-radius": func(v float32) ui.Element { return e.Radius(v) }, diff --git a/platform/mygo/style_test.go b/platform/mygo/style_test.go index fea5f580..8e31a192 100644 --- a/platform/mygo/style_test.go +++ b/platform/mygo/style_test.go @@ -33,6 +33,17 @@ func TestLayoutStyleViewportFractionsAndContainerFrameRemoval(t *testing.T) { r = layoutRect(t, tester, "viewport child") layoutNear(t, "removed frame width", r.W, 40) layoutNear(t, "removed frame height", r.H, 20) + apply(t, b, 5, create(5, "row"), prop(5, "width", 100), prop(5, "height", 20), insert(2, 5, 1), + create(6, "box"), prop(6, "width", 80), prop(6, "min-width", 0), prop(6, "height", 20), prop(6, "accessibility-label", "shrink child"), insert(5, 6, 0), + create(7, "box"), prop(7, "width", 80), prop(7, "min-width", 0), prop(7, "height", 20), insert(5, 7, 1)) + tester.Frame() + layoutNear(t, "omitted shrink retains factor one", layoutRect(t, tester, "shrink child").W, 50) + apply(t, b, 6, prop(6, "shrink", 0.0)) + tester.Frame() + layoutNear(t, "zero shrink preserves authored basis", layoutRect(t, tester, "shrink child").W, 80) + apply(t, b, 7, Operation{Op: "remove-prop", ID: 6, Property: "shrink"}) + tester.Frame() + layoutNear(t, "removed shrink restores factor one", layoutRect(t, tester, "shrink child").W, 50) } func TestLayoutStyleRelativeAbsoluteOffsetsAndRemoval(t *testing.T) { @@ -343,7 +354,7 @@ func TestLayoutStyleOklchColorUsesNativeColorConversion(t *testing.T) { } func TestLayoutStyleUserSelectionCursorAndRemoval(t *testing.T) { - b := layoutBackend(t, "box", nil, prop(2, "user-select", "text"), prop(2, "cursor", "pointer"), prop(2,"pressed-background","#00ff00"), create(3, "text"), prop(3, "text", "copy words"), prop(3, "font-size", "24px"), insert(2, 3, 0)) + b := layoutBackend(t, "box", nil, prop(2, "user-select", "text"), prop(2, "cursor", "pointer"), prop(2, "pressed-background", "#00ff00"), create(3, "text"), prop(3, "text", "copy words"), prop(3, "font-size", "24px"), insert(2, 3, 0)) tester := ui.NewTester(b.View, 300, 120) r := layoutRect(t, tester, "copy words") tester.Move(r.X+10, r.Y+r.H/2) diff --git a/platform/web/melange/render/lui_web_props.ml b/platform/web/melange/render/lui_web_props.ml index cd9ba6df..0bc0c748 100644 --- a/platform/web/melange/render/lui_web_props.ml +++ b/platform/web/melange/render/lui_web_props.ml @@ -811,6 +811,8 @@ let rec apply_property renderer queue_split node kind dom_node property set_style dom_node "align-items" (cross_alignment_value alignment) | GrowValue, FloatValue grow -> set_style dom_node "flex-grow" (Js.Float.toString grow) + | ShrinkValue, FloatValue shrink -> + set_style dom_node "flex-shrink" (Js.Float.toString shrink) | GridColumns, IntValue columns -> apply_grid_columns dom_node columns | GridColumnSpan, IntValue span -> set_style dom_node "grid-column" ("span " ^ string_of_int span ^ " / span " ^ string_of_int span) @@ -1277,6 +1279,7 @@ let remove_property renderer queue_split node kind dom_node property = | MainAlignment -> set_style dom_node "justify-content" "" | CrossAlignment -> set_style dom_node "align-items" "" | GrowValue -> set_style dom_node "flex-grow" "" + | ShrinkValue -> set_style dom_node "flex-shrink" "" | GridColumns -> set_style dom_node "grid-auto-flow" ""; set_style dom_node "grid-auto-columns" ""; diff --git a/schema/components.json b/schema/components.json index 1c9f62c4..91b5ec4a 100644 --- a/schema/components.json +++ b/schema/components.json @@ -1851,6 +1851,11 @@ "lg": "ToggleDetailEnabled", "wire": "toggle-detail-enabled", "swift": "toggleDetailEnabled" + }, + { + "lg": "ShrinkValue", + "wire": "shrink", + "swift": "shrink" } ], "events": [ @@ -2263,7 +2268,8 @@ "cross" ], "row": [ - "orientation" + "orientation", + "shrink" ], "list-item": [ "display" @@ -2285,7 +2291,8 @@ "transition" ], "box": [ - "inert" + "inert", + "shrink" ], "popover": [ "collision-padding" @@ -2324,6 +2331,9 @@ ], "combobox": [ "read-only" + ], + "column": [ + "shrink" ] } } diff --git a/schema/mygo-manifest.json b/schema/mygo-manifest.json index 8a5ab38a..b802a39c 100644 --- a/schema/mygo-manifest.json +++ b/schema/mygo-manifest.json @@ -1,13 +1,13 @@ { "version": 1, "sourceHashes": { - "schema/components.json": "0e660e5b1c2f7e2f02b6d6655cff72e9ca0e083fd67bb73f78f929ba16d063b9", - "src/lui_protocol.ml": "b7a3b65dded1374016db030b793a7525f450918be6d3a36bd9ceaa5c65fe22e3", - "src/lui_protocol.mli": "ee29ca26d1d39dae08699aefa525acdaa9c7047ada6804ca72d7761bd3608242", - "src/lui_wire_schema.ml": "b7664408ffdcfc03e06bd68fbd62e0a58df567695a1f6acbfd9fecdaa5c7821e", + "schema/components.json": "02c36ff0c02e23745463bcbbb98581fc1c910c578d31afe3b4156ff8f89efdc9", + "src/lui_protocol.ml": "58a96acdeaf87b6eff4344cf429381683329465c89281c80e6fad588834774e2", + "src/lui_protocol.mli": "5cc9ee211ad8c805215055f282df4382353c434e9d2368efa297341ab375518d", + "src/lui_wire_schema.ml": "01c2a45b9dc5059c0e2dbf66b68d03f04f7bcced914df4c1fdf3b2f8372b5973", "tooling/export_mygo_contract.ml": "a055e72c226d0825664551aa073f3cf709a55ae648fcbab62b5c42eac23882ba" }, - "generatedGoHash": "16c014884921c72ecc75c13d40009abb9607bc8fee525c3da74273c09661cc4a", + "generatedGoHash": "fea9d7c506edc69aa4ad1c6abf6c66a7c196b1674706d6129c478a19bac54580", "kinds": { "root": { "contractHash": "1cda829a4bd05ddb79ca21f04ed1d7a51dae8bf3d388bb7eecdf8d5e2f3ec1eb", @@ -17,15 +17,15 @@ "baseline": {} }, "row": { - "contractHash": "8607888dd0b74341aaf93b9a2007d4400d8654129b0a6bca393b82eb27b53b13", - "propertyCount": 95, + "contractHash": "b3c4590cc5c33a3413f78e85e4c27c4f3e60a976a89a8b9d9b8a8d3c56f2a6a9", + "propertyCount": 96, "childCount": 81, "eventCount": 11, "baseline": {} }, "column": { - "contractHash": "d1625463275eac1a39011cec61b6bf07997f116c26977792579c7fdb6f2a1507", - "propertyCount": 92, + "contractHash": "ce1c29d86837e82dd55f36b7b7dd8a6e1642807c51a8059c17e10fd0848b2222", + "propertyCount": 93, "childCount": 81, "eventCount": 8, "baseline": {} @@ -99,8 +99,8 @@ "baseline": {} }, "box": { - "contractHash": "53c3f165be3a92e51235e68b433156fc4b1594d4ee6845cc2ab1d4dc1a270b64", - "propertyCount": 94, + "contractHash": "5171f2d9830dfa0251db34196a53df327ade6d4428410352b8902eb0c062f185", + "propertyCount": 95, "childCount": 81, "eventCount": 11, "baseline": {} diff --git a/src/lui_elements.ml b/src/lui_elements.ml index 944dea77..47fe9017 100644 --- a/src/lui_elements.ml +++ b/src/lui_elements.ml @@ -696,12 +696,13 @@ let apply_pointer_events context node ?on_press_detail ?on_pointer_down Option.iter (register_pointer_down context node) on_pointer_down; Option.iter (register_pointer_up context node) on_pointer_up; Option.iter (register_context_menu_press context node) on_context_menu -let apply_universal context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition = +let apply_universal ?shrink context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition = Option.iter (Lui_ui.key context node) key; Option.iter (Lui_ui.int_property context node Gap) gap; Option.iter (Lui_ui.string_property context node MainAlignment) (Option.map main_alignment_value main); Option.iter (Lui_ui.string_property context node CrossAlignment) (Option.map cross_alignment_value cross); Option.iter (Lui_ui.float_property context node GrowValue) grow; + Option.iter (Lui_ui.float_property context node ShrinkValue) shrink; Option.iter (Lui_ui.int_property context node GridColumns) columns; Option.iter (Lui_ui.int_property context node PaddingValue) padding; Option.iter (Lui_ui.int_property context node PaddingHorizontal) padding_horizontal; @@ -748,11 +749,11 @@ let apply_universal context node ~key ~gap ~main ~cross ~grow ~columns ~padding Option.iter (register_pointer_leave context node) on_pointer_leave; Option.iter (Lui_ui.string_property context node Transition) transition -let row ?as_ ?key ?gap ?main ?cross ?grow ?columns ?orientation ?orientation_signal ?padding ?padding_horizontal ?padding_vertical ?margin ?margin_horizontal ?margin_vertical ?margin_top ?margin_right ?margin_bottom ?margin_left ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?max_width ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?data_attrs ?data_attrs_signal ?on_appear ?on_pointer_enter ?on_pointer_leave ?pointer_capture ?on_pointer_down ?on_pointer_move ?on_pointer_up ?on_pointer_cancel ?on_navigation_key ?opacity ?opacity_signal ?display ?flex_wrap ?transition (children : t list) : t = +let row ?as_ ?key ?gap ?main ?cross ?grow ?shrink ?columns ?orientation ?orientation_signal ?padding ?padding_horizontal ?padding_vertical ?margin ?margin_horizontal ?margin_vertical ?margin_top ?margin_right ?margin_bottom ?margin_left ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?max_width ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?data_attrs ?data_attrs_signal ?on_appear ?on_pointer_enter ?on_pointer_leave ?pointer_capture ?on_pointer_down ?on_pointer_move ?on_pointer_up ?on_pointer_cancel ?on_navigation_key ?opacity ?opacity_signal ?display ?flex_wrap ?transition (children : t list) : t = fun context parent -> let node = Lui_ui.row context in Option.iter (fun tag -> Lui_ui.string_property context node As (element_tag_value tag)) as_; - apply_universal context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition; + apply_universal ?shrink context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition; Option.iter (Lui_ui.float_property context node Opacity) opacity; Option.iter (Lui_ui.float_property_signal context node Opacity) opacity_signal; Option.iter (Lui_ui.string_property context node DisplayValue) (Option.map display_value display); @@ -769,11 +770,11 @@ let row ?as_ ?key ?gap ?main ?cross ?grow ?columns ?orientation ?orientation_sig mount_children context node children; node -let column ?as_ ?key ?gap ?main ?cross ?grow ?grow_signal ?columns ?padding ?padding_horizontal ?padding_vertical ?margin ?margin_horizontal ?margin_vertical ?margin_top ?margin_right ?margin_bottom ?margin_left ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?min_width_signal ?max_width ?max_width_signal ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?data_attrs ?data_attrs_signal ?on_appear ?on_pointer_enter ?on_pointer_leave ?on_press_detail ?on_pointer_down ?on_pointer_up ?opacity ?opacity_signal ?display ?display_signal ?flex_wrap ?transition (children : t list) : t = +let column ?as_ ?key ?gap ?main ?cross ?grow ?shrink ?grow_signal ?columns ?padding ?padding_horizontal ?padding_vertical ?margin ?margin_horizontal ?margin_vertical ?margin_top ?margin_right ?margin_bottom ?margin_left ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?min_width_signal ?max_width ?max_width_signal ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?data_attrs ?data_attrs_signal ?on_appear ?on_pointer_enter ?on_pointer_leave ?on_press_detail ?on_pointer_down ?on_pointer_up ?opacity ?opacity_signal ?display ?display_signal ?flex_wrap ?transition (children : t list) : t = fun context parent -> let node = Lui_ui.column context in Option.iter (fun tag -> Lui_ui.string_property context node As (element_tag_value tag)) as_; - apply_universal context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition; + apply_universal ?shrink context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition; Option.iter (Lui_ui.float_property_signal context node GrowValue) grow_signal; Option.iter (Lui_ui.int_property_signal context node MinWidth) min_width_signal; Option.iter (Lui_ui.int_property_signal context node MaxWidth) max_width_signal; @@ -899,10 +900,10 @@ let bubble ?key ?gap ?main ?cross ?grow ?columns ?padding ?padding_horizontal ?p mount_children context node children; node -let box ?key ?gap ?main ?cross ?grow ?columns ?padding ?padding_horizontal ?padding_vertical ?margin ?margin_horizontal ?margin_vertical ?margin_top ?margin_right ?margin_bottom ?margin_left ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?max_width ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?data_attrs ?data_attrs_signal ?on_appear ?on_pointer_enter ?on_pointer_leave ?pointer_capture ?on_pointer_down ?on_pointer_move ?on_pointer_up ?on_pointer_cancel ?on_navigation_key ?opacity ?opacity_signal ?display ?flex_wrap ?inert ?inert_signal ?transition (children : t list) : t = +let box ?key ?gap ?main ?cross ?grow ?shrink ?columns ?padding ?padding_horizontal ?padding_vertical ?margin ?margin_horizontal ?margin_vertical ?margin_top ?margin_right ?margin_bottom ?margin_left ?background ?foreground ?border_color ?border_width ?corner_radius ?width ?height ?min_width ?max_width ?min_height ?max_height ?container_relative_frame ?container_relative_frame_inset ?accessibility_identifier ?accessibility_identifier_signal ?foreground_signal ?background_signal ?style_class ?data_attrs ?data_attrs_signal ?on_appear ?on_pointer_enter ?on_pointer_leave ?pointer_capture ?on_pointer_down ?on_pointer_move ?on_pointer_up ?on_pointer_cancel ?on_navigation_key ?opacity ?opacity_signal ?display ?flex_wrap ?inert ?inert_signal ?transition (children : t list) : t = fun context parent -> let node = Lui_ui.box context in - apply_universal context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition; + apply_universal ?shrink context node ~key ~gap ~main ~cross ~grow ~columns ~padding ~padding_horizontal ~padding_vertical ~margin ~margin_horizontal ~margin_vertical ~margin_top ~margin_right ~margin_bottom ~margin_left ~background ~foreground ~border_color ~border_width ~corner_radius ~width ~height ~min_width ~max_width ~min_height ~max_height ~container_relative_frame ~container_relative_frame_inset ~accessibility_identifier ~accessibility_identifier_signal ~foreground_signal ~background_signal ~style_class ~data_attrs ~data_attrs_signal ~on_appear ~on_pointer_enter ~on_pointer_leave ~transition; Option.iter (Lui_ui.float_property context node Opacity) opacity; Option.iter (Lui_ui.float_property_signal context node Opacity) opacity_signal; Option.iter (Lui_ui.string_property context node DisplayValue) (Option.map display_value display); diff --git a/src/lui_elements.mli b/src/lui_elements.mli index 632bbfaf..24a068b9 100644 --- a/src/lui_elements.mli +++ b/src/lui_elements.mli @@ -325,6 +325,7 @@ val register_resize : a web DOM contract for delegated-event readers, CSS attribute selectors and e2e locators; native hosts parse-ignore the prop. *) val apply_universal : + ?shrink:float -> Lui_ui.ui_context -> int -> key:string option -> @@ -374,6 +375,7 @@ val row : ?main:main_alignment -> ?cross:cross_alignment -> ?grow:float -> + ?shrink:float -> ?columns:int -> ?orientation:orientation -> ?orientation_signal:orientation Signal.signal -> @@ -428,6 +430,7 @@ val column : ?main:main_alignment -> ?cross:cross_alignment -> ?grow:float -> + ?shrink:float -> ?grow_signal:float Signal.signal -> ?columns:int -> ?padding:int -> @@ -902,6 +905,7 @@ val box : ?main:main_alignment -> ?cross:cross_alignment -> ?grow:float -> + ?shrink:float -> ?columns:int -> ?padding:int -> ?padding_horizontal:int -> diff --git a/src/lui_protocol.ml b/src/lui_protocol.ml index 17e06a92..57567405 100644 --- a/src/lui_protocol.ml +++ b/src/lui_protocol.ml @@ -310,6 +310,7 @@ type property = | PointerCapture | Indeterminate | ToggleDetailEnabled + | ShrinkValue module Property_map = Map.Make @@ -973,6 +974,7 @@ let common_property_supported kind property = || horizontal_container kind | GrowValue -> kind <> Avatar && (not (modal_surface kind)) && kind <> Tooltip + | ShrinkValue -> kind = Row || kind = Column || kind = Box | GridColumns -> kind = Grid | GridColumnSpan -> kind <> Root && not (modal_surface kind) && kind <> Tooltip | PaddingValue | PaddingHorizontal | PaddingVertical @@ -1620,7 +1622,7 @@ let property_value_supported property value = | Gap, IntValue value -> value >= 0 | MainAlignment, StringValue value -> main_alignment_supported value | CrossAlignment, StringValue value -> cross_alignment_supported value - | GrowValue, FloatValue value -> is_finite value && value >= 0.0 + | (GrowValue | ShrinkValue), FloatValue value -> is_finite value && value >= 0.0 | GridColumns, IntValue value -> value >= 0 | GridColumnSpan, IntValue value -> value > 0 | PaddingValue, IntValue _ -> true diff --git a/src/lui_protocol.mli b/src/lui_protocol.mli index c6559f34..9502cb8d 100644 --- a/src/lui_protocol.mli +++ b/src/lui_protocol.mli @@ -308,6 +308,7 @@ type property = | PointerCapture | Indeterminate | ToggleDetailEnabled + | ShrinkValue module Property_map : Map.S with type key = property diff --git a/src/lui_runtime.ml b/src/lui_runtime.ml index 4ef10a01..93f6593d 100644 --- a/src/lui_runtime.ml +++ b/src/lui_runtime.ml @@ -1851,6 +1851,11 @@ let notify_segment_property application node property value = let set_prop application node property value = let node = canonical_node application node in let kind = require_standard_node_kind application node in + if property = ShrinkValue then + (match application.runtime_backend.backend_profile.profile_host with + | WebHost | MyGoHost -> () + | GenericHost | SwiftUIHost | KotlinHost -> + invalid_arg "shrink is unsupported by backend profile"); if not (property_supported kind property) then invalid_arg (Printf.sprintf diff --git a/src/lui_wire_schema.ml b/src/lui_wire_schema.ml index bf905d35..e56923e3 100644 --- a/src/lui_wire_schema.ml +++ b/src/lui_wire_schema.ml @@ -382,6 +382,7 @@ let property_name property = | PointerCapture -> "pointer-capture" | Indeterminate -> "indeterminate" | ToggleDetailEnabled -> "toggle-detail-enabled" + | ShrinkValue -> "shrink" let kind_property_matrix kind = match kind with @@ -411,12 +412,12 @@ let kind_extra_properties kind = | Checkbox -> [ Gap; LabelOpacity; IndicatorBorderColor; CheckedIndicatorBorderColor ] | Radio -> [ Gap; LabelOpacity ] | Grid -> [ MainAlignment; CrossAlignment ] - | Row -> [ OrientationValue ] + | Row -> [ OrientationValue; ShrinkValue ] | ListItem -> [ DisplayValue ] | Dialog -> [ DescriptionValue; BackgroundValue; ForegroundValue; BorderColorValue; BorderWidth; CornerRadius; Gap; Transition ] | Drawer -> [ Transition ] | Sheet -> [ Transition ] - | Box -> [ Inert ] + | Box -> [ Inert; ShrinkValue ] | Popover -> [ CollisionPadding ] | DropdownMenu -> [ CollisionPadding ] | Tooltip -> [ CollisionPadding ] @@ -428,11 +429,12 @@ let kind_extra_properties kind = | SearchField -> [ ReadOnly ] | Textarea -> [ ReadOnly ] | Combobox -> [ ReadOnly ] + | Column -> [ ShrinkValue ] | _ -> [] let all_node_kinds = [ Root; Row; Column; Grid; Stack; EdgeInset; Overlay; ViewThatFits; Panel; Card; Alert; Bubble; Box; Text; Heading; Paragraph; Label; Button; ToggleButton; Toggle; RadioGroup; Radio; Slider; NumberStepper; TextField; SecureField; Input; SearchField; Textarea; Checkbox; SwitchControl; Progress; Divider; Scroll; ListContainer; VirtualList; Tabs; BottomTabs; BottomTab; ButtonGroup; ToggleGroup; Spacer; Spinner; Icon; Select; Combobox; DropdownMenu; ContextMenu; MenuItem; MenuTrigger; ListItem; Avatar; Image; MediaSurface; Stepper; Step; Timeline; TimelineItem; InputGroup; InputGroupActions; Breadcrumb; Pagination; Accordion; Table; TableRow; TableCell; Tree; Resizable; Split; Dialog; Drawer; Sheet; Tooltip; Toast; Toolbar; StatusBar; ListSection; ListSectionHeader; ListSectionFooter; SwipeActions; SwipeAction; FilePicker; Kbd; Link; FileImage; FilePreview; Popover ] -let all_properties = [ TextValue; Enabled; Gap; MainAlignment; CrossAlignment; GrowValue; GridColumns; PaddingValue; PaddingHorizontal; PaddingVertical; PaddingTop; PaddingRight; PaddingBottom; PaddingLeft; MarginValue; MarginHorizontal; MarginVertical; MarginTop; MarginRight; MarginBottom; MarginLeft; BackgroundValue; ForegroundValue; BorderColorValue; BorderWidth; CornerRadius; WidthValue; HeightValue; MinWidth; MaxWidth; MinHeight; MaxHeight; ContainerRelativeFrameValue; ContainerRelativeFrameInset; PlaceholderValue; AccessibilityLabel; AccessibilityIdentifier; StyleClass; HeadingLevel; Checked; ProgressValue; OrientationValue; PlacementValue; SizeValue; IconName; VariantValue; InlineIconName; IconPlacementValue; SelectionPlacementValue; Selected; Autofocus; SubmitOnEnter; LongPressEnabled; ChangeEnabled; ToggleEnabled; PressEnabled; SubmitEnabled; DoublePressEnabled; AppearEnabled; ImageIdValue; SurfaceIdValue; ActiveIndex; TitleValue; DescriptionValue; MetaValue; IndicatorValue; Connector; SourceX; SourceY; SourceWidth; SourceHeight; AnchorValue; AnchorTarget; AnchorAlignmentValue; AnchorOffset; TooltipDelay; DurationValue; TextAlignment; RoleValue; TreeLevel; Expanded; ResizeDuration; ResizeEasing; ResizeOrigin; ThemeValue; ThemeMode; KeyValue; SeparatorValue; StyleValue; ScrollTarget; ScrollAnchor; ScrollToken; ScrollAnimated; TrackVisibleRange; EdgeValue; PickerRequest; PickerTypes; PickerMultiple; PickerSource; PickerCompletion; MinValue; MaxValue; StepValue; Detents; Sizing; PathValue; UrlValue; MaxPixelSize; ImageFitValue; Visible; AlignmentValue; PointerEnabled; DataAttrs; As; AltValue; LoadingValue; ReferrerPolicy; TargetValue; Opacity; DisplayValue; TooltipText; TooltipKeys; InputType; PickerAccept; PickerDirectory; PopupX; PopupY; AvailableHeight; FontSize; FontWeight; FontStyle; LineHeight; LetterSpacing; Position; Inset; InsetTop; InsetRight; InsetBottom; InsetLeft; ZIndex; WhiteSpace; TextOverflow; Overflow; UserSelect; Cursor; Shadow; HoverBackground; HoverOpacity; HoverShadow; PressedBackground; PressedOpacity; PressedShadow; FocusShadow; FocusWithinBackground; FocusWithinBorder; SelectedBackground; SelectedShadow; SelectedHoverShadow; DisabledOpacity; WidthViewport; HeightViewport; MinWidthViewport; MaxWidthViewport; MinHeightViewport; MaxHeightViewport; FontFamily; TextDecoration; ListStyle; FlexWrap; HoverForeground; HoverBorder; Transition; Inert; NavigateEnabled; FirstPaneWidth; GridColumnSpan; LabelOpacity; SubmitOnBlur; AnchorAlignmentOffset; AnchorRect; ResizeHandleWidth; ResizeHandleOffset; IndicatorBorderColor; CheckedIndicatorBorderColor; CollisionPadding; LineClamp; ReadOnly; VirtualOverscan; VirtualItemExtent; VirtualTotalCount; VirtualGeneration; VirtualIndices; VirtualScrollIndex; PointerCapture; Indeterminate; ToggleDetailEnabled ] +let all_properties = [ TextValue; Enabled; Gap; MainAlignment; CrossAlignment; GrowValue; GridColumns; PaddingValue; PaddingHorizontal; PaddingVertical; PaddingTop; PaddingRight; PaddingBottom; PaddingLeft; MarginValue; MarginHorizontal; MarginVertical; MarginTop; MarginRight; MarginBottom; MarginLeft; BackgroundValue; ForegroundValue; BorderColorValue; BorderWidth; CornerRadius; WidthValue; HeightValue; MinWidth; MaxWidth; MinHeight; MaxHeight; ContainerRelativeFrameValue; ContainerRelativeFrameInset; PlaceholderValue; AccessibilityLabel; AccessibilityIdentifier; StyleClass; HeadingLevel; Checked; ProgressValue; OrientationValue; PlacementValue; SizeValue; IconName; VariantValue; InlineIconName; IconPlacementValue; SelectionPlacementValue; Selected; Autofocus; SubmitOnEnter; LongPressEnabled; ChangeEnabled; ToggleEnabled; PressEnabled; SubmitEnabled; DoublePressEnabled; AppearEnabled; ImageIdValue; SurfaceIdValue; ActiveIndex; TitleValue; DescriptionValue; MetaValue; IndicatorValue; Connector; SourceX; SourceY; SourceWidth; SourceHeight; AnchorValue; AnchorTarget; AnchorAlignmentValue; AnchorOffset; TooltipDelay; DurationValue; TextAlignment; RoleValue; TreeLevel; Expanded; ResizeDuration; ResizeEasing; ResizeOrigin; ThemeValue; ThemeMode; KeyValue; SeparatorValue; StyleValue; ScrollTarget; ScrollAnchor; ScrollToken; ScrollAnimated; TrackVisibleRange; EdgeValue; PickerRequest; PickerTypes; PickerMultiple; PickerSource; PickerCompletion; MinValue; MaxValue; StepValue; Detents; Sizing; PathValue; UrlValue; MaxPixelSize; ImageFitValue; Visible; AlignmentValue; PointerEnabled; DataAttrs; As; AltValue; LoadingValue; ReferrerPolicy; TargetValue; Opacity; DisplayValue; TooltipText; TooltipKeys; InputType; PickerAccept; PickerDirectory; PopupX; PopupY; AvailableHeight; FontSize; FontWeight; FontStyle; LineHeight; LetterSpacing; Position; Inset; InsetTop; InsetRight; InsetBottom; InsetLeft; ZIndex; WhiteSpace; TextOverflow; Overflow; UserSelect; Cursor; Shadow; HoverBackground; HoverOpacity; HoverShadow; PressedBackground; PressedOpacity; PressedShadow; FocusShadow; FocusWithinBackground; FocusWithinBorder; SelectedBackground; SelectedShadow; SelectedHoverShadow; DisabledOpacity; WidthViewport; HeightViewport; MinWidthViewport; MaxWidthViewport; MinHeightViewport; MaxHeightViewport; FontFamily; TextDecoration; ListStyle; FlexWrap; HoverForeground; HoverBorder; Transition; Inert; NavigateEnabled; FirstPaneWidth; GridColumnSpan; LabelOpacity; SubmitOnBlur; AnchorAlignmentOffset; AnchorRect; ResizeHandleWidth; ResizeHandleOffset; IndicatorBorderColor; CheckedIndicatorBorderColor; CollisionPadding; LineClamp; ReadOnly; VirtualOverscan; VirtualItemExtent; VirtualTotalCount; VirtualGeneration; VirtualIndices; VirtualScrollIndex; PointerCapture; Indeterminate; ToggleDetailEnabled; ShrinkValue ] type view_value = @@ -488,14 +490,14 @@ let view_contract = function | Row -> Some { view_result = "t"; view_children = ViewList "t"; - view_properties = [(As, view_element_tag, false); (KeyValue, ViewString, false); (Gap, ViewInt, false); (MainAlignment, view_main_alignment, false); (CrossAlignment, view_cross_alignment, false); (GrowValue, ViewFloat, false); (GridColumns, ViewInt, false); (OrientationValue, view_orientation, false); (PaddingValue, ViewInt, false); (PaddingHorizontal, ViewInt, false); (PaddingVertical, ViewInt, false); (MarginValue, ViewInt, false); (MarginHorizontal, ViewInt, false); (MarginVertical, ViewInt, false); (MarginTop, ViewInt, false); (MarginRight, ViewInt, false); (MarginBottom, ViewInt, false); (MarginLeft, ViewInt, false); (BackgroundValue, ViewString, false); (ForegroundValue, ViewString, false); (BorderColorValue, ViewString, false); (BorderWidth, ViewInt, false); (CornerRadius, ViewInt, false); (WidthValue, ViewInt, false); (HeightValue, ViewInt, false); (MinWidth, ViewInt, false); (MaxWidth, ViewInt, false); (MinHeight, ViewInt, false); (MaxHeight, ViewInt, false); (ContainerRelativeFrameValue, view_frame_axes, false); (ContainerRelativeFrameInset, ViewInt, false); (AccessibilityIdentifier, ViewString, false); (StyleClass, ViewString, false); (DataAttrs, ViewAttributes, false); (PointerCapture, ViewBool, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; + view_properties = [(As, view_element_tag, false); (KeyValue, ViewString, false); (Gap, ViewInt, false); (MainAlignment, view_main_alignment, false); (CrossAlignment, view_cross_alignment, false); (GrowValue, ViewFloat, false); (ShrinkValue, ViewFloat, false); (GridColumns, ViewInt, false); (OrientationValue, view_orientation, false); (PaddingValue, ViewInt, false); (PaddingHorizontal, ViewInt, false); (PaddingVertical, ViewInt, false); (MarginValue, ViewInt, false); (MarginHorizontal, ViewInt, false); (MarginVertical, ViewInt, false); (MarginTop, ViewInt, false); (MarginRight, ViewInt, false); (MarginBottom, ViewInt, false); (MarginLeft, ViewInt, false); (BackgroundValue, ViewString, false); (ForegroundValue, ViewString, false); (BorderColorValue, ViewString, false); (BorderWidth, ViewInt, false); (CornerRadius, ViewInt, false); (WidthValue, ViewInt, false); (HeightValue, ViewInt, false); (MinWidth, ViewInt, false); (MaxWidth, ViewInt, false); (MinHeight, ViewInt, false); (MaxHeight, ViewInt, false); (ContainerRelativeFrameValue, view_frame_axes, false); (ContainerRelativeFrameInset, ViewInt, false); (AccessibilityIdentifier, ViewString, false); (StyleClass, ViewString, false); (DataAttrs, ViewAttributes, false); (PointerCapture, ViewBool, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; view_events = ["appear"; "pointer-enter"; "pointer-leave"; "pointer-down"; "pointer-move"; "pointer-up"; "pointer-cancel"; "navigation-key"]; view_slots = []; } | Column -> Some { view_result = "t"; view_children = ViewList "t"; - view_properties = [(As, view_element_tag, false); (KeyValue, ViewString, false); (Gap, ViewInt, false); (MainAlignment, view_main_alignment, false); (CrossAlignment, view_cross_alignment, false); (GrowValue, ViewFloat, false); (GridColumns, ViewInt, false); (PaddingValue, ViewInt, false); (PaddingHorizontal, ViewInt, false); (PaddingVertical, ViewInt, false); (MarginValue, ViewInt, false); (MarginHorizontal, ViewInt, false); (MarginVertical, ViewInt, false); (MarginTop, ViewInt, false); (MarginRight, ViewInt, false); (MarginBottom, ViewInt, false); (MarginLeft, ViewInt, false); (BackgroundValue, ViewString, false); (ForegroundValue, ViewString, false); (BorderColorValue, ViewString, false); (BorderWidth, ViewInt, false); (CornerRadius, ViewInt, false); (WidthValue, ViewInt, false); (HeightValue, ViewInt, false); (MinWidth, ViewInt, false); (MaxWidth, ViewInt, false); (MinHeight, ViewInt, false); (MaxHeight, ViewInt, false); (ContainerRelativeFrameValue, view_frame_axes, false); (ContainerRelativeFrameInset, ViewInt, false); (AccessibilityIdentifier, ViewString, false); (StyleClass, ViewString, false); (DataAttrs, ViewAttributes, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; + view_properties = [(As, view_element_tag, false); (KeyValue, ViewString, false); (Gap, ViewInt, false); (MainAlignment, view_main_alignment, false); (CrossAlignment, view_cross_alignment, false); (GrowValue, ViewFloat, false); (ShrinkValue, ViewFloat, false); (GridColumns, ViewInt, false); (PaddingValue, ViewInt, false); (PaddingHorizontal, ViewInt, false); (PaddingVertical, ViewInt, false); (MarginValue, ViewInt, false); (MarginHorizontal, ViewInt, false); (MarginVertical, ViewInt, false); (MarginTop, ViewInt, false); (MarginRight, ViewInt, false); (MarginBottom, ViewInt, false); (MarginLeft, ViewInt, false); (BackgroundValue, ViewString, false); (ForegroundValue, ViewString, false); (BorderColorValue, ViewString, false); (BorderWidth, ViewInt, false); (CornerRadius, ViewInt, false); (WidthValue, ViewInt, false); (HeightValue, ViewInt, false); (MinWidth, ViewInt, false); (MaxWidth, ViewInt, false); (MinHeight, ViewInt, false); (MaxHeight, ViewInt, false); (ContainerRelativeFrameValue, view_frame_axes, false); (ContainerRelativeFrameInset, ViewInt, false); (AccessibilityIdentifier, ViewString, false); (StyleClass, ViewString, false); (DataAttrs, ViewAttributes, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; view_events = ["appear"; "pointer-enter"; "pointer-leave"; "press-detail"; "pointer-down"; "pointer-up"]; view_slots = []; } @@ -565,7 +567,7 @@ let view_contract = function | Box -> Some { view_result = "t"; view_children = ViewList "t"; - view_properties = [(KeyValue, ViewString, false); (Gap, ViewInt, false); (MainAlignment, view_main_alignment, false); (CrossAlignment, view_cross_alignment, false); (GrowValue, ViewFloat, false); (GridColumns, ViewInt, false); (PaddingValue, ViewInt, false); (PaddingHorizontal, ViewInt, false); (PaddingVertical, ViewInt, false); (MarginValue, ViewInt, false); (MarginHorizontal, ViewInt, false); (MarginVertical, ViewInt, false); (MarginTop, ViewInt, false); (MarginRight, ViewInt, false); (MarginBottom, ViewInt, false); (MarginLeft, ViewInt, false); (BackgroundValue, ViewString, false); (ForegroundValue, ViewString, false); (BorderColorValue, ViewString, false); (BorderWidth, ViewInt, false); (CornerRadius, ViewInt, false); (WidthValue, ViewInt, false); (HeightValue, ViewInt, false); (MinWidth, ViewInt, false); (MaxWidth, ViewInt, false); (MinHeight, ViewInt, false); (MaxHeight, ViewInt, false); (ContainerRelativeFrameValue, view_frame_axes, false); (ContainerRelativeFrameInset, ViewInt, false); (AccessibilityIdentifier, ViewString, false); (StyleClass, ViewString, false); (DataAttrs, ViewAttributes, false); (PointerCapture, ViewBool, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Inert, ViewBool, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; + view_properties = [(KeyValue, ViewString, false); (Gap, ViewInt, false); (MainAlignment, view_main_alignment, false); (CrossAlignment, view_cross_alignment, false); (GrowValue, ViewFloat, false); (ShrinkValue, ViewFloat, false); (GridColumns, ViewInt, false); (PaddingValue, ViewInt, false); (PaddingHorizontal, ViewInt, false); (PaddingVertical, ViewInt, false); (MarginValue, ViewInt, false); (MarginHorizontal, ViewInt, false); (MarginVertical, ViewInt, false); (MarginTop, ViewInt, false); (MarginRight, ViewInt, false); (MarginBottom, ViewInt, false); (MarginLeft, ViewInt, false); (BackgroundValue, ViewString, false); (ForegroundValue, ViewString, false); (BorderColorValue, ViewString, false); (BorderWidth, ViewInt, false); (CornerRadius, ViewInt, false); (WidthValue, ViewInt, false); (HeightValue, ViewInt, false); (MinWidth, ViewInt, false); (MaxWidth, ViewInt, false); (MinHeight, ViewInt, false); (MaxHeight, ViewInt, false); (ContainerRelativeFrameValue, view_frame_axes, false); (ContainerRelativeFrameInset, ViewInt, false); (AccessibilityIdentifier, ViewString, false); (StyleClass, ViewString, false); (DataAttrs, ViewAttributes, false); (PointerCapture, ViewBool, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Inert, ViewBool, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; view_events = ["appear"; "pointer-enter"; "pointer-leave"; "pointer-down"; "pointer-move"; "pointer-up"; "pointer-cancel"; "navigation-key"]; view_slots = []; } diff --git a/test/test_lui.ml b/test/test_lui.ml index 15df5468..18fcbb24 100644 --- a/test/test_lui.ml +++ b/test/test_lui.ml @@ -1713,6 +1713,54 @@ let test_drop_large_subtree_is_one_detach () = Alcotest.failf "dropping 4x nodes took %gs after %gs" elapsed_large elapsed_small +let test_shrink_contract () = + let open Lui_protocol in + let shrink = Lui_wire.property_of_name "shrink" in + List.iter (fun kind -> + Alcotest.(check bool) "shrink is restricted to layout containers" + (List.mem kind [Row; Column; Box]) (property_supported kind shrink)) + Lui_wire_schema.all_node_kinds; + List.iter (fun (value, accepted) -> + Alcotest.(check bool) "shrink accepts only finite nonnegative floats" + accepted (property_value_supported shrink value)) + [FloatValue 0., true; FloatValue 1.5, true; FloatValue (-1.), false; + FloatValue nan, false; FloatValue infinity, false; IntValue 0, false]; + let mount ?shrink host configure = + let backend = { (recording_backend ()) with + backend_profile = profile GenericOS host } in + Lui_app.create backend () (fun model () -> model) + (fun _ _ _ context parent -> + let node = Lui_elements.row ?shrink [] context parent in + configure context node; + node) + in + List.iter (fun host -> + let set = ref (fun () -> ()) and remove = ref (fun () -> ()) in + let app = mount host (fun context node -> + set := (fun () -> Lui_ui.float_property context node shrink 0.); + remove := (fun () -> + Lui_runtime.remove_prop context.Lui_ui.ui_application node shrink)) in + flush_app app; + Alcotest.(check bool) "omitted shrink leaves the host default" false + (List.exists (function SetProp (_, p, _) -> p = shrink | _ -> false) (all_ops ())); + !set (); + flush_app app; + Alcotest.(check bool) "supported host receives explicit nonshrink" true + (List.exists (function SetProp (_, p, FloatValue 0.) -> p = shrink | _ -> false) (all_ops ())); + !remove (); + flush_app app; + Alcotest.(check bool) "removal restores the host default" true + (List.exists (function RemoveProp (_, p) -> p = shrink | _ -> false) (all_ops ())); + Alcotest.(check bool) "shrink does not author grow" false + (List.exists (function SetProp (_, GrowValue, _) -> true | _ -> false) (all_ops ())); + ignore (Lui_app.dispose app)) + [WebHost; MyGoHost]; + List.iter (fun host -> + Alcotest.check_raises "unsupported host rejects opt-in" + (Invalid_argument "shrink is unsupported by backend profile") + (fun () -> ignore (mount ~shrink:0. host (fun _ _ -> ())))) + [GenericHost; SwiftUIHost; KotlinHost] + let test_float_encoding_rejects_non_finite () = let open Lui_protocol in Alcotest.check_raises "nan rejected" @@ -5666,6 +5714,7 @@ let () = test_data_attrs_protocol; Alcotest.test_case "finite float encoding" `Quick test_float_encoding_rejects_non_finite; + Alcotest.test_case "shrink contract" `Quick test_shrink_contract; Alcotest.test_case "as protocol" `Quick test_as_protocol; Alcotest.test_case "data-attrs + as emit" `Quick test_data_attrs_and_as_emit;