Skip to content

Commit

Permalink
✨ Added support for drag and drop
Browse files Browse the repository at this point in the history
For UList, TabContainer and Tree.
  • Loading branch information
misherpal authored and sherpal committed Aug 7, 2024
1 parent 87dd523 commit 3898ed9
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 18 deletions.
33 changes: 33 additions & 0 deletions demo/src/main/scala/demo/ListExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import be.doeraene.webcomponents.ui5.*
import be.doeraene.webcomponents.ui5.configkeys.*
import com.raquo.laminar.api.L.*
import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub}
import demo.helpers.MTG
import be.doeraene.webcomponents.ui5.eventtypes.MoveEventDetail

object ListExample extends Example("List") {

Expand Down Expand Up @@ -212,6 +214,37 @@ object ListExample extends Example("List") {
Toast(_.showFromTextEvents(countryDeleteBus.events.map(country => s"Should delete $country")))
)
//-- End
},
DemoPanel("Example of drag and drop (since 2.0.0)") {
//-- Begin: Example of drag and drop (since 2.0.0)
case class CardWithPlacement(card: MTG.Card, index: Int)
val cardsVar = Var(MTG.cards)

val moveBus: EventBus[MoveEventDetail[UList.item.Ref]] = new EventBus
val moveHandler = moveBus.events.withCurrentValueOf(cardsVar.signal).map { (eventDetail, cards) =>
val sourceIndex = eventDetail.source.element.dataset("index").toInt
val destinationIndexShift = if eventDetail.destination.placement == "Before" then 0 else 1
val destinationIndex = eventDetail.destination.element.dataset("index").toInt + destinationIndexShift

val cardsSourceRemoved = cards.patch(sourceIndex, Nil, 1)
val cardsAfterChange =
if destinationIndex < sourceIndex then cardsSourceRemoved.patch(destinationIndex, List(cards(sourceIndex)), 0)
else cardsSourceRemoved.patch(destinationIndex - 1, List(cards(sourceIndex)), 0)
cardsAfterChange
}

UList(
_.headerText := "You can change items order!",
children <-- cardsVar.signal
.map(_.zipWithIndex.map(CardWithPlacement(_, _)))
.map(_.map { card =>
UList.item(_.movable := true, dataAttr("index") := card.index.toString, card.card.name)
}),
_.events.onMoveOver.preventDefault --> Observer.empty,
_.events.onMove.map(_.detail) --> moveBus.writer,
moveHandler --> cardsVar.writer
)
//-- End
}
)

Expand Down
40 changes: 39 additions & 1 deletion demo/src/main/scala/demo/TabContainerExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import be.doeraene.webcomponents.ui5.*
import be.doeraene.webcomponents.ui5.configkeys.*
import com.raquo.laminar.api.L.*
import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub}
import demo.helpers.MTG
import be.doeraene.webcomponents.ui5.eventtypes.MoveEventDetail
import org.scalajs.dom

object TabContainerExample extends Example("TabContainer") {

Expand Down Expand Up @@ -127,7 +130,42 @@ object TabContainerExample extends Example("TabContainer") {
)
)
//-- End
)
),
DemoPanel("Tabs with Drag and Drop (since 2.0)") {
//-- Begin: Tabs with Drag and Drop (since 2.0)
case class TabWithPlacement(text: String, index: Int)
val tabsVar = Var(Vector("tab1", "tab2", "tab3", "tab4"))

val moveBus: EventBus[MoveEventDetail[dom.html.Element]] = new EventBus
val moveHandler = moveBus.events.withCurrentValueOf(tabsVar.signal).map { (eventDetail, tabs) =>
val sourceIndex = eventDetail.source.element.dataset("index").toInt
val destinationIndexShift = if eventDetail.destination.placement == "Before" then 0 else 1
val destinationIndex = eventDetail.destination.element.dataset("index").toInt + destinationIndexShift

val tabsSourceRemoved = tabs.patch(sourceIndex, Nil, 1)
val tabsAfterChange =
if destinationIndex < sourceIndex then tabsSourceRemoved.patch(destinationIndex, List(tabs(sourceIndex)), 0)
else tabsSourceRemoved.patch(destinationIndex - 1, List(tabs(sourceIndex)), 0)
tabsAfterChange
}

TabContainer(
children <-- tabsVar.signal
.map(_.zipWithIndex.map(TabWithPlacement(_, _)))
.map(_.map { tab =>
Tab(
_.movable := true,
dataAttr("index") := tab.index.toString,
_.text := tab.text,
s"This is the content for tab ${tab.text}"
)
}),
_.events.onMoveOver.preventDefault --> Observer.empty,
_.events.onMove.map(_.detail) --> moveBus.writer,
moveHandler --> tabsVar.writer
)
//-- End
}
)

}
11 changes: 10 additions & 1 deletion demo/src/main/scala/demo/TreeExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,16 @@ object TreeExample extends Example("Tree") {
)
)
//-- End
)
),
DemoPanel("Tree with Drag and Drop (since 2.0)") {
//-- Begin: Tree with Drag and Drop (since 2.0)
MessageStrip(
"Looking for an example with Drag and Drop? Check out examples for tabs or lists, they are similar (you can also contribute to make one here!)",
_.design := MessageStripDesign.Information,
_.hideCloseButton := true
)
//-- End
}
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ object ListItem extends WebComponent with HasIcon with HasDescription with HasAd
lazy val tpe: HtmlAttr[ListItemType] = ListItemType.asHtmlAttr("type")
lazy val selected: HtmlAttr[Boolean] = htmlAttr("selected", BooleanAsAttrPresenceCodec)
lazy val highlight: HtmlAttr[ValueState] = ValueState.asHtmlAttr("highlight")
lazy val movable: HtmlAttr[Boolean] = htmlAttr("movable", BooleanAsAttrPresenceCodec)

