From b2e0de05427bd8e9ee114776d0ae650201e29c91 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 18 Mar 2024 14:50:59 +0100 Subject: [PATCH] [SimplePrimitives] Fix Incremental.accordion Accordion with dynamic content list is weird and broken, but titles of sections must be adaptive. --- .../Primitives/SimplePrimitives.fs | 91 ++++++++++++------- .../resources/accordion.js | 53 +++++------ 2 files changed, 79 insertions(+), 65 deletions(-) diff --git a/src/Aardvark.UI.Primitives/Primitives/SimplePrimitives.fs b/src/Aardvark.UI.Primitives/Primitives/SimplePrimitives.fs index 3aa97014..1d3733ae 100644 --- a/src/Aardvark.UI.Primitives/Primitives/SimplePrimitives.fs +++ b/src/Aardvark.UI.Primitives/Primitives/SimplePrimitives.fs @@ -619,8 +619,23 @@ module SimplePrimitives = ) ) - let private accordionImpl (toggle: Option int -> 'msg>) (active: Choice, aval>) - (attributes: AttributeMap<'msg>) (sections: alist>) = + [] + type private AccordionInput<'msg> = + | Multi of active: aset * callback: (bool -> int -> 'msg) + | Single of active: aval * callback: (bool -> int -> 'msg) + | Empty of exclusive: bool + + member inline x.Callback = + match x with + | Multi (_, cb) | Single (_, cb) -> Some cb + | _ -> None + + member inline x.IsExclusive = + match x with + | Single _ | Empty true -> true + | _ -> false + + let private accordionImpl (input: AccordionInput<'msg>) (attributes: AttributeMap<'msg>) (sections: list * DomNode<'msg>>) = let dependencies = Html.semui @ [ { name = "accordion"; url = "resources/accordion.js"; kind = Script }] @@ -629,44 +644,57 @@ module SimplePrimitives = 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) + match input.Callback with + | Some cb -> + onEvent "onopen" [] (List.head >> int >> cb true) + onEvent "onclose" [] (List.head >> int >> cb false) + + | _ -> () ] AttributeMap.union attributes basic + let channel = + match input with + | AccordionInput.Multi (set, _) -> + Some (ASet.channel set) + + | AccordionInput.Single (index, _) -> + index |> AVal.map (fun i -> + if i < 0 then SetOperation.Rem -1 // Handle in JS, we don't know the actual index here + else SetOperation.Add i + ) + |> AVal.channel + |> Some + + | _ -> None + let boot = - let exclusive = - match active with - | Choice2Of2 _ -> "true" - | _ -> "false" + let exclusive = if input.IsExclusive then "true" else "false" + let channel = if channel.IsSome then "channelActive" else "null" String.concat "" [ "const $self = $('#__ID__');" - "aardvark.accordion($self, " + exclusive + ", channelActive);" + "aardvark.accordion($self, " + exclusive + ", " + channel + ");" ] let channels = - let channel = - match active with - | Choice1Of2 set -> ASet.channel set - | Choice2Of2 index -> AVal.channel index - - [ "channelActive", channel ] + match channel with + | Some ch -> [ "channelActive", ch ] + | _ -> [] require dependencies ( onBoot' channels boot ( - Incremental.div attributes <| alist { + Incremental.div attributes <| AList.ofList [ for (title, node) in sections do div [clazz "title"] [ i [clazz "dropdown icon"] [] - text title + Incremental.text title ] div [clazz "content"] [ node ] - } + ] ) ) @@ -674,25 +702,22 @@ module SimplePrimitives = /// 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) - (attributes: AttributeMap<'msg>) (sections: alist>) = - sections |> accordionImpl (Some (fun s i -> toggle (i, s))) (Choice1Of2 active) attributes + (attributes: AttributeMap<'msg>) (sections: list * DomNode<'msg>>) = + let cb s i = toggle (i, s) + sections |> accordionImpl (AccordionInput.Multi (active, cb)) 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) - (attributes: AttributeMap<'msg>) (sections: alist>) = - let map o i = if o then i else -1 - sections |> accordionImpl (Some (fun s -> map s >> setActive)) (Choice2Of2 active) attributes + (attributes: AttributeMap<'msg>) (sections: list * DomNode<'msg>>) = + let cb s i = (if s then i else -1) |> setActive + sections |> accordionImpl (AccordionInput.Single (active, cb)) 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>) = - let active = - if exclusive then Choice2Of2 (AVal.constant -1) - else Choice1Of2 ASet.empty - - sections |> accordionImpl None active attributes + let accordionSimple (exclusive: bool) (attributes: AttributeMap<'msg>) (sections: list * DomNode<'msg>>) = + sections |> accordionImpl (AccordionInput.Empty exclusive) attributes [] module ``Primtive Builders`` = @@ -966,7 +991,7 @@ module SimplePrimitives = let inline accordion (toggle: int * bool -> 'msg) (active: aset) (attributes: Attribute<'msg> list) (sections: list>) = let attributes = AttributeMap.ofList attributes - sections |> AList.ofList |> Incremental.accordion toggle active attributes + sections |> List.map (fun (t, n) -> AVal.constant t, n) |> 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. @@ -974,10 +999,10 @@ module SimplePrimitives = let inline accordionExclusive (setActive: int -> 'msg) (active: aval) (attributes: Attribute<'msg> list) (sections: list>) = let attributes = AttributeMap.ofList attributes - sections |> AList.ofList |> Incremental.accordionExclusive setActive active attributes + sections |> List.map (fun (t, n) -> AVal.constant t, n) |> 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>) = let attributes = AttributeMap.ofList attributes - sections |> AList.ofList |> Incremental.accordionSimple exclusive attributes \ No newline at end of file + sections |> List.map (fun (t, n) -> AVal.constant t, n) |> Incremental.accordionSimple exclusive attributes \ No newline at end of file diff --git a/src/Aardvark.UI.Primitives/resources/accordion.js b/src/Aardvark.UI.Primitives/resources/accordion.js index 782a95cd..524049fb 100644 --- a/src/Aardvark.UI.Primitives/resources/accordion.js +++ b/src/Aardvark.UI.Primitives/resources/accordion.js @@ -1,45 +1,34 @@ if (!aardvark.accordion) { /** * @param {HTMLElement[]} $self + * @param {HTMLElement[]} $content * @param {HTMLElement} item * @param {string} event */ - const onToggle = function ($self, item, event) { - const index = $self.children('.content').index(item); + const onToggle = function ($self, $content, item, event) { + const index = $content.index(item); aardvark.processEvent($self[0].id, event, index); }; /** * @param {HTMLElement[]} $self + * @param {HTMLElement[]} $content * @param {{cnt: int, value: int}} op */ - const processMessage = function ($self, op) { - if (op.value < 0 || op.value >= $self.children('.content').length) { - return; - } + const processMessage = function ($self, $content, op) { + var index = op.value; - if (op.cnt > 0) { - $self.accordion('open', op.value); - } else if (op.cnt < 0) { - $self.accordion('close', op.value); + // If we close a section in exclusive mode, we won't get + // the index from Media. Instead we have to figure it out here. + if (op.cnt < 0 && index < 0) { + index = $content.index($content.filter('.active')); } - }; - /** - * @param {HTMLElement[]} $self - * @param {int} index - */ - const processExclusiveMessage = function ($self, index) { - if (index >= 0) { - if (index < $self.children('.content').length) { + if (index >= 0 && index < $content.length) { + if (op.cnt > 0) { $self.accordion('open', index); - } - } else { - const $content = $self.children('.content'); - const active = $content.index($content.filter('.active')); - - if (active >= 0) { - $self.accordion('close', active); + } else if (op.cnt < 0) { + $self.accordion('close', index); } } }; @@ -47,19 +36,19 @@ if (!aardvark.accordion) { /** * @param {HTMLElement[]} $self * @param {boolean} exclusive - * @param {{onmessage: function}} channel + * @param {{onmessage: function} | null} channel */ aardvark.accordion = function ($self, exclusive, channel) { + const $content = $self.children('.content'); + $self.accordion({ exclusive: exclusive, - onOpen: function () { onToggle($self, this, 'onopen'); }, - onClose: function () { onToggle($self, this, 'onclose'); } + onOpen: function () { onToggle($self, $content, this, 'onopen'); }, + onClose: function () { onToggle($self, $content, this, 'onclose'); } }); - if (exclusive) { - channel.onmessage = (index) => processExclusiveMessage($self, index); - } else { - channel.onmessage = (op) => processMessage($self, op); + if (channel) { + channel.onmessage = (op) => processMessage($self, $content, op); } }; } \ No newline at end of file