Skip to content

Commit

Permalink
[SimplePrimitives] Add accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
hyazinthh committed Mar 14, 2024
1 parent 4c5ae6f commit aaa5f54
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 154 deletions.
1 change: 1 addition & 0 deletions src/Aardvark.UI.Primitives/Aardvark.UI.Primitives.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<EmbeddedResource Include="resources\spectrum.js" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<EmbeddedResource Include="resources\spectrum-overrides.css" />
<EmbeddedResource Include="resources\notifications.js" />
<EmbeddedResource Include="resources\accordion.js" />
<None Include="paket.references" />
</ItemGroup>
<ItemGroup>
Expand Down
99 changes: 98 additions & 1 deletion src/Aardvark.UI.Primitives/Primitives/SimplePrimitives.fs
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,81 @@ module SimplePrimitives =
)
)

let private accordionImpl (toggle: Option<bool -> int -> 'msg>) (active: Choice<aset<int>, aval<int>>)
(attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
let dependencies =
Html.semui @ [ { name = "accordion"; url = "resources/accordion.js"; kind = Script }]

let attributes =
let basic =
AttributeMap.ofList [
clazz "ui accordion"

if toggle.IsSome then
onEvent "onopen" [] (List.head >> int >> toggle.Value true)
onEvent "onclose" [] (List.head >> int >> toggle.Value false)
]

AttributeMap.union attributes basic

let boot =
let exclusive =
match active with
| Choice2Of2 _ -> "true"
| _ -> "false"

String.concat "" [
"const $self = $('#__ID__');"
"aardvark.accordion($self, " + exclusive + ", channelActive);"
]

let channels =
let channel =
match active with
| Choice1Of2 set -> ASet.channel set
| Choice2Of2 index -> AVal.channel index

[ "channelActive", channel ]

require dependencies (
onBoot' channels boot (
Incremental.div attributes <| alist {
for (title, node) in sections do
div [clazz "title"] [
i [clazz "dropdown icon"] []
text title
]
div [clazz "content"] [
node
]
}
)
)

/// Simple container dividing content into titled sections, which can be opened and closed.
/// The active set holds the indices of the open sections.
/// The toggle (index, isOpen) message is fired when a section is opened or closed.
let accordion (toggle: int * bool -> 'msg) (active: aset<int>)
(attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
sections |> accordionImpl (Some (fun s i -> toggle (i, s))) (Choice1Of2 active) attributes

/// Simple container dividing content into titled sections, which can be opened and closed (only one can be open at a time).
/// The active value holds the index of the open section, or -1 if there is no open section.
/// The setActive (index | -1) message is fired when a section is opened or closed.
let accordionExclusive (setActive: int -> 'msg) (active: aval<int>)
(attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
let map o i = if o then i else -1
sections |> accordionImpl (Some (fun s -> map s >> setActive)) (Choice2Of2 active) attributes

/// Simple container dividing content into titled sections, which can be opened and closed.
/// If exclusive is true, only one section can be open at a time.
let accordionSimple (exclusive: bool) (attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
let active =
if exclusive then Choice2Of2 (AVal.constant -1)
else Choice1Of2 ASet.empty

sections |> accordionImpl None active attributes

[<AutoOpen>]
module ``Primtive Builders`` =

Expand Down Expand Up @@ -883,4 +958,26 @@ module SimplePrimitives =
let inline dropdownMultiSelect (attributes : AttributeMap<'msg>)
(compare : Option<'T -> 'T -> int>) (defaultText : string)
(values : amap<'T, DomNode<'msg>>) (selected : alist<'T>) (update : 'T list -> 'msg) =
Incremental.dropdownMultiSelect attributes compare defaultText values selected update
Incremental.dropdownMultiSelect attributes compare defaultText values selected update

/// Simple container dividing content into titled sections, which can be opened and closed.
/// The active set holds the indices of the open sections.
/// The toggle message (index, isOpen) is fired when a section is opened or closed.
let inline accordion (toggle: int * bool -> 'msg) (active: aset<int>)
(attributes: Attribute<'msg> list) (sections: list<string * DomNode<'msg>>) =
let attributes = AttributeMap.ofList attributes
sections |> AList.ofList |> Incremental.accordion toggle active attributes

/// Simple container dividing content into titled sections, which can be opened and closed (only one can be open at a time).
/// The active value holds the index of the open section, or -1 if there is no open section.
/// The setActive (index | -1) message is fired when a section is opened or closed.
let inline accordionExclusive (setActive: int -> 'msg) (active: aval<int>)
(attributes: Attribute<'msg> list) (sections: list<string * DomNode<'msg>>) =
let attributes = AttributeMap.ofList attributes
sections |> AList.ofList |> Incremental.accordionExclusive setActive active attributes

/// Simple container dividing content into titled sections, which can be opened and closed.
/// If exclusive is true, only one section can be open at a time.
let inline accordionSimple (exclusive: bool) (attributes: Attribute<'msg> list) (sections: list<string * DomNode<'msg>>) =
let attributes = AttributeMap.ofList attributes
sections |> AList.ofList |> Incremental.accordionSimple exclusive attributes
65 changes: 65 additions & 0 deletions src/Aardvark.UI.Primitives/resources/accordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
if (!aardvark.accordion) {
/**
* @param {HTMLElement[]} $self
* @param {HTMLElement} item
* @param {string} event
*/
const onToggle = function ($self, item, event) {
const index = $self.children('.content').index(item);
aardvark.processEvent($self[0].id, event, index);
};

/**
* @param {HTMLElement[]} $self
* @param {{cnt: int, value: int}} op
*/
const processMessage = function ($self, op) {
if (op.value < 0 || op.value >= $self.children('.content').length) {
return;
}

if (op.cnt > 0) {
$self.accordion('open', op.value);
} else if (op.cnt < 0) {
$self.accordion('close', op.value);
}
};

/**
* @param {HTMLElement[]} $self
* @param {int} index
*/
const processExclusiveMessage = function ($self, index) {
if (index >= 0) {
if (index < $self.children('.content').length) {
$self.accordion('open', index);
}
} else {
const $content = $self.children('.content');
const active = $content.index($content.filter('.active'));

if (active >= 0) {
$self.accordion('close', active);
}
}
};

/**
* @param {HTMLElement[]} $self
* @param {boolean} exclusive
* @param {{onmessage: function}} channel
*/
aardvark.accordion = function ($self, exclusive, channel) {
$self.accordion({
exclusive: exclusive,
onOpen: function () { onToggle($self, this, 'onopen'); },
onClose: function () { onToggle($self, this, 'onclose'); }
});

if (exclusive) {
channel.onmessage = (index) => processExclusiveMessage($self, index);
} else {
channel.onmessage = (op) => processMessage($self, op);
}
};
}
Loading

0 comments on commit aaa5f54

Please sign in to comment.