@scala.annotation.compileTimeOnly("The image property has been replaced by the image slot.")
def image: HtmlAttr[String] = ???
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import be.doeraene.webcomponents.WebComponent

/** Element contained in a [[TabContainer]].
*
* @see
* <a href="https://sap.github.io/ui5-webcomponents/playground/components/TabContainer/">the doc</a> for more
* information.
/** The ui5-tab represents a selectable item inside a ui5-tabcontainer. It defines both the item in the tab strip (top
* part of the ui5-tabcontainer) and the content that is presented to the user once the tab is selected.
*/
object Tab extends WebComponent with HasIcon with HasText {

Expand All @@ -44,12 +41,11 @@ object Tab extends WebComponent with HasIcon with HasText {

protected val tag: CustomHtmlTag[Ref] = CustomHtmlTag("ui5-tab")

lazy val disabled: HtmlAttr[Boolean] = htmlAttr("disabled", BooleanAsAttrPresenceCodec)
lazy val selected: HtmlAttr[Boolean] = htmlAttr("selected", BooleanAsAttrPresenceCodec)

lazy val disabled: HtmlAttr[Boolean] = htmlAttr("disabled", BooleanAsAttrPresenceCodec)
lazy val selected: HtmlAttr[Boolean] = htmlAttr("selected", BooleanAsAttrPresenceCodec)
lazy val design: HtmlAttr[SemanticColour] = htmlAttr("design", SemanticColour.AsStringCodec)

lazy val additionalText: HtmlAttr[String] = htmlAttr("additional-text", StringAsIsCodec)
lazy val movable: HtmlAttr[Boolean] = htmlAttr("movable", BooleanAsAttrPresenceCodec)

object slots {
@deprecated("subTabs Tab slot has been renamed to items")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import org.scalajs.dom
import scala.scalajs.js
import scala.scalajs.js.annotation.{JSImport, JSName}
import be.doeraene.webcomponents.WebComponent
import be.doeraene.webcomponents.ui5.eventtypes.MoveEventDetail
import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget

/** Tab container
*
Expand Down Expand Up @@ -71,7 +73,14 @@ object TabContainer extends WebComponent {
def tabIndex: Int = js.native
}

val onTabSelect: EventProp[dom.Event & HasDetail[TabSelectDetail]] = new EventProp("tab-select")
val onTabSelect: EventProp[EventWithPreciseTarget[Ref] & HasDetail[TabSelectDetail]] = new EventProp("tab-select")

lazy val onMove: EventProp[EventWithPreciseTarget[Ref] & HasDetail[MoveEventDetail[tab.Ref]]] = new EventProp(
"move"
)
lazy val onMoveOver: EventProp[EventWithPreciseTarget[Ref] & HasDetail[MoveEventDetail[tab.Ref]]] = new EventProp(
"move-over"
)
}

def tab: Tab.type = Tab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget
import be.doeraene.webcomponents.ui5.eventtypes.HasDetail
import be.doeraene.webcomponents.ui5.eventtypes.HasItem
import be.doeraene.webcomponents.WebComponent
import be.doeraene.webcomponents.ui5.eventtypes.MoveEventDetail

/** The ui5-tree component provides a tree structure for displaying data in a hierarchy.
*
Expand Down Expand Up @@ -83,6 +84,12 @@ object Tree extends WebComponent with HasAccessibleName {
val onSelectionChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[SelectionChangeDetail]] = new EventProp(
"selection-change"
)
lazy val onMove: EventProp[EventWithPreciseTarget[Ref] & HasDetail[MoveEventDetail[item.Ref]]] = new EventProp(
"move"
)
lazy val onMoveOver: EventProp[EventWithPreciseTarget[Ref] & HasDetail[MoveEventDetail[item.Ref]]] = new EventProp(
"move-over"
)
}

object slots {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ object TreeItem extends WebComponent with HasIcon with HasText {

protected val tag: CustomHtmlTag[Ref] = CustomHtmlTag("ui5-tree-item")

lazy val expanded: HtmlAttr[Boolean] = htmlAttr[Boolean]("expanded", BooleanAsAttrPresenceCodec)
lazy val hasChildren: HtmlAttr[Boolean] = htmlAttr[Boolean]("has-children", BooleanAsAttrPresenceCodec)
lazy val intermediate: HtmlAttr[Boolean] = htmlAttr[Boolean]("intermediate", BooleanAsAttrPresenceCodec)
lazy val selected: HtmlAttr[Boolean] = htmlAttr[Boolean]("selected", BooleanAsAttrPresenceCodec)
lazy val expanded: HtmlAttr[Boolean] = htmlAttr("expanded", BooleanAsAttrPresenceCodec)
lazy val hasChildren: HtmlAttr[Boolean] = htmlAttr("has-children", BooleanAsAttrPresenceCodec)
lazy val intermediate: HtmlAttr[Boolean] = htmlAttr("intermediate", BooleanAsAttrPresenceCodec)
lazy val selected: HtmlAttr[Boolean] = htmlAttr("selected", BooleanAsAttrPresenceCodec)
lazy val highlight: HtmlAttr[ValueState] = ValueState.asHtmlAttr("highlight")
lazy val tooltip: HtmlAttr[String] = htmlAttr("tooltip", StringAsIsCodec)
lazy val navigated: HtmlAttr[Boolean] = htmlAttr[Boolean]("navigated", BooleanAsAttrPresenceCodec)
lazy val navigated: HtmlAttr[Boolean] = htmlAttr("navigated", BooleanAsAttrPresenceCodec)
lazy val movable: HtmlAttr[Boolean] = htmlAttr("movable", BooleanAsAttrPresenceCodec)

object slots {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@ import be.doeraene.webcomponents.WebComponent
object UList extends WebComponent with HasAccessibleName {

@js.native
trait RawElement extends js.Object {}
trait RawElement extends js.Object {
@JSName("listItems")
def listItemsJS: js.Array[item.Ref] = js.native
}

object RawElement extends js.Object {
extension (rawElement: RawElement) {
def listItems: List[item.Ref] = rawElement.listItemsJS.toList
}
}

@js.native
@JSImport("@ui5/webcomponents/dist/List.js", JSImport.Default)
Expand Down Expand Up @@ -85,6 +94,13 @@ object UList extends WebComponent with HasAccessibleName {

val onSelectionChange =
new EventProp[EventWithPreciseTarget[Ref] & HasDetail[SelectionChangeDetail]]("selection-change")

lazy val onMove: EventProp[EventWithPreciseTarget[Ref] & HasDetail[MoveEventDetail[item.Ref]]] = new EventProp(
"move"
)
lazy val onMoveOver: EventProp[EventWithPreciseTarget[Ref] & HasDetail[MoveEventDetail[item.Ref]]] = new EventProp(
"move-over"
)
}

object slots {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package be.doeraene.webcomponents.ui5.eventtypes

import scala.scalajs.js
import org.scalajs.dom

trait MoveEventDetail[+ElemType <: dom.html.Element] extends js.Object {
def source: MoveEventDetail.HasElement[ElemType]
def destination: MoveEventDetail.HasElement[ElemType] & MoveEventDetail.HasPlacement

}

object MoveEventDetail {
trait HasElement[+ElemType <: dom.html.Element] extends js.Object {
def element: ElemType
}

trait HasPlacement extends js.Object {
def placement: "Before" | "After" | "On"
}
}

0 comments on commit 3898ed9

Please sign in to comment.