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"