Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions airframe-rx-html/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# airframe-rx-html

A reactive HTML/SVG rendering library for Scala.

## Import Options

### Separate Imports (Traditional approach)

When using HTML and SVG separately, you can import them individually:

```scala
import wvlet.airframe.rx.html.all.* // HTML tags and attributes
import wvlet.airframe.rx.html.svgTags.* // SVG tags
import wvlet.airframe.rx.html.svgAttrs.* // SVG attributes
```

### Unified Import (New approach)

To use both HTML and SVG together without naming conflicts:

```scala
import wvlet.airframe.rx.html.unifiedAll.*
```

## Handling Naming Conflicts

The unified import resolves naming conflicts between HTML and SVG by prefixing SVG versions with `svg`:

### Simple Attribute Conflicts
These attributes exist in both HTML and SVG namespaces:

| Attribute | HTML Version | SVG Version |
|-----------|--------------|-------------|
| class | `class` or `cls` | `svgClass` |
| style | `style` | `svgStyle` |
| type | `type` | `svgType` |
| xmlns | `xmlns` | `svgXmlns` |
| max | `max` | `svgMax` |
| min | `min` | `svgMin` |
| height | `height` | `svgHeight` |
| width | `width` | `svgWidth` |
| id | `id` | `svgId` |

### Tag/Attribute Conflicts
These names exist as both tags and attributes:

| Name | HTML Attribute | SVG Tag | SVG Attribute |
|------|----------------|---------|---------------|
| title | `title` | `svgTitle` | N/A |
| pattern | `pattern` | `svgPattern` | N/A |
| filter | N/A | `svgFilter` | `svgFilterAttr` |
| mask | N/A | `svgMask` | `svgMaskAttr` |
| clipPath | N/A | `svgClipPath` | `svgClipPathAttr` |

## Examples

### Using Unified Import

```scala
import wvlet.airframe.rx.html.unifiedAll.*

val mixedContent = div(
cls -> "container", // HTML class attribute
style -> "padding: 20px;", // HTML style attribute
h1("Data Visualization"),
svg(
svgWidth -> "400", // SVG width attribute
svgHeight -> "300", // SVG height attribute
svgClass -> "chart", // SVG class attribute
svgStyle -> "border: 1px solid #ccc;", // SVG style attribute
g(
transform -> "translate(50, 50)",
rect(
x -> "0",
y -> "0",
svgWidth -> "300",
svgHeight -> "200",
fill -> "steelblue"
),
text(
x -> "150",
y -> "100",
textAnchor -> "middle",
svgStyle -> "fill: white; font-size: 24px;",
"Chart Title"
)
)
)
)
```

### Traditional Separate Imports

If you prefer to keep HTML and SVG separate:

```scala
import wvlet.airframe.rx.html.all.*

val htmlContent = div(
cls -> "container",
style -> "padding: 20px;",
h1("Hello World")
)
```

```scala
import wvlet.airframe.rx.html.svgTags.*
import wvlet.airframe.rx.html.svgAttrs.*

val svgContent = svg(
width -> "100",
height -> "100",
circle(
cx -> "50",
cy -> "50",
r -> "40",
fill -> "red"
)
)
```

## Namespace Handling

