/ 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)