fix(frontend): Forked svelte-select to fix overflow issues using a po… (#1778)

* fix(frontend): Forked svelte-select to fix overflow issues using a portal

* fix(frontend): fix event bubble

* fix(frontend): fix build

* fix(frontend): Remove uncessary support for multiple values

* fix(frontend): conditional portal

* fix(frontend): conditional portal

* fix(frontend): fix resourcepicker

* fix(frontend): AppMultiSelect fix

* fix(frontend): fix build

* fix(frontend): AppMultiSelect fix
This commit is contained in:
Faton Ramadani
2023-07-03 09:33:24 +02:00
committed by GitHub
parent 83e0f9b594
commit bd481adbfc
15 changed files with 1491 additions and 65 deletions

View File

@@ -34,7 +34,6 @@
"svelte-chartjs": "^3.1.0",
"svelte-dnd-action": "^0.9.22",
"svelte-portal": "^2.2.0",
"svelte-select": "^5.6.1",
"svelte-timezone-picker": "^2.0.3",
"tailwind-merge": "^1.12.0",
"vscode-ws-jsonrpc": "3.0.0",
@@ -44,6 +43,7 @@
"yjs": "^13.6.4"
},
"devDependencies": {
"@floating-ui/core": "^1.3.1",
"@playwright/test": "^1.34.3",
"@rgossiaux/svelte-headlessui": "^1.0.2",
"@sveltejs/adapter-static": "^2.0.2",
@@ -81,6 +81,7 @@
"svelte-awesome": "^3.2.0",
"svelte-awesome-color-picker": "^2.4.5",
"svelte-check": "^3.4.3",
"svelte-floating-ui": "^1.5.3",
"svelte-highlight": "^7.3.0",
"svelte-multiselect": "^8.6.2",
"svelte-overlay": "^1.4.1",
@@ -452,16 +453,18 @@
}
},
"node_modules/@floating-ui/core": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.5.tgz",
"integrity": "sha512-qrcbyfnRVziRlB6IYwjCopYhO7Vud750JlJyuljruIXcPxr22y8zdckcJGsuOdnQ639uVD1tTXddrcH3t3QYIQ=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==",
"dev": true
},
"node_modules/@floating-ui/dom": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.5.tgz",
"integrity": "sha512-+sAUfpQ3Frz+VCbPCqj+cZzvEESy3fjSeT/pDWkYCWOBXYNNKZfuVsHuv8/JO2zze8+Eb/Q7a6hZVgzS81fLbQ==",
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.3.tgz",
"integrity": "sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA==",
"dev": true,
"dependencies": {
"@floating-ui/core": "^1.2.4"
"@floating-ui/core": "^1.3.1"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
@@ -7143,12 +7146,13 @@
}
},
"node_modules/svelte-floating-ui": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.2.8.tgz",
"integrity": "sha512-8Ifi5CD2Ui7FX7NjJRmutFtXjrB8T/FMNoS2H8P81t5LHK4I9G4NIs007rLWG/nRl7y+zJUXa3tWuTjYXw/O5A==",
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.5.3.tgz",
"integrity": "sha512-yjLeRQ4SNcICbMabp5S2cXc5ubBd2eua+swfTqqGBeg8ZrUvotZYK8jsRWETl/3RgLQsaZbEYKx8OmLA3wL/yw==",
"dev": true,
"dependencies": {
"@floating-ui/core": "^1.1.0",
"@floating-ui/dom": "^1.1.0"
"@floating-ui/core": "^1.2.4",
"@floating-ui/dom": "^1.2.4"
}
},
"node_modules/svelte-highlight": {
@@ -7282,15 +7286,6 @@
"integrity": "sha512-fLkhfnyEc4uMOCZCl8IPyC0e9lujrjqdSyrKw28Y8j99YUPZR+pHnSxde7PqhM0ST6eyO4ugh2nFnLZTIoG9aQ==",
"dev": true
},
"node_modules/svelte-select": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-5.6.1.tgz",
"integrity": "sha512-Powj91VAWyaNMSSOQ0E29UMTw/ExWsHEsA83H7yQgfji2S2DhG6Rs5qZHslZ+ihbCPxXel5uEzwLsSqDABkQDw==",
"dependencies": {
"@floating-ui/dom": "^1.2.1",
"svelte-floating-ui": "1.2.8"
}
},
"node_modules/svelte-splitpanes": {
"version": "0.7.14",
"resolved": "https://registry.npmjs.org/svelte-splitpanes/-/svelte-splitpanes-0.7.14.tgz",
@@ -8394,16 +8389,18 @@
"dev": true
},
"@floating-ui/core": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.5.tgz",
"integrity": "sha512-qrcbyfnRVziRlB6IYwjCopYhO7Vud750JlJyuljruIXcPxr22y8zdckcJGsuOdnQ639uVD1tTXddrcH3t3QYIQ=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==",
"dev": true
},
"@floating-ui/dom": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.5.tgz",
"integrity": "sha512-+sAUfpQ3Frz+VCbPCqj+cZzvEESy3fjSeT/pDWkYCWOBXYNNKZfuVsHuv8/JO2zze8+Eb/Q7a6hZVgzS81fLbQ==",
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.3.tgz",
"integrity": "sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA==",
"dev": true,
"requires": {
"@floating-ui/core": "^1.2.4"
"@floating-ui/core": "^1.3.1"
}
},
"@fortawesome/fontawesome-common-types": {
@@ -13253,12 +13250,13 @@
"requires": {}
},
"svelte-floating-ui": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.2.8.tgz",
"integrity": "sha512-8Ifi5CD2Ui7FX7NjJRmutFtXjrB8T/FMNoS2H8P81t5LHK4I9G4NIs007rLWG/nRl7y+zJUXa3tWuTjYXw/O5A==",
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.5.3.tgz",
"integrity": "sha512-yjLeRQ4SNcICbMabp5S2cXc5ubBd2eua+swfTqqGBeg8ZrUvotZYK8jsRWETl/3RgLQsaZbEYKx8OmLA3wL/yw==",
"dev": true,
"requires": {
"@floating-ui/core": "^1.1.0",
"@floating-ui/dom": "^1.1.0"
"@floating-ui/core": "^1.2.4",
"@floating-ui/dom": "^1.2.4"
}
},
"svelte-highlight": {
@@ -13334,15 +13332,6 @@
"integrity": "sha512-fLkhfnyEc4uMOCZCl8IPyC0e9lujrjqdSyrKw28Y8j99YUPZR+pHnSxde7PqhM0ST6eyO4ugh2nFnLZTIoG9aQ==",
"dev": true
},
"svelte-select": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-5.6.1.tgz",
"integrity": "sha512-Powj91VAWyaNMSSOQ0E29UMTw/ExWsHEsA83H7yQgfji2S2DhG6Rs5qZHslZ+ihbCPxXel5uEzwLsSqDABkQDw==",
"requires": {
"@floating-ui/dom": "^1.2.1",
"svelte-floating-ui": "1.2.8"
}
},
"svelte-splitpanes": {
"version": "0.7.14",
"resolved": "https://registry.npmjs.org/svelte-splitpanes/-/svelte-splitpanes-0.7.14.tgz",

View File

@@ -16,6 +16,7 @@
"test": "playwright test --config=tests-out/playwright.config.js"
},
"devDependencies": {
"@floating-ui/core": "^1.3.1",
"@playwright/test": "^1.34.3",
"@rgossiaux/svelte-headlessui": "^1.0.2",
"@sveltejs/adapter-static": "^2.0.2",
@@ -53,6 +54,7 @@
"svelte-awesome": "^3.2.0",
"svelte-awesome-color-picker": "^2.4.5",
"svelte-check": "^3.4.3",
"svelte-floating-ui": "^1.5.3",
"svelte-highlight": "^7.3.0",
"svelte-multiselect": "^8.6.2",
"svelte-overlay": "^1.4.1",
@@ -94,7 +96,6 @@
"svelte-chartjs": "^3.1.0",
"svelte-dnd-action": "^0.9.22",
"svelte-portal": "^2.2.0",
"svelte-select": "^5.6.1",
"svelte-timezone-picker": "^2.0.3",
"tailwind-merge": "^1.12.0",
"vscode-ws-jsonrpc": "3.0.0",

View File

@@ -4,7 +4,7 @@
import { faPen, faPlus, faRotateRight } from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import Select from 'svelte-select'
import Select from './apps/svelte-select/lib/index'
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
import AppConnect from './AppConnect.svelte'
import { Button } from './common'

View File

@@ -4,7 +4,7 @@
import { hubScripts, workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import Select from 'svelte-select'
import Select from './apps/svelte-select/lib/index'
import { getScriptByPath } from '$lib/scripts'
import { Button, Drawer, DrawerContent } from './common'
@@ -97,6 +97,7 @@
placeholder="Pick a {itemKind}"
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
portal={false}
/>
{/if}

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
// import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { getContext, onMount } from 'svelte'
import { initConfig, initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
@@ -10,12 +9,18 @@
import ResolveConfig from '../helpers/ResolveConfig.svelte'
// @ts-ignore
import MultiSelect from 'svelte-multiselect'
import Portal from 'svelte-portal'
import { createFloatingActions } from 'svelte-floating-ui'
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
export let render: boolean
const [floatingRef, floatingContent] = createFloatingActions({
strategy: 'absolute'
})
const { app, worldStore, selectedComponent, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let items: string[]
@@ -62,7 +67,23 @@
$: css = concatCustomCss($app.css?.multiselectcomponent, customCss)
$: outerDiv && css?.multiselect?.style && outerDiv.setAttribute('style', css?.multiselect?.style)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
onMount(() => {
// Find ul element with class 'options' within the outerDiv
const ul = outerDiv?.querySelector('.options')
if (ul) {
// Move the ul element to the portal
portalRef?.appendChild(ul)
}
})
let w = 0
let h = 0
let open = false
</script>
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
@@ -84,23 +105,46 @@
e.stopPropagation()
}
}}
use:floatingRef
bind:clientWidth={w}
bind:clientHeight={h}
>
{#if !value || Array.isArray(value)}
<MultiSelect
bind:outerDiv
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full'} `}
bind:selected={value}
on:change={() => {
outputs?.result.set([...(value ?? [])])
}}
options={Array.isArray(items) ? items : []}
placeholder={resolvedConfig.placeholder}
allowUserOptions={resolvedConfig.create}
on:open={() => {
$selectedComponent = [id]
}}
/>
<div style={`height:${h}px;`}>
<MultiSelect
bind:outerDiv
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full'} `}
ulOptionsClass={'p-2'}
bind:selected={value}
on:change={() => {
outputs?.result.set([...(value ?? [])])
}}
options={Array.isArray(items) ? items : []}
placeholder={resolvedConfig.placeholder}
allowUserOptions={resolvedConfig.create}
on:open={() => {
$selectedComponent = [id]
open = true
}}
on:close={() => {
open = false
}}
>
<div slot="option" let:option class="hover:bg-gray-200 p-1 cursor-pointer z-50">
{option}
</div>
</MultiSelect>
<Portal>
<div use:floatingContent class="z1000" hidden={!open}>
<div
bind:this={portalRef}
class="bg-white w-full border shadow-md rounded-md"
style={`min-width: ${w}px; `}
/>
</div>
</Portal>
</div>
{:else}
Value {value} is not an array
{/if}
@@ -112,7 +156,8 @@
padding: 0 !important;
overflow: auto;
}
.svelte-select-list {
.z1000 {
z-index: 1000 !important;
}
</style>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import Select from 'svelte-select'
import Select from '../../svelte-select/lib/index'
import type {
AppViewerContext,

View File

@@ -0,0 +1,18 @@
<svg width="100%" height="100%" viewBox="0 0 20 20" focusable="false" aria-hidden="true">
<path
fill="currentColor"
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747
3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0
1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502
0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0
0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
<style>
svg {
width: var(--chevron-icon-width, 20px);
height: var(--chevron-icon-width, 20px);
color: var(--chevron-icon-colour, currentColor);
}
</style>

After

Width:  |  Height:  |  Size: 618 B

View File

@@ -0,0 +1,22 @@
<svg
width="100%"
height="100%"
viewBox="-2 -2 50 50"
focusable="false"
aria-hidden="true"
role="presentation"
>
<path
fill="currentColor"
d="M34.923,37.251L24,26.328L13.077,37.251L9.436,33.61l10.923-10.923L9.436,11.765l3.641-3.641L24,19.047L34.923,8.124
l3.641,3.641L27.641,22.688L38.564,33.61L34.923,37.251z"
/>
</svg>
<style>
svg {
width: var(--clear-icon-width, 20px);
height: var(--clear-icon-width, 20px);
color: var(--clear-icon-color, currentColor);
}
</style>

After

Width:  |  Height:  |  Size: 494 B

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import Portal from 'svelte-portal'
export let condition = false
</script>
{#if condition}
<Portal><slot /></Portal>
{:else}
<slot />
{/if}

View File

@@ -0,0 +1,34 @@
<svg class="loading" viewBox="25 25 50 50">
<circle
class="circle_path"
cx="50"
cy="50"
r="20"
fill="none"
stroke="currentColor"
stroke-width="5"
stroke-miterlimit="10"
/>
</svg>
<style>
.loading {
width: var(--spinner-width, 20px);
height: var(--spinner-height, 20px);
color: var(--spinner-color, var(--icons-color));
animation: rotate 0.75s linear infinite;
transform-origin: center center;
transform: none;
}
.circle_path {
stroke-dasharray: 90;
stroke-linecap: round;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
</style>

After

Width:  |  Height:  |  Size: 591 B

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
export default function filter({
loadOptions,
filterText,
items,
value,
itemId,
groupBy,
filterSelectedItems,
itemFilter,
convertStringItemsToObjects,
filterGroupedItems,
label
}) {
if (items && loadOptions) return items
if (!items) return []
if (items && items.length > 0 && typeof items[0] !== 'object') {
items = convertStringItemsToObjects(items)
}
let filterResults = items.filter((item) => {
let matchesFilter = itemFilter(item[label], filterText, item)
return matchesFilter
})
if (groupBy) {
filterResults = filterGroupedItems(filterResults)
}
return filterResults
}

View File

@@ -0,0 +1,30 @@
export default async function getItems({
dispatch,
loadOptions,
convertStringItemsToObjects,
filterText
}) {
let res = await loadOptions(filterText).catch((err) => {
console.warn('svelte-select loadOptions error :>> ', err)
dispatch('error', { type: 'loadOptions', details: err })
})
if (res && !res.cancelled) {
if (res) {
if (res && res.length > 0 && typeof res[0] !== 'object') {
res = convertStringItemsToObjects(res)
}
dispatch('loaded', { items: res })
} else {
res = []
}
return {
filteredItems: res,
loading: false,
focused: true,
listOpen: true
}
}
}

View File

@@ -0,0 +1 @@
export { default as default } from './Select.svelte'

View File

@@ -0,0 +1,130 @@
@import 'tailwindcss/base';
.svelte-select {
@apply border rounded box-border h-10 relative flex items-center px-4 py-0 bg-white m-0 w-full
hover:border-gray-400;
}
.svelte-select input {
@apply cursor-default border-none text-gray-600 h-10 leading-10 px-4 py-0 bg-transparent text-sm absolute left-0 m-0 w-full
focus:outline-none hover:border-gray-400;
}
.svelte-select.focused {
@apply border-blue-600;
}
.svelte-select.disabled {
@apply bg-gray-200 border-gray-200 text-gray-600;
}
.svelte-select.disabled input {
@apply placeholder:text-gray-400 placeholder:opacity-100;
}
.svelte-select .selected-item {
@apply leading-10 h-10 overflow-x-hidden pr-5
focus:outline-none;
}
.svelte-select .icons {
@apply absolute flex items-center right-0 translate-y-0 text-gray-200 pointer-events-none top-0 bottom-0;
}
.svelte-select .icons > * {
@apply transition-colors ease-in-out duration-200;
}
.svelte-select .clear-select {
@apply pointer-events-auto;
}
.svelte-select.focused .icons,
.svelte-select .chevron:hover,
.svelte-select .clear-select:hover {
@apply text-gray-600;
}
.svelte-select .clear-select {
@apply px-2 h-5 text-gray-300 flex-none w-9;
}
.svelte-select .chevron {
@apply flex pt-0 pr-2 pl-2 border-l-2 w-9 h-5 text-gray-300;
}
.svelte-select.multi {
@apply pr-9 pl-4 h-auto flex-wrap items-stretch;
}
.svelte-select.multi input {
@apply p-0 relative m-0;
}
.svelte-select.error {
@apply border-red-500 bg-white;
}
.a11y-text {
@apply sr-only;
}
.list {
@apply shadow-md rounded-sm max-h-64 overflow-y-auto bg-white border-none absolute z-10 w-full left-0 right-0;
}
.list .list-group-title {
@apply text-gray-400 cursor-default text-sm font-medium h-10 leading-10 px-5 overflow-ellipsis whitespace-nowrap uppercase;
}
.list .empty {
@apply text-center py-5 text-gray-500;
}
.item {
@apply cursor-default h-10 leading-10 px-5 text-gray-800 overflow-ellipsis overflow-hidden whitespace-nowrap;
}
.item.group-item {
@apply px-10;
}
.item:active {
@apply bg-blue-200;
}
.item.active {
@apply bg-blue-600 text-white;
}
.item.not-selectable {
@apply text-gray-300;
}
.item.first {
@apply rounded-t-sm;
}
.item.hover:not(.active) {
@apply bg-blue-100;
}
.multi input {
flex: 1 1 40px;
}
.multi-item {
@apply bg-gray-100 mt-1 mr-1 border border-gray-200 rounded-sm h-8 leading-8 flex cursor-default pr-1 pl-1 max-w-full items-center mr-1 overflow-hidden overflow-ellipsis whitespace-nowrap;
}
.multi-item.disabled {
@apply hover:bg-gray-300 hover:text-gray-500;
}
.multi-item-clear {
@apply flex items-center justify-center w-5;
}
.list-item {
@apply list-none;
}