Browse Source

Pull web components from androidx-compose

pull/685/head
Shagen Ogandzhanian 4 years ago
parent
commit
f4987d0ce9
  1. 33
      .gitignore
  2. 4
      web/build.gradle
  3. 49
      web/core/build.gradle
  4. 41
      web/core/src/commonMain/kotlin/withWeb/Alignment.kt
  5. 26
      web/core/src/commonMain/kotlin/withWeb/Arrangement.kt
  6. 33
      web/core/src/commonMain/kotlin/withWeb/Color.kt
  7. 12
      web/core/src/commonMain/kotlin/withWeb/Modifier.kt
  8. 22
      web/core/src/commonMain/kotlin/withWeb/layouts/box.kt
  9. 26
      web/core/src/commonMain/kotlin/withWeb/layouts/button.kt
  10. 22
      web/core/src/commonMain/kotlin/withWeb/layouts/column.kt
  11. 25
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/BoxDefault.kt
  12. 28
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ButtonDefault.kt
  13. 25
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ColumnDefault.kt
  14. 22
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/ModifierDefault.kt
  15. 28
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/RowDefault.kt
  16. 36
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/SliderDefault.kt
  17. 31
      web/core/src/commonMain/kotlin/withWeb/layouts/defaults/TextDefault.kt
  18. 28
      web/core/src/commonMain/kotlin/withWeb/layouts/row.kt
  19. 20
      web/core/src/commonMain/kotlin/withWeb/layouts/shapes/shapes.kt
  20. 28
      web/core/src/commonMain/kotlin/withWeb/layouts/slider.kt
  21. 29
      web/core/src/commonMain/kotlin/withWeb/layouts/text.kt
  22. 22
      web/core/src/commonMain/kotlin/withWeb/modifiers/border.kt
  23. 20
      web/core/src/commonMain/kotlin/withWeb/modifiers/clickable.kt
  24. 21
      web/core/src/commonMain/kotlin/withWeb/modifiers/clip.kt
  25. 20
      web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxHeight.kt
  26. 20
      web/core/src/commonMain/kotlin/withWeb/modifiers/fillMaxWidth.kt
  27. 21
      web/core/src/commonMain/kotlin/withWeb/modifiers/offset.kt
  28. 23
      web/core/src/commonMain/kotlin/withWeb/modifiers/onSizeChanged.kt
  29. 20
      web/core/src/commonMain/kotlin/withWeb/modifiers/size.kt
  30. 21
      web/core/src/commonMain/kotlin/withWeb/modifiers/width.kt
  31. 18
      web/core/src/commonMain/kotlin/withWeb/units/Dp.kt
  32. 34
      web/core/src/commonMain/kotlin/withWeb/units/IntSize.kt
  33. 28
      web/core/src/commonMain/kotlin/withWeb/units/TextUnit.kt
  34. 33
      web/core/src/commonMain/kotlin/withWeb/units/unitConversion.kt
  35. 155
      web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt
  36. 87
      web/core/src/jsMain/kotlin/androidx/compose/web/GlobalSnapshotManager.kt
  37. 28
      web/core/src/jsMain/kotlin/androidx/compose/web/JsMicrotasksDispatcher.kt
  38. 93
      web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt
  39. 367
      web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt
  40. 95
      web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt
  41. 293
      web/core/src/jsMain/kotlin/androidx/compose/web/attributes/EventsListenerBuilder.kt
  42. 150
      web/core/src/jsMain/kotlin/androidx/compose/web/attributes/PredefinedAttrValues.kt
  43. 184
      web/core/src/jsMain/kotlin/androidx/compose/web/attributes/WrappedEventListener.kt
  44. 450
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSS.kt
  45. 39
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSBuilder.kt
  46. 43
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSHelpers.kt
  47. 153
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSMediaRule.kt
  48. 30
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSPolyfill.kt
  49. 427
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSProperties.kt
  50. 50
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSRules.kt
  51. 142
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSUnits.kt
  52. 49
      web/core/src/jsMain/kotlin/androidx/compose/web/css/Color.kt
  53. 136
      web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleBuilder.kt
  54. 133
      web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleSheet.kt
  55. 68
      web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleSheetBuilder.kt
  56. 257
      web/core/src/jsMain/kotlin/androidx/compose/web/css/selectors/CSSSelectors.kt
  57. 111
      web/core/src/jsMain/kotlin/androidx/compose/web/elements/Base.kt
  58. 120
      web/core/src/jsMain/kotlin/androidx/compose/web/elements/ElementScope.kt
  59. 577
      web/core/src/jsMain/kotlin/androidx/compose/web/elements/Elements.kt
  60. 155
      web/core/src/jsMain/kotlin/androidx/compose/web/elements/Style.kt
  61. 123
      web/core/src/jsMain/kotlin/androidx/compose/web/events/WrappedEvent.kt
  62. 47
      web/core/src/jsMain/kotlin/withWeb/Modifier.kt
  63. 63
      web/core/src/jsMain/kotlin/withWeb/Styles.kt
  64. 35
      web/core/src/jsMain/kotlin/withWeb/internal/ActualModifier.kt
  65. 33
      web/core/src/jsMain/kotlin/withWeb/layouts/box.kt
  66. 37
      web/core/src/jsMain/kotlin/withWeb/layouts/button.kt
  67. 35
      web/core/src/jsMain/kotlin/withWeb/layouts/column.kt
  68. 58
      web/core/src/jsMain/kotlin/withWeb/layouts/row.kt
  69. 47
      web/core/src/jsMain/kotlin/withWeb/layouts/slider.kt
  70. 55
      web/core/src/jsMain/kotlin/withWeb/layouts/text.kt
  71. 31
      web/core/src/jsMain/kotlin/withWeb/modifiers/border.kt
  72. 25
      web/core/src/jsMain/kotlin/withWeb/modifiers/clickable.kt
  73. 31
      web/core/src/jsMain/kotlin/withWeb/modifiers/clip.kt
  74. 27
      web/core/src/jsMain/kotlin/withWeb/modifiers/fillMaxHeight.kt
  75. 27
      web/core/src/jsMain/kotlin/withWeb/modifiers/fillMaxWidth.kt
  76. 30
      web/core/src/jsMain/kotlin/withWeb/modifiers/offset.kt
  77. 25
      web/core/src/jsMain/kotlin/withWeb/modifiers/onSizeChanged.kt
  78. 14
      web/core/src/jsMain/kotlin/withWeb/modifiers/size.kt
  79. 28
      web/core/src/jsMain/kotlin/withWeb/modifiers/width.kt
  80. 28
      web/core/src/jsMain/resources/index.html
  81. 126
      web/core/src/jsTest/kotlin/DomSideEffectTests.kt
  82. 153
      web/core/src/jsTest/kotlin/InlineStyleTests.kt
  83. 599
      web/core/src/jsTest/kotlin/StaticComposableTests.kt
  84. 94
      web/core/src/jsTest/kotlin/TestUtils.kt
  85. 63
      web/core/src/jsTest/kotlin/commonApi/ModifierTests.kt
  86. 99
      web/core/src/jsTest/kotlin/elements/AttributesTests.kt
  87. 115
      web/core/src/jsTest/kotlin/elements/EventTests.kt
  88. 204
      web/core/src/jsTest/kotlin/elements/TableTests.kt
  89. 25
      web/core/src/jvmMain/kotlin/withWeb/Alignment.kt
  90. 24
      web/core/src/jvmMain/kotlin/withWeb/Arrangement.kt
  91. 21
      web/core/src/jvmMain/kotlin/withWeb/Color.kt
  92. 35
      web/core/src/jvmMain/kotlin/withWeb/Modifier.kt
  93. 27
      web/core/src/jvmMain/kotlin/withWeb/internal/ActualModifier.kt
  94. 29
      web/core/src/jvmMain/kotlin/withWeb/layouts/box.kt
  95. 32
      web/core/src/jvmMain/kotlin/withWeb/layouts/button.kt
  96. 29
      web/core/src/jvmMain/kotlin/withWeb/layouts/column.kt
  97. 40
      web/core/src/jvmMain/kotlin/withWeb/layouts/row.kt
  98. 25
      web/core/src/jvmMain/kotlin/withWeb/layouts/shapes/shapes.kt
  99. 38
      web/core/src/jvmMain/kotlin/withWeb/layouts/slider.kt
  100. 40
      web/core/src/jvmMain/kotlin/withWeb/layouts/text.kt
  101. Some files were not shown because too many files have changed in this diff Show More

33
.gitignore vendored

@ -1,30 +1,3 @@
# Intellij files //gradle
build
.idea/* .gradle
!.idea/copyright
!.idea/scopes
# Mac OS files
.DS_Store
# Compiled class file
*.class
# Log file
*.log
# Package Files #
*.jar
!gradle-wrapper.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

4
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)
}

49
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)
}
}
}
}

41
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 {}
}
}

26
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 {}
}

33
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)
}
}

12
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

22
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)

26
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
)

22
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)

25
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) }

28
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)
}

25
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) }

22
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)
}

28
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) }

36
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<Float> = 0f..1f,
steps: Int = 0,
modifier: Modifier = Modifier.Companion
) {
SliderActual(
value,
onValueChange,
valueRange,
steps,
modifier
)
}

31
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)
}

28
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
)

20
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

28
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<Float>,
steps: Int,
modifier: Modifier,
)

29
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
)

22
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

20
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

21
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

20
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

20
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

21
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

23
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

20
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

21
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

18
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)

34
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)

28
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)
}
}

33
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)

155
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<DomNodeWrapper>(root) {
override fun insertTopDown(index: Int, instance: DomNodeWrapper) {
// ignored. Building tree bottom-up
}
override fun insertBottomUp(index: Int, instance: DomNodeWrapper) {
current.insert(index, instance)
}
override fun remove(index: Int, count: Int) {
current.remove(index, count)
}
override fun move(from: Int, to: Int, count: Int) {
current.move(from, to, count)
}
override fun onClear() {
// or current.node.clear()?; in all examples it calls 'clear' on the root
root.node.clear()
}
}
class DomNodeWrapper(val node: Node) {
constructor(tag: String) : this(document.createElement(tag))
private var currentListeners: List<WrappedEventListener<*>> = emptyList()
private var currentAttrs: Map<String, String?> = emptyMap()
fun updateProperties(list: List<Pair<(HTMLElement, Any) -> Unit, Any>>) {
val htmlElement = node as? HTMLElement ?: return
list.forEach { it.first(htmlElement, it.second) }
}
fun updateEventListeners(list: List<WrappedEventListener<*>>) {
val htmlElement = node as? HTMLElement ?: return
currentListeners.forEach {
htmlElement.removeEventListener(it.event, it)
}
currentListeners = list
currentListeners.forEach {
htmlElement.addEventListener(it.event, it)
}
}
fun updateAttrs(attrs: Map<String, String?>) {
val htmlElement = node as? HTMLElement ?: return
currentAttrs.forEach {
htmlElement.removeAttribute(it.key)
}
currentAttrs = attrs
currentAttrs.forEach {
if (it.value != null) htmlElement.setAttribute(it.key, it.value ?: "")
}
}
fun updateStyleDeclarations(style: StyleHolder?) {
val htmlElement = node as? HTMLElement ?: return
// TODO: typed-om-polyfill hasn't StylePropertyMap::clear()
htmlElement.style.cssText = ""
style?.properties?.forEach { (name, value) ->
setProperty(htmlElement.attributeStyleMap, name, value)
}
style?.variables?.forEach { (name, value) ->
setVariable(htmlElement.style, name, value)
}
}
fun insert(index: Int, nodeWrapper: DomNodeWrapper) {
val length = node.childNodes.length
if (index < length) {
node.insertBefore(nodeWrapper.node, node.childNodes[index]!!)
} else {
node.appendChild(nodeWrapper.node)
}
}
fun remove(index: Int, count: Int) {
repeat(count) {
node.removeChild(node.childNodes[index]!!)
}
}
fun move(from: Int, to: Int, count: Int) {
if (from == to) {
return // nothing to do
}
for (i in 0 until count) {
// if "from" is after "to," the from index moves because we're inserting before it
val fromIndex = if (from > to) from + i else from
val toIndex = if (from > to) to + i else to + count - 2
val child = node.removeChild(node.childNodes[fromIndex]!!)
node.insertBefore(child, node.childNodes[toIndex]!!)
}
}
companion object {
val UpdateAttrs: DomNodeWrapper.(Map<String, String?>) -> Unit = {
this.updateAttrs(it)
}
val UpdateListeners: DomNodeWrapper.(List<WrappedEventListener<*>>) -> Unit = {
this.updateEventListeners(it)
}
val UpdateProperties: DomNodePropertiesUpdater = {
this.updateProperties(it)
}
val UpdateStyleDeclarations: DomNodeWrapper.(StyleHolder?) -> Unit = {
this.updateStyleDeclarations(it)
}
}
}
typealias DomNodePropertiesUpdater =
DomNodeWrapper.(List<Pair<(HTMLElement, Any) -> Unit, Any>>) -> Unit

87
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() }
}
}
}

28
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() }
}
}

93
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 <THTMLElement : HTMLElement> renderComposable(
root: THTMLElement,
content: @Composable DOMScope<THTMLElement>.() -> Unit
): Composition {
GlobalSnapshotManager.ensureStarted()
val context = DefaultMonotonicFrameClock + JsMicrotasksDispatcher()
val recomposer = Recomposer(context)
val composition = ControlledComposition(
applier = DomApplier(DomNodeWrapper(root)),
parent = recomposer
)
val scope = object : DOMScope<THTMLElement> {}
composition.setContent @Composable {
content(scope)
}
CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
return composition
}
/**
* Use this method to mount the composition at the element with id - [rootElementId].
*
* @param rootElementId - the id of the [HTMLElement] that will be the root of the DOM tree managed
* by Compose
* @param content - the Composable lambda that defines the composition content
*
* @return the instance of the [Composition]
*/
@Suppress("UNCHECKED_CAST")
fun renderComposable(
rootElementId: String,
content: @Composable DOMScope<HTMLElement>.() -> Unit
): Composition = renderComposable(
root = document.getElementById(rootElementId) as HTMLElement,
content = content
)
/**
* Use this method to mount the composition at the [HTMLBodyElement] of the current document
*
* @param content - the Composable lambda that defines the composition content
*
* @return the instance of the [Composition]
*/
fun renderComposableInBody(
content: @Composable DOMScope<HTMLBodyElement>.() -> Unit
): Composition = renderComposable(
root = document.getElementsByTagName("body")[0] as HTMLBodyElement,
content = content
)

