Skip to content

Instantly share code, notes, and snippets.

@JordanMarr
Last active August 2, 2023 07:25
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save JordanMarr/2d478dfecf9e91bba953c649fe2ff458 to your computer and use it in GitHub Desktop.
Save JordanMarr/2d478dfecf9e91bba953c649fe2ff458 to your computer and use it in GitHub Desktop.
Fable bindings for "react-dnd" using HTML5 provider
module DragDropPage
open Feliz
open Fable.React
open Fable.React.Props
open ReactDND
type Language = {
Name: string
}
let draggableLanguage = React.functionComponent(fun (props: {| lang: Language |}) ->
let dragState, drag, preview = ReactDND.useDrag [
DragSpec.Item { ``type`` = "Language"; dragSrc = props.lang }
DragSpec.Collect (fun mon -> { isDragging = mon.isDragging() })
//DragSpec.End (fun dragItem mon -> printf "DragEnd: %A; Mon.DropTarget: %A" dragItem mon)
]
div [
Ref drag
Style[Border "1px solid gray"; Padding "4px"; BackgroundColor (if dragState.isDragging then "yellow" else "white")]
Key props.lang.Name
] [
str props.lang.Name
]
)
let dropTarget = React.functionComponent(fun () ->
let selectedLang, setSelectedLang = React.useState<Language option>(None)
let dropState, drop = ReactDND.useDrop [
DropSpec.Accept "Language"
DropSpec.Collect (fun mon -> { canDrop = mon.canDrop(); isOver = mon.isOver() })
DropSpec.Drop (fun (dragItem: DragItem<Language>) ->
setSelectedLang(Some dragItem.dragSrc)
)
]
div [
Ref drop
Style[BackgroundColor "whitesmoke"; Padding "20px"; BackgroundColor (if dropState.isOver then "lightgreen" else "white")]
] [
selectedLang
|> Option.map (fun l -> sprintf "You selected: %s" l.Name)
|> Option.defaultValue "Drag your .net language of choice here"
|> str
]
)
let page = React.functionComponent(fun () ->
let languages, setLanguages = React.useState<Language list>([])
React.useEffectOnce(fun () ->
[ "C#"; "F#"; "VB" ]
|> List.map (fun l -> { Name = l })
|> setLanguages
)
div [Style[Width "300px"; MarginLeft "auto"; MarginRight "auto"]] [
//ReactDND.dndProviderHtml5 [] [
ReactDND.dndProvider [DndProviderProps.Backend html5Backend] [
dropTarget()
[ "F#"; "C#"; "VB" ]
|> List.map (fun l -> { Name = l })
|> List.map (fun lang -> draggableLanguage {| lang = lang |})
|> ofList
]
]
)
/// Custom bindings for "react-dnd"; authored by jmarr on Feb 2020.
/// This expects that "react-dnd" and "react-dnd-html5-backend" are both installed.
module ReactDND
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
open Fable.React.Props
open System.Text.RegularExpressions
let private kvl xs = JsInterop.keyValueList CaseRules.LowerFirst xs
type CollectedDragProps = {
isDragging: bool
}
type CollectedDropProps = {
isOver: bool
canDrop: bool
}
type [<AllowNullLiteral>] IDragMonitor =
abstract member isDragging: unit -> bool
type [<AllowNullLiteral>] IDropMonitor =
abstract member isOver: unit -> bool
abstract member canDrop: unit -> bool
abstract member getDropResult: unit -> obj
//
// Provider / Context
//
[<Import("default", from="react-dnd-html5-backend")>]
let html5Backend: obj = jsNative
type DndProviderProps =
| Backend of obj
let dndProvider (props: DndProviderProps list) = ofImport "DndProvider" "react-dnd" (kvl props)
/// Provides a Drag and Drop context using the "react-dnd-html-backend" provider.
let dndProviderHtml5 (props: DndProviderProps list) = dndProvider [DndProviderProps.Backend html5Backend]
//
// UseDrag
//
[<RequireQualifiedAccess>]
type DragSpec<'DragSrc> =
/// Specifies info about the drag source object and type (required).
| Item of DragItem<'DragSrc>
/// An optional handler to gather state.
| Collect of (IDragMonitor -> CollectedDragProps)
/// Provides an optional handler if actions are necessary at the end of a drag operation.
| End of (DragItem<'DragSrc> -> IDragMonitor -> unit)
and DragItem<'DragSrc> = {
``type``: string
dragSrc: 'DragSrc
}
let private useDragImpl: spec: obj -> collectedProps: CollectedDragProps * drag: (Browser.Types.Element -> unit) * preview: obj = import "useDrag" "react-dnd"
let useDrag<'TItem>(spec: DragSpec<'TItem> list) = useDragImpl (kvl spec)
//
// UseDrop
//
[<RequireQualifiedAccess>]
type DropSpec<'DragSrc, 'DropTgt> =
/// Specifies which kinds of drag item types are accepted (required).
| Accept of string
/// An optional handler to gather state.
| Collect of (IDropMonitor -> CollectedDropProps)
/// Provides an optional handler for the drop event.
| Drop of (DragItem<'DragSrc> -> 'DropTgt)
let private useDropImpl: spec: obj -> collectedProps: CollectedDropProps * drop: (Browser.Types.Element -> unit) = import "useDrop" "react-dnd"
let useDrop (spec: DropSpec<'DragSrc, 'DragTgt> list) = useDropImpl (kvl spec)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment