From 1939015273a3ad7c208ce6cd4b692349352194bf Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 2 Oct 2020 16:38:33 +0100 Subject: [PATCH] Add final version of multiselect --- .../src/components/common/MultiSelect.svelte | 223 ++++++++---------- 1 file changed, 94 insertions(+), 129 deletions(-) diff --git a/packages/builder/src/components/common/MultiSelect.svelte b/packages/builder/src/components/common/MultiSelect.svelte index f000df7f41..f2fa530530 100644 --- a/packages/builder/src/components/common/MultiSelect.svelte +++ b/packages/builder/src/components/common/MultiSelect.svelte @@ -6,74 +6,57 @@ "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" export let value = [] - export let readonly = false export let label let placeholder = "Type to search" - let input - let inputValue let options = [] - let activeOption let optionsVisible = false let selected = {} let first = true let slot onMount(() => { - slot.querySelectorAll("option").forEach(o => { - o.selected && !value.includes(o.value) && (value = [...value, o.value]) - options = [...options, { value: o.value, name: o.textContent }] - }) - value && - (selected = options.reduce( - (obj, op) => - value.includes(op.value) ? { ...obj, [op.value]: op } : obj, - {} - )) + const domOptions = Array.from(slot.querySelectorAll("option")) + options = domOptions.map(option => ({ + value: option.value, + name: option.textContent, + })) + if (value) { + options.forEach(option => { + if (value.includes(option.value)) { + selected[option.value] = option + } + }) + } first = false }) - $: if (!first) value = Object.values(selected).map(o => o.value) - $: filtered = options.filter(o => - inputValue ? o.name.toLowerCase().includes(inputValue.toLowerCase()) : o - ) - $: if ( - (activeOption && !filtered.includes(activeOption)) || - (!activeOption && inputValue) - ) - activeOption = filtered[0] + // Keep value up to date with selected options + $: { + if (!first) { + value = Object.values(selected).map(option => option.value) + } + } function add(token) { - if (!readonly) selected[token.value] = token + selected[token.value] = token } function remove(value) { - if (!readonly) { - const { [value]: val, ...rest } = selected - selected = rest - } + const { [value]: val, ...rest } = selected + selected = rest } function removeAll() { selected = [] - inputValue = "" } function showOptions(show) { optionsVisible = show - if (!show) { - activeOption = undefined - } else { - input.focus() - } } - function handleBlur() { - showOptions(false) - } - - function handleFocus() { - showOptions(true) + function handleClick() { + showOptions(!optionsVisible) } function handleOptionMousedown(e) { @@ -82,7 +65,6 @@ remove(value) } else { add(options.filter(option => option.value === value)[0]) - input.focus() } } @@ -91,75 +73,51 @@ {#if label} {/if} -
+
-
+
{#each Object.values(selected) as option} -
+
{option.name} - {#if !readonly} -
remove(option.value)}> - - - -
- {/if} +
remove(option.value)}> + + + +
{/each} + {#if !value || !value.length} {/if}
-
- {#if !readonly} - -
- - - -
- {/if} -
{#if optionsVisible} +
showOptions(false)} />
    - {#each filtered as option} -
  • + {#each options as option} +
  • {option.name}
  • {/each} - {#if !filtered.length && inputValue.length} + {#if !options.length}
  • No results
  • {/if}
@@ -175,7 +133,7 @@ justify-content: flex-start; align-items: stretch; } - .multiselect:not(.readonly):hover { + .multiselect:hover { border-bottom-color: hsl(0, 0%, 50%); } @@ -185,6 +143,7 @@ justify-content: flex-start; align-items: center; flex: 0 1 auto; + z-index: 2; } .tokens { @@ -194,6 +153,14 @@ position: relative; width: 0; flex: 1 1 auto; + background-color: var(--grey-2); + border-radius: var(--border-radius-m); + padding: 0 var(--spacing-m) calc(var(--spacing-m) - var(--spacing-xs)) + calc(var(--spacing-m) / 2); + border: var(--border-transparent); + } + .tokens:hover { + cursor: pointer; } .tokens::after { background: none repeat scroll 0 0 transparent; @@ -206,74 +173,72 @@ transition: width 0.3s ease 0s, left 0.3s ease 0s; width: 0; } - .tokens.showOptions::after { + .tokens.optionsVisible { + border: var(--border-blue); + } + .tokens.empty { + padding: var(--spacing-m); + font-size: var(--font-size-xs); + user-select: none; + } + .tokens::after { width: 100%; left: 0; } .token { font-size: var(--font-size-xs); align-items: center; - background-color: var(--grey-3); + background-color: white; border-radius: var(--border-radius-l); display: flex; - margin: 0.25rem 0.5rem 0.25rem 0; + margin: calc(var(--spacing-m) - var(--spacing-xs)) 0 0 + calc(var(--spacing-m) / 2); max-height: 1.3rem; - padding: var(--spacing-s) var(--spacing-m); + padding: var(--spacing-xs) var(--spacing-s); transition: background-color 0.3s; white-space: nowrap; } - .token:hover { - background-color: var(--grey-4); + .token span { + pointer-events: none; + user-select: none; } - .readonly .token { - color: hsl(0, 0%, 40%); - } - .token-remove, - .remove-all { + .token-remove { align-items: center; - background-color: var(--grey-5); + background-color: var(--grey-4); border-radius: 50%; color: var(--white); display: flex; justify-content: center; - height: 1.25rem; - margin-left: 0.25rem; - min-width: 1.25rem; + height: 1rem; + width: 1rem; + margin: calc(-1 * var(--spacing-xs)) 0 calc(-1 * var(--spacing-xs)) + var(--spacing-xs); } - .token-remove:hover, - .remove-all:hover { - background-color: var(--grey-6); + .token-remove:hover { + background-color: var(--grey-5); cursor: pointer; } - .actions { - align-items: center; - display: flex; - flex: 1; - } - .actions > * { - flex: 0 0 auto; - } - .actions > input { - border: none; - font-size: var(--font-size-xs); - line-height: 1.5rem; - outline: none; - background-color: transparent; - flex: 1 1 auto; - } - .icon-clear path { fill: white; } + .options-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1; + } + .options { + z-index: 2; left: 0; list-style: none; margin-block-end: 0; margin-block-start: 0; - max-height: 70vh; - overflow: auto; + overflow-y: auto; padding-inline-start: 0; position: absolute; top: calc(100% + 1px); @@ -283,8 +248,8 @@ box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15); margin-top: var(--spacing-xs); padding: var(--spacing-s) 0; - z-index: 1; background-color: white; + max-height: 200px; } li { background-color: white;