diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js index 3a08484635..9ea247f997 100644 --- a/packages/bbui/src/Actions/click_outside.js +++ b/packages/bbui/src/Actions/click_outside.js @@ -1,4 +1,4 @@ -const ignoredClasses = [".flatpickr-calendar", ".modal-container"] +const ignoredClasses = [".flatpickr-calendar"] let clickHandlers = [] /** diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 7570a39c8c..e89f28a69e 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -1,75 +1,69 @@ -export default function positionDropdown(element, { anchor, align, maxWidth }) { - let positionSide = "top" - let maxHeight = 0 - let dimensions = getDimensions(anchor) +export default function positionDropdown( + element, + { anchor, align, maxWidth, useAnchorWidth } +) { + const update = () => { + console.log("update") + const anchorBounds = anchor.getBoundingClientRect() + const elementBounds = element.getBoundingClientRect() + let styles = { + maxHeight: null, + minWidth: null, + maxWidth, + left: null, + top: null, + } - function getDimensions() { - const { - bottom, - top: spaceAbove, - left, - width, - } = anchor.getBoundingClientRect() - const spaceBelow = window.innerHeight - bottom - const containerRect = element.getBoundingClientRect() - - let y - - if (spaceAbove > spaceBelow) { - positionSide = "bottom" - maxHeight = spaceAbove - 20 - y = window.innerHeight - spaceAbove + 5 + // Determine vertical styles + if (window.innerHeight - anchorBounds.bottom < 100) { + styles.top = anchorBounds.top - elementBounds.height - 5 } else { - positionSide = "top" - y = bottom + 5 - maxHeight = spaceBelow - 20 + styles.top = anchorBounds.bottom + 5 + styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20 } - return { - [positionSide]: y, - left, - width, - containerWidth: containerRect.width, + // Determine horizontal styles + if (!maxWidth && useAnchorWidth) { + styles.maxWidth = anchorBounds.width } + if (useAnchorWidth) { + styles.minWidth = anchorBounds.width + } + if (align === "right") { + styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width + } else if (align === "right-side") { + styles.left = anchorBounds.left + anchorBounds.width + } else { + styles.left = anchorBounds.left + } + + // Apply styles + Object.entries(styles).forEach(([style, value]) => { + if (value) { + element.style[style] = `${value.toFixed(0)}px` + } else { + element.style[style] = null + } + }) } - function calcLeftPosition() { - let left - - if (align == "right") { - left = dimensions.left + dimensions.width - dimensions.containerWidth - } else if (align == "right-side") { - left = dimensions.left + dimensions.width - } else { - left = dimensions.left - } - - return left - } - + // Apply initial styles which don't need to change element.style.position = "absolute" element.style.zIndex = "9999" - if (maxWidth) { - element.style.maxWidth = `${maxWidth}px` - } - element.style.minWidth = `${dimensions.width}px` - element.style.maxHeight = `${maxHeight.toFixed(0)}px` - element.style.transformOrigin = `center ${positionSide}` - element.style[positionSide] = `${dimensions[positionSide]}px` - element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` + // Observe both anchor and element and resize the popover as appropriate const resizeObserver = new ResizeObserver(entries => { - entries.forEach(() => { - dimensions = getDimensions() - element.style[positionSide] = `${dimensions[positionSide]}px` - element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` - }) + entries.forEach(update) }) resizeObserver.observe(anchor) resizeObserver.observe(element) + + document.addEventListener("scroll", update, true) + return { destroy() { resizeObserver.disconnect() + document.removeEventListener("scroll", update, true) }, } } diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index 63f07b7cd6..81348452c7 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -2,12 +2,12 @@ import "@spectrum-css/picker/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css" - import { fly } from "svelte/transition" import { createEventDispatcher } from "svelte" import clickOutside from "../../Actions/click_outside" import Search from "./Search.svelte" import Icon from "../../Icon/Icon.svelte" import StatusLight from "../../StatusLight/StatusLight.svelte" + import Popover from "../../Popover/Popover.svelte" export let id = null export let disabled = false @@ -33,7 +33,10 @@ export let sort = false const dispatch = createEventDispatcher() + let searchTerm = null + let button + let popover $: sortedOptions = getSortedOptions(options, getOptionLabel, sort) $: filteredOptions = getFilteredOptions( @@ -76,77 +79,117 @@ } -
(open = false)}> - - {#if open} -
- {#if autocomplete} - (searchTerm = event.detail)} - {disabled} - placeholder="Search" - /> + {/if} + + + + (open = false)} + useAnchorWidth={!autoWidth} + maxWidth={autoWidth ? 400 : null} +> +
+ {#if autocomplete} + (searchTerm = event.detail)} + {disabled} + placeholder="Search" + /> + {/if} +
    + {#if placeholderOption} +
  • onSelectOption(null)} + > + {placeholderOption} + +
  • {/if} -
      - {#if placeholderOption} + {#if filteredOptions.length} + {#each filteredOptions as option, idx}
    • onSelectOption(null)} + on:click={() => onSelectOption(getOptionValue(option, idx))} + class:is-disabled={!isOptionEnabled(option)} > - {placeholderOption} + {#if getOptionIcon(option, idx)} + + + + {/if} + {#if getOptionColour(option, idx)} + + + + {/if} + + {getOptionLabel(option, idx)} +
    • - {/if} - {#if filteredOptions.length} - {#each filteredOptions as option, idx} -
    • onSelectOption(getOptionValue(option, idx))} - class:is-disabled={!isOptionEnabled(option)} - > - {#if getOptionIcon(option, idx)} - - - - {/if} - {#if getOptionColour(option, idx)} - - - - {/if} - - {getOptionLabel(option, idx)} - - -
    • - {/each} - {/if} -
    -
- {/if} -
+ {/each} + {/if} + +
+ diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index b81e76dc1f..191898578a 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -4,6 +4,7 @@ import { createEventDispatcher } from "svelte" import positionDropdown from "../Actions/position_dropdown" import clickOutside from "../Actions/click_outside" + import { fly } from "svelte/transition" const dispatch = createEventDispatcher() @@ -12,9 +13,10 @@ export let portalTarget export let dataCy export let maxWidth - export let direction = "bottom" export let showTip = false + export let open = false + export let useAnchorWidth = false let tipSvg = ' ' @@ -35,13 +37,22 @@ const handleOutsideClick = e => { if (open) { - e.stopPropagation() + // Stop propagation if the source is the anchor + let node = e.target + let fromAnchor = false + while (!fromAnchor && node && node.parentNode) { + fromAnchor = node === anchor + node = node.parentNode + } + if (fromAnchor) { + e.stopPropagation() + } + + // Hide the popover hide() } } - let open = null - function handleEscape(e) { if (open && e.key === "Escape") { hide() @@ -53,12 +64,13 @@