mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 16:05:58 +00:00
popup rework (#1904)
* popup rework * feat(frontend): adapt all popup * feat(frontend): fix build * feat(frontend): fix calendar * feat(frontend): Fix merge
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import { forbiddenIds } from '$lib/components/flows/idUtils'
|
||||
import { ArrowRight, Pencil } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { fade, slide } from 'svelte/transition'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { Button, Popup } from '../../../../common'
|
||||
|
||||
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
@@ -13,7 +13,6 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const regex = /^[a-zA-Z][a-zA-Z0-9]*$/
|
||||
let value = id
|
||||
let button: HTMLButtonElement
|
||||
let input: HTMLInputElement
|
||||
let error = ''
|
||||
|
||||
@@ -39,25 +38,19 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click|stopPropagation={() => {
|
||||
$selectedComponent = [id]
|
||||
}}
|
||||
bind:this={button}
|
||||
title="Edit ID"
|
||||
class="flex items-center px-1 rounded-sm bg-surface-selected hover:text-primary text-tertiary"
|
||||
aria-label="Open component ID editor"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
<Popup
|
||||
ref={button}
|
||||
options={{ placement: 'top-start' }}
|
||||
transition={fade}
|
||||
wrapperClasses="!z-[1002]"
|
||||
outerClasses="rounded shadow-xl bg-surface border p-3"
|
||||
on:close={() => (value = id)}
|
||||
>
|
||||
<Popup floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
|
||||
<svelte:fragment slot="button">
|
||||
<button
|
||||
on:click={() => {
|
||||
$selectedComponent = [id]
|
||||
}}
|
||||
title="Edit ID"
|
||||
class="flex items-center px-1 rounded-sm bg-surface-secondary hover:text-primary text-secondary"
|
||||
aria-label="Open component ID editor"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
<label class="block text-primary">
|
||||
<div class="pb-1 text-sm text-secondary">Component ID</div>
|
||||
<div class="flex w-full">
|
||||
|
||||
+63
-74
@@ -2,16 +2,13 @@
|
||||
import type { StaticInput } from '../../../inputType'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { ClearableInput, Popup } from '../../../../common'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
export let componentInput: StaticInput<string>
|
||||
|
||||
let anchor: HTMLElement
|
||||
let loading = false
|
||||
let items: { label: string; icon: any }[]
|
||||
let filteredItems: { label: string; icon: any }[]
|
||||
let search = ''
|
||||
let openPopup: () => void
|
||||
|
||||
$: if (search) {
|
||||
filteredItems = items.filter((item) => {
|
||||
@@ -23,7 +20,6 @@
|
||||
|
||||
async function getData() {
|
||||
if (items) {
|
||||
return openPopup()
|
||||
}
|
||||
loading = true
|
||||
const data = await import('lucide-svelte/dist/svelte/icons')
|
||||
@@ -33,7 +29,6 @@
|
||||
.map(([key, icon]) => ({ label: key, icon }))
|
||||
|
||||
loading = false
|
||||
openPopup()
|
||||
}
|
||||
|
||||
function select(label: string) {
|
||||
@@ -46,76 +41,70 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={anchor} class="relative">
|
||||
<ClearableInput
|
||||
readonly
|
||||
value={componentInput.value}
|
||||
on:change={({ detail }) => (componentInput.value = detail)}
|
||||
on:focus={getData}
|
||||
class="!pr-6"
|
||||
/>
|
||||
{#if loading}
|
||||
<div class="center-center absolute right-2 top-1/2 transform -translate-y-1/2 p-0.5">
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
<Popup
|
||||
let:close
|
||||
floatingConfig={{
|
||||
strategy: 'absolute',
|
||||
placement: 'bottom-end'
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<div class="relative">
|
||||
<ClearableInput
|
||||
readonly
|
||||
value={componentInput.value}
|
||||
on:change={({ detail }) => (componentInput.value = detail)}
|
||||
on:focus={getData}
|
||||
class="!pr-6"
|
||||
/>
|
||||
{#if loading}
|
||||
<div class="center-center absolute right-2 top-1/2 transform -translate-y-1/2 p-0.5">
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if anchor}
|
||||
<Popup
|
||||
ref={anchor}
|
||||
closeOn={[]}
|
||||
options={{
|
||||
placement: 'bottom-start',
|
||||
strategy: 'fixed',
|
||||
modifiers: [{ name: 'offset', options: { offset: [0, 6] } }]
|
||||
}}
|
||||
bind:open={openPopup}
|
||||
let:close
|
||||
transition={fade}
|
||||
wrapperClasses=" z-[1002]"
|
||||
>
|
||||
{#if !loading}
|
||||
<div class="w-72 shadow-[0_10px_40px_-5px_rgba(0,0,0,0.25)] bg-surface rounded-md p-2">
|
||||
{#if filteredItems}
|
||||
<input
|
||||
on:keydown={(event) => {
|
||||
if (!['ArrowDown', 'ArrowUp'].includes(event.key)) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}}
|
||||
bind:value={search}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
class="col-span-4 mb-2"
|
||||
/>
|
||||
<div class="grid gap-1 grid-cols-4 max-h-[300px] overflow-auto">
|
||||
{#each filteredItems as { label, icon }}
|
||||
<button
|
||||
type="button"
|
||||
title={label}
|
||||
on:click={() => {
|
||||
select(label)
|
||||
close()
|
||||
}}
|
||||
class="w-full center-center flex-col font-normal p-1
|
||||
hover:bg-gray-100 focus:bg-gray-100 rounded duration-200
|
||||
</svelte:fragment>
|
||||
{#if !loading}
|
||||
{#if filteredItems}
|
||||
<div class="w-72">
|
||||
<input
|
||||
on:keydown={(event) => {
|
||||
if (!['ArrowDown', 'ArrowUp'].includes(event.key)) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}}
|
||||
bind:value={search}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
class="col-span-4 mb-2"
|
||||
/>
|
||||
<div class="grid gap-1 grid-cols-4 max-h-[300px] overflow-auto">
|
||||
{#each filteredItems as { label, icon }}
|
||||
<button
|
||||
type="button"
|
||||
title={label}
|
||||
on:click={() => {
|
||||
select(label)
|
||||
close(null)
|
||||
}}
|
||||
class="w-full center-center flex-col font-normal p-1
|
||||
hover:bg-gray-100 focus:bg-gray-100 rounded duration-200 dark:hover:bg-frost-900 dark:focus:bg-frost-900
|
||||
{label === componentInput.value ? 'text-blue-600 bg-blue-50 pointer-events-none' : ''}"
|
||||
>
|
||||
<svelte:component this={icon} size={22} />
|
||||
<span class="inline-block w-full text-[10px] ellipsize pt-0.5">
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="col-span-4 text-center text-secondary text-sm p-2">
|
||||
No icons match your search
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center text-sm text-secondary p-2"> Couldn't load options </div>
|
||||
{/if}
|
||||
>
|
||||
<svelte:component this={icon} size={22} />
|
||||
<span class="inline-block w-full text-[10px] ellipsize pt-0.5">
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="col-span-4 text-center text-secondary text-sm p-2">
|
||||
No icons match your search
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center text-sm text-secondary p-2"> Couldn't load options </div>
|
||||
{/if}
|
||||
</Popup>
|
||||
{/if}
|
||||
{/if}
|
||||
</Popup>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import type Editor from '../Editor.svelte'
|
||||
import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons'
|
||||
import Popup from '../common/popup/Popup.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import { dbSchema, existsOpenaiResourcePath } from '$lib/stores'
|
||||
import type DiffEditor from '../DiffEditor.svelte'
|
||||
@@ -38,12 +37,6 @@
|
||||
return
|
||||
}
|
||||
try {
|
||||
// close popup ^^
|
||||
const elem = document.activeElement as HTMLElement
|
||||
if (elem.blur) {
|
||||
elem.blur()
|
||||
}
|
||||
|
||||
genLoading = true
|
||||
if (isEdit && selection) {
|
||||
const selectedCode = editor?.getSelectedLines() || ''
|
||||
@@ -171,41 +164,40 @@
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if inlineScript}
|
||||
<Button
|
||||
size="lg"
|
||||
bind:element={button}
|
||||
color="light"
|
||||
btnClasses="!px-2 !bg-surface-secondary hover:!bg-surface-hover"
|
||||
loading={genLoading}
|
||||
>
|
||||
<Icon scale={0.8} data={faMagicWandSparkles} />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
title="Generate code from prompt"
|
||||
btnClasses="!font-medium text-scondary"
|
||||
size="xs"
|
||||
color="light"
|
||||
spacingSize="md"
|
||||
bind:element={button}
|
||||
startIcon={{ icon: faMagicWandSparkles }}
|
||||
{iconOnly}
|
||||
loading={genLoading}
|
||||
>
|
||||
{isEdit ? 'AI Edit' : 'AI Gen'}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if !generatedCode && !genLoading}
|
||||
<Popup
|
||||
ref={button}
|
||||
options={{ placement: 'top-start' }}
|
||||
transition={fade}
|
||||
closeOn={[]}
|
||||
wrapperClasses="!z-[1002]"
|
||||
outerClasses="rounded shadow-xl bg-surface border p-3 w-96"
|
||||
>
|
||||
<label class="block text-primary">
|
||||
<Popup floatingConfig={{ placement: 'bottom-end', strategy: 'absolute' }}>
|
||||
<svelte:fragment slot="button">
|
||||
{#if inlineScript}
|
||||
<Button
|
||||
size="lg"
|
||||
bind:element={button}
|
||||
color="light"
|
||||
btnClasses="!px-2 !bg-surface-secondary hover:!bg-surface-hover"
|
||||
loading={genLoading}
|
||||
nonCaptureEvent={true}
|
||||
>
|
||||
<Icon scale={0.8} data={faMagicWandSparkles} />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
title="Generate code from prompt"
|
||||
btnClasses="!font-medium text-secondary"
|
||||
size="xs"
|
||||
color="light"
|
||||
spacingSize="md"
|
||||
bind:element={button}
|
||||
startIcon={{ icon: faMagicWandSparkles }}
|
||||
{iconOnly}
|
||||
loading={genLoading}
|
||||
nonCaptureEvent={true}
|
||||
>
|
||||
{isEdit ? 'AI Edit' : 'AI Gen'}
|
||||
</Button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<label class="block text-primary w-96">
|
||||
<div class="pb-1 text-sm text-secondary">Prompt</div>
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="text"
|
||||
@@ -234,11 +226,12 @@
|
||||
</Button>
|
||||
</div>
|
||||
{#if lang === 'postgresql' && $dbSchema}
|
||||
<p class="text-sm mt-2"
|
||||
>Will take into account the DB schema <Tooltip
|
||||
>In order to better generate the script, we pass the selected DB schema to GPT-4.</Tooltip
|
||||
></p
|
||||
>
|
||||
<p class="text-sm mt-2">
|
||||
Will take into account the DB schema
|
||||
<Tooltip>
|
||||
In order to better generate the script, we pass the selected DB schema to GPT-4.
|
||||
</Tooltip>
|
||||
</p>
|
||||
{/if}
|
||||
</label>
|
||||
</Popup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ArrowRight, Calendar } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Button, Popup } from '..'
|
||||
|
||||
export let date: string | undefined
|
||||
@@ -9,7 +8,6 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let value = date
|
||||
let button: HTMLButtonElement
|
||||
let input: HTMLInputElement
|
||||
|
||||
$: if (date && input) {
|
||||
@@ -21,37 +19,37 @@
|
||||
input.blur()
|
||||
}
|
||||
|
||||
function close() {
|
||||
const elem = document.activeElement as HTMLElement
|
||||
if (elem.blur) {
|
||||
elem.blur()
|
||||
function bg(e: KeyboardEvent) {
|
||||
if (e.key === 'Tab') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={button}
|
||||
title="Open calendar picker"
|
||||
class="absolute bottom-1 right-1 top-1 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface border rounded center-center hover:bg-gray-50 transition-all cursor-pointer"
|
||||
aria-label="Open calendar picker"
|
||||
on:click={() => {
|
||||
input.focus()
|
||||
}}
|
||||
>
|
||||
<Calendar size={14} />
|
||||
</button>
|
||||
<Popup
|
||||
ref={button}
|
||||
options={{ placement: 'top-start' }}
|
||||
transition={fade}
|
||||
wrapperClasses="!z-[1002]"
|
||||
outerClasses="rounded shadow-xl bg-surface border p-3"
|
||||
closeOn={[]}
|
||||
>
|
||||
<Popup floatingConfig={{ placement: 'top-end', strategy: 'absolute' }} let:close>
|
||||
<svelte:fragment slot="button">
|
||||
<button
|
||||
title="Open calendar picker"
|
||||
class="absolute bottom-1 right-2 top-1 py-1 min-w-min !px-2 items-center text-primary bg-surface border rounded center-center hover:bg-surface-hover transition-all cursor-pointer"
|
||||
aria-label="Open calendar picker"
|
||||
on:click={() => {
|
||||
input?.focus()
|
||||
}}
|
||||
>
|
||||
<Calendar size={14} />
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
|
||||
<label class="block text-primary">
|
||||
<div class="pb-1 text-sm text-tertiary">{label}</div>
|
||||
<div class="pb-1 text-sm text-secondary">{label}</div>
|
||||
<div class="flex w-full">
|
||||
<input type="datetime-local" bind:value class="!w-auto grow" bind:this={input} />
|
||||
<input
|
||||
type="datetime-local"
|
||||
bind:value
|
||||
class="!w-auto grow"
|
||||
bind:this={input}
|
||||
on:keydown={bg}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
@@ -60,7 +58,7 @@
|
||||
aria-label="Save ID"
|
||||
on:click={() => {
|
||||
save()
|
||||
close()
|
||||
close(null)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
|
||||
@@ -1,173 +1,40 @@
|
||||
<svelte:options accessors />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { slide, type TransitionConfig } from 'svelte/transition'
|
||||
import { createPopperActions, type PopperOptions } from 'svelte-popperjs'
|
||||
import { clickOutside } from '../../../utils'
|
||||
import { createStateMachine } from '../../../stateMachine'
|
||||
import { Popover, PopoverButton, PopoverPanel, Transition } from '@rgossiaux/svelte-headlessui'
|
||||
import Portal from 'svelte-portal'
|
||||
import { createFloatingActions, type ComputeConfig } from 'svelte-floating-ui'
|
||||
|
||||
export let ref: HTMLElement | undefined
|
||||
export let options: PopperOptions<any> = { placement: 'auto', strategy: 'fixed' }
|
||||
/** Events on the reference element */
|
||||
export let openOn: (keyof HTMLElementEventMap)[] = ['focus']
|
||||
/** Events on the reference element */
|
||||
export let closeOn: (keyof HTMLElementEventMap)[] = ['blur']
|
||||
export let innerClasses = ''
|
||||
export let outerClasses = ''
|
||||
export let wrapperClasses = ''
|
||||
export let transition: (node: Element, params?: Record<string, any>) => TransitionConfig = slide
|
||||
export { openFocusIn as open, closed as close }
|
||||
|
||||
const states = ['closed', 'open-focus-in', 'open-focus-out'] as const
|
||||
const stateMachine = createStateMachine(states, {
|
||||
to: {
|
||||
closed: ({ previousState, currentState }) => {
|
||||
return isFocusContained() ? previousState : currentState
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const [popperRef, popperContent, getInstance] = createPopperActions()
|
||||
const dispatch = createEventDispatcher()
|
||||
let popup: HTMLElement | undefined
|
||||
let focusableElements: HTMLElement[]
|
||||
|
||||
function getFocusableElements() {
|
||||
let elements: HTMLElement[] = []
|
||||
|
||||
popup
|
||||
?.querySelectorAll<HTMLElement>(
|
||||
'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
.forEach((elem) => elements.push(elem))
|
||||
|
||||
focusableElements = elements.filter(
|
||||
(el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden')
|
||||
)
|
||||
focusableElements.forEach((el) => {
|
||||
el.tabIndex = -1
|
||||
el.addEventListener('click', openFocusIn)
|
||||
el.addEventListener('blur', conditionalClosed)
|
||||
})
|
||||
export let floatingConfig: ComputeConfig = {
|
||||
strategy: 'absolute',
|
||||
placement: 'bottom-start'
|
||||
}
|
||||
|
||||
function isFocusContained() {
|
||||
const activeElem = document.activeElement
|
||||
return popup?.contains(activeElem) || ref?.contains(activeElem)
|
||||
}
|
||||
export let containerClasses: string = 'rounded-lg shadow-lg p-4 bg-surface'
|
||||
|
||||
function closed() {
|
||||
if ($stateMachine.currentState === 'open-focus-out') {
|
||||
setTimeout(() => {
|
||||
stateMachine.setState('closed')
|
||||
dispatch('close')
|
||||
}, 0)
|
||||
} else {
|
||||
stateMachine.setState('closed')
|
||||
dispatch('close')
|
||||
}
|
||||
}
|
||||
function conditionalClosed() {
|
||||
setTimeout(() => {
|
||||
if (isFocusContained()) return
|
||||
closed()
|
||||
}, 0)
|
||||
}
|
||||
function openFocusOut() {
|
||||
stateMachine.setState('open-focus-out')
|
||||
}
|
||||
function openFocusIn() {
|
||||
stateMachine.setState('open-focus-in')
|
||||
}
|
||||
|
||||
function keyDown(event: KeyboardEvent & { currentTarget: EventTarget & Window }) {
|
||||
const modifiers = ['Shift', 'Control', 'Command', 'Alt', 'Meta']
|
||||
// Prevent closing the popup when the only key pressed is a modifier key
|
||||
if (modifiers.includes(event.key) || $stateMachine.currentState === 'closed') return
|
||||
if (event.key === 'Escape') {
|
||||
;(<HTMLElement>document.activeElement)?.blur()
|
||||
return conditionalClosed()
|
||||
}
|
||||
|
||||
const prev = ['Up', 'Left']
|
||||
const next = ['Down', 'Right']
|
||||
if (![...prev, ...next].some((dir) => `Arrow${dir}` === event.key)) return
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
if (popup?.contains(document.activeElement)) {
|
||||
const index = focusableElements.findIndex((elem) => elem === document.activeElement)
|
||||
if (index === -1) return
|
||||
|
||||
let targetIndex: number | undefined = undefined
|
||||
if (prev.some((dir) => `Arrow${dir}` === event.key)) {
|
||||
targetIndex = index === 0 ? focusableElements.length - 1 : index - 1
|
||||
} else if (next.some((dir) => `Arrow${dir}` === event.key)) {
|
||||
targetIndex = index + 1 === focusableElements.length ? 0 : index + 1
|
||||
}
|
||||
if (targetIndex !== undefined) {
|
||||
focusableElements[targetIndex].focus()
|
||||
stateMachine.setState('open-focus-in')
|
||||
}
|
||||
} else {
|
||||
const elem = focusableElements[event.key === 'ArrowUp' ? focusableElements.length - 1 : 0]
|
||||
if (elem) {
|
||||
elem.focus()
|
||||
stateMachine.setState('open-focus-in')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addRefListeners() {
|
||||
if (!ref) return
|
||||
openOn.forEach((action) => ref!.addEventListener(action, openFocusOut))
|
||||
closeOn.forEach((action) => ref!.addEventListener(action, closed))
|
||||
}
|
||||
|
||||
function removeAllListeners() {
|
||||
focusableElements?.forEach((el) => el.removeEventListener('click', openFocusIn))
|
||||
focusableElements?.forEach((el) => el.removeEventListener('blur', conditionalClosed))
|
||||
if (!ref) return
|
||||
openOn.forEach((action) => ref!.removeEventListener(action, openFocusOut))
|
||||
closeOn.forEach((action) => ref!.removeEventListener(action, closed))
|
||||
}
|
||||
|
||||
$: if ($stateMachine.currentState === 'closed') {
|
||||
focusableElements?.forEach((el) => el.removeEventListener('click', openFocusIn))
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
getFocusableElements()
|
||||
getInstance()?.update()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
$: if (ref) {
|
||||
popperRef(ref)
|
||||
addRefListeners()
|
||||
}
|
||||
|
||||
$: $$slots.default && getInstance()?.update()
|
||||
|
||||
onDestroy(removeAllListeners)
|
||||
const [floatingRef, floatingContent] = createFloatingActions(floatingConfig)
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keyDown} />
|
||||
|
||||
<div
|
||||
class="z-50 {wrapperClasses}"
|
||||
bind:this={popup}
|
||||
use:popperContent={options}
|
||||
use:clickOutside
|
||||
on:click_outside={closed}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={$stateMachine.currentState !== 'closed'}
|
||||
>
|
||||
{#if $stateMachine.currentState !== 'closed'}
|
||||
<div transition:transition|local={{ duration: 100 }} class={outerClasses}>
|
||||
<div class={innerClasses}>
|
||||
<slot open={openFocusIn} close={closed} />
|
||||
</div>
|
||||
<Popover let:open on:close>
|
||||
<PopoverButton>
|
||||
<div use:floatingRef>
|
||||
<slot name="button" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PopoverButton>
|
||||
<Portal>
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<Transition
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<PopoverPanel let:close>
|
||||
<div class={containerClasses}>
|
||||
<slot {close} />
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Transition>
|
||||
</div>
|
||||
</Portal>
|
||||
</Popover>
|
||||
|
||||
@@ -315,16 +315,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative w-full">
|
||||
<span class="text-xs absolute -top-4">Max datetime</span>
|
||||
<input type="text" value={maxTs ?? 'zoom x axis to set max'} disabled />
|
||||
<CalendarPicker
|
||||
date={maxTs}
|
||||
label="Max datetimes"
|
||||
on:change={async ({ detail }) => {
|
||||
maxTs = new Date(detail).toISOString()
|
||||
jobs = await fetchJobs(maxTs, minTs)
|
||||
}}
|
||||
/>
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<span class="text-xs absolute -top-4">Max datetime</span>
|
||||
<input type="text" value={maxTs ?? 'zoom x axis to set max'} disabled />
|
||||
<CalendarPicker
|
||||
date={maxTs}
|
||||
label="Max datetimes"
|
||||
on:change={async ({ detail }) => {
|
||||
maxTs = new Date(detail).toISOString()
|
||||
jobs = await fetchJobs(maxTs, minTs)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
color="light"
|
||||
|
||||
Reference in New Issue
Block a user