/ client_code / fui.py
fui.py
  1  # SPDX-License-Identifier: MIT
  2  #
  3  # Copyright (c) 2021 The Anvil Extras project team members listed at
  4  # https://github.com/anvilistas/anvil-extras/graphs/contributors
  5  #
  6  # This software is published at https://github.com/anvilistas/anvil-extras
  7  
  8  from anvil.js import import_from
  9  from anvil.js.window import window as _W
 10  
 11  __version__ = "3.1.0"
 12  
 13  try:
 14      # support preloaded FloatingUIDOM
 15      FloatingUIDOM = _W.FloatingUIDOM
 16  except AttributeError:
 17      # https://floating-ui.com/
 18      FloatingUIDOM = import_from(
 19          "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.10/+esm"
 20      )
 21  
 22  _static_arrow_position = {
 23      "top": "bottom",
 24      "right": "left",
 25      "bottom": "top",
 26      "left": "right",
 27  }
 28  
 29  
 30  def size_middleware():
 31      def apply(context):
 32          availableHeight = context["availableHeight"]
 33          elements = context["elements"]
 34          elements.floating.style.maxHeight = f"{availableHeight}px"
 35  
 36      return {"apply": apply}
 37  
 38  
 39  def auto_update(
 40      reference_el,
 41      floating_el,
 42      *,
 43      placement="bottom",
 44      strategy="absolute",
 45      offset=None,
 46      shift={"padding": 5},
 47      hide={"padding": 15},
 48      arrow=None,
 49  ):
 50      """starts auto updating position of floating element to a reference element
 51      returns a cleanup function
 52      if using arrow, arrow should be an HTMLElement
 53      call this function in x-anvil-page-added
 54      call the cleanup in x-anvil-page-removed"""
 55  
 56      offset = 11 if arrow else 4
 57  
 58      def update(*args):
 59          middleware = [
 60              FloatingUIDOM.offset(offset),
 61              FloatingUIDOM.flip(),
 62              FloatingUIDOM.shift(shift),
 63              FloatingUIDOM.hide(hide),
 64              FloatingUIDOM.size(size_middleware()),
 65          ]
 66  
 67          if arrow:
 68              middleware.append(FloatingUIDOM.arrow({"element": arrow}))
 69  
 70          rv = FloatingUIDOM.computePosition(
 71              reference_el,
 72              floating_el,
 73              {
 74                  "placement": placement,
 75                  "strategy": strategy,
 76                  "middleware": middleware,
 77              },
 78          )
 79          floating_el.style.left = f"{rv.x}px"
 80          floating_el.style.top = f"{rv.y}px"
 81  
 82          middlewareData = rv.middlewareData
 83  
 84          if "hide" in middlewareData:
 85              hidden = middlewareData.hide.referenceHidden
 86              floating_el.style.visibility = "hidden" if hidden else "visible"
 87  
 88          main_axis = rv.placement.split("-")[0]
 89          static_side = _static_arrow_position.get(main_axis)
 90  
 91          if arrow and "arrow" in middlewareData:
 92              x = middlewareData.arrow.get("x")
 93              y = middlewareData.arrow.get("y")
 94  
 95              arrow.style.left = "" if x is None else f"{x}px"
 96              arrow.style.top = "" if y is None else f"{y}px"
 97              arrow.style.right = ""
 98              arrow.style.bottom = ""
 99              if static_side:
100                  arrow.style[static_side] = "-11px"
101  
102          floating_el.classList.remove("left", "right", "top", "bottom")
103          floating_el.classList.add(main_axis)
104  
105      return FloatingUIDOM.autoUpdate(reference_el, floating_el, update)