mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
wip
This commit is contained in:
@@ -4,6 +4,28 @@
|
||||
id: null,
|
||||
close: null
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a debounced function that delays invoking func until after wait milliseconds
|
||||
* have elapsed since the last time the debounced function was invoked.
|
||||
*/
|
||||
function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
return function (...args: Parameters<T>): void {
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
func(...args)
|
||||
timeoutId = null
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -16,7 +38,7 @@
|
||||
import ResolveOpen from '$lib/components/common/menu/ResolveOpen.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
|
||||
export let items: Item[] | (() => Item[]) | (() => Promise<Item[]>) = []
|
||||
export let disabled = false
|
||||
@@ -24,6 +46,8 @@
|
||||
export let usePointerDownOutside = false
|
||||
export let closeOnOtherDropdownOpen = true
|
||||
export let fixedHeight = true
|
||||
export let openOnHover = false
|
||||
export let hoverDelay = 200 // Delay for hover effects in milliseconds
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -38,6 +62,7 @@
|
||||
loop: true,
|
||||
onOpenChange: ({ curr, next }) => {
|
||||
if (curr !== next) {
|
||||
openByHover = false
|
||||
dispatch('openChange', next)
|
||||
}
|
||||
if (closeOnOtherDropdownOpen) {
|
||||
@@ -53,10 +78,16 @@
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
},
|
||||
forceVisible: true
|
||||
})
|
||||
|
||||
let open = false
|
||||
let hovering = false
|
||||
let openByHover = false
|
||||
let hoverTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let leaveTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const sync = createSync(states)
|
||||
$: sync.open(open, (v) => (open = Boolean(v)))
|
||||
|
||||
@@ -75,6 +106,29 @@
|
||||
async function getMenuElements(): Promise<HTMLElement[]> {
|
||||
return Array.from(document.querySelectorAll('[data-menu]')) as HTMLElement[]
|
||||
}
|
||||
|
||||
const setHovering = (value: boolean) => {
|
||||
hovering = value
|
||||
}
|
||||
|
||||
const debouncedHoverStart = debounce(() => setHovering(true), hoverDelay)
|
||||
const debouncedHoverEnd = debounce(() => setHovering(false), hoverDelay)
|
||||
|
||||
// Clear any pending timers on component destruction
|
||||
onDestroy(() => {
|
||||
if (hoverTimer) clearTimeout(hoverTimer)
|
||||
if (leaveTimer) clearTimeout(leaveTimer)
|
||||
})
|
||||
|
||||
$: if (openOnHover && hovering && !open) {
|
||||
open = true
|
||||
openByHover = true
|
||||
} else {
|
||||
if (openByHover && !hovering) {
|
||||
open = false
|
||||
openByHover = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ResolveOpen {open} on:open on:close />
|
||||
@@ -95,6 +149,14 @@
|
||||
close()
|
||||
}
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
if (leaveTimer) clearTimeout(leaveTimer)
|
||||
debouncedHoverStart()
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
if (hoverTimer) clearTimeout(hoverTimer)
|
||||
debouncedHoverEnd()
|
||||
}}
|
||||
data-menu
|
||||
>
|
||||
{#if $$slots.buttonReplacement}
|
||||
@@ -111,7 +173,20 @@
|
||||
</button>
|
||||
|
||||
{#if open}
|
||||
<div use:melt={$menu} data-menu class="z-[6000]">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
use:melt={$menu}
|
||||
data-menu
|
||||
class="z-[6000]"
|
||||
on:mouseenter={() => {
|
||||
if (leaveTimer) clearTimeout(leaveTimer)
|
||||
debouncedHoverStart()
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
if (hoverTimer) clearTimeout(hoverTimer)
|
||||
debouncedHoverEnd()
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="bg-surface border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
|
||||
>
|
||||
|
||||
@@ -1261,6 +1261,9 @@
|
||||
|
||||
let flowPreviewButtons: FlowPreviewButtons
|
||||
let isDropdownOpen = false
|
||||
let tooltipOpen = false
|
||||
$: tooltipOpen && msgInput && msgInput.focus()
|
||||
$: console.log('dbg isDropdownOpen', isDropdownOpen)
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
@@ -1469,22 +1472,29 @@
|
||||
Draft
|
||||
</Button>
|
||||
|
||||
<CustomPopover appearTimeout={0} focusEl={msgInput} disablePopup={isDropdownOpen}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
loading={loadingSave}
|
||||
size="xs"
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={async () => {
|
||||
await handleSaveFlow()
|
||||
}}
|
||||
dropdownItems={!newFlow ? dropdownItems : undefined}
|
||||
on:openChange={({ detail }) => (isDropdownOpen = detail)}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
<svelte:fragment slot="overlay">
|
||||
<div class="flex flex-row gap-2 w-80">
|
||||
<Button
|
||||
disabled={loading}
|
||||
loading={loadingSave}
|
||||
size="xs"
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={async () => {
|
||||
await handleSaveFlow()
|
||||
}}
|
||||
dropdownItems={!newFlow ? dropdownItems : undefined}
|
||||
on:open={() => {
|
||||
isDropdownOpen = true
|
||||
}}
|
||||
on:close={() => {
|
||||
isDropdownOpen = false
|
||||
}}
|
||||
on:tooltip-open={async ({ detail }) => {
|
||||
tooltipOpen = true
|
||||
}}
|
||||
dropdownOpenOnHover
|
||||
>
|
||||
Deploy
|
||||
<svelte:fragment slot="tooltip">
|
||||
<div class="flex flex-row gap-2 w-80 bg-surface rounded-md p-4 shadow-md">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Deployment message"
|
||||
@@ -1506,7 +1516,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</CustomPopover>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import { getModifierKey, type Item } from '$lib/utils'
|
||||
import { Loader2, ChevronDown } from 'lucide-svelte'
|
||||
import { createTooltip, melt } from '@melt-ui/svelte'
|
||||
|
||||
export let size: ButtonType.Size = 'md'
|
||||
export let spacingSize: ButtonType.Size = size
|
||||
@@ -34,6 +35,8 @@
|
||||
export let shortCut:
|
||||
| { key?: string; hide?: boolean; Icon?: any; withoutModifier?: boolean }
|
||||
| undefined = undefined
|
||||
export let dropdownOpen: boolean = false
|
||||
export let dropdownOpenOnHover: boolean = false
|
||||
|
||||
type MenuItem = {
|
||||
label: string
|
||||
@@ -117,6 +120,24 @@
|
||||
}
|
||||
|
||||
$: lucideIconSize = (iconMap[size] ?? 12) * 1
|
||||
|
||||
const {
|
||||
elements: { trigger, content }
|
||||
} = createTooltip({
|
||||
positioning: {
|
||||
placement: 'bottom-end'
|
||||
},
|
||||
openDelay: 0,
|
||||
closeDelay: 0,
|
||||
group: true,
|
||||
portal: 'body',
|
||||
onOpenChange: ({ curr, next }) => {
|
||||
if (curr != next) {
|
||||
dispatch('tooltip-open', next)
|
||||
}
|
||||
return next
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -200,6 +221,7 @@
|
||||
{...$$restProps}
|
||||
disabled={disabled || (loading && !clickableWhileLoading)}
|
||||
{style}
|
||||
use:melt={$trigger}
|
||||
>
|
||||
{#if loading}
|
||||
<Loader2 class={twMerge('animate-spin', iconOnlyPadding[size])} size={lucideIconSize} />
|
||||
@@ -232,8 +254,20 @@
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div use:melt={$content} class="z-[20000]">
|
||||
<slot name="tooltip" />
|
||||
</div>
|
||||
|
||||
{#if dropdownItems && dropdownItems.length > 0}
|
||||
<Dropdown items={computeDropdowns(dropdownItems)} class="h-auto w-fit" on:openChange>
|
||||
<Dropdown
|
||||
items={computeDropdowns(dropdownItems)}
|
||||
class="h-auto w-fit"
|
||||
on:openChange
|
||||
on:open
|
||||
on:close
|
||||
bind:open={dropdownOpen}
|
||||
openOnHover={dropdownOpenOnHover}
|
||||
>
|
||||
<svelte:fragment slot="buttonReplacement">
|
||||
<div
|
||||
class={twMerge(
|
||||
|
||||
Reference in New Issue
Block a user