367
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 <a> attributes */
fun AttrsBuilder<Tag.A>.href(value: String?) =
attr("href", value)
fun AttrsBuilder<Tag.A>.target(value: ATarget = ATarget.Self) =
attr("target", value.targetStr)
fun AttrsBuilder<Tag.A>.ref(value: ARel) =
attr("rel", value.relStr)
fun AttrsBuilder<Tag.A>.ping(value: String) =
attr("ping", value)
fun AttrsBuilder<Tag.A>.ping(vararg urls: String) =
attr("ping", urls.joinToString(" "))
fun AttrsBuilder<Tag.A>.hreflang(value: String) =
attr("hreflang", value)
fun AttrsBuilder<Tag.A>.download(value: String = "") =
attr("download", value)
/* Button attributes */
fun AttrsBuilder<Tag.Button>.autoFocus(value: Boolean = true) =
attr("autofocus", if (value) "" else null)
fun AttrsBuilder<Tag.Button>.disabled(value: Boolean = true) =
attr("disabled", if (value) "" else null)
fun AttrsBuilder<Tag.Button>.form(formId: String) =
attr("form", formId)
fun AttrsBuilder<Tag.Button>.formAction(url: String) =
attr("formaction", url)
fun AttrsBuilder<Tag.Button>.formEncType(value: ButtonFormEncType) =
attr("formenctype", value.typeStr)
fun AttrsBuilder<Tag.Button>.formMethod(value: ButtonFormMethod) =
attr("formmethod", value.methodStr)
fun AttrsBuilder<Tag.Button>.formNoValidate(value: Boolean = true) =
attr("formnovalidate", if (value) "" else null)
fun AttrsBuilder<Tag.Button>.formTarget(value: ButtonFormTarget) =
attr("formtarget", value.targetStr)
fun AttrsBuilder<Tag.Button>.name(value: String) =
attr("name", value)
fun AttrsBuilder<Tag.Button>.type(value: ButtonType) =
attr("type", value.str)
fun AttrsBuilder<Tag.Button>.value(value: String) =
attr("value", value)
/* Form attributes */
fun AttrsBuilder<Tag.Form>.action(value: String) =
attr("action", value)
fun AttrsBuilder<Tag.Form>.acceptCharset(value: String) =
attr("accept-charset", value)
fun AttrsBuilder<Tag.Form>.autoComplete(value: Boolean) =
attr("autocomplete", if (value) "" else null)
fun AttrsBuilder<Tag.Form>.encType(value: FormEncType) =
attr("enctype", value.typeStr)
fun AttrsBuilder<Tag.Form>.method(value: FormMethod) =
attr("method", value.methodStr)
fun AttrsBuilder<Tag.Form>.noValidate(value: Boolean = true) =
attr("novalidate", if (value) "" else null)
fun AttrsBuilder<Tag.Form>.target(value: FormTarget) =
attr("target", value.targetStr)
/* Input attributes */
fun AttrsBuilder<Tag.Input>.type(value: InputType) =
attr("type", value.typeStr)
fun AttrsBuilder<Tag.Input>.accept(value: String) =
attr("accept", value) // type: file only
fun AttrsBuilder<Tag.Input>.alt(value: String) =
attr("alt", value) // type: image only
fun AttrsBuilder<Tag.Input>.autoComplete(value: Boolean = true) =
attr("autocomplete", if (value) "" else null)
fun AttrsBuilder<Tag.Input>.autoFocus(value: Boolean = true) =
attr("autofocus", if (value) "" else null)
fun AttrsBuilder<Tag.Input>.capture(value: String) =
attr("capture", value) // type: file only
fun AttrsBuilder<Tag.Input>.checked(value: Boolean = true) =
attr("checked", if (value) "" else null) // radio, checkbox
fun AttrsBuilder<Tag.Input>.dirName(value: String) =
attr("dirname", value) // text, search
fun AttrsBuilder<Tag.Input>.disabled(value: Boolean = true) =
attr("disabled", if (value) "" else null)
fun AttrsBuilder<Tag.Input>.form(id: String) =
attr("form", id)
fun AttrsBuilder<Tag.Input>.formAction(url: String) =
attr("formaction", url)
fun AttrsBuilder<Tag.Input>.formEncType(value: InputFormEncType) =
attr("formenctype", value.typeStr)
fun AttrsBuilder<Tag.Input>.formMethod(value: InputFormMethod) =
attr("formmethod", value.methodStr)
fun AttrsBuilder<Tag.Input>.formNoValidate(value: Boolean = true) =
attr("formnovalidate", if (value) "" else null)
fun AttrsBuilder<Tag.Input>.formTarget(value: InputFormTarget) =
attr("formtarget", value.targetStr)
fun AttrsBuilder<Tag.Input>.height(value: Int) =
attr("height", value.toString()) // image only
fun AttrsBuilder<Tag.Input>.width(value: Int) =
attr("width", value.toString()) // image only
fun AttrsBuilder<Tag.Input>.list(dataListId: String) =
attr("list", dataListId)
fun AttrsBuilder<Tag.Input>.max(value: String) =
attr("max", value)
fun AttrsBuilder<Tag.Input>.maxLength(value: Int) =
attr("maxlength", value.toString())
fun AttrsBuilder<Tag.Input>.min(value: String) =
attr("min", value)
fun AttrsBuilder<Tag.Input>.minLength(value: Int) =
attr("minlength", value.toString())
fun AttrsBuilder<Tag.Input>.multiple(value: Boolean = true) =
attr("multiple", if (value) "" else null)
fun AttrsBuilder<Tag.Input>.name(value: String) =
attr("name", value)
fun AttrsBuilder<Tag.Input>.pattern(value: String) =
attr("pattern", value)
fun AttrsBuilder<Tag.Input>.placeholder(value: String) =
attr("placeholder", value)
fun AttrsBuilder<Tag.Input>.readOnly(value: Boolean = true) =
attr("readonly", if (value) "" else null)
fun AttrsBuilder<Tag.Input>.required(value: Boolean = true) =
attr("required", value.toString())
fun AttrsBuilder<Tag.Input>.size(value: Int) =
attr("size", value.toString())
fun AttrsBuilder<Tag.Input>.src(value: String) =
attr("src", value.toString()) // image only
fun AttrsBuilder<Tag.Input>.step(value: Int) =
attr("step", value.toString()) // numeric types only
fun AttrsBuilder<Tag.Input>.valueAttr(value: String) =
attr("value", value)
fun AttrsBuilder<Tag.Input>.value(value: String): AttrsBuilder<Tag.Input> {
prop(setInputValue, value)
return this
}
/* Option attributes */
fun AttrsBuilder<Tag.Option>.value(value: String) =
attr("value", value)
fun AttrsBuilder<Tag.Option>.disabled(value: Boolean = true) =
attr("disabled", if (value) "" else null)
fun AttrsBuilder<Tag.Option>.selected(value: Boolean = true) =
attr("selected", if (value) "" else null)
fun AttrsBuilder<Tag.Option>.label(value: String) =
attr("label", value)
/* Select attributes */
fun AttrsBuilder<Tag.Select>.autocomplete(value: String) =
attr("autocomplete", value)
fun AttrsBuilder<Tag.Select>.autofocus(value: Boolean = true) =
attr("autofocus", if (value) "" else null)
fun AttrsBuilder<Tag.Select>.disabled(value: Boolean = true) =
attr("disabled", if (value) "" else null)
fun AttrsBuilder<Tag.Select>.form(formId: String) =
attr("form", formId)
fun AttrsBuilder<Tag.Select>.multiple(value: Boolean = true) =
attr("multiple", if (value) "" else null)
fun AttrsBuilder<Tag.Select>.name(value: String) =
attr("name", value)
fun AttrsBuilder<Tag.Select>.required(value: Boolean = true) =
attr("required", if (value) "" else null)
fun AttrsBuilder<Tag.Select>.size(numberOfRows: Int) =
attr("size", numberOfRows.toString())
/* OptGroup attributes */
fun AttrsBuilder<Tag.OptGroup>.label(value: String) =
attr("label", value)
fun AttrsBuilder<Tag.OptGroup>.disabled(value: Boolean = true) =
attr("disabled", if (value) "" else null)
/* TextArea attributes */
fun AttrsBuilder<Tag.TextArea>.autoComplete(value: Boolean = true) =
attr("autocomplete", if (value) "on" else "off")
fun AttrsBuilder<Tag.TextArea>.autoFocus(value: Boolean = true) =
attr("autofocus", if (value) "" else null)
fun AttrsBuilder<Tag.TextArea>.cols(value: Int) =
attr("cols", value.toString())
fun AttrsBuilder<Tag.TextArea>.disabled(value: Boolean = true) =
attr("disabled", if (value) "" else null)
fun AttrsBuilder<Tag.TextArea>.form(formId: String) =
attr("form", formId)
fun AttrsBuilder<Tag.TextArea>.maxLength(value: Int) =
attr("maxlength", value.toString())
fun AttrsBuilder<Tag.TextArea>.minLength(value: Int) =
attr("minlength", value.toString())
fun AttrsBuilder<Tag.TextArea>.name(value: String) =
attr("name", value)
fun AttrsBuilder<Tag.TextArea>.placeholder(value: String) =
attr("placeholder", value)
fun AttrsBuilder<Tag.TextArea>.readOnly(value: Boolean = true) =
attr("readonly", if (value) "" else null)
fun AttrsBuilder<Tag.TextArea>.required(value: Boolean = true) =
attr("required", if (value) "" else null)
fun AttrsBuilder<Tag.TextArea>.rows(value: Int) =
attr("rows", value.toString())
fun AttrsBuilder<Tag.TextArea>.wrap(value: TextAreaWrap) =
attr("wrap", value.str)
fun AttrsBuilder<Tag.TextArea>.value(value: String): AttrsBuilder<Tag.TextArea> {
prop(setInputValue, value)
return this
}
/* Img attributes */
fun AttrsBuilder<Tag.Img>.src(value: String?): AttrsBuilder<Tag.Img> =
attr("src", value)
fun AttrsBuilder<Tag.Img>.alt(value: String?): AttrsBuilder<Tag.Img> =
attr("alt", value)
private val setInputValue: (HTMLInputElement, String) -> Unit = { e, v ->
e.value = v
}
/* Img attributes */
fun AttrsBuilder<Tag.Label>.forId(value: String?): AttrsBuilder<Tag.Label> =
attr("for", value)
/* Table attributes */
fun AttrsBuilder<Tag.Th>.scope(value: Scope?): AttrsBuilder<Tag.Th> =
attr("scope", value?.str)
fun AttrsBuilder<Tag.Col>.span(value: Int): AttrsBuilder<Tag.Col> =
attr("span", value.toString())
fun AttrsBuilder<Tag.Th>.colspan(value: Int): AttrsBuilder<Tag.Th> =
attr("colspan", value.toString())
fun AttrsBuilder<Tag.Th>.rowspan(value: Int): AttrsBuilder<Tag.Th> =
attr("rowspan", value.toString())
fun AttrsBuilder<Tag.Td>.colspan(value: Int): AttrsBuilder<Tag.Td> =
attr("colspan", value.toString())
fun AttrsBuilder<Tag.Td>.rowspan(value: Int): AttrsBuilder<Tag.Td> =
attr("rowspan", value.toString())

95
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<TTag : Tag> : EventsListenerBuilder() {
private val map = mutableMapOf<String, String>()
val propertyUpdates = mutableListOf<Pair<(HTMLElement, Any) -> Unit, Any>>()
var refEffect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null
inline fun classes(builder: ClassesAttrBuilder.() -> Unit) =
prop(setClassList, ClassesAttrBuilder().apply(builder).asList().toTypedArray())
fun classes(vararg classes: String) = prop(setClassList, classes)
fun id(value: String) = attr(ID, value)
fun hidden(value: Boolean) = attr(HIDDEN, value.toString())
fun title(value: String) = attr(TITLE, value)
fun dir(value: DirType) = attr(DIR, value.dirStr)
fun draggable(value: Draggable) = attr(DRAGGABLE, value.str)
fun contentEditable(value: Boolean) = attr(CONTENT_EDITABLE, value.toString())
fun lang(value: String) = attr(LANG, value)
fun tabIndex(value: Int) = attr(TAB_INDEX, value.toString())
fun spellCheck(value: Boolean) = attr(SPELLCHECK, value.toString())
fun ref(effect: DisposableEffectScope.(HTMLElement) -> DisposableEffectResult) {
this.refEffect = effect
}
fun attr(attr: String, value: String?): AttrsBuilder<TTag> {
if (value == null && attr in map) {
map.remove(attr)
} else if (value != null) {
map[attr] = value
}
return this
}
@Suppress("UNCHECKED_CAST")
fun <E : HTMLElement, V : Any> prop(update: (E, V) -> Unit, value: V) {
propertyUpdates.add((update to value) as Pair<(HTMLElement, Any) -> Unit, Any>)
}
fun collect(): Map<String, String> {
return map
}
companion object {
const val CLASS = "class"
const val ID = "id"
const val HIDDEN = "hidden"
const val TITLE = "title"
const val DIR = "dir"
const val DRAGGABLE = "draggable"
const val CONTENT_EDITABLE = "contenteditable"
const val LANG = "lang"
const val TAB_INDEX = "tabindex"
const val SPELLCHECK = "spellcheck"
}
}
class ClassesAttrBuilder {
private val classes = mutableListOf<String>()
operator fun String.unaryPlus() {
classes.add(this)
}
fun asList(): List<String> = classes
fun asString(): String = classes.joinToString(" ")
}
val setClassList: (HTMLElement, Array<out String>) -> Unit = { e, classList ->
e.className = ""
e.classList.add(*classList)
}

293
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<WrappedEventListener<*>>()
fun onCopy(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) {
listeners.add(ClipboardEventListener(COPY, options, listener))
}
fun onCut(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) {
listeners.add(ClipboardEventListener(CUT, options, listener))
}
fun onPaste(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) {
listeners.add(ClipboardEventListener(PASTE, options, listener))
}
fun onContextMenu(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(CONTEXTMENU, options, listener))
}
fun onClick(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(CLICK, options, listener))
}
fun onDoubleClick(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(DBLCLICK, options, listener))
}
fun onInput(options: Options = Options.DEFAULT, listener: (WrappedInputEvent) -> Unit) {
listeners.add(InputEventListener(INPUT, options, listener))
}
fun onTextInput(options: Options = Options.DEFAULT, listener: (WrappedTextInputEvent) -> Unit) {
listeners.add(TextInputEventListener(options, listener))
}
fun onCheckboxInput(
options: Options = Options.DEFAULT,
listener: (WrappedCheckBoxInputEvent) -> Unit
) {
listeners.add(CheckBoxInputEventListener(options, listener))
}
fun onRadioInput(
options: Options = Options.DEFAULT,
listener: (WrappedRadioInputEvent) -> Unit
) {
listeners.add(RadioInputEventListener(options, listener))
}
fun onRangeInput(
options: Options = Options.DEFAULT,
listener: (GenericWrappedEvent<*>) -> Unit
) {
listeners.add(WrappedEventListener(INPUT, options, listener))
}
fun onGenericInput(
options: Options = Options.DEFAULT,
listener: (GenericWrappedEvent<*>) -> Unit
) {
listeners.add(WrappedEventListener(INPUT, options, listener))
}
fun onChange(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(CHANGE, options, listener))
}
fun onInvalid(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(INVALID, options, listener))
}
fun onSearch(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(SEARCH, options, listener))
}
fun onFocus(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) {
listeners.add(FocusEventListener(FOCUS, options, listener))
}
fun onBlur(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) {
listeners.add(FocusEventListener(BLUR, options, listener))
}
fun onFocusIn(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) {
listeners.add(FocusEventListener(FOCUSIN, options, listener))
}
fun onFocusOut(options: Options = Options.DEFAULT, listener: (WrappedFocusEvent) -> Unit) {
listeners.add(FocusEventListener(FOCUSOUT, options, listener))
}
fun onKeyDown(options: Options = Options.DEFAULT, listener: (WrappedKeyboardEvent) -> Unit) {
listeners.add(KeyboardEventListener(KEYDOWN, options, listener))
}
fun onKeyUp(options: Options = Options.DEFAULT, listener: (WrappedKeyboardEvent) -> Unit) {
listeners.add(KeyboardEventListener(KEYUP, options, listener))
}
fun onMouseDown(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSEDOWN, options, listener))
}
fun onMouseUp(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSEUP, options, listener))
}
fun onMouseEnter(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSEENTER, options, listener))
}
fun onMouseLeave(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSELEAVE, options, listener))
}
fun onMouseMove(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSEMOVE, options, listener))
}
fun onMouseOut(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSEOUT, options, listener))
}
fun onMouseOver(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) {
listeners.add(MouseEventListener(MOUSEOVER, options, listener))
}
fun onWheel(options: Options = Options.DEFAULT, listener: (WrappedWheelEvent) -> Unit) {
listeners.add(MouseWheelEventListener(WHEEL, options, listener))
}
fun onScroll(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(SCROLL, options, listener))
}
fun onSelect(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(SELECT, options, listener))
}
fun onTouchCancel(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) {
listeners.add(TouchEventListener(TOUCHCANCEL, options, listener))
}
fun onTouchMove(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) {
listeners.add(TouchEventListener(TOUCHMOVE, options, listener))
}
fun onTouchEnd(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) {
listeners.add(TouchEventListener(TOUCHEND, options, listener))
}
fun onTouchStart(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) {
listeners.add(TouchEventListener(TOUCHSTART, options, listener))
}
fun onAnimationEnd(options: Options = Options.DEFAULT, listener: (WrappedTouchEvent) -> Unit) {
listeners.add(WrappedEventListener(ANIMATIONEND, options, listener))
}
fun onAnimationIteration(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(ANIMATIONITERATION, options, listener))
}
fun onAnimationStart(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) {
listeners.add(WrappedEventListener(ANIMATIONSTART, options, listener))
}
fun onBeforeInput(options: Options = Options.DEFAULT, listener: (WrappedInputEvent) -> Unit) {
listeners.add(InputEventListener(BEFOREINPUT, options, listener))
}
fun onDrag(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DRAG, options, listener))
}
fun onDrop(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DROP, options, listener))
}
fun onDragStart(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DRAGSTART, options, listener))
}
fun onDragEnd(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DRAGEND, options, listener))
}
fun onDragOver(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DRAGOVER, options, listener))
}
fun onDragEnter(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DRAGENTER, options, listener))
}
fun onDragLeave(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) {
listeners.add(DragEventListener(DRAGLEAVE, options, listener))
}
fun asList(): List<WrappedEventListener<*>> = listeners
fun addEventListener(
eventName: String,
options: Options = Options.DEFAULT,
listener: (WrappedEvent) -> Unit
) {
listeners.add(WrappedEventListener(eventName, options, listener))
}
companion object {
const val COPY = "copy"
const val CUT = "cut"
const val PASTE = "paste"
const val CONTEXTMENU = "contextmenu"
const val CLICK = "click"
const val DBLCLICK = "dblclick"
const val FOCUS = "focus"
const val BLUR = "blur"
const val FOCUSIN = "focusin"
const val FOCUSOUT = "focusout"
const val KEYDOWN = "keydown"
const val KEYUP = "keyup"
const val MOUSEDOWN = "mousedown"
const val MOUSEUP = "mouseup"
const val MOUSEENTER = "mouseenter"
const val MOUSELEAVE = "mouseleave"
const val MOUSEMOVE = "mousemove"
const val MOUSEOUT = "mouseout"
const val MOUSEOVER = "mouseover"
const val WHEEL = "wheel"
const val SCROLL = "scroll"
const val SELECT = "select"
const val TOUCHCANCEL = "touchcancel"
const val TOUCHEND = "touchend"
const val TOUCHMOVE = "touchmove"
const val TOUCHSTART = "touchstart"
const val ANIMATIONCANCEL = "animationcancel" // firefox and safari only
const val ANIMATIONEND = "animationend"
const val ANIMATIONITERATION = "animationiteration"
const val ANIMATIONSTART = "animationstart"
const val BEFOREINPUT = "beforeinput"
const val INPUT = "input"
const val CHANGE = "change"
const val INVALID = "invalid"
const val SEARCH = "search"
const val DRAG = "drag"
const val DROP = "drop"
const val DRAGSTART = "dragstart"
const val DRAGEND = "dragend"
const val DRAGOVER = "dragover"
const val DRAGENTER = "dragenter"
const val DRAGLEAVE = "dragleave"
}
}

150
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")
}

184
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<T : GenericWrappedEvent<*>>(
val event: String,
val options: Options,
val listener: (T) -> Unit
) : org.w3c.dom.events.EventListener {
@Suppress("UNCHECKED_CAST")
override fun handleEvent(event: Event) {
listener(WrappedEventImpl(event) as T)
}
}
class Options {
// TODO: add options for addEventListener
companion object {
val DEFAULT = Options()
}
}
internal class MouseEventListener(
event: String,
options: Options,
listener: (WrappedMouseEvent) -> Unit
) : WrappedEventListener<WrappedMouseEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedMouseEvent(event as MouseEvent))
}
}
internal class MouseWheelEventListener(
event: String,
options: Options,
listener: (WrappedWheelEvent) -> Unit
) : WrappedEventListener<WrappedWheelEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedWheelEvent(event as WheelEvent))
}
}
internal class KeyboardEventListener(
event: String,
options: Options,
listener: (WrappedKeyboardEvent) -> Unit
) : WrappedEventListener<WrappedKeyboardEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedKeyboardEvent(event as KeyboardEvent))
}
}
internal class FocusEventListener(
event: String,
options: Options,
listener: (WrappedFocusEvent) -> Unit
) : WrappedEventListener<WrappedFocusEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedFocusEvent(event as FocusEvent))
}
}
internal class TouchEventListener(
event: String,
options: Options,
listener: (WrappedTouchEvent) -> Unit
) : WrappedEventListener<WrappedTouchEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedTouchEvent(event as TouchEvent))
}
}
internal class DragEventListener(
event: String,
options: Options,
listener: (WrappedDragEvent) -> Unit
) : WrappedEventListener<WrappedDragEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedDragEvent(event as DragEvent))
}
}
internal class PointerEventListener(
event: String,
options: Options,
listener: (WrappedPointerEvent) -> Unit
) : WrappedEventListener<WrappedPointerEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedPointerEvent(event as PointerEvent))
}
}
internal class ClipboardEventListener(
event: String,
options: Options,
listener: (WrappedClipboardEvent) -> Unit
) : WrappedEventListener<WrappedClipboardEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedClipboardEvent(event as ClipboardEvent))
}
}
internal class InputEventListener(
event: String,
options: Options,
listener: (WrappedInputEvent) -> Unit
) : WrappedEventListener<WrappedInputEvent>(event, options, listener) {
override fun handleEvent(event: Event) {
listener(WrappedInputEvent(event as InputEvent))
}
}
internal class RadioInputEventListener(
options: Options,
listener: (WrappedRadioInputEvent) -> Unit
) : WrappedEventListener<WrappedRadioInputEvent>(EventsListenerBuilder.INPUT, options, listener) {
override fun handleEvent(event: Event) {
val checked = event.target.asDynamic().checked as Boolean
listener(WrappedRadioInputEvent(event, checked))
}
}
internal class CheckBoxInputEventListener(
options: Options,
listener: (WrappedCheckBoxInputEvent) -> Unit
) : WrappedEventListener<WrappedCheckBoxInputEvent>(
EventsListenerBuilder.INPUT, options, listener
) {
override fun handleEvent(event: Event) {
val checked = event.target.asDynamic().checked as Boolean
listener(WrappedCheckBoxInputEvent(event, checked))
}
}
internal class TextInputEventListener(
options: Options,
listener: (WrappedTextInputEvent) -> Unit
) : WrappedEventListener<WrappedTextInputEvent>(EventsListenerBuilder.INPUT, options, listener) {
override fun handleEvent(event: Event) {
val text = event.target.asDynamic().value as String
listener(WrappedTextInputEvent(event as InputEvent, text))
}
}