All SVG attributes and tags are automatically created with the correct SVG namespace (`http://www.w3.org/2000/svg`), ensuring proper rendering in browsers.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package wvlet.airframe.rx.html
trait HtmlSvgAttrs {
import HtmlTags.*

// private def attr(name: String): HtmlAttributeOf = html.attr(name, Namespace.svg)
private def attr(name: String): HtmlAttributeOf = new HtmlAttributeOf(name, Namespace.svg)

/**
* This attribute defines the distance from the origin to the top of accent characters, measured by a distance within
Expand Down Expand Up @@ -521,16 +521,14 @@ trait HtmlSvgAttrs {
/**
* MDN
*/
// Conflicts with Attr.height
// lazy val height = attr("height")
lazy val height = attr("height")

/**
* MDN
*/
lazy val imageRendering = attr("imageRendering")

// id is already available in Attrs
// lazy val id = attr("id")
lazy val id = attr("id")

/**
* MDN
Expand Down Expand Up @@ -998,8 +996,7 @@ trait HtmlSvgAttrs {
*
* MDN
*/
// width conflicts with Attr.width
// lazy val width = attr("width")
lazy val width = attr("width")

/*
*
Expand Down Expand Up @@ -1048,7 +1045,7 @@ trait HtmlSvgAttrs {
*
* MDN
*/
lazy val xLinkHref = attr("xlink:href", namespace = Namespace.svgXLink)
lazy val xLinkHref = new HtmlAttributeOf("xlink:href", Namespace.svgXLink)

/*
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package wvlet.airframe.rx.html

/**
* Unified tags and attributes that resolves naming conflicts between HTML and SVG.
* For conflicting names, SVG versions are prefixed with 'svg'.
*
* Due to complex inheritance conflicts where some names exist as both tags and attributes,
* we selectively import and re-export members rather than extending all traits.
*/
trait UnifiedTags extends HtmlTags with HtmlAttrs with RxEmbedding {
// Import SVG tags except the conflicting ones
export svgTags.{
title => _, pattern => _, filter => _, mask => _, clipPath => _,
*
}

// Import SVG attributes except the conflicting ones
export svgAttrs.{
`class` => _, style => _, `type` => _, xmlns => _, max => _, min => _,
height => _, width => _, id => _, filter => _, mask => _, clipPath => _,
*
}

// SVG attributes with prefixed names for conflicts
lazy val svgClass = new HtmlAttributeOf("class", Namespace.svg)
lazy val svgStyle = new HtmlAttributeOf("style", Namespace.svg)
lazy val svgType = new HtmlAttributeOf("type", Namespace.svg)
lazy val svgXmlns = new HtmlAttributeOf("xmlns", Namespace.svg)
lazy val svgMax = new HtmlAttributeOf("max", Namespace.svg)
lazy val svgMin = new HtmlAttributeOf("min", Namespace.svg)
lazy val svgHeight = new HtmlAttributeOf("height", Namespace.svg)
lazy val svgWidth = new HtmlAttributeOf("width", Namespace.svg)
lazy val svgId = new HtmlAttributeOf("id", Namespace.svg)

// SVG tags/attributes that conflict with HTML attributes
lazy val svgTitle = svgTags.title // SVG title tag
lazy val svgPattern = svgTags.pattern // SVG pattern tag
lazy val svgFilter = svgTags.filter // SVG filter tag
lazy val svgMask = svgTags.mask // SVG mask tag
lazy val svgClipPath = svgTags.clipPath // SVG clipPath tag

// SVG attributes for the conflicting tag names
lazy val svgFilterAttr = new HtmlAttributeOf("filter", Namespace.svg)
lazy val svgMaskAttr = new HtmlAttributeOf("mask", Namespace.svg)
lazy val svgClipPathAttr = new HtmlAttributeOf("clip-path", Namespace.svg)
}

// Object moved to syntaxes.scala

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comment // Object moved to syntaxes.scala appears to be a leftover from development. It can be removed to improve the clarity of this new file.

Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ object all extends HtmlTags with HtmlAttrs with RxEmbedding
object svgTags extends HtmlSvgTags

object svgAttrs extends HtmlSvgAttrs

// Unified import that includes both HTML and SVG with conflict resolution
// For conflicting names, SVG versions are prefixed with 'svg' (e.g., svgStyle, svgClass)
object unifiedAll extends UnifiedTags
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,27 @@ class HtmlTest extends AirSpec {
option(value -> "banana")
)
}

test("unified import for HTML and SVG") {
import unifiedAll.*

// Can use both HTML and SVG together without conflicts
div(
cls -> "container", // HTML class
style -> "padding: 10px;", // HTML style
h1("HTML and SVG Together"),
svg(
svgWidth -> "100",
svgHeight -> "100",
svgClass -> "my-svg", // SVG class (prefixed)
svgStyle -> "border: 1px solid black;", // SVG style (prefixed)
circle(
cx -> "50",
cy -> "50",
r -> "40",
fill -> "red"
)
)
)
}
}
Loading
Loading