Shagen Ogandzhanian
3 years ago
committed by
GitHub
7 changed files with 1246 additions and 0 deletions
@ -0,0 +1,49 @@
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
|
||||
kotlin { |
||||
js(IR) { |
||||
browser() { |
||||
testTask { |
||||
testLogging.showStandardStreams = true |
||||
useKarma { |
||||
useChromeHeadless() |
||||
} |
||||
} |
||||
} |
||||
binaries.executable() |
||||
} |
||||
|
||||
sourceSets { |
||||
val commonMain by getting { |
||||
dependencies { |
||||
implementation(compose.runtime) |
||||
implementation(kotlin("stdlib-common")) |
||||
} |
||||
} |
||||
|
||||
val jsMain by getting { |
||||
dependencies { |
||||
implementation(project(":internal-web-core-runtime")) |
||||
implementation(kotlin("stdlib-js")) |
||||
implementation(project(":web-core")) |
||||
} |
||||
} |
||||
|
||||
val jsTest by getting { |
||||
dependencies { |
||||
implementation(project(":test-utils")) |
||||
implementation(kotlin("test-js")) |
||||
} |
||||
} |
||||
|
||||
all { |
||||
languageSettings { |
||||
useExperimentalAnnotation("org.jetbrains.compose.web.testutils.ComposeWebExperimentalTestsApi") |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.web |
||||
|
||||
@RequiresOptIn("This API is experimental and is likely to change in the future.") |
||||
annotation class ExperimentalComposeWebSvgApi |
@ -0,0 +1,729 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.web.svg |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import kotlinx.browser.document |
||||
import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi |
||||
import org.jetbrains.compose.web.css.CSSLengthOrPercentageValue |
||||
import org.jetbrains.compose.web.dom.* |
||||
import org.w3c.css.masking.SVGClipPathElement |
||||
import org.w3c.css.masking.SVGMaskElement |
||||
import org.w3c.dom.Element |
||||
import org.w3c.dom.svg.* |
||||
|
||||
private open class ElementBuilderNS<TElement : Element>(private val tagName: String, private val namespace: String) : |
||||
ElementBuilder<TElement> { |
||||
private val el: Element by lazy { document.createElementNS(namespace, tagName) } |
||||
override fun create(): TElement = el.cloneNode().unsafeCast<TElement>() |
||||
} |
||||
|
||||
const val SVG_NS = "http://www.w3.org/2000/svg" |
||||
|
||||
private val A = ElementBuilderNS<SVGAElement>("a", SVG_NS) |
||||
private val Animate = ElementBuilderNS<SVGElement>("animate", SVG_NS) |
||||
private val AnimateMotion = ElementBuilderNS<SVGElement>("animateMotion", SVG_NS) |
||||
private val AnimateTransform = ElementBuilderNS<SVGElement>("animateTransform", SVG_NS) |
||||
private val Circle = ElementBuilderNS<SVGCircleElement>("circle", SVG_NS) |
||||
private val ClipPath = ElementBuilderNS<SVGClipPathElement>("clipPath", SVG_NS) |
||||
private val Defs = ElementBuilderNS<SVGDefsElement>("defs", SVG_NS) |
||||
private val Desc = ElementBuilderNS<SVGDescElement>("desc", SVG_NS) |
||||
private val Ellipse = ElementBuilderNS<SVGEllipseElement>("ellipse", SVG_NS) |
||||
private val Filter = ElementBuilderNS<SVGElement>("filter", SVG_NS) |
||||
private val G = ElementBuilderNS<SVGElement>("g", SVG_NS) |
||||
private val Image = ElementBuilderNS<SVGImageElement>("image", SVG_NS) |
||||
private val Line = ElementBuilderNS<SVGLineElement>("line", SVG_NS) |
||||
private val LinearGradient = ElementBuilderNS<SVGLinearGradientElement>("linearGradient", SVG_NS) |
||||
private val Marker = ElementBuilderNS<SVGMarkerElement>("marker", SVG_NS) |
||||
private val Mask = ElementBuilderNS<SVGMaskElement>("mask", SVG_NS) |
||||
private val Mpath = ElementBuilderNS<SVGElement>("mpath", SVG_NS) |
||||
private val Path = ElementBuilderNS<SVGPathElement>("path", SVG_NS) |
||||
private val Pattern = ElementBuilderNS<SVGPatternElement>("pattern", SVG_NS) |
||||
private val Polygon = ElementBuilderNS<SVGPolygonElement>("polygon", SVG_NS) |
||||
private val Polyline = ElementBuilderNS<SVGPolylineElement>("polyline", SVG_NS) |
||||
private val RadialGradient = ElementBuilderNS<SVGRadialGradientElement>("radialGradient", SVG_NS) |
||||
private val Rect = ElementBuilderNS<SVGRectElement>("rect", SVG_NS) |
||||
private val Set = ElementBuilderNS<SVGElement>("set", SVG_NS) |
||||
private val Stop = ElementBuilderNS<SVGStopElement>("stop", SVG_NS) |
||||
private val Svg = ElementBuilderNS<SVGElement>("svg", SVG_NS) |
||||
private val Switch = ElementBuilderNS<SVGSwitchElement>("switch", SVG_NS) |
||||
private val Symbol = ElementBuilderNS<SVGSymbolElement>("symbol", SVG_NS) |
||||
private val Text = ElementBuilderNS<SVGTextElement>("text", SVG_NS) |
||||
private val TextPath = ElementBuilderNS<SVGTextPathElement>("textPath", SVG_NS) |
||||
private val Title = ElementBuilderNS<SVGTitleElement>("title", SVG_NS) |
||||
private val Tspan = ElementBuilderNS<SVGTSpanElement>("tspan", SVG_NS) |
||||
private val Use = ElementBuilderNS<SVGUseElement>("use", SVG_NS) |
||||
private val View = ElementBuilderNS<SVGViewElement>("view", SVG_NS) |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun Svg( |
||||
viewBox: String? = null, |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Svg, |
||||
applyAttrs = { |
||||
viewBox?.let { attr("viewBox", it) } |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.SvgA( |
||||
href: String, |
||||
attrs: AttrBuilderContext<SVGAElement>? = null, |
||||
content: ContentBuilder<SVGAElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = A, |
||||
applyAttrs = { |
||||
attr("href", href) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Circle( |
||||
cx: CSSLengthOrPercentageValue, |
||||
cy: CSSLengthOrPercentageValue, |
||||
r: CSSLengthOrPercentageValue, |
||||
attrs: AttrBuilderContext<SVGCircleElement>? = null, |
||||
content: ContentBuilder<SVGCircleElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Circle, |
||||
applyAttrs = { |
||||
attr("cx", cx.toString()) |
||||
attr("cy", cy.toString()) |
||||
attr("r", r.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Circle( |
||||
cx: Number, |
||||
cy: Number, |
||||
r: Number, |
||||
attrs: AttrBuilderContext<SVGCircleElement>? = null, |
||||
content: ContentBuilder<SVGCircleElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Circle, |
||||
applyAttrs = { |
||||
attr("cx", cx.toString()) |
||||
attr("cy", cy.toString()) |
||||
attr("r", r.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.SvgText( |
||||
text: String, |
||||
x: Number = 0, |
||||
y: Number = 0, |
||||
attrs: AttrBuilderContext<SVGTextElement>? = null, |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Text, |
||||
applyAttrs = { |
||||
attr("x", x.toString()) |
||||
attr("y", y.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = { |
||||
Text(text) |
||||
} |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.View( |
||||
id: String, |
||||
viewBox: String, |
||||
attrs: AttrBuilderContext<SVGViewElement>? = null, |
||||
) { |
||||
TagElement( |
||||
elementBuilder = View, |
||||
applyAttrs = { |
||||
attr("id", id) |
||||
attr("viewBox", viewBox) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = null |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Rect( |
||||
x: Number, |
||||
y: Number, |
||||
width: Number, |
||||
height: Number, |
||||
attrs: AttrBuilderContext<SVGRectElement>? = null, |
||||
content: ContentBuilder<SVGRectElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Rect, |
||||
applyAttrs = { |
||||
attr("x", x.toString()) |
||||
attr("y", y.toString()) |
||||
attr("width", width.toString()) |
||||
attr("height", height.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Rect( |
||||
x: CSSLengthOrPercentageValue, |
||||
y: CSSLengthOrPercentageValue, |
||||
width: CSSLengthOrPercentageValue, |
||||
height: CSSLengthOrPercentageValue, |
||||
attrs: AttrBuilderContext<SVGRectElement>? = null, |
||||
content: ContentBuilder<SVGRectElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Rect, |
||||
applyAttrs = { |
||||
attr("x", x.toString()) |
||||
attr("y", y.toString()) |
||||
attr("width", width.toString()) |
||||
attr("height", height.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Ellipse( |
||||
cx: CSSLengthOrPercentageValue, |
||||
cy: CSSLengthOrPercentageValue, |
||||
rx: CSSLengthOrPercentageValue, |
||||
ry: CSSLengthOrPercentageValue, |
||||
attrs: AttrBuilderContext<SVGEllipseElement>? = null, |
||||
content: ContentBuilder<SVGEllipseElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Ellipse, |
||||
applyAttrs = { |
||||
attr("cx", cx.toString()) |
||||
attr("cy", cy.toString()) |
||||
attr("rx", rx.toString()) |
||||
attr("ry", ry.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Ellipse( |
||||
cx: Number, |
||||
cy: Number, |
||||
rx: Number, |
||||
ry: Number, |
||||
attrs: AttrBuilderContext<SVGEllipseElement>? = null, |
||||
content: ContentBuilder<SVGEllipseElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Ellipse, |
||||
applyAttrs = { |
||||
attr("cx", cx.toString()) |
||||
attr("cy", cy.toString()) |
||||
attr("rx", rx.toString()) |
||||
attr("ry", ry.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Symbol( |
||||
id: String? = null, |
||||
attrs: AttrBuilderContext<SVGSymbolElement>? = null, |
||||
content: ContentBuilder<SVGSymbolElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Symbol, |
||||
applyAttrs = { |
||||
id?.let { attr("id", it) } |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Use( |
||||
href: String, |
||||
attrs: AttrBuilderContext<SVGUseElement>? = null, |
||||
content: ContentBuilder<SVGUseElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Use, |
||||
applyAttrs = { |
||||
attr("href", href) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Line( |
||||
x1: CSSLengthOrPercentageValue, |
||||
y1: CSSLengthOrPercentageValue, |
||||
x2: CSSLengthOrPercentageValue, |
||||
y2: CSSLengthOrPercentageValue, |
||||
attrs: AttrBuilderContext<SVGLineElement>? = null, |
||||
content: ContentBuilder<SVGLineElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Line, |
||||
applyAttrs = { |
||||
attr("x1", x1.toString()) |
||||
attr("y1", y1.toString()) |
||||
attr("x2", x2.toString()) |
||||
attr("y2", y2.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Line( |
||||
x1: Number, |
||||
y1: Number, |
||||
x2: Number, |
||||
y2: Number, |
||||
attrs: AttrBuilderContext<SVGLineElement>? = null, |
||||
content: ContentBuilder<SVGLineElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Line, |
||||
applyAttrs = { |
||||
attr("x1", x1.toString()) |
||||
attr("y1", y1.toString()) |
||||
attr("x2", x2.toString()) |
||||
attr("y2", y2.toString()) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.ClipPath( |
||||
id: String, |
||||
attrs: AttrBuilderContext<SVGClipPathElement>? = null, |
||||
content: ContentBuilder<SVGClipPathElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = ClipPath, |
||||
applyAttrs = { |
||||
attr("id", id) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Path( |
||||
d: String, |
||||
attrs: AttrBuilderContext<SVGPathElement>? = null, |
||||
content: ContentBuilder<SVGPathElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Path, |
||||
applyAttrs = { |
||||
attr("d", d) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.G( |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = G, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Image( |
||||
href: String, |
||||
attrs: AttrBuilderContext<SVGImageElement>? = null, |
||||
content: ContentBuilder<SVGImageElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Image, |
||||
applyAttrs = { |
||||
attr("href", href) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Mask( |
||||
id: String? = null, |
||||
attrs: AttrBuilderContext<SVGMaskElement>? = null, |
||||
content: ContentBuilder<SVGMaskElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Mask, |
||||
applyAttrs = { |
||||
id?.let { attr("id", it) } |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Defs( |
||||
attrs: AttrBuilderContext<SVGDefsElement>? = null, |
||||
content: ContentBuilder<SVGDefsElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Defs, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Pattern( |
||||
id: String, |
||||
attrs: AttrBuilderContext<SVGPatternElement>? = null, |
||||
content: ContentBuilder<SVGPatternElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Pattern, |
||||
applyAttrs = { |
||||
attr("id", id) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Polygon( |
||||
vararg points: Number, |
||||
attrs: AttrBuilderContext<SVGPolygonElement>? = null, |
||||
content: ContentBuilder<SVGPolygonElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Polygon, |
||||
applyAttrs = { |
||||
attr("points", points.toList().chunked(2).joinToString(" ") { it.joinToString(",") }) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Polyline( |
||||
vararg points: Number, |
||||
attrs: AttrBuilderContext<SVGPolylineElement>? = null, |
||||
content: ContentBuilder<SVGPolylineElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Polyline, |
||||
applyAttrs = { |
||||
attr("points", points.toList().chunked(2).joinToString(" ") { it.joinToString(",") }) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.TextPath( |
||||
href: String, |
||||
text: String, |
||||
attrs: AttrBuilderContext<SVGTextPathElement>? = null, |
||||
) { |
||||
TagElement( |
||||
elementBuilder = TextPath, |
||||
applyAttrs = { |
||||
attr("href", href) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = { |
||||
Text(text) |
||||
} |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Animate( |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Animate, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.AnimateMotion( |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = AnimateMotion, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.AnimateTransform( |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = AnimateTransform, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.LinearGradient( |
||||
id: String? = null, |
||||
attrs: AttrBuilderContext<SVGLinearGradientElement>? = null, |
||||
content: ContentBuilder<SVGLinearGradientElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = LinearGradient, |
||||
applyAttrs = { |
||||
id?.let { attr("id", it) } |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.RadialGradient( |
||||
id: String? = null, |
||||
attrs: AttrBuilderContext<SVGRadialGradientElement>? = null, |
||||
content: ContentBuilder<SVGRadialGradientElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = RadialGradient, |
||||
applyAttrs = { |
||||
id?.let { attr("id", it) } |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Stop( |
||||
attrs: AttrBuilderContext<SVGStopElement>? = null, |
||||
content: ContentBuilder<SVGStopElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Stop, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Switch( |
||||
attrs: AttrBuilderContext<SVGSwitchElement>? = null, |
||||
content: ContentBuilder<SVGSwitchElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Switch, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Title( |
||||
text: String, |
||||
attrs: AttrBuilderContext<SVGTitleElement>? = null, |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Title, |
||||
applyAttrs = attrs, |
||||
content = { |
||||
Text(text) |
||||
} |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Tspan( |
||||
attrs: AttrBuilderContext<SVGTSpanElement>? = null, |
||||
content: ContentBuilder<SVGTSpanElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Tspan, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Desc( |
||||
content: String, |
||||
attrs: AttrBuilderContext<SVGDescElement>? = null, |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Desc, |
||||
applyAttrs = attrs, |
||||
content = { |
||||
Text(content) |
||||
} |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Marker( |
||||
attrs: AttrBuilderContext<SVGMarkerElement>? = null, |
||||
content: ContentBuilder<SVGMarkerElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Marker, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Mpath( |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Mpath, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Filter( |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Filter, |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun ElementScope<SVGElement>.Set( |
||||
attributeName: String, |
||||
to: String, |
||||
attrs: AttrBuilderContext<SVGElement>? = null, |
||||
content: ContentBuilder<SVGElement>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = Set, |
||||
applyAttrs = { |
||||
attr("attributeName", attributeName) |
||||
attr("to", to) |
||||
attrs?.invoke(this) |
||||
}, |
||||
content = content |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
@ExperimentalComposeWebSvgApi |
||||
fun <T : SVGElement> SvgElement( |
||||
name: String, |
||||
attrs: AttrBuilderContext<T>? = null, |
||||
content: ContentBuilder<T>? = null |
||||
) { |
||||
TagElement( |
||||
elementBuilder = ElementBuilderNS(name, SVG_NS), |
||||
applyAttrs = attrs, |
||||
content = content |
||||
) |
||||
} |
@ -0,0 +1,453 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.web.core.tests.svg |
||||
|
||||
import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi |
||||
import org.jetbrains.compose.web.css.opacity |
||||
import org.jetbrains.compose.web.css.percent |
||||
import org.jetbrains.compose.web.css.px |
||||
import org.jetbrains.compose.web.svg.* |
||||
import org.jetbrains.compose.web.testutils.* |
||||
import org.w3c.dom.svg.SVGCircleElement |
||||
import kotlin.test.Test |
||||
import kotlin.test.assertEquals |
||||
|
||||
@ExperimentalComposeWebSvgApi |
||||
class SvgTests { |
||||
@Test |
||||
fun nodeNames() = runTest { |
||||
composition { |
||||
Svg { |
||||
Animate() |
||||
AnimateMotion() |
||||
AnimateTransform() |
||||
Defs() |
||||
Filter() |
||||
G() |
||||
Marker() |
||||
Mpath() |
||||
Switch() |
||||
Tspan() |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><animate></animate><animateMotion></animateMotion><animateTransform></animateTransform><defs></defs><filter></filter><g></g><marker></marker><mpath></mpath><switch></switch><tspan></tspan></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun clipPathTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
ClipPath("myClip") { |
||||
Circle(40.px, 35.px, 36.px) |
||||
} |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><clipPath id=\"myClip\"><circle cx=\"40px\" cy=\"35px\" r=\"36px\"></circle></clipPath></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun maskTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Mask("myMask") |
||||
} |
||||
} |
||||
|
||||
assertEquals("<svg><mask id=\"myMask\"></mask></svg>", nextChild<SVGCircleElement>().outerHTML) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun svgATest() = runTest { |
||||
composition { |
||||
Svg { |
||||
SvgA("/docs/Web/SVG/Element/circle", { |
||||
attr("target", "_blank") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><a href=\"/docs/Web/SVG/Element/circle\" target=\"_blank\"></a></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun descTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Desc("some description", { attr("id", "myDesc") }) |
||||
} |
||||
} |
||||
|
||||
assertEquals("<svg><desc id=\"myDesc\">some description</desc></svg>", nextChild<SVGCircleElement>().outerHTML) |
||||
} |
||||
|
||||
@Test |
||||
fun setTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Rect(0, 0, 10, 10, { attr("id", "rect") }) { |
||||
Set(attributeName = "class", to = "round", { attr("begin", "me.click"); attr("dur", "2s") }) |
||||
} |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><rect x=\"0\" y=\"0\" width=\"10\" height=\"10\" id=\"rect\"><set attributeName=\"class\" to=\"round\" begin=\"me.click\" dur=\"2s\"></set></rect></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun titleTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Rect(10, 20, 30, 30) { |
||||
Title("some title") |
||||
} |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><rect x=\"10\" y=\"20\" width=\"30\" height=\"30\"><title>some title</title></rect></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun svgTextTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
SvgText("some text", 20, 30, { |
||||
classes("small") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><text x=\"20\" y=\"30\" class=\"small\">some text</text></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun textPathTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
TextPath("#someHref", "Some text") |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><textPath href=\"#someHref\">Some text</textPath></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun ellipseTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Ellipse(50, 60, 70, 20, { |
||||
attr("color", "yellow") |
||||
}) |
||||
Ellipse(50.px, 60.px, 70.percent, 20.px, { |
||||
attr("color", "red") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><ellipse cx=\"50\" cy=\"60\" rx=\"70\" ry=\"20\" color=\"yellow\"></ellipse><ellipse cx=\"50px\" cy=\"60px\" rx=\"70%\" ry=\"20px\" color=\"red\"></ellipse></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun circleTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Circle(50, 60, 70, { |
||||
attr("color", "red") |
||||
}) |
||||
Circle(50.px, 60.px, 70.percent, { |
||||
attr("color", "red") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><circle cx=\"50\" cy=\"60\" r=\"70\" color=\"red\"></circle><circle cx=\"50px\" cy=\"60px\" r=\"70%\" color=\"red\"></circle></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun rectTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Rect(0, 20, 100, 200, { |
||||
attr("color", "red") |
||||
}) |
||||
Rect(0.px, 20.px, 100.px, 200.px, { |
||||
attr("color", "red") |
||||
}) |
||||
|
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><rect x=\"0\" y=\"20\" width=\"100\" height=\"200\" color=\"red\"></rect><rect x=\"0px\" y=\"20px\" width=\"100px\" height=\"200px\" color=\"red\"></rect></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun imageTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Image("/image.png", { |
||||
attr("preserveAspectRatio", "xMidYMid meet") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><image href=\"/image.png\" preserveAspectRatio=\"xMidYMid meet\"></image></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun lineTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Line(0, 80, 100, 20, { |
||||
attr("stroke", "red") |
||||
}) |
||||
Line(0.px, 80.px, 100.px, 20.px, { |
||||
attr("stroke", "black") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><line x1=\"0\" y1=\"80\" x2=\"100\" y2=\"20\" stroke=\"red\"></line><line x1=\"0px\" y1=\"80px\" x2=\"100px\" y2=\"20px\" stroke=\"black\"></line></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun polylineTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Polyline(0, 100, 50, 25, 50, 75, 100, 0, attrs = { |
||||
attr("stroke", "red") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><polyline points=\"0,100 50,25 50,75 100,0\" stroke=\"red\"></polyline></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun polygonTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Polygon(0, 100, 50, 25, 50, 75, 100, 0, attrs = { |
||||
attr("stroke", "red") |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><polygon points=\"0,100 50,25 50,75 100,0\" stroke=\"red\"></polygon></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun linearGradientTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
LinearGradient("myGradient") { |
||||
Stop({ |
||||
attr("offset", 10.percent.toString()) |
||||
attr("stop-color", "gold") |
||||
}) |
||||
Stop({ |
||||
attr("offset", 95.percent.toString()) |
||||
attr("stop-color", "red") |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><linearGradient id=\"myGradient\"><stop offset=\"10%\" stop-color=\"gold\"></stop><stop offset=\"95%\" stop-color=\"red\"></stop></linearGradient></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun radialGradientTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
RadialGradient("myGradient") { |
||||
Stop({ |
||||
attr("offset", 10.percent.toString()) |
||||
attr("stop-color", "gold") |
||||
}) |
||||
Stop({ |
||||
attr("offset", 95.percent.toString()) |
||||
attr("stop-color", "red") |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><radialGradient id=\"myGradient\"><stop offset=\"10%\" stop-color=\"gold\"></stop><stop offset=\"95%\" stop-color=\"red\"></stop></radialGradient></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun patternTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Pattern("something") { |
||||
Polygon(0, 100, 50, 25, 50, 75, 100, 0, attrs = { |
||||
attr("stroke", "red") |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><pattern id=\"something\"><polygon points=\"0,100 50,25 50,75 100,0\" stroke=\"red\"></polygon></pattern></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun viewTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
View("one", "0 0 100 100") |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><view id=\"one\" viewBox=\"0 0 100 100\"></view></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun pathTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Path( |
||||
""" |
||||
M 10,30 |
||||
A 20,20 0,0,1 50,30 |
||||
A 20,20 0,0,1 90,30 |
||||
Q 90,60 50,90 |
||||
Q 10,60 10,30 z |
||||
""".trimIndent() |
||||
) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><path d=\"M 10,30\n" + |
||||
"A 20,20 0,0,1 50,30\n" + |
||||
"A 20,20 0,0,1 90,30\n" + |
||||
"Q 90,60 50,90\n" + |
||||
"Q 10,60 10,30 z\"></path></svg>", nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun useTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
Symbol("myDot", { |
||||
attr("width", "10") |
||||
attr("height", "10") |
||||
attr("viewBox", "0 0 2 2") |
||||
}) { |
||||
Circle(1.px, 1.px, 1.px) |
||||
} |
||||
|
||||
Use("myDot", { |
||||
attr("x", "5") |
||||
attr("y", "5") |
||||
style { |
||||
opacity(1) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><symbol id=\"myDot\" width=\"10\" height=\"10\" viewBox=\"0 0 2 2\"><circle cx=\"1px\" cy=\"1px\" r=\"1px\"></circle></symbol><use href=\"myDot\" x=\"5\" y=\"5\" style=\"opacity: 1;\"></use></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun svgElementTest() = runTest { |
||||
composition { |
||||
Svg { |
||||
SvgElement<SVGCircleElement>("circle", { |
||||
attr("cx", 12.px.toString()) |
||||
attr("cy", 22.px.toString()) |
||||
attr("r", 5.percent.toString()) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg><circle cx=\"12px\" cy=\"22px\" r=\"5%\"></circle></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun svgElementWithViewBoxTest() = runTest { |
||||
composition { |
||||
Svg(viewBox = "0 0 200 200") |
||||
} |
||||
|
||||
assertEquals( |
||||
"<svg viewBox=\"0 0 200 200\"></svg>", |
||||
nextChild<SVGCircleElement>().outerHTML |
||||
) |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue