Shagen Ogandzhanian
4 years ago
131 changed files with 9773 additions and 30 deletions
@ -1,30 +1,3 @@
|
||||
# Intellij files |
||||
|
||||
.idea/* |
||||
!.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* |
||||
|
||||
//gradle |
||||
build |
||||
.gradle |
||||
|
@ -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