450
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<CSSRuleList>()
inline fun StyleSheet.deleteRule(index: Int) {
this.asDynamic().deleteRule(index)
}
inline val CSSStyleRule.styleMap
get() = this.asDynamic().styleMap.unsafeCast<StylePropertyMap>()
inline operator fun CSSRuleList.get(index: Int): CSSRule {
return this.asDynamic()[index].unsafeCast<CSSRule>()
}
fun StyleSheet.insertRule(cssRule: String, index: Int? = null): Int {
return if (index != null) {
this.asDynamic().insertRule(cssRule, index).unsafeCast<Int>()
} else {
this.asDynamic().insertRule(cssRule).unsafeCast<Int>()
}
}
val ElementCSSInlineStyle.attributeStyleMap
get() = this.asDynamic().attributeStyleMap.unsafeCast<StylePropertyMap>()
external interface CSSStyleValue {
// toString() : string
}
@JsName("CSSStyleValue")
open external class CSSStyleValueJS : CSSStyleValue {
companion object {
fun parse(property: String, cssText: String): CSSStyleValue
fun parseAll(property: String, cssText: String): Array<CSSStyleValue>
}
}
external class CSSVariableReferenceValue(
variable: String,
fallback: CSSUnparsedValue? = definedExternally
) {
val variable: String
val fallback: CSSUnparsedValue?
}
// type CSSUnparsedSegment = String | CSSVariableReferenceValue
interface CSSUnparsedSegment {
companion object {
operator fun invoke(value: String) = value.unsafeCast<CSSUnparsedSegment>()
operator fun invoke(value: CSSVariableReferenceValue) =
value.unsafeCast<CSSUnparsedSegment>()
}
}
fun CSSUnparsedSegment.asString() = this.asDynamic() as? String
fun CSSUnparsedSegment.asCSSVariableReferenceValue() =
this.asDynamic() as? CSSVariableReferenceValue
external class CSSUnparsedValue(members: Array<CSSUnparsedSegment>) : CSSStyleValue {
// TODO: [Symbol.iterator]() : IterableIterator<CSSUnparsedSegment>
fun forEach(handler: (CSSUnparsedSegment) -> Unit)
val length: Int
// readonly [index: number]: CSSUnparsedSegment
operator fun get(index: Int): CSSUnparsedSegment
operator fun set(index: Int, value: CSSUnparsedSegment)
}
external interface CSSKeywordValue : CSSStyleValue {
val value: String
}
@JsName("CSSKeywordValue")
external class CSSKeywordValueJS(value: String) : CSSKeywordValue {
override val value: String
}
// type CSSNumberish = number | CSSNumericValue
interface CSSNumberish {
companion object {
operator fun invoke(value: Number) = value.unsafeCast<CSSNumberish>()
operator fun invoke(value: CSSNumericValue) =
value.unsafeCast<CSSNumberish>()
}
}
fun CSSNumberish.asNumber() = this.asDynamic() as? Number
fun CSSNumberish.asCSSNumericValue(): CSSNumericValue? = this.asDynamic() as? CSSNumericValueJS
// declare enum CSSNumericBaseType {
// 'length',
// 'angle',
// 'time',
// 'frequency',
// 'resolution',
// 'flex',
// 'percent',
// }
enum class CSSNumericBaseType(val value: String) {
@JsName("_length")
length("length"),
angle("angle"),
time("time"),
frequency("frequency"),
resolution("resolution"),
flex("flex"),
percent("percent")
}
external interface CSSNumericType {
val length: Number
val angle: Number
val time: Number
val frequency: Number
val resolution: Number
val flex: Number
val percent: Number
// percentHint: CSSNumericBaseType
}
val CSSNumericType.percentHint
get() = CSSNumericBaseType.valueOf(this.asDynamic().percentHint)
// set(value) { this.asDynamic().percentHint = value.value }
external interface CSSNumericValue : CSSStyleValue {
fun add(vararg values: CSSNumberish): CSSNumericValue
fun sub(vararg values: CSSNumberish): CSSNumericValue
fun mul(vararg values: CSSNumberish): CSSNumericValue
fun div(vararg values: CSSNumberish): CSSNumericValue
fun min(vararg values: CSSNumberish): CSSNumericValue
fun max(vararg values: CSSNumberish): CSSNumericValue
fun equals(vararg values: CSSNumberish): Boolean
fun to(unit: String): CSSUnitValue
fun toSum(vararg units: String): CSSMathSum
fun type(): CSSNumericType
}
abstract external class CSSNumericValueJS : CSSNumericValue {
override fun add(vararg values: CSSNumberish): CSSNumericValue
override fun sub(vararg values: CSSNumberish): CSSNumericValue
override fun mul(vararg values: CSSNumberish): CSSNumericValue
override fun div(vararg values: CSSNumberish): CSSNumericValue
override fun min(vararg values: CSSNumberish): CSSNumericValue
override fun max(vararg values: CSSNumberish): CSSNumericValue
override fun equals(vararg values: CSSNumberish): Boolean
override fun to(unit: String): CSSUnitValue
override fun toSum(vararg units: String): CSSMathSum
override fun type(): CSSNumericType
companion object {
fun parse(cssText: String): CSSNumericValue
}
}
external interface CSSUnitValue : CSSNumericValue {
val value: Number
val unit: String
}
external interface CSSTypedUnitValue<T> : CSSNumericValue {
val value: Number
val unit: T
}
@JsName("CSSUnitValue")
external class CSSUnitValueJS(value: Number, unit: String) : CSSNumericValueJS, CSSUnitValue {
override val value: Number
override val unit: String
}
// declare enum CSSMathOperator {
// 'sum',
// 'product',
// 'negate',
// 'invert',
// 'min',
// 'max',
// 'clamp',
// }
enum class CSSMathOperator(val value: String) {
sum("sum"),
product("product"),
negate("negate"),
invert("invert"),
min("min"),
max("max"),
clamp("clamp")
}
open external class CSSMathValue : CSSNumericValueJS {
// readonly operator: CSSMathOperator
}
val CSSMathValue.operator
get() = CSSMathOperator.valueOf(this.asDynamic().operator)
// set(value) { this.asDynamic().operator = value.value }
external class CSSMathSum(vararg args: CSSNumberish) : CSSMathValue {
val values: CSSNumericArray
}
external class CSSMathProduct(vararg args: CSSNumberish) : CSSMathValue {
val values: CSSNumericArray
}
external class CSSMathNegate(arg: CSSNumberish) : CSSMathValue {
val value: CSSNumericValue
}
external class CSSMathInvert(arg: CSSNumberish) : CSSMathValue {
val value: CSSNumericValue
}
external class CSSMathMin(vararg args: CSSNumberish) : CSSMathValue {
val values: CSSNumericArray
}
external class CSSMathMax(vararg args: CSSNumberish) : CSSMathValue {
val values: CSSNumericArray
}
// TODO(yavanosta) : conflict with base class properties
// Since there is no support for this class in any browser, it's better
// wait for the implementation.
// declare class CSSMathClamp extends CSSMathValue {
// constructor(min: CSSNumberish, val: CSSNumberish, max: CSSNumberish)
// readonly min: CSSNumericValue
// readonly val: CSSNumericValue
// readonly max: CSSNumericValue
// }
external class CSSNumericArray {
// TODO: [Symbol.iterator]() : IterableIterator<CSSNumericValue>
fun forEach(handler: (CSSNumericValue) -> Unit)
val length: Int
// readonly [index: number]: CSSNumericValue
operator fun get(index: Int): CSSNumericValue
}
external class CSSTransformValue(transforms: Array<CSSTransformComponent>) : CSSStyleValue {
// [Symbol.iterator]() : IterableIterator<CSSTransformComponent>
fun forEach(handler: (CSSTransformComponent) -> Unit)
val length: Int
// [index: number]: CSSTransformComponent
operator fun get(index: Int): CSSTransformComponent
operator fun set(index: Int, value: CSSTransformComponent)
val is2D: Boolean
fun toMatrix(): DOMMatrix
}
open external class CSSTransformComponent {
val is2D: Boolean
fun toMatrix(): DOMMatrix
// toString() : string
}
external class CSSTranslate(
x: CSSNumericValue,
y: CSSNumericValue,
z: CSSNumericValue? = definedExternally
) : CSSTransformComponent {
val x: CSSNumericValue
val y: CSSNumericValue
val z: CSSNumericValue
}
external class CSSRotate(angle: CSSNumericValue) : CSSTransformComponent {
constructor(x: CSSNumberish, y: CSSNumberish, z: CSSNumberish, angle: CSSNumericValue)
val x: CSSNumberish
val y: CSSNumberish
val z: CSSNumberish
val angle: CSSNumericValue
}
external class CSSScale(
x: CSSNumberish,
y: CSSNumberish,
z: CSSNumberish? = definedExternally
) : CSSTransformComponent {
val x: CSSNumberish
val y: CSSNumberish
val z: CSSNumberish
}
external class CSSSkew(ax: CSSNumericValue, ay: CSSNumericValue) : CSSTransformComponent {
val ax: CSSNumericValue
val ay: CSSNumericValue
}
external class CSSSkewX(ax: CSSNumericValue) : CSSTransformComponent {
val ax: CSSNumericValue
}
external class CSSSkewY(ay: CSSNumericValue) : CSSTransformComponent {
val ay: CSSNumericValue
}
/* Note that skew(x,y) is *not* the same as skewX(x) skewY(y),
thus the separate interfaces for all three. */
external class CSSPerspective(length: CSSNumericValue) : CSSTransformComponent {
val length: CSSNumericValue
}
external class CSSMatrixComponent(
matrix: DOMMatrixReadOnly,
options: CSSMatrixComponentOptions? = definedExternally
) : CSSTransformComponent {
val matrix: DOMMatrix
}
external interface CSSMatrixComponentOptions {
val is2D: Boolean
}
external class CSSImageValue : CSSStyleValue
open external class StylePropertyMapReadOnly {
// TODO: [Symbol.iterator]() : IterableIterator<[string, CSSStyleValue[]]>
fun get(property: String): CSSStyleValue? // CSSStyleValue | undefined
fun getAll(property: String): Array<CSSStyleValue>
fun has(property: String): Boolean
val size: Number
}
fun StylePropertyMapReadOnly.forEach(handler: (String, Array<CSSStyleValue>) -> Unit) {
this.asDynamic().forEach { entry: Array<dynamic> ->
handler(
entry[0].unsafeCast<String>(),
entry[1].unsafeCast<Array<CSSStyleValue>>()
)
}
}
// CSSStyleValue | string
interface StylePropertyValue {
companion object {
operator fun invoke(value: String) = value.unsafeCast<StylePropertyValue>()
operator fun invoke(value: Number) = value.unsafeCast<StylePropertyValue>()
operator fun invoke(value: CSSStyleValue) = value.unsafeCast<StylePropertyValue>()
}
}
fun StylePropertyValue.asString() = this.asDynamic() as? String
fun StylePropertyValue.asNumber() = this.asDynamic() as? Number
fun StylePropertyValue.asCSSStyleValue(): CSSStyleValue? = this.asDynamic() as? CSSStyleValueJS
external class StylePropertyMap : StylePropertyMapReadOnly {
fun set(property: String, vararg values: StylePropertyValue)
fun append(property: String, vararg values: StylePropertyValue)
fun delete(property: String)
fun clear()
}
inline fun Element.computedStyleMap(): StylePropertyMapReadOnly =
this.asDynamic().computedStyleMap().unsafeCast<StylePropertyMapReadOnly>()
external class CSS {
companion object {
fun number(value: Number): CSSUnitValue
fun percent(value: Number): CSSUnitValue
// <length>
fun em(value: Number): CSSUnitValue
fun ex(value: Number): CSSUnitValue
fun ch(value: Number): CSSUnitValue
fun ic(value: Number): CSSUnitValue
fun rem(value: Number): CSSUnitValue
fun lh(value: Number): CSSUnitValue
fun rlh(value: Number): CSSUnitValue
fun vw(value: Number): CSSUnitValue
fun vh(value: Number): CSSUnitValue
fun vi(value: Number): CSSUnitValue
fun vb(value: Number): CSSUnitValue
fun vmin(value: Number): CSSUnitValue
fun vmax(value: Number): CSSUnitValue
fun cm(value: Number): CSSUnitValue
fun mm(value: Number): CSSUnitValue
fun Q(value: Number): CSSUnitValue
// function _in(value: Number) : CSSUnitValue
// export
// { _in as in }
fun pt(value: Number): CSSUnitValue
fun pc(value: Number): CSSUnitValue
fun px(value: Number): CSSUnitValue
// <angle>
fun deg(value: Number): CSSUnitValue
fun grad(value: Number): CSSUnitValue
fun rad(value: Number): CSSUnitValue
fun turn(value: Number): CSSUnitValue
// <time>
fun s(value: Number): CSSUnitValue
fun ms(value: Number): CSSUnitValue
// <frequency>
fun Hz(value: Number): CSSUnitValue
fun kHz(value: Number): CSSUnitValue
// <resolution>
fun dpi(value: Number): CSSUnitValue
fun dpcm(value: Number): CSSUnitValue
fun dppx(value: Number): CSSUnitValue
// <flex>
fun fr(value: Number): CSSUnitValue
}
}
@Suppress("unused")
val cssTypedOMPolyfill = CSSTypedOMPolyfill.default(window)

39
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSBuilder.kt

@ -0,0 +1,39 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
import androidx.compose.web.css.selectors.CSSSelector
interface CSSBuilder : CSSStyleRuleBuilder, GenericStyleSheetBuilder<CSSBuilder> {
val root: CSSSelector
val self: CSSSelector
}
class CSSBuilderImpl(
override val root: CSSSelector,
override val self: CSSSelector,
rulesHolder: CSSRulesHolder
) : CSSRuleBuilderImpl(), CSSBuilder, CSSRulesHolder by rulesHolder {
override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) {
val (style, rules) = buildCSS(root, selector, cssRule)
rules.forEach { add(it) }
add(selector, style)
}
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSBuilder>.() -> Unit) =
CSSBuilderImpl(root, self, StyleSheetBuilderImpl()).apply(rulesBuild).cssRules
}

43
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSHelpers.kt

@ -0,0 +1,43 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("UNUSED")
package androidx.compose.web.css
interface CSSAutoValue : CSSKeywordValue
val auto = CSSKeywordValueJS("auto").unsafeCast<CSSAutoValue>()
fun asCSSAutoValue(value: dynamic) = (value as? CSSKeywordValueJS).unsafeCast<CSSAutoValue>()
// type CSSSizeOrAutoValue = CSSSizeValue | CSSAutoValue
interface CSSSizeOrAutoValue : CSSStyleValue, StylePropertyValue {
companion object {
operator fun invoke(value: CSSSizeValue) = value.unsafeCast<CSSSizeOrAutoValue>()
operator fun invoke(value: CSSAutoValue) = value.unsafeCast<CSSSizeOrAutoValue>()
}
}
fun CSSSizeOrAutoValue.asCSSSizeValue() = this.asDynamic() as? CSSUnitValueJS
fun CSSSizeOrAutoValue.asCSSAutoValue(): CSSAutoValue = asCSSAutoValue(this.asDynamic())
enum class Direction {
rtl,
ltr;
override fun toString(): String = this.name
}
typealias LanguageCode = String

153
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSMediaRule.kt

