diff --git a/docs/api-migrations.md b/docs/api-migrations.md index de90318c..f90d674e 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: opt-in container focus-within observation (Web and MyGo) `row`, `column` and `box` accept `?on_focus_within:(bool -> unit)` on 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 423a6963..1696bb21 100644 --- a/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt +++ b/platform/android/lui/src/main/kotlin/dev/lui/LuiKindRules.kt @@ -316,6 +316,7 @@ internal object LuiKindRules { * op is admissible for the node's kind. */ fun supports(kind: LuiNodeKind, property: String, value: LuiWireValue): Boolean { if (property == "focus-within-enabled") return false + 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 90b0fcee..6ca98893 100644 --- a/platform/android/lui/src/main/kotlin/dev/lui/LuiWireSchema.kt +++ b/platform/android/lui/src/main/kotlin/dev/lui/LuiWireSchema.kt @@ -296,6 +296,7 @@ enum class LuiProperty(val wireName: String) { indeterminate("indeterminate"), toggleDetailEnabled("toggle-detail-enabled"), focusWithinEnabled("focus-within-enabled"), + shrink("shrink"), ; companion object { @@ -338,13 +339,13 @@ 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, LuiProperty.focusWithinEnabled), - LuiNodeKind.column to setOf(LuiProperty.focusWithinEnabled), + LuiNodeKind.row to setOf(LuiProperty.orientation, LuiProperty.focusWithinEnabled, LuiProperty.shrink), + LuiNodeKind.column to setOf(LuiProperty.focusWithinEnabled, 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, LuiProperty.focusWithinEnabled), + LuiNodeKind.box to setOf(LuiProperty.inert, LuiProperty.focusWithinEnabled, LuiProperty.shrink), LuiNodeKind.popover to setOf(LuiProperty.collisionPadding), LuiNodeKind.dropdownMenu to setOf(LuiProperty.collisionPadding), LuiNodeKind.tooltip to setOf(LuiProperty.collisionPadding), diff --git a/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift b/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift index a28fa469..50041da5 100644 --- a/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift +++ b/platform/apple/Sources/LUIAppleBackend/LUIWireProtocol.swift @@ -300,6 +300,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) @@ -1089,6 +1091,7 @@ struct LUIRetainedTree { private static func supports(_ property: LUIProperty, on kind: LUINodeKind) -> Bool { if property == .focusWithinEnabled { return false } + if property == .shrink { return false } switch property { case .virtualTotalCount, .virtualGeneration, .virtualIndices, .virtualScrollIndex: return kind == .virtualList @@ -1124,6 +1127,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 be5da83d..589b3446 100644 --- a/platform/apple/Sources/LUIAppleBackend/LUIWireSchema.swift +++ b/platform/apple/Sources/LUIAppleBackend/LUIWireSchema.swift @@ -289,6 +289,7 @@ enum LUIProperty: String, Decodable, Hashable { case indeterminate = "indeterminate" case toggleDetailEnabled = "toggle-detail-enabled" case focusWithinEnabled = "focus-within-enabled" + case shrink = "shrink" } enum LUISchemaMatrix { @@ -318,13 +319,13 @@ enum LUISchemaMatrix { .checkbox: [.gap, .labelOpacity, .indicatorBorderColor, .checkedIndicatorBorderColor], .radio: [.gap, .labelOpacity], .grid: [.main, .cross], - .row: [.orientation, .focusWithinEnabled], - .column: [.focusWithinEnabled], + .row: [.orientation, .focusWithinEnabled, .shrink], + .column: [.focusWithinEnabled, .shrink], .listItem: [.display], .dialog: [.description, .background, .foreground, .borderColor, .borderWidth, .cornerRadius, .gap, .transition], .drawer: [.transition], .sheet: [.transition], - .box: [.inert, .focusWithinEnabled], + .box: [.inert, .focusWithinEnabled, .shrink], .popover: [.collisionPadding], .dropdownMenu: [.collisionPadding], .tooltip: [.collisionPadding], diff --git a/platform/mygo/SUPPORT.md b/platform/mygo/SUPPORT.md index f6847482..4b601095 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 | 96 | 81 | 12 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | -| `column` | render.go; style.go | 93 | 81 | 9 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | +| `row` | render.go; style.go | 97 | 81 | 12 | [TestLayoutGeometry](extensions_test.go), [TestLayoutSharedTypographyAndSpacingRestoreInheritedValues](layout_test.go) | +| `column` | render.go; style.go | 94 | 81 | 9 | [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 | 95 | 81 | 12 | [TestLayoutStyleViewportFractionsAndContainerFrameRemoval](style_test.go), [TestLayoutStyleShadowOpacityStatesAndRemoval](style_test.go) | +| `box` | render.go; style.go | 96 | 81 | 12 | [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) | @@ -309,6 +309,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 0a14323a..32b4297e 100644 --- a/platform/mygo/capabilities.json +++ b/platform/mygo/capabilities.json @@ -30,7 +30,7 @@ "extensions_test.go:TestLayoutGeometry", "layout_test.go:TestLayoutSharedTypographyAndSpacingRestoreInheritedValues" ], - "contractHash": "5b37ca2312b59c73d5ca14e59389ed49a11b1dc3710bdee1409ce9675f9d8c50" + "contractHash": "2b0005ed2f3e54d3678748d9eb0a731c4ad075ae07bddb12fc87d58413bfb99f" }, "column": { "route": "render.go; style.go", @@ -38,7 +38,7 @@ "extensions_test.go:TestLayoutGeometry", "layout_test.go:TestLayoutSharedTypographyAndSpacingRestoreInheritedValues" ], - "contractHash": "6f35e51043ab8ae815796b2dd3a147f0a5e01b0fe15b9bab93f588710264f720" + "contractHash": "e1c4ba75a65b5346c3a0231c00d01f0ebaffd12eba823b625f75aa77c36e0bf8" }, "grid": { "route": "render.go; style.go", @@ -117,7 +117,7 @@ "style_test.go:TestLayoutStyleViewportFractionsAndContainerFrameRemoval", "style_test.go:TestLayoutStyleShadowOpacityStatesAndRemoval" ], - "contractHash": "028237d9cea015c951093e45aa35dc7c3493f8a22f27106e43dfc506da741866" + "contractHash": "f5ed3d53bdef46b4bf03eef8188877933fa0c005203581850747ecd1423f2c49" }, "text": { "route": "render.go; style.go", @@ -2338,6 +2338,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 5c38f424..02f2bae8 100644 --- a/platform/mygo/schema_generated.go +++ b/platform/mygo/schema_generated.go @@ -291,12 +291,13 @@ var wireProperties = map[string]uint8{ "indeterminate": scalarBool, "toggle-detail-enabled": scalarBool, "focus-within-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, "focus-within-enabled": 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, "focus-within-enabled": 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, "focus-within-enabled": 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, "focus-within-enabled": 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}, @@ -306,7 +307,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, "focus-within-enabled": 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, "focus-within-enabled": 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}, @@ -944,7 +945,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 0c0fd000..b26709e8 100644 --- a/platform/mygo/style.go +++ b/platform/mygo/style.go @@ -50,7 +50,7 @@ func (b *Backend) style(c *ui.Context, n Node, e ui.Element, focusWithin func(ui 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 55437662..bd454721 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) @@ -1279,6 +1281,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 65d0af7e..ea18629e 100644 --- a/schema/components.json +++ b/schema/components.json @@ -1856,6 +1856,11 @@ "lg": "FocusWithinEnabled", "wire": "focus-within-enabled", "swift": "focusWithinEnabled" + }, + { + "lg": "ShrinkValue", + "wire": "shrink", + "swift": "shrink" } ], "events": [ @@ -2276,10 +2281,12 @@ ], "row": [ "orientation", - "focus-within-enabled" + "focus-within-enabled", + "shrink" ], "column": [ - "focus-within-enabled" + "focus-within-enabled", + "shrink" ], "list-item": [ "display" @@ -2302,7 +2309,8 @@ ], "box": [ "inert", - "focus-within-enabled" + "focus-within-enabled", + "shrink" ], "popover": [ "collision-padding" diff --git a/schema/mygo-manifest.json b/schema/mygo-manifest.json index 6db72d99..607a4ae5 100644 --- a/schema/mygo-manifest.json +++ b/schema/mygo-manifest.json @@ -1,13 +1,13 @@ { "version": 1, "sourceHashes": { - "schema/components.json": "b89403cbbba99f8559aab6b2392301d87682c090df87b4508825d891759860fd", - "src/lui_protocol.ml": "9550ac6d4f88c764758db1ae5909e2915cdae1b455b9e96fd32d417f030eec21", - "src/lui_protocol.mli": "30ee5de82811ffff64dae818dd84597689bc1beae413f12ba4da010ca82b9aa6", - "src/lui_wire_schema.ml": "2f451922adfe58e144ca29b3f0ad21b9b195bb0ccf269322a6a975baaa527a3c", + "schema/components.json": "50b63836550156cd7244bf0aabcea04cd4852166d18089155ecc771a2dbd39a6", + "src/lui_protocol.ml": "835442f99097604deb8ac4c3c8f7a75e58433e26ce505ebb21204ff09ef24126", + "src/lui_protocol.mli": "8e1cd3b5455f64bf13fdcbcff6c27d89a6535ff76b41f2f5439dedda44a883e9", + "src/lui_wire_schema.ml": "312a87bfc912194bc6608af7a43b9cadbf53c3ac6985ca7fd247cab4377357e5", "tooling/export_mygo_contract.ml": "246ade7e088225ad68d25ce5ebc96a6a850fec3e66e1aa9d9debbd720e04bab1" }, - "generatedGoHash": "805e4cdaa0d83c19046193ac53ecdbb6464156ec301031f15825f4a73c50a002", + "generatedGoHash": "107980e57e46a8404d3a4b040a8d9ababa185028ce7125429ea28bb30fd954eb", "kinds": { "root": { "contractHash": "1cda829a4bd05ddb79ca21f04ed1d7a51dae8bf3d388bb7eecdf8d5e2f3ec1eb", @@ -17,15 +17,15 @@ "baseline": {} }, "row": { - "contractHash": "5b37ca2312b59c73d5ca14e59389ed49a11b1dc3710bdee1409ce9675f9d8c50", - "propertyCount": 96, + "contractHash": "2b0005ed2f3e54d3678748d9eb0a731c4ad075ae07bddb12fc87d58413bfb99f", + "propertyCount": 97, "childCount": 81, "eventCount": 12, "baseline": {} }, "column": { - "contractHash": "6f35e51043ab8ae815796b2dd3a147f0a5e01b0fe15b9bab93f588710264f720", - "propertyCount": 93, + "contractHash": "e1c4ba75a65b5346c3a0231c00d01f0ebaffd12eba823b625f75aa77c36e0bf8", + "propertyCount": 94, "childCount": 81, "eventCount": 9, "baseline": {} @@ -99,8 +99,8 @@ "baseline": {} }, "box": { - "contractHash": "028237d9cea015c951093e45aa35dc7c3493f8a22f27106e43dfc506da741866", - "propertyCount": 95, + "contractHash": "f5ed3d53bdef46b4bf03eef8188877933fa0c005203581850747ecd1423f2c49", + "propertyCount": 96, "childCount": 81, "eventCount": 12, "baseline": {} diff --git a/src/lui_elements.ml b/src/lui_elements.ml index 2cda3752..5971cfba 100644 --- a/src/lui_elements.ml +++ b/src/lui_elements.ml @@ -702,12 +702,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; @@ -754,11 +755,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_focus_within ?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_focus_within ?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); @@ -776,11 +777,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_focus_within ?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_focus_within ?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; @@ -907,10 +908,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_focus_within ?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_focus_within ?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 e9f974f3..c598271f 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 -> @@ -429,6 +431,7 @@ val column : ?main:main_alignment -> ?cross:cross_alignment -> ?grow:float -> + ?shrink:float -> ?grow_signal:float Signal.signal -> ?columns:int -> ?padding:int -> @@ -904,6 +907,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 2f4b83ab..64f7fad8 100644 --- a/src/lui_protocol.ml +++ b/src/lui_protocol.ml @@ -311,6 +311,7 @@ type property = | Indeterminate | ToggleDetailEnabled | FocusWithinEnabled + | ShrinkValue module Property_map = Map.Make @@ -978,6 +979,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 @@ -1626,7 +1628,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 6a804d27..1b4fb008 100644 --- a/src/lui_protocol.mli +++ b/src/lui_protocol.mli @@ -309,6 +309,7 @@ type property = | Indeterminate | ToggleDetailEnabled | FocusWithinEnabled + | ShrinkValue module Property_map : Map.S with type key = property diff --git a/src/lui_runtime.ml b/src/lui_runtime.ml index 3b4d77f7..24a120b6 100644 --- a/src/lui_runtime.ml +++ b/src/lui_runtime.ml @@ -1856,6 +1856,11 @@ let set_prop application node property value = invalid_arg "focus-within observation is unsupported by backend profile"); 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 a4d06b4b..2cd72b3e 100644 --- a/src/lui_wire_schema.ml +++ b/src/lui_wire_schema.ml @@ -383,6 +383,7 @@ let property_name property = | Indeterminate -> "indeterminate" | ToggleDetailEnabled -> "toggle-detail-enabled" | FocusWithinEnabled -> "focus-within-enabled" + | ShrinkValue -> "shrink" let kind_property_matrix kind = match kind with @@ -412,13 +413,13 @@ let kind_extra_properties kind = | Checkbox -> [ Gap; LabelOpacity; IndicatorBorderColor; CheckedIndicatorBorderColor ] | Radio -> [ Gap; LabelOpacity ] | Grid -> [ MainAlignment; CrossAlignment ] - | Row -> [ OrientationValue; FocusWithinEnabled ] - | Column -> [ FocusWithinEnabled ] + | Row -> [ OrientationValue; FocusWithinEnabled; ShrinkValue ] + | Column -> [ FocusWithinEnabled; ShrinkValue ] | ListItem -> [ DisplayValue ] | Dialog -> [ DescriptionValue; BackgroundValue; ForegroundValue; BorderColorValue; BorderWidth; CornerRadius; Gap; Transition ] | Drawer -> [ Transition ] | Sheet -> [ Transition ] - | Box -> [ Inert; FocusWithinEnabled ] + | Box -> [ Inert; FocusWithinEnabled; ShrinkValue ] | Popover -> [ CollisionPadding ] | DropdownMenu -> [ CollisionPadding ] | Tooltip -> [ CollisionPadding ] @@ -434,7 +435,7 @@ let kind_extra_properties kind = 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; FocusWithinEnabled ] +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; FocusWithinEnabled; ShrinkValue ] type view_value = @@ -490,14 +491,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); (FocusWithinEnabled, ViewBool, 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); (FocusWithinEnabled, ViewBool, 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 = ["focus-within-changed"; "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); (FocusWithinEnabled, 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); (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); (FocusWithinEnabled, ViewBool, false); (Opacity, ViewFloat, false); (DisplayValue, view_display, false); (FlexWrap, view_flex_wrap, false); (Transition, ViewString, false); (AlignmentValue, view_alignment, false)]; view_events = ["focus-within-changed"; "appear"; "pointer-enter"; "pointer-leave"; "press-detail"; "pointer-down"; "pointer-up"]; view_slots = []; } @@ -567,7 +568,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); (FocusWithinEnabled, ViewBool, 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); (FocusWithinEnabled, ViewBool, 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 = ["focus-within-changed"; "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;