From 14cb9262dece2aef2c6fe03df0e6d6178907e14e Mon Sep 17 00:00:00 2001 From: Mihael Isaev Date: Sun, 18 Dec 2022 03:37:13 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8FImplement=20it=20using=20the?= =?UTF-8?q?=20full=20power=20of=20CSS3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.swift | 2 +- README.md | 780 +++++++++ Sources/Autolayout/Autolayout.swift | 2272 ++++++++++++--------------- 3 files changed, 1756 insertions(+), 1298 deletions(-) create mode 100644 README.md diff --git a/Package.swift b/Package.swift index 7c5c93d..5579cca 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let package = Package( .library(name: "Autolayout", type: .static, targets: ["Autolayout"]) ], dependencies: [ - .package(url: "https://github.com/swifweb/web", from: "1.0.0-beta.1.20.0") + .package(url: "https://github.com/swifweb/web", from: "1.0.0-beta.1.22.0") ], targets: [ .target(name: "Autolayout", dependencies: [ diff --git a/README.md b/README.md new file mode 100644 index 0000000..438eeba --- /dev/null +++ b/README.md @@ -0,0 +1,780 @@ +[![SwifWeb](https://user-images.githubusercontent.com/1272610/208269515-82c38e3b-530c-4611-9638-6eaac2cf87e3.png)](http://swifweb.com) + +

+ + MIT License + + + Swift 5.7 + + + Swift.Stream + +

+ +This library gives you the powerful autolayout which works on pure CSS3 ❤️ + +It will help you to easily build your awesome reactive web app in beloved Swift ❤️ + +# Installation + +Add package to your SwifWeb app's `Package.swift` + +In dependencies section +```swift + dependencies: [ + .package(url: "https://github.com/swifweb/autolayout", from: "1.0.0") +] +``` +In target section +```swift +targets: [ + .executableTarget(name: "App", dependencies: [ + .product(name: "Web", package: "web"), + .product(name: "Autolayout", package: "autolayout") + ] +] +``` + +# Usage + +Start using it at any view by simply declaring methods listed below. + +## Breakpoints + +Breakpoints can be added in the end of any autolayout-method. It is direct full power of CSS3 `@media` rule. + +Classic way to set `@media` rules is like this + +```swift +.all.minWidth(576.px).maxWidth(767.px) +``` +or like this if you need to declare several +```swift +.all.maxWidth(575.px), .all.minWidth(576.px).maxWidth(767.px), .all.minWidth(768.px).maxWidth(991.px) +``` + +But you can predefine needed breakpoints easily like this + +```swift +extension MediaRule.MediaType { + static var extraSmall: MediaRule.MediaType { .init(.all.maxWidth(575.px), label: "xs") } + static var small: MediaRule.MediaType { .init(.all.minWidth(576.px).maxWidth(767.px), label: "s") } + static var medium: MediaRule.MediaType { .init(.all.minWidth(768.px).maxWidth(991.px), label: "m") } +} +``` + +So you will be able to use them simply like this + +```swift +.top(100.px, breakpoints: .extraSmall, .small, .medium) +``` + +To save your time there are already predefined breakpoints + +```swift +.extraSmall // <576px +.small // ≥576px and <768px +.medium // ≥768px and <992px +.large // ≥992px and <1200px +.extraLarge // ≥1200px and <1400px +.extraExtraLarge // ≥1400px +``` + +## Methods + +> You can declare multiple same methods but with different breakpoints. + +> Each method also accept `@State` value + +### Top + +Specifies `top` position to the first parent element with relative position + +#### Top to top + +```swift +// will set top to 0px +.top() + +// will set top to 100px +.top(100.px) + +// will set top to 50px +.top(100.px, multiplier: 0.5) + +// will set top to 0px only for extra-small, small and medium screens +.top(breakpoints: .xs, .s, m) + +// will set top to 50px only for extra-small, small and medium screens +.top(50.px, breakpoints: .xs, .s, m) + +// will set top to 25px only for extra-small, small and medium screens +.top(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Top to center + +Simply add `side: .center` as a second argument and `top` side will stick to `center` of the first parent with relative position + +```swift +// will set top to 0px from the center +.top(side: .center) + +// will set top to 100px from the center +.top(100.px, side: .center) + +// will set top to 50px from the center +.top(100.px, side: .center, multiplier: 0.5) + +// will set top to 0px from the center only for extra-small, small and medium screens +.top(side: .center, breakpoints: .xs, .s, m) + +// will set top to 50px from the center only for extra-small, small and medium screens +.top(50.px, side: .center, breakpoints: .xs, .s, m) + +// will set top to 25px from the center only for extra-small, small and medium screens +.top(50.px, side: .center, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Top to bottom + +Simply add `side: .bottom` as a second argument and `top` side will stick to `bottom` of the first parent with relative position + +```swift +// will set top to 0px from the bottom +.top(side: .bottom) + +// will set top to 100px from the bottom +.top(100.px, side: .bottom) + +// will set top to 50px from the bottom +.top(100.px, side: .bottom, multiplier: 0.5) + +// will set top to 0px from the bottom only for extra-small, small and medium screens +.top(side: .bottom, breakpoints: .xs, .s, m) + +// will set top to 50px from the bottom only for extra-small, small and medium screens +.top(50.px, side: .bottom, breakpoints: .xs, .s, m) + +// will set top to 25px from the bottom only for extra-small, small and medium screens +.top(50.px, side: .bottom, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Bottom + +Specifies `bottom` position to the first parent element with relative position + +#### Bottom to bottom + +```swift +// will set bottom to 0px +.bottom() + +// will set bottom to 100px +.bottom(100.px) + +// will set bottom to 50px +.bottom(100.px, multiplier: 0.5) + +// will set bottom to 0px only for extra-small, small and medium screens +.bottom(breakpoints: .xs, .s, m) + +// will set bottom to 50px only for extra-small, small and medium screens +.bottom(50.px, breakpoints: .xs, .s, m) + +// will set bottom to 25px only for extra-small, small and medium screens +.bottom(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Bottom to center + +Simply add `side: .center` as a second argument and `bottom` side will stick to `center` of the first parent with relative position + +```swift +// will set bottom to 0px from the center +.bottom(side: .center) + +// will set bottom to 100px from the center +.bottom(100.px, side: .center) + +// will set bottom to 50px from the center +.bottom(100.px, side: .center, multiplier: 0.5) + +// will set bottom to 0px from the center only for extra-small, small and medium screens +.bottom(side: .center, breakpoints: .xs, .s, m) + +// will set bottom to 50px from the center only for extra-small, small and medium screens +.bottom(50.px, side: .center, breakpoints: .xs, .s, m) + +// will set bottom to 25px from the center only for extra-small, small and medium screens +.bottom(50.px, side: .center, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Bottom to top + +Simply add `side: .top` as a second argument and `bottom` side will stick to `top` of the first parent with relative position + +```swift +// will set bottom to 0px from the top +.bottom(side: .top) + +// will set bottom to 100px from the top +.bottom(100.px, side: .top) + +// will set bottom to 50px from the top +.bottom(100.px, side: .top, multiplier: 0.5) + +// will set bottom to 0px from the top only for extra-small, small and medium screens +.bottom(side: .top, breakpoints: .xs, .s, m) + +// will set bottom to 50px from the top only for extra-small, small and medium screens +.bottom(50.px, side: .top, breakpoints: .xs, .s, m) + +// will set bottom to 25px from the top only for extra-small, small and medium screens +.bottom(50.px, side: .top, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Left + +Specifies `left` position to the first parent element with relative position + +#### Left to left + +```swift +// will set left to 0px +.left() + +// will set left to 100px +.left(100.px) + +// will set left to 50px +.left(100.px, multiplier: 0.5) + +// will set left to 0px only for extra-small, small and medium screens +.left(breakpoints: .xs, .s, m) + +// will set left to 50px only for extra-small, small and medium screens +.left(50.px, breakpoints: .xs, .s, m) + +// will set left to 25px only for extra-small, small and medium screens +.left(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Left to center + +Simply add `side: .center` as a second argument and `left` side will stick to `center` of the first parent with relative position + +```swift +// will set left to 0px from the center +.left(side: .center) + +// will set left to 100px from the center +.left(100.px, side: .center) + +// will set left to 50px from the center +.left(100.px, side: .center, multiplier: 0.5) + +// will set left to 0px from the center only for extra-small, small and medium screens +.left(side: .center, breakpoints: .xs, .s, m) + +// will set left to 50px from the center only for extra-small, small and medium screens +.left(50.px, side: .center, breakpoints: .xs, .s, m) + +// will set left to 25px from the center only for extra-small, small and medium screens +.left(50.px, side: .center, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Left to right + +Simply add `side: .right` as a second argument and `left` side will stick to `right` of the first parent with relative position + +```swift +// will set left to 0px from the right +.left(side: .right) + +// will set left to 100px from the right +.left(100.px, side: .right) + +// will set left to 50px from the right +.left(100.px, side: .right, multiplier: 0.5) + +// will set left to 0px from the right only for extra-small, small and medium screens +.left(side: .right, breakpoints: .xs, .s, m) + +// will set left to 50px from the right only for extra-small, small and medium screens +.left(50.px, side: .right, breakpoints: .xs, .s, m) + +// will set left to 25px from the right only for extra-small, small and medium screens +.left(50.px, side: .right, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Right + +Specifies `right` position to the first parent element with relative position + +#### Right to right + +```swift +// will set right to 0px +.right() + +// will set right to 100px +.right(100.px) + +// will set right to 50px +.right(100.px, multiplier: 0.5) + +// will set right to 0px only for extra-small, small and medium screens +.right(breakpoints: .xs, .s, m) + +// will set right to 50px only for extra-small, small and medium screens +.right(50.px, breakpoints: .xs, .s, m) + +// will set right to 25px only for extra-small, small and medium screens +.right(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Right to center + +Simply add `side: .center` as a second argument and `right` side will stick to `center` of the first parent with relative position + +```swift +// will set right to 0px from the center +.right(side: .center) + +// will set right to 100px from the center +.right(100.px, side: .center) + +// will set right to 50px from the center +.right(100.px, side: .center, multiplier: 0.5) + +// will set right to 0px from the center only for extra-small, small and medium screens +.right(side: .center, breakpoints: .xs, .s, m) + +// will set right to 50px from the center only for extra-small, small and medium screens +.right(50.px, side: .center, breakpoints: .xs, .s, m) + +// will set right to 25px from the center only for extra-small, small and medium screens +.right(50.px, side: .center, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Right to left + +Simply add `side: .left` as a second argument and `right` side will stick to `left` of the first parent with relative position + +```swift +// will set right to 0px from the left +.right(side: .left) + +// will set right to 100px from the left +.right(100.px, side: .left) + +// will set right to 50px from the left +.right(100.px, side: .left, multiplier: 0.5) + +// will set right to 0px from the left only for extra-small, small and medium screens +.right(side: .left, breakpoints: .xs, .s, m) + +// will set right to 50px from the left only for extra-small, small and medium screens +.right(50.px, side: .left, breakpoints: .xs, .s, m) + +// will set right to 25px from the left only for extra-small, small and medium screens +.right(50.px, side: .left, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Edges + +Convenience setter for all sides: top, right, bottom, left + +#### All edges + +```swift +// Will set top, right, bottom, and left to 0px +.edges() + +// Will set top, right, bottom, and left to 10px +.edges(10.px) + +// Will set top, right, bottom, and left to 5px only for extra-small, small and medium screens +.edges(5.px, breakpoints: .xs, .s, m) +``` + +#### Horizontal edges + +```swift +// Will set left and right to 0px +.edges(h: 0.px) + +// Will set left and right to 10px +.edges(h: 10.px) + +// Will set left and right to 5px only for extra-small, small and medium screens +.edges(h: 5.px, breakpoints: .xs, .s, m) +``` + +#### Vertical edges + +```swift +// Will set top and bottom to 0px +.edges(v: 0.px) + +// Will set top and bottom to 10px +.edges(v: 10.px) + +// Will set top and bottom to 5px only for extra-small, small and medium screens +.edges(v: 5.px, breakpoints: .xs, .s, m) +``` + +#### Horizontal and vertical edges + +```swift +// Will set left and right to 0px, and top and bottom to 0px +.edges(h: 0.px, v: 0.px) + +// Will set left and right to 0px, and top and bottom to 10px +.edges(h: 0.px, v: 10.px) + +// Will set left and right to 2px, and top and bottom to 4px only for extra-small, small and medium screens +.edges(h: 2.px, v: 4.px, breakpoints: .xs, .s, m) +``` + +### Center X + +Specifies the horizontal center position to the first parent element with relative position + +#### Center to center + +```swift +// will set centerX to 0px +.centerX() + +// will set centerX to 100px +.centerX(100.px) + +// will set centerX to 50px +.centerX(100.px, multiplier: 0.5) + +// will set centerX to 0px only for extra-small, small and medium screens +.centerX(breakpoints: .xs, .s, m) + +// will set centerX to 50px only for extra-small, small and medium screens +.centerX(50.px, breakpoints: .xs, .s, m) + +// will set centerX to 25px only for extra-small, small and medium screens +.centerX(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Center to left + +Simply add `side: .left` as a second argument and `centerX` side will stick to `left` of the first parent with relative position + +```swift +// will set centerX to 0px of the left +.centerX(side: .left) + +// will set centerX to 100px of the left +.centerX(100.px, side: .left) + +// will set centerX to 50px of the left +.centerX(100.px, side: .left, multiplier: 0.5) + +// will set centerX to 0px of the left only for extra-small, small and medium screens +.centerX(side: .left, breakpoints: .xs, .s, m) + +// will set centerX to 50px of the left only for extra-small, small and medium screens +.centerX(50.px, side: .left, breakpoints: .xs, .s, m) + +// will set centerX to 25px of the left only for extra-small, small and medium screens +.centerX(50.px, side: .left, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Center to right + +Simply add `side: .right` as a second argument and `centerX ` side will stick to `right` of the first parent with relative position + +```swift +// will set centerX to 0px of the right +.centerX(side: .right) + +// will set centerX to 100px of the right +.centerX(100.px, side: .right) + +// will set centerX to 50px of the right +.centerX(100.px, side: .right, multiplier: 0.5) + +// will set centerX to 0px of the right only for extra-small, small and medium screens +.centerX(side: .right, breakpoints: .xs, .s, m) + +// will set centerX to 50px of the right only for extra-small, small and medium screens +.centerX(50.px, side: .right, breakpoints: .xs, .s, m) + +// will set centerX to 25px of the right only for extra-small, small and medium screens +.centerX(50.px, side: .right, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Center Y + +Specifies the vertical center position to the first parent element with relative position + +#### Center to center + +```swift +// will set centerY to 0px +.centerY() + +// will set centerY to 100px +.centerY(100.px) + +// will set centerY to 50px +.centerY(100.px, multiplier: 0.5) + +// will set centerY to 0px only for extra-small, small and medium screens +.centerY(breakpoints: .xs, .s, m) + +// will set centerY to 50px only for extra-small, small and medium screens +.centerY(50.px, breakpoints: .xs, .s, m) + +// will set centerY to 25px only for extra-small, small and medium screens +.centerY(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Center to top + +Simply add `side: .top` as a second argument and `centerY` side will stick to `top` of the first parent with relative position + +```swift +// will set centerY to 0px of the top +.centerY(side: .top) + +// will set centerY to 100px of the top +.centerY(100.px, side: .top) + +// will set centerY to 50px of the top +.centerY(100.px, side: .top, multiplier: 0.5) + +// will set centerY to 0px of the top only for extra-small, small and medium screens +.centerY(side: .top, breakpoints: .xs, .s, m) + +// will set centerY to 50px of the top only for extra-small, small and medium screens +.centerY(50.px, side: .top, breakpoints: .xs, .s, m) + +// will set centerY to 25px of the top only for extra-small, small and medium screens +.centerY(50.px, side: .top, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +#### Center to bottom + +Simply add `side: .bottom` as a second argument and `centerY ` side will stick to `bottom` of the first parent with relative position + +```swift +// will set centerY to 0px of the bottom +.centerY(side: .bottom) + +// will set centerY to 100px of the bottom +.centerY(100.px, side: .bottom) + +// will set centerY to 50px of the bottom +.centerY(100.px, side: .bottom, multiplier: 0.5) + +// will set centerY to 0px of the bottom only for extra-small, small and medium screens +.centerY(side: .bottom, breakpoints: .xs, .s, m) + +// will set centerY to 50px of the bottom only for extra-small, small and medium screens +.centerY(50.px, side: .bottom, breakpoints: .xs, .s, m) + +// will set centerY to 25px of the bottom only for extra-small, small and medium screens +.centerY(50.px, side: .bottom, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Center X+Y + +Specifies both vertical and horizontal center position to the first parent element with relative position + +```swift +// will set centerX and centerY to 0px +.center() + +// will set centerX and centerY to 100px +.center(100.px) + +// will set centerX and centerY to 50px +.center(100.px, multiplier: 0.5) + +// will set centerX and centerY to 0px only for extra-small, small and medium screens +.center(breakpoints: .xs, .s, m) + +// will set centerX and centerY to 50px only for extra-small, small and medium screens +.center(50.px, breakpoints: .xs, .s, m) + +// will set centerX and centerY to 25px only for extra-small, small and medium screens +.center(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Width + +Sets width of an element + +```swift +// will set width to 0px +.width() + +// will set width to 100px +.width(100.px) + +// will set width to 100% +.width(100.percent) + +// will set width to 50px +.width(100.px, multiplier: 0.5) + +// will set width to 0px only for extra-small, small and medium screens +.width(breakpoints: .xs, .s, m) + +// will set width to 50px only for extra-small, small and medium screens +.width(50.px, breakpoints: .xs, .s, m) + +// will set width to 25px only for extra-small, small and medium screens +.width(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Width to parent + +Sets width of an element to fit first parent element with relative position + +```swift +// will set width to 100% of first parent element with relative position +.widthToParent() + +// will set width to 100% of first parent element with relative position only for extra-small, small and medium screens +.widthToParent(breakpoints: .xs, .s, m) + +// will set width to 100% + 100px of first parent element with relative position +.widthToParent(extra: 100.px) + +// will set width to 100% + 100px of first parent element with relative position +// only for extra-small, small and medium screens +.widthToParent(extra: 100.px, breakpoints: .xs, .s, m) + +// will set width to (100% + 100px) * 0.5 of first parent element with relative position +.widthToParent(extra: 100.px, multiplier: 0.5) + +// will set width to (100% + 100px) * 0.5 of first parent element with relative position +// only for extra-small, small and medium screens +.widthToParent(extra: 100.px, multiplier: 0.5, breakpoints: .xs, .s, m) + +// will set width to 50% of first parent element with relative position +.widthToParent(multiplier: 0.5) + +// will set width to 50% of first parent element with relative position +// only for extra-small, small and medium screens +.widthToParent(multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Height + +Sets height of an element + +```swift +// will set height to 0px +.height() + +// will set height to 100px +.height(100.px) + +// will set height to 100% +.height(100.percent) + +// will set height to 50px +.height(100.px, multiplier: 0.5) + +// will set height to 0px only for extra-small, small and medium screens +.height(breakpoints: .xs, .s, m) + +// will set height to 50px only for extra-small, small and medium screens +.height(50.px, breakpoints: .xs, .s, m) + +// will set height to 25px only for extra-small, small and medium screens +.height(50.px, multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Height to parent + +Sets height of an element to fit first parent element with relative position + +```swift +// will set height to 100% of first parent element with relative position +.heightToParent() + +// will set height to 100% of first parent element with relative position only for extra-small, small and medium screens +.heightToParent(breakpoints: .xs, .s, m) + +// will set height to 100% + 100px of first parent element with relative position +.heightToParent(extra: 100.px) + +// will set height to 100% + 100px of first parent element with relative position +// only for extra-small, small and medium screens +.heightToParent(extra: 100.px, breakpoints: .xs, .s, m) + +// will set height to (100% + 100px) * 0.5 of first parent element with relative position +.heightToParent(extra: 100.px, multiplier: 0.5) + +// will set height to (100% + 100px) * 0.5 of first parent element with relative position +// only for extra-small, small and medium screens +.heightToParent(extra: 100.px, multiplier: 0.5, breakpoints: .xs, .s, m) + +// will set height to 50% of first parent element with relative position +.heightToParent(multiplier: 0.5) + +// will set height to 50% of first parent element with relative position +// only for extra-small, small and medium screens +.heightToParent(multiplier: 0.5, breakpoints: .xs, .s, m) +``` + +### Position + +Specifies the type of positioning method used for an element `static, relative, absolute or fixed` + +```swift +// will set position to absolute +.position(.absolute) + +// will set position to absolute only for extra-small, small and medium screens +.position(.absolute, breakpoints: .xs, .s, m) +``` + +### Display + +Specifies how a certain HTML element should be displayed + +```swift +// will set display to block +.display(.block) + +// will set display to block only for extra-small, small and medium screens +.display(.block, breakpoints: .xs, .s, m) +``` + +### Visibility + +Specifies whether or not an element is visible + +```swift +// will set visibility to visible +.visibility(.visible) + +// will set visibility to hidden only for extra-small, small and medium screens +.visibility(.hidden, breakpoints: .xs, .s, m) +``` + +### Opacity + +Sets the opacity level for an element + +```swift +// will set opacity to 0.8 +.opacity(0.8) + +// will set opacity to 0.5 only for extra-small, small and medium screens +.opacity(0.5, breakpoints: .xs, .s, m) +``` diff --git a/Sources/Autolayout/Autolayout.swift b/Sources/Autolayout/Autolayout.swift index 198b5b8..24e6ecd 100644 --- a/Sources/Autolayout/Autolayout.swift +++ b/Sources/Autolayout/Autolayout.swift @@ -8,92 +8,23 @@ import Web import ResizeObserverAPI +let _autolayout = Autolayout() + public class Autolayout { - // MARK: Storage keys + let stylesheet = Stylesheet().id("autolayout_styles") + var ruleIndexCache: [String: Int] = [:] - struct ResizeObserverStorageKey: StorageKey { - typealias Value = ResizeObserver - } - - struct StoredConstraintStorageKey: StorageKey { - typealias Value = Set - } - - class StoredConstraint: Equatable, Hashable, CustomStringConvertible { - typealias Handler = (Rect) -> Void - - let destinationView: BaseElement - let constraint: Constraint - var handlers: [Handler] - - init (destinationView: BaseElement, constraint: Constraint, handlers: [Handler]) { - self.destinationView = destinationView - self.constraint = constraint - self.handlers = handlers - } - - // Equatable - - static func == (lhs: Autolayout.StoredConstraint, rhs: Autolayout.StoredConstraint) -> Bool { - lhs.destinationView == rhs.destinationView && lhs.constraint == rhs.constraint - } - - // Hashable - - public func hash(into hasher: inout Hasher) { - hasher.combine(destinationView) - hasher.combine(constraint) - } - - public var description: String { - "#\(destinationView.properties._id)/\(constraint.rawValue)" - } + fileprivate init () { + WebApp.shared.document.head.appendChild(stylesheet) } // MARK: Constraints - enum Constraint: String { - case widthToWidthOfView - case widthToHeightOfView - case heightToHeightOfView - case heightToWidthOfView - case leftToLeftOfView - case leftToRightOfView - case leftToCenterOfView - case rightToRightOfView - case rightToLeftOfView - case rightToCenterOfView - case topToTopOfView - case topToBottomOfView - case topToCenterOfView - case bottomToBottomOfView - case bottomToTopOfView - case bottomToCenterOfView - case widthToSuperview - case heightToSuperview - case topToSuperview - case bottomToSuperview - case leadingToSuperview - case trailingToSuperview - case leftToSuperview - case rightToSuperview - case centerXInSuperview - case centerXToLeftOfView - case centerXToRightOfView - case centerXToCenterXOfView - case centerYInSuperview - case centerYToTopOfView - case centerYToBottomOfView - case centerYToCenterYOfView - } - enum ConstraintAttribute: String { case left case right case top case bottom - case leading - case trailing case width case height case centerX @@ -103,32 +34,23 @@ public class Autolayout { // MARK: Constraint sides public enum ConstraintCXSide { - case x - case leading, left - case trailing, right - case centerX + case left, right, center var side: ConstraintAttribute { switch self { - case .x: return .centerX - case .leading: return .leading case .left: return .left - case .trailing: return .trailing case .right: return .right - case .centerX: return .centerX + case .center: return .centerX } } } public enum ConstraintCYSide { - case y - case top, bottom - case centerY + case top, bottom, center var side: ConstraintAttribute { switch self { - case .y: return .centerY case .top: return .top case .bottom: return .bottom - case .centerY: return .centerY + case .center: return .centerY } } } @@ -144,1558 +66,1314 @@ public class Autolayout { } public enum ConstraintXSide { - case leading, left, trailing, right, centerX + case left, right, center var side: ConstraintAttribute { switch self { - case .leading: return .leading case .left: return .left - case .trailing: return .trailing case .right: return .right - case .centerX: return .centerX + case .center: return .centerX } } } public enum ConstraintYSide { - case top, bottom, centerY + case top, bottom, center var side: ConstraintAttribute { switch self { case .top: return .top case .bottom: return .bottom - case .centerY: return .centerY + case .center: return .centerY } } } } -extension BaseElement { - var resizeObserver: ResizeObserver? { - get { self.storage.get(Autolayout.ResizeObserverStorageKey.self) } - set { self.storage.set(Autolayout.ResizeObserverStorageKey.self, to: newValue) } - } - var storedConstraints: Set { - get { self.storage.get(Autolayout.StoredConstraintStorageKey.self) ?? .init() } - set { self.storage.set(Autolayout.StoredConstraintStorageKey.self, to: newValue) } - } +extension MediaRule.MediaType { + /// Extra small `<576px` + public static var extraSmall: MediaRule.MediaType { .init(.all.maxWidth(575.px), label: "xs") } - // MARK: Relative + /// Small `≥576px` and `<768px` + public static var small: MediaRule.MediaType { .init(.all.minWidth(576.px).maxWidth(767.px), label: "s") } - private func setupConstraint(_ constraint: Autolayout.Constraint, for view: BaseElement, _ handler: @escaping (Rect) -> Void) { - let resizeObserver = view.resizeObserver ?? ResizeObserver().observe(view) - - if let member = view.storedConstraints.first(where: { $0.destinationView == view && $0.constraint == constraint }) { -// print("❗️storedConstraints already contains member #\(view.properties._id) set handlersForView #\(self.properties._id)") - member.handlers.append(handler) - } else { - let member = Autolayout.StoredConstraint(destinationView: view, constraint: constraint, handlers: [handler]) - view.storedConstraints.insert(member) - } - -// print("#\(view.properties._id) set handlersForView #\(self.properties._id)") - - resizeObserver.setObserver(for: self) { entries, observer in - let rect: Rect = entries.first?.contentRect ?? .zero - entries.forEach { entrie in - print("entrie(#\(entrie.target.id.string ?? "nnn")) entrie.contentRect: \(entrie.contentRect)") - } -// print("#\(self?.properties._id ?? "nnn") #\(view.properties._id).resizeObserver fired, storedConstraints.count: \(view.storedConstraints.count), values: \(view.storedConstraints.map { $0.description })") - view.storedConstraints.forEach { -// print("#\(self?.properties._id ?? "nnn") calling #\($0.destinationView.properties._id) handler for \($0.constraint)") - $0.handlers.forEach { $0(rect) } - } + /// Medium `≥768px` and `<992px` + public static var medium: MediaRule.MediaType { .init(.all.minWidth(768.px).maxWidth(991.px), label: "m") } + + /// Large `≥992px` and `<1200px` + public static var large: MediaRule.MediaType { .init(.all.minWidth(992.px).maxWidth(1199.px), label: "l") } + + /// Large `≥1200px` and `<1400px` + public static var extraLarge: MediaRule.MediaType { .init(.all.minWidth(1200.px).maxWidth(1399.px), label: "xl") } + + /// Large `≥1400px` + public static var extraExtraLarge: MediaRule.MediaType { .init(.all.minWidth(1400.px), label: "xxl") } +} + +extension BaseElement { + private func _getClassName(_ methodName: String, breakpoints: [MediaRule.MediaType]) -> String { + let media = breakpoints.map { + String($0.description.map { [" ", ",", "(", ")", "-", ":", "."].contains($0) ? "_" : $0 }) + }.joined(separator: "_") + return properties._id + "_" + methodName + (media.isEmpty ? "" : "_" + media) + } + + private func _setRule( + _ className: String, + breakpoints: [MediaRule.MediaType], + _ rulesHandler: @escaping (CSSRule) -> CSSRule + ) { + if let indexToDelete = _autolayout.ruleIndexCache[className] { + _autolayout.stylesheet.deleteRule(indexToDelete) } - view.resizeObserver = resizeObserver - if isInDOM { - handler(view.boundingClientRect) + let index: Int + if breakpoints.count == 0 { + let rule = rulesHandler(CSSRule(Class(stringLiteral: className).pointer)) + index = _autolayout.stylesheet.addRule(rule) } else { - onDidAddToDOM { handler(view.boundingClientRect) } + let cssRule = CSSRule(Class(stringLiteral: className).pointer) + let mediaRule = MediaRule(breakpoints) { cssRule } + let rule = rulesHandler(cssRule) + index = _autolayout.stylesheet.addMediaRule(mediaRule) } - view.onDidAddToDOM { handler(view.boundingClientRect) } - } - - // MARK: - Relative - - private func _createRelative( - value: State, - multiplier: Double, - attribute1: Autolayout.ConstraintAttribute, - attribute2: Autolayout.ConstraintAttribute, - destinationView: BaseElement - ) -> Self { - var printedWarning: [Autolayout.Constraint: Bool] = [:] - func _setupSides( - currentAbsolute: @escaping () -> Double?, - currentOffset: @escaping () -> Double?, - destinationAbsolute: @escaping () -> Double?, - constraint: Autolayout.Constraint, - trackSelf: Bool = false, - newValueHandler: @escaping (UnitValue) -> Void - ) { - func _setup(_ value: UnitValue) { -// print("_setupSides #\(self.properties._id) -> #\(destinationView.properties._id) \(constraint)") - let updateHandler: (Rect) -> Void = { [weak self] rect in - guard let self = self else { return } - let _position = Window.shared.getComputedStyle(self, for: CSS.PropertyType.position.rawValue) - guard let p = _position, p != "", p != "static" else { - if printedWarning[constraint] != true { - printedWarning[constraint] = true - #if DEBUG - Console.warning("⚠️🎨 \(attribute1.rawValue.capitalized) to \(attribute2.rawValue) constraint doesn't work with static position (#\(self.properties._id).\(attribute1.rawValue) to #\(destinationView.properties._id).\(attribute2.rawValue))") - #endif - } - return - } -// if (p != "absolute" || p != "fixed"), [ -// .topToSuperview, .topToTopOfView, .topToBottomOfView, .topToCenterOfView, -// .bottomToSuperview, .bottomToTopOfView, .bottomToBottomOfView, .bottomToCenterOfView, -// .leftToSuperview, .leftToLeftOfView, .leftToRightOfView, .leftToCenterOfView, -// .rightToSuperview, .rightToLeftOfView, .rightToRightOfView, .rightToCenterOfView, -// .leadingToSuperview, -// .trailingToSuperview -// ].contains(constraint) { -// self.position(.absolute) -// #if DEBUG -// Console.warning("⚠️🎨 \(attribute1.rawValue.capitalized) to \(attribute2.rawValue) automatically fixed position to absolute (#\(self.properties._id).\(attribute1.rawValue) to #\(destinationView.properties._id).\(attribute2.rawValue))") -// #endif -// } - let currentAbsolute = 0.0//currentAbsolute() ?? 0 - let currentOffset = 0.0//currentOffset() ?? 0 - let destinationAbsolute = 0.0//destinationAbsolute() ?? 0 - printedWarning[constraint] = false - var diff: Double = 0 - let newValue: Double - if currentAbsolute > destinationAbsolute { - diff = currentAbsolute - destinationAbsolute - newValue = currentOffset - diff - } else if currentAbsolute < destinationAbsolute { - diff = destinationAbsolute - currentAbsolute - newValue = currentOffset + diff - } else { - newValue = currentOffset - } -// print("#\(self.properties._id) -> #\(destinationView.properties._id) cAbsolute: \(currentAbsolute) cOffset: \(currentOffset) dAbsolute: \(destinationAbsolute)") - newValueHandler(.init(newValue * multiplier + value.value, .px)) - } - setupConstraint(constraint, for: destinationView, updateHandler) - if trackSelf { - setupConstraint(constraint, for: self, updateHandler) - } - } - value.listen { - _setup(.init($0.value.doubleValue, $0.unit)) - } - _setup(.init(value.wrappedValue.value.doubleValue, value.wrappedValue.unit)) - } - switch attribute1 { - case .left, .leading: - switch attribute2 { - case .left, .leading: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .leftToLeftOfView - ) { [weak self] in -// print("left to left called") - self?.left($0) - } - case .right, .trailing: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteRight }, - constraint: .leftToRightOfView - ) { [weak self] in -// print("left to right called") - self?.left($0) - } -// case .leading: // TODO -// break -// case .trailing: // TODO -// break - case .centerX: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .leftToCenterOfView - ) { [weak self] in -// print("left to centerX called") - guard let self = self else { return } - self.left(($0.value + (destinationView.clientWidth / 2)).px) - } - default: - break - } - case .right, .trailing: - switch attribute2 { - case .left, .leading: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .rightToLeftOfView, - trackSelf: true - ) { [weak self] in -// print("right to left called") - guard let self = self else { return } - self.left(($0.value - self.clientWidth).px) - } - case .right, .trailing: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .rightToRightOfView, - trackSelf: true - ) { [weak self] in -// print("right to right called") - guard let self = self else { return } - if destinationView.clientWidth > self.clientWidth { - self.left(($0.value + (destinationView.clientWidth - self.clientWidth)).px) - } else if self.clientWidth > destinationView.clientWidth { - self.left(($0.value + (self.clientWidth - destinationView.clientWidth)).px) - } else { - self.left($0) - } - } -// case .leading: // TODO -// break -// case .trailing: // TODO -// break - case .centerX: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .rightToCenterOfView, - trackSelf: true - ) { [weak self] in -// print("right to centerX called") - guard let self = self else { return } - let destinationCenterPoint = ($0.value + (destinationView.clientWidth / 2)) - let currentRightPoint = $0.value + self.clientWidth - let diff = currentRightPoint - destinationCenterPoint - if destinationCenterPoint > currentRightPoint { - self.left(($0.value + (destinationCenterPoint - currentRightPoint)).px) - } else if destinationCenterPoint < currentRightPoint { - self.left(($0.value - (currentRightPoint - destinationCenterPoint)).px) - } else { - self.left($0) - } - } - default: - break - } - case .top: - switch attribute2 { - case .top: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .topToTopOfView - ) { [weak self] in -// print("top to top called") - self?.top($0) - } - case .bottom: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteBottom }, - constraint: .topToBottomOfView - ) { [weak self] in -// print("top to bottom called") - self?.top($0) - } - case .centerY: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .topToCenterOfView - ) { [weak self] in -// print("top to centerY called") - guard let self = self else { return } - self.top(($0.value + (destinationView.clientHeight / 2)).px) - } - default: - break - } - case .bottom: - switch attribute2 { - case .top: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .bottomToTopOfView, - trackSelf: true - ) { [weak self] in -// print("bottom to top called") - guard let self = self else { return } - self.top(($0.value - self.clientHeight).px) - } - case .bottom: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .bottomToBottomOfView, - trackSelf: true - ) { [weak self] in -// print("bottom to bottom called") - guard let self = self else { return } - if destinationView.clientHeight > self.clientHeight { - self.top(($0.value + (destinationView.clientHeight - self.clientHeight)).px) - } else if self.clientHeight > destinationView.clientHeight { - self.top(($0.value + (self.clientHeight - destinationView.clientHeight)).px) - } else { - self.top($0) - } - } - case .centerY: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .bottomToCenterOfView, - trackSelf: true - ) { [weak self] in -// print("bottom to centerY called") - guard let self = self else { return } - let destinationCenterPoint = ($0.value + (destinationView.clientHeight / 2)) - let currentRightPoint = $0.value + self.clientHeight - let diff = currentRightPoint - destinationCenterPoint - if destinationCenterPoint > currentRightPoint { - self.top(($0.value + (destinationCenterPoint - currentRightPoint)).px) - } else if destinationCenterPoint < currentRightPoint { - self.top(($0.value - (currentRightPoint - destinationCenterPoint)).px) - } else { - self.top($0) - } - } - default: - break - } -// case .leading: // TODO -// switch attribute2 { -// case .left: -// break -// case .right: -// break -// case .leading: -// break -// case .trailing: -// break -// case .centerX: -// break -// default: -// break -// } -// case .trailing: // TODO -// switch attribute2 { -// case .left: -// break -// case .right: -// break -// case .leading: -// break -// case .trailing: -// break -// case .centerX: -// break -// default: -// break -// } - case .width: - switch attribute2 { - case .width: - func setup(_ value: UnitValue) { - let updateHandler: (Rect) -> Void = { [weak self] rect in -// print("height to width called") - self?.width(UnitValue(rect.width * multiplier + value.value, .px)) - } - setupConstraint(.widthToWidthOfView, for: destinationView, updateHandler) - } - value.listen { - setup(.init($0.value.doubleValue, $0.unit)) - } - setup(.init(value.wrappedValue.value.doubleValue, value.wrappedValue.unit)) - case .height: - func setup(_ value: UnitValue) { - let updateHandler: (Rect) -> Void = { [weak self] rect in -// print("width to height called") - self?.width(UnitValue(rect.height * multiplier + value.value, .px)) - } - setupConstraint(.widthToHeightOfView, for: destinationView, updateHandler) - } - value.listen { - setup(.init($0.value.doubleValue, $0.unit)) - } - setup(.init(value.wrappedValue.value.doubleValue, value.wrappedValue.unit)) - default: - break - } - case .height: - switch attribute2 { - case .width: - func setup(_ value: UnitValue) { - let updateHandler: (Rect) -> Void = { [weak self] rect in -// print("height to width called") - self?.height(UnitValue(rect.width * multiplier + value.value, .px)) - } - setupConstraint(.heightToWidthOfView, for: destinationView, updateHandler) - } - value.listen { - setup(.init($0.value.doubleValue, $0.unit)) - } - setup(.init(value.wrappedValue.value.doubleValue, value.wrappedValue.unit)) - case .height: - func setup(_ value: UnitValue) { - let updateHandler: (Rect) -> Void = { [weak self] rect in -// print("height to height called") - self?.visibility(.hidden) - self?.height(UnitValue(rect.height * multiplier + value.value, .px)) - self?.visibility(.visible) - } - setupConstraint(.heightToHeightOfView, for: destinationView, updateHandler) - } - value.listen { - setup(.init($0.value.doubleValue, $0.unit)) - } - setup(.init(value.wrappedValue.value.doubleValue, value.wrappedValue.unit)) - default: - break - } - case .centerX: -// print("create centerX 1") - switch attribute2 { - case .left, .leading: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .centerXToLeftOfView, - trackSelf: true - ) { [weak self] in -// print("centerX to left called") - guard let self = self else { return } - self.left(($0.value - (self.clientWidth / 2)).px) - } - case .right, .trailing: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteRight }, - constraint: .centerXToRightOfView, - trackSelf: true - ) { [weak self] in -// print("centerX to right called") - guard let self = self else { return } - self.left(($0.value - (self.clientWidth / 2)).px) - } -// case .leading: // TODO -// break -// case .trailing: // TODO -// break - case .centerX: -// print("centerX(#\(self.properties._id)) to centerX(#\(destinationView.properties._id)) case 1") - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteLeft }, - currentOffset: { [weak self] in self?.offsetLeft }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteLeft }, - constraint: .centerXToCenterXOfView, - trackSelf: true - ) { [weak self] in -// print("centerX to centerX called") -// print("centerX(#\(self?.properties._id ?? "nnn")) to centerX(#\(destinationView.properties._id)) case 2") - guard let self = self else { return } -// print("centerX(#\(self.properties._id)) to centerX(#\(destinationView.properties._id)) case 3") - let currentCenter = (self.clientWidth / 2) - let destinationCenter = (destinationView.clientWidth / 2) - if currentCenter > destinationCenter { -// print("centerX(#\(self.properties._id)) to centerX(#\(destinationView.properties._id)) case 4.1") - self.left(($0.value + destinationCenter - currentCenter).px) - } else if destinationCenter > currentCenter { -// print("centerX(#\(self.properties._id)) to centerX(#\(destinationView.properties._id)) case 4.2") - self.left(($0.value + destinationCenter - currentCenter).px) - } else { -// print("centerX(#\(self.properties._id)) to centerX(#\(destinationView.properties._id)) case 4.3") - self.left($0) - } - } - default: - break - } - case .centerY: - switch attribute2 { - case .top: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .centerYToTopOfView, - trackSelf: true - ) { [weak self] in -// print("centerY to top called") - guard let self = self else { return } - self.top(($0.value - (self.clientHeight / 2)).px) - } - case .bottom: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteBottom }, - constraint: .centerYToBottomOfView, - trackSelf: true - ) { [weak self] in -// print("centerY to bottom called") - guard let self = self else { return } - self.top(($0.value - (self.clientHeight / 2)).px) - } - case .centerY: - _setupSides( - currentAbsolute: { [weak self] in self?.absoluteTop }, - currentOffset: { [weak self] in self?.offsetTop }, - destinationAbsolute: { [weak destinationView] in destinationView?.absoluteTop }, - constraint: .centerYToCenterYOfView, - trackSelf: true - ) { [weak self] in - print("centerY to centerY called: destinationView.clientHeight: \(destinationView.clientHeight)") - guard let self = self else { return } - let currentCenter = (self.clientHeight / 2) - let destinationCenter = (destinationView.clientHeight / 2) - if currentCenter > destinationCenter { - print("centerY to centerY case 1") - self.top(($0.value + destinationCenter - currentCenter).px) - } else if destinationCenter > currentCenter { - print("centerY to centerY case 2") - self.top(($0.value + destinationCenter - currentCenter).px) - } else { - print("centerY to centerY case 3") - self.top($0) - } - } - default: - break - } + if index >= 0 { + _autolayout.ruleIndexCache[className] = index } - return self } - // MARK: - top + // MARK: - Edges - /// Has no effect with **position: static** + /// Convenience setter for all sides: top, right, bottom, left @discardableResult - public func top( - to side: Autolayout.ConstraintYSide, - of view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func edges( + _ value: U = 0.px, + breakpoints: [MediaRule.MediaType] ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .top, - attribute2: side.side, - destinationView: view) + top(value) + .left(value) + .right(UnitValue(value.value.doubleValue * (-1), value.unit)) + .bottom(UnitValue(value.value.doubleValue * (-1), value.unit)) } - /// By default to `bottom` of destination view - /// - /// Has no effect with **position: static** + /// Convenience setter for all sides: top, right, bottom, left @discardableResult - public func top( - to view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func edges( + _ value: U = 0.px, + breakpoints: MediaRule.MediaType... ) -> Self { - top(to: .bottom, of: view, state, multiplier: multiplier) + edges(value, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Convenience setter for all sides: top, right, bottom, left + public func edges( + _ value: State, + breakpoints: [MediaRule.MediaType] + ) -> Self { + top(value) + .left(value) + .right(value.map { UnitValue($0.value.doubleValue * (-1), $0.unit) }) + .bottom(value.map { UnitValue($0.value.doubleValue * (-1), $0.unit) }) + } + + /// Convenience setter for all sides: top, right, bottom, left + public func edges( + _ value: State, + breakpoints: MediaRule.MediaType... + ) -> Self { + edges(value, breakpoints: breakpoints) + } + + /// Convenience setter for horizontal sides: left and right @discardableResult - public func top( - to side: Autolayout.ConstraintYSide, - of view: BaseElement, - _ value: U = 0.px + public func edges( + h: U, + breakpoints: [MediaRule.MediaType] ) -> Self { - top(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + left(h) + .right(UnitValue(h.value.doubleValue * (-1), h.unit)) } - /// By default to `bottom` of destination view - /// - /// Has no effect with **position: static** + /// Convenience setter for horizontal sides: left and right @discardableResult - public func top( - to view: BaseElement, - _ value: U = 0.px + public func edges( + h: U, + breakpoints: MediaRule.MediaType... ) -> Self { - top(to: .bottom, of: view, value) + edges(h: h, breakpoints: breakpoints) } - // MARK: - leading + /// Convenience setter for horizontal sides: left and right + @discardableResult + public func edges( + h: State, + breakpoints: [MediaRule.MediaType] + ) -> Self { + left(h) + .right(h.map { UnitValue($0.value.doubleValue * (-1), $0.unit) }) + } - /// Has no effect with **position: static** + /// Convenience setter for horizontal sides: left and right @discardableResult - public func leading( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func edges( + h: State, + breakpoints: MediaRule.MediaType... ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .leading, - attribute2: side.side, - destinationView: view) + edges(h: h, breakpoints: breakpoints) } - /// By default to `trailing` of destination view - /// - /// Has no effect with **position: static** + /// Convenience setter for vertical sides: top and bottom @discardableResult - public func leading( - to view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func edges( + v: U, + breakpoints: [MediaRule.MediaType] ) -> Self { - leading(to: .trailing, of: view, state, multiplier: multiplier) + top(v) + .bottom(UnitValue(v.value.doubleValue * (-1), v.unit)) } - /// Has no effect with **position: static** + /// Convenience setter for vertical sides: top and bottom @discardableResult - public func leading( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, - _ value: U = 0.px + public func edges( + v: U, + breakpoints: MediaRule.MediaType... ) -> Self { - leading(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + edges(v: v, breakpoints: breakpoints) } - /// By default to `trailing` of destination view - /// - /// Has no effect with **position: static** + /// Convenience setter for vertical sides: top and bottom @discardableResult - public func leading( - to view: BaseElement, - _ value: U = 0.px + public func edges( + v: State, + breakpoints: [MediaRule.MediaType] ) -> Self { - leading(to: .trailing, of: view, value) + top(v) + .bottom(v.map { UnitValue($0.value.doubleValue * (-1), $0.unit) }) } - // MARK: - left + /// Convenience setter for vertical sides: top and bottom + @discardableResult + public func edges( + v: State, + breakpoints: MediaRule.MediaType... + ) -> Self { + edges(v: v, breakpoints: breakpoints) + } - /// Has no effect with **position: static** + /// Convenience setter for horizontal sides: left and right, and vertical sides: top and bottom @discardableResult - public func left( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func edges( + h: H, + v: V, + breakpoints: [MediaRule.MediaType] ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .left, - attribute2: side.side, - destinationView: view) + top(v) + .left(h) + .right(UnitValue(h.value.doubleValue * (-1), h.unit)) + .bottom(UnitValue(v.value.doubleValue * (-1), v.unit)) } - /// By default to `right` of destination view - /// - /// Has no effect with **position: static** + /// Convenience setter for horizontal sides: left and right, and vertical sides: top and bottom @discardableResult - public func left( - to view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func edges( + h: H, + v: V, + breakpoints: MediaRule.MediaType... ) -> Self { - left(to: .right, of: view, state, multiplier: multiplier) + edges(h: h, v: v, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Convenience setter for horizontal sides: left and right, and vertical sides: top and bottom @discardableResult - public func left( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, - _ value: U = 0.px + public func edges( + h: State, + v: State, + breakpoints: [MediaRule.MediaType] ) -> Self { - left(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + top(v) + .left(h) + .right(h.map { UnitValue($0.value.doubleValue * (-1), $0.unit) }) + .bottom(v.map { UnitValue($0.value.doubleValue * (-1), $0.unit) }) } - /// By default to `right` of destination view - /// - /// Has no effect with **position: static** + /// Convenience setter for horizontal sides: left and right, and vertical sides: top and bottom @discardableResult - public func left( - to view: BaseElement, - _ value: U = 0.px + public func edges( + h: State, + v: State, + breakpoints: MediaRule.MediaType... ) -> Self { - left(to: .right, of: view, value) + edges(h: h, v: v, breakpoints: breakpoints) } - // MARK: - trailing + // MARK: - Top + + /// Specifies the top position to the first parent element with relative position + @discardableResult + public func top( + _ state: State, + to side: State = .init(wrappedValue: .top), + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("top", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Autolayout.ConstraintYSide, Double) -> Void = { [weak self] value, side, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + let percentage: Int + switch side { + case .top: percentage = 0 + case .center: percentage = 50 + case .bottom: percentage = 100 + } + if value.value.doubleValue > 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("top", value.description + important) + } else { + return rule.custom("top", "calc(\(percentage)% + \(value.description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("top", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("top", "calc((\(percentage)% + \(value.description)) * \(multiplier))" + important) + } + } + } else if value.value.doubleValue < 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("top", value.description + important) + } else { + return rule.custom("top", "calc(\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("top", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("top", "calc((\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description)) * \(multiplier))" + important) + } + } + } else { + return rule.custom("top", "0px" + important) + } + } + } + perform(state.wrappedValue, side.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, side.wrappedValue, multiplier.wrappedValue) + } + side.listen { + perform(state.wrappedValue, $0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, side.wrappedValue, $0) + } + return self + } - /// Has no effect with **position: static** + /// Specifies the top position to the first parent element with relative position @discardableResult - public func trailing( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, + public func top( _ state: State, - multiplier: Double = 1 + to side: State = .init(wrappedValue: .top), + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... + ) -> Self { + top(state, to: side, multiplier: multiplier, breakpoints: breakpoints) + } + + /// Specifies the top position to the first parent element with relative position + @discardableResult + public func top( + _ value: U = 0.px, + to side: Autolayout.ConstraintYSide = .top, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .trailing, - attribute2: side.side, - destinationView: view) + top(.init(wrappedValue: value), to: .init(wrappedValue: side), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `leading` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the top position to the first parent element with relative position @discardableResult - public func trailing( - to view: BaseElement, + public func top( + _ value: U = 0.px, + to side: Autolayout.ConstraintYSide = .top, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... + ) -> Self { + top(value, to: side, multiplier: multiplier, breakpoints: breakpoints) + } + + // MARK: - Left + + /// Specifies the left position to the first parent element with relative position + @discardableResult + public func left( _ state: State, - multiplier: Double = 1 + to side: State = .init(wrappedValue: .left), + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("left", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Autolayout.ConstraintXSide, Double) -> Void = { [weak self] value, side, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + let percentage: Int + switch side { + case .left: percentage = 0 + case .center: percentage = 50 + case .right: percentage = 100 + } + if value.value.doubleValue > 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("left", value.description + important) + } else { + return rule.custom("left", "calc(\(percentage)% + \(value.description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("left", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("left", "calc((\(percentage)% + \(value.description)) * \(multiplier))" + important) + } + } + } else if value.value.doubleValue < 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("left", value.description + important) + } else { + return rule.custom("left", "calc(\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("left", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("left", "calc((\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description)) * \(multiplier))" + important) + } + } + } else { + return rule.custom("left", "0px" + important) + } + } + } + perform(state.wrappedValue, side.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, side.wrappedValue, multiplier.wrappedValue) + } + side.listen { + perform(state.wrappedValue, $0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, side.wrappedValue, $0) + } + return self + } + + /// Specifies the left position to the first parent element with relative position + @discardableResult + public func left( + _ state: State, + to side: State = .init(wrappedValue: .left), + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - trailing(to: .leading, of: view, state, multiplier: multiplier) + left(state, to: side, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies the left position to the first parent element with relative position @discardableResult - public func trailing( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, - _ value: U = 0.px + public func left( + _ value: U = 0.px, + to side: Autolayout.ConstraintXSide = .left, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - trailing(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + left(.init(wrappedValue: value), to: .init(wrappedValue: side), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `leading` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the left position to the first parent element with relative position @discardableResult - public func trailing( - to view: BaseElement, - _ value: U = 0.px + public func left( + _ value: U = 0.px, + to side: Autolayout.ConstraintXSide = .left, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - trailing(to: .leading, of: view, value) + left(value, to: side, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - right + // MARK: - Right - /// Has no effect with **position: static** + /// Specifies the right position to the first parent element with relative position @discardableResult public func right( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, _ state: State, - multiplier: Double = 1 - ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .right, - attribute2: side.side, - destinationView: view) + to side: State = .init(wrappedValue: .right), + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("right", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Autolayout.ConstraintXSide, Double) -> Void = { [weak self] value, side, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + let percentage: Int + switch side { + case .left: percentage = 100 + case .center: percentage = 50 + case .right: percentage = 0 + } + if value.value.doubleValue > 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("right", value.description + important) + } else { + return rule.custom("right", "calc(\(percentage)% + \(value.description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("right", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("right", "calc((\(percentage)% + \(value.description)) * \(multiplier))" + important) + } + } + } else if value.value.doubleValue < 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("right", value.description + important) + } else { + return rule.custom("right", "calc(\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("right", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("right", "calc((\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description)) * \(multiplier))" + important) + } + } + } else { + return rule.custom("right", "0px" + important) + } + } + } + perform(state.wrappedValue, side.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, side.wrappedValue, multiplier.wrappedValue) + } + side.listen { + perform(state.wrappedValue, $0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, side.wrappedValue, $0) + } + return self } - /// By default to `left` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the right position to the first parent element with relative position @discardableResult public func right( - to view: BaseElement, _ state: State, - multiplier: Double = 1 + to side: State = .init(wrappedValue: .right), + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - right(to: .left, of: view, state, multiplier: multiplier) + right(state, to: side, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies the right position to the first parent element with relative position @discardableResult public func right( - to side: Autolayout.ConstraintXSide, - of view: BaseElement, - _ value: U = 0.px + _ value: U = 0.px, + to side: Autolayout.ConstraintXSide = .right, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - right(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + right(.init(wrappedValue: value), to: .init(wrappedValue: side), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `left` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the right position to the first parent element with relative position @discardableResult public func right( - to view: BaseElement, - _ value: U = 0.px + _ value: U = 0.px, + to side: Autolayout.ConstraintXSide = .right, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - right(to: .left, of: view, value) + right(value, to: side, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - bottom + // MARK: - Bottom - /// Has no effect with **position: static** + /// Specifies the bottom position to the first parent element with relative position @discardableResult public func bottom( - to side: Autolayout.ConstraintYSide, - of view: BaseElement, _ state: State, - multiplier: Double = 1 - ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .bottom, - attribute2: side.side, - destinationView: view) + to side: State = .init(wrappedValue: .bottom), + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("bottom", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Autolayout.ConstraintYSide, Double) -> Void = { [weak self] value, side, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + let percentage: Int + switch side { + case .top: percentage = 100 + case .center: percentage = 50 + case .bottom: percentage = 0 + } + if value.value.doubleValue > 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("bottom", value.description + important) + } else { + return rule.custom("bottom", "calc(\(percentage)% + \(value.description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("bottom", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("bottom", "calc((\(percentage)% + \(value.description)) * \(multiplier))" + important) + } + } + } else if value.value.doubleValue < 0 { + if multiplier == 1 { + if percentage == 0 { + return rule.custom("bottom", value.description + important) + } else { + return rule.custom("bottom", "calc(\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description))" + important) + } + } else { + if percentage == 0 { + return rule.custom("bottom", "calc(\(value.description) * \(multiplier))" + important) + } else { + return rule.custom("bottom", "calc((\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description)) * \(multiplier))" + important) + } + } + } else { + return rule.custom("bottom", "0px" + important) + } + } + } + perform(state.wrappedValue, side.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, side.wrappedValue, multiplier.wrappedValue) + } + side.listen { + perform(state.wrappedValue, $0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, side.wrappedValue, $0) + } + return self } - /// By default to `top` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the bottom position to the first parent element with relative position @discardableResult public func bottom( - to view: BaseElement, _ state: State, - multiplier: Double = 1 + to side: State = .init(wrappedValue: .bottom), + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - bottom(to: .top, of: view, state, multiplier: multiplier) + bottom(state, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies the bottom position to the first parent element with relative position @discardableResult public func bottom( - to side: Autolayout.ConstraintYSide, - of view: BaseElement, - _ value: U = 0.px + _ value: U = 0.px, + to side: Autolayout.ConstraintYSide = .bottom, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - bottom(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + bottom(.init(wrappedValue: value), to: .init(wrappedValue: side), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `top` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the bottom position to the first parent element with relative position @discardableResult public func bottom( - to view: BaseElement, - _ value: U = 0.px + _ value: U = 0.px, + to side: Autolayout.ConstraintYSide = .bottom, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - bottom(to: .top, of: view, value) + bottom(value, to: side, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - center x + // MARK: - Center X - /// Has no effect with **position: static** + /// Specifies the horizontal center position to the first parent element with relative position @discardableResult public func centerX( - to side: Autolayout.ConstraintCXSide, - of view: BaseElement, _ state: State, - multiplier: Double = 1 - ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .centerX, - attribute2: side.side, - destinationView: view) + to side: State = .init(wrappedValue: .center), + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("left", breakpoints: breakpoints) + let translationClassName = _getClassName("translate", breakpoints: breakpoints) + self.class(.init(stringLiteral: className), .init(stringLiteral: translationClassName)) + let perform: (U, Autolayout.ConstraintCXSide, Double) -> Void = { [weak self] value, side, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + let percentage: Int + switch side { + case .left: percentage = 0 + case .center: percentage = 50 + case .right: percentage = 100 + } + if value.value.doubleValue > 0 { + return rule.custom("left", "calc((\(percentage)% + \(value.description)) * \(multiplier))" + important) + } else if value.value.doubleValue < 0 { + return rule.custom("left", "calc((\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description)) * \(multiplier))" + important) + } else { + if multiplier == 1 { + return rule.custom("left", "\(percentage)%" + important) + } else { + return rule.custom("left", "calc(\(percentage)% * \(multiplier))" + important) + } + } + } + } + let performTranslate: () -> Void = { [weak self] in + self?._setRule(translationClassName, breakpoints: breakpoints) { rule in + rule.custom("--translate-x", "-50%").custom("translate", "var(--translate-x, 0) var(--translate-y, 0)" + important) + } + } + perform(state.wrappedValue, side.wrappedValue, multiplier.wrappedValue) + performTranslate() + state.listen { + perform($0, side.wrappedValue, multiplier.wrappedValue) + performTranslate() + } + side.listen { + perform(state.wrappedValue, $0, multiplier.wrappedValue) + performTranslate() + } + multiplier.listen { + perform(state.wrappedValue, side.wrappedValue, $0) + performTranslate() + } + return self } - /// By default to `centerX` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the horizontal center position to the first parent element with relative position @discardableResult public func centerX( - to view: BaseElement, _ state: State, - multiplier: Double = 1 + to side: State = .init(wrappedValue: .center), + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - centerX(to: .x, of: view, state, multiplier: multiplier) + centerX(state, to: side, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies the horizontal center position to the first parent element with relative position @discardableResult public func centerX( - to side: Autolayout.ConstraintCXSide, - of view: BaseElement, _ value: U = 0.px, - multiplier: Double = 1 + to side: Autolayout.ConstraintCXSide = .center, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - centerX(to: side, - of: view, - .init(wrappedValue: value), - multiplier: multiplier) + centerX(.init(wrappedValue: value), to: .init(wrappedValue: side), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `centerX` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the horizontal center position to the first parent element with relative position @discardableResult public func centerX( - to view: BaseElement, _ value: U = 0.px, - multiplier: Double = 1 + to side: Autolayout.ConstraintCXSide = .center, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - centerX(to: .x, of: view, value, multiplier: multiplier) + centerX(value, to: side, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - center y + // MARK: - Center Y - /// Has no effect with **position: static** + /// Specifies the vertical center position to the first parent element with relative position @discardableResult public func centerY( - to side: Autolayout.ConstraintCYSide, - of view: BaseElement, _ state: State, - multiplier: Double = 1 - ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .centerY, - attribute2: side.side, - destinationView: view) + to side: State = .init(wrappedValue: .center), + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("top", breakpoints: breakpoints) + let translationClassName = _getClassName("translate", breakpoints: breakpoints) + self.class(.init(stringLiteral: className), .init(stringLiteral: translationClassName)) + let perform: (U, Autolayout.ConstraintCYSide, Double) -> Void = { [weak self] value, side, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + let percentage: Int + switch side { + case .top: percentage = 0 + case .center: percentage = 50 + case .bottom: percentage = 100 + } + if value.value.doubleValue > 0 { + return rule.custom("top", "calc((\(percentage)% + \(value.description)) * \(multiplier))" + important) + } else if value.value.doubleValue < 0 { + return rule.custom("top", "calc((\(percentage)% - \(UnitValue(-value.value.doubleValue, value.unit).description)) * \(multiplier))" + important) + } else { + if multiplier == 1 { + return rule.custom("top", "\(percentage)%" + important) + } else { + return rule.custom("top", "calc(\(percentage)% * \(multiplier))" + important) + } + } + } + } + let performTranslate: () -> Void = { [weak self] in + self?._setRule(translationClassName, breakpoints: breakpoints) { rule in + rule.custom("--translate-y", "-50%").custom("translate", "var(--translate-x, 0) var(--translate-y, 0)" + important) + } + } + perform(state.wrappedValue, side.wrappedValue, multiplier.wrappedValue) + performTranslate() + state.listen { + perform($0, side.wrappedValue, multiplier.wrappedValue) + performTranslate() + } + side.listen { + perform(state.wrappedValue, $0, multiplier.wrappedValue) + performTranslate() + } + multiplier.listen { + perform(state.wrappedValue, side.wrappedValue, $0) + performTranslate() + } + return self } - /// By default to `centerY` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the vertical center position to the first parent element with relative position @discardableResult public func centerY( - to view: BaseElement, _ state: State, - multiplier: Double = 1 + to side: State = .init(wrappedValue: .center), + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - centerY(to: .y, of: view, state, multiplier: multiplier) + centerY(state, to: side, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies the vertical center position to the first parent element with relative position @discardableResult public func centerY( - to side: Autolayout.ConstraintCYSide, - of view: BaseElement, _ value: U = 0.px, - multiplier: Double = 1 + to side: Autolayout.ConstraintCYSide = .center, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - centerY(to: side, - of: view, - .init(wrappedValue: value), - multiplier: multiplier) + centerY(.init(wrappedValue: value), to: .init(wrappedValue: side), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `centerY` of destination view - /// - /// Has no effect with **position: static** + /// Specifies the vertical center position to the first parent element with relative position @discardableResult public func centerY( - to view: BaseElement, _ value: U = 0.px, - multiplier: Double = 1 + to side: Autolayout.ConstraintCYSide = .center, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - centerY(to: .y, of: view, value, multiplier: multiplier) + centerY(value, to: side, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - center both + // MARK: Center X+Y - /// Has no effect with **position: static** + /// Specifies both vertical and horizontal center position to the first parent element with relative position @discardableResult public func center( - to view: BaseElement, - _ value: State, - multiplier: Double = 1 + _ state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] ) -> Self { - centerX(to: view, value, multiplier: multiplier) - .centerY(to: view, value, multiplier: multiplier) + centerX(state, multiplier: multiplier, breakpoints: breakpoints) + .centerY(state, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies both vertical and horizontal center position to the first parent element with relative position @discardableResult public func center( - to view: BaseElement, - _ value: U - ) -> Self { - centerX(to: view, value).centerY(to: view, value) - } - - /// Has no effect with **position: static** - @discardableResult - public func center( - to view: BaseElement, - x: State, - y: State, - multiplier: Double = 1 - ) -> Self { - centerX(to: view, x, multiplier: multiplier) - .centerY(to: view, y, multiplier: multiplier) - } - - /// Has no effect with **position: static** - @discardableResult - public func center( - to view: BaseElement, - x: State, - y: Y, - multiplier: Double = 1 + _ state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - centerX(to: view, x, multiplier: multiplier) - .centerY(to: view, y, multiplier: multiplier) + center(state, multiplier: multiplier, breakpoints: breakpoints) } - /// Has no effect with **position: static** + /// Specifies both vertical and horizontal center position to the first parent element with relative position @discardableResult - public func center( - to view: BaseElement, - x: X, - y: State, - multiplier: Double = 1 + public func center( + _ value: U = 0.px, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - centerX(to: view, x, multiplier: multiplier) - .centerY(to: view, y, multiplier: multiplier) + center( + .init(wrappedValue: value), + multiplier: .init(wrappedValue: multiplier), + breakpoints: breakpoints + ) } - /// Has no effect with **position: static** + /// Specifies both vertical and horizontal center position to the first parent element with relative position @discardableResult - public func center( - to view: BaseElement, - x: X = 0.px, - y: Y = 0.px + public func center( + _ value: U = 0.px, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - centerX(to: view, x).centerY(to: view, y) + center(value, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - width + // MARK: - Width + /// Sets the width of an element @discardableResult public func width( - to side: Autolayout.ConstraintDSide, - of view: BaseElement, _ state: State, - multiplier: Double = 1 - ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .width, - attribute2: side.side, - destinationView: view) + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let className = _getClassName("width", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Double) -> Void = { [weak self] value, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + return rule.custom("width", UnitValue(value.value.doubleValue, value.unit, important: breakpoints.count > 0).description) + } + } + perform(state.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, $0) + } + return self } - /// By default to `width` of destination view + /// Sets the width of an element @discardableResult public func width( - to view: BaseElement, _ state: State, - multiplier: Double = 1 + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - width(to: .width, of: view, state, multiplier: multiplier) + width(state, multiplier: multiplier, breakpoints: breakpoints) } + /// Sets the width of an element @discardableResult public func width( - to side: Autolayout.ConstraintDSide, - of view: BaseElement, - _ value: U = 0.px + _ value: U = 0.px, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - width(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + width(.init(wrappedValue: value), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `width` of destination view + /// Sets the width of an element @discardableResult public func width( - to view: BaseElement, - _ value: U = 0.px + _ value: U = 0.px, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - width(to: .width, of: view, value) + width(value, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - height + // MARK: - Width to parent + /// Sets the width of an element to fit first parent element with relative position @discardableResult - public func height( - to side: Autolayout.ConstraintDSide, - of view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func widthToParent( + extra state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] ) -> Self { - _createRelative(value: state, - multiplier: multiplier, - attribute1: .height, - attribute2: side.side, - destinationView: view) + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("width", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Double) -> Void = { [weak self] extra, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + if extra.value.doubleValue > 0 { + return rule.custom("width", "calc(\(100 * multiplier)% + \(extra.description))" + important) + } else if extra.value.doubleValue < 0 { + return rule.custom("width", "calc(\(100 * multiplier)% - \(extra.description))" + important) + } else { + return rule.custom("width", "\(100 * multiplier)%" + important) + } + } + } + perform(state.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, $0) + } + return self } - /// By default to `height` of destination view + /// Sets the width of an element to fit first parent element with relative position @discardableResult - public func height( - to view: BaseElement, - _ state: State, - multiplier: Double = 1 + public func widthToParent( + extra state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - height(to: .height, of: view, state, multiplier: multiplier) + widthToParent(extra: state, multiplier: multiplier, breakpoints: breakpoints) } + /// Sets the width of an element to fit first parent element with relative position @discardableResult - public func height( - to side: Autolayout.ConstraintDSide, - of view: BaseElement, - _ value: U = 0.px + public func widthToParent( + extra value: U = 0.px, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - height(to: side, - of: view, - .init(wrappedValue: value), - multiplier: 1) + widthToParent(extra: .init(wrappedValue: value), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - /// By default to `height` of destination view + /// Sets the width of an element to fit first parent element with relative position @discardableResult - public func height( - to view: BaseElement, - _ value: U = 0.px + public func widthToParent( + extra value: U = 0.px, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - width(to: .width, of: view, value) + widthToParent(extra: value, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - equal + // MARK: - Height + /// Sets the height of an element @discardableResult - public func equalSize( - to: BaseElement, - _ value: U = 0.px - ) -> Self { - width(to: to, value) - .height(to: to, value) + public func height( + _ state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let className = _getClassName("height", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Double) -> Void = { [weak self] value, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + return rule.custom("height", UnitValue(value.value.doubleValue, value.unit, important: breakpoints.count > 0).description) + } + } + perform(state.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, multiplier.wrappedValue) + } + multiplier.listen { + perform(state.wrappedValue, $0) + } + return self } + /// Sets the height of an element @discardableResult - public func equalSize( - to: BaseElement, + public func height( _ state: State, - multiplier: Double = 1 + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - width(to: to, state, multiplier: multiplier) - .height(to: to, state, multiplier: multiplier) - } - - // MARK: - Super - - @discardableResult - public func edgesToSuperview(_ value: U = 0.px) -> Self { - topToSuperview(value) - .leadingToSuperview(value) - .trailingToSuperview(UnitValue(value.value.doubleValue * (-1), .px)) - .bottomToSuperview(UnitValue(value.value.doubleValue * (-1), .px)) - } - - @discardableResult - public func edgesToSuperview(h: U) -> Self { - leadingToSuperview(h) - .trailingToSuperview(UnitValue(h.value.doubleValue * (-1), .px)) + height(state, multiplier: multiplier, breakpoints: breakpoints) } + /// Sets the height of an element @discardableResult - public func edgesToSuperview(v: U) -> Self { - topToSuperview(v) - .bottomToSuperview(UnitValue(v.value.doubleValue * (-1), .px)) - } - - @discardableResult - public func edgesToSuperview(h: H, v: V) -> Self { - topToSuperview(v) - .leadingToSuperview(h) - .trailingToSuperview(UnitValue(h.value.doubleValue * (-1), .px)) - .bottomToSuperview(UnitValue(v.value.doubleValue * (-1), .px)) - } - - public func edgesToSuperview(_ value: State) -> Self { - topToSuperview(value) - .leadingToSuperview(value) - .trailingToSuperview(value.map { UnitValue($0.value.doubleValue * (-1), .px) }) - .bottomToSuperview(value.map { UnitValue($0.value.doubleValue * (-1), .px) }) + public func height( + _ value: U = 0.px, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] + ) -> Self { + height(.init(wrappedValue: value), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } + /// Sets the height of an element @discardableResult - public func edgesToSuperview(h: State) -> Self { - leadingToSuperview(h) - .trailingToSuperview(h.map { UnitValue($0.value.doubleValue * (-1), .px) }) + public func height( + _ value: U = 0.px, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... + ) -> Self { + height(value, multiplier: multiplier, breakpoints: breakpoints) } - @discardableResult - public func edgesToSuperview(v: State) -> Self { - topToSuperview(v) - .bottomToSuperview(v.map { UnitValue($0.value.doubleValue * (-1), .px) }) - } + // MARK: - Height to parent + /// Sets the height of an element to fit first parent element with relative position @discardableResult - public func edgesToSuperview(h: State, v: State) -> Self { - topToSuperview(v) - .leadingToSuperview(h) - .trailingToSuperview(h.map { UnitValue($0.value.doubleValue * (-1), .px) }) - .bottomToSuperview(v.map { UnitValue($0.value.doubleValue * (-1), .px) }) - } - - @discardableResult - public func edgesToSuperview(top: U? = nil, leading: U? = nil, trailing: U? = nil, bottom: U? = nil) -> Self { - if let top = top { - topToSuperview(top) - } - if let leading = leading { - leadingToSuperview(leading) - } - if let trailing = trailing { - trailingToSuperview(trailing) - } - if let bottom = bottom { - bottomToSuperview(bottom) - } - return self - } - - @discardableResult - public func edgesToSuperview(top: State? = nil, leading: State? = nil, trailing: State? = nil, bottom: State? = nil) -> Self { - if let top = top { - topToSuperview(top) - } - if let leading = leading { - leadingToSuperview(leading) + public func heightToParent( + extra state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: [MediaRule.MediaType] + ) -> Self { + let important = breakpoints.count > 0 ? "!important" : "" + let className = _getClassName("height", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (U, Double) -> Void = { [weak self] extra, multiplier in + self?._setRule(className, breakpoints: breakpoints) { rule in + if extra.value.doubleValue > 0 { + return rule.custom("height", "calc(\(100 * multiplier)% + \(extra.description))" + important) + } else if extra.value.doubleValue < 0 { + return rule.custom("height", "calc(\(100 * multiplier)% - \(extra.description))" + important) + } else { + return rule.custom("height", "\(100 * multiplier)%" + important) + } + } } - if let trailing = trailing { - trailingToSuperview(trailing) + perform(state.wrappedValue, multiplier.wrappedValue) + state.listen { + perform($0, multiplier.wrappedValue) } - if let bottom = bottom { - bottomToSuperview(bottom) + multiplier.listen { + perform(state.wrappedValue, $0) } return self } - // MARK: - top - + /// Sets the height of an element to fit first parent element with relative position @discardableResult - public func topToSuperview( - _ state: State, - multiplier: Double = 1 + public func heightToParent( + extra state: State, + multiplier: State = .init(wrappedValue: 1), + breakpoints: MediaRule.MediaType... ) -> Self { - let setup = { [weak self] in - guard let self = self else { return } - guard let superview = self.superview else { return } - self.top(to: .top, of: superview, state, multiplier: multiplier) - } - setup() - onDidAddToDOM(setup) - return self + heightToParent(extra: state, multiplier: multiplier, breakpoints: breakpoints) } + /// Sets the height of an element to fit first parent element with relative position @discardableResult - public func topToSuperview(_ value: U = 0.px, multiplier: Double = 1) -> Self { - topToSuperview( - .init(wrappedValue: value), - multiplier: multiplier - ) - } - - // MARK: - left - - @discardableResult - public func leftToSuperview( - _ state: State, - multiplier: Double = 1 + public func heightToParent( + extra value: U = 0.px, + multiplier: Double = 1, + breakpoints: [MediaRule.MediaType] ) -> Self { - let setup = { [weak self] in - guard let self = self else { return } - guard let superview = self.superview else { return } - self.left(to: .left, of: superview, state, multiplier: multiplier) - } - setup() - onDidAddToDOM(setup) - return self + heightToParent(extra: .init(wrappedValue: value), multiplier: .init(wrappedValue: multiplier), breakpoints: breakpoints) } - + + /// Sets the height of an element to fit first parent element with relative position @discardableResult - public func leftToSuperview( - _ value: U = 0.px, - multiplier: Double = 1 + public func heightToParent( + extra value: U = 0.px, + multiplier: Double = 1, + breakpoints: MediaRule.MediaType... ) -> Self { - leftToSuperview(.init(wrappedValue: value), multiplier: multiplier) + heightToParent(extra: value, multiplier: multiplier, breakpoints: breakpoints) } - // MARK: - leading + // MARK: - Position + /// Specifies the type of positioning method used for an element (static, relative, absolute or fixed) @discardableResult - public func leadingToSuperview( - _ state: State, - multiplier: Double = 1 + public func position( + _ state: State, + breakpoints: [MediaRule.MediaType] ) -> Self { - let setup = { [weak self] in - guard let self = self else { return } - guard let superview = self.superview else { return } - self.leading(to: .leading, of: superview, state, multiplier: multiplier) + let className = _getClassName("position", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (PositionType) -> Void = { [weak self] value in + self?._setRule(className, breakpoints: breakpoints) { rule in + if breakpoints.count > 0 { + return rule.position(value.important) + } else { + return rule.position(value) + } + } + } + perform(state.wrappedValue) + state.listen { + perform($0) } - setup() - onDidAddToDOM(setup) return self } - + + /// Specifies the type of positioning method used for an element (static, relative, absolute or fixed) @discardableResult - public func leadingToSuperview( - _ value: U = 0.px, - multiplier: Double = 1 + public func position( + _ state: State, + breakpoints: MediaRule.MediaType... ) -> Self { - leadingToSuperview(.init(wrappedValue: value), multiplier: multiplier) + position(state, breakpoints: breakpoints) } - // MARK: - right - + /// Specifies the type of positioning method used for an element (static, relative, absolute or fixed) @discardableResult - public func rightToSuperview( - _ state: State, - multiplier: Double = 1 + public func position( + _ value: PositionType, + breakpoints: [MediaRule.MediaType] ) -> Self { - let setup = { [weak self] in - guard let self = self else { return } - guard let superview = self.superview else { return } - self.right(to: .right, of: superview, state, multiplier: multiplier) - } - setup() - onDidAddToDOM(setup) - return self + position(.init(wrappedValue: value), breakpoints: breakpoints) } + /// Specifies the type of positioning method used for an element (static, relative, absolute or fixed) @discardableResult - public func rightToSuperview( - _ value: U = 0.px, - multiplier: Double = 1 + public func position( + _ value: PositionType, + breakpoints: MediaRule.MediaType... ) -> Self { - rightToSuperview(.init(wrappedValue: value), multiplier: multiplier) + position(value, breakpoints: breakpoints) } - // MARK: - trailing + // MARK: - Display + /// Specifies how a certain HTML element should be displayed @discardableResult - public func trailingToSuperview( - _ state: State, - multiplier: Double = 1 + public func display( + _ state: State, + breakpoints: [MediaRule.MediaType] ) -> Self { - let setup = { [weak self] in - guard let self = self else { return } - guard let superview = self.superview else { return } - self.trailing(to: .trailing, of: superview, state, multiplier: multiplier) + let className = _getClassName("display", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (DisplayType) -> Void = { [weak self] value in + self?._setRule(className, breakpoints: breakpoints) { rule in + if breakpoints.count > 0 { + return rule.display(value.important) + } else { + return rule.display(value) + } + } + } + perform(state.wrappedValue) + state.listen { + perform($0) } - setup() - onDidAddToDOM(setup) return self } + /// Specifies how a certain HTML element should be displayed @discardableResult - public func trailingToSuperview( - _ value: U = 0.px, - multiplier: Double = 1 + public func display( + _ state: State, + breakpoints: MediaRule.MediaType... ) -> Self { - trailingToSuperview(.init(wrappedValue: value), multiplier: multiplier) + display(state, breakpoints: breakpoints) } - // MARK: - bottom - + /// Specifies how a certain HTML element should be displayed @discardableResult - public func bottomToSuperview( - _ state: State, - multiplier: Double = 1 + public func display( + _ value: DisplayType, + breakpoints: [MediaRule.MediaType] ) -> Self { - let setup = { [weak self] in - guard let self = self else { return } - guard let superview = self.superview else { return } - self.bottom(to: .bottom, of: superview, state, multiplier: multiplier) - } - setup() - onDidAddToDOM(setup) - return self + display(.init(wrappedValue: value), breakpoints: breakpoints) } + /// Specifies how a certain HTML element should be displayed @discardableResult - public func bottomToSuperview( - _ value: U = 0.px, - multiplier: Double = 1 + public func display( + _ value: DisplayType, + breakpoints: MediaRule.MediaType... ) -> Self { - bottomToSuperview(.init(wrappedValue: value), multiplier: multiplier) + display(value, breakpoints: breakpoints) } - // MARK: - center x + // MARK: - Visibility + /// Specifies whether or not an element is visible @discardableResult - public func centerXInSuperview( - _ state: State, - side: Autolayout.ConstraintCXSide = .centerX, - multiplier: Double = 1 - ) -> Self { -//// print("centerXInSuperview 1") -// let setup = { [weak self] in -//// print("centerXInSuperview 2") -// guard let self = self else { return } -//// print("centerXInSuperview 3") -// guard let superview = self.superview else { return } -//// print("centerXInSuperview 4") -// self.centerX(to: side, of: superview, state, multiplier: multiplier) -// } -// setup() -// onDidAddToDOM(setup) -// return self - left(50.percent).transform(.translateX(-50.percent)) - } - - @discardableResult - public func centerXInSuperview( - _ value: U = 0.px, - side: Autolayout.ConstraintCXSide = .centerX, - multiplier: Double = 1 + public func visibility( + _ state: State, + breakpoints: [MediaRule.MediaType] ) -> Self { - centerXInSuperview(.init(wrappedValue: value), side: side, multiplier: multiplier) - } - - // MARK: - center y - - @discardableResult - public func centerYInSuperview( - _ state: State, - side: Autolayout.ConstraintCYSide = .centerY, - multiplier: Double = 1 - ) -> Self { -// let setup = { [weak self] in -// guard let self = self else { return } -// guard let superview = self.superview else { return } -// self.centerY(to: side, of: superview, state, multiplier: multiplier) -// } -// setup() -// onDidAddToDOM(setup) -// return self - let set: (U) -> Void = { [weak self] value in - guard let self = self else { return } - if value.value.doubleValue > 0 { - self.custom("top", "calc((50% + \(value.value.doubleValue)) * \(multiplier))") - } else if value.value.doubleValue < 0 { - self.custom("top", "calc((50% - \(value.value.doubleValue)) * \(multiplier))") - } else { - self.custom("top", "calc(50% * \(multiplier))") + let className = _getClassName("visibility", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (VisibilityType) -> Void = { [weak self] value in + self?._setRule(className, breakpoints: breakpoints) { rule in + if breakpoints.count > 0 { + return rule.visibility(value.important) + } else { + return rule.visibility(value) + } } } - state.listen { set($0) } - set(state.wrappedValue) - return transform(.translate(-20.percent, -50.percent)) + perform(state.wrappedValue) + state.listen { + perform($0) + } + return self } + /// Specifies whether or not an element is visible @discardableResult - public func centerYInSuperview( - _ value: U = 0.px, - side: Autolayout.ConstraintCYSide = .centerY, - multiplier: Double = 1 + public func visibility( + _ state: State, + breakpoints: MediaRule.MediaType... ) -> Self { - centerYInSuperview(.init(wrappedValue: value), side: side, multiplier: multiplier) + visibility(state, breakpoints: breakpoints) } - // MARK: center both - + /// Specifies whether or not an element is visible @discardableResult - public func centerInSuperview( - _ state: State, - multiplier: Double = 1 + public func visibility( + _ value: VisibilityType, + breakpoints: [MediaRule.MediaType] ) -> Self { - centerXInSuperview(state, multiplier: multiplier) - .centerYInSuperview(state, multiplier: multiplier) + visibility(.init(wrappedValue: value), breakpoints: breakpoints) } + /// Specifies whether or not an element is visible @discardableResult - public func centerInSuperview(_ value: U = 0.px) -> Self { - centerXInSuperview(value) - .centerYInSuperview(value) + public func visibility( + _ value: VisibilityType, + breakpoints: MediaRule.MediaType... + ) -> Self { + visibility(value, breakpoints: breakpoints) } + // MARK: - Opacity + + /// Sets the opacity level for an element @discardableResult - public func centerInSuperview(x: X, y: Y) -> Self { - centerXInSuperview(x) - .centerYInSuperview(y) + public func opacity( + _ state: State, + breakpoints: [MediaRule.MediaType] + ) -> Self where N: UniValue, N.UniValue: NumericValue { + let className = _getClassName("opacity", breakpoints: breakpoints) + self.class(.init(stringLiteral: className)) + let perform: (N) -> Void = { [weak self] value in + self?._setRule(className, breakpoints: breakpoints) { rule in + let container = NumericValueContainer(value, important: breakpoints.count > 0) + return rule.custom("opacity", container.value) + } + } + perform(state.wrappedValue) + state.listen { + perform($0) + } + return self } + /// Sets the opacity level for an element @discardableResult - public func centerInSuperview( - x: State, - y: State, - multiplier: Double = 1 - ) -> Self { - centerXInSuperview(x, multiplier: multiplier) - .centerYInSuperview(y, multiplier: multiplier) + public func opacity( + _ state: State, + breakpoints: MediaRule.MediaType... + ) -> Self where N: UniValue, N.UniValue: NumericValue { + opacity(state, breakpoints: breakpoints) } - // MARK: - width - + /// Sets the opacity level for an element @discardableResult - public func widthToSuperview( - _ state: State, - dimension: Autolayout.ConstraintDSide = .width, - multiplier: Double = 1 - ) -> Self { - width((multiplier * 100).percent) + public func opacity( + _ value: N, + breakpoints: [MediaRule.MediaType] + ) -> Self where N: UniValue, N.UniValue: NumericValue { + opacity(.init(wrappedValue: value), breakpoints: breakpoints) } -// @discardableResult -// public func widthToHeightOfSuperview( -// _ value: U = 0.px, -// multiplier: Double = 1 -// ) -> Self { -// let setup = { [weak self] in -// guard let self = self else { return } -// guard let superview = self.superview else { return } -// self.width(to: .height, of: superview, state, multiplier: multiplier) -// } -// setup() -// onDidAddToDOM(setup) -// } - - // MARK: - height - + /// Sets the opacity level for an element @discardableResult - public func heightToSuperview( - _ state: State, - dimension: Autolayout.ConstraintDSide = .height, - multiplier: Double = 1 - ) -> Self { -// let setup = { [weak self] in -// guard let self = self else { return } -// guard let superview = self.superview else { return } -// self.height(to: dimension, of: superview, state, multiplier: multiplier) -// } -// setup() -// onDidAddToDOM(setup) -// return self - height((multiplier * 100).percent) - } - -// @discardableResult -// public func heightToSuperview( -// _ value: U = 0.px, -// dimension: Autolayout.ConstraintDSide = .height, -// multiplier: Double = 1 -// ) -> Self { -// heightToSuperview( -// .init(wrappedValue: value), -// dimension: dimension, -// multiplier: multiplier -// ) -// } + public func opacity( + _ value: N, + breakpoints: MediaRule.MediaType... + ) -> Self where N: UniValue, N.UniValue: NumericValue { + opacity(value, breakpoints: breakpoints) + } }