@ -0,0 +1,153 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
interface CSSMediaQuery {
interface Invertible : CSSMediaQuery
interface Combinable : CSSMediaQuery
interface Atomic : Invertible, Combinable
data class Raw(val string: String) : Atomic {
override fun toString() = string
}
data class MediaType(val type: Enum) : Atomic {
enum class Enum {
all, print, screen, speech
}
override fun toString() = type.name
}
data class MediaFeature(
val name: String,
val value: StylePropertyValue? = null
) : CSSMediaQuery, Atomic {
override fun equals(other: Any?): Boolean {
return if (other is MediaFeature) {
name == other.name && value.toString() == other.value.toString()
} else false
}
override fun toString() = "($name${ value?.let { ": $value)" } ?: "" }"
}
// looks like it doesn't work at least in chrome
data class NotFeature(val query: MediaFeature) : CSSMediaQuery {
override fun toString() = "(not $query)"
}
data class And(val mediaList: MutableList<Atomic>) : CSSMediaQuery, Invertible, Combinable {
override fun toString() = mediaList.joinToString(" and ")
}
data class Not(val query: Invertible) : CSSMediaQuery {
override fun toString() = "not $query"
}
data class Combine(val mediaList: MutableList<CSSMediaQuery>) : CSSMediaQuery {
override fun toString() = mediaList.joinToString(", ")
}
data class Only(val type: MediaType, val query: Combinable) : CSSMediaQuery, Invertible {
override fun toString() = "only $type and $query"
}
// looks like it doesn't work at least in chrome, maybe need fallback to Combine
data class Or(val mediaList: List<CSSMediaQuery>) {
override fun toString() = mediaList.joinToString(" or ")
}
}
class CSSMediaRuleDeclaration(
val query: CSSMediaQuery,
rules: CSSRuleDeclarationList
) : CSSGroupingRuleDeclaration(rules) {
override val header: String
get() = "@media $query"
override fun equals(other: Any?): Boolean {
return if (other is CSSMediaRuleDeclaration) {
rules == other.rules && query == other.query
} else false
}
}
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media(
query: CSSMediaQuery,
rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit
) {
val rules = buildRules(rulesBuild)
add(CSSMediaRuleDeclaration(query, rules))
}
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media(
query: String,
rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit
) {
media(CSSMediaQuery.Raw(query), rulesBuild)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media(
name: String,
value: StylePropertyValue? = null,
noinline rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit
) {
media(feature(name, value), rulesBuild)
}
fun feature(
name: String,
value: StylePropertyValue? = null
) = CSSMediaQuery.MediaFeature(name, value)
@Suppress("NOTHING_TO_INLINE")
inline fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.media(
vararg mediaList: CSSMediaQuery,
noinline rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit
) {
media(combine(*mediaList), rulesBuild)
}
fun combine(
vararg mediaList: CSSMediaQuery
) = CSSMediaQuery.Combine(mediaList.toMutableList())
infix fun CSSMediaQuery.Atomic.and(
query: CSSMediaQuery.Atomic
) = CSSMediaQuery.And(mutableListOf(this, query))
infix fun CSSMediaQuery.And.and(
query: CSSMediaQuery.Atomic
) = this.apply {
this.mediaList.add(query)
}
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.not(
query: CSSMediaQuery.Invertible
) = CSSMediaQuery.Not(query)
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.minWidth(value: CSSSizeValue) =
CSSMediaQuery.MediaFeature("min-width", StylePropertyValue(value))
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.maxWidth(value: CSSSizeValue) =
CSSMediaQuery.MediaFeature("max-width", StylePropertyValue(value))
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.minHeight(value: CSSSizeValue) =
CSSMediaQuery.MediaFeature("min-height", StylePropertyValue(value))
fun <TBuilder> GenericStyleSheetBuilder<TBuilder>.maxHeight(value: CSSSizeValue) =
CSSMediaQuery.MediaFeature("max-height", StylePropertyValue(value))

30
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSPolyfill.kt

@ -0,0 +1,30 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
import org.w3c.dom.Window
@JsModule("css-typed-om")
@JsNonModule
abstract external class CSSTypedOMPolyfill {
companion object {
fun default(window: Window)
}
}
fun StylePropertyMap.clear() {
throw AssertionError("StylePropertyMap::clear isn't polyfilled")
}

427
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSProperties.kt

@ -0,0 +1,427 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
fun StyleBuilder.opacity(value: Number) {
property("opacity", value(value))
}
fun StyleBuilder.order(value: Int) {
property("order", StylePropertyValue(value))
}
fun StyleBuilder.flexGrow(value: Number) {
property("flex-grow", StylePropertyValue(value))
}
fun StyleBuilder.flexShrink(value: Number) {
property("flex-shrink", StylePropertyValue(value))
}
fun StyleBuilder.opacity(value: CSSpercentValue) {
property("opacity", value(value.value as Double / 100))
}
fun StyleBuilder.color(value: String) {
property("color", value(value))
}
fun StyleBuilder.color(value: Color) {
// color hasn't Typed OM yet
property("color", value.styleValue())
}
fun StyleBuilder.backgroundColor(value: CSSVariableValue<Color>) {
property("background-color", value)
}
fun StyleBuilder.backgroundColor(value: Color) {
property("background-color", value.styleValue())
}
fun StyleBuilder.backgroundColor(value: String) {
property("background-color", value(value))
}
enum class LineStyle {
None,
Hidden,
Dotted,
Dashed,
Solid,
Double,
Groove,
Ridge,
Inset,
Outset
}
enum class DisplayStyle(val value: String) {
Block("block"),
Inline("inline"),
InlineBlock("inline-block"),
Flex("flex"),
LegacyInlineFlex("inline-flex"),
Grid("grid"),
LegacyInlineGrid("inline-grid"),
FlowRoot("flow-root"),
None("none"),
Contents("contents"),
// TODO(shabunc): This properties behave them iconsistenly in both Chrome and Firefox so I turned the off so far
// BlockFlow("block flow"),
// InlineFlow("inline flow"),
// InlineFlowRoot("inline flow-root"),
// BlocklFlex("block flex"),
// InlineFlex("inline flex"),
// BlockGrid("block grid"),
// InlineGrid("inline grid"),
// BlockFlowRoot("block flow-root"),
Table("table"),
TableRow("table-row"),
ListItem("list-item"),
Inherit("inherit"),
Initial("initial"),
Unset("unset")
}
enum class FlexDirection(val value: String) {
Row("row"),
RowReverse("row-reverse"),
Column("column"),
ColumnReverse("column-reverse")
}
enum class FlexWrap(val value: String) {
Wrap("wrap"),
Nowrap("nowrap"),
WrapReverse("wrap-reverse")
}
enum class JustifyContent(val value: String) {
Center("center"),
Start("start"),
End("end"),
FlexStart("flex-start"),
FlexEnd("flex-end"),
Left("left"),
Right("right"),
Normal("normal"),
SpaceBetween("space-between"),
SpaceAround("space-around"),
SpaceEvenly("space-evenly"),
Stretch("stretch"),
Inherit("inherit"),
Initial("initial"),
Unset("unset"),
SafeCenter("safe center"),
UnsafeCenter("unsafe center"),
}
enum class AlignSelf(val value: String) {
Auto("auto"),
Normal("normal"),
Center("center"),
Start("start"),
End("end"),
SelfStart("self-start"),
SelfEnd("self-end"),
FlexStart("flex-start"),
FlexEnd("flex-end"),
Baseline("baseline"),
// FirstBaseline("first baseline"),
// LastBaseline("last baseline"),
Stretch("stretch"),
SafeCenter("safe center"),
UnsafeCenter("unsafe center"),
Inherit("inherit"),
Initial("initial"),
Unset("unset")
}
enum class AlignItems(val value: String) {
Normal("normal"),
Stretch("stretch"),
Center("center"),
Start("start"),
End("end"),
FlexStart("flex-start"),
FlexEnd("flex-end"),
Baseline("baseline"),
// FirstBaseline("first baseline"),
// LastBaseline("last baseline"),
SafeCenter("safe center"),
UnsafeCenter("unsafe center"),
Inherit("inherit"),
Initial("initial"),
Unset("unset")
}
enum class AlignContent(val value: String) {
Center("center"),
Start("start"),
End("end"),
FlexStart("flex-start"),
FlexEnd("flex-end"),
Baseline("baseline"),
// FirstBaseline("first baseline"),
// LastBaseline("last baseline"),
SafeCenter("safe center"),
UnsafeCenter("unsafe center"),
SpaceBetween("space-between"),
SpaceAround("space-around"),
SpaceEvenly("space-evenly"),
Stretch("stretch"),
Inherit("inherit"),
Initial("initial"),
Unset("unset")
}
enum class Position(val value: String) {
Static("static"),
Relative("relative"),
Absolute("absolute"),
Sticky("sticky"),
Fixed("fixed")
}
class CSSBorder : CustomStyleValue {
var width: StylePropertyValue? = null
var style: StylePropertyValue? = null
var color: StylePropertyValue? = null
fun width(size: CSSSizeValue) {
width = StylePropertyValue(size)
}
fun style(style: LineStyle) {
this.style = StylePropertyValue(style.name)
}
fun color(color: Color) {
this.color = color.styleValue()
}
fun color(color: CSSVariableValue<Color>) {
this.color = color
}
override fun equals(other: Any?): Boolean {
return if (other is CSSBorder) {
styleValue().toString() == other.styleValue().toString()
} else false
}
override fun styleValue(): StylePropertyValue {
val values = listOfNotNull(width, style, color)
return StylePropertyValue(values.joinToString(" "))
}
}
inline fun StyleBuilder.border(crossinline borderBuild: CSSBorder.() -> Unit) {
val border = CSSBorder().apply(borderBuild)
property("border", border.styleValue())
}
fun StyleBuilder.border(
width: CSSSizeValue? = null,
style: LineStyle? = null,
color: Color? = null
) {
border {
width?.let { width(it) }
style?.let { style(it) }
color?.let { color(it) }
}
}
fun StyleBuilder.display(displayStyle: DisplayStyle) {
property("display", StylePropertyValue(displayStyle.value))
}
fun StyleBuilder.flexDirection(flexDirection: FlexDirection) {
property("flex-direction", StylePropertyValue(flexDirection.value))
}
fun StyleBuilder.flexWrap(flexWrap: FlexWrap) {
property("flex-wrap", StylePropertyValue(flexWrap.value))
}
fun StyleBuilder.flexFlow(flexDirection: FlexDirection, flexWrap: FlexWrap) {
property(
"flex-flow",
StylePropertyValue("${flexDirection.value} ${flexWrap.value}")
)
}
fun StyleBuilder.justifyContent(justifyContent: JustifyContent) {
property(
"justify-content",
StylePropertyValue(justifyContent.value)
)
}
fun StyleBuilder.alignSelf(alignSelf: AlignSelf) {
property(
"align-self",
StylePropertyValue(alignSelf.value)
)
}
fun StyleBuilder.alignItems(alignItems: AlignItems) {
property(
"align-items",
StylePropertyValue(alignItems.value)
)
}
fun StyleBuilder.alignContent(alignContent: AlignContent) {
property(
"align-content",
StylePropertyValue(alignContent.value)
)
}
fun StyleBuilder.position(position: Position) {
property(
"position",
StylePropertyValue(position.value)
)
}
fun StyleBuilder.width(value: CSSSizeOrAutoValue) {
property("width", value)
}
fun StyleBuilder.borderRadius(r: CSSSizeValue) {
property("border-radius", StylePropertyValue(r.toString()))
}
fun StyleBuilder.borderRadius(topLeft: CSSSizeValue, bottomRight: CSSSizeValue) {
property("border-radius", StylePropertyValue("$topLeft $bottomRight"))
}
fun StyleBuilder.borderRadius(
topLeft: CSSSizeValue,
topRightAndBottomLeft: CSSSizeValue,
bottomRight: CSSSizeValue
) {
property("border-radius", StylePropertyValue("$topLeft $topRightAndBottomLeft $bottomRight"))
}
fun StyleBuilder.borderRadius(
topLeft: CSSSizeValue,
topRight: CSSSizeValue,
bottomRight: CSSSizeValue,
bottomLeft: CSSSizeValue
) {
property("border-radius", StylePropertyValue("$topLeft $topRight $bottomRight $bottomLeft"))
}
fun StyleBuilder.width(value: CSSSizeValue) {
width(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.width(value: CSSAutoValue) {
width(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.height(value: CSSSizeOrAutoValue) {
property("height", value)
}
fun StyleBuilder.height(value: CSSSizeValue) {
height(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.height(value: CSSAutoValue) {
height(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.top(value: CSSSizeOrAutoValue) {
property("top", value)
}
fun StyleBuilder.top(value: CSSSizeValue) {
top(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.top(value: CSSAutoValue) {
top(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.bottom(value: CSSSizeOrAutoValue) {
property("bottom", value)
}
fun StyleBuilder.bottom(value: CSSSizeValue) {
bottom(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.bottom(value: CSSAutoValue) {
bottom(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.left(value: CSSSizeOrAutoValue) {
property("left", value)
}
fun StyleBuilder.left(value: CSSSizeValue) {
left(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.left(value: CSSAutoValue) {
left(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.right(value: CSSSizeOrAutoValue) {
property("right", value)
}
fun StyleBuilder.right(value: CSSSizeValue) {
right(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.right(value: CSSAutoValue) {
right(CSSSizeOrAutoValue(value))
}
fun StyleBuilder.fontSize(value: CSSSizeValue) {
property("font-size", value(value))
}
fun StyleBuilder.margin(value: CSSSizeValue) {
// marign hasn't Typed OM yet
property("margin", value(value.toString()))
}
fun StyleBuilder.marginLeft(value: CSSSizeValue) {
property("margin-left", value(value.toString()))
}
fun StyleBuilder.marginTop(value: CSSSizeValue) {
property("margin-top", value(value.toString()))
}
fun StyleBuilder.padding(value: CSSSizeValue) {
// padding hasn't Typed OM yet
property("padding", value(value.toString()))
}

50
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSRules.kt

@ -0,0 +1,50 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
import androidx.compose.web.css.selectors.CSSSelector
interface CSSStyleRuleBuilder : StyleBuilder
open class CSSRuleBuilderImpl : CSSStyleRuleBuilder, StyleBuilderImpl()
abstract class CSSRuleDeclaration {
abstract val header: String
abstract override fun equals(other: Any?): Boolean
}
data class CSSStyleRuleDeclaration(
val selector: CSSSelector,
val style: StyleHolder
) : CSSRuleDeclaration() {
override val header
get() = selector.toString()
}
abstract class CSSGroupingRuleDeclaration(
val rules: CSSRuleDeclarationList
) : CSSRuleDeclaration()
typealias CSSRuleDeclarationList = List<CSSRuleDeclaration>
typealias MutableCSSRuleDeclarationList = MutableList<CSSRuleDeclaration>
fun buildCSSStyleRule(cssRule: CSSStyleRuleBuilder.() -> Unit): StyleHolder {
val builder = CSSRuleBuilderImpl()
builder.cssRule()
return builder
}

142
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSUnits.kt

@ -0,0 +1,142 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("UNUSED")
package androidx.compose.web.css
external interface CSSSizeValue : CSSUnitValue
// fake interfaces to distinguish units
external interface CSSRelValue : CSSSizeValue
external interface CSSpercentValue : CSSRelValue
external interface CSSemValue : CSSRelValue
external interface CSSexValue : CSSRelValue
external interface CSSchValue : CSSRelValue
external interface CSSicValue : CSSRelValue
external interface CSSremValue : CSSRelValue
external interface CSSlhValue : CSSRelValue
external interface CSSrlhValue : CSSRelValue
external interface CSSvwValue : CSSRelValue
external interface CSSvhValue : CSSRelValue
external interface CSSviValue : CSSRelValue
external interface CSSvbValue : CSSRelValue
external interface CSSvminValue : CSSRelValue
external interface CSSvmaxValue : CSSRelValue
external interface CSScmValue : CSSRelValue
external interface CSSmmValue : CSSRelValue
external interface CSSQValue : CSSRelValue
external interface CSSAbsValue : CSSSizeValue
external interface CSSptValue : CSSAbsValue
external interface CSSpcValue : CSSAbsValue
external interface CSSpxValue : CSSAbsValue
external interface CSSangleValue : CSSUnitValue
external interface CSSdegValue : CSSangleValue
external interface CSSgradValue : CSSangleValue
external interface CSSradValue : CSSangleValue
external interface CSSturnValue : CSSangleValue
external interface CSSTimeValue : CSSUnitValue
external interface CSSsValue : CSSTimeValue
external interface CSSmsValue : CSSTimeValue
external interface CSSFrequencyValue : CSSUnitValue
external interface CSSHzValue : CSSFrequencyValue
external interface CSSkHzValue : CSSFrequencyValue
external interface CSSResolutionValue : CSSUnitValue
external interface CSSdpiValue : CSSResolutionValue
external interface CSSdpcmValue : CSSResolutionValue
external interface CSSdppxValue : CSSResolutionValue
external interface CSSFlexValue : CSSUnitValue
external interface CSSfrValue : CSSFlexValue
val Number.number
get(): CSSUnitValue = CSS.number(this)
val Number.percent
get(): CSSpercentValue = CSS.percent(this).unsafeCast<CSSpercentValue>()
val Number.em
get(): CSSemValue = CSS.em(this).unsafeCast<CSSemValue>()
val Number.ex
get(): CSSexValue = CSS.ex(this).unsafeCast<CSSexValue>()
val Number.ch
get(): CSSchValue = CSS.ch(this).unsafeCast<CSSchValue>()
val Number.ic
get(): CSSicValue = CSS.ic(this).unsafeCast<CSSicValue>()
val Number.rem
get(): CSSremValue = CSS.rem(this).unsafeCast<CSSremValue>()
val Number.lh
get(): CSSlhValue = CSS.lh(this).unsafeCast<CSSlhValue>()
val Number.rlh
get(): CSSrlhValue = CSS.rlh(this).unsafeCast<CSSrlhValue>()
val Number.vw
get(): CSSvwValue = CSS.vw(this).unsafeCast<CSSvwValue>()
val Number.vh
get(): CSSvhValue = CSS.vh(this).unsafeCast<CSSvhValue>()
val Number.vi
get(): CSSviValue = CSS.vi(this).unsafeCast<CSSviValue>()
val Number.vb
get(): CSSvbValue = CSS.vb(this).unsafeCast<CSSvbValue>()
val Number.vmin
get(): CSSvminValue = CSS.vmin(this).unsafeCast<CSSvminValue>()
val Number.vmax
get(): CSSvmaxValue = CSS.vmax(this).unsafeCast<CSSvmaxValue>()
val Number.cm
get(): CSScmValue = CSS.cm(this).unsafeCast<CSScmValue>()
val Number.mm
get(): CSSmmValue = CSS.mm(this).unsafeCast<CSSmmValue>()
val Number.Q
get(): CSSQValue = CSS.Q(this).unsafeCast<CSSQValue>()
val Number.pt
get(): CSSptValue = CSS.pt(this).unsafeCast<CSSptValue>()
val Number.pc
get(): CSSpcValue = CSS.pc(this).unsafeCast<CSSpcValue>()
val Number.px
get(): CSSpxValue = CSS.px(this).unsafeCast<CSSpxValue>()
val Number.deg
get(): CSSdegValue = CSS.deg(this).unsafeCast<CSSdegValue>()
val Number.grad
get(): CSSgradValue = CSS.grad(this).unsafeCast<CSSgradValue>()
val Number.rad
get(): CSSradValue = CSS.rad(this).unsafeCast<CSSradValue>()
val Number.turn
get(): CSSturnValue = CSS.turn(this).unsafeCast<CSSturnValue>()
val Number.s
get(): CSSsValue = CSS.s(this).unsafeCast<CSSsValue>()
val Number.ms
get(): CSSmsValue = CSS.ms(this).unsafeCast<CSSmsValue>()
val Number.Hz
get(): CSSHzValue = CSS.Hz(this).unsafeCast<CSSHzValue>()
val Number.kHz
get(): CSSkHzValue = CSS.kHz(this).unsafeCast<CSSkHzValue>()
val Number.dpi
get(): CSSdpiValue = CSS.dpi(this).unsafeCast<CSSdpiValue>()
val Number.dpcm
get(): CSSdpcmValue = CSS.dpcm(this).unsafeCast<CSSdpcmValue>()
val Number.dppx
get(): CSSdppxValue = CSS.dppx(this).unsafeCast<CSSdppxValue>()
val Number.fr
get(): CSSfrValue = CSS.fr(this).unsafeCast<CSSfrValue>()

49
web/core/src/jsMain/kotlin/androidx/compose/web/css/Color.kt

@ -0,0 +1,49 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
abstract class Color : CustomStyleValue {
override fun styleValue(): StylePropertyValue = StylePropertyValue(toString())
data class Named(val value: String) : Color() {
override fun toString(): String = value
}
data class RGB(val r: Number, val g: Number, val b: Number) : Color() {
override fun toString(): String = "rgb($r, $g, $b)"
}
data class RGBA(val r: Number, val g: Number, val b: Number, val a: Number) : Color() {
override fun toString(): String = "rgba($r, $g, $b, $a)"
}
data class HSL(val h: CSSangleValue, val s: Number, val l: Number) : Color() {
constructor(h: Number, s: Number, l: Number) : this(h.deg, s, l)
override fun toString(): String = "hsl($h, $s%, $l%)"
}
data class HSLA(val h: CSSangleValue, val s: Number, val l: Number, val a: Number) : Color() {
constructor(h: Number, s: Number, l: Number, a: Number) : this(h.deg, s, l, a)
override fun toString(): String = "hsla($h, $s%, $l%, $a)"
}
companion object {
operator fun invoke(name: String) = Named(name)
}
}

136
web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleBuilder.kt

@ -0,0 +1,136 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
import kotlin.properties.ReadOnlyProperty
interface StyleBuilder {
fun property(propertyName: String, value: StylePropertyValue)
fun variable(variableName: String, value: StylePropertyValue)
operator fun <TValue> CSSStyleVariable<TValue>.invoke(value: TValue) {
variable(this.name, (value as? CustomStyleValue)?.styleValue() ?: value(value.toString()))
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun StyleBuilder.value(value: String) = StylePropertyValue(value)
@Suppress("NOTHING_TO_INLINE")
inline fun StyleBuilder.value(value: Number) = StylePropertyValue(value)
@Suppress("NOTHING_TO_INLINE")
inline fun StyleBuilder.value(value: CSSStyleValue) = StylePropertyValue(value)
fun variableValue(variableName: String, fallback: StylePropertyValue? = null) =
StylePropertyValue("var(--$variableName${fallback?.let { ", $it" } ?: ""})")
interface CSSVariableValue<TValue> : StylePropertyValue {
companion object {
operator fun <TValue> invoke(value: String) =
StylePropertyValue(value).unsafeCast<CSSVariableValue<TValue>>()
operator fun <TValue> invoke(value: Number) =
StylePropertyValue(value).unsafeCast<CSSVariableValue<TValue>>()
operator fun <TValue : CSSStyleValue> invoke(value: TValue) =
StylePropertyValue(value).unsafeCast<CSSVariableValue<TValue>>()
operator fun <TValue> invoke(value: StylePropertyValue) =
value.unsafeCast<CSSVariableValue<TValue>>()
}
}
// after adding `variable` word `add` became ambiguous
@Deprecated(
"use property instead, will remove it soon",
ReplaceWith("property(propertyName, value)")
)
fun StyleBuilder.add(
propertyName: String,
value: StylePropertyValue
) = property(propertyName, value)
interface CSSVariables
interface CSSVariable {
val name: String
}
interface CustomStyleValue {
fun styleValue(): StylePropertyValue
}
data class CSSStyleVariable<TValue>(override val name: String) : CSSVariable
fun <TValue> CSSStyleVariable<TValue>.value(fallback: TValue? = null) =
CSSVariableValue<TValue>(
variableValue(
name,
fallback?.let {
(fallback as? CustomStyleValue)?.styleValue()
?: StylePropertyValue(fallback.toString())
}
)
)
fun <TValue> CSSVariables.variable() =
ReadOnlyProperty<Any?, CSSStyleVariable<TValue>> { _, property ->
CSSStyleVariable(property.name)
}
interface StyleHolder {
val properties: StylePropertyList
val variables: StylePropertyList
}
open class StyleBuilderImpl : StyleBuilder, StyleHolder {
override val properties: MutableStylePropertyList = mutableListOf()
override val variables: MutableStylePropertyList = mutableListOf()
override fun property(propertyName: String, value: StylePropertyValue) {
properties.add(StylePropertyDeclaration(propertyName, value))
}
override fun variable(variableName: String, value: StylePropertyValue) {
variables.add(StylePropertyDeclaration(variableName, value))
}
// StylePropertyValue is js native object without equals
override fun equals(other: Any?): Boolean {
return if (other is StyleHolder) {
properties.nativeEquals(other.properties) &&
variables.nativeEquals(other.variables)
} else false
}
}
data class StylePropertyDeclaration(
val name: String,
val value: StylePropertyValue
)
typealias StylePropertyList = List<StylePropertyDeclaration>
typealias MutableStylePropertyList = MutableList<StylePropertyDeclaration>
fun StylePropertyList.nativeEquals(properties: StylePropertyList): Boolean {
if (this.size != properties.size) return false
var index = 0
return all { prop ->
val otherProp = properties[index++]
prop.name == otherProp.name &&
prop.value.toString() == otherProp.value.toString()
}
}

133
web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleSheet.kt

@ -0,0 +1,133 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.web.css.selectors.CSSSelector
import androidx.compose.web.css.selectors.className
import androidx.compose.web.elements.Style
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class CSSRulesHolderState : CSSRulesHolder {
override var cssRules: CSSRuleDeclarationList by mutableStateOf(listOf())
override fun add(cssRule: CSSRuleDeclaration) {
cssRules += cssRule
}
}
/**
* Represents a collection of the css style rules.
* StyleSheet needs to be mounted.
* @see [Style]
*
* Example:
* ```
* object AppStylesheet : StyleSheet() {
* val containerClass by style {
* padding(24.px)
* }
* }
* ```
*
* Usage Example:
* ```
* Style(AppStylesheet) // this mounts the stylesheet
* Div(classes = listOf(AppStylesheet.containerClass),...)
* ```
*/
open class StyleSheet(
private val rulesHolder: CSSRulesHolder = CSSRulesHolderState()
) : StyleSheetBuilder, CSSRulesHolder by rulesHolder {
private val boundClasses = mutableMapOf<String, CSSRuleDeclarationList>()
protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(cssRule)
companion object {
var counter = 0
}
data class CSSSelfSelector(var selector: CSSSelector? = null) : CSSSelector() {
override fun toString(): String = selector.toString()
override fun equals(other: Any?): Boolean {
return other is CSSSelfSelector
}
}
// TODO: just proof of concept, do not use it
fun css(cssBuild: CSSBuilder.() -> Unit): String {
val selfSelector = CSSSelfSelector()
val (style, newCssRules) = buildCSS(selfSelector, selfSelector, cssBuild)
val cssRule = cssRules.find {
it is CSSStyleRuleDeclaration &&
it.selector is CSSSelector.CSSClass && it.style == style &&
(boundClasses[it.selector.className] ?: emptyList()) == newCssRules
}.unsafeCast<CSSStyleRuleDeclaration?>()
return if (cssRule != null) {
cssRule.selector.unsafeCast<CSSSelector.CSSClass>().className
} else {
val classNameSelector = className("auto-${counter++}")
selfSelector.selector = classNameSelector
add(classNameSelector, style)
newCssRules.forEach { add(it) }
boundClasses[classNameSelector.className] = newCssRules
classNameSelector.className
}
}
protected class CSSHolder(val cssBuilder: CSSBuilder.() -> Unit) {
operator fun provideDelegate(
sheet: StyleSheet,
property: KProperty<*>
): ReadOnlyProperty<Any?, String> {
val sheetName = "${sheet::class.simpleName}-"
val selector = className("$sheetName${property.name}")
val (properties, rules) = buildCSS(selector, selector, cssBuilder)
sheet.add(selector, properties)
rules.forEach { sheet.add(it) }
return ReadOnlyProperty { _, _ ->
selector.className
}
}
}
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSStyleRuleBuilder>.() -> Unit) =
StyleSheet().apply(rulesBuild).cssRules
}
fun buildCSS(
thisClass: CSSSelector,
thisContext: CSSSelector,
cssRule: CSSBuilder.() -> Unit
): Pair<StyleHolder, CSSRuleDeclarationList> {
val styleSheet = StyleSheetBuilderImpl()
val builder = CSSBuilderImpl(thisClass, thisContext, styleSheet)
builder.cssRule()
return builder to styleSheet.cssRules
}
@Composable
inline fun Style(
styleSheet: CSSRulesHolder
) {
Style(cssRules = styleSheet.cssRules)
}

68
web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleSheetBuilder.kt

@ -0,0 +1,68 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css
import androidx.compose.web.css.selectors.CSSSelector
interface CSSRulesHolder {
val cssRules: CSSRuleDeclarationList
fun add(cssRule: CSSRuleDeclaration)
fun add(selector: CSSSelector, style: StyleHolder) {
add(CSSStyleRuleDeclaration(selector, style))
}
}
interface GenericStyleSheetBuilder<TBuilder> : CSSRulesHolder {
fun buildRules(
rulesBuild: GenericStyleSheetBuilder<TBuilder>.() -> Unit
): CSSRuleDeclarationList
fun style(selector: CSSSelector, cssRule: TBuilder.() -> Unit)
operator fun CSSSelector.invoke(cssRule: TBuilder.() -> Unit) {
style(this, cssRule)
}
infix fun CSSSelector.style(cssRule: TBuilder.() -> Unit) {
style(this, cssRule)
}
operator fun String.invoke(cssRule: TBuilder.() -> Unit) {
style(CSSSelector.Raw(this), cssRule)
}
infix fun String.style(cssRule: TBuilder.() -> Unit) {
style(CSSSelector.Raw(this), cssRule)
}
}
interface StyleSheetBuilder : CSSRulesHolder, GenericStyleSheetBuilder<CSSStyleRuleBuilder> {
override fun style(selector: CSSSelector, cssRule: CSSStyleRuleBuilder.() -> Unit) {
add(selector, buildCSSStyleRule(cssRule))
}
}
open class StyleSheetBuilderImpl : StyleSheetBuilder {
override val cssRules: MutableCSSRuleDeclarationList = mutableListOf()
override fun add(cssRule: CSSRuleDeclaration) {
cssRules.add(cssRule)
}
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSStyleRuleBuilder>.() -> Unit) =
StyleSheetBuilderImpl().apply(rulesBuild).cssRules
}

257
web/core/src/jsMain/kotlin/androidx/compose/web/css/selectors/CSSSelectors.kt

@ -0,0 +1,257 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.css.selectors
import androidx.compose.web.css.LanguageCode
sealed class Nth {
data class Functional(val a: Int? = null, val b: Int? = null) {
override fun toString(): String = when {
a != null && b != null -> "${a}n+$b"
a != null -> "${a}n"
b != null -> "$b"
else -> ""
}
}
object Odd : Nth() {
override fun toString(): String = "odd"
}
object Even : Nth() {
override fun toString(): String = "even"
}
}
open class CSSSelector {
override fun equals(other: Any?): Boolean {
return toString() == other.toString()
}
data class Raw(val selector: String) : CSSSelector() {
override fun toString(): String = selector
}
object Universal : CSSSelector() {
override fun toString(): String = "*"
}
data class Type(val type: String) : CSSSelector() {
override fun toString(): String = type
}
data class CSSClass(val className: String) : CSSSelector() {
override fun toString(): String = ".$className"
}
data class Id(val id: String) : CSSSelector() {
override fun toString(): String = "#$id"
}
data class Attribute(
val name: String,
val value: String? = null,
val operator: Operator = Operator.Equals,
val caseSensitive: Boolean = true
) : CSSSelector() {
enum class Operator(val value: String) {
Equals("="),
ListContains("~="),
Hyphened("|="),
Prefixed("^="),
Suffixed("$="),
Contains("*=")
}
override fun toString(): String {
val valueStr = value?.let {
"${operator.value}$value${if (!caseSensitive) " i" else ""}"
} ?: ""
return "[$name$valueStr]"
}
}
data class Combine(val selectors: MutableList<CSSSelector>) : CSSSelector() {
override fun toString(): String = selectors.joinToString("")
}
data class Group(val selectors: List<CSSSelector>) : CSSSelector() {
override fun toString(): String = selectors.joinToString(", ")
}
data class Descendant(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun toString(): String = "$parent $selected"
}
data class Child(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun toString(): String = "$parent > $selected"
}
data class Sibling(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun toString(): String = "$prev ~ $selected"
}
data class Adjacent(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun toString(): String = "$prev + $selected"
}
open class PseudoClass(val name: String) : CSSSelector() {
override fun equals(other: Any?): Boolean {
return if (other is PseudoClass) {
name == other.name && argsStr() == other.argsStr()
} else false
}
open fun argsStr(): String? = null
override fun toString(): String = ":$name${argsStr()?.let { "($it)" } ?: ""}"
companion object {
// Location pseudo-classes
val anyLink = PseudoClass("any-link")
val link = PseudoClass("link")
val visited = PseudoClass("visited")
val localLink = PseudoClass("local-link")
val target = PseudoClass("target")
val targetWithin = PseudoClass("target-within")
val scope = PseudoClass("scope")
// User action pseudo-classes
val hover = PseudoClass("hover")
val active = PseudoClass("active")
val focus = PseudoClass("focus")
val focusVisible = PseudoClass("focus-visible")
// Resource state pseudo-classes
val playing = PseudoClass("playing")
val paused = PseudoClass("paused")
// The input pseudo-classes
val autofill = PseudoClass("autofill")
val enabled = PseudoClass("enabled")
val disabled = PseudoClass("disabled")
val readOnly = PseudoClass("read-only")
val readWrite = PseudoClass("read-write")
val placeholderShown = PseudoClass("placeholder-shown")
val default = PseudoClass("default")
val checked = PseudoClass("checked")
val indeterminate = PseudoClass("indeterminate")
val blank = PseudoClass("blank")
val valid = PseudoClass("valid")
val invalid = PseudoClass("invalid")
val inRange = PseudoClass("in-range")
val outOfRange = PseudoClass("out-of-range")
val required = PseudoClass("required")
val optional = PseudoClass("optional")
val userInvalid = PseudoClass("user-invalid")
// Tree-structural pseudo-classes
val root = PseudoClass("root")
val empty = PseudoClass("empty")
val first = PseudoClass("first")
val firstChild = PseudoClass("first-child")
val lastChild = PseudoClass("last-child")
val onlyChild = PseudoClass("only-child")
val firstOfType = PseudoClass("first-of-type")
val lastOfType = PseudoClass("last-of-type")
val onlyOfType = PseudoClass("only-of-type")
val host = PseudoClass("host")
// Etc
val defined = PseudoClass("defined")
val left = PseudoClass("left")
val right = PseudoClass("right")
}
// Linguistic pseudo-classes
class Lang(val langCode: LanguageCode) : PseudoClass("lang") {
override fun argsStr() = langCode
}
// Tree-structural pseudo-classes
class NthChild(val nth: Nth) : PseudoClass("nth-child") {
override fun argsStr() = "$nth"
}
class NthLastChild(val nth: Nth) : PseudoClass("nth-last-child") {
override fun argsStr() = "$nth"
}
class NthOfType(val nth: Nth) : PseudoClass("nth-of-type") {
override fun argsStr() = "$nth"
}
class NthLastOfType(val nth: Nth) : PseudoClass("nth-last-of-type") {
override fun argsStr() = "$nth"
}
class Host(val selector: CSSSelector) : PseudoClass("host") {
override fun argsStr() = "$selector"
}
// Etc
class Not(val selector: CSSSelector) : PseudoClass("not") {
override fun argsStr() = "$selector"
}
}
open class PseudoElement(val name: String) : CSSSelector() {
override fun equals(other: Any?): Boolean {
return if (other is PseudoElement) {
name == other.name && argsStr() == other.argsStr()
} else false
}
open fun argsStr(): String? = null
override fun toString(): String = "::$name${argsStr()?.let { "($it)" } ?: ""}"
companion object {
val after = PseudoElement("after")
val before = PseudoElement("before")
val cue = PseudoElement("cue")
val cueRegion = PseudoElement("cue-region")
val firstLetter = PseudoElement("first-letter")
val firstLine = PseudoElement("first-line")
val fileSelectorButton = PseudoElement("file-selector-button")
val selection = PseudoElement("selection")
}
class Slotted(val selector: CSSSelector) : PseudoElement("slotted") {
override fun argsStr() = selector.toString()
}
}
}
fun selector(selector: String) = CSSSelector.Raw(selector)
fun combine(vararg selectors: CSSSelector) = CSSSelector.Combine(selectors.toMutableList())
operator fun CSSSelector.plus(selector: CSSSelector) = combine(this, selector)
operator fun String.plus(selector: CSSSelector) = combine(selector(this), selector)
operator fun CSSSelector.plus(selector: String) = combine(this, selector(selector))
operator fun CSSSelector.Combine.plus(selector: CSSSelector) { this.selectors.add(selector) }
operator fun CSSSelector.Combine.plus(selector: String) { this.selectors.add(selector(selector)) }
fun universal() = CSSSelector.Universal
fun type(type: String) = CSSSelector.Type(type)
fun className(className: String) = CSSSelector.CSSClass(className)
fun id(id: String) = CSSSelector.Id(id)
fun attr(
name: String,
value: String? = null,
operator: CSSSelector.Attribute.Operator = CSSSelector.Attribute.Operator.Equals,
caseSensitive: Boolean = true
) = CSSSelector.Attribute(name, value, operator, caseSensitive)
fun group(vararg selectors: CSSSelector) = CSSSelector.Group(selectors.toList())
fun descendant(parent: CSSSelector, selected: CSSSelector) =
CSSSelector.Descendant(parent, selected)
fun child(parent: CSSSelector, selected: CSSSelector) =
CSSSelector.Child(parent, selected)
fun sibling(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Descendant(sibling, selected)
fun adjacent(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Adjacent(sibling, selected)
fun not(selector: CSSSelector) = CSSSelector.PseudoClass.Not(selector)
fun hover() = CSSSelector.PseudoClass.hover
fun hover(selector: CSSSelector) = selector + hover()

111
web/core/src/jsMain/kotlin/androidx/compose/web/elements/Base.kt

@ -0,0 +1,111 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.elements
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeCompilerApi
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.DisposableEffectResult
import androidx.compose.runtime.DisposableEffectScope
import androidx.compose.runtime.ExplicitGroupsComposable
import androidx.compose.runtime.SkippableUpdater
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.remember
import androidx.compose.web.DomApplier
import androidx.compose.web.DomNodeWrapper
import androidx.compose.web.attributes.AttrsBuilder
import androidx.compose.web.attributes.Tag
import androidx.compose.web.css.StyleBuilder
import androidx.compose.web.css.StyleBuilderImpl
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
@OptIn(ComposeCompilerApi::class)
@Composable
@ExplicitGroupsComposable
inline fun <TScope, T, reified E : Applier<*>> ComposeDomNode(
noinline factory: () -> T,
elementScope: TScope,
noinline attrsSkippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
noinline styleSkippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
content: @Composable TScope.() -> Unit
) {
if (currentComposer.applier !is E) error("Invalid applier")
currentComposer.startNode()
if (currentComposer.inserting) {
currentComposer.createNode(factory)
} else {
currentComposer.useNode()
}
// Updater<T>(currentComposer).update()
SkippableUpdater<T>(currentComposer).apply {
attrsSkippableUpdate()
styleSkippableUpdate()
}
currentComposer.startReplaceableGroup(0x7ab4aae9)
content(elementScope)
currentComposer.endReplaceableGroup()
currentComposer.endNode()
}
class DisposableEffectHolder(
var effect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null
)
@Composable
inline fun <TTag : Tag, THTMLElement : HTMLElement> TagElement(
tagName: String,
crossinline applyAttrs: AttrsBuilder<TTag>.() -> Unit,
crossinline applyStyle: StyleBuilder.() -> Unit,
content: @Composable ElementScope<THTMLElement>.() -> Unit
) {
val scope = remember { ElementScopeImpl<THTMLElement>() }
val refEffect = remember { DisposableEffectHolder() }
ComposeDomNode<ElementScope<THTMLElement>, DomNodeWrapper, DomApplier>(
factory = {
DomNodeWrapper(document.createElement(tagName)).also {
scope.element = it.node.unsafeCast<THTMLElement>()
}
},
attrsSkippableUpdate = {
val attrsApplied = AttrsBuilder<TTag>().also { it.applyAttrs() }
refEffect.effect = attrsApplied.refEffect
val attrsCollected = attrsApplied.collect()
val events = attrsApplied.asList()
update {
set(attrsCollected, DomNodeWrapper.UpdateAttrs)
set(events, DomNodeWrapper.UpdateListeners)
set(attrsApplied.propertyUpdates, DomNodeWrapper.UpdateProperties)
}
},
styleSkippableUpdate = {
val style = StyleBuilderImpl().apply(applyStyle)
update {
set(style, DomNodeWrapper.UpdateStyleDeclarations)
}
},
elementScope = scope,
content = content
)
DisposableEffect(null) {
refEffect.effect?.invoke(this, scope.element) ?: onDispose {}
}
}

120
web/core/src/jsMain/kotlin/androidx/compose/web/elements/ElementScope.kt

@ -0,0 +1,120 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.elements
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeCompilerApi
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.DisposableEffectResult
import androidx.compose.runtime.DisposableEffectScope
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.remember
import org.w3c.dom.HTMLElement
interface DOMScope<out THTMLElement : HTMLElement>
interface ElementScope<out THTMLElement : HTMLElement> : DOMScope<THTMLElement> {
@Composable
@NonRestartableComposable
fun DisposableRefEffect(
key: Any?,
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult
)
@Composable
@NonRestartableComposable
fun DisposableRefEffect(
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult
) {
DisposableRefEffect(null, effect)
}
@Composable
@NonRestartableComposable
fun DomSideEffect(key: Any?, effect: DomEffectScope.(THTMLElement) -> Unit)
@Composable
@NonRestartableComposable
fun DomSideEffect(effect: DomEffectScope.(THTMLElement) -> Unit)
}
abstract class ElementScopeBase<out THTMLElement : HTMLElement> : ElementScope<THTMLElement> {
abstract val element: THTMLElement
private var nextDisposableDomEffectKey = 0
@Composable
@NonRestartableComposable
override fun DisposableRefEffect(
key: Any?,
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult
) {
DisposableEffect(key) { effect(element) }
}
@Composable
@NonRestartableComposable
@OptIn(ComposeCompilerApi::class)
override fun DomSideEffect(
key: Any?,
effect: DomEffectScope.(THTMLElement) -> Unit
) {
val changed = currentComposer.changed(key)
val effectHolder = remember(key) {
DomDisposableEffectHolder(this)
}
SideEffect {
if (changed) effectHolder.effect(element)
}
}
@Composable
@NonRestartableComposable
override fun DomSideEffect(effect: DomEffectScope.(THTMLElement) -> Unit) {
DomSideEffect(nextDisposableDomEffectKey++, effect)
}
}
open class ElementScopeImpl<THTMLElement : HTMLElement> : ElementScopeBase<THTMLElement>() {
public override lateinit var element: THTMLElement
}
interface DomEffectScope {
fun onDispose(disposeEffect: (HTMLElement) -> Unit)
}
private class DomDisposableEffectHolder(
val elementScope: ElementScopeBase<HTMLElement>
) : RememberObserver, DomEffectScope {
var onDispose: ((HTMLElement) -> Unit)? = null
override fun onRemembered() {}
override fun onForgotten() {
onDispose?.invoke(elementScope.element)
}
override fun onAbandoned() {}
override fun onDispose(disposeEffect: (HTMLElement) -> Unit) {
onDispose = disposeEffect
}
}

577
web/core/src/jsMain/kotlin/androidx/compose/web/elements/Elements.kt

@ -0,0 +1,577 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.elements
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
import androidx.compose.web.DomApplier
import androidx.compose.web.DomNodeWrapper
import androidx.compose.web.attributes.AttrsBuilder
import androidx.compose.web.attributes.InputType
import androidx.compose.web.attributes.Tag
import androidx.compose.web.attributes.action
import androidx.compose.web.attributes.alt
import androidx.compose.web.attributes.forId
import androidx.compose.web.attributes.href
import androidx.compose.web.attributes.label
import androidx.compose.web.attributes.src
import androidx.compose.web.attributes.type
import androidx.compose.web.attributes.value
import androidx.compose.web.css.StyleBuilder
import kotlinx.browser.document
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLBRElement
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLFormElement
import org.w3c.dom.HTMLHeadingElement
import org.w3c.dom.HTMLImageElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLLIElement
import org.w3c.dom.HTMLOListElement
import org.w3c.dom.HTMLOptGroupElement
import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.HTMLParagraphElement
import org.w3c.dom.HTMLPreElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.HTMLTableCaptionElement
import org.w3c.dom.HTMLTableCellElement
import org.w3c.dom.HTMLTableColElement
import org.w3c.dom.HTMLTableElement
import org.w3c.dom.HTMLTableRowElement
import org.w3c.dom.HTMLTableSectionElement
import org.w3c.dom.HTMLTextAreaElement
import org.w3c.dom.HTMLUListElement
import org.w3c.dom.Text
@Composable
fun Text(value: String) {
ComposeNode<DomNodeWrapper, DomApplier>(
factory = { DomNodeWrapper(document.createTextNode("")) },
update = {
set(value) { value -> (node as Text).data = value }
},
)
}
@Composable
inline fun Div(
crossinline attrs: (AttrsBuilder<Tag.Div>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLDivElement>.() -> Unit
) {
TagElement(
tagName = "div",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun A(
href: String? = null,
crossinline attrs: (AttrsBuilder<Tag.A>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLAnchorElement>.() -> Unit
) {
TagElement<Tag.A, HTMLAnchorElement>(
tagName = "a",
applyAttrs = {
href(href)
attrs()
},
applyStyle = style,
content = content
)
}
@Composable
inline fun Input(
type: InputType = InputType.Text,
value: String = "",
crossinline attrs: (AttrsBuilder<Tag.Input>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLInputElement>.() -> Unit = {}
) {
TagElement<Tag.Input, HTMLInputElement>(
tagName = "input",
applyAttrs = {
type(type)
value(value)
attrs()
},
applyStyle = style,
content = content
)
}
@Composable
inline fun Button(
crossinline attrs: AttrsBuilder<Tag.Button>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("button", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun H1(
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("h1", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun H2(
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("h2", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun H3(
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("h3", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun H4(
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("h4", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun H5(
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("h5", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun H6(
crossinline attrs: AttrsBuilder<Tag.H>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLHeadingElement>.() -> Unit
) = TagElement("h6", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun P(
crossinline attrs: AttrsBuilder<Tag.P>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLParagraphElement>.() -> Unit
) = TagElement("p", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Em(
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) = TagElement("em", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun I(
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) = TagElement("i", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun B(
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) = TagElement("b", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Small(
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) = TagElement("small", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Span(
crossinline attrs: AttrsBuilder<Tag.Span>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLSpanElement>.() -> Unit
) = TagElement("span", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Br(
crossinline attrs: AttrsBuilder<Tag.Br>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLBRElement>.() -> Unit
) = TagElement("br", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Ul(
crossinline attrs: AttrsBuilder<Tag.Ul>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLUListElement>.() -> Unit,
) = TagElement("ul", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Ol(
crossinline attrs: AttrsBuilder<Tag.Ol>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLOListElement>.() -> Unit
) = TagElement("ol", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun DOMScope<HTMLOListElement>.Li(
crossinline attrs: AttrsBuilder<Tag.Li>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLLIElement>.() -> Unit
) = TagElement("li", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun DOMScope<HTMLUListElement>.Li(
crossinline attrs: AttrsBuilder<Tag.Li>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLLIElement>.() -> Unit
) = TagElement("li", applyAttrs = attrs, applyStyle = style, content = content)
@Composable
inline fun Img(
src: String,
alt: String = "",
crossinline attrs: AttrsBuilder<Tag.Img>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLImageElement>.() -> Unit = {}
) = TagElement<Tag.Img, HTMLImageElement>(
tagName = "img",
applyAttrs = {
src(src).alt(alt)
attrs()
},
applyStyle = style, content = content
)
@Composable
inline fun Form(
action: String? = null,
crossinline attrs: AttrsBuilder<Tag.Form>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLFormElement>.() -> Unit
) = TagElement<Tag.Form, HTMLFormElement>(
tagName = "form",
applyAttrs = {
if (!action.isNullOrEmpty()) action(action)
attrs()
},
applyStyle = style, content = content
)
@Composable
inline fun Select(
crossinline attrs: AttrsBuilder<Tag.Select>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLSelectElement>.() -> Unit
) = TagElement(
tagName = "select",
applyAttrs = attrs,
applyStyle = style,
content = content
)
@Composable
inline fun DOMScope<HTMLUListElement>.Option(
value: String,
crossinline attrs: AttrsBuilder<Tag.Option>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLOptionElement>.() -> Unit
) = TagElement<Tag.Option, HTMLOptionElement>(
tagName = "option",
applyAttrs = {
value(value)
attrs()
},
applyStyle = style,
content = content
)
@Composable
inline fun OptGroup(
label: String,
crossinline attrs: AttrsBuilder<Tag.OptGroup>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLOptGroupElement>.() -> Unit
) = TagElement<Tag.OptGroup, HTMLOptGroupElement>(
tagName = "optgroup",
applyAttrs = {
label(label)
attrs()
},
applyStyle = style,
content = content
)
@Composable
inline fun Section(
crossinline attrs: AttrsBuilder<Tag>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) = TagElement(
tagName = "section",
applyAttrs = attrs,
applyStyle = style,
content = content
)
@Composable
inline fun TextArea(
crossinline attrs: AttrsBuilder<Tag.TextArea>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
value: String
) = TagElement<Tag.TextArea, HTMLTextAreaElement>(
tagName = "textarea",
applyAttrs = {
value(value)
attrs()
},
applyStyle = style
) {
Text(value)
}
@Composable
inline fun Nav(
crossinline attrs: AttrsBuilder<Tag.Nav>.() -> Unit = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) = TagElement(
tagName = "nav",
applyAttrs = attrs,
applyStyle = style,
content = content
)
@Composable
inline fun Pre(
crossinline attrs: (AttrsBuilder<Tag.Pre>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLPreElement>.() -> Unit
) {
TagElement(
tagName = "pre",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Code(
crossinline attrs: (AttrsBuilder<Tag.Code>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit
) {
TagElement(
tagName = "code",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Main(
crossinline attrs: (AttrsBuilder<Tag.Div>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit = {}
) {
TagElement<Tag.Div, HTMLAnchorElement>(
tagName = "main",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Footer(
crossinline attrs: (AttrsBuilder<Tag.Div>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit = {}
) {
TagElement<Tag.Div, HTMLAnchorElement>(
tagName = "footer",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Label(
forId: String? = null,
crossinline attrs: (AttrsBuilder<Tag.Label>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLElement>.() -> Unit = {}
) {
TagElement<Tag.Label, HTMLAnchorElement>(
tagName = "label",
applyAttrs = {
forId(forId)
attrs()
},
applyStyle = style,
content = content
)
}
@Composable
inline fun Table(
crossinline attrs: (AttrsBuilder<Tag.Table>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableElement>.() -> Unit
) {
TagElement(
tagName = "table",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Caption(
crossinline attrs: (AttrsBuilder<Tag.Caption>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableCaptionElement>.() -> Unit
) {
TagElement(
tagName = "caption",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Col(
crossinline attrs: (AttrsBuilder<Tag.Col>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableColElement>.() -> Unit
) {
TagElement(
tagName = "col",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Colgroup(
crossinline attrs: (AttrsBuilder<Tag.Colgroup>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableColElement>.() -> Unit
) {
TagElement(
tagName = "colgroup",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Tr(
crossinline attrs: (AttrsBuilder<Tag.Tr>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableRowElement>.() -> Unit
) {
TagElement(
tagName = "tr",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Thead(
crossinline attrs: (AttrsBuilder<Tag.Thead>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableSectionElement>.() -> Unit
) {
TagElement(
tagName = "thead",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Th(
crossinline attrs: (AttrsBuilder<Tag.Th>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableCellElement>.() -> Unit
) {
TagElement(
tagName = "th",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Td(
crossinline attrs: (AttrsBuilder<Tag.Td>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableCellElement>.() -> Unit
) {
TagElement(
tagName = "td",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Tbody(
crossinline attrs: (AttrsBuilder<Tag.Tbody>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableSectionElement>.() -> Unit
) {
TagElement(
tagName = "tbody",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}
@Composable
inline fun Tfoot(
crossinline attrs: (AttrsBuilder<Tag.Tfoot>.() -> Unit) = {},
crossinline style: (StyleBuilder.() -> Unit) = {},
content: @Composable ElementScope<HTMLTableSectionElement>.() -> Unit
) {
TagElement(
tagName = "tfoot",
applyAttrs = attrs,
applyStyle = style,
content = content
)
}

155
web/core/src/jsMain/kotlin/androidx/compose/web/elements/Style.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.elements
import androidx.compose.runtime.Composable
import androidx.compose.web.attributes.AttrsBuilder
import androidx.compose.web.attributes.Tag
import androidx.compose.web.css.CSSGroupingRuleDeclaration
import androidx.compose.web.css.CSSRuleDeclaration
import androidx.compose.web.css.CSSRuleDeclarationList
import androidx.compose.web.css.CSSStyleRuleDeclaration
import androidx.compose.web.css.StylePropertyMap
import androidx.compose.web.css.StylePropertyValue
import androidx.compose.web.css.StyleSheetBuilder
import androidx.compose.web.css.StyleSheetBuilderImpl
import androidx.compose.web.css.cssRules
import androidx.compose.web.css.deleteRule
import androidx.compose.web.css.get
import androidx.compose.web.css.insertRule
import androidx.compose.web.css.styleMap
import org.w3c.dom.HTMLStyleElement
import org.w3c.dom.css.CSSGroupingRule
import org.w3c.dom.css.CSSRule
import org.w3c.dom.css.CSSStyleDeclaration
import org.w3c.dom.css.CSSStyleRule
import org.w3c.dom.css.StyleSheet
/**
* Use this function to mount the <style> tag into the DOM tree.
*
* @param rulesBuild allows to define the style rules using [StyleSheetBuilder]
*/
@Composable
inline fun Style(
crossinline applyAttrs: AttrsBuilder<Tag.Style>.() -> Unit = {},
rulesBuild: StyleSheetBuilder.() -> Unit
) {
val builder = StyleSheetBuilderImpl()
builder.rulesBuild()
Style(applyAttrs, builder.cssRules)
}
/**
* Use this function to mount the <style> tag into the DOM tree.
*
* @param cssRules - is a list of style rules.
* Usually, it's [androidx.compose.web.css.StyleSheet] instance
*/
@Composable
inline fun Style(
crossinline applyAttrs: AttrsBuilder<Tag.Style>.() -> Unit = {},
cssRules: CSSRuleDeclarationList
) {
TagElement<Tag.Style, HTMLStyleElement>(
tagName = "style",
applyAttrs = {
applyAttrs()
},
applyStyle = {}
) {
DomSideEffect(cssRules) { style ->
style.sheet?.let { sheet ->
setCSSRules(sheet, cssRules)
onDispose {
clearCSSRules(sheet)
}
}
}
}
}
fun clearCSSRules(sheet: StyleSheet) {
repeat(sheet.cssRules.length) {
sheet.deleteRule(0)
}
}
fun setCSSRules(sheet: StyleSheet, cssRules: CSSRuleDeclarationList) {
cssRules.forEach { cssRule ->
sheet.addRule(cssRule)
}
}
private fun StyleSheet.addRule(cssRule: String): CSSRule {
val cssRuleIndex = this.insertRule(cssRule, this.cssRules.length)
return this.cssRules[cssRuleIndex]
}
private fun CSSGroupingRule.addRule(cssRule: String): CSSRule {
val cssRuleIndex = this.insertRule(cssRule, this.cssRules.length)
return this.cssRules[cssRuleIndex]
}
private fun StyleSheet.addRule(cssRuleDeclaration: CSSRuleDeclaration) {
val cssRule = addRule("${cssRuleDeclaration.header} {}")
fillRule(cssRuleDeclaration, cssRule)
}
private fun CSSGroupingRule.addRule(cssRuleDeclaration: CSSRuleDeclaration) {
val cssRule = addRule("${cssRuleDeclaration.header} {}")
fillRule(cssRuleDeclaration, cssRule)
}
private fun fillRule(
cssRuleDeclaration: CSSRuleDeclaration,
cssRule: CSSRule
) {
when (cssRuleDeclaration) {
is CSSStyleRuleDeclaration -> {
val cssStyleRule = cssRule.unsafeCast<CSSStyleRule>()
cssRuleDeclaration.style.properties.forEach { (name, value) ->
setProperty(cssStyleRule.styleMap, name, value)
}
cssRuleDeclaration.style.variables.forEach { (name, value) ->
setVariable(cssStyleRule.style, name, value)
}
}
is CSSGroupingRuleDeclaration -> {
val cssGroupingRule = cssRule.unsafeCast<CSSGroupingRule>()
cssRuleDeclaration.rules.forEach { childRuleDeclaration ->
cssGroupingRule.addRule(childRuleDeclaration)
}
}
}
}
fun setProperty(
styleMap: StylePropertyMap,
name: String,
value: StylePropertyValue
) {
styleMap.set(name, value)
}
fun setVariable(
style: CSSStyleDeclaration,
name: String,
value: StylePropertyValue
) {
style.setProperty("--$name", value.toString())
}

123
web/core/src/jsMain/kotlin/androidx/compose/web/events/WrappedEvent.kt

@ -0,0 +1,123 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.web.events
import org.w3c.dom.DragEvent
import org.w3c.dom.TouchEvent
import org.w3c.dom.clipboard.ClipboardEvent
import org.w3c.dom.events.CompositionEvent
import org.w3c.dom.events.Event
import org.w3c.dom.events.FocusEvent
import org.w3c.dom.events.InputEvent
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import org.w3c.dom.events.WheelEvent
import org.w3c.dom.pointerevents.PointerEvent
interface GenericWrappedEvent<T : Event> {
val nativeEvent: T
}
interface WrappedEvent : GenericWrappedEvent<Event>
open class WrappedMouseEvent(
override val nativeEvent: MouseEvent
) : GenericWrappedEvent<MouseEvent> {
// MouseEvent doesn't support movementX and movementY on IE6-11, and it's OK for now.
val movementX: Double
get() = nativeEvent.asDynamic().movementX as Double
val movementY: Double
get() = nativeEvent.asDynamic().movementY as Double
}
open class WrappedWheelEvent(
override val nativeEvent: WheelEvent
) : GenericWrappedEvent<WheelEvent>
open class WrappedInputEvent(
override val nativeEvent: InputEvent
) : GenericWrappedEvent<InputEvent>
open class WrappedKeyboardEvent(
override val nativeEvent: KeyboardEvent
) : GenericWrappedEvent<KeyboardEvent> {
fun getNormalizedKey(): String = nativeEvent.key.let {
normalizedKeys[it] ?: it
}
companion object {
private val normalizedKeys = mapOf(
"Esc" to "Escape",
"Spacebar" to " ",
"Left" to "ArrowLeft",
"Up" to "ArrowUp",
"Right" to "ArrowRight",
"Down" to "ArrowDown",
"Del" to "Delete",
"Apps" to "ContextMenu",
"Menu" to "ContextMenu",
"Scroll" to "ScrollLock",
"MozPrintableKey" to "Unidentified",
)
// Firefox bug for Windows key https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
}
}
open class WrappedFocusEvent(
override val nativeEvent: FocusEvent
) : GenericWrappedEvent<FocusEvent>
open class WrappedTouchEvent(
override val nativeEvent: TouchEvent
) : GenericWrappedEvent<TouchEvent>
open class WrappedCompositionEvent(
override val nativeEvent: CompositionEvent
) : GenericWrappedEvent<CompositionEvent>
open class WrappedDragEvent(
override val nativeEvent: DragEvent
) : GenericWrappedEvent<DragEvent>
open class WrappedPointerEvent(
override val nativeEvent: PointerEvent
) : GenericWrappedEvent<PointerEvent>
open class WrappedClipboardEvent(
override val nativeEvent: ClipboardEvent
) : GenericWrappedEvent<ClipboardEvent>
class WrappedTextInputEvent(
nativeEvent: InputEvent,
val inputValue: String
) : WrappedInputEvent(nativeEvent)
class WrappedCheckBoxInputEvent(
override val nativeEvent: Event,
val checked: Boolean
) : GenericWrappedEvent<Event>
class WrappedRadioInputEvent(
override val nativeEvent: Event,
val checked: Boolean
) : GenericWrappedEvent<Event>
class WrappedEventImpl(
override val nativeEvent: Event
) : WrappedEvent

47
web/core/src/jsMain/kotlin/withWeb/Modifier.kt

@ -0,0 +1,47 @@
package org.jetbrains.compose.common.ui
import org.jetbrains.compose.common.ui.unit.Dp
import org.jetbrains.compose.common.core.graphics.Color
import androidx.compose.web.css.backgroundColor
import androidx.compose.web.css.margin
import androidx.compose.web.css.px
import androidx.compose.web.css.Color.RGB
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.StyleBuilder
import androidx.compose.web.attributes.AttrsBuilder
actual fun Modifier.background(color: Color): Modifier = castOrCreate().apply {
add {
backgroundColor(RGB(color.red, color.green, color.blue))
}
}
fun Modifier.asStyleBuilderApplier(
passThroughHandler: (StyleBuilder.() -> Unit)? = null
): StyleBuilder.() -> Unit = castOrCreate().let { modifier ->
val st: StyleBuilder.() -> Unit = {
modifier.styleHandlers.forEach { it.invoke(this) }
passThroughHandler?.invoke(this)
}
st
}
fun Modifier.asAttributeBuilderApplier(
passThroughHandler: (AttrsBuilder<*>.() -> Unit)? = null
): AttrsBuilder<*>.() -> Unit =
castOrCreate().let { modifier ->
val st: AttrsBuilder<*>.() -> Unit = {
modifier.attrHandlers.forEach { it.invoke(this) }
passThroughHandler?.invoke(this)
}
st
}
actual fun Modifier.padding(all: Dp): Modifier = castOrCreate().apply {
// yes, it's not a typo, what Modifier.padding does is actually adding marginEe
add {
margin(all.value.px)
}
}

63
web/core/src/jsMain/kotlin/withWeb/Styles.kt

@ -0,0 +1,63 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.web.ui
import androidx.compose.web.css.justifyContent
import androidx.compose.web.css.JustifyContent
import androidx.compose.web.css.alignItems
import androidx.compose.web.css.AlignItems
import androidx.compose.web.css.flexDirection
import androidx.compose.web.css.FlexDirection
import androidx.compose.web.css.display
import androidx.compose.web.css.DisplayStyle
import androidx.compose.web.css.left
import androidx.compose.web.css.px
import androidx.compose.web.css.StyleSheet
object Styles : StyleSheet() {
val columnClass = "compose-web-column"
val textClass by style {
display(DisplayStyle.Block)
left(0.px)
}
val rowClass by style {
display(DisplayStyle.Flex)
flexDirection(FlexDirection.Row)
}
val composeWebArrangementHorizontalStart by style {
justifyContent(JustifyContent.FlexStart)
}
val composeWebArrangementHorizontalEnd by style {
justifyContent(JustifyContent.FlexEnd)
}
val composeWebAlignmentVerticalTop by style {
alignItems(AlignItems.FlexStart)
}
val composeWebAlignmentVerticalCenter by style {
alignItems(AlignItems.Center)
}
val composeWebAlignmentVerticalBottom by style {
alignItems(AlignItems.FlexEnd)
}
}

35
web/core/src/jsMain/kotlin/withWeb/internal/ActualModifier.kt

@ -0,0 +1,35 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.internal
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.web.css.StyleBuilder
import androidx.compose.web.attributes.AttrsBuilder
class ActualModifier : Modifier {
val styleHandlers = mutableListOf<StyleBuilder.() -> Unit>()
val attrHandlers = mutableListOf<AttrsBuilder<*>.() -> Unit>()
fun add(builder: StyleBuilder.() -> Unit) {
styleHandlers.add(builder)
}
fun addAttributeBuilder(builder: AttrsBuilder<*>.() -> Unit) {
attrHandlers.add(builder)
}
}
fun Modifier.castOrCreate(): ActualModifier = (this as? ActualModifier) ?: ActualModifier()

33
web/core/src/jsMain/kotlin/withWeb/layouts/box.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.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.ui.asStyleBuilderApplier
import androidx.compose.runtime.Composable
import androidx.compose.web.elements.Div
import org.jetbrains.compose.common.ui.asAttributeBuilderApplier
@Composable
internal actual fun BoxActual(modifier: Modifier, content: @Composable () -> Unit) {
Div(
style = modifier.asStyleBuilderApplier(),
attrs = modifier.asAttributeBuilderApplier()
) {
content()
}
}

37
web/core/src/jsMain/kotlin/withWeb/layouts/button.kt

@ -0,0 +1,37 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.material
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.runtime.Composable
import androidx.compose.web.elements.Button
import org.jetbrains.compose.common.ui.asStyleBuilderApplier
@Composable
actual fun ButtonActual(
modifier: Modifier,
onClick: () -> Unit,
content: @Composable () -> Unit
) {
Button(
style = modifier.asStyleBuilderApplier(),
attrs = {
onClick { onClick() }
}
) {
content()
}
}

35
web/core/src/jsMain/kotlin/withWeb/layouts/column.kt

@ -0,0 +1,35 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.runtime.Composable
import org.jetbrains.compose.common.ui.asStyleBuilderApplier
import androidx.compose.web.elements.Div
import org.jetbrains.compose.web.ui.Styles
@Composable
internal actual fun ColumnActual(modifier: Modifier, content: @Composable () -> Unit) {
Div(
attrs = {
classes(Styles.columnClass)
},
style = modifier.asStyleBuilderApplier()
) {
content()
}
}

58
web/core/src/jsMain/kotlin/withWeb/layouts/row.kt

@ -0,0 +1,58 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.runtime.Composable
import androidx.compose.web.elements.Div
import org.jetbrains.compose.common.ui.asStyleBuilderApplier
import org.jetbrains.compose.common.ui.Alignment
import org.jetbrains.compose.web.ui.Styles
private fun Arrangement.Horizontal.asClassName() = when (this) {
Arrangement.End -> Styles.composeWebArrangementHorizontalEnd
else -> Styles.composeWebArrangementHorizontalStart
}
private fun Alignment.Vertical.asClassName() = when (this) {
Alignment.Top -> Styles.composeWebAlignmentVerticalTop
Alignment.CenterVertically -> Styles.composeWebAlignmentVerticalCenter
else -> Styles.composeWebAlignmentVerticalBottom
}
@Composable
internal actual fun RowActual(
modifier: Modifier,
horizontalArrangement: Arrangement.Horizontal,
verticalAlignment: Alignment.Vertical,
content: @Composable () -> Unit
) {
Div(
attrs = {
classes(
*arrayOf(
Styles.rowClass,
horizontalArrangement.asClassName(),
verticalAlignment.asClassName()
)
)
},
style = modifier.asStyleBuilderApplier()
) {
content()
}
}

47
web/core/src/jsMain/kotlin/withWeb/layouts/slider.kt

@ -0,0 +1,47 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.material
import androidx.compose.runtime.Composable
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.web.elements.Input
import androidx.compose.web.attributes.InputType
@Composable
actual fun SliderActual(
value: Float,
onValueChange: (Float) -> Unit,
valueRange: ClosedFloatingPointRange<Float>,
steps: Int,
modifier: Modifier,
) {
val stepCount = if (steps == 0) 100 else steps
val step = (valueRange.endInclusive - valueRange.start) / stepCount
Input(
type = InputType.Range,
value = value.toString(),
attrs = {
attr("min", valueRange.start.toString())
attr("max", valueRange.endInclusive.toString())
attr("step", step.toString())
onRangeInput {
val value: String = it.nativeEvent.target.asDynamic().value
onValueChange(value.toFloat())
}
}
) {}
}

55
web/core/src/jsMain/kotlin/withWeb/layouts/text.kt

@ -0,0 +1,55 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.material
import androidx.compose.runtime.Composable
import androidx.compose.web.elements.Text as TextNode
import androidx.compose.web.elements.Span
import org.jetbrains.compose.web.ui.Styles
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.ui.asStyleBuilderApplier
import org.jetbrains.compose.common.ui.asAttributeBuilderApplier
import org.jetbrains.compose.common.core.graphics.Color
import androidx.compose.web.css.color
import androidx.compose.web.css.fontSize
import androidx.compose.web.css.Color.RGB
import org.jetbrains.compose.common.ui.unit.TextUnit
import org.jetbrains.compose.common.ui.unit.TextUnitType
import androidx.compose.web.css.em
import androidx.compose.web.css.px
@Composable
actual fun TextActual(
text: String,
modifier: Modifier,
color: Color,
size: TextUnit
) {
Span(
style = modifier.asStyleBuilderApplier() {
color(RGB(color.red, color.green, color.blue))
when (size.unitType) {
TextUnitType.Em -> fontSize(size.value.em)
TextUnitType.Sp -> fontSize(size.value.px)
}
},
attrs = modifier.asAttributeBuilderApplier() {
classes(Styles.textClass)
}
) {
TextNode(text)
}
}

31
web/core/src/jsMain/kotlin/withWeb/modifiers/border.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.foundation
import org.jetbrains.compose.common.ui.unit.Dp
import org.jetbrains.compose.common.core.graphics.Color
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.px
import androidx.compose.web.css.LineStyle
import androidx.compose.web.css.border
import androidx.compose.web.css.Color.RGB
actual fun Modifier.border(size: Dp, color: Color): Modifier = castOrCreate().apply {
add {
border(size.value.px, LineStyle.Solid, RGB(color.red, color.green, color.blue))
}
}

25
web/core/src/jsMain/kotlin/withWeb/modifiers/clickable.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
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.internal.castOrCreate
actual fun Modifier.clickable(onClick: () -> Unit): Modifier = castOrCreate().apply {
addAttributeBuilder {
onClick { onClick() }
}
}

31
web/core/src/jsMain/kotlin/withWeb/modifiers/clip.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.ui.draw
import org.jetbrains.compose.common.ui.Modifier
import jetbrains.compose.common.shapes.Shape
import jetbrains.compose.common.shapes.CircleShape
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.borderRadius
import androidx.compose.web.css.percent
actual fun Modifier.clip(shape: Shape): Modifier = castOrCreate().apply {
when (shape) {
CircleShape -> add {
borderRadius(50.percent)
}
}
}

27
web/core/src/jsMain/kotlin/withWeb/modifiers/fillMaxHeight.kt

@ -0,0 +1,27 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.height
import androidx.compose.web.css.percent
actual fun Modifier.fillMaxHeight(fraction: Float): Modifier = castOrCreate().apply {
add {
height((100 * fraction).percent)
}
}

27
web/core/src/jsMain/kotlin/withWeb/modifiers/fillMaxWidth.kt

@ -0,0 +1,27 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.width
import androidx.compose.web.css.percent
actual fun Modifier.fillMaxWidth(): Modifier = castOrCreate().apply {
add {
width(100.percent)
}
}

30
web/core/src/jsMain/kotlin/withWeb/modifiers/offset.kt

@ -0,0 +1,30 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import org.jetbrains.compose.common.ui.unit.Dp
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.marginTop
import androidx.compose.web.css.marginLeft
import androidx.compose.web.css.px
actual fun Modifier.offset(x: Dp, y: Dp): Modifier = castOrCreate().apply {
add {
marginLeft(x.value.px)
marginTop(y.value.px)
}
}

25
web/core/src/jsMain/kotlin/withWeb/modifiers/onSizeChanged.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.ui.layout
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.ui.unit.IntSize
actual fun Modifier.onSizeChanged(
onSizeChanged: (IntSize) -> Unit
): Modifier {
return this
}

14
web/core/src/jsMain/kotlin/withWeb/modifiers/size.kt

@ -0,0 +1,14 @@
package org.jetbrains.compose.common.ui
import org.jetbrains.compose.common.ui.unit.Dp
import androidx.compose.web.css.width
import androidx.compose.web.css.height
import androidx.compose.web.css.px
import org.jetbrains.compose.common.internal.castOrCreate
actual fun Modifier.size(width: Dp, height: Dp): Modifier = castOrCreate().apply {
add {
width(width.value.px)
height(height.value.px)
}
}

28
web/core/src/jsMain/kotlin/withWeb/modifiers/width.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.unit.Dp
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.web.css.px
import androidx.compose.web.css.width
actual fun Modifier.width(size: Dp): Modifier = castOrCreate().apply {
add {
width(size.value.px)
}
}

28
web/core/src/jsMain/resources/index.html

@ -0,0 +1,28 @@
<!--
~ Copyright 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>compose-browser-demo</title>
</head>
<body>
<h1>Hello world!</h1>
<div id="root"/>
<script src="web.js"></script>
</body>
</html>

126
web/core/src/jsTest/kotlin/DomSideEffectTests.kt

@ -0,0 +1,126 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.web.elements.Div
import androidx.compose.web.renderComposable
import kotlinx.browser.document
import kotlinx.dom.clear
import kotlin.test.Test
import kotlin.test.assertEquals
class DomSideEffectTests {
@Test
fun canCreateElementsInDomSideEffect() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div {
DomSideEffect {
it.appendChild(
document.createElement("p").also {
it.appendChild(document.createTextNode("Hello World!"))
}
)
}
}
}
assertEquals(
expected = "<div><div style=\"\"><p>Hello World!</p></div></div>",
actual = root.outerHTML
)
}
@Test
fun canUpdateElementsCreatedInDomSideEffect() = runTest {
var i: Int by mutableStateOf(0)
val disposeCalls = mutableListOf<Int>()
@Composable
fun CustomDiv(value: Int) {
Div {
DomSideEffect(value) {
it.appendChild(
it.appendChild(document.createTextNode("Value = $value"))
)
onDispose {
disposeCalls.add(value)
it.clear()
}
}
}
}
composition { CustomDiv(i) }
assertEquals(
expected = "<div><div style=\"\">Value = 0</div></div>",
actual = root.outerHTML
)
i = 1
waitChanges()
assertEquals(
expected = 1,
actual = disposeCalls.size,
message = "Only one onDispose call expected"
)
assertEquals(
expected = 0,
actual = disposeCalls[0],
message = "onDispose should be called with a previous value"
)
assertEquals(
expected = "<div><div style=\"\">Value = 1</div></div>",
actual = root.outerHTML
)
}
@Test
fun onDisposeIsCalledWhenComposableRemovedFromComposition() = runTest {
var showDiv: Boolean by mutableStateOf(true)
var onDisposeCalledTimes = 0
composition {
if (showDiv) {
Div {
DomSideEffect {
it.appendChild(document.createTextNode("Goedemorgen!"))
onDispose { onDisposeCalledTimes++ }
}
}
}
}
assertEquals(
expected = "<div><div style=\"\">Goedemorgen!</div></div>",
actual = root.outerHTML
)
showDiv = false
waitChanges()
assertEquals(1, onDisposeCalledTimes)
assertEquals(expected = "<div></div>", actual = root.outerHTML)
}
}

153
web/core/src/jsTest/kotlin/InlineStyleTests.kt

@ -0,0 +1,153 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.web.css.color
import androidx.compose.web.elements.Span
import androidx.compose.web.elements.Text
import kotlin.test.Test
import kotlin.test.assertEquals
class InlineStyleTests {
@Test
fun conditionalStyleAppliedProperly() = runTest {
var isRed by mutableStateOf(true)
composition {
Span(
style = {
if (isRed) {
color("red")
} else {
color("green")
}
}
) {
Text("text")
}
}
assertEquals(
expected = "<span style=\"color: red;\">text</span>",
actual = root.innerHTML
)
isRed = false
waitChanges()
assertEquals(
expected = "<span style=\"color: green;\">text</span>",
actual = root.innerHTML
)
}
@Test
fun conditionalStyleAddedWhenTrue() = runTest {
var isRed by mutableStateOf(false)
composition {
Span(
style = {
if (isRed) {
color("red")
}
}
) {
Text("text")
}
}
assertEquals(
expected = "<span style=\"\">text</span>",
actual = root.innerHTML
)
isRed = true
waitChanges()
assertEquals(
expected = "<span style=\"color: red;\">text</span>",
actual = root.innerHTML
)
}
@Test
fun conditionalStyleGetsRemovedWhenFalse() = runTest {
var isRed by mutableStateOf(true)
composition {
Span(
style = {
if (isRed) {
color("red")
}
}
) {
Text("text")
}
}
assertEquals(
expected = "<span style=\"color: red;\">text</span>",
actual = root.innerHTML
)
isRed = false
waitChanges()
assertEquals(
expected = "<span style=\"\">text</span>",
actual = root.innerHTML
)
}
@Test
fun conditionalStyleUpdatedProperly() = runTest {
var isRed by mutableStateOf(true)
composition {
Span(
style = {
if (isRed) {
color("red")
}
}
) {
Text("text")
}
}
assertEquals(
expected = "<span style=\"color: red;\">text</span>",
actual = root.innerHTML
)
repeat(4) {
isRed = !isRed
waitChanges()
val expected = if (isRed) {
"<span style=\"color: red;\">text</span>"
} else {
"<span style=\"\">text</span>"
}
assertEquals(
expected = expected,
actual = root.innerHTML
)
}
}
}

599
web/core/src/jsTest/kotlin/StaticComposableTests.kt

@ -0,0 +1,599 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.compose.web.css.AlignContent
import androidx.compose.web.css.AlignItems
import androidx.compose.web.css.AlignSelf
import androidx.compose.web.css.Color
import androidx.compose.web.css.DisplayStyle
import androidx.compose.web.css.FlexDirection
import androidx.compose.web.css.FlexWrap
import androidx.compose.web.css.JustifyContent
import androidx.compose.web.css.Position
import androidx.compose.web.css.alignContent
import androidx.compose.web.css.alignItems
import androidx.compose.web.css.alignSelf
import androidx.compose.web.css.border
import androidx.compose.web.css.borderRadius
import androidx.compose.web.css.bottom
import androidx.compose.web.css.color
import androidx.compose.web.css.display
import androidx.compose.web.css.flexDirection
import androidx.compose.web.css.flexFlow
import androidx.compose.web.css.flexGrow
import androidx.compose.web.css.flexShrink
import androidx.compose.web.css.flexWrap
import androidx.compose.web.css.height
import androidx.compose.web.css.justifyContent
import androidx.compose.web.css.left
import androidx.compose.web.css.opacity
import androidx.compose.web.css.order
import androidx.compose.web.css.position
import androidx.compose.web.css.px
import androidx.compose.web.css.right
import androidx.compose.web.css.top
import androidx.compose.web.css.value
import androidx.compose.web.css.width
import androidx.compose.web.elements.Div
import androidx.compose.web.elements.Span
import androidx.compose.web.elements.Text
import androidx.compose.web.renderComposable
import org.w3c.dom.HTMLElement
import org.w3c.dom.get
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class StaticComposableTests {
@Test
fun emptyComposable() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {}
assertEquals("<div></div>", root.outerHTML)
}
@Test
fun textChild() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Text("inner text")
}
assertEquals("<div>inner text</div>", root.outerHTML)
}
@Test
fun attrs() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
attrs = {
classes("some", "simple", "classes")
id("special")
attr("data-val", "some data")
attr("data-val", "some other data")
id("verySpecial")
}
) {}
}
val el = root.firstChild
assertTrue(el is HTMLElement, "element not found")
assertEquals("verySpecial", el.getAttribute("id"))
assertEquals("some simple classes", el.getAttribute("class"))
assertEquals("some other data", el.getAttribute("data-val"))
}
@Test
fun styles() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
opacity(0.3)
color("red")
opacity(0.2)
color("green")
}
) {}
}
assertEquals("opacity: 0.2; color: green;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesBorder() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
property("border", value("1px solid red"))
}
) {}
Div(
style = {
border(3.px, color = Color("green"))
}
) {}
}
assertEquals("border: 1px solid red;", (root.children[0] as HTMLElement).style.cssText)
root.children[1]?.let { el ->
assertEquals(
"green",
el.asDynamic().attributeStyleMap.get("border-color").toString(),
)
assertEquals(
"3px",
el.asDynamic().attributeStyleMap.get("border-width").toString(),
)
}
}
@Test
fun stylesOrder() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
order(-4)
}
) {}
Div(
style = {
order(3)
}
) {}
}
assertEquals("order: -4;", (root.children[0] as HTMLElement).style.cssText)
assertEquals("order: 3;", (root.children[1] as HTMLElement).style.cssText)
}
@Test
fun stylesFlexGrow() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
flexGrow(3)
}
) {}
Div(
style = {
flexGrow(2.5)
}
) {}
Div(
style = {
flexGrow(1e2)
}
) {}
Div(
style = {
flexGrow(.6)
}
) {}
}
assertEquals("flex-grow: 3;", (root.children[0] as HTMLElement).style.cssText)
assertEquals("flex-grow: 2.5;", (root.children[1] as HTMLElement).style.cssText)
assertEquals("flex-grow: 100;", (root.children[2] as HTMLElement).style.cssText)
assertEquals("flex-grow: 0.6;", (root.children[3] as HTMLElement).style.cssText)
}
@Test
fun stylesFlexShrink() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
flexShrink(3)
}
) {}
Div(
style = {
flexShrink(2.5)
}
) {}
Div(
style = {
flexShrink(1e2)
}
) {}
Div(
style = {
flexShrink(.6)
}
) {}
}
assertEquals("flex-shrink: 3;", (root.children[0] as HTMLElement).style.cssText)
assertEquals("flex-shrink: 2.5;", (root.children[1] as HTMLElement).style.cssText)
assertEquals("flex-shrink: 100;", (root.children[2] as HTMLElement).style.cssText)
assertEquals("flex-shrink: 0.6;", (root.children[3] as HTMLElement).style.cssText)
}
@Test
fun stylesWidth() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
width(100.px)
}
) {}
}
assertEquals("width: 100px;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesBorderRadius() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
borderRadius(3.px)
}
) {}
Div(
style = {
borderRadius(3.px, 5.px)
}
) {}
Div(
style = {
borderRadius(3.px, 5.px, 4.px)
}
) {}
Div(
style = {
borderRadius(3.px, 5.px, 4.px, 1.px)
}
) {}
}
assertEquals("border-radius: 3px;", (root.children[0] as HTMLElement).style.cssText)
assertEquals("border-radius: 3px 5px;", (root.children[1] as HTMLElement).style.cssText)
assertEquals("border-radius: 3px 5px 4px;", (root.children[2] as HTMLElement).style.cssText)
assertEquals(
"border-radius: 3px 5px 4px 1px;",
(root.children[3] as HTMLElement).style.cssText
)
}
@Test
fun stylesTop() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
top(100.px)
}
) {}
}
assertEquals("top: 100px;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesBottom() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
bottom(100.px)
}
) {}
}
assertEquals("bottom: 100px;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesLeft() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
left(100.px)
}
) {}
}
assertEquals("left: 100px;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesRight() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
right(100.px)
}
) {}
}
assertEquals("right: 100px;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesHeight() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Div(
style = {
height(100.px)
}
) {}
}
assertEquals("height: 100px;", (root.children[0] as HTMLElement).style.cssText)
}
@Test
fun stylesDisplay() {
val root = "div".asHtmlElement()
val enumValues = enumValues<DisplayStyle>()
renderComposable(
root = root
) {
enumValues.forEach { displayStyle ->
Div(
style = {
display(displayStyle)
}
) { }
}
}
enumValues.forEachIndexed { index, displayStyle ->
assertEquals(
"display: ${displayStyle.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesFlexDirection() {
val root = "div".asHtmlElement()
val enumValues = enumValues<FlexDirection>()
renderComposable(
root = root
) {
enumValues.forEach { flexDirection ->
Span(
style = {
flexDirection(flexDirection)
}
) { }
}
}
enumValues.forEachIndexed { index, displayStyle ->
assertEquals(
"flex-direction: ${displayStyle.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesFlexWrap() {
val root = "div".asHtmlElement()
val enumValues = enumValues<FlexWrap>()
renderComposable(
root = root
) {
enumValues.forEach { flexWrap ->
Span(
style = {
flexWrap(flexWrap)
}
) { }
}
}
enumValues.forEachIndexed { index, displayStyle ->
assertEquals(
"flex-wrap: ${displayStyle.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesFlexFlow() {
val root = "div".asHtmlElement()
val flexWraps = enumValues<FlexWrap>()
val flexDirections = enumValues<FlexDirection>()
renderComposable(
root = root
) {
flexDirections.forEach { flexDirection ->
flexWraps.forEach { flexWrap ->
Span(
style = {
flexFlow(flexDirection, flexWrap)
}
) { }
}
}
}
flexDirections.forEachIndexed { i, flexDirection ->
flexWraps.forEachIndexed { j, flexWrap ->
assertEquals(
"flex-flow: ${flexDirection.value} ${flexWrap.value};",
(root.children[3 * i + j % 3] as HTMLElement).style.cssText
)
}
}
}
@Test
fun stylesJustifyContent() {
val root = "div".asHtmlElement()
val enumValues = enumValues<JustifyContent>()
renderComposable(
root = root
) {
enumValues.forEach { justifyContent ->
Span(
style = {
justifyContent(justifyContent)
}
) { }
}
}
enumValues.forEachIndexed { index, justifyContent ->
assertEquals(
"justify-content: ${justifyContent.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesAlignSelf() {
val root = "div".asHtmlElement()
val enumValues = enumValues<AlignSelf>()
renderComposable(
root = root
) {
enumValues.forEach { alignSelf ->
Span(
style = {
alignSelf(alignSelf)
}
) { }
}
}
enumValues.forEachIndexed { index, alignSelf ->
assertEquals(
"align-self: ${alignSelf.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesAlignItems() {
val root = "div".asHtmlElement()
val enumValues = enumValues<AlignItems>()
renderComposable(
root = root
) {
enumValues.forEach { alignItems ->
Span(
style = {
alignItems(alignItems)
}
) { }
}
}
enumValues.forEachIndexed { index, alignItems ->
assertEquals(
"align-items: ${alignItems.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesAlignContent() {
val root = "div".asHtmlElement()
val enumValues = enumValues<AlignContent>()
renderComposable(
root = root
) {
enumValues.forEach { alignContent ->
Span(
style = {
alignContent(alignContent)
}
) { }
}
}
enumValues.forEachIndexed { index, alignContent ->
assertEquals(
"align-content: ${alignContent.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
@Test
fun stylesPosition() {
val root = "div".asHtmlElement()
val enumValues = enumValues<Position>()
renderComposable(
root = root
) {
enumValues.forEach { position ->
Span(
style = {
position(position)
}
) { }
}
}
enumValues.forEachIndexed { index, position ->
assertEquals(
"position: ${position.value};",
(root.children[index] as HTMLElement).style.cssText
)
}
}
}

94
web/core/src/jsTest/kotlin/TestUtils.kt

@ -0,0 +1,94 @@
import androidx.compose.runtime.Composable
import androidx.compose.web.renderComposable
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.promise
import kotlinx.dom.clear
import org.w3c.dom.HTMLElement
import org.w3c.dom.MutationObserver
import org.w3c.dom.MutationObserverInit
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
private val testScope = MainScope()
class TestScope : CoroutineScope by testScope {
val root = "div".asHtmlElement()
fun composition(content: @Composable () -> Unit) {
root.clear()
renderComposable(root) {
content()
}
}
suspend fun waitChanges() {
waitForChanges(root)
}
}
internal fun runTest(block: suspend TestScope.() -> Unit): dynamic {
val scope = TestScope()
return scope.promise { block(scope) }
}
internal fun runBlockingTest(
block: suspend CoroutineScope.() -> Unit
): dynamic = testScope.promise { this.block() }
internal fun String.asHtmlElement() = document.createElement(this) as HTMLElement
/* Currently, the recompositionRunner relies on AnimationFrame to run the recomposition and
applyChanges. Therefore we can use this method after updating the state and before making
assertions.
If tests get broken, then DefaultMonotonicFrameClock need to be checked if it still
uses window.requestAnimationFrame */
internal suspend fun waitForAnimationFrame() {
suspendCoroutine<Unit> { continuation ->
window.requestAnimationFrame {
continuation.resume(Unit)
}
}
}
private object MutationObserverOptions : MutationObserverInit {
override var childList: Boolean? = true
override var attributes: Boolean? = true
override var characterData: Boolean? = true
override var subtree: Boolean? = true
override var attributeOldValue: Boolean? = true
}
internal suspend fun waitForChanges(elementId: String) {
waitForChanges(document.getElementById(elementId) as HTMLElement)
}
internal suspend fun waitForChanges(element: HTMLElement) {
suspendCoroutine<Unit> { continuation ->
val observer = MutationObserver { mutations, observer ->
continuation.resume(Unit)
observer.disconnect()
}
observer.observe(element, MutationObserverOptions)
}
}

63
web/core/src/jsTest/kotlin/commonApi/ModifierTests.kt

@ -0,0 +1,63 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.compose.web.renderComposable
import org.jetbrains.compose.common.core.graphics.Color
import org.jetbrains.compose.common.foundation.layout.Box
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.ui.background
import org.jetbrains.compose.common.ui.size
import org.jetbrains.compose.common.ui.unit.dp
import org.w3c.dom.HTMLElement
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class ModifierTests {
@Test
fun backgroundModifier() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Box(
Modifier.background(Color(255, 0, 0))
) { }
}
val el = root.firstChild
assertTrue(el is HTMLElement, "element not found")
assertEquals("background-color: rgb(255, 0, 0);", el.style.cssText)
}
@Test
fun size() {
val root = "div".asHtmlElement()
renderComposable(
root = root
) {
Box(
Modifier.size(40.dp)
) { }
}
val el = root.firstChild
assertTrue(el is HTMLElement, "element not found")
assertEquals("width: 40px; height: 40px;", el.style.cssText)
}
}

99
web/core/src/jsTest/kotlin/elements/AttributesTests.kt

@ -0,0 +1,99 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package elements
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.web.attributes.disabled
import androidx.compose.web.attributes.forId
import androidx.compose.web.elements.Button
import androidx.compose.web.elements.Label
import androidx.compose.web.elements.Text
import org.w3c.dom.HTMLButtonElement
import runTest
import kotlin.test.Test
import kotlin.test.assertEquals
class AttributesTests {
@Test
fun labelForIdAttrAppliedProperly() = runTest {
composition {
Label(forId = "l1") { Text("label") }
}
assertEquals(
expected = "<label for=\"l1\" style=\"\">label</label>",
actual = root.innerHTML
)
}
@Test
fun labelForIdIsOptional() = runTest {
composition {
Label { Text("label") }
}
assertEquals(
expected = "<label style=\"\">label</label>",
actual = root.innerHTML
)
}
@Test
fun labelForIdIsAppliedFromAttrs() = runTest {
composition {
Label(
attrs = {
forId("lb1")
}
) {
Text("label")
}
}
assertEquals(
expected = "<label for=\"lb1\" style=\"\">label</label>",
actual = root.innerHTML
)
}
@Test
fun buttonDisabledAttributeAddedOnlyWhenTrue() = runTest {
var disabled by mutableStateOf(false)
composition {
Button(
attrs = {
disabled(disabled)
}
) {}
}
val btn = root.firstChild as HTMLButtonElement
assertEquals(null, btn.getAttribute("disabled"))
disabled = true
waitChanges()
assertEquals("", btn.getAttribute("disabled"))
}
}

115
web/core/src/jsTest/kotlin/elements/EventTests.kt

@ -0,0 +1,115 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package elements
import androidx.compose.web.attributes.InputType
import androidx.compose.web.elements.Button
import androidx.compose.web.elements.Input
import androidx.compose.web.elements.TextArea
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLTextAreaElement
import org.w3c.dom.events.Event
import org.w3c.dom.events.InputEvent
import org.w3c.dom.events.MouseEvent
import runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EventTests {
@Test
fun buttonClickHandled() = runTest {
var handeled = false
composition {
Button(
attrs = {
onClick { handeled = true }
}
) {}
}
assertEquals(1, root.childElementCount)
val btn = root.firstChild as HTMLElement
btn.dispatchEvent(MouseEvent("click"))
assertTrue(handeled)
}
@Test
fun checkboxInputHandled() = runTest {
var handeled = false
composition {
Input(
type = InputType.Checkbox,
attrs = {
onCheckboxInput { handeled = true }
}
)
}
val checkbox = root.firstChild as HTMLInputElement
checkbox.dispatchEvent(Event("input"))
assertTrue(handeled)
}
@Test
fun radioButtonInputHandled() = runTest {
var handeled = false
composition {
Input(
type = InputType.Radio,
attrs = {
onRadioInput { handeled = true }
}
)
}
val radio = root.firstChild as HTMLInputElement
radio.dispatchEvent(Event("input"))
assertEquals(false, radio.checked)
assertTrue(handeled)
}
@Test
fun textAreaInputHandled() = runTest {
var handeled = false
composition {
TextArea(
attrs = {
onTextInput { handeled = true }
},
value = ""
)
}
val radio = root.firstChild as HTMLTextAreaElement
radio.dispatchEvent(InputEvent("input"))
assertEquals("", radio.value)
assertTrue(handeled)
}
}

204
web/core/src/jsTest/kotlin/elements/TableTests.kt

@ -0,0 +1,204 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package elements
import androidx.compose.web.attributes.Scope
import androidx.compose.web.attributes.colspan
import androidx.compose.web.attributes.rowspan
import androidx.compose.web.attributes.scope
import androidx.compose.web.attributes.span
import androidx.compose.web.elements.Caption
import androidx.compose.web.elements.Col
import androidx.compose.web.elements.Colgroup
import androidx.compose.web.elements.Table
import androidx.compose.web.elements.Tbody
import androidx.compose.web.elements.Td
import androidx.compose.web.elements.Text
import androidx.compose.web.elements.Tfoot
import androidx.compose.web.elements.Th
import androidx.compose.web.elements.Thead
import androidx.compose.web.elements.Tr
import org.w3c.dom.HTMLElement
import runTest
import kotlin.test.Test
import kotlin.test.assertEquals
class TableTests {
@Test
fun colAttributes() = runTest {
composition {
Col(
attrs = {
span(2)
}
) { }
}
assertEquals(
expected = "2",
actual = (root.firstChild!! as HTMLElement).getAttribute("span")
)
}
@Test
fun create() = runTest {
composition {
Table {
Caption {
Text("CaptionText")
}
Colgroup {
Col { }
Col { }
Col { }
}
Thead {
Tr {
Th { }
Th { }
Th(
attrs = {
colspan(2)
}
) {
Text("First")
}
}
Tr {
Th { }
Th { }
Th(
attrs = {
scope(Scope.Col)
}
) { Text("A") }
Th(
attrs = {
scope(Scope.Col)
}
) { Text("B") }
}
}
Tbody {
Tr {
Th(
attrs = {
scope(Scope.Row)
rowspan(2)
}
) {
Text("Rows")
}
Th(
attrs = {
scope(Scope.Row)
}
) {
Text("1")
}
Td {
Text("30")
}
Td {
Text("40")
}
}
Tr {
Th(
attrs = {
scope(Scope.Row)
}
) {
Text("2")
}
Td {
Text("10")
}
Td {
Text("20")
}
}
}
Tfoot {
Tr {
Th(
attrs = {
scope(Scope.Row)
}
) {
Text("Totals")
}
Th { }
Td { Text("40") }
Td { Text("60") }
}
}
}
}
assertEquals(
expected = """
<table style="">
<caption style="">CaptionText</caption>
<colgroup style="">
<col style="">
<col style="">
<col style="">
</colgroup>
<thead style="">
<tr style="">
<th style=""></th>
<th style=""></th>
<th colspan="2" style="">First</th>
</tr>
<tr style="">
<th style=""></th>
<th style=""></th>
<th scope="col" style="">A</th>
<th scope="col" style="">B</th>
</tr>
</thead>
<tbody style="">
<tr style="">
<th scope="row" rowspan="2" style="">Rows</th>
<th scope="row" style="">1</th>
<td style="">30</td>
<td style="">40</td>
</tr>
<tr style="">
<th scope="row" style="">2</th>
<td style="">10</td>
<td style="">20</td>
</tr>
</tbody>
<tfoot style="">
<tr style="">
<th scope="row" style="">Totals</th>
<th style=""></th>
<td style="">40</td>
<td style="">60</td>
</tr>
</tfoot>
</table>
""".trimIndent()
.replace("\n", "")
.replace("\\s{2,}".toRegex(), ""),
actual = root.innerHTML
)
}
}

25
web/core/src/jvmMain/kotlin/withWeb/Alignment.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.ui
import androidx.compose.ui.Alignment as JAlignment
val Alignment.Vertical.implementation: JAlignment.Vertical
get() = when (this) {
Alignment.Top -> JAlignment.Top
Alignment.CenterVertically -> JAlignment.CenterVertically
else -> JAlignment.Bottom
}

24
web/core/src/jvmMain/kotlin/withWeb/Arrangement.kt

@ -0,0 +1,24 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import androidx.compose.foundation.layout.Arrangement as JArrangement
val Arrangement.Horizontal.implementation: JArrangement.Horizontal
get() = when (this) {
Arrangement.End -> JArrangement.End
else -> JArrangement.Start
}

21
web/core/src/jvmMain/kotlin/withWeb/Color.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.core.graphics
import androidx.compose.ui.graphics.Color as JColor
val Color.implementation
get() = JColor(red, green, blue)

35
web/core/src/jvmMain/kotlin/withWeb/Modifier.kt

@ -0,0 +1,35 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.ui
import org.jetbrains.compose.common.ui.unit.Dp
import org.jetbrains.compose.common.ui.unit.implementation
import androidx.compose.foundation.background
import org.jetbrains.compose.common.core.graphics.Color
import org.jetbrains.compose.common.core.graphics.implementation
import org.jetbrains.compose.common.internal.castOrCreate
import androidx.compose.foundation.layout.padding
actual fun Modifier.background(color: Color): Modifier = castOrCreate().apply {
modifier = modifier.background(color.implementation)
}
actual fun Modifier.padding(all: Dp): Modifier = castOrCreate().apply {
modifier = modifier.padding(all.implementation)
}
val Modifier.implementation
get() = castOrCreate().modifier

27
web/core/src/jvmMain/kotlin/withWeb/internal/ActualModifier.kt

@ -0,0 +1,27 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.internal
import androidx.compose.ui.Modifier as JModifier
import org.jetbrains.compose.common.ui.Modifier
private class ModifierElement : JModifier.Element
class ActualModifier : Modifier {
var modifier: JModifier = ModifierElement()
}
fun Modifier.castOrCreate(): ActualModifier = (this as? ActualModifier) ?: ActualModifier()

29
web/core/src/jvmMain/kotlin/withWeb/layouts/box.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.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.ui.implementation
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.Box as JBox
@Composable
internal actual fun BoxActual(modifier: Modifier, content: @Composable () -> Unit) {
JBox(modifier.implementation) {
content.invoke()
}
}

32
web/core/src/jvmMain/kotlin/withWeb/layouts/button.kt

@ -0,0 +1,32 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.material
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.runtime.Composable
import org.jetbrains.compose.common.ui.implementation
import androidx.compose.material.Button as JButton
@Composable
actual fun ButtonActual(
modifier: Modifier,
onClick: () -> Unit,
content: @Composable () -> Unit
) {
JButton(onClick, modifier.implementation) {
content()
}
}

29
web/core/src/jvmMain/kotlin/withWeb/layouts/column.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.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.Column as JColumn
import org.jetbrains.compose.common.ui.implementation
@Composable
internal actual fun ColumnActual(modifier: Modifier, content: @Composable () -> Unit) {
JColumn(modifier = modifier.implementation) {
content.invoke()
}
}

40
web/core/src/jvmMain/kotlin/withWeb/layouts/row.kt

@ -0,0 +1,40 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.foundation.layout
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.Row as JRow
import org.jetbrains.compose.common.ui.implementation
import org.jetbrains.compose.common.ui.implementation
import org.jetbrains.compose.common.ui.Alignment
@Composable
internal actual fun RowActual(
modifier: Modifier,
horizontalArrangement: Arrangement.Horizontal,
verticalAlignment: Alignment.Vertical,
content: @Composable () -> Unit
) {
JRow(
modifier.implementation,
horizontalArrangement.implementation,
verticalAlignment.implementation
) {
content.invoke()
}
}

25
web/core/src/jvmMain/kotlin/withWeb/layouts/shapes/shapes.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 jetbrains.compose.common.shapes
import androidx.compose.ui.graphics.Shape as JShape
import androidx.compose.foundation.shape.CircleShape as JCircleShape
val Shape.implementation: JShape
get() = when (this) {
CircleShape -> JCircleShape
else -> throw ClassCastException("Currently supporting only circle shape")
}

38
web/core/src/jvmMain/kotlin/withWeb/layouts/slider.kt

@ -0,0 +1,38 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.material
import androidx.compose.runtime.Composable
import org.jetbrains.compose.common.ui.Modifier
import androidx.compose.material.Slider as JSlider
import org.jetbrains.compose.common.ui.implementation
@Composable
actual fun SliderActual(
value: Float,
onValueChange: (Float) -> Unit,
valueRange: ClosedFloatingPointRange<Float>,
steps: Int,
modifier: Modifier
) {
JSlider(
value,
onValueChange = onValueChange,
modifier = modifier.implementation,
valueRange = valueRange,
steps = steps
)
}

40
web/core/src/jvmMain/kotlin/withWeb/layouts/text.kt

@ -0,0 +1,40 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.compose.common.material
import androidx.compose.runtime.Composable
import androidx.compose.material.Text as JText
import org.jetbrains.compose.common.ui.Modifier
import org.jetbrains.compose.common.ui.implementation
import org.jetbrains.compose.common.core.graphics.Color
import org.jetbrains.compose.common.core.graphics.implementation
import org.jetbrains.compose.common.ui.unit.TextUnit
import org.jetbrains.compose.common.ui.unit.implementation
@Composable
actual fun TextActual(
text: String,
modifier: Modifier,
color: Color,
size: TextUnit
) {
JText(
text,
modifier = modifier.implementation,
color = color.implementation,
fontSize = size.implementation
)
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save