forked from tsai/budibase
10 changed files with 267 additions and 47 deletions
@ -1,36 +1,218 @@ |
|||||
<script> |
<script> |
||||
import { onMount } from "svelte" |
import { onMount, beforeUpdate } from "svelte" |
||||
|
//Create utilities and move this in |
||||
|
import { buildStyle } from "../../../../standard-components/src/buildStyle.js" |
||||
|
export let options = [] |
||||
export let value = "" |
export let value = "" |
||||
|
export let styleBindingProperty |
||||
export let onChange = value => {} |
export let onChange = value => {} |
||||
export let options = [] |
|
||||
export let initialValue = "" |
let open = null |
||||
export let styleBindingProperty = "" |
let rotate = "" |
||||
|
let select |
||||
|
let selectMenu |
||||
|
let icon |
||||
|
|
||||
|
let selectYPosition = null |
||||
|
let availableSpace = 0 |
||||
|
|
||||
|
let positionSide = "top" |
||||
|
let maxHeight = null |
||||
|
let menuHeight |
||||
|
|
||||
const handleStyleBind = value => |
const handleStyleBind = value => |
||||
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
||||
|
|
||||
$: isOptionsObject = options.every(o => typeof o === "object") |
|
||||
|
|
||||
onMount(() => { |
onMount(() => { |
||||
if (!value && !!initialValue) { |
if (select) { |
||||
value = initialValue |
select.addEventListener("keydown", addSelectKeyEvents) |
||||
|
} |
||||
|
return () => { |
||||
|
select.removeEventListener("keydown", addSelectKeyEvents) |
||||
} |
} |
||||
}) |
}) |
||||
|
|
||||
|
function checkPosition() { |
||||
|
const { bottom, top: spaceAbove } = select.getBoundingClientRect() |
||||
|
const spaceBelow = window.innerHeight - bottom |
||||
|
|
||||
|
if (spaceAbove > spaceBelow) { |
||||
|
positionSide = "bottom" |
||||
|
maxHeight = `${spaceAbove.toFixed(0) - 20}px` |
||||
|
} else { |
||||
|
positionSide = "top" |
||||
|
maxHeight = `${spaceBelow.toFixed(0) - 20}px` |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function addSelectKeyEvents(e) { |
||||
|
if (e.key === "Enter") { |
||||
|
if (!open) { |
||||
|
toggleSelect(true) |
||||
|
} |
||||
|
} else if (e.key === "Escape") { |
||||
|
if (open) { |
||||
|
toggleSelect(false) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function toggleSelect(isOpen) { |
||||
|
checkPosition() |
||||
|
if (isOpen) { |
||||
|
icon.style.transform = "rotate(180deg)" |
||||
|
} else { |
||||
|
icon.style.transform = "rotate(0deg)" |
||||
|
} |
||||
|
open = isOpen |
||||
|
} |
||||
|
|
||||
|
function handleClick(val) { |
||||
|
value = val |
||||
|
onChange(value) |
||||
|
} |
||||
|
|
||||
|
$: menuStyle = buildStyle({ |
||||
|
maxHeight, |
||||
|
transformOrigin: `center ${positionSide}`, |
||||
|
[positionSide]: "32px", |
||||
|
}) |
||||
|
|
||||
|
$: isOptionsObject = options.every(o => typeof o === "object") |
||||
|
|
||||
|
$: selectedOption = isOptionsObject |
||||
|
? options.find(o => o.value === value) |
||||
|
: {} |
||||
|
|
||||
|
$: displayLabel = |
||||
|
selectedOption && selectedOption.label ? selectedOption.label : value || "" |
||||
</script> |
</script> |
||||
|
|
||||
<select |
<div |
||||
class="uk-select uk-form-small" |
tabindex="0" |
||||
{value} |
bind:this={select} |
||||
on:change={ev => onChange(ev.target.value)}> |
class="bb-select-container" |
||||
{#if isOptionsObject} |
on:click={() => toggleSelect(!open)}> |
||||
{#each options as { value, label }} |
<div class="bb-select-anchor selected"> |
||||
<option {...handleStyleBind(value || label)} value={value || label}> |
<span>{displayLabel}</span> |
||||
{label} |
<i bind:this={icon} class="ri-arrow-down-s-fill" /> |
||||
</option> |
</div> |
||||
{/each} |
<div |
||||
{:else} |
bind:this={selectMenu} |
||||
{#each options as value} |
style={menuStyle} |
||||
<option {...handleStyleBind(value)} {value}>{value}</option> |
class="bb-select-menu" |
||||
{/each} |
class:open> |
||||
{/if} |
<ul> |
||||
</select> |
{#if isOptionsObject} |
||||
|
{#each options as { value: v, label }} |
||||
|
<li |
||||
|
{...handleStyleBind(v)} |
||||
|
on:click|self={handleClick(v)} |
||||
|
class:selected={value === v}> |
||||
|
{label} |
||||
|
</li> |
||||
|
{/each} |
||||
|
{:else} |
||||
|
{#each options as v} |
||||
|
<li |
||||
|
{...handleStyleBind(v)} |
||||
|
on:click|self={handleClick(v)} |
||||
|
class:selected={value === v}> |
||||
|
{v} |
||||
|
</li> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
{#if open} |
||||
|
<div on:click|self={() => toggleSelect(false)} class="overlay" /> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
.overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.bb-select-container { |
||||
|
position: relative; |
||||
|
outline: none; |
||||
|
margin: 5px; |
||||
|
width: 164px; |
||||
|
height: 32px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.bb-select-anchor { |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
padding: 5px 10px; |
||||
|
background-color: #f2f2f2; |
||||
|
border-radius: 2px; |
||||
|
border: 1px solid var(--grey-dark); |
||||
|
} |
||||
|
|
||||
|
.bb-select-anchor > span { |
||||
|
color: #565a66; |
||||
|
font-weight: 500; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.bb-select-anchor > i { |
||||
|
flex: 0 0 20px; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
color: #565a66; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.bb-select-menu { |
||||
|
position: absolute; |
||||
|
display: flex; |
||||
|
box-sizing: border-box; |
||||
|
flex-direction: column; |
||||
|
opacity: 0; |
||||
|
width: 164px; |
||||
|
z-index: 2; |
||||
|
color: #808192; |
||||
|
font-weight: 500; |
||||
|
height: fit-content !important; |
||||
|
border-bottom-left-radius: 2px; |
||||
|
border-bottom-right-radius: 2px; |
||||
|
border-right: 1px solid var(--grey-dark); |
||||
|
border-left: 1px solid var(--grey-dark); |
||||
|
border-bottom: 1px solid var(--grey-dark); |
||||
|
background-color: #f2f2f2; |
||||
|
transform: scale(0); |
||||
|
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1); |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.open { |
||||
|
transform: scale(1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
ul { |
||||
|
list-style-type: none; |
||||
|
margin: 0; |
||||
|
padding: 5px 10px; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
height: auto; |
||||
|
padding: 5px 0px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
transition: transform 0.13s ease; |
||||
|
transform-origin: center; |
||||
|
} |
||||
|
</style> |
||||
|
|||||
@ -0,0 +1,33 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
export let value = "" |
||||
|
export let onChange = value => {} |
||||
|
export let options = [] |
||||
|
export let initialValue = "" |
||||
|
export let styleBindingProperty = "" |
||||
|
|
||||
|
const handleStyleBind = value => |
||||
|
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
||||
|
|
||||
|
$: isOptionsObject = options.every(o => typeof o === "object") |
||||
|
|
||||
|
onMount(() => { |
||||
|
if (!value && !!initialValue) { |
||||
|
value = initialValue |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<select {value} on:change={ev => onChange(ev.target.value)}> |
||||
|
{#if isOptionsObject} |
||||
|
{#each options as { value, label }} |
||||
|
<option {...handleStyleBind(value || label)} value={value || label}> |
||||
|
{label} |
||||
|
</option> |
||||
|
{/each} |
||||
|
{:else} |
||||
|
{#each options as value} |
||||
|
<option {...handleStyleBind(value)} {value}>{value}</option> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</select> |
||||
Loading…
Reference in new issue