Merge pull request #397 from Budibase/bugfix/select-overflow-clipping

OptionSelect - Fix for Overflow Clipping
This commit is contained in:
Conor_Mack 2020-06-29 15:38:08 +01:00 committed by GitHub
commit b565c940f2
3 changed files with 90 additions and 61 deletions

View File

@ -70,6 +70,7 @@
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"string_decoder": "^1.2.0", "string_decoder": "^1.2.0",
"svelte-portal": "^0.1.0",
"svelte-simple-modal": "^0.4.2", "svelte-simple-modal": "^0.4.2",
"uikit": "^3.1.7" "uikit": "^3.1.7"
}, },

View File

@ -1,16 +1,27 @@
<script> <script>
import {onMount} from "svelte"
import PropertyGroup from "./PropertyGroup.svelte" import PropertyGroup from "./PropertyGroup.svelte"
import FlatButtonGroup from "./FlatButtonGroup.svelte" import FlatButtonGroup from "./FlatButtonGroup.svelte"
export let panelDefinition = {} export let panelDefinition = {}
export let componentInstance = {} export let componentInstance = {}
export let componentDefinition = {} export let componentDefinition = {}
export let onStyleChanged = () => {} export let onStyleChanged = () => {}
let selectedCategory = "normal" let selectedCategory = "normal"
let propGroup = null
const getProperties = name => panelDefinition[name] const getProperties = name => panelDefinition[name]
onMount(() => {
// if(propGroup) {
// propGroup.addEventListener("scroll", function(e){
// console.log("I SCROLLED", e.target.scrollTop)
// })
// }
})
function onChange(category) { function onChange(category) {
selectedCategory = category selectedCategory = category
} }
@ -31,7 +42,7 @@
</div> </div>
<div class="positioned-wrapper"> <div class="positioned-wrapper">
<div class="design-view-property-groups"> <div bind:this={propGroup} class="design-view-property-groups">
{#if propertyGroupNames.length > 0} {#if propertyGroupNames.length > 0}
{#each propertyGroupNames as groupName} {#each propertyGroupNames as groupName}
<PropertyGroup <PropertyGroup

View File

@ -1,5 +1,6 @@
<script> <script>
import { onMount, beforeUpdate } from "svelte" import { onMount, beforeUpdate, afterUpdate } from "svelte"
import Portal from "svelte-portal"
import { buildStyle } from "../../helpers.js" import { buildStyle } from "../../helpers.js"
export let options = [] export let options = []
export let value = "" export let value = ""
@ -12,52 +13,61 @@
let selectMenu let selectMenu
let icon let icon
let selectYPosition = null let selectAnchor = null;
let availableSpace = 0 let dimensions = {top: 0, bottom: 0, left: 0}
let positionSide = "top" let positionSide = "top"
let maxHeight = null let maxHeight = 0
let menuHeight let scrollTop = 0;
let containerEl = null;
const handleStyleBind = value => const handleStyleBind = value =>
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} !!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {}
onMount(() => { onMount(() => {
if (select) { if (select) {
select.addEventListener("keydown", addSelectKeyEvents) select.addEventListener("keydown", handleEnter)
} }
return () => { return () => {
select.removeEventListener("keydown", addSelectKeyEvents) select.removeEventListener("keydown", handleEnter)
} }
}) })
function checkPosition() { function handleEscape(e) {
const { bottom, top: spaceAbove } = select.getBoundingClientRect() if(open && e.key === "Escape") {
const spaceBelow = window.innerHeight - bottom toggleSelect(false)
if (spaceAbove > spaceBelow) {
positionSide = "bottom"
maxHeight = `${spaceAbove.toFixed(0) - 20}px`
} else {
positionSide = "top"
maxHeight = `${spaceBelow.toFixed(0) - 20}px`
} }
} }
function addSelectKeyEvents(e) { function getDimensions() {
if (e.key === "Enter") { const { bottom, top: spaceAbove, left } = selectAnchor.getBoundingClientRect()
if (!open) { const spaceBelow = window.innerHeight - bottom
let y;
if (spaceAbove > spaceBelow) {
positionSide = "bottom"
maxHeight = spaceAbove - 20
y = (window.innerHeight - spaceAbove)
} else {
positionSide = "top"
y = bottom
maxHeight = spaceBelow - 20
}
dimensions = {[positionSide]: y, left}
}
function handleEnter(e) {
if (!open && e.key === "Enter") {
toggleSelect(true) toggleSelect(true)
}
} else if (e.key === "Escape") {
if (open) {
toggleSelect(false)
}
} }
} }
function toggleSelect(isOpen) { function toggleSelect(isOpen) {
checkPosition() getDimensions()
if (isOpen) { if (isOpen) {
icon.style.transform = "rotate(180deg)" icon.style.transform = "rotate(180deg)"
} else { } else {
@ -66,15 +76,17 @@
open = isOpen open = isOpen
} }
function handleClick(val) { function handleClick(val) {
value = val value = val
onChange(value) onChange(value)
} }
$: menuStyle = buildStyle({ $: menuStyle = buildStyle({
"max-height": maxHeight, "max-height": `${maxHeight.toFixed(0)}px`,
"transform-origin": `center ${positionSide}`, "transform-origin": `center ${positionSide}`,
[positionSide]: "32px", [positionSide]: `${dimensions[positionSide]}px`,
"left": `${dimensions.left.toFixed(0)}px`,
}) })
$: isOptionsObject = options.every(o => typeof o === "object") $: isOptionsObject = options.every(o => typeof o === "object")
@ -83,6 +95,10 @@
? options.find(o => o.value === value) ? options.find(o => o.value === value)
: {} : {}
$: if(open && selectMenu) {
selectMenu.focus()
}
$: displayLabel = $: displayLabel =
selectedOption && selectedOption.label ? selectedOption.label : value || "" selectedOption && selectedOption.label ? selectedOption.label : value || ""
</script> </script>
@ -92,45 +108,49 @@
bind:this={select} bind:this={select}
class="bb-select-container" class="bb-select-container"
on:click={() => toggleSelect(!open)}> on:click={() => toggleSelect(!open)}>
<div class="bb-select-anchor selected"> <div bind:this={selectAnchor} class="bb-select-anchor selected">
<span>{displayLabel}</span> <span>{displayLabel}</span>
<i bind:this={icon} class="ri-arrow-down-s-fill" /> <i bind:this={icon} class="ri-arrow-down-s-fill" />
</div> </div>
<div {#if open}
bind:this={selectMenu} <Portal>
style={menuStyle} <div
class="bb-select-menu" tabindex="0"
class:open> class:open
<ul> bind:this={selectMenu}
{#if isOptionsObject} style={menuStyle}
{#each options as { value: v, label }} on:keydown={handleEscape}
<li class="bb-select-menu">
{...handleStyleBind(v)} <ul>
on:click|self={handleClick(v)} {#if isOptionsObject}
class:selected={value === v}> {#each options as { value: v, label }}
{label} <li
</li> {...handleStyleBind(v)}
{/each} on:click|self={handleClick(v)}
{:else} class:selected={value === v}>
{#each options as v} {label}
<li </li>
{...handleStyleBind(v)} {/each}
on:click|self={handleClick(v)} {:else}
class:selected={value === v}> {#each options as v}
{v} <li
</li> {...handleStyleBind(v)}
{/each} on:click|self={handleClick(v)}
{/if} class:selected={value === v}>
</ul> {v}
</div> </li>
{/each}
{/if}
</ul>
</div>
<div on:click|self={() => toggleSelect(false)} class="overlay" />
</Portal>
{/if}
</div> </div>
{#if open}
<div on:click|self={() => toggleSelect(false)} class="overlay" />
{/if}
<style> <style>
.overlay { .overlay {
position: absolute; position: fixed;
top: 0; top: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
@ -139,7 +159,6 @@
} }
.bb-select-container { .bb-select-container {
position: relative;
outline: none; outline: none;
width: 160px; width: 160px;
height: 36px; height: 36px;
@ -180,6 +199,7 @@
.bb-select-menu { .bb-select-menu {
position: absolute; position: absolute;
display: flex; display: flex;
outline: none;
box-sizing: border-box; box-sizing: border-box;
flex-direction: column; flex-direction: column;
opacity: 0; opacity: 0;
@ -190,9 +210,6 @@
height: fit-content !important; height: fit-content !important;
border-bottom-left-radius: 2px; border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px; border-bottom-right-radius: 2px;
border-right: 1px solid var(--grey-4);
border-left: 1px solid var(--grey-4);
border-bottom: 1px solid var(--grey-4);
background-color: var(--grey-2); background-color: var(--grey-2);
transform: scale(0); transform: scale(0);
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1); transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);