diff --git a/.gitignore b/.gitignore index 9c462479d2..90e3ac6200 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/web/build.gradle b/web/build.gradle new file mode 100644 index 0000000000..7d9603d1d0 --- /dev/null +++ b/web/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) +} diff --git a/web/core/build.gradle b/web/core/build.gradle new file mode 100644 index 0000000000..ec87a46066 --- /dev/null +++ b/web/core/build.gradle @@ -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) + } + } + } +} diff --git a/web/core/src/commonMain/kotlin/withWeb/Alignment.kt b/web/core/src/commonMain/kotlin/withWeb/Alignment.kt new file mode 100644 index 0000000000..3d09595bfc --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/Alignment.kt @@ -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 {} + } +} diff --git a/web/core/src/commonMain/kotlin/withWeb/Arrangement.kt b/web/core/src/commonMain/kotlin/withWeb/Arrangement.kt new file mode 100644 index 0000000000..5cd3f3dd29 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/Arrangement.kt @@ -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 {} +} \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/Color.kt b/web/core/src/commonMain/kotlin/withWeb/Color.kt new file mode 100644 index 0000000000..e3f1cd35fa --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/Color.kt @@ -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) + } +} \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/Modifier.kt b/web/core/src/commonMain/kotlin/withWeb/Modifier.kt new file mode 100644 index 0000000000..72a25d16af --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/Modifier.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/box.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/box.kt new file mode 100644 index 0000000000..99521ab598 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/box.kt @@ -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) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/button.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/button.kt new file mode 100644 index 0000000000..e151f9ff2a --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/button.kt @@ -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 +) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/column.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/column.kt new file mode 100644 index 0000000000..858c537682 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/column.kt @@ -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) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/BoxDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/BoxDefault.kt new file mode 100644 index 0000000000..88547ef3a0 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/BoxDefault.kt @@ -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) } \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ButtonDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ButtonDefault.kt new file mode 100644 index 0000000000..a670fe566d --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ButtonDefault.kt @@ -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) +} \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ColumnDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ColumnDefault.kt new file mode 100644 index 0000000000..c276a4617e --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ColumnDefault.kt @@ -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) } \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ModifierDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ModifierDefault.kt new file mode 100644 index 0000000000..17bc7e9488 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ModifierDefault.kt @@ -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) +} diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/RowDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/RowDefault.kt new file mode 100644 index 0000000000..e40f6c7151 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/RowDefault.kt @@ -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) } \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/SliderDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/SliderDefault.kt new file mode 100644 index 0000000000..2c95ad631f --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/SliderDefault.kt @@ -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 = 0f..1f, + steps: Int = 0, + modifier: Modifier = Modifier.Companion +) { + SliderActual( + value, + onValueChange, + valueRange, + steps, + modifier + ) +} \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/TextDefault.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/TextDefault.kt new file mode 100644 index 0000000000..ced44fa373 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/defaults/TextDefault.kt @@ -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) +} \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/row.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/row.kt new file mode 100644 index 0000000000..df57c7f504 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/row.kt @@ -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 +) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/shapes/shapes.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/shapes/shapes.kt new file mode 100644 index 0000000000..5f2628c102 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/shapes/shapes.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/slider.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/slider.kt new file mode 100644 index 0000000000..b6099cf42a --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/slider.kt @@ -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, + steps: Int, + modifier: Modifier, +) diff --git a/web/core/src/commonMain/kotlin/withWeb/layouts/text.kt b/web/core/src/commonMain/kotlin/withWeb/layouts/text.kt new file mode 100644 index 0000000000..610462d00e --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/layouts/text.kt @@ -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 +) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/border.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/border.kt new file mode 100644 index 0000000000..1981237349 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/border.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/clickable.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/clickable.kt new file mode 100644 index 0000000000..3ef2e722d1 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/clickable.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/clip.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/clip.kt new file mode 100644 index 0000000000..c2f01e1ceb --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/clip.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxHeight.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxHeight.kt new file mode 100644 index 0000000000..fb73c77094 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxHeight.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxWidth.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxWidth.kt new file mode 100644 index 0000000000..f3bfc93856 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxWidth.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/offset.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/offset.kt new file mode 100644 index 0000000000..b4e41d86c4 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/offset.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/onSizeChanged.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/onSizeChanged.kt new file mode 100644 index 0000000000..7585647adb --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/onSizeChanged.kt @@ -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 diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/size.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/size.kt new file mode 100644 index 0000000000..afcacc7a95 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/size.kt @@ -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 diff --git a/web/core/src/commonMain/kotlin/withWeb/modifiers/width.kt b/web/core/src/commonMain/kotlin/withWeb/modifiers/width.kt new file mode 100644 index 0000000000..4e3b37d00c --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/modifiers/width.kt @@ -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 \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/units/Dp.kt b/web/core/src/commonMain/kotlin/withWeb/units/Dp.kt new file mode 100644 index 0000000000..866f59570a --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/units/Dp.kt @@ -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) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/units/IntSize.kt b/web/core/src/commonMain/kotlin/withWeb/units/IntSize.kt new file mode 100644 index 0000000000..591ed5ccac --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/units/IntSize.kt @@ -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) \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/units/TextUnit.kt b/web/core/src/commonMain/kotlin/withWeb/units/TextUnit.kt new file mode 100644 index 0000000000..21cef590b6 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/units/TextUnit.kt @@ -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) + } +} \ No newline at end of file diff --git a/web/core/src/commonMain/kotlin/withWeb/units/unitConversion.kt b/web/core/src/commonMain/kotlin/withWeb/units/unitConversion.kt new file mode 100644 index 0000000000..64ced71291 --- /dev/null +++ b/web/core/src/commonMain/kotlin/withWeb/units/unitConversion.kt @@ -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) diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt new file mode 100644 index 0000000000..8f4f584ee0 --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt @@ -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(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> = emptyList() + private var currentAttrs: Map = emptyMap() + + fun updateProperties(list: List Unit, Any>>) { + val htmlElement = node as? HTMLElement ?: return + list.forEach { it.first(htmlElement, it.second) } + } + + fun updateEventListeners(list: List>) { + 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) { + 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) -> Unit = { + this.updateAttrs(it) + } + val UpdateListeners: DomNodeWrapper.(List>) -> Unit = { + this.updateEventListeners(it) + } + val UpdateProperties: DomNodePropertiesUpdater = { + this.updateProperties(it) + } + val UpdateStyleDeclarations: DomNodeWrapper.(StyleHolder?) -> Unit = { + this.updateStyleDeclarations(it) + } + } +} + +typealias DomNodePropertiesUpdater = + DomNodeWrapper.(List Unit, Any>>) -> Unit \ No newline at end of file diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/GlobalSnapshotManager.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/GlobalSnapshotManager.kt new file mode 100644 index 0000000000..fac4ad1d48 --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/GlobalSnapshotManager.kt @@ -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() } + } + } +} diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/JsMicrotasksDispatcher.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/JsMicrotasksDispatcher.kt new file mode 100644 index 0000000000..0699bef555 --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/JsMicrotasksDispatcher.kt @@ -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() } + } +} diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt new file mode 100644 index 0000000000..dbd3d0ad72 --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt @@ -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 renderComposable( + root: THTMLElement, + content: @Composable DOMScope.() -> 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 {} + 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.() -> 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.() -> Unit +): Composition = renderComposable( + root = document.getElementsByTagName("body")[0] as HTMLBodyElement, + content = content +) \ No newline at end of file diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt new file mode 100644 index 0000000000..b1d0d72539 --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt @@ -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 attributes */ + +fun AttrsBuilder.href(value: String?) = + attr("href", value) + +fun AttrsBuilder.target(value: ATarget = ATarget.Self) = + attr("target", value.targetStr) + +fun AttrsBuilder.ref(value: ARel) = + attr("rel", value.relStr) + +fun AttrsBuilder.ping(value: String) = + attr("ping", value) + +fun AttrsBuilder.ping(vararg urls: String) = + attr("ping", urls.joinToString(" ")) + +fun AttrsBuilder.hreflang(value: String) = + attr("hreflang", value) + +fun AttrsBuilder.download(value: String = "") = + attr("download", value) + +/* Button attributes */ + +fun AttrsBuilder.autoFocus(value: Boolean = true) = + attr("autofocus", if (value) "" else null) + +fun AttrsBuilder.disabled(value: Boolean = true) = + attr("disabled", if (value) "" else null) + +fun AttrsBuilder.form(formId: String) = + attr("form", formId) + +fun AttrsBuilder.formAction(url: String) = + attr("formaction", url) + +fun AttrsBuilder.formEncType(value: ButtonFormEncType) = + attr("formenctype", value.typeStr) + +fun AttrsBuilder.formMethod(value: ButtonFormMethod) = + attr("formmethod", value.methodStr) + +fun AttrsBuilder.formNoValidate(value: Boolean = true) = + attr("formnovalidate", if (value) "" else null) + +fun AttrsBuilder.formTarget(value: ButtonFormTarget) = + attr("formtarget", value.targetStr) + +fun AttrsBuilder.name(value: String) = + attr("name", value) + +fun AttrsBuilder.type(value: ButtonType) = + attr("type", value.str) + +fun AttrsBuilder.value(value: String) = + attr("value", value) + +/* Form attributes */ + +fun AttrsBuilder.action(value: String) = + attr("action", value) + +fun AttrsBuilder.acceptCharset(value: String) = + attr("accept-charset", value) + +fun AttrsBuilder.autoComplete(value: Boolean) = + attr("autocomplete", if (value) "" else null) + +fun AttrsBuilder.encType(value: FormEncType) = + attr("enctype", value.typeStr) + +fun AttrsBuilder.method(value: FormMethod) = + attr("method", value.methodStr) + +fun AttrsBuilder.noValidate(value: Boolean = true) = + attr("novalidate", if (value) "" else null) + +fun AttrsBuilder.target(value: FormTarget) = + attr("target", value.targetStr) + +/* Input attributes */ + +fun AttrsBuilder.type(value: InputType) = + attr("type", value.typeStr) + +fun AttrsBuilder.accept(value: String) = + attr("accept", value) // type: file only + +fun AttrsBuilder.alt(value: String) = + attr("alt", value) // type: image only + +fun AttrsBuilder.autoComplete(value: Boolean = true) = + attr("autocomplete", if (value) "" else null) + +fun AttrsBuilder.autoFocus(value: Boolean = true) = + attr("autofocus", if (value) "" else null) + +fun AttrsBuilder.capture(value: String) = + attr("capture", value) // type: file only + +fun AttrsBuilder.checked(value: Boolean = true) = + attr("checked", if (value) "" else null) // radio, checkbox + +fun AttrsBuilder.dirName(value: String) = + attr("dirname", value) // text, search + +fun AttrsBuilder.disabled(value: Boolean = true) = + attr("disabled", if (value) "" else null) + +fun AttrsBuilder.form(id: String) = + attr("form", id) + +fun AttrsBuilder.formAction(url: String) = + attr("formaction", url) + +fun AttrsBuilder.formEncType(value: InputFormEncType) = + attr("formenctype", value.typeStr) + +fun AttrsBuilder.formMethod(value: InputFormMethod) = + attr("formmethod", value.methodStr) + +fun AttrsBuilder.formNoValidate(value: Boolean = true) = + attr("formnovalidate", if (value) "" else null) + +fun AttrsBuilder.formTarget(value: InputFormTarget) = + attr("formtarget", value.targetStr) + +fun AttrsBuilder.height(value: Int) = + attr("height", value.toString()) // image only + +fun AttrsBuilder.width(value: Int) = + attr("width", value.toString()) // image only + +fun AttrsBuilder.list(dataListId: String) = + attr("list", dataListId) + +fun AttrsBuilder.max(value: String) = + attr("max", value) + +fun AttrsBuilder.maxLength(value: Int) = + attr("maxlength", value.toString()) + +fun AttrsBuilder.min(value: String) = + attr("min", value) + +fun AttrsBuilder.minLength(value: Int) = + attr("minlength", value.toString()) + +fun AttrsBuilder.multiple(value: Boolean = true) = + attr("multiple", if (value) "" else null) + +fun AttrsBuilder.name(value: String) = + attr("name", value) + +fun AttrsBuilder.pattern(value: String) = + attr("pattern", value) + +fun AttrsBuilder.placeholder(value: String) = + attr("placeholder", value) + +fun AttrsBuilder.readOnly(value: Boolean = true) = + attr("readonly", if (value) "" else null) + +fun AttrsBuilder.required(value: Boolean = true) = + attr("required", value.toString()) + +fun AttrsBuilder.size(value: Int) = + attr("size", value.toString()) + +fun AttrsBuilder.src(value: String) = + attr("src", value.toString()) // image only + +fun AttrsBuilder.step(value: Int) = + attr("step", value.toString()) // numeric types only + +fun AttrsBuilder.valueAttr(value: String) = + attr("value", value) + +fun AttrsBuilder.value(value: String): AttrsBuilder { + prop(setInputValue, value) + return this +} + +/* Option attributes */ + +fun AttrsBuilder.value(value: String) = + attr("value", value) + +fun AttrsBuilder.disabled(value: Boolean = true) = + attr("disabled", if (value) "" else null) + +fun AttrsBuilder.selected(value: Boolean = true) = + attr("selected", if (value) "" else null) + +fun AttrsBuilder.label(value: String) = + attr("label", value) + +/* Select attributes */ + +fun AttrsBuilder.autocomplete(value: String) = + attr("autocomplete", value) + +fun AttrsBuilder.autofocus(value: Boolean = true) = + attr("autofocus", if (value) "" else null) + +fun AttrsBuilder.disabled(value: Boolean = true) = + attr("disabled", if (value) "" else null) + +fun AttrsBuilder.form(formId: String) = + attr("form", formId) + +fun AttrsBuilder.multiple(value: Boolean = true) = + attr("multiple", if (value) "" else null) + +fun AttrsBuilder.name(value: String) = + attr("name", value) + +fun AttrsBuilder.required(value: Boolean = true) = + attr("required", if (value) "" else null) + +fun AttrsBuilder.size(numberOfRows: Int) = + attr("size", numberOfRows.toString()) + +/* OptGroup attributes */ + +fun AttrsBuilder.label(value: String) = + attr("label", value) + +fun AttrsBuilder.disabled(value: Boolean = true) = + attr("disabled", if (value) "" else null) + +/* TextArea attributes */ + +fun AttrsBuilder.autoComplete(value: Boolean = true) = + attr("autocomplete", if (value) "on" else "off") + +fun AttrsBuilder.autoFocus(value: Boolean = true) = + attr("autofocus", if (value) "" else null) + +fun AttrsBuilder.cols(value: Int) = + attr("cols", value.toString()) + +fun AttrsBuilder.disabled(value: Boolean = true) = + attr("disabled", if (value) "" else null) + +fun AttrsBuilder.form(formId: String) = + attr("form", formId) + +fun AttrsBuilder.maxLength(value: Int) = + attr("maxlength", value.toString()) + +fun AttrsBuilder.minLength(value: Int) = + attr("minlength", value.toString()) + +fun AttrsBuilder.name(value: String) = + attr("name", value) + +fun AttrsBuilder.placeholder(value: String) = + attr("placeholder", value) + +fun AttrsBuilder.readOnly(value: Boolean = true) = + attr("readonly", if (value) "" else null) + +fun AttrsBuilder.required(value: Boolean = true) = + attr("required", if (value) "" else null) + +fun AttrsBuilder.rows(value: Int) = + attr("rows", value.toString()) + +fun AttrsBuilder.wrap(value: TextAreaWrap) = + attr("wrap", value.str) + +fun AttrsBuilder.value(value: String): AttrsBuilder { + prop(setInputValue, value) + return this +} + +/* Img attributes */ + +fun AttrsBuilder.src(value: String?): AttrsBuilder = + attr("src", value) + +fun AttrsBuilder.alt(value: String?): AttrsBuilder = + attr("alt", value) + +private val setInputValue: (HTMLInputElement, String) -> Unit = { e, v -> + e.value = v +} + +/* Img attributes */ +fun AttrsBuilder.forId(value: String?): AttrsBuilder = + attr("for", value) + +/* Table attributes */ +fun AttrsBuilder.scope(value: Scope?): AttrsBuilder = + attr("scope", value?.str) + +fun AttrsBuilder.span(value: Int): AttrsBuilder = + attr("span", value.toString()) + +fun AttrsBuilder.colspan(value: Int): AttrsBuilder = + attr("colspan", value.toString()) + +fun AttrsBuilder.rowspan(value: Int): AttrsBuilder = + attr("rowspan", value.toString()) + +fun AttrsBuilder.colspan(value: Int): AttrsBuilder = + attr("colspan", value.toString()) + +fun AttrsBuilder.rowspan(value: Int): AttrsBuilder = + attr("rowspan", value.toString()) diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt new file mode 100644 index 0000000000..ff8842b401 --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt @@ -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 : EventsListenerBuilder() { + private val map = mutableMapOf() + + val propertyUpdates = mutableListOf 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 { + if (value == null && attr in map) { + map.remove(attr) + } else if (value != null) { + map[attr] = value + } + + return this + } + + @Suppress("UNCHECKED_CAST") + fun prop(update: (E, V) -> Unit, value: V) { + propertyUpdates.add((update to value) as Pair<(HTMLElement, Any) -> Unit, Any>) + } + + fun collect(): Map { + 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() + + operator fun String.unaryPlus() { + classes.add(this) + } + + fun asList(): List = classes + fun asString(): String = classes.joinToString(" ") +} + +val setClassList: (HTMLElement, Array) -> Unit = { e, classList -> + e.className = "" + e.classList.add(*classList) +} diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/EventsListenerBuilder.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/EventsListenerBuilder.kt new file mode 100644 index 0000000000..892f21792e --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/EventsListenerBuilder.kt @@ -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>() + + 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> = 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" + } +} \ No newline at end of file diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/PredefinedAttrValues.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/PredefinedAttrValues.kt new file mode 100644 index 0000000000..28b611c85b --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/PredefinedAttrValues.kt @@ -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") +} diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/WrappedEventListener.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/WrappedEventListener.kt new file mode 100644 index 0000000000..1e8d9c8cfe --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/WrappedEventListener.kt @@ -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>( + 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(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedMouseEvent(event as MouseEvent)) + } +} + +internal class MouseWheelEventListener( + event: String, + options: Options, + listener: (WrappedWheelEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedWheelEvent(event as WheelEvent)) + } +} + +internal class KeyboardEventListener( + event: String, + options: Options, + listener: (WrappedKeyboardEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedKeyboardEvent(event as KeyboardEvent)) + } +} + +internal class FocusEventListener( + event: String, + options: Options, + listener: (WrappedFocusEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedFocusEvent(event as FocusEvent)) + } +} + +internal class TouchEventListener( + event: String, + options: Options, + listener: (WrappedTouchEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedTouchEvent(event as TouchEvent)) + } +} + +internal class DragEventListener( + event: String, + options: Options, + listener: (WrappedDragEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedDragEvent(event as DragEvent)) + } +} + +internal class PointerEventListener( + event: String, + options: Options, + listener: (WrappedPointerEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedPointerEvent(event as PointerEvent)) + } +} + +internal class ClipboardEventListener( + event: String, + options: Options, + listener: (WrappedClipboardEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedClipboardEvent(event as ClipboardEvent)) + } +} + +internal class InputEventListener( + event: String, + options: Options, + listener: (WrappedInputEvent) -> Unit +) : WrappedEventListener(event, options, listener) { + override fun handleEvent(event: Event) { + listener(WrappedInputEvent(event as InputEvent)) + } +} + +internal class RadioInputEventListener( + options: Options, + listener: (WrappedRadioInputEvent) -> Unit +) : WrappedEventListener(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( + 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(EventsListenerBuilder.INPUT, options, listener) { + override fun handleEvent(event: Event) { + val text = event.target.asDynamic().value as String + listener(WrappedTextInputEvent(event as InputEvent, text)) + } +} diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/css/CSS.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/css/CSS.kt new file mode 100644 index 0000000000..01e2b6a73d --- /dev/null +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/css/CSS.kt @@ -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() + +inline fun StyleSheet.deleteRule(index: Int) { + this.asDynamic().deleteRule(index) +} + +inline val CSSStyleRule.styleMap + get() = this.asDynamic().styleMap.unsafeCast() + +inline operator fun CSSRuleList.get(index: Int): CSSRule { + return this.asDynamic()[index].unsafeCast() +} + +fun StyleSheet.insertRule(cssRule: String, index: Int? = null): Int { + return if (index != null) { + this.asDynamic().insertRule(cssRule, index).unsafeCast() + } else { + this.asDynamic().insertRule(cssRule).unsafeCast() + } +} + +val ElementCSSInlineStyle.attributeStyleMap + get() = this.asDynamic().attributeStyleMap.unsafeCast() + +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 + } +} + +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() + operator fun invoke(value: CSSVariableReferenceValue) = + value.unsafeCast() + } +} + +fun CSSUnparsedSegment.asString() = this.asDynamic() as? String +fun CSSUnparsedSegment.asCSSVariableReferenceValue() = + this.asDynamic() as? CSSVariableReferenceValue + +external class CSSUnparsedValue(members: Array) : CSSStyleValue { + // TODO: [Symbol.iterator]() : IterableIterator + 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() + operator fun invoke(value: CSSNumericValue) = + value.unsafeCast() + } +} + +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 : 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 + fun forEach(handler: (CSSNumericValue) -> Unit) + val length: Int + + // readonly [index: number]: CSSNumericValue + operator fun get(index: Int): CSSNumericValue +} + +external class CSSTransformValue(transforms: Array) : CSSStyleValue { + // [Symbol.iterator]() : IterableIterator + 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 + fun has(property: String): Boolean + val size: Number +} + +fun StylePropertyMapReadOnly.forEach(handler: (String, Array) -> Unit) { + this.asDynamic().forEach { entry: Array -> + handler( + entry[0].unsafeCast(), + entry[1].unsafeCast>() + ) + } +} + +// CSSStyleValue | string +interface StylePropertyValue { + companion object { + operator fun invoke(value: String) = value.unsafeCast() + operator fun invoke(value: Number) = value.unsafeCast() + operator fun invoke(value: CSSStyleValue) = value.unsafeCast() + } +} + +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() + +external class CSS { + companion object { + fun number(value: Number): CSSUnitValue + fun percent(value: Number): CSSUnitValue + + // + 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 + + // + fun deg(value: Number): CSSUnitValue + fun grad(value: Number): CSSUnitValue + fun rad(value: Number): CSSUnitValue + fun turn(value: Number): CSSUnitValue + + //