diff --git a/README.md b/README.md
index de5dcb0..1350dc3 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,93 @@
-# Compose for Web (now Compose HTML) Material
+# Compose HTML Material
-[![Maven Central](https://img.shields.io/maven-central/v/com.huanshankeji/compose-web-material)](https://search.maven.org/artifact/com.huanshankeji/compose-web-material)
-[![Gradle Plugin Portal](https://img.shields.io/gradle-plugin-portal/v/com.huanshankeji.compose-web-material-conventions)](https://plugins.gradle.org/plugin/com.huanshankeji.compose-web-material-conventions)
+[![Maven Central](https://img.shields.io/maven-central/v/com.huanshankeji/compose-html-material3)](https://search.maven.org/artifact/com.huanshankeji/compose-html-material3)
-Some Material components for Compose for Web, based on [Material Web (with Web Components) (or material-web, or `mwc`)](https://github.com/material-components/material-web) (preferred) and [Material Components for the web (or material-components-web, or `mdc`)](https://github.com/material-components/material-components-web) (fallback)
+Material 3 wrapper components for Compose HTML based on [Material Web](https://github.com/material-components/material-web)
-~~This project is in prototype and the components are not complete. More components will be added. It will probably go through huge refactors and API changes, too.~~
+For unified multiplatform APIs which are more akin to those in `androidx.compose`, check out [Compose Multiplatform Material](https://github.com/huanshankeji/compose-multiplatform-material).
-**This project is not currently under active development. Here is a list of reasons and alternatives:**
+For Material 2 support, you are recommended to check out [KMDC](https://github.com/mpetuska/kmdc) instead. For information on our obsolete work on legacy Material 2 components, check out [the legacy README](/legacy/README.md).
-1. The [material-web](https://github.com/material-components/material-web) team is working on Material You (Material Design 3) support and [the Material 2 branch (`mwc`)](https://github.com/material-components/material-web/tree/mwc) is no longer under active development. Existing Compose wrappers of their Material 2 components are still kept in [the `:compose-web-material` subproject](compose-web-material) but not updated.
-1. [KMDC](https://github.com/mpetuska/kmdc) wrapping around [material-components-web (`mdc`)](https://github.com/material-components/material-components-web) provides a much more complete set of Material Design components for Compose for Web.
-1. We are currently focusing more on [compose-multiplatform-material](https://github.com/huanshankeji/compose-multiplatform-material) to provide multiplatform Compose Material wrappers, whose web portion depends on KMDC and [the `:compose-web-common` subproject](compose-web-common), which may be occasionally updated for the dependent project.
+## Supported components
-## Instructions on how to use
+Not all components of Material Web are supported yet. Also, not all Material Design components are supported by Material Web yet (see [their roadmap](https://github.com/material-components/material-web/blob/main/docs/roadmap.md)).
-Some configurations are needed to use this library due to the immaturities of this project and Kotlin/JS.
+Here is a list of supported compoent APIs:
-### Add the dependency
+- `MdElevatedButton`, `MdFilledButton`, `MdFilledTonalButton`, `MdOutlinedButton`, `MdTextButton`
+- `MdCheckbox`
+- `MdFab`, `MdBrandedFab`
+- `MdIcon`
+- `MdIconButton`, `MdFilledIconButton`, `MdFilledTonalIconButton`, `MdOutlinedIconButton`
+- `MdList`, `MdListItem`
+- `MdSwitch`, `LabelWithMdSwitch`
+- `MdFilledTextField`, `MdOutlinedTextField`
-```kotlin
-implementation("com.huanshankeji:compose-web-material:$version")
-```
+### "labs" components
-### In code
+Here is a list of supported component APIs in the [Material Web "labs" directory](https://github.com/material-components/material-web/tree/main/labs), which "contains experimental features that are not recommended for production" as they state:
-Call `mwcRequires()` in your `main` function before calling any component Composable functions.
+- `MdElevatedCard`, `MdOutlinedCard`
+- `MdNavigationBar`
+- `MdNavigationTab`
-### Kotlin/JS Webpack configuration
+You should opt-in to `@MaterialWebLabsApi` to use them.
-If you use this library in an app project with Webpack [which Kotlin/JS currently uses](https://kotlinlang.org/docs/js-project-setup.html), you might want to configure it as recommended by Material Web and Material Components for the web. Some instructions on how to do this simply are as below.
+## Brief Instructions
+
+### Add the dependency
-This plugin helps add the dependency to this project (if you do this you can skip the "Add the dependency" step above) and the `devNpm` dependencies:
+With Gradle:
```kotlin
-plugins {
- id("com.huanshankeji.compose-web-material-conventions") version someVersion
+kotlin {
+ sourceSets {
+ jsMain {
+ dependencies {
+ // ...
+ implementation("com.huanshankeji:compose-html-material3:$version")
+ }
+ }
+ }
}
```
-However, the plugin doesn't [make further adjustments to the webpack configuration](https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file), so you also need to refer to [the demo further adjustments](demo/webpack.config.d/further_adjustments.js) and [the demo HTML page](demo/html/demo.html) to add your own. Just copy and possibly adapt them as you like.
+### Material Symbols & Icons
+
+The Material 3 module uses [Material Symbols & Icons](https://fonts.google.com/icons), but doesn't depend on the stylesheet directly. For Material Icons to work properly, you may need to configure your project following the quick instructions below or [the developer guide](https://developers.google.com/fonts/docs/material_symbols).
+
+#### Quick instructions
+
+In short, there are 3 ways to add the Material Symbols & Icons dependency:
+
+1. Add the stylesheet hosted by Google directly in your HTML file `head`:
+
+ ```html
+
+ ```
+
+1. Use [Marella's self-hosted Material Symbols](https://www.npmjs.com/package/material-symbols).
+
+ First add the dependency in your build script:
+
+ ```kotlin
+ implementation(npm("material-symbols", "0.17.4"))
+ ```
+
+ And then import the icons in your program. For example you can use CommonJS `require`:
+
+ ```kotlin
+ external fun require(module: String): dynamic
+ fun main() {
+ require("material-symbols/outlined.css")
+ renderComposableInBody { App() }
+ }
+ ```
+
+ If you are familiar with web development and Kotlin/JS, you can depend on the stylesheet in any way that works and you prefer. For example, you can use `@JsModule` corresponding to the UMD import, or configure it as a Webpack entry point. See the following docs fore more details:
+ 1. [JavaScript modules | Kotlin Documentation](https://kotlinlang.org/docs/js-modules.html)
+ 1. [the "webpack configuration file" section in Set up a Kotlin/JS project | Kotlin Documentation](https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file)
+ 1. [Code Splitting | webpack](https://webpack.js.org/guides/code-splitting/)
+ 1. [Advanced entry | webpack](https://webpack.js.org/guides/entry-advanced/)
+
+1. [Download and self-host the latest font](https://developers.google.com/fonts/docs/material_symbols#self-hosting_the_font).
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 7b5a11e..8740221 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -11,7 +11,7 @@ val huanshankejiGradlePluginsVersion = "0.5.1"
dependencies {
implementation(kotlin("gradle-plugin", "1.9.23"))
- implementation("org.jetbrains.compose:compose-gradle-plugin:1.6.1")
+ implementation("org.jetbrains.compose:compose-gradle-plugin:1.6.2")
implementation("com.huanshankeji:kotlin-common-gradle-plugins:$huanshankejiGradlePluginsVersion")
implementation("com.huanshankeji.team:gradle-plugins:$huanshankejiGradlePluginsVersion")
implementation("com.huanshankeji:common-gradle-dependencies:0.7.1-20240314")
diff --git a/buildSrc/src/main/kotlin/VersionsAndDependencies.kt b/buildSrc/src/main/kotlin/VersionsAndDependencies.kt
index 3ad724c..76ab979 100644
--- a/buildSrc/src/main/kotlin/VersionsAndDependencies.kt
+++ b/buildSrc/src/main/kotlin/VersionsAndDependencies.kt
@@ -1,8 +1,14 @@
import com.huanshankeji.CommonDependencies
-const val projectVersion = "0.2.2"
+const val projectVersion = "0.3.0"
object DependencyVersions {
+ val kobweb = "0.17.3"
+ val materialWeb = "1.4.1"
+
+
+ // legacy versions that don't need to be updated
+
val webcomponents = "2.6.0"
val mwc = "0.25.3"
diff --git a/buildSrc/src/main/kotlin/lib-conventions.gradle.kts b/buildSrc/src/main/kotlin/lib-conventions.gradle.kts
index d1e548e..66b361d 100644
--- a/buildSrc/src/main/kotlin/lib-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/lib-conventions.gradle.kts
@@ -1,9 +1,3 @@
-import com.huanshankeji.team.`Shreck Ye`
-import com.huanshankeji.team.pomForTeamDefaultOpenSource
-import org.gradle.api.publish.maven.MavenPublication
-import org.gradle.api.tasks.bundling.Jar
-import org.gradle.kotlin.dsl.*
-
plugins {
id("com.huanshankeji.kotlin-multiplatform-js-browser-conventions")
id("org.jetbrains.compose")
@@ -12,6 +6,7 @@ plugins {
repositories {
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ maven("https://us-central1-maven.pkg.dev/varabyte-repos/public") // for Kobweb
}
group = "com.huanshankeji"
diff --git a/compose-web-common/build.gradle.kts b/compose-html-common/build.gradle.kts
similarity index 64%
rename from compose-web-common/build.gradle.kts
rename to compose-html-common/build.gradle.kts
index f570584..0b34ba0 100644
--- a/compose-web-common/build.gradle.kts
+++ b/compose-html-common/build.gradle.kts
@@ -11,7 +11,8 @@ kotlin {
dependencies {
implementation(compose.html.core)
implementation(compose.runtime)
- implementation(commonDependencies.kotlinx.coroutines.core())
+ api("com.varabyte.kobweb:compose-html-ext:${DependencyVersions.kobweb}")
+ //implementation(commonDependencies.kotlinx.coroutines.core())
}
}
}
@@ -20,8 +21,8 @@ kotlin {
publishing.publications.withType {
pomForTeamDefaultOpenSource(
project,
- "Huanshankeji Compose for Web common",
- "Huanshankeji's common code for Compose for Web"
+ "Huanshankeji Compose HTML common",
+ "Huanshankeji's common code for Compose HTML"
) {
`Shreck Ye`()
}
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Layouts.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Layouts.kt
similarity index 91%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Layouts.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Layouts.kt
index 4041035..f87d670 100644
--- a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Layouts.kt
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Layouts.kt
@@ -30,11 +30,13 @@ fun Flexbox(
}
} + attrs, content)
-@Composable
+@PreferringKobwebComposeLayoutApi
@Deprecated(WITH_STYLES_DEPRECATED_MESSAGE)
+@Composable
fun FlexboxS(styles: Styles? = null, content: ContentBuilder) =
Flexbox(styles.wrapInAttrs(), content)
+@PreferringKobwebComposeLayoutApi
@Composable
fun Column(
attrs: AttrBuilderContext? = null,
@@ -48,11 +50,13 @@ fun Column(
}
} + attrs, content)
-@Composable
+@PreferringKobwebComposeLayoutApi
@Deprecated(WITH_STYLES_DEPRECATED_MESSAGE)
+@Composable
fun ColumnS(styles: Styles? = null, fitContent: Boolean = true, content: ContentBuilder) =
Column(styles.wrapInAttrs(), fitContent, content)
+@PreferringKobwebComposeLayoutApi
@Composable
fun ColumnWithSpaceBetween(
attrs: AttrBuilderContext? = null,
@@ -66,6 +70,7 @@ fun ColumnWithSpaceBetween(
} + attrs, fitContent, content)
+@PreferringKobwebComposeLayoutApi
@Composable
fun Row(
attrs: AttrBuilderContext? = null,
@@ -77,14 +82,16 @@ fun Row(
}
} + attrs, content)
-@Composable
+@PreferringKobwebComposeLayoutApi
@Deprecated(WITH_STYLES_DEPRECATED_MESSAGE)
+@Composable
fun RowS(
styles: Styles? = null,
content: ContentBuilder
) =
Row(styles.wrapInAttrs(), content)
+@PreferringKobwebComposeLayoutApi
@Composable
fun RowWithSpaceBetween(
attrs: AttrBuilderContext? = null,
@@ -96,6 +103,7 @@ fun RowWithSpaceBetween(
}
} + attrs, content)
+@PreferringKobwebComposeLayoutApi
@Composable
fun ColumnWithGaps(
attrs: AttrBuilderContext? = null,
@@ -109,6 +117,7 @@ fun ColumnWithGaps(
}
} + attrs, fitContent, content)
+@PreferringKobwebComposeLayoutApi
@Composable
fun RowWithGaps(
attrs: AttrBuilderContext? = null,
@@ -158,6 +167,8 @@ fun FrGrid(
}
}, content)
+@PreferringKobwebComposeLayoutApi
+@Deprecated("This API is not implemented yet.")
@Composable
fun Spacer(numPxs: Int) =
TODO() as Unit
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/LoadingState.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/LoadingState.kt
similarity index 100%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/LoadingState.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/LoadingState.kt
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/PreferringKobwebComposeLayoutApi.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/PreferringKobwebComposeLayoutApi.kt
new file mode 100644
index 0000000..3407e42
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/PreferringKobwebComposeLayoutApi.kt
@@ -0,0 +1,9 @@
+package com.huanshankeji.compose.web
+
+@RequiresOptIn(
+ "You are recommend to use the similar layout APIs in Kobweb Compose. See \"https://github.com/varabyte/kobweb/tree/main/frontend/kobweb-compose/src/jsMain/kotlin/com/varabyte/kobweb/compose/foundation/layout\".",
+ RequiresOptIn.Level.WARNING
+)
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.FUNCTION)
+annotation class PreferringKobwebComposeLayoutApi
\ No newline at end of file
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Types.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Types.kt
similarity index 100%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Types.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/Types.kt
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrBuilderContext.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrBuilderContext.kt
similarity index 100%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrBuilderContext.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrBuilderContext.kt
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrConversion.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrConversion.kt
new file mode 100644
index 0000000..37202a1
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrConversion.kt
@@ -0,0 +1,12 @@
+package com.huanshankeji.compose.web.attributes
+
+// consider moving to a "web-common" or "html-common" module
+
+fun Boolean.isTrueOrNull(): Boolean? =
+ if (this) true else null
+
+fun Boolean.isFalseOrNull(): Boolean? =
+ if (this) null else false
+
+fun Boolean.toOnOrOff(): String =
+ if (this) "on" else "off"
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/Attrs.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/Attrs.kt
new file mode 100644
index 0000000..25781c9
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/Attrs.kt
@@ -0,0 +1,82 @@
+package com.huanshankeji.compose.web.attributes
+
+import org.jetbrains.compose.web.attributes.AttrsScope
+
+/**
+ * Adds an attribute that is made present by its key aka [attr].
+ * @see AttrsScope.attr
+ */
+fun AttrsScope<*>.attr(attr: String, value: Boolean = true) =
+ attr(attr, value.toString())
+
+/**
+ * [Int] attributes are used in Compose HTML. See [org.jetbrains.compose.web.attributes.maxLength] for example.
+ */
+fun AttrsScope<*>.attr(attr: String, value: Int) =
+ attr(attr, value.toString())
+
+/**
+ * Adds an attribute that has an explicit [Boolean] value unlike [attr].
+ */
+fun AttrsScope<*>.booleanAttr(attr: String, value: Boolean) =
+ attr(attr, value.toString())
+
+
+fun AttrsScope<*>.attrIfNotNull(attr: String, value: String?) {
+ value?.let { attr(attr, it) }
+}
+
+fun AttrsScope<*>.attrIfNotNull(attr: String, value: Boolean?) {
+ value?.let { attr(attr, it) }
+}
+
+fun AttrsScope<*>.attrIfNotNull(attr: String, value: Int?) {
+ value?.let { attr(attr, it) }
+}
+
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/slot
+fun AttrsScope<*>.slot(value: String) =
+ attr("slot", value)
+
+
+enum class AutoCapitalize(val strValue: String, val alternativeStrValue: String? = null) {
+ None("none", "off"), Sentences("sentences", "on"), Words("words"), Characters("characters");
+
+ companion object {
+ val valueSet = entries.asSequence().map { it.strValue }.toSet()
+ }
+}
+
+// This is actually a global attribute
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize
+fun AttrsScope<*>.autoCapitalize(value: String) =
+ attr("autocapitalize", value)
+
+fun AttrsScope<*>.autoCapitalize(value: AutoCapitalize) =
+ autoCapitalize(value.strValue)
+
+
+// Safari only
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#attr-autocorrect
+fun AttrsScope<*>.autocorrect(value: String) =
+ attr("autocorrect", value)
+
+fun AttrsScope<*>.autocorrect(onOrOff: Boolean) =
+ attr("autocorrect", onOrOff.toOnOrOff())
+
+
+enum class EnterKeyHint(val strValue: String) {
+ Enter("enter"), Done("done"), Go("go"), Next("next"), Previous("previous"), Search("search"), Send("send");
+
+ companion object {
+ val valueSet = entries.map { it.strValue }.toSet()
+ }
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint
+fun AttrsScope<*>.enterKeyHint(value: String) =
+ attr("enterkeyhint", value)
+
+fun AttrsScope<*>.enterKeyHint(value: EnterKeyHint) =
+ enterKeyHint(value.strValue)
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/Types.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/Types.kt
new file mode 100644
index 0000000..e287f21
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/Types.kt
@@ -0,0 +1,5 @@
+package com.huanshankeji.compose.web.attributes
+
+import org.jetbrains.compose.web.attributes.AttrsScope
+
+typealias Attrs = AttrsScope.() -> Unit
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/ext/Attrs.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/ext/Attrs.kt
new file mode 100644
index 0000000..2f64bf8
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/ext/Attrs.kt
@@ -0,0 +1,144 @@
+package com.huanshankeji.compose.web.attributes.ext
+
+import com.huanshankeji.compose.web.attributes.AutoCapitalize
+import com.huanshankeji.compose.web.attributes.EnterKeyHint
+import com.huanshankeji.compose.web.attributes.attrIfNotNull
+import com.huanshankeji.compose.web.attributes.autoCapitalize
+import com.huanshankeji.compose.web.attributes.enterKeyHint
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.attributes.AutoComplete
+import org.jetbrains.compose.web.attributes.InputType
+
+/*
+Attributes in the `ext` package are one of 2 kinds:
+1. Those for `HTMLElement`s which don't implement the interfaces (such as `HTMLInputElement`) of the elements they behave like,
+such as the `HTMLElement`s of those components in Material Web.
+These attributes should not be universally available on most elements.
+1. Those taking nullable values.
+
+Also consider moving to a `compose-html-material-common` module and depend on them with `implementation`.
+Consider reordering them.
+ */
+
+
+// https://www.w3schools.com/accessibility/accessibility_labels.php
+// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label
+fun AttrsScope<*>.ariaLabel(value: String) =
+ attr("aria-label", value)
+
+
+// https://www.w3schools.com/tags/att_disabled.asp
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled
+fun AttrsScope<*>.disabled(disabled: Boolean?) {
+ disabled?.let { disabled(it) }
+}
+
+// https://www.w3schools.com/tags/att_href.asp
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href
+fun AttrsScope<*>.href(value: String?) =
+ attrIfNotNull("href", value)
+
+// https://www.w3schools.com/tags/att_target.asp
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target
+fun AttrsScope<*>.target(value: String?) =
+ attrIfNotNull("target", value)
+
+
+// https://www.w3schools.com/tags/att_input_type.asp
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#type
+fun AttrsScope<*>.type(value: String?) =
+ attrIfNotNull("type", value)
+
+// https://www.w3schools.com/tags/att_value.asp
+fun AttrsScope<*>.value(value: String?) =
+ attrIfNotNull("value", value)
+
+
+// https://www.w3schools.com/tags/att_name.asp
+fun AttrsScope<*>.name(value: String?) =
+ attrIfNotNull("name", value)
+
+
+// https://www.w3schools.com/tags/att_form.asp
+fun AttrsScope<*>.form(value: String?) =
+ attrIfNotNull("form", value)
+
+// https://www.w3schools.com/tags/att_required.asp
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required
+fun AttrsScope<*>.required(value: Boolean?) =
+ attrIfNotNull("required", value)
+
+// https://www.geeksforgeeks.org/html-label-attribute/
+// https://www.w3schools.com/tags/att_label.asp
+fun AttrsScope<*>.label(value: String?) =
+ attrIfNotNull("label", value)
+
+// copied and adapted from `Attrs.kt` in `org.jetbrains.compose.web.attributes`
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#max
+fun AttrsScope<*>.max(value: String?) =
+ attrIfNotNull("max", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#maxlength
+fun AttrsScope<*>.maxLength(value: Int?) =
+ attrIfNotNull("maxlength", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#min
+fun AttrsScope<*>.min(value: String?) =
+ attrIfNotNull("min", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#minlength
+fun AttrsScope<*>.minLength(value: Int?) =
+ attrIfNotNull("minlength", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#pattern
+fun AttrsScope<*>.pattern(value: String?) =
+ attrIfNotNull("pattern", value)
+
+// https://www.w3schools.com/tags/att_input_placeholder.asp
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/placeholder
+fun AttrsScope<*>.placeholder(value: String?) =
+ attrIfNotNull("placeholder", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#readonly
+fun AttrsScope<*>.readOnly(value: Boolean?) =
+ attrIfNotNull("readonly", value)
+
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#multiple
+fun AttrsScope<*>.multiple(value: Boolean?) =
+ attrIfNotNull("multiple", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step
+fun AttrsScope<*>.step(value: String?) =
+ attrIfNotNull("step", value)
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types
+fun AttrsScope<*>.type(value: InputType<*>?) {
+ value?.let { attr("type", it.typeStr) }
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
+fun AttrsScope<*>.autoComplete(value: AutoComplete?) {
+ value?.let { attr("autocomplete", it.unsafeCast()) }
+}
+
+
+// This is actually a global attribute
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize
+fun AttrsScope<*>.autoCapitalize(value: String?) =
+ value?.let { this@autoCapitalize.autoCapitalize(value) }
+
+fun AttrsScope<*>.autoCapitalizeRequiringValid(value: String) {
+ require(value in AutoCapitalize.valueSet)
+ autoCapitalize(value)
+}
+
+
+fun AttrsScope<*>.enterKeyHintIfValid(value: String) {
+ if (value in EnterKeyHint.valueSet) enterKeyHint(value)
+}
+
+
+fun AttrsScope<*>.selected(value: Boolean?) =
+ attrIfNotNull("selected", value)
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/ext/EventAttrs.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/ext/EventAttrs.kt
new file mode 100644
index 0000000..c620012
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/ext/EventAttrs.kt
@@ -0,0 +1,15 @@
+package com.huanshankeji.compose.web.attributes.ext
+
+import androidx.compose.web.events.SyntheticEvent
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.attributes.EventsListenerScope
+import org.w3c.dom.HTMLElement
+
+/**
+ * @see com.huanshankeji.compose.web.dom.ext.value
+ */
+//fun EventsListenerScope.onInput(
+fun AttrsScope.onInput(
+ listener: (SyntheticEvent) -> Unit
+) =
+ addEventListener(EventsListenerScope.INPUT, listener)
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/CSS.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/CSS.kt
similarity index 82%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/CSS.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/CSS.kt
index e92972d..bde0bd4 100644
--- a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/CSS.kt
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/CSS.kt
@@ -2,10 +2,10 @@
package com.huanshankeji.compose.web.css
-import org.jetbrains.compose.web.css.StyleBuilder
import org.jetbrains.compose.web.css.StylePropertyEnum
+import org.jetbrains.compose.web.css.StyleScope
-fun StyleBuilder.visibility(visibility: Visibility) =
+fun StyleScope.visibility(visibility: Visibility) =
property("visibility", visibility)
interface Visibility : StylePropertyEnum {
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/StyleScope.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/StyleScope.kt
similarity index 100%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/StyleScope.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/StyleScope.kt
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/Styles.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/Styles.kt
similarity index 100%
rename from compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/Styles.kt
rename to compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/css/Styles.kt
diff --git a/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/dom/ext/dom.kt b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/dom/ext/dom.kt
new file mode 100644
index 0000000..b1a0b08
--- /dev/null
+++ b/compose-html-common/src/jsMain/kotlin/com/huanshankeji/compose/web/dom/ext/dom.kt
@@ -0,0 +1,12 @@
+package com.huanshankeji.compose.web.dom.ext
+
+import org.w3c.dom.HTMLElement
+
+/**
+ * @see com.huanshankeji.compose.web.attributes.ext.onInput
+ */
+var HTMLElement.value: String
+ get() = asDynamic().value as String
+ set(value) {
+ asDynamic().value = value
+ }
diff --git a/compose-web-material/build.gradle.kts b/compose-html-material-legacy/build.gradle.kts
similarity index 87%
rename from compose-web-material/build.gradle.kts
rename to compose-html-material-legacy/build.gradle.kts
index d7f2fcc..4735f5e 100644
--- a/compose-web-material/build.gradle.kts
+++ b/compose-html-material-legacy/build.gradle.kts
@@ -7,11 +7,11 @@ plugins {
kotlin {
sourceSets {
- val jsMain by getting {
+ jsMain {
dependencies {
implementation(compose.html.core)
implementation(compose.runtime)
- implementation(project(":compose-web-common"))
+ implementation(project(":compose-html-common"))
implementation(npm("@webcomponents/webcomponentsjs", DependencyVersions.webcomponents))
@@ -43,8 +43,8 @@ kotlin {
publishing.publications.withType {
pomForTeamDefaultOpenSource(
project,
- "Compose for Web Material",
- "Some Material components for Compose for Web"
+ "Compose HTML Material 2",
+ "Legacy Material 2 components for Compose HTML"
) {
`Shreck Ye`()
}
diff --git a/compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Components.kt b/compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Components.kt
similarity index 100%
rename from compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Components.kt
rename to compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Components.kt
diff --git a/compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Layouts.kt b/compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Layouts.kt
similarity index 100%
rename from compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Layouts.kt
rename to compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Layouts.kt
diff --git a/compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/MwcRequires.kt b/compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/MwcRequires.kt
similarity index 100%
rename from compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/MwcRequires.kt
rename to compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/MwcRequires.kt
diff --git a/compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Styles.kt b/compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Styles.kt
similarity index 100%
rename from compose-web-material/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Styles.kt
rename to compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/compose/web/material/Styles.kt
diff --git a/compose-web-material/src/jsMain/kotlin/com/huanshankeji/material/Colors.kt b/compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/material/Colors.kt
similarity index 100%
rename from compose-web-material/src/jsMain/kotlin/com/huanshankeji/material/Colors.kt
rename to compose-html-material-legacy/src/jsMain/kotlin/com/huanshankeji/material/Colors.kt
diff --git a/compose-html-material3/build.gradle.kts b/compose-html-material3/build.gradle.kts
new file mode 100644
index 0000000..afff985
--- /dev/null
+++ b/compose-html-material3/build.gradle.kts
@@ -0,0 +1,30 @@
+import com.huanshankeji.team.`Shreck Ye`
+import com.huanshankeji.team.pomForTeamDefaultOpenSource
+
+plugins {
+ `lib-conventions`
+}
+
+kotlin {
+ sourceSets {
+ jsMain {
+ dependencies {
+ implementation(compose.html.core)
+ implementation(compose.runtime)
+ implementation(project(":compose-html-common"))
+
+ implementation(npm("@material/web", DependencyVersions.materialWeb))
+ }
+ }
+ }
+}
+
+publishing.publications.withType {
+ pomForTeamDefaultOpenSource(
+ project,
+ "Compose HTML Material 3",
+ "Material 3 components for Compose HTML"
+ ) {
+ `Shreck Ye`()
+ }
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MaterialWebLabsApi.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MaterialWebLabsApi.kt
new file mode 100644
index 0000000..8090682
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MaterialWebLabsApi.kt
@@ -0,0 +1,9 @@
+package com.huanshankeji.compose.html.material3
+
+@RequiresOptIn(
+ "This API depends on components in the Material Web \"labs\" directory, which contains experimental features that are not recommended for production. " +
+ "See https://github.com/material-components/material-web/tree/main/labs for more details.",
+ RequiresOptIn.Level.WARNING
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class MaterialWebLabsApi
\ No newline at end of file
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdButton.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdButton.kt
new file mode 100644
index 0000000..52dde19
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdButton.kt
@@ -0,0 +1,207 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.ext.*
+import com.huanshankeji.compose.web.attributes.slot
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/button.md
+https://material-web.dev/components/button/
+https://material-web.dev/components/button/stories/
+*/
+
+/*
+// TODO not working
+@JsModule("@material/web/button/elevated-button.js")
+external class MdElevatedButton /* : Element */ // TODO
+
+private var toImport = true
+*/
+
+private fun commonButtonAttrs(
+ disabled: Boolean?,
+ href: String?,
+ target: String?,
+ trailingIcon: Boolean?,
+ hasIcon: Boolean?,
+ type: String?,
+ value: String?,
+ name: String?,
+ form: String?,
+ attrs: Attrs?
+): Attrs =
+ {
+ disabled(disabled)
+ href(href)
+ target(target)
+ trailingIcon?.let { attr("trailing-icon", it.toString()) }
+ hasIcon?.let { attr("has-icon", it.toString()) }
+ type(type)
+ value(value)
+ name(name)
+ form(form)
+
+ attrs?.invoke(this)
+ }
+
+private fun (@Composable MdButtonScope.() -> Unit)?.toElementScopeContent(): (@Composable ElementScope.() -> Unit)? =
+ this?.let {
+ { MdButtonScope(this).it() }
+ }
+
+@Composable
+private fun CommonButton(
+ tagName: String,
+ disabled: Boolean?,
+ href: String?,
+ target: String?,
+ trailingIcon: Boolean?,
+ hasIcon: Boolean?,
+ type: String?,
+ value: String?,
+ name: String?,
+ form: String?, // The form ID
+ attrs: Attrs?,
+ content: (@Composable MdButtonScope.() -> Unit)?
+) =
+ //TagElement({ MdElevatedButton().asDynamic() }, { TODO() }) { TODO() }
+ TagElement(
+ tagName,
+ commonButtonAttrs(disabled, href, target, trailingIcon, hasIcon, type, value, name, form, attrs),
+ content.toElementScopeContent()
+ )
+
+@Composable
+fun MdElevatedButton(
+ disabled: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ trailingIcon: Boolean? = null,
+ hasIcon: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null,
+ attrs: Attrs?,
+ content: (@Composable MdButtonScope.() -> Unit)?
+) {
+ /*
+ // TODO not working
+ if (toImport) {
+ MdElevatedButton()
+ toImport = false
+ }
+ */
+ // `require` can't be wrapped in `CommonButton` taken a `module` parameter because it seems to be processed by Webpack, JS `require` only takes constants.
+ // It seems there is no need to put this in an effect block, because it seems on Compose HTML recomposition happens exactly when there is a need to re-invoke a composable.
+ require("@material/web/button/elevated-button.js")
+
+ CommonButton(
+ "md-elevated-button",
+ disabled, href, target, trailingIcon, hasIcon, type, value, name, form,
+ attrs, content
+ )
+}
+
+@Composable
+fun MdFilledButton(
+ disabled: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ trailingIcon: Boolean? = null,
+ hasIcon: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null, // The form ID
+ attrs: Attrs?,
+ content: (@Composable MdButtonScope.() -> Unit)?
+) {
+ require("@material/web/button/filled-button.js")
+
+ CommonButton(
+ "md-filled-button",
+ disabled, href, target, trailingIcon, hasIcon, type, value, name, form,
+ attrs, content
+ )
+}
+
+@Composable
+fun MdFilledTonalButton(
+ disabled: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ trailingIcon: Boolean? = null,
+ hasIcon: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null, // The form ID
+ attrs: Attrs?,
+ content: (@Composable MdButtonScope.() -> Unit)?
+) {
+ require("@material/web/button/filled-tonal-button.js")
+
+ CommonButton(
+ "md-filled-tonal-button",
+ disabled, href, target, trailingIcon, hasIcon, type, value, name, form,
+ attrs, content
+ )
+}
+
+@Composable
+fun MdOutlinedButton(
+ disabled: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ trailingIcon: Boolean? = null,
+ hasIcon: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null, // The form ID
+ attrs: Attrs?,
+ content: (@Composable MdButtonScope.() -> Unit)?
+) {
+ require("@material/web/button/outlined-button.js")
+
+ CommonButton(
+ "md-outlined-button",
+ disabled, href, target, trailingIcon, hasIcon, type, value, name, form,
+ attrs, content
+ )
+}
+
+@Composable
+fun MdTextButton(
+ disabled: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ trailingIcon: Boolean? = null,
+ hasIcon: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null, // The form ID
+ attrs: Attrs?,
+ content: (@Composable MdButtonScope.() -> Unit)?
+) {
+ require("@material/web/button/text-button.js")
+
+ CommonButton(
+ "md-text-button",
+ disabled, href, target, trailingIcon, hasIcon, type, value, name, form,
+ attrs, content
+ )
+}
+
+
+class MdButtonScope(val elementScope: ElementScope) {
+ fun AttrsScope<*>.slotEqIcon() =
+ slot("icon")
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdCard.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdCard.kt
new file mode 100644
index 0000000..7ac69c5
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdCard.kt
@@ -0,0 +1,45 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/labs/card/internal/card.ts
+https://github.com/material-components/material-web/blob/main/labs/card/demo/stories.ts
+ */
+
+@MaterialWebLabsApi
+@Composable
+fun MdElevatedCard(
+ attrs: Attrs? = null,
+ content: @Composable (ElementScope.() -> Unit)?
+) {
+ require("@material/web/labs/card/elevated-card.js")
+
+ TagElement("md-elevated-card", attrs, content)
+}
+
+@MaterialWebLabsApi
+@Composable
+fun MdFilledCard(
+ attrs: Attrs? = null,
+ content: @Composable (ElementScope.() -> Unit)?
+) {
+ require("@material/web/labs/card/filled-card.js")
+
+ TagElement("md-filled-card", attrs, content)
+}
+
+@MaterialWebLabsApi
+@Composable
+fun MdOutlinedCard(
+ attrs: Attrs? = null,
+ content: @Composable (ElementScope.() -> Unit)?
+) {
+ require("@material/web/labs/card/outlined-card.js")
+
+ TagElement("md-outlined-card", attrs, content)
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdCheckbox.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdCheckbox.kt
new file mode 100644
index 0000000..7d76f32
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdCheckbox.kt
@@ -0,0 +1,77 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attr
+import com.huanshankeji.compose.web.attributes.ext.*
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/checkbox.md
+https://material-web.dev/components/checkbox/
+https://material-web.dev/components/checkbox/stories/
+*/
+@Composable
+fun MdCheckbox(
+ checked: Boolean? = null,
+ disabled: Boolean? = null,
+ indeterminate: Boolean? = null,
+ required: Boolean? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null,
+ attrs: Attrs? = null
+) {
+ require("@material/web/checkbox/checkbox.js")
+
+ TagElement("md-checkbox", {
+ attr("touch-target", "wrapper")
+ checked?.let { attr("checked", it) }
+ disabled(disabled)
+ indeterminate?.let { attr("indeterminate", it) }
+ required(required)
+ value(value)
+ name(name)
+ form(form)
+
+ attrs?.invoke(this)
+ }, null)
+}
+
+
+enum class MdCheckboxState {
+ Unchecked, Checked, Indeterminate
+}
+
+@Composable
+fun MdCheckbox(
+ state: MdCheckboxState,
+ disabled: Boolean? = null,
+ required: Boolean? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null,
+ attrs: Attrs? = null
+) {
+ val checked: Boolean?
+ val indeterminate: Boolean?
+ when (state) {
+ MdCheckboxState.Unchecked -> {
+ checked = null
+ indeterminate = null
+ }
+
+ MdCheckboxState.Checked -> {
+ checked = true
+ indeterminate = null
+ }
+
+ MdCheckboxState.Indeterminate -> {
+ checked = null
+ indeterminate = true
+ }
+ }
+
+ MdCheckbox(checked, disabled, indeterminate, required, value, name, form, attrs)
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdFab.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdFab.kt
new file mode 100644
index 0000000..ff4c93a
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdFab.kt
@@ -0,0 +1,72 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attr
+import com.huanshankeji.compose.web.attributes.ext.label
+import com.huanshankeji.compose.web.attributes.slot
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/fab.md
+https://material-web.dev/components/fab/
+https://material-web.dev/components/fab/stories/
+ */
+
+@Composable
+private fun CommonMdFab(
+ tagName: String,
+ variant: String?,
+ size: String?,
+ label: String?,
+ lowered: Boolean?,
+ attrs: Attrs?,
+ content: @Composable (MdFabScope.() -> Unit)?
+) =
+ TagElement(tagName, {
+ variant?.let { attr("variant", it) }
+ size?.let { attr("size", it) }
+ label?.let { label(it) }
+ lowered?.let { attr("lowered", it) }
+
+ attrs?.invoke(this)
+ }, content?.let {
+ { MdFabScope(this).it() }
+ })
+
+@Composable
+fun MdFab(
+ variant: String? = null,
+ size: String? = null,
+ label: String? = null,
+ lowered: Boolean? = null,
+ attrs: Attrs? = null,
+ content: @Composable (MdFabScope.() -> Unit)?
+) {
+ require("@material/web/fab/fab.js")
+
+ CommonMdFab("md-fab", variant, size, label, lowered, attrs, content)
+}
+
+@Composable
+fun MdBrandedFab(
+ variant: String? = null,
+ size: String? = null,
+ label: String? = null,
+ lowered: Boolean? = null,
+ attrs: Attrs? = null,
+ content: @Composable (MdFabScope.() -> Unit)?
+) {
+ require("@material/web/fab/branded-fab.js")
+
+ CommonMdFab("md-branded-fab", variant, size, label, lowered, attrs, content)
+}
+
+
+class MdFabScope(val elementScope: ElementScope) {
+ fun AttrsScope<*>.slotEqIcon() =
+ slot("icon")
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdIcon.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdIcon.kt
new file mode 100644
index 0000000..afe2bb6
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdIcon.kt
@@ -0,0 +1,32 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attr
+import org.jetbrains.compose.web.dom.TagElement
+import org.jetbrains.compose.web.dom.Text
+import org.w3c.dom.HTMLElement
+
+// https://github.com/material-components/material-web/blob/main/docs/components/icon.md
+
+/**
+ * @param content see https://github.com/material-components/material-web/blob/main/docs/components/icon.md#usage
+ */
+@Composable
+fun MdIcon(filled: Boolean? = null, attrs: Attrs?, content: @Composable () -> Unit) {
+ require("@material/web/icon/icon.js")
+
+ @Suppress("RemoveExplicitTypeArguments")
+ TagElement("md-icon", {
+ filled?.let { attr("filled", it) }
+
+ attrs?.invoke(this)
+ }) {
+ content()
+ }
+}
+
+@Composable
+fun MdIcon(filled: Boolean? = null, attrs: Attrs?, materialIconName: String) =
+ MdIcon(filled, attrs) { Text(materialIconName) }
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdIconButton.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdIconButton.kt
new file mode 100644
index 0000000..e702297
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdIconButton.kt
@@ -0,0 +1,189 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attr
+import com.huanshankeji.compose.web.attributes.ext.*
+import com.huanshankeji.compose.web.attributes.slot
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/icon-button.md
+https://material-web.dev/components/icon-button/
+https://material-web.dev/components/icon-button/stories/
+ */
+
+
+@Composable
+private fun CommonMdIconButton(
+ tagName: String,
+ disabled: Boolean?,
+ flipIconInRtl: Boolean?,
+ href: String?,
+ target: String?,
+ ariaLabelSelected: String?,
+ toggle: Boolean?,
+ selected: Boolean?,
+ type: String?,
+ value: String?,
+ attrs: Attrs?,
+ content: @Composable MdIconButtonScope.() -> Unit
+) =
+ @Suppress("RemoveExplicitTypeArguments")
+ TagElement(
+ tagName,
+ {
+ disabled(disabled)
+ flipIconInRtl?.let { attr("flip-icon-in-rtl", it) }
+ href(href)
+ target(target)
+ ariaLabelSelected?.let { attr("aria-label-selected", it) }
+ toggle?.let { attr("toggle", it) }
+ selected(selected)
+ type(type)
+ value(value)
+
+ attrs?.invoke(this)
+ }
+ ) {
+ MdIconButtonScope(this).content()
+ }
+
+
+@Composable
+fun MdIconButton(
+ disabled: Boolean? = null,
+ flipIconInRtl: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ ariaLabelSelected: String? = null,
+ toggle: Boolean? = null,
+ selected: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ attrs: Attrs? = null,
+ content: @Composable MdIconButtonScope.() -> Unit
+) {
+ require("@material/web/iconbutton/icon-button.js")
+
+ CommonMdIconButton(
+ "md-icon-button",
+ disabled,
+ flipIconInRtl,
+ href,
+ target,
+ ariaLabelSelected,
+ toggle,
+ selected,
+ type,
+ value,
+ attrs,
+ content
+ )
+}
+
+@Composable
+fun MdFilledIconButton(
+ disabled: Boolean? = null,
+ flipIconInRtl: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ ariaLabelSelected: String? = null,
+ toggle: Boolean? = null,
+ selected: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ attrs: Attrs? = null,
+ content: @Composable MdIconButtonScope.() -> Unit
+) {
+ require("@material/web/iconbutton/filled-icon-button.js")
+
+ CommonMdIconButton(
+ "md-filled-icon-button",
+ disabled,
+ flipIconInRtl,
+ href,
+ target,
+ ariaLabelSelected,
+ toggle,
+ selected,
+ type,
+ value,
+ attrs,
+ content
+ )
+}
+
+@Composable
+fun MdFilledTonalIconButton(
+ disabled: Boolean? = null,
+ flipIconInRtl: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ ariaLabelSelected: String? = null,
+ toggle: Boolean? = null,
+ selected: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ attrs: Attrs? = null,
+ content: @Composable MdIconButtonScope.() -> Unit
+) {
+ require("@material/web/iconbutton/filled-tonal-icon-button.js")
+
+ CommonMdIconButton(
+ "md-filled-tonal-icon-button",
+ disabled,
+ flipIconInRtl,
+ href,
+ target,
+ ariaLabelSelected,
+ toggle,
+ selected,
+ type,
+ value,
+ attrs,
+ content
+ )
+}
+
+@Composable
+fun MdOutlinedIconButton(
+ disabled: Boolean? = null,
+ flipIconInRtl: Boolean? = null,
+ href: String? = null,
+ target: String? = null,
+ ariaLabelSelected: String? = null,
+ toggle: Boolean? = null,
+ selected: Boolean? = null,
+ type: String? = null,
+ value: String? = null,
+ attrs: Attrs? = null,
+ content: @Composable MdIconButtonScope.() -> Unit
+) {
+ require("@material/web/iconbutton/outlined-icon-button.js")
+
+ CommonMdIconButton(
+ "md-outlined-icon-button",
+ disabled,
+ flipIconInRtl,
+ href,
+ target,
+ ariaLabelSelected,
+ toggle,
+ selected,
+ type,
+ value,
+ attrs,
+ content
+ )
+}
+
+
+class MdIconButtonScope(val elementScope: ElementScope) {
+ fun AttrsScope<*>.slotEqSelected() =
+ slot("selected")
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdList.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdList.kt
new file mode 100644
index 0000000..0ae50a4
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdList.kt
@@ -0,0 +1,91 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.ext.disabled
+import com.huanshankeji.compose.web.attributes.ext.href
+import com.huanshankeji.compose.web.attributes.ext.target
+import com.huanshankeji.compose.web.attributes.ext.type
+import com.huanshankeji.compose.web.attributes.slot
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/list.md
+https://material-web.dev/components/list/
+https://material-web.dev/components/list/stories/
+ */
+
+@Composable
+fun MdList(attrs: Attrs? = null, content: @Composable MdListScope.() -> Unit) {
+ require("@material/web/list/list.js")
+
+ TagElement("md-list", attrs) {
+ MdListScope(this).content()
+ }
+}
+
+class MdListScope(val elementScope: ElementScope) {
+ @Composable
+ fun MdListItem(
+ disabled: Boolean? = null,
+ type: MdListItemType? = null,
+ href: String? = null,
+ target: String? = null,
+ attrs: Attrs? = null,
+ content: @Composable MdListItemScope.() -> Unit
+ ) =
+ @OptIn(ExposedMdListApi::class)
+ com.huanshankeji.compose.html.material3.MdListItem(disabled, type, href, target, attrs, content)
+}
+
+
+@RequiresOptIn(
+ "An `MdListItem` is usually in an `MdList`. This component is exposed and should be used carefully. In most cases try to use the one in `MdListScope` instead.",
+ RequiresOptIn.Level.WARNING
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExposedMdListApi
+
+@ExposedMdListApi
+@Composable
+fun MdListItem(
+ disabled: Boolean? = null,
+ type: MdListItemType? = null,
+ href: String? = null,
+ target: String? = null,
+ attrs: Attrs? = null,
+ content: @Composable MdListItemScope.() -> Unit
+) {
+ require("@material/web/list/list-item.js")
+
+ //@Suppress("RemoveExplicitTypeArguments")
+ TagElement("md-list-item", {
+ disabled(disabled)
+ type(type?.stringValue)
+ href(href)
+ target(target)
+
+ attrs?.invoke(this)
+ }) {
+ MdListItemScope(this).content()
+ }
+}
+
+enum class MdListItemType(val stringValue: String) {
+ Text("text"), Link("link"), Button("button")
+}
+
+class MdListItemScope(val elementScope: ElementScope) {
+ enum class Slot(val stringValue: String) {
+ Headline("headline"),
+ Start("start"), End("end"),
+ SupportingText("supporting-text"), TrailingSupportingText("trailing-supporting-text"),
+ Overline("overline")
+ }
+
+ fun AttrsScope<*>.slot(value: Slot) =
+ slot(value.stringValue)
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdNavigationBar.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdNavigationBar.kt
new file mode 100644
index 0000000..81ba624
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdNavigationBar.kt
@@ -0,0 +1,31 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attrIfNotNull
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/labs/navigationbar/internal/navigation-bar.ts
+https://github.com/material-components/material-web/blob/main/labs/navigationbar/demo/stories.ts
+ */
+
+@MaterialWebLabsApi
+@Composable
+fun MdNavigationBar(
+ activeIndex: Int? = null,
+ hideInactiveLabels: Boolean? = null,
+ attrs: Attrs? = null,
+ content: @Composable (ElementScope.() -> Unit)?
+) {
+ require("@material/web/labs/navigationbar/navigation-bar.js")
+
+ TagElement("md-navigation-bar", {
+ attrIfNotNull("active-index", activeIndex)
+ attrIfNotNull("hide-inactive-labels", hideInactiveLabels)
+
+ attrs?.invoke(this)
+ }, content)
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdNavigationTab.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdNavigationTab.kt
new file mode 100644
index 0000000..9537eb4
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdNavigationTab.kt
@@ -0,0 +1,52 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attrIfNotNull
+import com.huanshankeji.compose.web.attributes.ext.disabled
+import com.huanshankeji.compose.web.attributes.ext.label
+import com.huanshankeji.compose.web.attributes.slot
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/labs/navigationtab/internal/navigation-tab.ts
+https://github.com/material-components/material-web/blob/main/labs/navigationbar/demo/stories.ts (navigation bar storybook)
+ */
+
+@MaterialWebLabsApi
+@Composable
+fun MdNavigationTab(
+ disabled: Boolean? = null,
+ active: Boolean? = null,
+ hideInactiveLabel: Boolean? = null,
+ label: String? = null,
+ badgeValue: String? = null,
+ showBadge: Boolean? = null,
+ attrs: Attrs? = null,
+ content: @Composable (MdNavigationTabScope.() -> Unit)?
+) {
+ require("@material/web/labs/navigationtab/navigation-tab.js")
+
+ TagElement("md-navigation-tab", {
+ disabled(disabled)
+ attrIfNotNull("active", active)
+ attrIfNotNull("hide-inactive-label", hideInactiveLabel)
+ label(label)
+ attrIfNotNull("badge-value", badgeValue)
+ attrIfNotNull("show-badge", showBadge)
+
+ attrs?.invoke(this)
+ }, content?.let { { MdNavigationTabScope(this).it() } })
+}
+
+class MdNavigationTabScope(val elementScope: ElementScope) {
+ enum class Slot(val stringValue: String) {
+ ActiveIcon("active-icon"), InactiveIcon("inactive-icon")
+ }
+
+ fun AttrsScope<*>.slot(value: Slot) =
+ slot(value.stringValue)
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdSwitch.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdSwitch.kt
new file mode 100644
index 0000000..b80ed60
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdSwitch.kt
@@ -0,0 +1,62 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attr
+import com.huanshankeji.compose.web.attributes.ext.*
+import org.jetbrains.compose.web.dom.Label
+import org.jetbrains.compose.web.dom.TagElement
+import org.jetbrains.compose.web.dom.Text
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/switch.md
+https://material-web.dev/components/switch/
+https://material-web.dev/components/switch/stories/
+*/
+@Composable
+fun MdSwitch(
+ disabled: Boolean? = null,
+ selected: Boolean? = null, // `false` won't work here
+ icons: Boolean? = null,
+ showOnlySelectedIcon: Boolean? = null,
+ required: Boolean? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null,
+ attrs: Attrs? = null
+) {
+ require("@material/web/switch/switch.js")
+
+ TagElement("md-switch", {
+ disabled(disabled)
+ selected?.let { attr("selected", it) }
+ icons?.let { attr("icons", it) }
+ showOnlySelectedIcon?.let { attr("show-only-selected-icon", it) }
+ required(required)
+ value(value)
+ name(name)
+ form(form)
+
+ attrs?.invoke(this)
+ }, null)
+}
+
+// https://github.com/material-components/material-web/blob/main/docs/components/switch.md#label
+@Composable
+fun LabelWithMdSwitch(
+ label: String,
+ disabled: Boolean? = null,
+ selected: Boolean? = null,
+ icons: Boolean? = null,
+ showOnlySelectedIcon: Boolean? = null,
+ required: Boolean? = null,
+ value: String? = null,
+ name: String? = null,
+ form: String? = null,
+ attrs: Attrs? = null
+) =
+ Label {
+ Text(label)
+ MdSwitch(disabled, selected, icons, showOnlySelectedIcon, required, value, name, form, attrs)
+ }
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdTextField.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdTextField.kt
new file mode 100644
index 0000000..0a397e5
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/MdTextField.kt
@@ -0,0 +1,225 @@
+package com.huanshankeji.compose.html.material3
+
+import androidx.compose.runtime.Composable
+import com.huanshankeji.compose.web.attributes.Attrs
+import com.huanshankeji.compose.web.attributes.attr
+import com.huanshankeji.compose.web.attributes.ext.*
+import com.huanshankeji.compose.web.attributes.slot
+import org.jetbrains.compose.web.attributes.AttrsScope
+import org.jetbrains.compose.web.attributes.AutoComplete
+import org.jetbrains.compose.web.attributes.InputMode
+import org.jetbrains.compose.web.attributes.InputType
+import org.jetbrains.compose.web.dom.ElementScope
+import org.jetbrains.compose.web.dom.TagElement
+import org.w3c.dom.HTMLElement
+
+/*
+https://github.com/material-components/material-web/blob/main/docs/components/text-field.md
+https://material-web.dev/components/text-field/
+https://material-web.dev/components/text-field/stories/
+*/
+
+@Composable
+private fun CommonTextField(
+ tagName: String,
+ disabled: Boolean?,
+ error: Boolean?,
+ errorText: String?,
+ label: String?, // This attribute seems to have different semantics here from its original semantics in HTML.
+ required: Boolean?,
+ value: String?,
+ prefixText: String?,
+ suffixText: String?,
+ hasLeadingIcon: Boolean?,
+ hasTrailingIcon: Boolean?,
+ supportingText: String?,
+ textDirection: String?,
+ rows: Int?, // The JavaScript `number` is actually a `Double`. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number.
+ cols: Int?,
+ inputMode: InputMode?,
+ max: String?,
+ maxLength: Int?,
+ min: String?,
+ minLength: Int?,
+ pattern: String?,
+ placeholder: String?,
+ readOnly: Boolean?,
+ multiple: Boolean?,
+ step: String?,
+ type: InputType<*>?,
+ autocomplete: AutoComplete?,
+ attrs: Attrs?,
+ content: (@Composable MdTextFieldScope.() -> Unit)?
+) =
+ TagElement(tagName, {
+ disabled(disabled)
+ error?.let { attr("error", it) }
+ errorText?.let { attr("error-text", it) }
+ label(label)
+ required(required)
+ value(value)
+ prefixText?.let { attr("prefix-text", it) }
+ suffixText?.let { attr("suffix-text", it) }
+ hasLeadingIcon?.let { attr("has-leading-icon", it) }
+ hasTrailingIcon?.let { attr("has-trailing-icon", it) }
+ supportingText?.let { attr("supporting-text", it) }
+ textDirection?.let { attr("text-direction", it) }
+ rows?.let { attr("rows", it) }
+ cols?.let { attr("cols", it) }
+ inputMode?.let { inputMode(it) }
+ max(max)
+ maxLength(maxLength)
+ min(min)
+ minLength(minLength)
+ pattern(pattern)
+ placeholder(placeholder)
+ readOnly(readOnly)
+ multiple(multiple)
+ step(step)
+ type(type)
+ autoComplete(autocomplete)
+
+ attrs?.invoke(this)
+ }, content?.let { { MdTextFieldScope(this).it() } })
+
+@Composable
+fun MdFilledTextField(
+ disabled: Boolean? = null,
+ error: Boolean? = null,
+ errorText: String? = null,
+ label: String? = null,
+ required: Boolean? = null,
+ value: String? = null,
+ prefixText: String? = null,
+ suffixText: String? = null,
+ hasLeadingIcon: Boolean? = null,
+ hasTrailingIcon: Boolean? = null,
+ supportingText: String? = null,
+ textDirection: String? = null,
+ rows: Int? = null,
+ cols: Int? = null,
+ inputMode: InputMode? = null,
+ max: String? = null,
+ maxLength: Int? = null,
+ min: String? = null,
+ minLength: Int? = null,
+ pattern: String? = null,
+ placeholder: String? = null,
+ readOnly: Boolean? = null,
+ multiple: Boolean? = null,
+ step: String? = null,
+ type: InputType<*>? = null,
+ autocomplete: AutoComplete? = null,
+ attrs: Attrs? = null,
+ content: (@Composable MdTextFieldScope.() -> Unit)? = null
+) {
+ require("@material/web/textfield/filled-text-field.js")
+
+ CommonTextField(
+ "md-filled-text-field",
+ disabled,
+ error,
+ errorText,
+ label,
+ required,
+ value,
+ prefixText,
+ suffixText,
+ hasLeadingIcon,
+ hasTrailingIcon,
+ supportingText,
+ textDirection,
+ rows,
+ cols,
+ inputMode,
+ max,
+ maxLength,
+ min,
+ minLength,
+ pattern,
+ placeholder,
+ readOnly,
+ multiple,
+ step,
+ type,
+ autocomplete,
+ attrs,
+ content
+ )
+}
+
+@Composable
+fun MdOutlinedTextField(
+ disabled: Boolean? = null,
+ error: Boolean? = null,
+ errorText: String? = null,
+ label: String? = null,
+ required: Boolean? = null,
+ value: String? = null,
+ prefixText: String? = null,
+ suffixText: String? = null,
+ hasLeadingIcon: Boolean? = null,
+ hasTrailingIcon: Boolean? = null,
+ supportingText: String? = null,
+ textDirection: String? = null,
+ rows: Int? = null,
+ cols: Int? = null,
+ inputMode: InputMode? = null,
+ max: String? = null,
+ maxLength: Int? = null,
+ min: String? = null,
+ minLength: Int? = null,
+ pattern: String? = null,
+ placeholder: String? = null,
+ readOnly: Boolean? = null,
+ multiple: Boolean? = null,
+ step: String? = null,
+ type: InputType<*>? = null,
+ autocomplete: AutoComplete? = null,
+ attrs: Attrs? = null,
+ content: (@Composable MdTextFieldScope.() -> Unit)? = null
+) {
+ require("@material/web/textfield/outlined-text-field.js")
+
+ CommonTextField(
+ "md-outlined-text-field",
+ disabled,
+ error,
+ errorText,
+ label,
+ required,
+ value,
+ prefixText,
+ suffixText,
+ hasLeadingIcon,
+ hasTrailingIcon,
+ supportingText,
+ textDirection,
+ rows,
+ cols,
+ inputMode,
+ max,
+ maxLength,
+ min,
+ minLength,
+ pattern,
+ placeholder,
+ readOnly,
+ multiple,
+ step,
+ type,
+ autocomplete,
+ attrs,
+ content
+ )
+}
+
+
+class MdTextFieldScope(val elementScope: ElementScope) {
+ enum class Slot(val value: String) {
+ LeadingIcon("leading-icon"), TrailingIcon("trailing-icon")
+ }
+
+ fun AttrsScope<*>.slot(value: Slot) =
+ slot(value.value)
+}
diff --git a/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/Require.kt b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/Require.kt
new file mode 100644
index 0000000..47c646c
--- /dev/null
+++ b/compose-html-material3/src/jsMain/kotlin/com/huanshankeji/compose/html/material3/Require.kt
@@ -0,0 +1,4 @@
+package com.huanshankeji.compose.html.material3
+
+// TODO use the one in "kotlin-common"
+external fun require(module: String): dynamic
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrsScope.kt b/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrsScope.kt
deleted file mode 100644
index 25347c8..0000000
--- a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/attributes/AttrsScope.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.huanshankeji.compose.web.attributes
-
-import org.jetbrains.compose.web.attributes.AttrsScope
-import org.w3c.dom.HTMLElement
-
-fun AttrsScope.attr(attr: String, isPresent: Boolean = true) =
- attr(attr, isPresent.toString())
diff --git a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/dom/ElementScope.kt b/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/dom/ElementScope.kt
deleted file mode 100644
index cf64f59..0000000
--- a/compose-web-common/src/jsMain/kotlin/com/huanshankeji/compose/web/dom/ElementScope.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.huanshankeji.compose.web.dom
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.NonRestartableComposable
-import kotlinx.coroutines.CoroutineScope
-import org.jetbrains.compose.web.dom.ElementScope
-import org.w3c.dom.Element
-
-@Composable
-@NonRestartableComposable
-fun ElementScope.LaunchedRefEffect(
- vararg keys: Any?,
- block: suspend CoroutineScope.(TElement) -> Unit
-) =
- TODO() as Unit
diff --git a/gradle-plugins/build.gradle.kts b/gradle-plugins/build.gradle.kts
index 1ac43eb..88ce53f 100644
--- a/gradle-plugins/build.gradle.kts
+++ b/gradle-plugins/build.gradle.kts
@@ -31,15 +31,15 @@ group = "com.huanshankeji"
version = projectVersion
gradlePlugin {
- website.set("https://github.com/huanshankeji/compose-web-material")
+ website.set("https://github.com/huanshankeji/compose-html-material")
vcsUrl.set("${website.get()}.git")
plugins {
- getByName("com.huanshankeji.compose-web-material-conventions") {
- displayName = "Compose for Web Material conventions"
+ getByName("com.huanshankeji.compose-html-material-conventions-legacy") {
+ displayName = "Compose HTML Material conventions"
description =
- "This plugin adds the needed Maven dependencies and npm devDependencies (mainly for Webpack) for a Compose for Web project with Material components."
- tags.set(listOf("kotlin", "kotlin-js", "compose-multiplatform", "compose-web"))
+ "This plugin adds the needed Maven dependencies and npm devDependencies (mainly for Webpack) for a Compose HTML project with Material components."
+ tags.set(listOf("kotlin", "kotlin-js", "compose-multiplatform", "compose-html"))
}
}
}
diff --git a/gradle-plugins/src/main/kotlin/com/huanshankeji/compose-web-material-conventions.gradle.kts b/gradle-plugins/src/main/kotlin/com/huanshankeji/compose-html-material-conventions-legacy.gradle.kts
similarity index 87%
rename from gradle-plugins/src/main/kotlin/com/huanshankeji/compose-web-material-conventions.gradle.kts
rename to gradle-plugins/src/main/kotlin/com/huanshankeji/compose-html-material-conventions-legacy.gradle.kts
index fc2af9f..561bafe 100644
--- a/gradle-plugins/src/main/kotlin/com/huanshankeji/compose-web-material-conventions.gradle.kts
+++ b/gradle-plugins/src/main/kotlin/com/huanshankeji/compose-html-material-conventions-legacy.gradle.kts
@@ -10,7 +10,7 @@ kotlin {
sourceSets {
val jsMain by getting {
dependencies {
- implementation("com.huanshankeji:compose-web-material:$projectVersion")
+ implementation("com.huanshankeji:compose-html-material-legacy:$projectVersion")
implementation(devNpm("css-loader", NpmDevDependencyVersions.cssLoader))
implementation(devNpm("extract-loader", NpmDevDependencyVersions.extractLoader))
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
index 509fe20..bf0ba8b 100644
--- a/kotlin-js-store/yarn.lock
+++ b/kotlin-js-store/yarn.lock
@@ -70,6 +70,11 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
+"@lit-labs/ssr-dom-shim@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz#353ce4a76c83fadec272ea5674ede767650762fd"
+ integrity sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==
+
"@lit/reactive-element@1.0.0-rc.4":
version "1.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.0-rc.4.tgz#3473f6a687e9925fc2e9ff1b716a86b5f4b576ad"
@@ -80,6 +85,13 @@
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.3.2.tgz#43e470537b6ec2c23510c07812616d5aa27a17cd"
integrity sha512-A2e18XzPMrIh35nhIdE4uoqRzoIpEU5vZYuQN4S3Ee1zkGdYC27DP12pewbw/RLgPHzaE4kx/YqxMzebOpm0dA==
+"@lit/reactive-element@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.4.tgz#8f2ed950a848016383894a26180ff06c56ae001b"
+ integrity sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==
+ dependencies:
+ "@lit-labs/ssr-dom-shim" "^1.2.0"
+
"@material/animation@14.0.0-canary.261f2db59.0":
version "14.0.0-canary.261f2db59.0"
resolved "https://registry.yarnpkg.com/@material/animation/-/animation-14.0.0-canary.261f2db59.0.tgz#1e3897a25dc4264afd5beb40f8040686276903fc"
@@ -696,6 +708,14 @@
"@material/theme" "14.0.0-canary.261f2db59.0"
tslib "^2.1.0"
+"@material/web@1.4.1":
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/@material/web/-/web-1.4.1.tgz#116480759d0b5815ea181086d32cc3ccb54a30f6"
+ integrity sha512-17MZA6Bt7ie6uDc5cVEGtm6frQfm7ASLGVJhJuYJ0W2Fs7Zogd8wJ3ExoYU8jyzv8LEXcfvnZKcvr8dlquGsig==
+ dependencies:
+ lit "^2.7.4 || ^3.0.0"
+ tslib "^2.4.0"
+
"@socket.io/component-emitter@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
@@ -1793,6 +1813,15 @@ lit-element@^3.2.0:
"@lit/reactive-element" "^1.3.0"
lit-html "^2.2.0"
+lit-element@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-4.0.4.tgz#e0b37ebbe2394bcb9578d611a409f49475dff361"
+ integrity sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==
+ dependencies:
+ "@lit-labs/ssr-dom-shim" "^1.2.0"
+ "@lit/reactive-element" "^2.0.4"
+ lit-html "^3.1.2"
+
lit-html@^2.2.0:
version "2.2.5"
resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.2.5.tgz#e2b4fbdb3b57e38a3bce23c8473d72ba0e652ed8"
@@ -1800,6 +1829,13 @@ lit-html@^2.2.0:
dependencies:
"@types/trusted-types" "^2.0.2"
+lit-html@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-3.1.2.tgz#6655ce82367472de7680c62b1bcb0beb0e426fa1"
+ integrity sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==
+ dependencies:
+ "@types/trusted-types" "^2.0.2"
+
lit@^2.0.0:
version "2.2.5"
resolved "https://registry.yarnpkg.com/lit/-/lit-2.2.5.tgz#60e8dd5b751fe31f849c64f74f1784a88235d664"
@@ -1809,6 +1845,15 @@ lit@^2.0.0:
lit-element "^3.2.0"
lit-html "^2.2.0"
+"lit@^2.7.4 || ^3.0.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lit/-/lit-3.1.2.tgz#f276258e8a56593f1d834a5fd00a7eb5e891ae73"
+ integrity sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==
+ dependencies:
+ "@lit/reactive-element" "^2.0.4"
+ lit-element "^4.0.4"
+ lit-html "^3.1.2"
+
loader-runner@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
@@ -2410,6 +2455,11 @@ tslib@^2.0.1, tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
+tslib@^2.4.0:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
+ integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
diff --git a/legacy/README.md b/legacy/README.md
new file mode 100644
index 0000000..b10b57d
--- /dev/null
+++ b/legacy/README.md
@@ -0,0 +1,59 @@
+# Compose HTML Material (legacy Material 2)
+
+[![Maven Central](https://img.shields.io/maven-central/v/com.huanshankeji/compose-html-material-legacy)](https://search.maven.org/artifact/com.huanshankeji/compose-html-material-legacy)
+[![Gradle Plugin Portal](https://img.shields.io/gradle-plugin-portal/v/com.huanshankeji.compose-html-material-conventions-legacy)](https://plugins.gradle.org/plugin/com.huanshankeji.compose-html-material-conventions-legacy)
+
+Some legacy Material 2 components for Compose HTML, based on [the obsolete `mwc` branch of Material Web](https://github.com/material-components/material-web/tree/mwc) (preferred) and [Material Components for the web (or material-components-web, or `mdc`)](https://github.com/material-components/material-components-web) (fallback)
+
+**There will be no further development effort in this legacy Material 2 portion of this project. Here is a list of reasons and alternatives:**
+
+1. The [material-web](https://github.com/material-components/material-web) team is working on Material 3 support and [the Material 2 branch (`mwc`)](https://github.com/material-components/material-web/tree/mwc) is no longer under active development. Existing Compose wrappers of their Material 2 components are still kept in [the `:compose-html-material-legacy` subproject](/compose-html-material-legacy) but not updated.
+1. [KMDC](https://github.com/mpetuska/kmdc) wrapping around [material-components-web (`mdc`)](https://github.com/material-components/material-components-web) provides a much more complete set of Material Design components for Compose HTML.
+
+## Instructions on how to use
+
+Some configurations are needed to use this library due to the immaturities of this project and Kotlin/JS.
+
+### Add the dependency
+
+Prior to v0.3.0:
+
+```kotlin
+implementation("com.huanshankeji:compose-web-material:$version")
+```
+
+Since v0.3.0:
+
+```kotlin
+implementation("com.huanshankeji:compose-html-material-legacy:$version")
+```
+
+### In code
+
+Call `mwcRequires()` in your `main` function before calling any component Composable functions.
+
+### Kotlin/JS Webpack configuration
+
+If you use this library in an app project with Webpack [which Kotlin/JS currently uses](https://kotlinlang.org/docs/js-project-setup.html), you might want to configure it as recommended by Material Web and Material Components for the web. Some instructions on how to do this simply are as below.
+
+If you use a version prior to v0.3.0, this plugin helps add the dependency to this project (if you do this you can skip the "Add the dependency" step above) and the `devNpm` dependencies:
+
+Prior to v0.3.0:
+
+```kotlin
+plugins {
+ id("com.huanshankeji.compose-web-material-conventions") version someVersion
+}
+```
+
+Since v0.3.0:
+
+```kotlin
+plugins {
+ id("com.huanshankeji.compose-web-material-conventions-legacy") version someVersion
+}
+```
+
+However, the plugin doesn't [make further adjustments to the webpack configuration](https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file), so you also need to refer to [the demo further adjustments](demo/webpack.config.d/further_adjustments.js) and [the demo HTML page](demo/html/demo.html) to add your own. Just copy and possibly adapt them as you like.
+
+Also note that there is [a new approach to adding CSS and SCSS support in the Kotlin Gradle Plugin since 1.8](https://kotlinlang.org/docs/whatsnew18.html#new-approach-to-adding-css-support-to-your-project), which replaces the function of this plugin.
diff --git a/demo/html/demo.html b/legacy/demo/html/demo.html
similarity index 100%
rename from demo/html/demo.html
rename to legacy/demo/html/demo.html
diff --git a/demo/webpack.config.d/further_adjustments.js b/legacy/demo/webpack.config.d/further_adjustments.js
similarity index 100%
rename from demo/webpack.config.d/further_adjustments.js
rename to legacy/demo/webpack.config.d/further_adjustments.js
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 897a4c8..9cedcae 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,6 +1,7 @@
-rootProject.name = "compose-web-material"
+rootProject.name = "compose-html-material"
-include("compose-web-common")
-include(rootProject.name)
+include("compose-html-common")
+include("compose-html-material-legacy")
+include("compose-html-material3")
include("gradle-plugins")
-project(":gradle-plugins").name = rootProject.name + "-gradle-plugins"
+project(":gradle-plugins").name = "compose-html-material-gradle-plugins-legacy"