Shagen Ogandzhanian
4 years ago
131 changed files with 9773 additions and 30 deletions
@ -1,30 +1,3 @@ |
|||||||
# Intellij files |
//gradle |
||||||
|
build |
||||||
.idea/* |
.gradle |
||||||
!.idea/copyright |
|
||||||
!.idea/scopes |
|
||||||
|
|
||||||
# Mac OS files |
|
||||||
|
|
||||||
.DS_Store |
|
||||||
|
|
||||||
# Compiled class file |
|
||||||
*.class |
|
||||||
|
|
||||||
# Log file |
|
||||||
*.log |
|
||||||
|
|
||||||
# Package Files # |
|
||||||
*.jar |
|
||||||
!gradle-wrapper.jar |
|
||||||
|
|
||||||
*.war |
|
||||||
*.nar |
|
||||||
*.ear |
|
||||||
*.zip |
|
||||||
*.tar.gz |
|
||||||
*.rar |
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml |
|
||||||
hs_err_pid* |
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@ |
|||||||
|
plugins { |
||||||
|
id("org.jetbrains.kotlin.multiplatform") version("1.4.32") apply(false) |
||||||
|
id("org.jetbrains.compose") version "0.0.0-web-dev-12" apply(false) |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
plugins { |
||||||
|
id("kotlin-multiplatform") |
||||||
|
id("org.jetbrains.compose") |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
kotlin { |
||||||
|
jvm() |
||||||
|
js(IR) { |
||||||
|
browser() { |
||||||
|
testTask { |
||||||
|
testLogging.showStandardStreams = true |
||||||
|
useKarma { |
||||||
|
useChromeHeadless() |
||||||
|
useFirefox() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
binaries.executable() |
||||||
|
} |
||||||
|
|
||||||
|
sourceSets { |
||||||
|
commonMain { |
||||||
|
dependencies { |
||||||
|
implementation(compose.runtime) |
||||||
|
implementation(kotlin("stdlib-common")) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
jsMain { |
||||||
|
dependencies { |
||||||
|
implementation(kotlin("stdlib-js")) |
||||||
|
implementation(npm('css-typed-om', '0.4.0')) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
jsTest { |
||||||
|
dependencies { |
||||||
|
implementation kotlin("test-js") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
jvmMain { |
||||||
|
dependencies { |
||||||
|
implementation(compose.desktop.currentOs) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
interface Alignment { |
||||||
|
interface Vertical : Alignment |
||||||
|
interface Horizontal : Alignment |
||||||
|
|
||||||
|
companion object { |
||||||
|
val TopStart = object : Alignment {} |
||||||
|
val TopCenter = object : Alignment {} |
||||||
|
val TopEnd = object : Alignment {} |
||||||
|
val CenterStart = object : Alignment {} |
||||||
|
val Center = object : Alignment {} |
||||||
|
val CenterEnd = object : Alignment {} |
||||||
|
val BoottomStart = object : Alignment {} |
||||||
|
val BoottomCenter = object : Alignment {} |
||||||
|
val BoottomEnd = object : Alignment {} |
||||||
|
|
||||||
|
val Top = object : Alignment.Vertical {} |
||||||
|
val CenterVertically = object : Alignment.Vertical {} |
||||||
|
val Bottom = object : Alignment.Vertical {} |
||||||
|
|
||||||
|
val Start = object : Alignment.Horizontal {} |
||||||
|
val CenterHorizontally = object : Alignment.Horizontal {} |
||||||
|
val End = object : Alignment.Horizontal {} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
object Arrangement { |
||||||
|
interface Horizontal |
||||||
|
interface Vertical |
||||||
|
|
||||||
|
val End = object : Horizontal {} |
||||||
|
val Start = object : Horizontal {} |
||||||
|
val Top = object : Vertical {} |
||||||
|
val Bottom = object : Vertical {} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.core.graphics |
||||||
|
|
||||||
|
public data class Color(val red: Int, val green: Int, val blue: Int) { |
||||||
|
|
||||||
|
companion object { |
||||||
|
val Black = Color(0, 0, 0) |
||||||
|
val DarkGray = Color(0x44, 0x44, 0x44) |
||||||
|
val Gray = Color(0x88, 0x88, 0x88) |
||||||
|
val LightGray = Color(0xCC, 0xCC, 0xCC) |
||||||
|
val White = Color(0xFF, 0xFF, 0xFF) |
||||||
|
val Red = Color(0xFF, 0, 0) |
||||||
|
val Green = Color(0, 0xFF, 0) |
||||||
|
val Blue = Color(0, 0, 0xFF) |
||||||
|
val Yellow = Color(0xFF, 0xFF, 0x00) |
||||||
|
val Cyan = Color(0, 0xFF, 0xFF) |
||||||
|
val Magenta = Color(0xFF, 0, 0xFF) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
|
||||||
|
interface Modifier { |
||||||
|
open class Element : Modifier |
||||||
|
companion object : Element() |
||||||
|
} |
||||||
|
|
||||||
|
expect fun Modifier.background(color: Color): Modifier |
||||||
|
expect fun Modifier.padding(all: Dp): Modifier |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal expect fun BoxActual(modifier: Modifier, content: @Composable () -> Unit) |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
@Composable |
||||||
|
expect fun ButtonActual( |
||||||
|
modifier: Modifier, |
||||||
|
onClick: () -> Unit, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal expect fun ColumnActual(modifier: Modifier, content: @Composable () -> Unit) |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Box( |
||||||
|
modifier: Modifier = Modifier.Companion, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { BoxActual(modifier, content) } |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Button( |
||||||
|
modifier: Modifier = Modifier.Companion, |
||||||
|
onClick: () -> Unit, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { |
||||||
|
ButtonActual(modifier, onClick, content) |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Column( |
||||||
|
modifier: Modifier = Modifier.Companion, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { ColumnActual(modifier, content) } |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
|
||||||
|
fun Modifier.size(size: Dp): Modifier { |
||||||
|
return size(size, size) |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Alignment |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Row( |
||||||
|
modifier: Modifier = Modifier.Companion, |
||||||
|
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, |
||||||
|
verticalAlignment: Alignment.Vertical = Alignment.Top, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { RowActual(modifier, horizontalArrangement, verticalAlignment, content) } |
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Slider( |
||||||
|
value: Float, |
||||||
|
onValueChange: (Float) -> Unit = {}, |
||||||
|
valueRange: ClosedFloatingPointRange<Float> = 0f..1f, |
||||||
|
steps: Int = 0, |
||||||
|
modifier: Modifier = Modifier.Companion |
||||||
|
) { |
||||||
|
SliderActual( |
||||||
|
value, |
||||||
|
onValueChange, |
||||||
|
valueRange, |
||||||
|
steps, |
||||||
|
modifier |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.ui.unit.TextUnit |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Text( |
||||||
|
text: String, |
||||||
|
modifier: Modifier = Modifier.Companion, |
||||||
|
color: Color = Color.Black, |
||||||
|
size: TextUnit = TextUnit.Unspecified |
||||||
|
) { |
||||||
|
TextActual(text, modifier, color, size) |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Alignment |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal expect fun RowActual( |
||||||
|
modifier: Modifier, |
||||||
|
horizontalArrangement: Arrangement.Horizontal, |
||||||
|
verticalAlignment: Alignment.Vertical, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package jetbrains.compose.common.shapes |
||||||
|
|
||||||
|
interface Shape |
||||||
|
|
||||||
|
object CircleShape : Shape |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
@Composable |
||||||
|
expect fun SliderActual( |
||||||
|
value: Float, |
||||||
|
onValueChange: (Float) -> Unit, |
||||||
|
valueRange: ClosedFloatingPointRange<Float>, |
||||||
|
steps: Int, |
||||||
|
modifier: Modifier, |
||||||
|
) |
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.ui.unit.TextUnit |
||||||
|
|
||||||
|
@Composable |
||||||
|
expect fun TextActual( |
||||||
|
text: String, |
||||||
|
modifier: Modifier, |
||||||
|
color: Color, |
||||||
|
size: TextUnit |
||||||
|
) |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
expect fun Modifier.border(size: Dp, color: Color): Modifier |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
expect fun Modifier.clickable(onClick: () -> Unit): Modifier |
@ -0,0 +1,21 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.draw |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import jetbrains.compose.common.shapes.Shape |
||||||
|
|
||||||
|
expect fun Modifier.clip(shape: Shape): Modifier |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
expect fun Modifier.fillMaxHeight(fraction: Float): Modifier |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
expect fun Modifier.fillMaxWidth(): Modifier |
@ -0,0 +1,21 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
expect fun Modifier.offset(x: Dp, y: Dp): Modifier |
@ -0,0 +1,23 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.unit.IntSize |
||||||
|
|
||||||
|
expect fun Modifier.onSizeChanged( |
||||||
|
onSizeChanged: (IntSize) -> Unit |
||||||
|
): Modifier |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
|
||||||
|
expect fun Modifier.size(width: Dp, height: Dp): Modifier |
@ -0,0 +1,21 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
expect fun Modifier.width(size: Dp): Modifier |
@ -0,0 +1,18 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.unit |
||||||
|
|
||||||
|
data class Dp(val value: Float) |
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.unit |
||||||
|
|
||||||
|
data class IntSize(val width: Int, val height: Int) |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.unit |
||||||
|
|
||||||
|
enum class TextUnitType { |
||||||
|
Unspecified, |
||||||
|
Em, |
||||||
|
Sp |
||||||
|
} |
||||||
|
|
||||||
|
data class TextUnit(val value: Float, val unitType: TextUnitType) { |
||||||
|
companion object { |
||||||
|
val Unspecified = TextUnit(Float.NaN, TextUnitType.Unspecified) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.ui.unit |
||||||
|
|
||||||
|
// TODO: this have to be in a separate package otherwise there's an error for in cross-module usage (for JVM target) |
||||||
|
val Int.dp: Dp |
||||||
|
get() = Dp(this.toFloat()) |
||||||
|
|
||||||
|
val Int.em: TextUnit |
||||||
|
get() = TextUnit(toFloat(), TextUnitType.Em) |
||||||
|
|
||||||
|
val Float.em: TextUnit |
||||||
|
get() = TextUnit(this, TextUnitType.Em) |
||||||
|
|
||||||
|
val Int.sp: TextUnit |
||||||
|
get() = TextUnit(toFloat(), TextUnitType.Sp) |
||||||
|
|
||||||
|
val Float.sp: TextUnit |
||||||
|
get() = TextUnit(this, TextUnitType.Sp) |
@ -0,0 +1,155 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web |
||||||
|
|
||||||
|
import androidx.compose.runtime.AbstractApplier |
||||||
|
import androidx.compose.web.attributes.WrappedEventListener |
||||||
|
import androidx.compose.web.css.StyleHolder |
||||||
|
import androidx.compose.web.css.attributeStyleMap |
||||||
|
import androidx.compose.web.elements.setProperty |
||||||
|
import androidx.compose.web.elements.setVariable |
||||||
|
import kotlinx.browser.document |
||||||
|
import kotlinx.dom.clear |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.Node |
||||||
|
import org.w3c.dom.get |
||||||
|
|
||||||
|
class DomApplier( |
||||||
|
root: DomNodeWrapper |
||||||
|
) : AbstractApplier<DomNodeWrapper>(root) { |
||||||
|
|
||||||
|
override fun insertTopDown(index: Int, instance: DomNodeWrapper) { |
||||||
|
// ignored. Building tree bottom-up |
||||||
|
} |
||||||
|
|
||||||
|
override fun insertBottomUp(index: Int, instance: DomNodeWrapper) { |
||||||
|
current.insert(index, instance) |
||||||
|
} |
||||||
|
|
||||||
|
override fun remove(index: Int, count: Int) { |
||||||
|
current.remove(index, count) |
||||||
|
} |
||||||
|
|
||||||
|
override fun move(from: Int, to: Int, count: Int) { |
||||||
|
current.move(from, to, count) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onClear() { |
||||||
|
// or current.node.clear()?; in all examples it calls 'clear' on the root |
||||||
|
root.node.clear() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class DomNodeWrapper(val node: Node) { |
||||||
|
|
||||||
|
constructor(tag: String) : this(document.createElement(tag)) |
||||||
|
|
||||||
|
private var currentListeners: List<WrappedEventListener<*>> = emptyList() |
||||||
|
private var currentAttrs: Map<String, String?> = emptyMap() |
||||||
|
|
||||||
|
fun updateProperties(list: List<Pair<(HTMLElement, Any) -> Unit, Any>>) { |
||||||
|
val htmlElement = node as? HTMLElement ?: return |
||||||
|
list.forEach { it.first(htmlElement, it.second) } |
||||||
|
} |
||||||
|
|
||||||
|
fun updateEventListeners(list: List<WrappedEventListener<*>>) { |
||||||
|
val htmlElement = node as? HTMLElement ?: return |
||||||
|
|
||||||
|
currentListeners.forEach { |
||||||
|
htmlElement.removeEventListener(it.event, it) |
||||||
|
} |
||||||
|
|
||||||
|
currentListeners = list |
||||||
|
|
||||||
|
currentListeners.forEach { |
||||||
|
htmlElement.addEventListener(it.event, it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun updateAttrs(attrs: Map<String, String?>) { |
||||||
|
val htmlElement = node as? HTMLElement ?: return |
||||||
|
currentAttrs.forEach { |
||||||
|
htmlElement.removeAttribute(it.key) |
||||||
|
} |
||||||
|
currentAttrs = attrs |
||||||
|
currentAttrs.forEach { |
||||||
|
if (it.value != null) htmlElement.setAttribute(it.key, it.value ?: "") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun updateStyleDeclarations(style: StyleHolder?) { |
||||||
|
val htmlElement = node as? HTMLElement ?: return |
||||||
|
// TODO: typed-om-polyfill hasn't StylePropertyMap::clear() |
||||||
|
htmlElement.style.cssText = "" |
||||||
|
|
||||||
|
style?.properties?.forEach { (name, value) -> |
||||||
|
setProperty(htmlElement.attributeStyleMap, name, value) |
||||||
|
} |
||||||
|
style?.variables?.forEach { (name, value) -> |
||||||
|
setVariable(htmlElement.style, name, value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun insert(index: Int, nodeWrapper: DomNodeWrapper) { |
||||||
|
val length = node.childNodes.length |
||||||
|
if (index < length) { |
||||||
|
node.insertBefore(nodeWrapper.node, node.childNodes[index]!!) |
||||||
|
} else { |
||||||
|
node.appendChild(nodeWrapper.node) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun remove(index: Int, count: Int) { |
||||||
|
repeat(count) { |
||||||
|
node.removeChild(node.childNodes[index]!!) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun move(from: Int, to: Int, count: Int) { |
||||||
|
if (from == to) { |
||||||
|
return // nothing to do |
||||||
|
} |
||||||
|
|
||||||
|
for (i in 0 until count) { |
||||||
|
// if "from" is after "to," the from index moves because we're inserting before it |
||||||
|
val fromIndex = if (from > to) from + i else from |
||||||
|
val toIndex = if (from > to) to + i else to + count - 2 |
||||||
|
|
||||||
|
val child = node.removeChild(node.childNodes[fromIndex]!!) |
||||||
|
node.insertBefore(child, node.childNodes[toIndex]!!) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
val UpdateAttrs: DomNodeWrapper.(Map<String, String?>) -> Unit = { |
||||||
|
this.updateAttrs(it) |
||||||
|
} |
||||||
|
val UpdateListeners: DomNodeWrapper.(List<WrappedEventListener<*>>) -> Unit = { |
||||||
|
this.updateEventListeners(it) |
||||||
|
} |
||||||
|
val UpdateProperties: DomNodePropertiesUpdater = { |
||||||
|
this.updateProperties(it) |
||||||
|
} |
||||||
|
val UpdateStyleDeclarations: DomNodeWrapper.(StyleHolder?) -> Unit = { |
||||||
|
this.updateStyleDeclarations(it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
typealias DomNodePropertiesUpdater = |
||||||
|
DomNodeWrapper.(List<Pair<(HTMLElement, Any) -> Unit, Any>>) -> Unit |
@ -0,0 +1,87 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web |
||||||
|
|
||||||
|
import androidx.compose.runtime.snapshots.ObserverHandle |
||||||
|
import androidx.compose.runtime.snapshots.Snapshot |
||||||
|
import androidx.compose.web.GlobalSnapshotManager.ensureStarted |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.SupervisorJob |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
|
||||||
|
/** |
||||||
|
* Platform-specific mechanism for starting a monitor of global snapshot state writes |
||||||
|
* in order to schedule the periodic dispatch of snapshot apply notifications. |
||||||
|
* This process should remain platform-specific; it is tied to the threading and update model of |
||||||
|
* a particular platform and framework target. |
||||||
|
* |
||||||
|
* Composition bootstrapping mechanisms for a particular platform/framework should call |
||||||
|
* [ensureStarted] during setup to initialize periodic global snapshot notifications. |
||||||
|
*/ |
||||||
|
internal object GlobalSnapshotManager { |
||||||
|
private var started = false |
||||||
|
private var commitPending = false |
||||||
|
private var removeWriteObserver: (ObserverHandle)? = null |
||||||
|
|
||||||
|
private val scheduleScope = CoroutineScope(JsMicrotasksDispatcher() + SupervisorJob()) |
||||||
|
|
||||||
|
fun ensureStarted() { |
||||||
|
if (!started) { |
||||||
|
started = true |
||||||
|
removeWriteObserver = Snapshot.registerGlobalWriteObserver(globalWriteObserver) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private val globalWriteObserver: (Any) -> Unit = { |
||||||
|
// Race, but we don't care too much if we end up with multiple calls scheduled. |
||||||
|
if (!commitPending) { |
||||||
|
commitPending = true |
||||||
|
schedule { |
||||||
|
commitPending = false |
||||||
|
Snapshot.sendApplyNotifications() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* List of deferred callbacks to run serially. Guarded by its own monitor lock. |
||||||
|
*/ |
||||||
|
private val scheduledCallbacks = mutableListOf<() -> Unit>() |
||||||
|
|
||||||
|
/** |
||||||
|
* Guarded by [scheduledCallbacks]'s monitor lock. |
||||||
|
*/ |
||||||
|
private var isSynchronizeScheduled = false |
||||||
|
|
||||||
|
/** |
||||||
|
* Synchronously executes any outstanding callbacks and brings snapshots into a |
||||||
|
* consistent, updated state. |
||||||
|
*/ |
||||||
|
private fun synchronize() { |
||||||
|
scheduledCallbacks.forEach { it.invoke() } |
||||||
|
scheduledCallbacks.clear() |
||||||
|
isSynchronizeScheduled = false |
||||||
|
} |
||||||
|
|
||||||
|
private fun schedule(block: () -> Unit) { |
||||||
|
scheduledCallbacks.add(block) |
||||||
|
if (!isSynchronizeScheduled) { |
||||||
|
isSynchronizeScheduled = true |
||||||
|
scheduleScope.launch { synchronize() } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web |
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher |
||||||
|
import kotlinx.coroutines.Runnable |
||||||
|
import kotlin.coroutines.CoroutineContext |
||||||
|
import kotlin.js.Promise |
||||||
|
|
||||||
|
internal class JsMicrotasksDispatcher : CoroutineDispatcher() { |
||||||
|
override fun dispatch(context: CoroutineContext, block: Runnable) { |
||||||
|
Promise.resolve(Unit).then { block.run() } |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.Composition |
||||||
|
import androidx.compose.runtime.ControlledComposition |
||||||
|
import androidx.compose.runtime.DefaultMonotonicFrameClock |
||||||
|
import androidx.compose.runtime.Recomposer |
||||||
|
import androidx.compose.web.elements.DOMScope |
||||||
|
import kotlinx.browser.document |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.CoroutineStart |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import org.w3c.dom.HTMLBodyElement |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.get |
||||||
|
/** |
||||||
|
* Use this method to mount the composition at the certain [root] |
||||||
|
* |
||||||
|
* @param root - the [HTMLElement] that will be the root of the DOM tree managed by Compose |
||||||
|
* @param content - the Composable lambda that defines the composition content |
||||||
|
* |
||||||
|
* @return the instance of the [Composition] |
||||||
|
*/ |
||||||
|
fun <THTMLElement : HTMLElement> renderComposable( |
||||||
|
root: THTMLElement, |
||||||
|
content: @Composable DOMScope<THTMLElement>.() -> Unit |
||||||
|
): Composition { |
||||||
|
GlobalSnapshotManager.ensureStarted() |
||||||
|
|
||||||
|
val context = DefaultMonotonicFrameClock + JsMicrotasksDispatcher() |
||||||
|
val recomposer = Recomposer(context) |
||||||
|
val composition = ControlledComposition( |
||||||
|
applier = DomApplier(DomNodeWrapper(root)), |
||||||
|
parent = recomposer |
||||||
|
) |
||||||
|
val scope = object : DOMScope<THTMLElement> {} |
||||||
|
composition.setContent @Composable { |
||||||
|
content(scope) |
||||||
|
} |
||||||
|
|
||||||
|
CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) { |
||||||
|
recomposer.runRecomposeAndApplyChanges() |
||||||
|
} |
||||||
|
return composition |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Use this method to mount the composition at the element with id - [rootElementId]. |
||||||
|
* |
||||||
|
* @param rootElementId - the id of the [HTMLElement] that will be the root of the DOM tree managed |
||||||
|
* by Compose |
||||||
|
* @param content - the Composable lambda that defines the composition content |
||||||
|
* |
||||||
|
* @return the instance of the [Composition] |
||||||
|
*/ |
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
fun renderComposable( |
||||||
|
rootElementId: String, |
||||||
|
content: @Composable DOMScope<HTMLElement>.() -> Unit |
||||||
|
): Composition = renderComposable( |
||||||
|
root = document.getElementById(rootElementId) as HTMLElement, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
/** |
||||||
|
* Use this method to mount the composition at the [HTMLBodyElement] of the current document |
||||||
|
* |
||||||
|
* @param content - the Composable lambda that defines the composition content |
||||||
|
* |
||||||
|
* @return the instance of the [Composition] |
||||||
|
*/ |
||||||
|
fun renderComposableInBody( |
||||||
|
content: @Composable DOMScope<HTMLBodyElement>.() -> Unit |
||||||
|
): Composition = renderComposable( |
||||||
|
root = document.getElementsByTagName("body")[0] as HTMLBodyElement, |
||||||
|
content = content |
||||||
|
) |
@ -0,0 +1,367 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.attributes |
||||||
|
|
||||||
|
import org.w3c.dom.HTMLInputElement |
||||||
|
|
||||||
|
open class Tag { |
||||||
|
object Div : Tag() |
||||||
|
object A : Tag() |
||||||
|
object Button : Tag() |
||||||
|
object Form : Tag() |
||||||
|
object Input : Tag() |
||||||
|
object Select : Tag() |
||||||
|
object Option : Tag() |
||||||
|
object OptGroup : Tag() |
||||||
|
object H : Tag() |
||||||
|
object Ul : Tag() |
||||||
|
object Ol : Tag() |
||||||
|
object Li : Tag() |
||||||
|
object Img : Tag() |
||||||
|
object TextArea : Tag() |
||||||
|
object Nav : Tag() |
||||||
|
object Span : Tag() |
||||||
|
object P : Tag() |
||||||
|
object Br : Tag() |
||||||
|
object Style : Tag() |
||||||
|
object Pre : Tag() |
||||||
|
object Code : Tag() |
||||||
|
object Label : Tag() |
||||||
|
object Table : Tag() |
||||||
|
object Caption : Tag() |
||||||
|
object Col : Tag() |
||||||
|
object Colgroup : Tag() |
||||||
|
object Tr : Tag() |
||||||
|
object Thead : Tag() |
||||||
|
object Th : Tag() |
||||||
|
object Td : Tag() |
||||||
|
object Tbody : Tag() |
||||||
|
object Tfoot : Tag() |
||||||
|
} |
||||||
|
|
||||||
|
/* Anchor <a> attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.href(value: String?) = |
||||||
|
attr("href", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.target(value: ATarget = ATarget.Self) = |
||||||
|
attr("target", value.targetStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.ref(value: ARel) = |
||||||
|
attr("rel", value.relStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.ping(value: String) = |
||||||
|
attr("ping", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.ping(vararg urls: String) = |
||||||
|
attr("ping", urls.joinToString(" ")) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.hreflang(value: String) = |
||||||
|
attr("hreflang", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.A>.download(value: String = "") = |
||||||
|
attr("download", value) |
||||||
|
|
||||||
|
/* Button attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.autoFocus(value: Boolean = true) = |
||||||
|
attr("autofocus", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.disabled(value: Boolean = true) = |
||||||
|
attr("disabled", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.form(formId: String) = |
||||||
|
attr("form", formId) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.formAction(url: String) = |
||||||
|
attr("formaction", url) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.formEncType(value: ButtonFormEncType) = |
||||||
|
attr("formenctype", value.typeStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.formMethod(value: ButtonFormMethod) = |
||||||
|
attr("formmethod", value.methodStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.formNoValidate(value: Boolean = true) = |
||||||
|
attr("formnovalidate", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.formTarget(value: ButtonFormTarget) = |
||||||
|
attr("formtarget", value.targetStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.name(value: String) = |
||||||
|
attr("name", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.type(value: ButtonType) = |
||||||
|
attr("type", value.str) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Button>.value(value: String) = |
||||||
|
attr("value", value) |
||||||
|
|
||||||
|
/* Form attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.action(value: String) = |
||||||
|
attr("action", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.acceptCharset(value: String) = |
||||||
|
attr("accept-charset", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.autoComplete(value: Boolean) = |
||||||
|
attr("autocomplete", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.encType(value: FormEncType) = |
||||||
|
attr("enctype", value.typeStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.method(value: FormMethod) = |
||||||
|
attr("method", value.methodStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.noValidate(value: Boolean = true) = |
||||||
|
attr("novalidate", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Form>.target(value: FormTarget) = |
||||||
|
attr("target", value.targetStr) |
||||||
|
|
||||||
|
/* Input attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.type(value: InputType) = |
||||||
|
attr("type", value.typeStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.accept(value: String) = |
||||||
|
attr("accept", value) // type: file only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.alt(value: String) = |
||||||
|
attr("alt", value) // type: image only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.autoComplete(value: Boolean = true) = |
||||||
|
attr("autocomplete", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.autoFocus(value: Boolean = true) = |
||||||
|
attr("autofocus", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.capture(value: String) = |
||||||
|
attr("capture", value) // type: file only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.checked(value: Boolean = true) = |
||||||
|
attr("checked", if (value) "" else null) // radio, checkbox |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.dirName(value: String) = |
||||||
|
attr("dirname", value) // text, search |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.disabled(value: Boolean = true) = |
||||||
|
attr("disabled", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.form(id: String) = |
||||||
|
attr("form", id) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.formAction(url: String) = |
||||||
|
attr("formaction", url) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.formEncType(value: InputFormEncType) = |
||||||
|
attr("formenctype", value.typeStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.formMethod(value: InputFormMethod) = |
||||||
|
attr("formmethod", value.methodStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.formNoValidate(value: Boolean = true) = |
||||||
|
attr("formnovalidate", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.formTarget(value: InputFormTarget) = |
||||||
|
attr("formtarget", value.targetStr) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.height(value: Int) = |
||||||
|
attr("height", value.toString()) // image only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.width(value: Int) = |
||||||
|
attr("width", value.toString()) // image only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.list(dataListId: String) = |
||||||
|
attr("list", dataListId) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.max(value: String) = |
||||||
|
attr("max", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.maxLength(value: Int) = |
||||||
|
attr("maxlength", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.min(value: String) = |
||||||
|
attr("min", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.minLength(value: Int) = |
||||||
|
attr("minlength", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.multiple(value: Boolean = true) = |
||||||
|
attr("multiple", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.name(value: String) = |
||||||
|
attr("name", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.pattern(value: String) = |
||||||
|
attr("pattern", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.placeholder(value: String) = |
||||||
|
attr("placeholder", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.readOnly(value: Boolean = true) = |
||||||
|
attr("readonly", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.required(value: Boolean = true) = |
||||||
|
attr("required", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.size(value: Int) = |
||||||
|
attr("size", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.src(value: String) = |
||||||
|
attr("src", value.toString()) // image only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.step(value: Int) = |
||||||
|
attr("step", value.toString()) // numeric types only |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.valueAttr(value: String) = |
||||||
|
attr("value", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Input>.value(value: String): AttrsBuilder<Tag.Input> { |
||||||
|
prop(setInputValue, value) |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
/* Option attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Option>.value(value: String) = |
||||||
|
attr("value", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Option>.disabled(value: Boolean = true) = |
||||||
|
attr("disabled", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Option>.selected(value: Boolean = true) = |
||||||
|
attr("selected", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Option>.label(value: String) = |
||||||
|
attr("label", value) |
||||||
|
|
||||||
|
/* Select attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.autocomplete(value: String) = |
||||||
|
attr("autocomplete", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.autofocus(value: Boolean = true) = |
||||||
|
attr("autofocus", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.disabled(value: Boolean = true) = |
||||||
|
attr("disabled", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.form(formId: String) = |
||||||
|
attr("form", formId) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.multiple(value: Boolean = true) = |
||||||
|
attr("multiple", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.name(value: String) = |
||||||
|
attr("name", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.required(value: Boolean = true) = |
||||||
|
attr("required", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Select>.size(numberOfRows: Int) = |
||||||
|
attr("size", numberOfRows.toString()) |
||||||
|
|
||||||
|
/* OptGroup attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.OptGroup>.label(value: String) = |
||||||
|
attr("label", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.OptGroup>.disabled(value: Boolean = true) = |
||||||
|
attr("disabled", if (value) "" else null) |
||||||
|
|
||||||
|
/* TextArea attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.autoComplete(value: Boolean = true) = |
||||||
|
attr("autocomplete", if (value) "on" else "off") |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.autoFocus(value: Boolean = true) = |
||||||
|
attr("autofocus", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.cols(value: Int) = |
||||||
|
attr("cols", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.disabled(value: Boolean = true) = |
||||||
|
attr("disabled", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.form(formId: String) = |
||||||
|
attr("form", formId) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.maxLength(value: Int) = |
||||||
|
attr("maxlength", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.minLength(value: Int) = |
||||||
|
attr("minlength", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.name(value: String) = |
||||||
|
attr("name", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.placeholder(value: String) = |
||||||
|
attr("placeholder", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.readOnly(value: Boolean = true) = |
||||||
|
attr("readonly", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.required(value: Boolean = true) = |
||||||
|
attr("required", if (value) "" else null) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.rows(value: Int) = |
||||||
|
attr("rows", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.wrap(value: TextAreaWrap) = |
||||||
|
attr("wrap", value.str) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.TextArea>.value(value: String): AttrsBuilder<Tag.TextArea> { |
||||||
|
prop(setInputValue, value) |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
/* Img attributes */ |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Img>.src(value: String?): AttrsBuilder<Tag.Img> = |
||||||
|
attr("src", value) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Img>.alt(value: String?): AttrsBuilder<Tag.Img> = |
||||||
|
attr("alt", value) |
||||||
|
|
||||||
|
private val setInputValue: (HTMLInputElement, String) -> Unit = { e, v -> |
||||||
|
e.value = v |
||||||
|
} |
||||||
|
|
||||||
|
/* Img attributes */ |
||||||
|
fun AttrsBuilder<Tag.Label>.forId(value: String?): AttrsBuilder<Tag.Label> = |
||||||
|
attr("for", value) |
||||||
|
|
||||||
|
/* Table attributes */ |
||||||
|
fun AttrsBuilder<Tag.Th>.scope(value: Scope?): AttrsBuilder<Tag.Th> = |
||||||
|
attr("scope", value?.str) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Col>.span(value: Int): AttrsBuilder<Tag.Col> = |
||||||
|
attr("span", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Th>.colspan(value: Int): AttrsBuilder<Tag.Th> = |
||||||
|
attr("colspan", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Th>.rowspan(value: Int): AttrsBuilder<Tag.Th> = |
||||||
|
attr("rowspan", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Td>.colspan(value: Int): AttrsBuilder<Tag.Td> = |
||||||
|
attr("colspan", value.toString()) |
||||||
|
|
||||||
|
fun AttrsBuilder<Tag.Td>.rowspan(value: Int): AttrsBuilder<Tag.Td> = |
||||||
|
attr("rowspan", value.toString()) |
@ -0,0 +1,95 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.attributes |
||||||
|
|
||||||
|
import androidx.compose.runtime.DisposableEffectResult |
||||||
|
import androidx.compose.runtime.DisposableEffectScope |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
|
||||||
|
class AttrsBuilder<TTag : Tag> : EventsListenerBuilder() { |
||||||
|
private val map = mutableMapOf<String, String>() |
||||||
|
|
||||||
|
val propertyUpdates = mutableListOf<Pair<(HTMLElement, Any) -> Unit, Any>>() |
||||||
|
var refEffect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null |
||||||
|
|
||||||
|
inline fun classes(builder: ClassesAttrBuilder.() -> Unit) = |
||||||
|
prop(setClassList, ClassesAttrBuilder().apply(builder).asList().toTypedArray()) |
||||||
|
|
||||||
|
fun classes(vararg classes: String) = prop(setClassList, classes) |
||||||
|
|
||||||
|
fun id(value: String) = attr(ID, value) |
||||||
|
fun hidden(value: Boolean) = attr(HIDDEN, value.toString()) |
||||||
|
fun title(value: String) = attr(TITLE, value) |
||||||
|
fun dir(value: DirType) = attr(DIR, value.dirStr) |
||||||
|
fun draggable(value: Draggable) = attr(DRAGGABLE, value.str) |
||||||
|
fun contentEditable(value: Boolean) = attr(CONTENT_EDITABLE, value.toString()) |
||||||
|
fun lang(value: String) = attr(LANG, value) |
||||||
|
fun tabIndex(value: Int) = attr(TAB_INDEX, value.toString()) |
||||||
|
fun spellCheck(value: Boolean) = attr(SPELLCHECK, value.toString()) |
||||||
|
|
||||||
|
fun ref(effect: DisposableEffectScope.(HTMLElement) -> DisposableEffectResult) { |
||||||
|
this.refEffect = effect |
||||||
|
} |
||||||
|
|
||||||
|
fun attr(attr: String, value: String?): AttrsBuilder<TTag> { |
||||||
|
if (value == null && attr in map) { |
||||||
|
map.remove(attr) |
||||||
|
} else if (value != null) { |
||||||
|
map[attr] = value |
||||||
|
} |
||||||
|
|
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
fun <E : HTMLElement, V : Any> prop(update: (E, V) -> Unit, value: V) { |
||||||
|
propertyUpdates.add((update to value) as Pair<(HTMLElement, Any) -> Unit, Any>) |
||||||
|
} |
||||||
|
|
||||||
|
fun collect(): Map<String, String> { |
||||||
|
return map |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
const val CLASS = "class" |
||||||
|
const val ID = "id" |
||||||
|
const val HIDDEN = "hidden" |
||||||
|
const val TITLE = "title" |
||||||
|
const val DIR = "dir" |
||||||
|
const val DRAGGABLE = "draggable" |
||||||
|
const val CONTENT_EDITABLE = "contenteditable" |
||||||
|
const val LANG = "lang" |
||||||
|
const val TAB_INDEX = "tabindex" |
||||||
|
const val SPELLCHECK = "spellcheck" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ClassesAttrBuilder { |
||||||
|
private val classes = mutableListOf<String>() |
||||||
|
|
||||||
|
operator fun String.unaryPlus() { |
||||||
|
classes.add(this) |
||||||
|
} |
||||||
|
|
||||||
|
fun asList(): List<String> = classes |
||||||
|
fun asString(): String = classes.joinToString(" ") |
||||||
|
} |
||||||
|
|
||||||
|
val setClassList: (HTMLElement, Array<out String>) -> Unit = { e, classList -> |
||||||
|
e.className = "" |
||||||
|
e.classList.add(*classList) |
||||||
|
} |
@ -0,0 +1,293 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.attributes |
||||||
|
|
||||||
|
import androidx.compose.web.events.WrappedCheckBoxInputEvent |
||||||
|
import androidx.compose.web.events.WrappedClipboardEvent |
||||||
|
import androidx.compose.web.events.WrappedDragEvent |
||||||
|
import androidx.compose.web.events.WrappedEvent |
||||||
|
import androidx.compose.web.events.WrappedFocusEvent |
||||||
|
import androidx.compose.web.events.WrappedInputEvent |
||||||
|
import androidx.compose.web.events.WrappedKeyboardEvent |
||||||
|
import androidx.compose.web.events.WrappedMouseEvent |
||||||
|
import androidx.compose.web.events.WrappedRadioInputEvent |
||||||
|
import androidx.compose.web.events.WrappedTextInputEvent |
||||||
|
import androidx.compose.web.events.WrappedTouchEvent |
||||||
|
import androidx.compose.web.events.WrappedWheelEvent |
||||||
|
import androidx.compose.web.events.GenericWrappedEvent |
||||||
|
|
||||||
|
open class EventsListenerBuilder { |
||||||
|
|
||||||
|
private val listeners = mutableListOf<WrappedEventListener<*>>() |
||||||
|
|
||||||
|
fun onCopy(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { |
||||||
|
listeners.add(ClipboardEventListener(COPY, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onCut(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { |
||||||
|
listeners.add(ClipboardEventListener(CUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onPaste(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { |
||||||
|
listeners.add(ClipboardEventListener(PASTE, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onContextMenu(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(CONTEXTMENU, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onClick(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(CLICK, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDoubleClick(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(DBLCLICK, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onInput(options: Options = Options.DEFAULT, listener: (WrappedInputEvent) -> Unit) { |
||||||
|
listeners.add(InputEventListener(INPUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onTextInput(options: Options = Options.DEFAULT, listener: (WrappedTextInputEvent) -> Unit) { |
||||||
|
listeners.add(TextInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onCheckboxInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (WrappedCheckBoxInputEvent) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(CheckBoxInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onRadioInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (WrappedRadioInputEvent) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(RadioInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onRangeInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (GenericWrappedEvent<*>) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(WrappedEventListener(INPUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onGenericInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (GenericWrappedEvent<*>) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(WrappedEventListener(INPUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onChange(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(CHANGE, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onInvalid(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(INVALID, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onSearch(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(SEARCH, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onFocus(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) { |
||||||
|
listeners.add(FocusEventListener(FOCUS, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onBlur(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) { |
||||||
|
listeners.add(FocusEventListener(BLUR, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onFocusIn(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) { |
||||||
|
listeners.add(FocusEventListener(FOCUSIN, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onFocusOut(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) { |
||||||
|
listeners.add(FocusEventListener(FOCUSOUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onKeyDown(options: Options = Options.DEFAULT, listener: (WrappedKeyboardEvent) -> Unit) { |
||||||
|
listeners.add(KeyboardEventListener(KEYDOWN, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onKeyUp(options: Options = Options.DEFAULT, listener: (WrappedKeyboardEvent) -> Unit) { |
||||||
|
listeners.add(KeyboardEventListener(KEYUP, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseDown(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSEDOWN, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseUp(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSEUP, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseEnter(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSEENTER, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseLeave(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSELEAVE, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseMove(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSEMOVE, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseOut(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSEOUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onMouseOver(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { |
||||||
|
listeners.add(MouseEventListener(MOUSEOVER, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onWheel(options: Options = Options.DEFAULT, listener: (WrappedWheelEvent) -> Unit) { |
||||||
|
listeners.add(MouseWheelEventListener(WHEEL, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onScroll(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(SCROLL, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onSelect(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(SELECT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onTouchCancel(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) { |
||||||
|
listeners.add(TouchEventListener(TOUCHCANCEL, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onTouchMove(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) { |
||||||
|
listeners.add(TouchEventListener(TOUCHMOVE, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onTouchEnd(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) { |
||||||
|
listeners.add(TouchEventListener(TOUCHEND, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onTouchStart(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) { |
||||||
|
listeners.add(TouchEventListener(TOUCHSTART, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onAnimationEnd(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(ANIMATIONEND, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onAnimationIteration(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(ANIMATIONITERATION, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onAnimationStart(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { |
||||||
|
listeners.add(WrappedEventListener(ANIMATIONSTART, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onBeforeInput(options: Options = Options.DEFAULT, listener: (WrappedInputEvent) -> Unit) { |
||||||
|
listeners.add(InputEventListener(BEFOREINPUT, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDrag(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DRAG, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDrop(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DROP, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDragStart(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DRAGSTART, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDragEnd(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DRAGEND, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDragOver(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DRAGOVER, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDragEnter(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DRAGENTER, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun onDragLeave(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { |
||||||
|
listeners.add(DragEventListener(DRAGLEAVE, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
fun asList(): List<WrappedEventListener<*>> = listeners |
||||||
|
|
||||||
|
fun addEventListener( |
||||||
|
eventName: String, |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (WrappedEvent) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(WrappedEventListener(eventName, options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
const val COPY = "copy" |
||||||
|
const val CUT = "cut" |
||||||
|
const val PASTE = "paste" |
||||||
|
const val CONTEXTMENU = "contextmenu" |
||||||
|
|
||||||
|
const val CLICK = "click" |
||||||
|
const val DBLCLICK = "dblclick" |
||||||
|
const val FOCUS = "focus" |
||||||
|
const val BLUR = "blur" |
||||||
|
const val FOCUSIN = "focusin" |
||||||
|
const val FOCUSOUT = "focusout" |
||||||
|
|
||||||
|
const val KEYDOWN = "keydown" |
||||||
|
const val KEYUP = "keyup" |
||||||
|
const val MOUSEDOWN = "mousedown" |
||||||
|
const val MOUSEUP = "mouseup" |
||||||
|
const val MOUSEENTER = "mouseenter" |
||||||
|
const val MOUSELEAVE = "mouseleave" |
||||||
|
const val MOUSEMOVE = "mousemove" |
||||||
|
const val MOUSEOUT = "mouseout" |
||||||
|
const val MOUSEOVER = "mouseover" |
||||||
|
const val WHEEL = "wheel" |
||||||
|
const val SCROLL = "scroll" |
||||||
|
const val SELECT = "select" |
||||||
|
|
||||||
|
const val TOUCHCANCEL = "touchcancel" |
||||||
|
const val TOUCHEND = "touchend" |
||||||
|
const val TOUCHMOVE = "touchmove" |
||||||
|
const val TOUCHSTART = "touchstart" |
||||||
|
|
||||||
|
const val ANIMATIONCANCEL = "animationcancel" // firefox and safari only |
||||||
|
const val ANIMATIONEND = "animationend" |
||||||
|
const val ANIMATIONITERATION = "animationiteration" |
||||||
|
const val ANIMATIONSTART = "animationstart" |
||||||
|
|
||||||
|
const val BEFOREINPUT = "beforeinput" |
||||||
|
const val INPUT = "input" |
||||||
|
const val CHANGE = "change" |
||||||
|
const val INVALID = "invalid" |
||||||
|
const val SEARCH = "search" |
||||||
|
|
||||||
|
const val DRAG = "drag" |
||||||
|
const val DROP = "drop" |
||||||
|
const val DRAGSTART = "dragstart" |
||||||
|
const val DRAGEND = "dragend" |
||||||
|
const val DRAGOVER = "dragover" |
||||||
|
const val DRAGENTER = "dragenter" |
||||||
|
const val DRAGLEAVE = "dragleave" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,150 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.attributes |
||||||
|
|
||||||
|
sealed class InputType(val typeStr: String) { |
||||||
|
object Button : InputType("button") |
||||||
|
object Checkbox : InputType("checkbox") |
||||||
|
object Color : InputType("color") |
||||||
|
object Date : InputType("date") |
||||||
|
object DateTimeLocal : InputType("datetime-local") |
||||||
|
object Email : InputType("email") |
||||||
|
object File : InputType("file") |
||||||
|
object Hidden : InputType("hidden") |
||||||
|
object Month : InputType("month") |
||||||
|
object Number : InputType("number") |
||||||
|
object Password : InputType("password") |
||||||
|
object Radio : InputType("radio") |
||||||
|
object Range : InputType("range") |
||||||
|
object Search : InputType("search") |
||||||
|
object Submit : InputType("submit") |
||||||
|
object Tel : InputType("tel") |
||||||
|
object Text : InputType("text") |
||||||
|
object Time : InputType("time") |
||||||
|
object Url : InputType("url") |
||||||
|
object Week : InputType("week") |
||||||
|
} |
||||||
|
|
||||||
|
sealed class DirType(val dirStr: String) { |
||||||
|
object Ltr : DirType("ltr") |
||||||
|
object Rtl : DirType("rtl") |
||||||
|
object Auto : DirType("auto") |
||||||
|
} |
||||||
|
|
||||||
|
sealed class ATarget(val targetStr: String) { |
||||||
|
object Blank : ATarget("_blank") |
||||||
|
object Parent : ATarget("_parent") |
||||||
|
object Self : ATarget("_self") |
||||||
|
object Top : ATarget("_top") |
||||||
|
} |
||||||
|
|
||||||
|
sealed class ARel(val relStr: String) { |
||||||
|
object Alternate : ARel("alternate") |
||||||
|
object Author : ARel("author") |
||||||
|
object Bookmark : ARel("bookmark") |
||||||
|
object External : ARel("external") |
||||||
|
object Help : ARel("help") |
||||||
|
object License : ARel("license") |
||||||
|
object Next : ARel("next") |
||||||
|
object First : ARel("first") |
||||||
|
object Prev : ARel("prev") |
||||||
|
object Last : ARel("last") |
||||||
|
object NoFollow : ARel("nofollow") |
||||||
|
object NoOpener : ARel("noopener") |
||||||
|
object NoReferrer : ARel("noreferrer") |
||||||
|
object Opener : ARel("opener") |
||||||
|
object Search : ARel("search") |
||||||
|
object Tag : ARel("tag") |
||||||
|
|
||||||
|
class CustomARel(value: String) : ARel(value) |
||||||
|
} |
||||||
|
|
||||||
|
enum class Draggable(val str: String) { |
||||||
|
True("true"), False("false"), Auto("auto"); |
||||||
|
} |
||||||
|
|
||||||
|
enum class ButtonType(val str: String) { |
||||||
|
Button("button"), Reset("reset"), Submit("submit") |
||||||
|
} |
||||||
|
|
||||||
|
sealed class ButtonFormTarget(val targetStr: String) { |
||||||
|
object Blank : ButtonFormTarget("_blank") |
||||||
|
object Parent : ButtonFormTarget("_parent") |
||||||
|
object Self : ButtonFormTarget("_self") |
||||||
|
object Top : ButtonFormTarget("_top") |
||||||
|
} |
||||||
|
|
||||||
|
enum class ButtonFormMethod(val methodStr: String) { |
||||||
|
Get("get"), Post("post") |
||||||
|
} |
||||||
|
|
||||||
|
enum class ButtonFormEncType(val typeStr: String) { |
||||||
|
MultipartFormData("multipart/form-data"), |
||||||
|
ApplicationXWwwFormUrlEncoded("application/x-www-form-urlencoded"), |
||||||
|
TextPlain("text/plain") |
||||||
|
} |
||||||
|
|
||||||
|
enum class FormEncType(val typeStr: String) { |
||||||
|
MultipartFormData("multipart/form-data"), |
||||||
|
ApplicationXWwwFormUrlEncoded("application/x-www-form-urlencoded"), |
||||||
|
TextPlain("text/plain") |
||||||
|
} |
||||||
|
|
||||||
|
enum class FormMethod(val methodStr: String) { |
||||||
|
Get("get"), |
||||||
|
Post("post"), |
||||||
|
Dialog("dialog") |
||||||
|
} |
||||||
|
|
||||||
|
sealed class FormTarget(val targetStr: String) { |
||||||
|
object Blank : FormTarget("_blank") |
||||||
|
object Parent : FormTarget("_parent") |
||||||
|
object Self : FormTarget("_self") |
||||||
|
object Top : FormTarget("_top") |
||||||
|
} |
||||||
|
|
||||||
|
enum class InputFormEncType(val typeStr: String) { |
||||||
|
MultipartFormData("multipart/form-data"), |
||||||
|
ApplicationXWwwFormUrlEncoded("application/x-www-form-urlencoded"), |
||||||
|
TextPlain("text/plain") |
||||||
|
} |
||||||
|
|
||||||
|
enum class InputFormMethod(val methodStr: String) { |
||||||
|
Get("get"), |
||||||
|
Post("post"), |
||||||
|
Dialog("dialog") |
||||||
|
} |
||||||
|
|
||||||
|
sealed class InputFormTarget(val targetStr: String) { |
||||||
|
object Blank : InputFormTarget("_blank") |
||||||
|
object Parent : InputFormTarget("_parent") |
||||||
|
object Self : InputFormTarget("_self") |
||||||
|
object Top : InputFormTarget("_top") |
||||||
|
} |
||||||
|
|
||||||
|
enum class TextAreaWrap(val str: String) { |
||||||
|
Hard("hard"), |
||||||
|
Soft("soft"), |
||||||
|
Off("off") |
||||||
|
} |
||||||
|
|
||||||
|
enum class Scope(val str: String) { |
||||||
|
Row("row"), |
||||||
|
Rowgroup("rowgroup"), |
||||||
|
Col("col"), |
||||||
|
Colgroup("colgroup") |
||||||
|
} |
@ -0,0 +1,184 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.attributes |
||||||
|
|
||||||
|
import androidx.compose.web.events.GenericWrappedEvent |
||||||
|
import androidx.compose.web.events.WrappedCheckBoxInputEvent |
||||||
|
import androidx.compose.web.events.WrappedClipboardEvent |
||||||
|
import androidx.compose.web.events.WrappedDragEvent |
||||||
|
import androidx.compose.web.events.WrappedEventImpl |
||||||
|
import androidx.compose.web.events.WrappedFocusEvent |
||||||
|
import androidx.compose.web.events.WrappedInputEvent |
||||||
|
import androidx.compose.web.events.WrappedKeyboardEvent |
||||||
|
import androidx.compose.web.events.WrappedMouseEvent |
||||||
|
import androidx.compose.web.events.WrappedPointerEvent |
||||||
|
import androidx.compose.web.events.WrappedRadioInputEvent |
||||||
|
import androidx.compose.web.events.WrappedTextInputEvent |
||||||
|
import androidx.compose.web.events.WrappedTouchEvent |
||||||
|
import androidx.compose.web.events.WrappedWheelEvent |
||||||
|
import org.w3c.dom.DragEvent |
||||||
|
import org.w3c.dom.TouchEvent |
||||||
|
import org.w3c.dom.clipboard.ClipboardEvent |
||||||
|
import org.w3c.dom.events.Event |
||||||
|
import org.w3c.dom.events.FocusEvent |
||||||
|
import org.w3c.dom.events.InputEvent |
||||||
|
import org.w3c.dom.events.KeyboardEvent |
||||||
|
import org.w3c.dom.events.MouseEvent |
||||||
|
import org.w3c.dom.events.WheelEvent |
||||||
|
import org.w3c.dom.pointerevents.PointerEvent |
||||||
|
|
||||||
|
open class WrappedEventListener<T : GenericWrappedEvent<*>>( |
||||||
|
val event: String, |
||||||
|
val options: Options, |
||||||
|
val listener: (T) -> Unit |
||||||
|
) : org.w3c.dom.events.EventListener { |
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST") |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedEventImpl(event) as T) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Options { |
||||||
|
// TODO: add options for addEventListener |
||||||
|
|
||||||
|
companion object { |
||||||
|
val DEFAULT = Options() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class MouseEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedMouseEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedMouseEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedMouseEvent(event as MouseEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class MouseWheelEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedWheelEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedWheelEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedWheelEvent(event as WheelEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class KeyboardEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedKeyboardEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedKeyboardEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedKeyboardEvent(event as KeyboardEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class FocusEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedFocusEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedFocusEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedFocusEvent(event as FocusEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class TouchEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedTouchEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedTouchEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedTouchEvent(event as TouchEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class DragEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedDragEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedDragEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedDragEvent(event as DragEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class PointerEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedPointerEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedPointerEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedPointerEvent(event as PointerEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class ClipboardEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedClipboardEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedClipboardEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedClipboardEvent(event as ClipboardEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class InputEventListener( |
||||||
|
event: String, |
||||||
|
options: Options, |
||||||
|
listener: (WrappedInputEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedInputEvent>(event, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
listener(WrappedInputEvent(event as InputEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class RadioInputEventListener( |
||||||
|
options: Options, |
||||||
|
listener: (WrappedRadioInputEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedRadioInputEvent>(EventsListenerBuilder.INPUT, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
val checked = event.target.asDynamic().checked as Boolean |
||||||
|
listener(WrappedRadioInputEvent(event, checked)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class CheckBoxInputEventListener( |
||||||
|
options: Options, |
||||||
|
listener: (WrappedCheckBoxInputEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedCheckBoxInputEvent>( |
||||||
|
EventsListenerBuilder.INPUT, options, listener |
||||||
|
) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
val checked = event.target.asDynamic().checked as Boolean |
||||||
|
listener(WrappedCheckBoxInputEvent(event, checked)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class TextInputEventListener( |
||||||
|
options: Options, |
||||||
|
listener: (WrappedTextInputEvent) -> Unit |
||||||
|
) : WrappedEventListener<WrappedTextInputEvent>(EventsListenerBuilder.INPUT, options, listener) { |
||||||
|
override fun handleEvent(event: Event) { |
||||||
|
val text = event.target.asDynamic().value as String |
||||||
|
listener(WrappedTextInputEvent(event as InputEvent, text)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,450 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
@file:Suppress("UNUSED") |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import kotlinx.browser.window |
||||||
|
import org.w3c.dom.DOMMatrix |
||||||
|
import org.w3c.dom.DOMMatrixReadOnly |
||||||
|
import org.w3c.dom.Element |
||||||
|
import org.w3c.dom.css.CSSRule |
||||||
|
import org.w3c.dom.css.CSSRuleList |
||||||
|
import org.w3c.dom.css.CSSStyleRule |
||||||
|
import org.w3c.dom.css.ElementCSSInlineStyle |
||||||
|
import org.w3c.dom.css.StyleSheet |
||||||
|
|
||||||
|
inline val StyleSheet.cssRules |
||||||
|
get() = this.asDynamic().cssRules.unsafeCast<CSSRuleList>() |
||||||
|
|
||||||
|
inline fun StyleSheet.deleteRule(index: Int) { |
||||||
|
this.asDynamic().deleteRule(index) |
||||||
|
} |
||||||
|
|
||||||
|
inline val CSSStyleRule.styleMap |
||||||
|
get() = this.asDynamic().styleMap.unsafeCast<StylePropertyMap>() |
||||||
|
|
||||||
|
inline operator fun CSSRuleList.get(index: Int): CSSRule { |
||||||
|
return this.asDynamic()[index].unsafeCast<CSSRule>() |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleSheet.insertRule(cssRule: String, index: Int? = null): Int { |
||||||
|
return if (index != null) { |
||||||
|
this.asDynamic().insertRule(cssRule, index).unsafeCast<Int>() |
||||||
|
} else { |
||||||
|
this.asDynamic().insertRule(cssRule).unsafeCast<Int>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val ElementCSSInlineStyle.attributeStyleMap |
||||||
|
get() = this.asDynamic().attributeStyleMap.unsafeCast<StylePropertyMap>() |
||||||
|
|
||||||
|
external interface CSSStyleValue { |
||||||
|
// toString() : string |
||||||
|
} |
||||||
|
|
||||||
|
@JsName("CSSStyleValue") |
||||||
|
open external class CSSStyleValueJS : CSSStyleValue { |
||||||
|
companion object { |
||||||
|
fun parse(property: String, cssText: String): CSSStyleValue |
||||||
|
fun parseAll(property: String, cssText: String): Array<CSSStyleValue> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSVariableReferenceValue( |
||||||
|
variable: String, |
||||||
|
fallback: CSSUnparsedValue? = definedExternally |
||||||
|
) { |
||||||
|
val variable: String |
||||||
|
val fallback: CSSUnparsedValue? |
||||||
|
} |
||||||
|
|
||||||
|
// type CSSUnparsedSegment = String | CSSVariableReferenceValue |
||||||
|
interface CSSUnparsedSegment { |
||||||
|
companion object { |
||||||
|
operator fun invoke(value: String) = value.unsafeCast<CSSUnparsedSegment>() |
||||||
|
operator fun invoke(value: CSSVariableReferenceValue) = |
||||||
|
value.unsafeCast<CSSUnparsedSegment>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun CSSUnparsedSegment.asString() = this.asDynamic() as? String |
||||||
|
fun CSSUnparsedSegment.asCSSVariableReferenceValue() = |
||||||
|
this.asDynamic() as? CSSVariableReferenceValue |
||||||
|
|
||||||
|
external class CSSUnparsedValue(members: Array<CSSUnparsedSegment>) : CSSStyleValue { |
||||||
|
// TODO: [Symbol.iterator]() : IterableIterator<CSSUnparsedSegment> |
||||||
|
fun forEach(handler: (CSSUnparsedSegment) -> Unit) |
||||||
|
val length: Int |
||||||
|
|
||||||
|
// readonly [index: number]: CSSUnparsedSegment |
||||||
|
operator fun get(index: Int): CSSUnparsedSegment |
||||||
|
operator fun set(index: Int, value: CSSUnparsedSegment) |
||||||
|
} |
||||||
|
|
||||||
|
external interface CSSKeywordValue : CSSStyleValue { |
||||||
|
val value: String |
||||||
|
} |
||||||
|
|
||||||
|
@JsName("CSSKeywordValue") |
||||||
|
external class CSSKeywordValueJS(value: String) : CSSKeywordValue { |
||||||
|
override val value: String |
||||||
|
} |
||||||
|
|
||||||
|
// type CSSNumberish = number | CSSNumericValue |
||||||
|
interface CSSNumberish { |
||||||
|
companion object { |
||||||
|
operator fun invoke(value: Number) = value.unsafeCast<CSSNumberish>() |
||||||
|
operator fun invoke(value: CSSNumericValue) = |
||||||
|
value.unsafeCast<CSSNumberish>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun CSSNumberish.asNumber() = this.asDynamic() as? Number |
||||||
|
fun CSSNumberish.asCSSNumericValue(): CSSNumericValue? = this.asDynamic() as? CSSNumericValueJS |
||||||
|
|
||||||
|
// declare enum CSSNumericBaseType { |
||||||
|
// 'length', |
||||||
|
// 'angle', |
||||||
|
// 'time', |
||||||
|
// 'frequency', |
||||||
|
// 'resolution', |
||||||
|
// 'flex', |
||||||
|
// 'percent', |
||||||
|
// } |
||||||
|
enum class CSSNumericBaseType(val value: String) { |
||||||
|
@JsName("_length") |
||||||
|
length("length"), |
||||||
|
angle("angle"), |
||||||
|
time("time"), |
||||||
|
frequency("frequency"), |
||||||
|
resolution("resolution"), |
||||||
|
flex("flex"), |
||||||
|
percent("percent") |
||||||
|
} |
||||||
|
|
||||||
|
external interface CSSNumericType { |
||||||
|
val length: Number |
||||||
|
val angle: Number |
||||||
|
val time: Number |
||||||
|
val frequency: Number |
||||||
|
val resolution: Number |
||||||
|
val flex: Number |
||||||
|
val percent: Number |
||||||
|
// percentHint: CSSNumericBaseType |
||||||
|
} |
||||||
|
|
||||||
|
val CSSNumericType.percentHint |
||||||
|
get() = CSSNumericBaseType.valueOf(this.asDynamic().percentHint) |
||||||
|
// set(value) { this.asDynamic().percentHint = value.value } |
||||||
|
|
||||||
|
external interface CSSNumericValue : CSSStyleValue { |
||||||
|
fun add(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
fun sub(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
fun mul(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
fun div(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
fun min(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
fun max(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
|
||||||
|
fun equals(vararg values: CSSNumberish): Boolean |
||||||
|
|
||||||
|
fun to(unit: String): CSSUnitValue |
||||||
|
fun toSum(vararg units: String): CSSMathSum |
||||||
|
fun type(): CSSNumericType |
||||||
|
} |
||||||
|
|
||||||
|
abstract external class CSSNumericValueJS : CSSNumericValue { |
||||||
|
override fun add(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
override fun sub(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
override fun mul(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
override fun div(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
override fun min(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
override fun max(vararg values: CSSNumberish): CSSNumericValue |
||||||
|
|
||||||
|
override fun equals(vararg values: CSSNumberish): Boolean |
||||||
|
|
||||||
|
override fun to(unit: String): CSSUnitValue |
||||||
|
override fun toSum(vararg units: String): CSSMathSum |
||||||
|
override fun type(): CSSNumericType |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun parse(cssText: String): CSSNumericValue |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
external interface CSSUnitValue : CSSNumericValue { |
||||||
|
val value: Number |
||||||
|
val unit: String |
||||||
|
} |
||||||
|
|
||||||
|
external interface CSSTypedUnitValue<T> : CSSNumericValue { |
||||||
|
val value: Number |
||||||
|
val unit: T |
||||||
|
} |
||||||
|
|
||||||
|
@JsName("CSSUnitValue") |
||||||
|
external class CSSUnitValueJS(value: Number, unit: String) : CSSNumericValueJS, CSSUnitValue { |
||||||
|
override val value: Number |
||||||
|
override val unit: String |
||||||
|
} |
||||||
|
|
||||||
|
// declare enum CSSMathOperator { |
||||||
|
// 'sum', |
||||||
|
// 'product', |
||||||
|
// 'negate', |
||||||
|
// 'invert', |
||||||
|
// 'min', |
||||||
|
// 'max', |
||||||
|
// 'clamp', |
||||||
|
// } |
||||||
|
enum class CSSMathOperator(val value: String) { |
||||||
|
sum("sum"), |
||||||
|
product("product"), |
||||||
|
negate("negate"), |
||||||
|
invert("invert"), |
||||||
|
min("min"), |
||||||
|
max("max"), |
||||||
|
clamp("clamp") |
||||||
|
} |
||||||
|
|
||||||
|
open external class CSSMathValue : CSSNumericValueJS { |
||||||
|
// readonly operator: CSSMathOperator |
||||||
|
} |
||||||
|
|
||||||
|
val CSSMathValue.operator |
||||||
|
get() = CSSMathOperator.valueOf(this.asDynamic().operator) |
||||||
|
// set(value) { this.asDynamic().operator = value.value } |
||||||
|
|
||||||
|
external class CSSMathSum(vararg args: CSSNumberish) : CSSMathValue { |
||||||
|
val values: CSSNumericArray |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSMathProduct(vararg args: CSSNumberish) : CSSMathValue { |
||||||
|
val values: CSSNumericArray |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSMathNegate(arg: CSSNumberish) : CSSMathValue { |
||||||
|
val value: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSMathInvert(arg: CSSNumberish) : CSSMathValue { |
||||||
|
val value: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSMathMin(vararg args: CSSNumberish) : CSSMathValue { |
||||||
|
val values: CSSNumericArray |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSMathMax(vararg args: CSSNumberish) : CSSMathValue { |
||||||
|
val values: CSSNumericArray |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(yavanosta) : conflict with base class properties |
||||||
|
// Since there is no support for this class in any browser, it's better |
||||||
|
// wait for the implementation. |
||||||
|
// declare class CSSMathClamp extends CSSMathValue { |
||||||
|
// constructor(min: CSSNumberish, val: CSSNumberish, max: CSSNumberish) |
||||||
|
// readonly min: CSSNumericValue |
||||||
|
// readonly val: CSSNumericValue |
||||||
|
// readonly max: CSSNumericValue |
||||||
|
// } |
||||||
|
|
||||||
|
external class CSSNumericArray { |
||||||
|
// TODO: [Symbol.iterator]() : IterableIterator<CSSNumericValue> |
||||||
|
fun forEach(handler: (CSSNumericValue) -> Unit) |
||||||
|
val length: Int |
||||||
|
|
||||||
|
// readonly [index: number]: CSSNumericValue |
||||||
|
operator fun get(index: Int): CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSTransformValue(transforms: Array<CSSTransformComponent>) : CSSStyleValue { |
||||||
|
// [Symbol.iterator]() : IterableIterator<CSSTransformComponent> |
||||||
|
fun forEach(handler: (CSSTransformComponent) -> Unit) |
||||||
|
val length: Int |
||||||
|
|
||||||
|
// [index: number]: CSSTransformComponent |
||||||
|
operator fun get(index: Int): CSSTransformComponent |
||||||
|
operator fun set(index: Int, value: CSSTransformComponent) |
||||||
|
val is2D: Boolean |
||||||
|
fun toMatrix(): DOMMatrix |
||||||
|
} |
||||||
|
|
||||||
|
open external class CSSTransformComponent { |
||||||
|
val is2D: Boolean |
||||||
|
fun toMatrix(): DOMMatrix |
||||||
|
// toString() : string |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSTranslate( |
||||||
|
x: CSSNumericValue, |
||||||
|
y: CSSNumericValue, |
||||||
|
z: CSSNumericValue? = definedExternally |
||||||
|
) : CSSTransformComponent { |
||||||
|
val x: CSSNumericValue |
||||||
|
val y: CSSNumericValue |
||||||
|
val z: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSRotate(angle: CSSNumericValue) : CSSTransformComponent { |
||||||
|
constructor(x: CSSNumberish, y: CSSNumberish, z: CSSNumberish, angle: CSSNumericValue) |
||||||
|
|
||||||
|
val x: CSSNumberish |
||||||
|
val y: CSSNumberish |
||||||
|
val z: CSSNumberish |
||||||
|
val angle: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSScale( |
||||||
|
x: CSSNumberish, |
||||||
|
y: CSSNumberish, |
||||||
|
z: CSSNumberish? = definedExternally |
||||||
|
) : CSSTransformComponent { |
||||||
|
val x: CSSNumberish |
||||||
|
val y: CSSNumberish |
||||||
|
val z: CSSNumberish |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSSkew(ax: CSSNumericValue, ay: CSSNumericValue) : CSSTransformComponent { |
||||||
|
val ax: CSSNumericValue |
||||||
|
val ay: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSSkewX(ax: CSSNumericValue) : CSSTransformComponent { |
||||||
|
val ax: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSSkewY(ay: CSSNumericValue) : CSSTransformComponent { |
||||||
|
val ay: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
/* Note that skew(x,y) is *not* the same as skewX(x) skewY(y), |
||||||
|
thus the separate interfaces for all three. */ |
||||||
|
|
||||||
|
external class CSSPerspective(length: CSSNumericValue) : CSSTransformComponent { |
||||||
|
val length: CSSNumericValue |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSMatrixComponent( |
||||||
|
matrix: DOMMatrixReadOnly, |
||||||
|
options: CSSMatrixComponentOptions? = definedExternally |
||||||
|
) : CSSTransformComponent { |
||||||
|
val matrix: DOMMatrix |
||||||
|
} |
||||||
|
|
||||||
|
external interface CSSMatrixComponentOptions { |
||||||
|
val is2D: Boolean |
||||||
|
} |
||||||
|
|
||||||
|
external class CSSImageValue : CSSStyleValue |
||||||
|
|
||||||
|
open external class StylePropertyMapReadOnly { |
||||||
|
// TODO: [Symbol.iterator]() : IterableIterator<[string, CSSStyleValue[]]> |
||||||
|
|
||||||
|
fun get(property: String): CSSStyleValue? // CSSStyleValue | undefined |
||||||
|
fun getAll(property: String): Array<CSSStyleValue> |
||||||
|
fun has(property: String): Boolean |
||||||
|
val size: Number |
||||||
|
} |
||||||
|
|
||||||
|
fun StylePropertyMapReadOnly.forEach(handler: (String, Array<CSSStyleValue>) -> Unit) { |
||||||
|
this.asDynamic().forEach { entry: Array<dynamic> -> |
||||||
|
handler( |
||||||
|
entry[0].unsafeCast<String>(), |
||||||
|
entry[1].unsafeCast<Array<CSSStyleValue>>() |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// CSSStyleValue | string |
||||||
|
interface StylePropertyValue { |
||||||
|
companion object { |
||||||
|
operator fun invoke(value: String) = value.unsafeCast<StylePropertyValue>() |
||||||
|
operator fun invoke(value: Number) = value.unsafeCast<StylePropertyValue>() |
||||||
|
operator fun invoke(value: CSSStyleValue) = value.unsafeCast<StylePropertyValue>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun StylePropertyValue.asString() = this.asDynamic() as? String |
||||||
|
fun StylePropertyValue.asNumber() = this.asDynamic() as? Number |
||||||
|
fun StylePropertyValue.asCSSStyleValue(): CSSStyleValue? = this.asDynamic() as? CSSStyleValueJS |
||||||
|
|
||||||
|
external class StylePropertyMap : StylePropertyMapReadOnly { |
||||||
|
fun set(property: String, vararg values: StylePropertyValue) |
||||||
|
fun append(property: String, vararg values: StylePropertyValue) |
||||||
|
fun delete(property: String) |
||||||
|
fun clear() |
||||||
|
} |
||||||
|
|
||||||
|
inline fun Element.computedStyleMap(): StylePropertyMapReadOnly = |
||||||
|
this.asDynamic().computedStyleMap().unsafeCast<StylePropertyMapReadOnly>() |
||||||
|
|
||||||
|
external class CSS { |
||||||
|
companion object { |
||||||
|
fun number(value: Number): CSSUnitValue |
||||||
|
fun percent(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// <length> |
||||||
|
fun em(value: Number): CSSUnitValue |
||||||
|
fun ex(value: Number): CSSUnitValue |
||||||
|
fun ch(value: Number): CSSUnitValue |
||||||
|
fun ic(value: Number): CSSUnitValue |
||||||
|
fun rem(value: Number): CSSUnitValue |
||||||
|
fun lh(value: Number): CSSUnitValue |
||||||
|
fun rlh(value: Number): CSSUnitValue |
||||||
|
fun vw(value: Number): CSSUnitValue |
||||||
|
fun vh(value: Number): CSSUnitValue |
||||||
|
fun vi(value: Number): CSSUnitValue |
||||||
|
fun vb(value: Number): CSSUnitValue |
||||||
|
fun vmin(value: Number): CSSUnitValue |
||||||
|
fun vmax(value: Number): CSSUnitValue |
||||||
|
fun cm(value: Number): CSSUnitValue |
||||||
|
fun mm(value: Number): CSSUnitValue |
||||||
|
fun Q(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// function _in(value: Number) : CSSUnitValue |
||||||
|
// export |
||||||
|
// { _in as in } |
||||||
|
fun pt(value: Number): CSSUnitValue |
||||||
|
fun pc(value: Number): CSSUnitValue |
||||||
|
fun px(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// <angle> |
||||||
|
fun deg(value: Number): CSSUnitValue |
||||||
|
fun grad(value: Number): CSSUnitValue |
||||||
|
fun rad(value: Number): CSSUnitValue |
||||||
|
fun turn(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// <time> |
||||||
|
fun s(value: Number): CSSUnitValue |
||||||
|
fun ms(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// <frequency> |
||||||
|
fun Hz(value: Number): CSSUnitValue |
||||||
|
fun kHz(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// <resolution> |
||||||
|
fun dpi(value: Number): CSSUnitValue |
||||||
|
fun dpcm(value: Number): CSSUnitValue |
||||||
|
fun dppx(value: Number): CSSUnitValue |
||||||
|
|
||||||
|
// <flex> |
||||||
|
fun fr(value: Number): CSSUnitValue |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("unused") |
||||||
|
val cssTypedOMPolyfill = CSSTypedOMPolyfill.default(window) |
@ -0,0 +1,39 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import androidx.compose.web.css.selectors.CSSSelector |
||||||
|
|
||||||
|
interface CSSBuilder : CSSStyleRuleBuilder, GenericStyleSheetBuilder<CSSBuilder> { |
||||||
|
val root: CSSSelector |
||||||
|
val self: CSSSelector |
||||||
|
} |
||||||
|
|
||||||
|
class CSSBuilderImpl( |
||||||
|
override val root: CSSSelector, |
||||||
|
override val self: CSSSelector, |
||||||
|
rulesHolder: CSSRulesHolder |
||||||
|
) : CSSRuleBuilderImpl(), CSSBuilder, CSSRulesHolder by rulesHolder { |
||||||
|
override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) { |
||||||
|
val (style, rules) = buildCSS(root, selector, cssRule) |
||||||
|
rules.forEach { add(it) } |
||||||
|
add(selector, style) |
||||||
|
} |
||||||
|
|
||||||
|
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSBuilder>.() -> Unit) = |
||||||
|
CSSBuilderImpl(root, self, StyleSheetBuilderImpl()).apply(rulesBuild).cssRules |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
@file:Suppress("UNUSED") |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
interface CSSAutoValue : CSSKeywordValue |
||||||
|
|
||||||
|
val auto = CSSKeywordValueJS("auto").unsafeCast<CSSAutoValue>() |
||||||
|
fun asCSSAutoValue(value: dynamic) = (value as? CSSKeywordValueJS).unsafeCast<CSSAutoValue>() |
||||||
|
|
||||||
|
// type CSSSizeOrAutoValue = CSSSizeValue | CSSAutoValue |
||||||
|
interface CSSSizeOrAutoValue : CSSStyleValue, StylePropertyValue { |
||||||
|
companion object { |
||||||
|
operator fun invoke(value: CSSSizeValue) = value.unsafeCast<CSSSizeOrAutoValue>() |
||||||
|
operator fun invoke(value: CSSAutoValue) = value.unsafeCast<CSSSizeOrAutoValue>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun CSSSizeOrAutoValue.asCSSSizeValue() = this.asDynamic() as? CSSUnitValueJS |
||||||
|
fun CSSSizeOrAutoValue.asCSSAutoValue(): CSSAutoValue = asCSSAutoValue(this.asDynamic()) |
||||||
|
|
||||||
|
enum class Direction { |
||||||
|
rtl, |
||||||
|
ltr; |
||||||
|
|
||||||
|
override fun toString(): String = this.name |
||||||
|
} |
||||||
|
|
||||||
|
typealias LanguageCode = String |
@ -0,0 +1,153 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
interface CSSMediaQuery { |
||||||
|
interface Invertible : CSSMediaQuery |
||||||
|
interface Combinable : CSSMediaQuery |
||||||
|
interface Atomic : Invertible, Combinable |
||||||
|
data class Raw(val string: String) : Atomic { |
||||||
|
override fun toString() = string |
||||||
|
} |
||||||
|
data class MediaType(val type: Enum) : Atomic { |
||||||
|
enum class Enum { |
||||||
|
all, print, screen, speech |
||||||
|
} |
||||||
|
|
||||||
|
override fun toString() = type.name |
||||||
|
} |
||||||
|
|
||||||
|
data class MediaFeature( |
||||||
|
val name: String, |
||||||
|
val value: StylePropertyValue? = null |
||||||
|
) : CSSMediaQuery, Atomic { |
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return if (other is MediaFeature) { |
||||||
|
name == other.name && value.toString() == other.value.toString() |
||||||
|
} else false |
||||||
|
} |
||||||
|
|
||||||
|
override fun toString() = "($name${ value?.let { ": $value)" } ?: "" }" |
||||||
|
} |
||||||
|
|
||||||
|
// looks like it doesn't work at least in chrome |
||||||
|
data class NotFeature(val query: MediaFeature) : CSSMediaQuery { |
||||||
|
override fun toString() = "(not $query)" |
||||||
|
} |
||||||
|
|
||||||
|
data class And(val mediaList: MutableList<Atomic>) : CSSMediaQuery, Invertible, Combinable { |
||||||
|
override fun toString() = mediaList.joinToString(" and ") |
||||||
|
} |
||||||
|
|
||||||
|
data class Not(val query: Invertible) : CSSMediaQuery { |
||||||
|
override fun toString() = "not $query" |
||||||
|
} |
||||||
|
|
||||||
|
data class Combine(val mediaList: MutableList<CSSMediaQuery>) : CSSMediaQuery { |
||||||
|
override fun toString() = mediaList.joinToString(", ") |
||||||
|
} |
||||||
|
|
||||||
|
data class Only(val type: MediaType, val query: Combinable) : CSSMediaQuery, Invertible { |
||||||
|
override fun toString() = "only $type and $query" |
||||||
|
} |
||||||
|
|
||||||
|
// looks like it doesn't work at least in chrome, maybe need fallback to Combine |
||||||
|
data class Or(val mediaList: List<CSSMediaQuery>) { |
||||||
|
override fun toString() = mediaList.joinToString(" or ") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class CSSMediaRuleDeclaration( |
||||||
|
val query: CSSMediaQuery, |
||||||
|
rules: CSSRuleDeclarationList |
||||||
|
) : CSSGroupingRuleDeclaration(rules) { |
||||||
|
override val header: String |
||||||
|
get() = "@media $query" |
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return if (other is CSSMediaRuleDeclaration) { |
||||||
|
rules == other.rules && query == other.query |
||||||
|
} else false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media( |
||||||
|
query: CSSMediaQuery, |
||||||
|
rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit |
||||||
|
) { |
||||||
|
val rules = buildRules(rulesBuild) |
||||||
|
add(CSSMediaRuleDeclaration(query, rules)) |
||||||
|
} |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media( |
||||||
|
query: String, |
||||||
|
rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit |
||||||
|
) { |
||||||
|
media(CSSMediaQuery.Raw(query), rulesBuild) |
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") |
||||||
|
inline fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media( |
||||||
|
name: String, |
||||||
|
value: StylePropertyValue? = null, |
||||||
|
noinline rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit |
||||||
|
) { |
||||||
|
media(feature(name, value), rulesBuild) |
||||||
|
} |
||||||
|
|
||||||
|
fun feature( |
||||||
|
name: String, |
||||||
|
value: StylePropertyValue? = null |
||||||
|
) = CSSMediaQuery.MediaFeature(name, value) |
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") |
||||||
|
inline fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media( |
||||||
|
vararg mediaList: CSSMediaQuery, |
||||||
|
noinline rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit |
||||||
|
) { |
||||||
|
media(combine(*mediaList), rulesBuild) |
||||||
|
} |
||||||
|
|
||||||
|
fun combine( |
||||||
|
vararg mediaList: CSSMediaQuery |
||||||
|
) = CSSMediaQuery.Combine(mediaList.toMutableList()) |
||||||
|
|
||||||
|
infix fun CSSMediaQuery.Atomic.and( |
||||||
|
query: CSSMediaQuery.Atomic |
||||||
|
) = CSSMediaQuery.And(mutableListOf(this, query)) |
||||||
|
|
||||||
|
infix fun CSSMediaQuery.And.and( |
||||||
|
query: CSSMediaQuery.Atomic |
||||||
|
) = this.apply { |
||||||
|
this.mediaList.add(query) |
||||||
|
} |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.not( |
||||||
|
query: CSSMediaQuery.Invertible |
||||||
|
) = CSSMediaQuery.Not(query) |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.minWidth(value: CSSSizeValue) = |
||||||
|
CSSMediaQuery.MediaFeature("min-width", StylePropertyValue(value)) |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.maxWidth(value: CSSSizeValue) = |
||||||
|
CSSMediaQuery.MediaFeature("max-width", StylePropertyValue(value)) |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.minHeight(value: CSSSizeValue) = |
||||||
|
CSSMediaQuery.MediaFeature("min-height", StylePropertyValue(value)) |
||||||
|
|
||||||
|
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.maxHeight(value: CSSSizeValue) = |
||||||
|
CSSMediaQuery.MediaFeature("max-height", StylePropertyValue(value)) |
@ -0,0 +1,30 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import org.w3c.dom.Window |
||||||
|
|
||||||
|
@JsModule("css-typed-om") |
||||||
|
@JsNonModule |
||||||
|
abstract external class CSSTypedOMPolyfill { |
||||||
|
companion object { |
||||||
|
fun default(window: Window) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun StylePropertyMap.clear() { |
||||||
|
throw AssertionError("StylePropertyMap::clear isn't polyfilled") |
||||||
|
} |
@ -0,0 +1,427 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
fun StyleBuilder.opacity(value: Number) { |
||||||
|
property("opacity", value(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.order(value: Int) { |
||||||
|
property("order", StylePropertyValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.flexGrow(value: Number) { |
||||||
|
property("flex-grow", StylePropertyValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.flexShrink(value: Number) { |
||||||
|
property("flex-shrink", StylePropertyValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.opacity(value: CSSpercentValue) { |
||||||
|
property("opacity", value(value.value as Double / 100)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.color(value: String) { |
||||||
|
property("color", value(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.color(value: Color) { |
||||||
|
// color hasn't Typed OM yet |
||||||
|
property("color", value.styleValue()) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.backgroundColor(value: CSSVariableValue<Color>) { |
||||||
|
property("background-color", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.backgroundColor(value: Color) { |
||||||
|
property("background-color", value.styleValue()) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.backgroundColor(value: String) { |
||||||
|
property("background-color", value(value)) |
||||||
|
} |
||||||
|
|
||||||
|
enum class LineStyle { |
||||||
|
None, |
||||||
|
Hidden, |
||||||
|
Dotted, |
||||||
|
Dashed, |
||||||
|
Solid, |
||||||
|
Double, |
||||||
|
Groove, |
||||||
|
Ridge, |
||||||
|
Inset, |
||||||
|
Outset |
||||||
|
} |
||||||
|
|
||||||
|
enum class DisplayStyle(val value: String) { |
||||||
|
Block("block"), |
||||||
|
Inline("inline"), |
||||||
|
InlineBlock("inline-block"), |
||||||
|
Flex("flex"), |
||||||
|
LegacyInlineFlex("inline-flex"), |
||||||
|
Grid("grid"), |
||||||
|
LegacyInlineGrid("inline-grid"), |
||||||
|
FlowRoot("flow-root"), |
||||||
|
|
||||||
|
None("none"), |
||||||
|
Contents("contents"), |
||||||
|
|
||||||
|
// TODO(shabunc): This properties behave them iconsistenly in both Chrome and Firefox so I turned the off so far |
||||||
|
// BlockFlow("block flow"), |
||||||
|
// InlineFlow("inline flow"), |
||||||
|
// InlineFlowRoot("inline flow-root"), |
||||||
|
// BlocklFlex("block flex"), |
||||||
|
// InlineFlex("inline flex"), |
||||||
|
// BlockGrid("block grid"), |
||||||
|
// InlineGrid("inline grid"), |
||||||
|
// BlockFlowRoot("block flow-root"), |
||||||
|
|
||||||
|
Table("table"), |
||||||
|
TableRow("table-row"), |
||||||
|
ListItem("list-item"), |
||||||
|
|
||||||
|
Inherit("inherit"), |
||||||
|
Initial("initial"), |
||||||
|
Unset("unset") |
||||||
|
} |
||||||
|
|
||||||
|
enum class FlexDirection(val value: String) { |
||||||
|
Row("row"), |
||||||
|
RowReverse("row-reverse"), |
||||||
|
Column("column"), |
||||||
|
ColumnReverse("column-reverse") |
||||||
|
} |
||||||
|
|
||||||
|
enum class FlexWrap(val value: String) { |
||||||
|
Wrap("wrap"), |
||||||
|
Nowrap("nowrap"), |
||||||
|
WrapReverse("wrap-reverse") |
||||||
|
} |
||||||
|
|
||||||
|
enum class JustifyContent(val value: String) { |
||||||
|
Center("center"), |
||||||
|
Start("start"), |
||||||
|
End("end"), |
||||||
|
FlexStart("flex-start"), |
||||||
|
FlexEnd("flex-end"), |
||||||
|
Left("left"), |
||||||
|
Right("right"), |
||||||
|
Normal("normal"), |
||||||
|
SpaceBetween("space-between"), |
||||||
|
SpaceAround("space-around"), |
||||||
|
SpaceEvenly("space-evenly"), |
||||||
|
Stretch("stretch"), |
||||||
|
Inherit("inherit"), |
||||||
|
Initial("initial"), |
||||||
|
Unset("unset"), |
||||||
|
SafeCenter("safe center"), |
||||||
|
UnsafeCenter("unsafe center"), |
||||||
|
} |
||||||
|
|
||||||
|
enum class AlignSelf(val value: String) { |
||||||
|
Auto("auto"), |
||||||
|
Normal("normal"), |
||||||
|
Center("center"), |
||||||
|
Start("start"), |
||||||
|
End("end"), |
||||||
|
SelfStart("self-start"), |
||||||
|
SelfEnd("self-end"), |
||||||
|
FlexStart("flex-start"), |
||||||
|
FlexEnd("flex-end"), |
||||||
|
Baseline("baseline"), |
||||||
|
// FirstBaseline("first baseline"), |
||||||
|
// LastBaseline("last baseline"), |
||||||
|
Stretch("stretch"), |
||||||
|
SafeCenter("safe center"), |
||||||
|
UnsafeCenter("unsafe center"), |
||||||
|
Inherit("inherit"), |
||||||
|
Initial("initial"), |
||||||
|
Unset("unset") |
||||||
|
} |
||||||
|
|
||||||
|
enum class AlignItems(val value: String) { |
||||||
|
Normal("normal"), |
||||||
|
Stretch("stretch"), |
||||||
|
Center("center"), |
||||||
|
Start("start"), |
||||||
|
End("end"), |
||||||
|
FlexStart("flex-start"), |
||||||
|
FlexEnd("flex-end"), |
||||||
|
Baseline("baseline"), |
||||||
|
// FirstBaseline("first baseline"), |
||||||
|
// LastBaseline("last baseline"), |
||||||
|
SafeCenter("safe center"), |
||||||
|
UnsafeCenter("unsafe center"), |
||||||
|
|
||||||
|
Inherit("inherit"), |
||||||
|
Initial("initial"), |
||||||
|
Unset("unset") |
||||||
|
} |
||||||
|
|
||||||
|
enum class AlignContent(val value: String) { |
||||||
|
Center("center"), |
||||||
|
Start("start"), |
||||||
|
End("end"), |
||||||
|
FlexStart("flex-start"), |
||||||
|
FlexEnd("flex-end"), |
||||||
|
Baseline("baseline"), |
||||||
|
// FirstBaseline("first baseline"), |
||||||
|
// LastBaseline("last baseline"), |
||||||
|
SafeCenter("safe center"), |
||||||
|
UnsafeCenter("unsafe center"), |
||||||
|
SpaceBetween("space-between"), |
||||||
|
SpaceAround("space-around"), |
||||||
|
SpaceEvenly("space-evenly"), |
||||||
|
Stretch("stretch"), |
||||||
|
|
||||||
|
Inherit("inherit"), |
||||||
|
Initial("initial"), |
||||||
|
Unset("unset") |
||||||
|
} |
||||||
|
|
||||||
|
enum class Position(val value: String) { |
||||||
|
Static("static"), |
||||||
|
Relative("relative"), |
||||||
|
Absolute("absolute"), |
||||||
|
Sticky("sticky"), |
||||||
|
Fixed("fixed") |
||||||
|
} |
||||||
|
|
||||||
|
class CSSBorder : CustomStyleValue { |
||||||
|
var width: StylePropertyValue? = null |
||||||
|
var style: StylePropertyValue? = null |
||||||
|
var color: StylePropertyValue? = null |
||||||
|
|
||||||
|
fun width(size: CSSSizeValue) { |
||||||
|
width = StylePropertyValue(size) |
||||||
|
} |
||||||
|
|
||||||
|
fun style(style: LineStyle) { |
||||||
|
this.style = StylePropertyValue(style.name) |
||||||
|
} |
||||||
|
|
||||||
|
fun color(color: Color) { |
||||||
|
this.color = color.styleValue() |
||||||
|
} |
||||||
|
|
||||||
|
fun color(color: CSSVariableValue<Color>) { |
||||||
|
this.color = color |
||||||
|
} |
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return if (other is CSSBorder) { |
||||||
|
styleValue().toString() == other.styleValue().toString() |
||||||
|
} else false |
||||||
|
} |
||||||
|
|
||||||
|
override fun styleValue(): StylePropertyValue { |
||||||
|
val values = listOfNotNull(width, style, color) |
||||||
|
return StylePropertyValue(values.joinToString(" ")) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inline fun StyleBuilder.border(crossinline borderBuild: CSSBorder.() -> Unit) { |
||||||
|
val border = CSSBorder().apply(borderBuild) |
||||||
|
property("border", border.styleValue()) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.border( |
||||||
|
width: CSSSizeValue? = null, |
||||||
|
style: LineStyle? = null, |
||||||
|
color: Color? = null |
||||||
|
) { |
||||||
|
border { |
||||||
|
width?.let { width(it) } |
||||||
|
style?.let { style(it) } |
||||||
|
color?.let { color(it) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.display(displayStyle: DisplayStyle) { |
||||||
|
property("display", StylePropertyValue(displayStyle.value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.flexDirection(flexDirection: FlexDirection) { |
||||||
|
property("flex-direction", StylePropertyValue(flexDirection.value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.flexWrap(flexWrap: FlexWrap) { |
||||||
|
property("flex-wrap", StylePropertyValue(flexWrap.value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.flexFlow(flexDirection: FlexDirection, flexWrap: FlexWrap) { |
||||||
|
property( |
||||||
|
"flex-flow", |
||||||
|
StylePropertyValue("${flexDirection.value} ${flexWrap.value}") |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.justifyContent(justifyContent: JustifyContent) { |
||||||
|
property( |
||||||
|
"justify-content", |
||||||
|
StylePropertyValue(justifyContent.value) |
||||||
|
) |
||||||
|
} |
||||||
|
fun StyleBuilder.alignSelf(alignSelf: AlignSelf) { |
||||||
|
property( |
||||||
|
"align-self", |
||||||
|
StylePropertyValue(alignSelf.value) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.alignItems(alignItems: AlignItems) { |
||||||
|
property( |
||||||
|
"align-items", |
||||||
|
StylePropertyValue(alignItems.value) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.alignContent(alignContent: AlignContent) { |
||||||
|
property( |
||||||
|
"align-content", |
||||||
|
StylePropertyValue(alignContent.value) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.position(position: Position) { |
||||||
|
property( |
||||||
|
"position", |
||||||
|
StylePropertyValue(position.value) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.width(value: CSSSizeOrAutoValue) { |
||||||
|
property("width", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.borderRadius(r: CSSSizeValue) { |
||||||
|
property("border-radius", StylePropertyValue(r.toString())) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.borderRadius(topLeft: CSSSizeValue, bottomRight: CSSSizeValue) { |
||||||
|
property("border-radius", StylePropertyValue("$topLeft $bottomRight")) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.borderRadius( |
||||||
|
topLeft: CSSSizeValue, |
||||||
|
topRightAndBottomLeft: CSSSizeValue, |
||||||
|
bottomRight: CSSSizeValue |
||||||
|
) { |
||||||
|
property("border-radius", StylePropertyValue("$topLeft $topRightAndBottomLeft $bottomRight")) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.borderRadius( |
||||||
|
topLeft: CSSSizeValue, |
||||||
|
topRight: CSSSizeValue, |
||||||
|
bottomRight: CSSSizeValue, |
||||||
|
bottomLeft: CSSSizeValue |
||||||
|
) { |
||||||
|
property("border-radius", StylePropertyValue("$topLeft $topRight $bottomRight $bottomLeft")) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.width(value: CSSSizeValue) { |
||||||
|
width(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.width(value: CSSAutoValue) { |
||||||
|
width(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.height(value: CSSSizeOrAutoValue) { |
||||||
|
property("height", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.height(value: CSSSizeValue) { |
||||||
|
height(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.height(value: CSSAutoValue) { |
||||||
|
height(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.top(value: CSSSizeOrAutoValue) { |
||||||
|
property("top", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.top(value: CSSSizeValue) { |
||||||
|
top(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.top(value: CSSAutoValue) { |
||||||
|
top(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.bottom(value: CSSSizeOrAutoValue) { |
||||||
|
property("bottom", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.bottom(value: CSSSizeValue) { |
||||||
|
bottom(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.bottom(value: CSSAutoValue) { |
||||||
|
bottom(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.left(value: CSSSizeOrAutoValue) { |
||||||
|
property("left", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.left(value: CSSSizeValue) { |
||||||
|
left(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.left(value: CSSAutoValue) { |
||||||
|
left(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.right(value: CSSSizeOrAutoValue) { |
||||||
|
property("right", value) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.right(value: CSSSizeValue) { |
||||||
|
right(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.right(value: CSSAutoValue) { |
||||||
|
right(CSSSizeOrAutoValue(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.fontSize(value: CSSSizeValue) { |
||||||
|
property("font-size", value(value)) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.margin(value: CSSSizeValue) { |
||||||
|
// marign hasn't Typed OM yet |
||||||
|
property("margin", value(value.toString())) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.marginLeft(value: CSSSizeValue) { |
||||||
|
property("margin-left", value(value.toString())) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.marginTop(value: CSSSizeValue) { |
||||||
|
property("margin-top", value(value.toString())) |
||||||
|
} |
||||||
|
|
||||||
|
fun StyleBuilder.padding(value: CSSSizeValue) { |
||||||
|
// padding hasn't Typed OM yet |
||||||
|
property("padding", value(value.toString())) |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import androidx.compose.web.css.selectors.CSSSelector |
||||||
|
|
||||||
|
interface CSSStyleRuleBuilder : StyleBuilder |
||||||
|
|
||||||
|
open class CSSRuleBuilderImpl : CSSStyleRuleBuilder, StyleBuilderImpl() |
||||||
|
|
||||||
|
abstract class CSSRuleDeclaration { |
||||||
|
abstract val header: String |
||||||
|
|
||||||
|
abstract override fun equals(other: Any?): Boolean |
||||||
|
} |
||||||
|
|
||||||
|
data class CSSStyleRuleDeclaration( |
||||||
|
val selector: CSSSelector, |
||||||
|
val style: StyleHolder |
||||||
|
) : CSSRuleDeclaration() { |
||||||
|
override val header |
||||||
|
get() = selector.toString() |
||||||
|
} |
||||||
|
|
||||||
|
abstract class CSSGroupingRuleDeclaration( |
||||||
|
val rules: CSSRuleDeclarationList |
||||||
|
) : CSSRuleDeclaration() |
||||||
|
|
||||||
|
typealias CSSRuleDeclarationList = List<CSSRuleDeclaration> |
||||||
|
typealias MutableCSSRuleDeclarationList = MutableList<CSSRuleDeclaration> |
||||||
|
|
||||||
|
fun buildCSSStyleRule(cssRule: CSSStyleRuleBuilder.() -> Unit): StyleHolder { |
||||||
|
val builder = CSSRuleBuilderImpl() |
||||||
|
builder.cssRule() |
||||||
|
return builder |
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
@file:Suppress("UNUSED") |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
external interface CSSSizeValue : CSSUnitValue |
||||||
|
|
||||||
|
// fake interfaces to distinguish units |
||||||
|
external interface CSSRelValue : CSSSizeValue |
||||||
|
external interface CSSpercentValue : CSSRelValue |
||||||
|
external interface CSSemValue : CSSRelValue |
||||||
|
external interface CSSexValue : CSSRelValue |
||||||
|
external interface CSSchValue : CSSRelValue |
||||||
|
external interface CSSicValue : CSSRelValue |
||||||
|
external interface CSSremValue : CSSRelValue |
||||||
|
external interface CSSlhValue : CSSRelValue |
||||||
|
external interface CSSrlhValue : CSSRelValue |
||||||
|
external interface CSSvwValue : CSSRelValue |
||||||
|
external interface CSSvhValue : CSSRelValue |
||||||
|
external interface CSSviValue : CSSRelValue |
||||||
|
external interface CSSvbValue : CSSRelValue |
||||||
|
external interface CSSvminValue : CSSRelValue |
||||||
|
external interface CSSvmaxValue : CSSRelValue |
||||||
|
external interface CSScmValue : CSSRelValue |
||||||
|
external interface CSSmmValue : CSSRelValue |
||||||
|
external interface CSSQValue : CSSRelValue |
||||||
|
|
||||||
|
external interface CSSAbsValue : CSSSizeValue |
||||||
|
external interface CSSptValue : CSSAbsValue |
||||||
|
external interface CSSpcValue : CSSAbsValue |
||||||
|
external interface CSSpxValue : CSSAbsValue |
||||||
|
|
||||||
|
external interface CSSangleValue : CSSUnitValue |
||||||
|
external interface CSSdegValue : CSSangleValue |
||||||
|
external interface CSSgradValue : CSSangleValue |
||||||
|
external interface CSSradValue : CSSangleValue |
||||||
|
external interface CSSturnValue : CSSangleValue |
||||||
|
|
||||||
|
external interface CSSTimeValue : CSSUnitValue |
||||||
|
external interface CSSsValue : CSSTimeValue |
||||||
|
external interface CSSmsValue : CSSTimeValue |
||||||
|
|
||||||
|
external interface CSSFrequencyValue : CSSUnitValue |
||||||
|
external interface CSSHzValue : CSSFrequencyValue |
||||||
|
external interface CSSkHzValue : CSSFrequencyValue |
||||||
|
|
||||||
|
external interface CSSResolutionValue : CSSUnitValue |
||||||
|
external interface CSSdpiValue : CSSResolutionValue |
||||||
|
external interface CSSdpcmValue : CSSResolutionValue |
||||||
|
external interface CSSdppxValue : CSSResolutionValue |
||||||
|
|
||||||
|
external interface CSSFlexValue : CSSUnitValue |
||||||
|
external interface CSSfrValue : CSSFlexValue |
||||||
|
|
||||||
|
val Number.number |
||||||
|
get(): CSSUnitValue = CSS.number(this) |
||||||
|
|
||||||
|
val Number.percent |
||||||
|
get(): CSSpercentValue = CSS.percent(this).unsafeCast<CSSpercentValue>() |
||||||
|
|
||||||
|
val Number.em |
||||||
|
get(): CSSemValue = CSS.em(this).unsafeCast<CSSemValue>() |
||||||
|
val Number.ex |
||||||
|
get(): CSSexValue = CSS.ex(this).unsafeCast<CSSexValue>() |
||||||
|
val Number.ch |
||||||
|
get(): CSSchValue = CSS.ch(this).unsafeCast<CSSchValue>() |
||||||
|
val Number.ic |
||||||
|
get(): CSSicValue = CSS.ic(this).unsafeCast<CSSicValue>() |
||||||
|
val Number.rem |
||||||
|
get(): CSSremValue = CSS.rem(this).unsafeCast<CSSremValue>() |
||||||
|
val Number.lh |
||||||
|
get(): CSSlhValue = CSS.lh(this).unsafeCast<CSSlhValue>() |
||||||
|
val Number.rlh |
||||||
|
get(): CSSrlhValue = CSS.rlh(this).unsafeCast<CSSrlhValue>() |
||||||
|
val Number.vw |
||||||
|
get(): CSSvwValue = CSS.vw(this).unsafeCast<CSSvwValue>() |
||||||
|
val Number.vh |
||||||
|
get(): CSSvhValue = CSS.vh(this).unsafeCast<CSSvhValue>() |
||||||
|
val Number.vi |
||||||
|
get(): CSSviValue = CSS.vi(this).unsafeCast<CSSviValue>() |
||||||
|
val Number.vb |
||||||
|
get(): CSSvbValue = CSS.vb(this).unsafeCast<CSSvbValue>() |
||||||
|
val Number.vmin |
||||||
|
get(): CSSvminValue = CSS.vmin(this).unsafeCast<CSSvminValue>() |
||||||
|
val Number.vmax |
||||||
|
get(): CSSvmaxValue = CSS.vmax(this).unsafeCast<CSSvmaxValue>() |
||||||
|
val Number.cm |
||||||
|
get(): CSScmValue = CSS.cm(this).unsafeCast<CSScmValue>() |
||||||
|
val Number.mm |
||||||
|
get(): CSSmmValue = CSS.mm(this).unsafeCast<CSSmmValue>() |
||||||
|
val Number.Q |
||||||
|
get(): CSSQValue = CSS.Q(this).unsafeCast<CSSQValue>() |
||||||
|
|
||||||
|
val Number.pt |
||||||
|
get(): CSSptValue = CSS.pt(this).unsafeCast<CSSptValue>() |
||||||
|
val Number.pc |
||||||
|
get(): CSSpcValue = CSS.pc(this).unsafeCast<CSSpcValue>() |
||||||
|
val Number.px |
||||||
|
get(): CSSpxValue = CSS.px(this).unsafeCast<CSSpxValue>() |
||||||
|
|
||||||
|
val Number.deg |
||||||
|
get(): CSSdegValue = CSS.deg(this).unsafeCast<CSSdegValue>() |
||||||
|
val Number.grad |
||||||
|
get(): CSSgradValue = CSS.grad(this).unsafeCast<CSSgradValue>() |
||||||
|
val Number.rad |
||||||
|
get(): CSSradValue = CSS.rad(this).unsafeCast<CSSradValue>() |
||||||
|
val Number.turn |
||||||
|
get(): CSSturnValue = CSS.turn(this).unsafeCast<CSSturnValue>() |
||||||
|
|
||||||
|
val Number.s |
||||||
|
get(): CSSsValue = CSS.s(this).unsafeCast<CSSsValue>() |
||||||
|
val Number.ms |
||||||
|
get(): CSSmsValue = CSS.ms(this).unsafeCast<CSSmsValue>() |
||||||
|
|
||||||
|
val Number.Hz |
||||||
|
get(): CSSHzValue = CSS.Hz(this).unsafeCast<CSSHzValue>() |
||||||
|
val Number.kHz |
||||||
|
get(): CSSkHzValue = CSS.kHz(this).unsafeCast<CSSkHzValue>() |
||||||
|
|
||||||
|
val Number.dpi |
||||||
|
get(): CSSdpiValue = CSS.dpi(this).unsafeCast<CSSdpiValue>() |
||||||
|
val Number.dpcm |
||||||
|
get(): CSSdpcmValue = CSS.dpcm(this).unsafeCast<CSSdpcmValue>() |
||||||
|
val Number.dppx |
||||||
|
get(): CSSdppxValue = CSS.dppx(this).unsafeCast<CSSdppxValue>() |
||||||
|
|
||||||
|
val Number.fr |
||||||
|
get(): CSSfrValue = CSS.fr(this).unsafeCast<CSSfrValue>() |
@ -0,0 +1,49 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
abstract class Color : CustomStyleValue { |
||||||
|
override fun styleValue(): StylePropertyValue = StylePropertyValue(toString()) |
||||||
|
|
||||||
|
data class Named(val value: String) : Color() { |
||||||
|
override fun toString(): String = value |
||||||
|
} |
||||||
|
|
||||||
|
data class RGB(val r: Number, val g: Number, val b: Number) : Color() { |
||||||
|
override fun toString(): String = "rgb($r, $g, $b)" |
||||||
|
} |
||||||
|
|
||||||
|
data class RGBA(val r: Number, val g: Number, val b: Number, val a: Number) : Color() { |
||||||
|
override fun toString(): String = "rgba($r, $g, $b, $a)" |
||||||
|
} |
||||||
|
|
||||||
|
data class HSL(val h: CSSangleValue, val s: Number, val l: Number) : Color() { |
||||||
|
constructor(h: Number, s: Number, l: Number) : this(h.deg, s, l) |
||||||
|
|
||||||
|
override fun toString(): String = "hsl($h, $s%, $l%)" |
||||||
|
} |
||||||
|
|
||||||
|
data class HSLA(val h: CSSangleValue, val s: Number, val l: Number, val a: Number) : Color() { |
||||||
|
constructor(h: Number, s: Number, l: Number, a: Number) : this(h.deg, s, l, a) |
||||||
|
|
||||||
|
override fun toString(): String = "hsla($h, $s%, $l%, $a)" |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
operator fun invoke(name: String) = Named(name) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import kotlin.properties.ReadOnlyProperty |
||||||
|
|
||||||
|
interface StyleBuilder { |
||||||
|
fun property(propertyName: String, value: StylePropertyValue) |
||||||
|
fun variable(variableName: String, value: StylePropertyValue) |
||||||
|
|
||||||
|
operator fun <TValue> CSSStyleVariable<TValue>.invoke(value: TValue) { |
||||||
|
variable(this.name, (value as? CustomStyleValue)?.styleValue() ?: value(value.toString())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") |
||||||
|
inline fun StyleBuilder.value(value: String) = StylePropertyValue(value) |
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") |
||||||
|
inline fun StyleBuilder.value(value: Number) = StylePropertyValue(value) |
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE") |
||||||
|
inline fun StyleBuilder.value(value: CSSStyleValue) = StylePropertyValue(value) |
||||||
|
|
||||||
|
fun variableValue(variableName: String, fallback: StylePropertyValue? = null) = |
||||||
|
StylePropertyValue("var(--$variableName${fallback?.let { ", $it" } ?: ""})") |
||||||
|
|
||||||
|
interface CSSVariableValue<TValue> : StylePropertyValue { |
||||||
|
companion object { |
||||||
|
operator fun <TValue> invoke(value: String) = |
||||||
|
StylePropertyValue(value).unsafeCast<CSSVariableValue<TValue>>() |
||||||
|
operator fun <TValue> invoke(value: Number) = |
||||||
|
StylePropertyValue(value).unsafeCast<CSSVariableValue<TValue>>() |
||||||
|
operator fun <TValue : CSSStyleValue> invoke(value: TValue) = |
||||||
|
StylePropertyValue(value).unsafeCast<CSSVariableValue<TValue>>() |
||||||
|
|
||||||
|
operator fun <TValue> invoke(value: StylePropertyValue) = |
||||||
|
value.unsafeCast<CSSVariableValue<TValue>>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// after adding `variable` word `add` became ambiguous |
||||||
|
@Deprecated( |
||||||
|
"use property instead, will remove it soon", |
||||||
|
ReplaceWith("property(propertyName, value)") |
||||||
|
) |
||||||
|
fun StyleBuilder.add( |
||||||
|
propertyName: String, |
||||||
|
value: StylePropertyValue |
||||||
|
) = property(propertyName, value) |
||||||
|
|
||||||
|
interface CSSVariables |
||||||
|
|
||||||
|
interface CSSVariable { |
||||||
|
val name: String |
||||||
|
} |
||||||
|
|
||||||
|
interface CustomStyleValue { |
||||||
|
fun styleValue(): StylePropertyValue |
||||||
|
} |
||||||
|
|
||||||
|
data class CSSStyleVariable<TValue>(override val name: String) : CSSVariable |
||||||
|
|
||||||
|
fun <TValue> CSSStyleVariable<TValue>.value(fallback: TValue? = null) = |
||||||
|
CSSVariableValue<TValue>( |
||||||
|
variableValue( |
||||||
|
name, |
||||||
|
fallback?.let { |
||||||
|
(fallback as? CustomStyleValue)?.styleValue() |
||||||
|
?: StylePropertyValue(fallback.toString()) |
||||||
|
} |
||||||
|
) |
||||||
|
) |
||||||
|
|
||||||
|
fun <TValue> CSSVariables.variable() = |
||||||
|
ReadOnlyProperty<Any?, CSSStyleVariable<TValue>> { _, property -> |
||||||
|
CSSStyleVariable(property.name) |
||||||
|
} |
||||||
|
|
||||||
|
interface StyleHolder { |
||||||
|
val properties: StylePropertyList |
||||||
|
val variables: StylePropertyList |
||||||
|
} |
||||||
|
|
||||||
|
open class StyleBuilderImpl : StyleBuilder, StyleHolder { |
||||||
|
override val properties: MutableStylePropertyList = mutableListOf() |
||||||
|
override val variables: MutableStylePropertyList = mutableListOf() |
||||||
|
|
||||||
|
override fun property(propertyName: String, value: StylePropertyValue) { |
||||||
|
properties.add(StylePropertyDeclaration(propertyName, value)) |
||||||
|
} |
||||||
|
|
||||||
|
override fun variable(variableName: String, value: StylePropertyValue) { |
||||||
|
variables.add(StylePropertyDeclaration(variableName, value)) |
||||||
|
} |
||||||
|
|
||||||
|
// StylePropertyValue is js native object without equals |
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return if (other is StyleHolder) { |
||||||
|
properties.nativeEquals(other.properties) && |
||||||
|
variables.nativeEquals(other.variables) |
||||||
|
} else false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
data class StylePropertyDeclaration( |
||||||
|
val name: String, |
||||||
|
val value: StylePropertyValue |
||||||
|
) |
||||||
|
typealias StylePropertyList = List<StylePropertyDeclaration> |
||||||
|
typealias MutableStylePropertyList = MutableList<StylePropertyDeclaration> |
||||||
|
|
||||||
|
fun StylePropertyList.nativeEquals(properties: StylePropertyList): Boolean { |
||||||
|
if (this.size != properties.size) return false |
||||||
|
|
||||||
|
var index = 0 |
||||||
|
return all { prop -> |
||||||
|
val otherProp = properties[index++] |
||||||
|
prop.name == otherProp.name && |
||||||
|
prop.value.toString() == otherProp.value.toString() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import androidx.compose.web.css.selectors.CSSSelector |
||||||
|
import androidx.compose.web.css.selectors.className |
||||||
|
import androidx.compose.web.elements.Style |
||||||
|
import kotlin.properties.ReadOnlyProperty |
||||||
|
import kotlin.reflect.KProperty |
||||||
|
|
||||||
|
class CSSRulesHolderState : CSSRulesHolder { |
||||||
|
override var cssRules: CSSRuleDeclarationList by mutableStateOf(listOf()) |
||||||
|
|
||||||
|
override fun add(cssRule: CSSRuleDeclaration) { |
||||||
|
cssRules += cssRule |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Represents a collection of the css style rules. |
||||||
|
* StyleSheet needs to be mounted. |
||||||
|
* @see [Style] |
||||||
|
* |
||||||
|
* Example: |
||||||
|
* ``` |
||||||
|
* object AppStylesheet : StyleSheet() { |
||||||
|
* val containerClass by style { |
||||||
|
* padding(24.px) |
||||||
|
* } |
||||||
|
* } |
||||||
|
* ``` |
||||||
|
* |
||||||
|
* Usage Example: |
||||||
|
* ``` |
||||||
|
* Style(AppStylesheet) // this mounts the stylesheet |
||||||
|
* Div(classes = listOf(AppStylesheet.containerClass),...) |
||||||
|
* ``` |
||||||
|
*/ |
||||||
|
open class StyleSheet( |
||||||
|
private val rulesHolder: CSSRulesHolder = CSSRulesHolderState() |
||||||
|
) : StyleSheetBuilder, CSSRulesHolder by rulesHolder { |
||||||
|
private val boundClasses = mutableMapOf<String, CSSRuleDeclarationList>() |
||||||
|
|
||||||
|
protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(cssRule) |
||||||
|
|
||||||
|
companion object { |
||||||
|
var counter = 0 |
||||||
|
} |
||||||
|
|
||||||
|
data class CSSSelfSelector(var selector: CSSSelector? = null) : CSSSelector() { |
||||||
|
override fun toString(): String = selector.toString() |
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return other is CSSSelfSelector |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: just proof of concept, do not use it |
||||||
|
fun css(cssBuild: CSSBuilder.() -> Unit): String { |
||||||
|
val selfSelector = CSSSelfSelector() |
||||||
|
val (style, newCssRules) = buildCSS(selfSelector, selfSelector, cssBuild) |
||||||
|
val cssRule = cssRules.find { |
||||||
|
it is CSSStyleRuleDeclaration && |
||||||
|
it.selector is CSSSelector.CSSClass && it.style == style && |
||||||
|
(boundClasses[it.selector.className] ?: emptyList()) == newCssRules |
||||||
|
}.unsafeCast<CSSStyleRuleDeclaration?>() |
||||||
|
return if (cssRule != null) { |
||||||
|
cssRule.selector.unsafeCast<CSSSelector.CSSClass>().className |
||||||
|
} else { |
||||||
|
val classNameSelector = className("auto-${counter++}") |
||||||
|
selfSelector.selector = classNameSelector |
||||||
|
add(classNameSelector, style) |
||||||
|
newCssRules.forEach { add(it) } |
||||||
|
boundClasses[classNameSelector.className] = newCssRules |
||||||
|
classNameSelector.className |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected class CSSHolder(val cssBuilder: CSSBuilder.() -> Unit) { |
||||||
|
operator fun provideDelegate( |
||||||
|
sheet: StyleSheet, |
||||||
|
property: KProperty<*> |
||||||
|
): ReadOnlyProperty<Any?, String> { |
||||||
|
val sheetName = "${sheet::class.simpleName}-" |
||||||
|
val selector = className("$sheetName${property.name}") |
||||||
|
val (properties, rules) = buildCSS(selector, selector, cssBuilder) |
||||||
|
sheet.add(selector, properties) |
||||||
|
rules.forEach { sheet.add(it) } |
||||||
|
|
||||||
|
return ReadOnlyProperty { _, _ -> |
||||||
|
selector.className |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSStyleRuleBuilder>.() -> Unit) = |
||||||
|
StyleSheet().apply(rulesBuild).cssRules |
||||||
|
} |
||||||
|
|
||||||
|
fun buildCSS( |
||||||
|
thisClass: CSSSelector, |
||||||
|
thisContext: CSSSelector, |
||||||
|
cssRule: CSSBuilder.() -> Unit |
||||||
|
): Pair<StyleHolder, CSSRuleDeclarationList> { |
||||||
|
val styleSheet = StyleSheetBuilderImpl() |
||||||
|
val builder = CSSBuilderImpl(thisClass, thisContext, styleSheet) |
||||||
|
builder.cssRule() |
||||||
|
return builder to styleSheet.cssRules |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Style( |
||||||
|
styleSheet: CSSRulesHolder |
||||||
|
) { |
||||||
|
Style(cssRules = styleSheet.cssRules) |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css |
||||||
|
|
||||||
|
import androidx.compose.web.css.selectors.CSSSelector |
||||||
|
|
||||||
|
interface CSSRulesHolder { |
||||||
|
val cssRules: CSSRuleDeclarationList |
||||||
|
fun add(cssRule: CSSRuleDeclaration) |
||||||
|
fun add(selector: CSSSelector, style: StyleHolder) { |
||||||
|
add(CSSStyleRuleDeclaration(selector, style)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface GenericStyleSheetBuilder<TBuilder> : CSSRulesHolder { |
||||||
|
fun buildRules( |
||||||
|
rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit |
||||||
|
): CSSRuleDeclarationList |
||||||
|
|
||||||
|
fun style(selector: CSSSelector, cssRule: TBuilder.() -> Unit) |
||||||
|
|
||||||
|
operator fun CSSSelector.invoke(cssRule: TBuilder.() -> Unit) { |
||||||
|
style(this, cssRule) |
||||||
|
} |
||||||
|
|
||||||
|
infix fun CSSSelector.style(cssRule: TBuilder.() -> Unit) { |
||||||
|
style(this, cssRule) |
||||||
|
} |
||||||
|
|
||||||
|
operator fun String.invoke(cssRule: TBuilder.() -> Unit) { |
||||||
|
style(CSSSelector.Raw(this), cssRule) |
||||||
|
} |
||||||
|
|
||||||
|
infix fun String.style(cssRule: TBuilder.() -> Unit) { |
||||||
|
style(CSSSelector.Raw(this), cssRule) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface StyleSheetBuilder : CSSRulesHolder, GenericStyleSheetBuilder<CSSStyleRuleBuilder> { |
||||||
|
override fun style(selector: CSSSelector, cssRule: CSSStyleRuleBuilder.() -> Unit) { |
||||||
|
add(selector, buildCSSStyleRule(cssRule)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
open class StyleSheetBuilderImpl : StyleSheetBuilder { |
||||||
|
override val cssRules: MutableCSSRuleDeclarationList = mutableListOf() |
||||||
|
|
||||||
|
override fun add(cssRule: CSSRuleDeclaration) { |
||||||
|
cssRules.add(cssRule) |
||||||
|
} |
||||||
|
|
||||||
|
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSStyleRuleBuilder>.() -> Unit) = |
||||||
|
StyleSheetBuilderImpl().apply(rulesBuild).cssRules |
||||||
|
} |
@ -0,0 +1,257 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.css.selectors |
||||||
|
|
||||||
|
import androidx.compose.web.css.LanguageCode |
||||||
|
|
||||||
|
sealed class Nth { |
||||||
|
data class Functional(val a: Int? = null, val b: Int? = null) { |
||||||
|
override fun toString(): String = when { |
||||||
|
a != null && b != null -> "${a}n+$b" |
||||||
|
a != null -> "${a}n" |
||||||
|
b != null -> "$b" |
||||||
|
else -> "" |
||||||
|
} |
||||||
|
} |
||||||
|
object Odd : Nth() { |
||||||
|
override fun toString(): String = "odd" |
||||||
|
} |
||||||
|
object Even : Nth() { |
||||||
|
override fun toString(): String = "even" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
open class CSSSelector { |
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return toString() == other.toString() |
||||||
|
} |
||||||
|
|
||||||
|
data class Raw(val selector: String) : CSSSelector() { |
||||||
|
override fun toString(): String = selector |
||||||
|
} |
||||||
|
|
||||||
|
object Universal : CSSSelector() { |
||||||
|
override fun toString(): String = "*" |
||||||
|
} |
||||||
|
|
||||||
|
data class Type(val type: String) : CSSSelector() { |
||||||
|
override fun toString(): String = type |
||||||
|
} |
||||||
|
|
||||||
|
data class CSSClass(val className: String) : CSSSelector() { |
||||||
|
override fun toString(): String = ".$className" |
||||||
|
} |
||||||
|
|
||||||
|
data class Id(val id: String) : CSSSelector() { |
||||||
|
override fun toString(): String = "#$id" |
||||||
|
} |
||||||
|
|
||||||
|
data class Attribute( |
||||||
|
val name: String, |
||||||
|
val value: String? = null, |
||||||
|
val operator: Operator = Operator.Equals, |
||||||
|
val caseSensitive: Boolean = true |
||||||
|
) : CSSSelector() { |
||||||
|
enum class Operator(val value: String) { |
||||||
|
Equals("="), |
||||||
|
ListContains("~="), |
||||||
|
Hyphened("|="), |
||||||
|
Prefixed("^="), |
||||||
|
Suffixed("$="), |
||||||
|
Contains("*=") |
||||||
|
} |
||||||
|
override fun toString(): String { |
||||||
|
val valueStr = value?.let { |
||||||
|
"${operator.value}$value${if (!caseSensitive) " i" else ""}" |
||||||
|
} ?: "" |
||||||
|
return "[$name$valueStr]" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
data class Combine(val selectors: MutableList<CSSSelector>) : CSSSelector() { |
||||||
|
override fun toString(): String = selectors.joinToString("") |
||||||
|
} |
||||||
|
|
||||||
|
data class Group(val selectors: List<CSSSelector>) : CSSSelector() { |
||||||
|
override fun toString(): String = selectors.joinToString(", ") |
||||||
|
} |
||||||
|
|
||||||
|
data class Descendant(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() { |
||||||
|
override fun toString(): String = "$parent $selected" |
||||||
|
} |
||||||
|
|
||||||
|
data class Child(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() { |
||||||
|
override fun toString(): String = "$parent > $selected" |
||||||
|
} |
||||||
|
|
||||||
|
data class Sibling(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() { |
||||||
|
override fun toString(): String = "$prev ~ $selected" |
||||||
|
} |
||||||
|
|
||||||
|
data class Adjacent(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() { |
||||||
|
override fun toString(): String = "$prev + $selected" |
||||||
|
} |
||||||
|
|
||||||
|
open class PseudoClass(val name: String) : CSSSelector() { |
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return if (other is PseudoClass) { |
||||||
|
name == other.name && argsStr() == other.argsStr() |
||||||
|
} else false |
||||||
|
} |
||||||
|
open fun argsStr(): String? = null |
||||||
|
override fun toString(): String = ":$name${argsStr()?.let { "($it)" } ?: ""}" |
||||||
|
|
||||||
|
companion object { |
||||||
|
// Location pseudo-classes |
||||||
|
val anyLink = PseudoClass("any-link") |
||||||
|
val link = PseudoClass("link") |
||||||
|
val visited = PseudoClass("visited") |
||||||
|
val localLink = PseudoClass("local-link") |
||||||
|
val target = PseudoClass("target") |
||||||
|
val targetWithin = PseudoClass("target-within") |
||||||
|
val scope = PseudoClass("scope") |
||||||
|
|
||||||
|
// User action pseudo-classes |
||||||
|
val hover = PseudoClass("hover") |
||||||
|
val active = PseudoClass("active") |
||||||
|
val focus = PseudoClass("focus") |
||||||
|
val focusVisible = PseudoClass("focus-visible") |
||||||
|
|
||||||
|
// Resource state pseudo-classes |
||||||
|
val playing = PseudoClass("playing") |
||||||
|
val paused = PseudoClass("paused") |
||||||
|
|
||||||
|
// The input pseudo-classes |
||||||
|
val autofill = PseudoClass("autofill") |
||||||
|
val enabled = PseudoClass("enabled") |
||||||
|
val disabled = PseudoClass("disabled") |
||||||
|
val readOnly = PseudoClass("read-only") |
||||||
|
val readWrite = PseudoClass("read-write") |
||||||
|
val placeholderShown = PseudoClass("placeholder-shown") |
||||||
|
val default = PseudoClass("default") |
||||||
|
val checked = PseudoClass("checked") |
||||||
|
val indeterminate = PseudoClass("indeterminate") |
||||||
|
val blank = PseudoClass("blank") |
||||||
|
val valid = PseudoClass("valid") |
||||||
|
val invalid = PseudoClass("invalid") |
||||||
|
val inRange = PseudoClass("in-range") |
||||||
|
val outOfRange = PseudoClass("out-of-range") |
||||||
|
val required = PseudoClass("required") |
||||||
|
val optional = PseudoClass("optional") |
||||||
|
val userInvalid = PseudoClass("user-invalid") |
||||||
|
|
||||||
|
// Tree-structural pseudo-classes |
||||||
|
val root = PseudoClass("root") |
||||||
|
val empty = PseudoClass("empty") |
||||||
|
val first = PseudoClass("first") |
||||||
|
val firstChild = PseudoClass("first-child") |
||||||
|
val lastChild = PseudoClass("last-child") |
||||||
|
val onlyChild = PseudoClass("only-child") |
||||||
|
val firstOfType = PseudoClass("first-of-type") |
||||||
|
val lastOfType = PseudoClass("last-of-type") |
||||||
|
val onlyOfType = PseudoClass("only-of-type") |
||||||
|
val host = PseudoClass("host") |
||||||
|
|
||||||
|
// Etc |
||||||
|
val defined = PseudoClass("defined") |
||||||
|
val left = PseudoClass("left") |
||||||
|
val right = PseudoClass("right") |
||||||
|
} |
||||||
|
|
||||||
|
// Linguistic pseudo-classes |
||||||
|
class Lang(val langCode: LanguageCode) : PseudoClass("lang") { |
||||||
|
override fun argsStr() = langCode |
||||||
|
} |
||||||
|
|
||||||
|
// Tree-structural pseudo-classes |
||||||
|
class NthChild(val nth: Nth) : PseudoClass("nth-child") { |
||||||
|
override fun argsStr() = "$nth" |
||||||
|
} |
||||||
|
class NthLastChild(val nth: Nth) : PseudoClass("nth-last-child") { |
||||||
|
override fun argsStr() = "$nth" |
||||||
|
} |
||||||
|
class NthOfType(val nth: Nth) : PseudoClass("nth-of-type") { |
||||||
|
override fun argsStr() = "$nth" |
||||||
|
} |
||||||
|
class NthLastOfType(val nth: Nth) : PseudoClass("nth-last-of-type") { |
||||||
|
override fun argsStr() = "$nth" |
||||||
|
} |
||||||
|
class Host(val selector: CSSSelector) : PseudoClass("host") { |
||||||
|
override fun argsStr() = "$selector" |
||||||
|
} |
||||||
|
|
||||||
|
// Etc |
||||||
|
class Not(val selector: CSSSelector) : PseudoClass("not") { |
||||||
|
override fun argsStr() = "$selector" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
open class PseudoElement(val name: String) : CSSSelector() { |
||||||
|
override fun equals(other: Any?): Boolean { |
||||||
|
return if (other is PseudoElement) { |
||||||
|
name == other.name && argsStr() == other.argsStr() |
||||||
|
} else false |
||||||
|
} |
||||||
|
open fun argsStr(): String? = null |
||||||
|
override fun toString(): String = "::$name${argsStr()?.let { "($it)" } ?: ""}" |
||||||
|
|
||||||
|
companion object { |
||||||
|
val after = PseudoElement("after") |
||||||
|
val before = PseudoElement("before") |
||||||
|
val cue = PseudoElement("cue") |
||||||
|
val cueRegion = PseudoElement("cue-region") |
||||||
|
val firstLetter = PseudoElement("first-letter") |
||||||
|
val firstLine = PseudoElement("first-line") |
||||||
|
val fileSelectorButton = PseudoElement("file-selector-button") |
||||||
|
val selection = PseudoElement("selection") |
||||||
|
} |
||||||
|
|
||||||
|
class Slotted(val selector: CSSSelector) : PseudoElement("slotted") { |
||||||
|
override fun argsStr() = selector.toString() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun selector(selector: String) = CSSSelector.Raw(selector) |
||||||
|
fun combine(vararg selectors: CSSSelector) = CSSSelector.Combine(selectors.toMutableList()) |
||||||
|
operator fun CSSSelector.plus(selector: CSSSelector) = combine(this, selector) |
||||||
|
operator fun String.plus(selector: CSSSelector) = combine(selector(this), selector) |
||||||
|
operator fun CSSSelector.plus(selector: String) = combine(this, selector(selector)) |
||||||
|
operator fun CSSSelector.Combine.plus(selector: CSSSelector) { this.selectors.add(selector) } |
||||||
|
operator fun CSSSelector.Combine.plus(selector: String) { this.selectors.add(selector(selector)) } |
||||||
|
|
||||||
|
fun universal() = CSSSelector.Universal |
||||||
|
fun type(type: String) = CSSSelector.Type(type) |
||||||
|
fun className(className: String) = CSSSelector.CSSClass(className) |
||||||
|
fun id(id: String) = CSSSelector.Id(id) |
||||||
|
fun attr( |
||||||
|
name: String, |
||||||
|
value: String? = null, |
||||||
|
operator: CSSSelector.Attribute.Operator = CSSSelector.Attribute.Operator.Equals, |
||||||
|
caseSensitive: Boolean = true |
||||||
|
) = CSSSelector.Attribute(name, value, operator, caseSensitive) |
||||||
|
fun group(vararg selectors: CSSSelector) = CSSSelector.Group(selectors.toList()) |
||||||
|
fun descendant(parent: CSSSelector, selected: CSSSelector) = |
||||||
|
CSSSelector.Descendant(parent, selected) |
||||||
|
fun child(parent: CSSSelector, selected: CSSSelector) = |
||||||
|
CSSSelector.Child(parent, selected) |
||||||
|
fun sibling(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Descendant(sibling, selected) |
||||||
|
fun adjacent(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Adjacent(sibling, selected) |
||||||
|
|
||||||
|
fun not(selector: CSSSelector) = CSSSelector.PseudoClass.Not(selector) |
||||||
|
fun hover() = CSSSelector.PseudoClass.hover |
||||||
|
fun hover(selector: CSSSelector) = selector + hover() |
@ -0,0 +1,111 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.elements |
||||||
|
|
||||||
|
import androidx.compose.runtime.Applier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.ComposeCompilerApi |
||||||
|
import androidx.compose.runtime.DisposableEffect |
||||||
|
import androidx.compose.runtime.DisposableEffectResult |
||||||
|
import androidx.compose.runtime.DisposableEffectScope |
||||||
|
import androidx.compose.runtime.ExplicitGroupsComposable |
||||||
|
import androidx.compose.runtime.SkippableUpdater |
||||||
|
import androidx.compose.runtime.currentComposer |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.web.DomApplier |
||||||
|
import androidx.compose.web.DomNodeWrapper |
||||||
|
import androidx.compose.web.attributes.AttrsBuilder |
||||||
|
import androidx.compose.web.attributes.Tag |
||||||
|
import androidx.compose.web.css.StyleBuilder |
||||||
|
import androidx.compose.web.css.StyleBuilderImpl |
||||||
|
import kotlinx.browser.document |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
|
||||||
|
@OptIn(ComposeCompilerApi::class) |
||||||
|
@Composable |
||||||
|
@ExplicitGroupsComposable |
||||||
|
inline fun <TScope, T, reified E : Applier<*>> ComposeDomNode( |
||||||
|
noinline factory: () -> T, |
||||||
|
elementScope: TScope, |
||||||
|
noinline attrsSkippableUpdate: @Composable SkippableUpdater<T>.() -> Unit, |
||||||
|
noinline styleSkippableUpdate: @Composable SkippableUpdater<T>.() -> Unit, |
||||||
|
content: @Composable TScope.() -> Unit |
||||||
|
) { |
||||||
|
if (currentComposer.applier !is E) error("Invalid applier") |
||||||
|
currentComposer.startNode() |
||||||
|
if (currentComposer.inserting) { |
||||||
|
currentComposer.createNode(factory) |
||||||
|
} else { |
||||||
|
currentComposer.useNode() |
||||||
|
} |
||||||
|
// Updater<T>(currentComposer).update() |
||||||
|
SkippableUpdater<T>(currentComposer).apply { |
||||||
|
attrsSkippableUpdate() |
||||||
|
styleSkippableUpdate() |
||||||
|
} |
||||||
|
currentComposer.startReplaceableGroup(0x7ab4aae9) |
||||||
|
content(elementScope) |
||||||
|
currentComposer.endReplaceableGroup() |
||||||
|
currentComposer.endNode() |
||||||
|
} |
||||||
|
|
||||||
|
class DisposableEffectHolder( |
||||||
|
var effect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun <TTag : Tag, THTMLElement : HTMLElement> TagElement( |
||||||
|
tagName: String, |
||||||
|
crossinline applyAttrs: AttrsBuilder<TTag>.() -> Unit, |
||||||
|
crossinline applyStyle: StyleBuilder.() -> Unit, |
||||||
|
content: @Composable ElementScope<THTMLElement>.() -> Unit |
||||||
|
) { |
||||||
|
val scope = remember { ElementScopeImpl<THTMLElement>() } |
||||||
|
val refEffect = remember { DisposableEffectHolder() } |
||||||
|
|
||||||
|
ComposeDomNode<ElementScope<THTMLElement>, DomNodeWrapper, DomApplier>( |
||||||
|
factory = { |
||||||
|
DomNodeWrapper(document.createElement(tagName)).also { |
||||||
|
scope.element = it.node.unsafeCast<THTMLElement>() |
||||||
|
} |
||||||
|
}, |
||||||
|
attrsSkippableUpdate = { |
||||||
|
val attrsApplied = AttrsBuilder<TTag>().also { it.applyAttrs() } |
||||||
|
refEffect.effect = attrsApplied.refEffect |
||||||
|
val attrsCollected = attrsApplied.collect() |
||||||
|
val events = attrsApplied.asList() |
||||||
|
|
||||||
|
update { |
||||||
|
set(attrsCollected, DomNodeWrapper.UpdateAttrs) |
||||||
|
set(events, DomNodeWrapper.UpdateListeners) |
||||||
|
set(attrsApplied.propertyUpdates, DomNodeWrapper.UpdateProperties) |
||||||
|
} |
||||||
|
}, |
||||||
|
styleSkippableUpdate = { |
||||||
|
val style = StyleBuilderImpl().apply(applyStyle) |
||||||
|
update { |
||||||
|
set(style, DomNodeWrapper.UpdateStyleDeclarations) |
||||||
|
} |
||||||
|
}, |
||||||
|
elementScope = scope, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
DisposableEffect(null) { |
||||||
|
refEffect.effect?.invoke(this, scope.element) ?: onDispose {} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.elements |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.ComposeCompilerApi |
||||||
|
import androidx.compose.runtime.DisposableEffect |
||||||
|
import androidx.compose.runtime.DisposableEffectResult |
||||||
|
import androidx.compose.runtime.DisposableEffectScope |
||||||
|
import androidx.compose.runtime.NonRestartableComposable |
||||||
|
import androidx.compose.runtime.RememberObserver |
||||||
|
import androidx.compose.runtime.SideEffect |
||||||
|
import androidx.compose.runtime.currentComposer |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
|
||||||
|
interface DOMScope<out THTMLElement : HTMLElement> |
||||||
|
|
||||||
|
interface ElementScope<out THTMLElement : HTMLElement> : DOMScope<THTMLElement> { |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun DisposableRefEffect( |
||||||
|
key: Any?, |
||||||
|
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun DisposableRefEffect( |
||||||
|
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult |
||||||
|
) { |
||||||
|
DisposableRefEffect(null, effect) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun DomSideEffect(key: Any?, effect: DomEffectScope.(THTMLElement) -> Unit) |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun DomSideEffect(effect: DomEffectScope.(THTMLElement) -> Unit) |
||||||
|
} |
||||||
|
|
||||||
|
abstract class ElementScopeBase<out THTMLElement : HTMLElement> : ElementScope<THTMLElement> { |
||||||
|
abstract val element: THTMLElement |
||||||
|
|
||||||
|
private var nextDisposableDomEffectKey = 0 |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
override fun DisposableRefEffect( |
||||||
|
key: Any?, |
||||||
|
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult |
||||||
|
) { |
||||||
|
DisposableEffect(key) { effect(element) } |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
@OptIn(ComposeCompilerApi::class) |
||||||
|
override fun DomSideEffect( |
||||||
|
key: Any?, |
||||||
|
effect: DomEffectScope.(THTMLElement) -> Unit |
||||||
|
) { |
||||||
|
val changed = currentComposer.changed(key) |
||||||
|
val effectHolder = remember(key) { |
||||||
|
DomDisposableEffectHolder(this) |
||||||
|
} |
||||||
|
SideEffect { |
||||||
|
if (changed) effectHolder.effect(element) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
override fun DomSideEffect(effect: DomEffectScope.(THTMLElement) -> Unit) { |
||||||
|
DomSideEffect(nextDisposableDomEffectKey++, effect) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
open class ElementScopeImpl<THTMLElement : HTMLElement> : ElementScopeBase<THTMLElement>() { |
||||||
|
public override lateinit var element: THTMLElement |
||||||
|
} |
||||||
|
|
||||||
|
interface DomEffectScope { |
||||||
|
fun onDispose(disposeEffect: (HTMLElement) -> Unit) |
||||||
|
} |
||||||
|
|
||||||
|
private class DomDisposableEffectHolder( |
||||||
|
val elementScope: ElementScopeBase<HTMLElement> |
||||||
|
) : RememberObserver, DomEffectScope { |
||||||
|
var onDispose: ((HTMLElement) -> Unit)? = null |
||||||
|
|
||||||
|
override fun onRemembered() {} |
||||||
|
|
||||||
|
override fun onForgotten() { |
||||||
|
onDispose?.invoke(elementScope.element) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAbandoned() {} |
||||||
|
|
||||||
|
override fun onDispose(disposeEffect: (HTMLElement) -> Unit) { |
||||||
|
onDispose = disposeEffect |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,577 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.elements |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.ComposeNode |
||||||
|
import androidx.compose.web.DomApplier |
||||||
|
import androidx.compose.web.DomNodeWrapper |
||||||
|
import androidx.compose.web.attributes.AttrsBuilder |
||||||
|
import androidx.compose.web.attributes.InputType |
||||||
|
import androidx.compose.web.attributes.Tag |
||||||
|
import androidx.compose.web.attributes.action |
||||||
|
import androidx.compose.web.attributes.alt |
||||||
|
import androidx.compose.web.attributes.forId |
||||||
|
import androidx.compose.web.attributes.href |
||||||
|
import androidx.compose.web.attributes.label |
||||||
|
import androidx.compose.web.attributes.src |
||||||
|
import androidx.compose.web.attributes.type |
||||||
|
import androidx.compose.web.attributes.value |
||||||
|
import androidx.compose.web.css.StyleBuilder |
||||||
|
import kotlinx.browser.document |
||||||
|
import org.w3c.dom.HTMLAnchorElement |
||||||
|
import org.w3c.dom.HTMLBRElement |
||||||
|
import org.w3c.dom.HTMLDivElement |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.HTMLFormElement |
||||||
|
import org.w3c.dom.HTMLHeadingElement |
||||||
|
import org.w3c.dom.HTMLImageElement |
||||||
|
import org.w3c.dom.HTMLInputElement |
||||||
|
import org.w3c.dom.HTMLLIElement |
||||||
|
import org.w3c.dom.HTMLOListElement |
||||||
|
import org.w3c.dom.HTMLOptGroupElement |
||||||
|
import org.w3c.dom.HTMLOptionElement |
||||||
|
import org.w3c.dom.HTMLParagraphElement |
||||||
|
import org.w3c.dom.HTMLPreElement |
||||||
|
import org.w3c.dom.HTMLSelectElement |
||||||
|
import org.w3c.dom.HTMLSpanElement |
||||||
|
import org.w3c.dom.HTMLTableCaptionElement |
||||||
|
import org.w3c.dom.HTMLTableCellElement |
||||||
|
import org.w3c.dom.HTMLTableColElement |
||||||
|
import org.w3c.dom.HTMLTableElement |
||||||
|
import org.w3c.dom.HTMLTableRowElement |
||||||
|
import org.w3c.dom.HTMLTableSectionElement |
||||||
|
import org.w3c.dom.HTMLTextAreaElement |
||||||
|
import org.w3c.dom.HTMLUListElement |
||||||
|
import org.w3c.dom.Text |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Text(value: String) { |
||||||
|
ComposeNode<DomNodeWrapper, DomApplier>( |
||||||
|
factory = { DomNodeWrapper(document.createTextNode("")) }, |
||||||
|
update = { |
||||||
|
set(value) { value -> (node as Text).data = value } |
||||||
|
}, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Div( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Div>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLDivElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "div", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun A( |
||||||
|
href: String? = null, |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.A>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLAnchorElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement<Tag.A, HTMLAnchorElement>( |
||||||
|
tagName = "a", |
||||||
|
applyAttrs = { |
||||||
|
href(href) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Input( |
||||||
|
type: InputType = InputType.Text, |
||||||
|
value: String = "", |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Input>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLInputElement>.() -> Unit = {} |
||||||
|
) { |
||||||
|
TagElement<Tag.Input, HTMLInputElement>( |
||||||
|
tagName = "input", |
||||||
|
applyAttrs = { |
||||||
|
type(type) |
||||||
|
value(value) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Button( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Button>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("button", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun H1( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("h1", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun H2( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("h2", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun H3( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("h3", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun H4( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("h4", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun H5( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("h5", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun H6( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit |
||||||
|
) = TagElement("h6", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun P( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.P>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLParagraphElement>.() -> Unit |
||||||
|
) = TagElement("p", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Em( |
||||||
|
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) = TagElement("em", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun I( |
||||||
|
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) = TagElement("i", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun B( |
||||||
|
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) = TagElement("b", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Small( |
||||||
|
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) = TagElement("small", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Span( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Span>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLSpanElement>.() -> Unit |
||||||
|
) = TagElement("span", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Br( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Br>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLBRElement>.() -> Unit |
||||||
|
) = TagElement("br", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Ul( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Ul>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLUListElement>.() -> Unit, |
||||||
|
) = TagElement("ul", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Ol( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Ol>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLOListElement>.() -> Unit |
||||||
|
) = TagElement("ol", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun DOMScope<HTMLOListElement>.Li( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Li>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLLIElement>.() -> Unit |
||||||
|
) = TagElement("li", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun DOMScope<HTMLUListElement>.Li( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Li>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLLIElement>.() -> Unit |
||||||
|
) = TagElement("li", applyAttrs = attrs, applyStyle = style, content = content) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Img( |
||||||
|
src: String, |
||||||
|
alt: String = "", |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Img>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLImageElement>.() -> Unit = {} |
||||||
|
) = TagElement<Tag.Img, HTMLImageElement>( |
||||||
|
tagName = "img", |
||||||
|
applyAttrs = { |
||||||
|
src(src).alt(alt) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Form( |
||||||
|
action: String? = null, |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Form>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLFormElement>.() -> Unit |
||||||
|
) = TagElement<Tag.Form, HTMLFormElement>( |
||||||
|
tagName = "form", |
||||||
|
applyAttrs = { |
||||||
|
if (!action.isNullOrEmpty()) action(action) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Select( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Select>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLSelectElement>.() -> Unit |
||||||
|
) = TagElement( |
||||||
|
tagName = "select", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun DOMScope<HTMLUListElement>.Option( |
||||||
|
value: String, |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Option>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLOptionElement>.() -> Unit |
||||||
|
) = TagElement<Tag.Option, HTMLOptionElement>( |
||||||
|
tagName = "option", |
||||||
|
applyAttrs = { |
||||||
|
value(value) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun OptGroup( |
||||||
|
label: String, |
||||||
|
crossinline attrs: AttrsBuilder<Tag.OptGroup>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLOptGroupElement>.() -> Unit |
||||||
|
) = TagElement<Tag.OptGroup, HTMLOptGroupElement>( |
||||||
|
tagName = "optgroup", |
||||||
|
applyAttrs = { |
||||||
|
label(label) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Section( |
||||||
|
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) = TagElement( |
||||||
|
tagName = "section", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun TextArea( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.TextArea>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
value: String |
||||||
|
) = TagElement<Tag.TextArea, HTMLTextAreaElement>( |
||||||
|
tagName = "textarea", |
||||||
|
applyAttrs = { |
||||||
|
value(value) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style |
||||||
|
) { |
||||||
|
Text(value) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Nav( |
||||||
|
crossinline attrs: AttrsBuilder<Tag.Nav>.() -> Unit = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) = TagElement( |
||||||
|
tagName = "nav", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Pre( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Pre>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLPreElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "pre", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Code( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Code>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "code", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Main( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Div>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit = {} |
||||||
|
) { |
||||||
|
TagElement<Tag.Div, HTMLAnchorElement>( |
||||||
|
tagName = "main", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Footer( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Div>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit = {} |
||||||
|
) { |
||||||
|
TagElement<Tag.Div, HTMLAnchorElement>( |
||||||
|
tagName = "footer", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Label( |
||||||
|
forId: String? = null, |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Label>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLElement>.() -> Unit = {} |
||||||
|
) { |
||||||
|
TagElement<Tag.Label, HTMLAnchorElement>( |
||||||
|
tagName = "label", |
||||||
|
applyAttrs = { |
||||||
|
forId(forId) |
||||||
|
attrs() |
||||||
|
}, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Table( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Table>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "table", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Caption( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Caption>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableCaptionElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "caption", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Col( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Col>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableColElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "col", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Colgroup( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Colgroup>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableColElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "colgroup", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Tr( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Tr>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableRowElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "tr", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Thead( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Thead>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableSectionElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "thead", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Th( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Th>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableCellElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "th", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Td( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Td>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableCellElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "td", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Tbody( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Tbody>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableSectionElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "tbody", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
inline fun Tfoot( |
||||||
|
crossinline attrs: (AttrsBuilder<Tag.Tfoot>.() -> Unit) = {}, |
||||||
|
crossinline style: (StyleBuilder.() -> Unit) = {}, |
||||||
|
content: @Composable ElementScope<HTMLTableSectionElement>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
tagName = "tfoot", |
||||||
|
applyAttrs = attrs, |
||||||
|
applyStyle = style, |
||||||
|
content = content |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.elements |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.web.attributes.AttrsBuilder |
||||||
|
import androidx.compose.web.attributes.Tag |
||||||
|
import androidx.compose.web.css.CSSGroupingRuleDeclaration |
||||||
|
import androidx.compose.web.css.CSSRuleDeclaration |
||||||
|
import androidx.compose.web.css.CSSRuleDeclarationList |
||||||
|
import androidx.compose.web.css.CSSStyleRuleDeclaration |
||||||
|
import androidx.compose.web.css.StylePropertyMap |
||||||
|
import androidx.compose.web.css.StylePropertyValue |
||||||
|
import androidx.compose.web.css.StyleSheetBuilder |
||||||
|
import androidx.compose.web.css.StyleSheetBuilderImpl |
||||||
|
import androidx.compose.web.css.cssRules |
||||||
|
import androidx.compose.web.css.deleteRule |
||||||
|
import androidx.compose.web.css.get |
||||||
|
import androidx.compose.web.css.insertRule |
||||||
|
import androidx.compose.web.css.styleMap |
||||||
|
import org.w3c.dom.HTMLStyleElement |
||||||
|
import org.w3c.dom.css.CSSGroupingRule |
||||||
|
import org.w3c.dom.css.CSSRule |
||||||
|
import org.w3c.dom.css.CSSStyleDeclaration |
||||||
|
import org.w3c.dom.css.CSSStyleRule |
||||||
|
import org.w3c.dom.css.StyleSheet |
||||||
|
|
||||||
|
/** |
||||||
|
* Use this function to mount the <style> tag into the DOM tree. |
||||||
|
* |
||||||
|
* @param rulesBuild allows to define the style rules using [StyleSheetBuilder] |
||||||
|
*/ |
||||||
|
@Composable |
||||||
|
inline fun Style( |
||||||
|
crossinline applyAttrs: AttrsBuilder<Tag.Style>.() -> Unit = {}, |
||||||
|
rulesBuild: StyleSheetBuilder.() -> Unit |
||||||
|
) { |
||||||
|
val builder = StyleSheetBuilderImpl() |
||||||
|
builder.rulesBuild() |
||||||
|
Style(applyAttrs, builder.cssRules) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Use this function to mount the <style> tag into the DOM tree. |
||||||
|
* |
||||||
|
* @param cssRules - is a list of style rules. |
||||||
|
* Usually, it's [androidx.compose.web.css.StyleSheet] instance |
||||||
|
*/ |
||||||
|
@Composable |
||||||
|
inline fun Style( |
||||||
|
crossinline applyAttrs: AttrsBuilder<Tag.Style>.() -> Unit = {}, |
||||||
|
cssRules: CSSRuleDeclarationList |
||||||
|
) { |
||||||
|
TagElement<Tag.Style, HTMLStyleElement>( |
||||||
|
tagName = "style", |
||||||
|
applyAttrs = { |
||||||
|
applyAttrs() |
||||||
|
}, |
||||||
|
applyStyle = {} |
||||||
|
) { |
||||||
|
DomSideEffect(cssRules) { style -> |
||||||
|
style.sheet?.let { sheet -> |
||||||
|
setCSSRules(sheet, cssRules) |
||||||
|
onDispose { |
||||||
|
clearCSSRules(sheet) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun clearCSSRules(sheet: StyleSheet) { |
||||||
|
repeat(sheet.cssRules.length) { |
||||||
|
sheet.deleteRule(0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setCSSRules(sheet: StyleSheet, cssRules: CSSRuleDeclarationList) { |
||||||
|
cssRules.forEach { cssRule -> |
||||||
|
sheet.addRule(cssRule) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun StyleSheet.addRule(cssRule: String): CSSRule { |
||||||
|
val cssRuleIndex = this.insertRule(cssRule, this.cssRules.length) |
||||||
|
return this.cssRules[cssRuleIndex] |
||||||
|
} |
||||||
|
|
||||||
|
private fun CSSGroupingRule.addRule(cssRule: String): CSSRule { |
||||||
|
val cssRuleIndex = this.insertRule(cssRule, this.cssRules.length) |
||||||
|
return this.cssRules[cssRuleIndex] |
||||||
|
} |
||||||
|
|
||||||
|
private fun StyleSheet.addRule(cssRuleDeclaration: CSSRuleDeclaration) { |
||||||
|
val cssRule = addRule("${cssRuleDeclaration.header} {}") |
||||||
|
fillRule(cssRuleDeclaration, cssRule) |
||||||
|
} |
||||||
|
|
||||||
|
private fun CSSGroupingRule.addRule(cssRuleDeclaration: CSSRuleDeclaration) { |
||||||
|
val cssRule = addRule("${cssRuleDeclaration.header} {}") |
||||||
|
fillRule(cssRuleDeclaration, cssRule) |
||||||
|
} |
||||||
|
|
||||||
|
private fun fillRule( |
||||||
|
cssRuleDeclaration: CSSRuleDeclaration, |
||||||
|
cssRule: CSSRule |
||||||
|
) { |
||||||
|
when (cssRuleDeclaration) { |
||||||
|
is CSSStyleRuleDeclaration -> { |
||||||
|
val cssStyleRule = cssRule.unsafeCast<CSSStyleRule>() |
||||||
|
cssRuleDeclaration.style.properties.forEach { (name, value) -> |
||||||
|
setProperty(cssStyleRule.styleMap, name, value) |
||||||
|
} |
||||||
|
cssRuleDeclaration.style.variables.forEach { (name, value) -> |
||||||
|
setVariable(cssStyleRule.style, name, value) |
||||||
|
} |
||||||
|
} |
||||||
|
is CSSGroupingRuleDeclaration -> { |
||||||
|
val cssGroupingRule = cssRule.unsafeCast<CSSGroupingRule>() |
||||||
|
cssRuleDeclaration.rules.forEach { childRuleDeclaration -> |
||||||
|
cssGroupingRule.addRule(childRuleDeclaration) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setProperty( |
||||||
|
styleMap: StylePropertyMap, |
||||||
|
name: String, |
||||||
|
value: StylePropertyValue |
||||||
|
) { |
||||||
|
styleMap.set(name, value) |
||||||
|
} |
||||||
|
|
||||||
|
fun setVariable( |
||||||
|
style: CSSStyleDeclaration, |
||||||
|
name: String, |
||||||
|
value: StylePropertyValue |
||||||
|
) { |
||||||
|
style.setProperty("--$name", value.toString()) |
||||||
|
} |
@ -0,0 +1,123 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package androidx.compose.web.events |
||||||
|
|
||||||
|
import org.w3c.dom.DragEvent |
||||||
|
import org.w3c.dom.TouchEvent |
||||||
|
import org.w3c.dom.clipboard.ClipboardEvent |
||||||
|
import org.w3c.dom.events.CompositionEvent |
||||||
|
import org.w3c.dom.events.Event |
||||||
|
import org.w3c.dom.events.FocusEvent |
||||||
|
import org.w3c.dom.events.InputEvent |
||||||
|
import org.w3c.dom.events.KeyboardEvent |
||||||
|
import org.w3c.dom.events.MouseEvent |
||||||
|
import org.w3c.dom.events.WheelEvent |
||||||
|
import org.w3c.dom.pointerevents.PointerEvent |
||||||
|
|
||||||
|
interface GenericWrappedEvent<T : Event> { |
||||||
|
val nativeEvent: T |
||||||
|
} |
||||||
|
|
||||||
|
interface WrappedEvent : GenericWrappedEvent<Event> |
||||||
|
|
||||||
|
open class WrappedMouseEvent( |
||||||
|
override val nativeEvent: MouseEvent |
||||||
|
) : GenericWrappedEvent<MouseEvent> { |
||||||
|
|
||||||
|
// MouseEvent doesn't support movementX and movementY on IE6-11, and it's OK for now. |
||||||
|
val movementX: Double |
||||||
|
get() = nativeEvent.asDynamic().movementX as Double |
||||||
|
val movementY: Double |
||||||
|
get() = nativeEvent.asDynamic().movementY as Double |
||||||
|
} |
||||||
|
|
||||||
|
open class WrappedWheelEvent( |
||||||
|
override val nativeEvent: WheelEvent |
||||||
|
) : GenericWrappedEvent<WheelEvent> |
||||||
|
|
||||||
|
open class WrappedInputEvent( |
||||||
|
override val nativeEvent: InputEvent |
||||||
|
) : GenericWrappedEvent<InputEvent> |
||||||
|
|
||||||
|
open class WrappedKeyboardEvent( |
||||||
|
override val nativeEvent: KeyboardEvent |
||||||
|
) : GenericWrappedEvent<KeyboardEvent> { |
||||||
|
|
||||||
|
fun getNormalizedKey(): String = nativeEvent.key.let { |
||||||
|
normalizedKeys[it] ?: it |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
private val normalizedKeys = mapOf( |
||||||
|
"Esc" to "Escape", |
||||||
|
"Spacebar" to " ", |
||||||
|
"Left" to "ArrowLeft", |
||||||
|
"Up" to "ArrowUp", |
||||||
|
"Right" to "ArrowRight", |
||||||
|
"Down" to "ArrowDown", |
||||||
|
"Del" to "Delete", |
||||||
|
"Apps" to "ContextMenu", |
||||||
|
"Menu" to "ContextMenu", |
||||||
|
"Scroll" to "ScrollLock", |
||||||
|
"MozPrintableKey" to "Unidentified", |
||||||
|
) |
||||||
|
// Firefox bug for Windows key https://bugzilla.mozilla.org/show_bug.cgi?id=1232918 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
open class WrappedFocusEvent( |
||||||
|
override val nativeEvent: FocusEvent |
||||||
|
) : GenericWrappedEvent<FocusEvent> |
||||||
|
|
||||||
|
open class WrappedTouchEvent( |
||||||
|
override val nativeEvent: TouchEvent |
||||||
|
) : GenericWrappedEvent<TouchEvent> |
||||||
|
|
||||||
|
open class WrappedCompositionEvent( |
||||||
|
override val nativeEvent: CompositionEvent |
||||||
|
) : GenericWrappedEvent<CompositionEvent> |
||||||
|
|
||||||
|
open class WrappedDragEvent( |
||||||
|
override val nativeEvent: DragEvent |
||||||
|
) : GenericWrappedEvent<DragEvent> |
||||||
|
|
||||||
|
open class WrappedPointerEvent( |
||||||
|
override val nativeEvent: PointerEvent |
||||||
|
) : GenericWrappedEvent<PointerEvent> |
||||||
|
|
||||||
|
open class WrappedClipboardEvent( |
||||||
|
override val nativeEvent: ClipboardEvent |
||||||
|
) : GenericWrappedEvent<ClipboardEvent> |
||||||
|
|
||||||
|
class WrappedTextInputEvent( |
||||||
|
nativeEvent: InputEvent, |
||||||
|
val inputValue: String |
||||||
|
) : WrappedInputEvent(nativeEvent) |
||||||
|
|
||||||
|
class WrappedCheckBoxInputEvent( |
||||||
|
override val nativeEvent: Event, |
||||||
|
val checked: Boolean |
||||||
|
) : GenericWrappedEvent<Event> |
||||||
|
|
||||||
|
class WrappedRadioInputEvent( |
||||||
|
override val nativeEvent: Event, |
||||||
|
val checked: Boolean |
||||||
|
) : GenericWrappedEvent<Event> |
||||||
|
|
||||||
|
class WrappedEventImpl( |
||||||
|
override val nativeEvent: Event |
||||||
|
) : WrappedEvent |
@ -0,0 +1,47 @@ |
|||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import androidx.compose.web.css.backgroundColor |
||||||
|
import androidx.compose.web.css.margin |
||||||
|
import androidx.compose.web.css.px |
||||||
|
import androidx.compose.web.css.Color.RGB |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.StyleBuilder |
||||||
|
import androidx.compose.web.attributes.AttrsBuilder |
||||||
|
|
||||||
|
actual fun Modifier.background(color: Color): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
backgroundColor(RGB(color.red, color.green, color.blue)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun Modifier.asStyleBuilderApplier( |
||||||
|
passThroughHandler: (StyleBuilder.() -> Unit)? = null |
||||||
|
): StyleBuilder.() -> Unit = castOrCreate().let { modifier -> |
||||||
|
val st: StyleBuilder.() -> Unit = { |
||||||
|
modifier.styleHandlers.forEach { it.invoke(this) } |
||||||
|
passThroughHandler?.invoke(this) |
||||||
|
} |
||||||
|
|
||||||
|
st |
||||||
|
} |
||||||
|
|
||||||
|
fun Modifier.asAttributeBuilderApplier( |
||||||
|
passThroughHandler: (AttrsBuilder<*>.() -> Unit)? = null |
||||||
|
): AttrsBuilder<*>.() -> Unit = |
||||||
|
castOrCreate().let { modifier -> |
||||||
|
val st: AttrsBuilder<*>.() -> Unit = { |
||||||
|
modifier.attrHandlers.forEach { it.invoke(this) } |
||||||
|
passThroughHandler?.invoke(this) |
||||||
|
} |
||||||
|
|
||||||
|
st |
||||||
|
} |
||||||
|
|
||||||
|
actual fun Modifier.padding(all: Dp): Modifier = castOrCreate().apply { |
||||||
|
// yes, it's not a typo, what Modifier.padding does is actually adding marginEe |
||||||
|
add { |
||||||
|
margin(all.value.px) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.web.ui |
||||||
|
|
||||||
|
import androidx.compose.web.css.justifyContent |
||||||
|
import androidx.compose.web.css.JustifyContent |
||||||
|
import androidx.compose.web.css.alignItems |
||||||
|
import androidx.compose.web.css.AlignItems |
||||||
|
import androidx.compose.web.css.flexDirection |
||||||
|
import androidx.compose.web.css.FlexDirection |
||||||
|
import androidx.compose.web.css.display |
||||||
|
import androidx.compose.web.css.DisplayStyle |
||||||
|
import androidx.compose.web.css.left |
||||||
|
import androidx.compose.web.css.px |
||||||
|
|
||||||
|
import androidx.compose.web.css.StyleSheet |
||||||
|
|
||||||
|
object Styles : StyleSheet() { |
||||||
|
val columnClass = "compose-web-column" |
||||||
|
|
||||||
|
val textClass by style { |
||||||
|
display(DisplayStyle.Block) |
||||||
|
left(0.px) |
||||||
|
} |
||||||
|
|
||||||
|
val rowClass by style { |
||||||
|
display(DisplayStyle.Flex) |
||||||
|
flexDirection(FlexDirection.Row) |
||||||
|
} |
||||||
|
|
||||||
|
val composeWebArrangementHorizontalStart by style { |
||||||
|
justifyContent(JustifyContent.FlexStart) |
||||||
|
} |
||||||
|
|
||||||
|
val composeWebArrangementHorizontalEnd by style { |
||||||
|
justifyContent(JustifyContent.FlexEnd) |
||||||
|
} |
||||||
|
|
||||||
|
val composeWebAlignmentVerticalTop by style { |
||||||
|
alignItems(AlignItems.FlexStart) |
||||||
|
} |
||||||
|
|
||||||
|
val composeWebAlignmentVerticalCenter by style { |
||||||
|
alignItems(AlignItems.Center) |
||||||
|
} |
||||||
|
|
||||||
|
val composeWebAlignmentVerticalBottom by style { |
||||||
|
alignItems(AlignItems.FlexEnd) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.internal |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.web.css.StyleBuilder |
||||||
|
import androidx.compose.web.attributes.AttrsBuilder |
||||||
|
|
||||||
|
class ActualModifier : Modifier { |
||||||
|
val styleHandlers = mutableListOf<StyleBuilder.() -> Unit>() |
||||||
|
val attrHandlers = mutableListOf<AttrsBuilder<*>.() -> Unit>() |
||||||
|
|
||||||
|
fun add(builder: StyleBuilder.() -> Unit) { |
||||||
|
styleHandlers.add(builder) |
||||||
|
} |
||||||
|
|
||||||
|
fun addAttributeBuilder(builder: AttrsBuilder<*>.() -> Unit) { |
||||||
|
attrHandlers.add(builder) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun Modifier.castOrCreate(): ActualModifier = (this as? ActualModifier) ?: ActualModifier() |
@ -0,0 +1,33 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.asStyleBuilderApplier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.web.elements.Div |
||||||
|
import org.jetbrains.compose.common.ui.asAttributeBuilderApplier |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun BoxActual(modifier: Modifier, content: @Composable () -> Unit) { |
||||||
|
Div( |
||||||
|
style = modifier.asStyleBuilderApplier(), |
||||||
|
attrs = modifier.asAttributeBuilderApplier() |
||||||
|
) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.web.elements.Button |
||||||
|
import org.jetbrains.compose.common.ui.asStyleBuilderApplier |
||||||
|
|
||||||
|
@Composable |
||||||
|
actual fun ButtonActual( |
||||||
|
modifier: Modifier, |
||||||
|
onClick: () -> Unit, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { |
||||||
|
Button( |
||||||
|
style = modifier.asStyleBuilderApplier(), |
||||||
|
attrs = { |
||||||
|
onClick { onClick() } |
||||||
|
} |
||||||
|
) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.asStyleBuilderApplier |
||||||
|
import androidx.compose.web.elements.Div |
||||||
|
import org.jetbrains.compose.web.ui.Styles |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun ColumnActual(modifier: Modifier, content: @Composable () -> Unit) { |
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
classes(Styles.columnClass) |
||||||
|
}, |
||||||
|
style = modifier.asStyleBuilderApplier() |
||||||
|
) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.web.elements.Div |
||||||
|
import org.jetbrains.compose.common.ui.asStyleBuilderApplier |
||||||
|
import org.jetbrains.compose.common.ui.Alignment |
||||||
|
import org.jetbrains.compose.web.ui.Styles |
||||||
|
|
||||||
|
private fun Arrangement.Horizontal.asClassName() = when (this) { |
||||||
|
Arrangement.End -> Styles.composeWebArrangementHorizontalEnd |
||||||
|
else -> Styles.composeWebArrangementHorizontalStart |
||||||
|
} |
||||||
|
|
||||||
|
private fun Alignment.Vertical.asClassName() = when (this) { |
||||||
|
Alignment.Top -> Styles.composeWebAlignmentVerticalTop |
||||||
|
Alignment.CenterVertically -> Styles.composeWebAlignmentVerticalCenter |
||||||
|
else -> Styles.composeWebAlignmentVerticalBottom |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun RowActual( |
||||||
|
modifier: Modifier, |
||||||
|
horizontalArrangement: Arrangement.Horizontal, |
||||||
|
verticalAlignment: Alignment.Vertical, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { |
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
classes( |
||||||
|
*arrayOf( |
||||||
|
Styles.rowClass, |
||||||
|
horizontalArrangement.asClassName(), |
||||||
|
verticalAlignment.asClassName() |
||||||
|
) |
||||||
|
) |
||||||
|
}, |
||||||
|
style = modifier.asStyleBuilderApplier() |
||||||
|
) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.web.elements.Input |
||||||
|
import androidx.compose.web.attributes.InputType |
||||||
|
|
||||||
|
@Composable |
||||||
|
actual fun SliderActual( |
||||||
|
value: Float, |
||||||
|
onValueChange: (Float) -> Unit, |
||||||
|
valueRange: ClosedFloatingPointRange<Float>, |
||||||
|
steps: Int, |
||||||
|
modifier: Modifier, |
||||||
|
) { |
||||||
|
val stepCount = if (steps == 0) 100 else steps |
||||||
|
val step = (valueRange.endInclusive - valueRange.start) / stepCount |
||||||
|
|
||||||
|
Input( |
||||||
|
type = InputType.Range, |
||||||
|
value = value.toString(), |
||||||
|
attrs = { |
||||||
|
attr("min", valueRange.start.toString()) |
||||||
|
attr("max", valueRange.endInclusive.toString()) |
||||||
|
attr("step", step.toString()) |
||||||
|
onRangeInput { |
||||||
|
val value: String = it.nativeEvent.target.asDynamic().value |
||||||
|
onValueChange(value.toFloat()) |
||||||
|
} |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.web.elements.Text as TextNode |
||||||
|
import androidx.compose.web.elements.Span |
||||||
|
import org.jetbrains.compose.web.ui.Styles |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.asStyleBuilderApplier |
||||||
|
import org.jetbrains.compose.common.ui.asAttributeBuilderApplier |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import androidx.compose.web.css.color |
||||||
|
import androidx.compose.web.css.fontSize |
||||||
|
import androidx.compose.web.css.Color.RGB |
||||||
|
import org.jetbrains.compose.common.ui.unit.TextUnit |
||||||
|
import org.jetbrains.compose.common.ui.unit.TextUnitType |
||||||
|
import androidx.compose.web.css.em |
||||||
|
import androidx.compose.web.css.px |
||||||
|
|
||||||
|
@Composable |
||||||
|
actual fun TextActual( |
||||||
|
text: String, |
||||||
|
modifier: Modifier, |
||||||
|
color: Color, |
||||||
|
size: TextUnit |
||||||
|
) { |
||||||
|
Span( |
||||||
|
style = modifier.asStyleBuilderApplier() { |
||||||
|
color(RGB(color.red, color.green, color.blue)) |
||||||
|
when (size.unitType) { |
||||||
|
TextUnitType.Em -> fontSize(size.value.em) |
||||||
|
TextUnitType.Sp -> fontSize(size.value.px) |
||||||
|
} |
||||||
|
}, |
||||||
|
attrs = modifier.asAttributeBuilderApplier() { |
||||||
|
classes(Styles.textClass) |
||||||
|
} |
||||||
|
) { |
||||||
|
TextNode(text) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.px |
||||||
|
import androidx.compose.web.css.LineStyle |
||||||
|
import androidx.compose.web.css.border |
||||||
|
import androidx.compose.web.css.Color.RGB |
||||||
|
|
||||||
|
actual fun Modifier.border(size: Dp, color: Color): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
border(size.value.px, LineStyle.Solid, RGB(color.red, color.green, color.blue)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
|
||||||
|
actual fun Modifier.clickable(onClick: () -> Unit): Modifier = castOrCreate().apply { |
||||||
|
addAttributeBuilder { |
||||||
|
onClick { onClick() } |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.draw |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import jetbrains.compose.common.shapes.Shape |
||||||
|
import jetbrains.compose.common.shapes.CircleShape |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.borderRadius |
||||||
|
import androidx.compose.web.css.percent |
||||||
|
|
||||||
|
actual fun Modifier.clip(shape: Shape): Modifier = castOrCreate().apply { |
||||||
|
when (shape) { |
||||||
|
CircleShape -> add { |
||||||
|
borderRadius(50.percent) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.height |
||||||
|
import androidx.compose.web.css.percent |
||||||
|
|
||||||
|
actual fun Modifier.fillMaxHeight(fraction: Float): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
height((100 * fraction).percent) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.width |
||||||
|
import androidx.compose.web.css.percent |
||||||
|
|
||||||
|
actual fun Modifier.fillMaxWidth(): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
width(100.percent) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.marginTop |
||||||
|
import androidx.compose.web.css.marginLeft |
||||||
|
import androidx.compose.web.css.px |
||||||
|
|
||||||
|
actual fun Modifier.offset(x: Dp, y: Dp): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
marginLeft(x.value.px) |
||||||
|
marginTop(y.value.px) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.unit.IntSize |
||||||
|
|
||||||
|
actual fun Modifier.onSizeChanged( |
||||||
|
onSizeChanged: (IntSize) -> Unit |
||||||
|
): Modifier { |
||||||
|
return this |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import androidx.compose.web.css.width |
||||||
|
import androidx.compose.web.css.height |
||||||
|
import androidx.compose.web.css.px |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
|
||||||
|
actual fun Modifier.size(width: Dp, height: Dp): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
width(width.value.px) |
||||||
|
height(height.value.px) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.web.css.px |
||||||
|
import androidx.compose.web.css.width |
||||||
|
|
||||||
|
actual fun Modifier.width(size: Dp): Modifier = castOrCreate().apply { |
||||||
|
add { |
||||||
|
width(size.value.px) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
<!-- |
||||||
|
~ Copyright 2021 The Android Open Source Project |
||||||
|
~ |
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
~ you may not use this file except in compliance with the License. |
||||||
|
~ You may obtain a copy of the License at |
||||||
|
~ |
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
~ |
||||||
|
~ Unless required by applicable law or agreed to in writing, software |
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
~ See the License for the specific language governing permissions and |
||||||
|
~ limitations under the License. |
||||||
|
--> |
||||||
|
|
||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<title>compose-browser-demo</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<h1>Hello world!</h1> |
||||||
|
<div id="root"/> |
||||||
|
<script src="web.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,126 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import androidx.compose.web.elements.Div |
||||||
|
import androidx.compose.web.renderComposable |
||||||
|
import kotlinx.browser.document |
||||||
|
import kotlinx.dom.clear |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
class DomSideEffectTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun canCreateElementsInDomSideEffect() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
|
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div { |
||||||
|
DomSideEffect { |
||||||
|
it.appendChild( |
||||||
|
document.createElement("p").also { |
||||||
|
it.appendChild(document.createTextNode("Hello World!")) |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
assertEquals( |
||||||
|
expected = "<div><div style=\"\"><p>Hello World!</p></div></div>", |
||||||
|
actual = root.outerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun canUpdateElementsCreatedInDomSideEffect() = runTest { |
||||||
|
var i: Int by mutableStateOf(0) |
||||||
|
val disposeCalls = mutableListOf<Int>() |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun CustomDiv(value: Int) { |
||||||
|
Div { |
||||||
|
DomSideEffect(value) { |
||||||
|
it.appendChild( |
||||||
|
it.appendChild(document.createTextNode("Value = $value")) |
||||||
|
) |
||||||
|
onDispose { |
||||||
|
disposeCalls.add(value) |
||||||
|
it.clear() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
composition { CustomDiv(i) } |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<div><div style=\"\">Value = 0</div></div>", |
||||||
|
actual = root.outerHTML |
||||||
|
) |
||||||
|
|
||||||
|
i = 1 |
||||||
|
|
||||||
|
waitChanges() |
||||||
|
assertEquals( |
||||||
|
expected = 1, |
||||||
|
actual = disposeCalls.size, |
||||||
|
message = "Only one onDispose call expected" |
||||||
|
) |
||||||
|
assertEquals( |
||||||
|
expected = 0, |
||||||
|
actual = disposeCalls[0], |
||||||
|
message = "onDispose should be called with a previous value" |
||||||
|
) |
||||||
|
assertEquals( |
||||||
|
expected = "<div><div style=\"\">Value = 1</div></div>", |
||||||
|
actual = root.outerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun onDisposeIsCalledWhenComposableRemovedFromComposition() = runTest { |
||||||
|
var showDiv: Boolean by mutableStateOf(true) |
||||||
|
var onDisposeCalledTimes = 0 |
||||||
|
|
||||||
|
composition { |
||||||
|
if (showDiv) { |
||||||
|
Div { |
||||||
|
DomSideEffect { |
||||||
|
it.appendChild(document.createTextNode("Goedemorgen!")) |
||||||
|
onDispose { onDisposeCalledTimes++ } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<div><div style=\"\">Goedemorgen!</div></div>", |
||||||
|
actual = root.outerHTML |
||||||
|
) |
||||||
|
|
||||||
|
showDiv = false |
||||||
|
|
||||||
|
waitChanges() |
||||||
|
assertEquals(1, onDisposeCalledTimes) |
||||||
|
assertEquals(expected = "<div></div>", actual = root.outerHTML) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,153 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import androidx.compose.web.css.color |
||||||
|
import androidx.compose.web.elements.Span |
||||||
|
import androidx.compose.web.elements.Text |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
class InlineStyleTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun conditionalStyleAppliedProperly() = runTest { |
||||||
|
|
||||||
|
var isRed by mutableStateOf(true) |
||||||
|
composition { |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
if (isRed) { |
||||||
|
color("red") |
||||||
|
} else { |
||||||
|
color("green") |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("text") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"color: red;\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
|
||||||
|
isRed = false |
||||||
|
waitChanges() |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"color: green;\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun conditionalStyleAddedWhenTrue() = runTest { |
||||||
|
var isRed by mutableStateOf(false) |
||||||
|
composition { |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
if (isRed) { |
||||||
|
color("red") |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("text") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
|
||||||
|
isRed = true |
||||||
|
waitChanges() |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"color: red;\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun conditionalStyleGetsRemovedWhenFalse() = runTest { |
||||||
|
var isRed by mutableStateOf(true) |
||||||
|
composition { |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
if (isRed) { |
||||||
|
color("red") |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("text") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"color: red;\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
|
||||||
|
isRed = false |
||||||
|
waitChanges() |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun conditionalStyleUpdatedProperly() = runTest { |
||||||
|
var isRed by mutableStateOf(true) |
||||||
|
composition { |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
if (isRed) { |
||||||
|
color("red") |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("text") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<span style=\"color: red;\">text</span>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
|
||||||
|
repeat(4) { |
||||||
|
isRed = !isRed |
||||||
|
waitChanges() |
||||||
|
|
||||||
|
val expected = if (isRed) { |
||||||
|
"<span style=\"color: red;\">text</span>" |
||||||
|
} else { |
||||||
|
"<span style=\"\">text</span>" |
||||||
|
} |
||||||
|
assertEquals( |
||||||
|
expected = expected, |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,599 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import androidx.compose.web.css.AlignContent |
||||||
|
import androidx.compose.web.css.AlignItems |
||||||
|
import androidx.compose.web.css.AlignSelf |
||||||
|
import androidx.compose.web.css.Color |
||||||
|
import androidx.compose.web.css.DisplayStyle |
||||||
|
import androidx.compose.web.css.FlexDirection |
||||||
|
import androidx.compose.web.css.FlexWrap |
||||||
|
import androidx.compose.web.css.JustifyContent |
||||||
|
import androidx.compose.web.css.Position |
||||||
|
import androidx.compose.web.css.alignContent |
||||||
|
import androidx.compose.web.css.alignItems |
||||||
|
import androidx.compose.web.css.alignSelf |
||||||
|
import androidx.compose.web.css.border |
||||||
|
import androidx.compose.web.css.borderRadius |
||||||
|
import androidx.compose.web.css.bottom |
||||||
|
import androidx.compose.web.css.color |
||||||
|
import androidx.compose.web.css.display |
||||||
|
import androidx.compose.web.css.flexDirection |
||||||
|
import androidx.compose.web.css.flexFlow |
||||||
|
import androidx.compose.web.css.flexGrow |
||||||
|
import androidx.compose.web.css.flexShrink |
||||||
|
import androidx.compose.web.css.flexWrap |
||||||
|
import androidx.compose.web.css.height |
||||||
|
import androidx.compose.web.css.justifyContent |
||||||
|
import androidx.compose.web.css.left |
||||||
|
import androidx.compose.web.css.opacity |
||||||
|
import androidx.compose.web.css.order |
||||||
|
import androidx.compose.web.css.position |
||||||
|
import androidx.compose.web.css.px |
||||||
|
import androidx.compose.web.css.right |
||||||
|
import androidx.compose.web.css.top |
||||||
|
import androidx.compose.web.css.value |
||||||
|
import androidx.compose.web.css.width |
||||||
|
import androidx.compose.web.elements.Div |
||||||
|
import androidx.compose.web.elements.Span |
||||||
|
import androidx.compose.web.elements.Text |
||||||
|
import androidx.compose.web.renderComposable |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.get |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
import kotlin.test.assertTrue |
||||||
|
|
||||||
|
class StaticComposableTests { |
||||||
|
@Test |
||||||
|
fun emptyComposable() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) {} |
||||||
|
assertEquals("<div></div>", root.outerHTML) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun textChild() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Text("inner text") |
||||||
|
} |
||||||
|
assertEquals("<div>inner text</div>", root.outerHTML) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun attrs() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
classes("some", "simple", "classes") |
||||||
|
id("special") |
||||||
|
attr("data-val", "some data") |
||||||
|
attr("data-val", "some other data") |
||||||
|
id("verySpecial") |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
val el = root.firstChild |
||||||
|
assertTrue(el is HTMLElement, "element not found") |
||||||
|
|
||||||
|
assertEquals("verySpecial", el.getAttribute("id")) |
||||||
|
assertEquals("some simple classes", el.getAttribute("class")) |
||||||
|
assertEquals("some other data", el.getAttribute("data-val")) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun styles() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
opacity(0.3) |
||||||
|
color("red") |
||||||
|
opacity(0.2) |
||||||
|
color("green") |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("opacity: 0.2; color: green;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesBorder() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
property("border", value("1px solid red")) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
border(3.px, color = Color("green")) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("border: 1px solid red;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
root.children[1]?.let { el -> |
||||||
|
assertEquals( |
||||||
|
"green", |
||||||
|
el.asDynamic().attributeStyleMap.get("border-color").toString(), |
||||||
|
) |
||||||
|
assertEquals( |
||||||
|
"3px", |
||||||
|
el.asDynamic().attributeStyleMap.get("border-width").toString(), |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesOrder() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
order(-4) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
order(3) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("order: -4;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
assertEquals("order: 3;", (root.children[1] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesFlexGrow() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexGrow(3) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexGrow(2.5) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexGrow(1e2) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexGrow(.6) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("flex-grow: 3;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
assertEquals("flex-grow: 2.5;", (root.children[1] as HTMLElement).style.cssText) |
||||||
|
assertEquals("flex-grow: 100;", (root.children[2] as HTMLElement).style.cssText) |
||||||
|
assertEquals("flex-grow: 0.6;", (root.children[3] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesFlexShrink() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexShrink(3) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexShrink(2.5) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexShrink(1e2) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
flexShrink(.6) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("flex-shrink: 3;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
assertEquals("flex-shrink: 2.5;", (root.children[1] as HTMLElement).style.cssText) |
||||||
|
assertEquals("flex-shrink: 100;", (root.children[2] as HTMLElement).style.cssText) |
||||||
|
assertEquals("flex-shrink: 0.6;", (root.children[3] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesWidth() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
width(100.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("width: 100px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesBorderRadius() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
borderRadius(3.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
borderRadius(3.px, 5.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
borderRadius(3.px, 5.px, 4.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
borderRadius(3.px, 5.px, 4.px, 1.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("border-radius: 3px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
assertEquals("border-radius: 3px 5px;", (root.children[1] as HTMLElement).style.cssText) |
||||||
|
assertEquals("border-radius: 3px 5px 4px;", (root.children[2] as HTMLElement).style.cssText) |
||||||
|
assertEquals( |
||||||
|
"border-radius: 3px 5px 4px 1px;", |
||||||
|
(root.children[3] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesTop() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
top(100.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("top: 100px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesBottom() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
bottom(100.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("bottom: 100px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesLeft() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
left(100.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("left: 100px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesRight() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
right(100.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("right: 100px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesHeight() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
height(100.px) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("height: 100px;", (root.children[0] as HTMLElement).style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesDisplay() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<DisplayStyle>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { displayStyle -> |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
display(displayStyle) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, displayStyle -> |
||||||
|
assertEquals( |
||||||
|
"display: ${displayStyle.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesFlexDirection() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<FlexDirection>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { flexDirection -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
flexDirection(flexDirection) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, displayStyle -> |
||||||
|
assertEquals( |
||||||
|
"flex-direction: ${displayStyle.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesFlexWrap() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<FlexWrap>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { flexWrap -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
flexWrap(flexWrap) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, displayStyle -> |
||||||
|
assertEquals( |
||||||
|
"flex-wrap: ${displayStyle.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesFlexFlow() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val flexWraps = enumValues<FlexWrap>() |
||||||
|
val flexDirections = enumValues<FlexDirection>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
flexDirections.forEach { flexDirection -> |
||||||
|
flexWraps.forEach { flexWrap -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
flexFlow(flexDirection, flexWrap) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
flexDirections.forEachIndexed { i, flexDirection -> |
||||||
|
flexWraps.forEachIndexed { j, flexWrap -> |
||||||
|
assertEquals( |
||||||
|
"flex-flow: ${flexDirection.value} ${flexWrap.value};", |
||||||
|
(root.children[3 * i + j % 3] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesJustifyContent() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<JustifyContent>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { justifyContent -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
justifyContent(justifyContent) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, justifyContent -> |
||||||
|
assertEquals( |
||||||
|
"justify-content: ${justifyContent.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesAlignSelf() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<AlignSelf>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { alignSelf -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
alignSelf(alignSelf) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, alignSelf -> |
||||||
|
assertEquals( |
||||||
|
"align-self: ${alignSelf.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesAlignItems() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<AlignItems>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { alignItems -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
alignItems(alignItems) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, alignItems -> |
||||||
|
assertEquals( |
||||||
|
"align-items: ${alignItems.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesAlignContent() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<AlignContent>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { alignContent -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
alignContent(alignContent) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, alignContent -> |
||||||
|
assertEquals( |
||||||
|
"align-content: ${alignContent.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun stylesPosition() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
val enumValues = enumValues<Position>() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
enumValues.forEach { position -> |
||||||
|
Span( |
||||||
|
style = { |
||||||
|
position(position) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enumValues.forEachIndexed { index, position -> |
||||||
|
assertEquals( |
||||||
|
"position: ${position.value};", |
||||||
|
(root.children[index] as HTMLElement).style.cssText |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.web.renderComposable |
||||||
|
import kotlinx.browser.document |
||||||
|
import kotlinx.browser.window |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.MainScope |
||||||
|
import kotlinx.coroutines.promise |
||||||
|
import kotlinx.dom.clear |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.MutationObserver |
||||||
|
import org.w3c.dom.MutationObserverInit |
||||||
|
import kotlin.coroutines.resume |
||||||
|
import kotlin.coroutines.suspendCoroutine |
||||||
|
|
||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
private val testScope = MainScope() |
||||||
|
|
||||||
|
class TestScope : CoroutineScope by testScope { |
||||||
|
|
||||||
|
val root = "div".asHtmlElement() |
||||||
|
|
||||||
|
fun composition(content: @Composable () -> Unit) { |
||||||
|
root.clear() |
||||||
|
renderComposable(root) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
suspend fun waitChanges() { |
||||||
|
waitForChanges(root) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal fun runTest(block: suspend TestScope.() -> Unit): dynamic { |
||||||
|
val scope = TestScope() |
||||||
|
return scope.promise { block(scope) } |
||||||
|
} |
||||||
|
|
||||||
|
internal fun runBlockingTest( |
||||||
|
block: suspend CoroutineScope.() -> Unit |
||||||
|
): dynamic = testScope.promise { this.block() } |
||||||
|
|
||||||
|
internal fun String.asHtmlElement() = document.createElement(this) as HTMLElement |
||||||
|
|
||||||
|
/* Currently, the recompositionRunner relies on AnimationFrame to run the recomposition and |
||||||
|
applyChanges. Therefore we can use this method after updating the state and before making |
||||||
|
assertions. |
||||||
|
|
||||||
|
If tests get broken, then DefaultMonotonicFrameClock need to be checked if it still |
||||||
|
uses window.requestAnimationFrame */ |
||||||
|
internal suspend fun waitForAnimationFrame() { |
||||||
|
suspendCoroutine<Unit> { continuation -> |
||||||
|
window.requestAnimationFrame { |
||||||
|
continuation.resume(Unit) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private object MutationObserverOptions : MutationObserverInit { |
||||||
|
override var childList: Boolean? = true |
||||||
|
override var attributes: Boolean? = true |
||||||
|
override var characterData: Boolean? = true |
||||||
|
override var subtree: Boolean? = true |
||||||
|
override var attributeOldValue: Boolean? = true |
||||||
|
} |
||||||
|
|
||||||
|
internal suspend fun waitForChanges(elementId: String) { |
||||||
|
waitForChanges(document.getElementById(elementId) as HTMLElement) |
||||||
|
} |
||||||
|
|
||||||
|
internal suspend fun waitForChanges(element: HTMLElement) { |
||||||
|
suspendCoroutine<Unit> { continuation -> |
||||||
|
val observer = MutationObserver { mutations, observer -> |
||||||
|
continuation.resume(Unit) |
||||||
|
observer.disconnect() |
||||||
|
} |
||||||
|
observer.observe(element, MutationObserverOptions) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import androidx.compose.web.renderComposable |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.foundation.layout.Box |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.background |
||||||
|
import org.jetbrains.compose.common.ui.size |
||||||
|
import org.jetbrains.compose.common.ui.unit.dp |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
import kotlin.test.assertTrue |
||||||
|
|
||||||
|
class ModifierTests { |
||||||
|
@Test |
||||||
|
fun backgroundModifier() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Box( |
||||||
|
Modifier.background(Color(255, 0, 0)) |
||||||
|
) { } |
||||||
|
} |
||||||
|
|
||||||
|
val el = root.firstChild |
||||||
|
assertTrue(el is HTMLElement, "element not found") |
||||||
|
|
||||||
|
assertEquals("background-color: rgb(255, 0, 0);", el.style.cssText) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun size() { |
||||||
|
val root = "div".asHtmlElement() |
||||||
|
renderComposable( |
||||||
|
root = root |
||||||
|
) { |
||||||
|
Box( |
||||||
|
Modifier.size(40.dp) |
||||||
|
) { } |
||||||
|
} |
||||||
|
|
||||||
|
val el = root.firstChild |
||||||
|
assertTrue(el is HTMLElement, "element not found") |
||||||
|
|
||||||
|
assertEquals("width: 40px; height: 40px;", el.style.cssText) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package elements |
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import androidx.compose.web.attributes.disabled |
||||||
|
import androidx.compose.web.attributes.forId |
||||||
|
import androidx.compose.web.elements.Button |
||||||
|
import androidx.compose.web.elements.Label |
||||||
|
import androidx.compose.web.elements.Text |
||||||
|
import org.w3c.dom.HTMLButtonElement |
||||||
|
import runTest |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
class AttributesTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun labelForIdAttrAppliedProperly() = runTest { |
||||||
|
|
||||||
|
composition { |
||||||
|
Label(forId = "l1") { Text("label") } |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<label for=\"l1\" style=\"\">label</label>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun labelForIdIsOptional() = runTest { |
||||||
|
|
||||||
|
composition { |
||||||
|
Label { Text("label") } |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<label style=\"\">label</label>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun labelForIdIsAppliedFromAttrs() = runTest { |
||||||
|
|
||||||
|
composition { |
||||||
|
Label( |
||||||
|
attrs = { |
||||||
|
forId("lb1") |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("label") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "<label for=\"lb1\" style=\"\">label</label>", |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun buttonDisabledAttributeAddedOnlyWhenTrue() = runTest { |
||||||
|
var disabled by mutableStateOf(false) |
||||||
|
|
||||||
|
composition { |
||||||
|
Button( |
||||||
|
attrs = { |
||||||
|
disabled(disabled) |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
val btn = root.firstChild as HTMLButtonElement |
||||||
|
assertEquals(null, btn.getAttribute("disabled")) |
||||||
|
|
||||||
|
disabled = true |
||||||
|
waitChanges() |
||||||
|
|
||||||
|
assertEquals("", btn.getAttribute("disabled")) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package elements |
||||||
|
|
||||||
|
import androidx.compose.web.attributes.InputType |
||||||
|
import androidx.compose.web.elements.Button |
||||||
|
import androidx.compose.web.elements.Input |
||||||
|
import androidx.compose.web.elements.TextArea |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.HTMLInputElement |
||||||
|
import org.w3c.dom.HTMLTextAreaElement |
||||||
|
import org.w3c.dom.events.Event |
||||||
|
import org.w3c.dom.events.InputEvent |
||||||
|
import org.w3c.dom.events.MouseEvent |
||||||
|
import runTest |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
import kotlin.test.assertTrue |
||||||
|
|
||||||
|
class EventTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun buttonClickHandled() = runTest { |
||||||
|
var handeled = false |
||||||
|
|
||||||
|
composition { |
||||||
|
Button( |
||||||
|
attrs = { |
||||||
|
onClick { handeled = true } |
||||||
|
} |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals(1, root.childElementCount) |
||||||
|
|
||||||
|
val btn = root.firstChild as HTMLElement |
||||||
|
btn.dispatchEvent(MouseEvent("click")) |
||||||
|
|
||||||
|
assertTrue(handeled) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun checkboxInputHandled() = runTest { |
||||||
|
var handeled = false |
||||||
|
|
||||||
|
composition { |
||||||
|
Input( |
||||||
|
type = InputType.Checkbox, |
||||||
|
attrs = { |
||||||
|
onCheckboxInput { handeled = true } |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
val checkbox = root.firstChild as HTMLInputElement |
||||||
|
checkbox.dispatchEvent(Event("input")) |
||||||
|
|
||||||
|
assertTrue(handeled) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun radioButtonInputHandled() = runTest { |
||||||
|
var handeled = false |
||||||
|
|
||||||
|
composition { |
||||||
|
Input( |
||||||
|
type = InputType.Radio, |
||||||
|
attrs = { |
||||||
|
onRadioInput { handeled = true } |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
val radio = root.firstChild as HTMLInputElement |
||||||
|
radio.dispatchEvent(Event("input")) |
||||||
|
assertEquals(false, radio.checked) |
||||||
|
|
||||||
|
assertTrue(handeled) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun textAreaInputHandled() = runTest { |
||||||
|
var handeled = false |
||||||
|
|
||||||
|
composition { |
||||||
|
TextArea( |
||||||
|
attrs = { |
||||||
|
onTextInput { handeled = true } |
||||||
|
}, |
||||||
|
value = "" |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
val radio = root.firstChild as HTMLTextAreaElement |
||||||
|
radio.dispatchEvent(InputEvent("input")) |
||||||
|
|
||||||
|
assertEquals("", radio.value) |
||||||
|
|
||||||
|
assertTrue(handeled) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,204 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package elements |
||||||
|
|
||||||
|
import androidx.compose.web.attributes.Scope |
||||||
|
import androidx.compose.web.attributes.colspan |
||||||
|
import androidx.compose.web.attributes.rowspan |
||||||
|
import androidx.compose.web.attributes.scope |
||||||
|
import androidx.compose.web.attributes.span |
||||||
|
import androidx.compose.web.elements.Caption |
||||||
|
import androidx.compose.web.elements.Col |
||||||
|
import androidx.compose.web.elements.Colgroup |
||||||
|
import androidx.compose.web.elements.Table |
||||||
|
import androidx.compose.web.elements.Tbody |
||||||
|
import androidx.compose.web.elements.Td |
||||||
|
import androidx.compose.web.elements.Text |
||||||
|
import androidx.compose.web.elements.Tfoot |
||||||
|
import androidx.compose.web.elements.Th |
||||||
|
import androidx.compose.web.elements.Thead |
||||||
|
import androidx.compose.web.elements.Tr |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import runTest |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
class TableTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun colAttributes() = runTest { |
||||||
|
composition { |
||||||
|
Col( |
||||||
|
attrs = { |
||||||
|
span(2) |
||||||
|
} |
||||||
|
) { } |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = "2", |
||||||
|
actual = (root.firstChild!! as HTMLElement).getAttribute("span") |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun create() = runTest { |
||||||
|
composition { |
||||||
|
Table { |
||||||
|
Caption { |
||||||
|
Text("CaptionText") |
||||||
|
} |
||||||
|
Colgroup { |
||||||
|
Col { } |
||||||
|
Col { } |
||||||
|
Col { } |
||||||
|
} |
||||||
|
Thead { |
||||||
|
Tr { |
||||||
|
Th { } |
||||||
|
Th { } |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
colspan(2) |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("First") |
||||||
|
} |
||||||
|
} |
||||||
|
Tr { |
||||||
|
Th { } |
||||||
|
Th { } |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
scope(Scope.Col) |
||||||
|
} |
||||||
|
) { Text("A") } |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
scope(Scope.Col) |
||||||
|
} |
||||||
|
) { Text("B") } |
||||||
|
} |
||||||
|
} |
||||||
|
Tbody { |
||||||
|
Tr { |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
scope(Scope.Row) |
||||||
|
rowspan(2) |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("Rows") |
||||||
|
} |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
scope(Scope.Row) |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("1") |
||||||
|
} |
||||||
|
Td { |
||||||
|
Text("30") |
||||||
|
} |
||||||
|
Td { |
||||||
|
Text("40") |
||||||
|
} |
||||||
|
} |
||||||
|
Tr { |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
scope(Scope.Row) |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("2") |
||||||
|
} |
||||||
|
Td { |
||||||
|
Text("10") |
||||||
|
} |
||||||
|
Td { |
||||||
|
Text("20") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Tfoot { |
||||||
|
Tr { |
||||||
|
Th( |
||||||
|
attrs = { |
||||||
|
scope(Scope.Row) |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("Totals") |
||||||
|
} |
||||||
|
Th { } |
||||||
|
Td { Text("40") } |
||||||
|
Td { Text("60") } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals( |
||||||
|
expected = """ |
||||||
|
<table style=""> |
||||||
|
<caption style="">CaptionText</caption> |
||||||
|
<colgroup style=""> |
||||||
|
<col style=""> |
||||||
|
<col style=""> |
||||||
|
<col style=""> |
||||||
|
</colgroup> |
||||||
|
<thead style=""> |
||||||
|
<tr style=""> |
||||||
|
<th style=""></th> |
||||||
|
<th style=""></th> |
||||||
|
<th colspan="2" style="">First</th> |
||||||
|
</tr> |
||||||
|
<tr style=""> |
||||||
|
<th style=""></th> |
||||||
|
<th style=""></th> |
||||||
|
<th scope="col" style="">A</th> |
||||||
|
<th scope="col" style="">B</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody style=""> |
||||||
|
<tr style=""> |
||||||
|
<th scope="row" rowspan="2" style="">Rows</th> |
||||||
|
<th scope="row" style="">1</th> |
||||||
|
<td style="">30</td> |
||||||
|
<td style="">40</td> |
||||||
|
</tr> |
||||||
|
<tr style=""> |
||||||
|
<th scope="row" style="">2</th> |
||||||
|
<td style="">10</td> |
||||||
|
<td style="">20</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
<tfoot style=""> |
||||||
|
<tr style=""> |
||||||
|
<th scope="row" style="">Totals</th> |
||||||
|
<th style=""></th> |
||||||
|
<td style="">40</td> |
||||||
|
<td style="">60</td> |
||||||
|
</tr> |
||||||
|
</tfoot> |
||||||
|
</table> |
||||||
|
""".trimIndent() |
||||||
|
.replace("\n", "") |
||||||
|
.replace("\\s{2,}".toRegex(), ""), |
||||||
|
actual = root.innerHTML |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import androidx.compose.ui.Alignment as JAlignment |
||||||
|
|
||||||
|
val Alignment.Vertical.implementation: JAlignment.Vertical |
||||||
|
get() = when (this) { |
||||||
|
Alignment.Top -> JAlignment.Top |
||||||
|
Alignment.CenterVertically -> JAlignment.CenterVertically |
||||||
|
else -> JAlignment.Bottom |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement as JArrangement |
||||||
|
|
||||||
|
val Arrangement.Horizontal.implementation: JArrangement.Horizontal |
||||||
|
get() = when (this) { |
||||||
|
Arrangement.End -> JArrangement.End |
||||||
|
else -> JArrangement.Start |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.core.graphics |
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color as JColor |
||||||
|
|
||||||
|
val Color.implementation |
||||||
|
get() = JColor(red, green, blue) |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.ui |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.unit.Dp |
||||||
|
import org.jetbrains.compose.common.ui.unit.implementation |
||||||
|
import androidx.compose.foundation.background |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.core.graphics.implementation |
||||||
|
import org.jetbrains.compose.common.internal.castOrCreate |
||||||
|
import androidx.compose.foundation.layout.padding |
||||||
|
|
||||||
|
actual fun Modifier.background(color: Color): Modifier = castOrCreate().apply { |
||||||
|
modifier = modifier.background(color.implementation) |
||||||
|
} |
||||||
|
|
||||||
|
actual fun Modifier.padding(all: Dp): Modifier = castOrCreate().apply { |
||||||
|
modifier = modifier.padding(all.implementation) |
||||||
|
} |
||||||
|
|
||||||
|
val Modifier.implementation |
||||||
|
get() = castOrCreate().modifier |
@ -0,0 +1,27 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.internal |
||||||
|
|
||||||
|
import androidx.compose.ui.Modifier as JModifier |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
|
||||||
|
private class ModifierElement : JModifier.Element |
||||||
|
|
||||||
|
class ActualModifier : Modifier { |
||||||
|
var modifier: JModifier = ModifierElement() |
||||||
|
} |
||||||
|
|
||||||
|
fun Modifier.castOrCreate(): ActualModifier = (this as? ActualModifier) ?: ActualModifier() |
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.foundation.layout.Box as JBox |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun BoxActual(modifier: Modifier, content: @Composable () -> Unit) { |
||||||
|
JBox(modifier.implementation) { |
||||||
|
content.invoke() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
import androidx.compose.material.Button as JButton |
||||||
|
|
||||||
|
@Composable |
||||||
|
actual fun ButtonActual( |
||||||
|
modifier: Modifier, |
||||||
|
onClick: () -> Unit, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { |
||||||
|
JButton(onClick, modifier.implementation) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.foundation.layout.Column as JColumn |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun ColumnActual(modifier: Modifier, content: @Composable () -> Unit) { |
||||||
|
JColumn(modifier = modifier.implementation) { |
||||||
|
content.invoke() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.common.foundation.layout |
||||||
|
|
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.foundation.layout.Row as JRow |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
import org.jetbrains.compose.common.ui.Alignment |
||||||
|
|
||||||
|
@Composable |
||||||
|
internal actual fun RowActual( |
||||||
|
modifier: Modifier, |
||||||
|
horizontalArrangement: Arrangement.Horizontal, |
||||||
|
verticalAlignment: Alignment.Vertical, |
||||||
|
content: @Composable () -> Unit |
||||||
|
) { |
||||||
|
JRow( |
||||||
|
modifier.implementation, |
||||||
|
horizontalArrangement.implementation, |
||||||
|
verticalAlignment.implementation |
||||||
|
) { |
||||||
|
content.invoke() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package jetbrains.compose.common.shapes |
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Shape as JShape |
||||||
|
import androidx.compose.foundation.shape.CircleShape as JCircleShape |
||||||
|
|
||||||
|
val Shape.implementation: JShape |
||||||
|
get() = when (this) { |
||||||
|
CircleShape -> JCircleShape |
||||||
|
else -> throw ClassCastException("Currently supporting only circle shape") |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import androidx.compose.material.Slider as JSlider |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
|
||||||
|
@Composable |
||||||
|
actual fun SliderActual( |
||||||
|
value: Float, |
||||||
|
onValueChange: (Float) -> Unit, |
||||||
|
valueRange: ClosedFloatingPointRange<Float>, |
||||||
|
steps: Int, |
||||||
|
modifier: Modifier |
||||||
|
) { |
||||||
|
JSlider( |
||||||
|
value, |
||||||
|
onValueChange = onValueChange, |
||||||
|
modifier = modifier.implementation, |
||||||
|
valueRange = valueRange, |
||||||
|
steps = steps |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2021 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.jetbrains.compose.common.material |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.material.Text as JText |
||||||
|
import org.jetbrains.compose.common.ui.Modifier |
||||||
|
import org.jetbrains.compose.common.ui.implementation |
||||||
|
import org.jetbrains.compose.common.core.graphics.Color |
||||||
|
import org.jetbrains.compose.common.core.graphics.implementation |
||||||
|
import org.jetbrains.compose.common.ui.unit.TextUnit |
||||||
|
import org.jetbrains.compose.common.ui.unit.implementation |
||||||
|
|
||||||
|
@Composable |
||||||
|
actual fun TextActual( |
||||||
|
text: String, |
||||||
|
modifier: Modifier, |
||||||
|
color: Color, |
||||||
|
size: TextUnit |
||||||
|
) { |
||||||
|
JText( |
||||||
|
text, |
||||||
|
modifier = modifier.implementation, |
||||||
|
color = color.implementation, |
||||||
|
fontSize = size.implementation |
||||||
|
) |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue