mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-30 02:37:46 +03:00
Basic focus management support (#1719)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
fe109a6b30
commit
fee0f28b07
@ -389,7 +389,7 @@ export function createModel (builder: Builder): void {
|
|||||||
action: view.actionImpl.SelectItem,
|
action: view.actionImpl.SelectItem,
|
||||||
keyBinding: ['keyX'],
|
keyBinding: ['keyX'],
|
||||||
category: view.category.General,
|
category: view.category.General,
|
||||||
input: 'focus',
|
input: 'any',
|
||||||
target: core.class.Doc,
|
target: core.class.Doc,
|
||||||
context: { mode: 'browser' }
|
context: { mode: 'browser' }
|
||||||
},
|
},
|
||||||
@ -434,7 +434,7 @@ export function createModel (builder: Builder): void {
|
|||||||
input: 'none',
|
input: 'none',
|
||||||
target: core.class.Doc,
|
target: core.class.Doc,
|
||||||
context: {
|
context: {
|
||||||
mode: ['workbench', 'browser', 'popup', 'panel', 'editor', 'input']
|
mode: ['workbench', 'browser', 'panel', 'editor', 'input']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
view.action.ShowActions
|
view.action.ShowActions
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
{#if $$slots.space}
|
{#if $$slots.space}
|
||||||
<slot name="space" />
|
<slot name="space" />
|
||||||
{:else if spaceClass && spaceLabel && spacePlaceholder}
|
{:else if spaceClass && spaceLabel && spacePlaceholder}
|
||||||
<SpaceSelect _class={spaceClass} {spaceQuery} label={spaceLabel} bind:value={space} />
|
<SpaceSelect focus focusIndex={-10} _class={spaceClass} {spaceQuery} label={spaceLabel} bind:value={space} />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="antiCard-header__divider">›</span>
|
<span class="antiCard-header__divider">›</span>
|
||||||
{/if}
|
{/if}
|
||||||
@ -53,6 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="buttons-group small-gap">
|
<div class="buttons-group small-gap">
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={10002}
|
||||||
icon={IconClose}
|
icon={IconClose}
|
||||||
kind={'transparent'}
|
kind={'transparent'}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@ -73,6 +74,7 @@
|
|||||||
<MiniToggle label={presentation.string.CreateMore} bind:on={createMore} />
|
<MiniToggle label={presentation.string.CreateMore} bind:on={createMore} />
|
||||||
{/if}
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={10001}
|
||||||
disabled={!canSave}
|
disabled={!canSave}
|
||||||
label={okLabel}
|
label={okLabel}
|
||||||
kind={'primary'}
|
kind={'primary'}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { getClient } from '../utils'
|
import { getClient } from '../utils'
|
||||||
|
|
||||||
import { Label, showPopup, IconFolder, Button, eventToHTMLElement } from '@anticrm/ui'
|
import { Label, showPopup, IconFolder, Button, eventToHTMLElement, getFocusManager } from '@anticrm/ui'
|
||||||
import SpacesPopup from './SpacesPopup.svelte'
|
import SpacesPopup from './SpacesPopup.svelte'
|
||||||
|
|
||||||
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
|
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
|
||||||
@ -25,11 +25,14 @@
|
|||||||
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
|
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
|
||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
export let value: Ref<Space> | undefined
|
export let value: Ref<Space> | undefined
|
||||||
|
export let focusIndex = -1
|
||||||
|
export let focus = false
|
||||||
|
|
||||||
let selected: Space | undefined
|
let selected: Space | undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
|
const mgr = getFocusManager()
|
||||||
async function updateSelected (value: Ref<Space> | undefined) {
|
async function updateSelected (value: Ref<Space> | undefined) {
|
||||||
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
||||||
}
|
}
|
||||||
@ -38,6 +41,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
{focus}
|
||||||
|
{focusIndex}
|
||||||
icon={IconFolder}
|
icon={IconFolder}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
kind={'no-border'}
|
kind={'no-border'}
|
||||||
@ -45,6 +50,7 @@
|
|||||||
showPopup(SpacesPopup, { _class, spaceQuery }, eventToHTMLElement(ev), (result) => {
|
showPopup(SpacesPopup, { _class, spaceQuery }, eventToHTMLElement(ev), (result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
value = result._id
|
value = result._id
|
||||||
|
mgr?.setFocusPos(focusIndex)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import { createQuery } from '../utils'
|
import { createQuery } from '../utils'
|
||||||
import SpaceInfo from './SpaceInfo.svelte'
|
import SpaceInfo from './SpaceInfo.svelte'
|
||||||
import presentation from '..'
|
import presentation from '..'
|
||||||
|
import { ListView } from '@anticrm/ui'
|
||||||
|
|
||||||
export let _class: Ref<Class<Space>>
|
export let _class: Ref<Class<Space>>
|
||||||
export let spaceQuery: DocumentQuery<Space> | undefined
|
export let spaceQuery: DocumentQuery<Space> | undefined
|
||||||
@ -42,24 +43,64 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (input) input.focus()
|
if (input) input.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let selection = 0
|
||||||
|
let list: ListView
|
||||||
|
|
||||||
|
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
|
||||||
|
const space = objects[selection]
|
||||||
|
dispatch('close', space)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeydown (key: KeyboardEvent): void {
|
||||||
|
if (key.code === 'ArrowUp') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection - 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowDown') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection + 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'Enter') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
handleSelection(key, selection)
|
||||||
|
}
|
||||||
|
if (key.code === 'Escape') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
dispatch('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="selectPopup">
|
<div class="selectPopup" on:keydown={onKeydown}>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} on:input={() => {}} on:change />
|
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} on:input={() => {}} on:change />
|
||||||
</div>
|
</div>
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
{#each objects as space}
|
<ListView
|
||||||
<button
|
bind:this={list}
|
||||||
class="menu-item flex-between"
|
count={objects.length}
|
||||||
on:click={() => {
|
bind:selection
|
||||||
dispatch('close', space)
|
on:click={(evt) => handleSelection(evt, evt.detail)}
|
||||||
}}
|
>
|
||||||
>
|
<svelte:fragment slot="item" let:item>
|
||||||
<SpaceInfo size={'large'} value={space} />
|
{@const space = objects[item]}
|
||||||
</button>
|
|
||||||
{/each}
|
<button
|
||||||
|
class="menu-item flex-between"
|
||||||
|
on:click={() => {
|
||||||
|
handleSelection(undefined, item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SpaceInfo size={'large'} value={space} />
|
||||||
|
</button>
|
||||||
|
</svelte:fragment>
|
||||||
|
</ListView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import contact, { Contact, formatName } from '@anticrm/contact'
|
import contact, { Contact, formatName } from '@anticrm/contact'
|
||||||
import type { Class, Ref } from '@anticrm/core'
|
import type { Class, Ref } from '@anticrm/core'
|
||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import type { TooltipAlignment, ButtonKind, ButtonSize } from '@anticrm/ui'
|
import { TooltipAlignment, ButtonKind, ButtonSize, getFocusManager } from '@anticrm/ui'
|
||||||
import { Button, Label, showPopup, Tooltip } from '@anticrm/ui'
|
import { Button, Label, showPopup, Tooltip } from '@anticrm/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import presentation from '..'
|
import presentation from '..'
|
||||||
@ -38,6 +38,7 @@
|
|||||||
export let justify: 'left' | 'center' = 'center'
|
export let justify: 'left' | 'center' = 'center'
|
||||||
export let width: string | undefined = undefined
|
export let width: string | undefined = undefined
|
||||||
export let labelDirection: TooltipAlignment | undefined = undefined
|
export let labelDirection: TooltipAlignment | undefined = undefined
|
||||||
|
export let focusIndex = -1
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -56,11 +57,13 @@
|
|||||||
const isPerson = client.getHierarchy().isDerived(obj._class, contact.class.Person)
|
const isPerson = client.getHierarchy().isDerived(obj._class, contact.class.Person)
|
||||||
return isPerson ? formatName(obj.name) : obj.name
|
return isPerson ? formatName(obj.name) : obj.name
|
||||||
}
|
}
|
||||||
|
const mgr = getFocusManager()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={container} class="min-w-0">
|
<div bind:this={container} class="min-w-0">
|
||||||
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
|
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
|
||||||
<Button
|
<Button
|
||||||
|
{focusIndex}
|
||||||
icon={size === 'x-large' && selected ? undefined : IconPerson}
|
icon={size === 'x-large' && selected ? undefined : IconPerson}
|
||||||
width={width ?? 'min-content'}
|
width={width ?? 'min-content'}
|
||||||
{size}
|
{size}
|
||||||
@ -81,6 +84,7 @@
|
|||||||
value = result._id
|
value = result._id
|
||||||
dispatch('change', value)
|
dispatch('change', value)
|
||||||
}
|
}
|
||||||
|
mgr?.setFocusPos(focusIndex)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { translate } from '@anticrm/platform'
|
import { translate } from '@anticrm/platform'
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
|
|
||||||
import { Tooltip, CheckBox } from '@anticrm/ui'
|
import { Tooltip, CheckBox, ListView } from '@anticrm/ui'
|
||||||
import UserInfo from './UserInfo.svelte'
|
import UserInfo from './UserInfo.svelte'
|
||||||
|
|
||||||
import type { Ref, Class } from '@anticrm/core'
|
import type { Ref, Class } from '@anticrm/core'
|
||||||
@ -70,43 +70,85 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (input) input.focus()
|
if (input) input.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let selection = 0
|
||||||
|
let list: ListView
|
||||||
|
|
||||||
|
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
|
||||||
|
const person = objects[selection]
|
||||||
|
|
||||||
|
if (!multiSelect) {
|
||||||
|
selected = person._id === selected ? undefined : person._id
|
||||||
|
dispatch('close', selected !== undefined ? person : undefined)
|
||||||
|
} else {
|
||||||
|
checkSelected(person)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeydown (key: KeyboardEvent): void {
|
||||||
|
if (key.code === 'ArrowUp') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection - 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowDown') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection + 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'Enter') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
handleSelection(key, selection)
|
||||||
|
}
|
||||||
|
if (key.code === 'Escape') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
dispatch('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="selectPopup" class:plainContainer={!shadows}>
|
<div class="selectPopup" class:plainContainer={!shadows} on:keydown={onKeydown}>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} on:change />
|
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} on:change />
|
||||||
</div>
|
</div>
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
{#each objects as person}
|
<ListView
|
||||||
<button
|
bind:this={list}
|
||||||
class="menu-item"
|
count={objects.length}
|
||||||
on:click={() => {
|
bind:selection
|
||||||
if (!multiSelect) {
|
on:click={(evt) => handleSelection(evt, evt.detail)}
|
||||||
selected = person._id === selected ? undefined : person._id
|
>
|
||||||
dispatch('close', selected !== undefined ? person : undefined)
|
<svelte:fragment slot="item" let:item>
|
||||||
} else checkSelected(person)
|
{@const person = objects[item]}
|
||||||
}}
|
<button
|
||||||
>
|
class="menu-item w-full"
|
||||||
{#if multiSelect}
|
on:click={() => {
|
||||||
<div class="check pointer-events-none">
|
handleSelection(undefined, item)
|
||||||
<CheckBox checked={isSelected(person)} primary />
|
}}
|
||||||
</div>
|
>
|
||||||
{/if}
|
{#if multiSelect}
|
||||||
<UserInfo size={'x-small'} value={person} />
|
<div class="check pointer-events-none">
|
||||||
{#if allowDeselect && person._id === selected}
|
<CheckBox checked={isSelected(person)} primary />
|
||||||
<div class="check-right pointer-events-none">
|
</div>
|
||||||
{#if titleDeselect}
|
{/if}
|
||||||
<Tooltip label={titleDeselect ?? presentation.string.Deselect}>
|
<UserInfo size={'x-small'} value={person} />
|
||||||
|
{#if allowDeselect && person._id === selected}
|
||||||
|
<div class="check-right pointer-events-none">
|
||||||
|
{#if titleDeselect}
|
||||||
|
<Tooltip label={titleDeselect ?? presentation.string.Deselect}>
|
||||||
|
<CheckBox checked circle primary />
|
||||||
|
</Tooltip>
|
||||||
|
{:else}
|
||||||
<CheckBox checked circle primary />
|
<CheckBox checked circle primary />
|
||||||
</Tooltip>
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<CheckBox checked circle primary />
|
{/if}
|
||||||
{/if}
|
</button>
|
||||||
</div>
|
</svelte:fragment>
|
||||||
{/if}
|
</ListView>
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,12 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IntlString, Asset } from '@anticrm/platform'
|
import type { Asset, IntlString } from '@anticrm/platform'
|
||||||
import type { AnySvelteComponent, ButtonKind, ButtonSize } from '../types'
|
|
||||||
import Spinner from './Spinner.svelte'
|
|
||||||
import Label from './Label.svelte'
|
|
||||||
import Icon from './Icon.svelte'
|
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
import { registerFocus } from '../focus'
|
||||||
|
import type { AnySvelteComponent, ButtonKind, ButtonSize } from '../types'
|
||||||
|
import Icon from './Icon.svelte'
|
||||||
|
import Label from './Label.svelte'
|
||||||
|
import Spinner from './Spinner.svelte'
|
||||||
|
|
||||||
export let label: IntlString | undefined = undefined
|
export let label: IntlString | undefined = undefined
|
||||||
export let labelParams: Record<string, any> = {}
|
export let labelParams: Record<string, any> = {}
|
||||||
@ -38,7 +39,6 @@
|
|||||||
export let title: string | undefined = undefined
|
export let title: string | undefined = undefined
|
||||||
export let borderStyle: 'solid' | 'dashed' = 'solid'
|
export let borderStyle: 'solid' | 'dashed' = 'solid'
|
||||||
export let id: string | undefined = undefined
|
export let id: string | undefined = undefined
|
||||||
|
|
||||||
export let input: HTMLButtonElement | undefined = undefined
|
export let input: HTMLButtonElement | undefined = undefined
|
||||||
|
|
||||||
$: iconOnly = label === undefined && $$slots.content === undefined
|
$: iconOnly = label === undefined && $$slots.content === undefined
|
||||||
@ -53,8 +53,31 @@
|
|||||||
click = false
|
click = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Focusable control with index
|
||||||
|
export let focusIndex = -1
|
||||||
|
const { idx, focusManager } = registerFocus(focusIndex, {
|
||||||
|
focus: () => {
|
||||||
|
if (!disabled) {
|
||||||
|
input?.focus()
|
||||||
|
}
|
||||||
|
return !disabled && input != null
|
||||||
|
},
|
||||||
|
isFocus: () => document.activeElement === input
|
||||||
|
})
|
||||||
|
|
||||||
|
$: if (idx !== -1 && focusManager) {
|
||||||
|
focusManager.updateFocus(idx, focusIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (input != null) {
|
||||||
|
input.addEventListener('focus', () => {
|
||||||
|
focusManager?.setFocus(idx)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- {focusIndex} -->
|
||||||
<button
|
<button
|
||||||
bind:this={input}
|
bind:this={input}
|
||||||
class="button {kind} {size} jf-{justify}"
|
class="button {kind} {size} jf-{justify}"
|
||||||
@ -172,7 +195,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--primary-edit-border-color);
|
border-color: var(--accent-color) !important;
|
||||||
}
|
}
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: rgb(var(--caption-color) / 40%);
|
color: rgb(var(--caption-color) / 40%);
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { translate } from '@anticrm/platform'
|
import { translate } from '@anticrm/platform'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import { getPlatformColor } from '..'
|
import { getPlatformColor, ListView } from '..'
|
||||||
|
|
||||||
export let placeholder: IntlString | undefined = undefined
|
export let placeholder: IntlString | undefined = undefined
|
||||||
export let placeholderParam: any | undefined = undefined
|
export let placeholderParam: any | undefined = undefined
|
||||||
@ -33,27 +33,79 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: objects = value.filter((el) => el.label.toLowerCase().includes(search.toLowerCase()))
|
||||||
|
|
||||||
|
let selection = 0
|
||||||
|
let list: ListView
|
||||||
|
|
||||||
|
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
|
||||||
|
const space = objects[selection]
|
||||||
|
dispatch('close', space)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeydown (key: KeyboardEvent): void {
|
||||||
|
if (key.code === 'ArrowUp') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection - 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowDown') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection + 1)
|
||||||
|
}
|
||||||
|
if (key.code === 'Enter') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
handleSelection(key, selection)
|
||||||
|
}
|
||||||
|
if (key.code === 'Escape') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
dispatch('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let input: HTMLElement
|
||||||
|
onMount(() => {
|
||||||
|
if (input) input.focus()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="selectPopup">
|
<div class="selectPopup" on:keydown={onKeydown}>
|
||||||
{#if searchable}
|
{#if searchable}
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<input type="text" bind:value={search} placeholder={phTraslate} on:input={(ev) => {}} on:change />
|
<input
|
||||||
|
bind:this={input}
|
||||||
|
type="text"
|
||||||
|
bind:value={search}
|
||||||
|
placeholder={phTraslate}
|
||||||
|
on:input={(ev) => {}}
|
||||||
|
on:change
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
{#each value.filter((el) => el.label.toLowerCase().includes(search.toLowerCase())) as item}
|
<ListView
|
||||||
<button
|
bind:this={list}
|
||||||
class="menu-item"
|
count={objects.length}
|
||||||
on:click={() => {
|
bind:selection
|
||||||
dispatch('close', item)
|
on:click={(evt) => handleSelection(evt, evt.detail)}
|
||||||
}}
|
>
|
||||||
>
|
<svelte:fragment slot="item" let:item>
|
||||||
<div class="color" style="background-color: {getPlatformColor(item.color)}" />
|
{@const itemValue = objects[item]}
|
||||||
<span class="label">{item.label}</span>
|
<button
|
||||||
</button>
|
class="menu-item"
|
||||||
{/each}
|
on:click={() => {
|
||||||
|
dispatch('close', itemValue)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="color" style="background-color: {getPlatformColor(itemValue.color)}" />
|
||||||
|
<span class="label">{itemValue.label}</span>
|
||||||
|
</button>
|
||||||
|
</svelte:fragment>
|
||||||
|
</ListView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,13 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount, afterUpdate } from 'svelte'
|
import type { Asset, IntlString } from '@anticrm/platform'
|
||||||
import type { IntlString, Asset } from '@anticrm/platform'
|
|
||||||
import { translate } from '@anticrm/platform'
|
import { translate } from '@anticrm/platform'
|
||||||
import type { AnySvelteComponent } from '../types'
|
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
|
||||||
import Label from './Label.svelte'
|
import { registerFocus } from '../focus'
|
||||||
import Icon from './Icon.svelte'
|
|
||||||
import plugin from '../plugin'
|
import plugin from '../plugin'
|
||||||
|
import type { AnySvelteComponent } from '../types'
|
||||||
|
import Icon from './Icon.svelte'
|
||||||
|
import Label from './Label.svelte'
|
||||||
|
|
||||||
export let label: IntlString | undefined = undefined
|
export let label: IntlString | undefined = undefined
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
@ -66,6 +67,22 @@
|
|||||||
afterUpdate(() => {
|
afterUpdate(() => {
|
||||||
computeSize(input)
|
computeSize(input)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Focusable control with index
|
||||||
|
export let focusIndex = -1
|
||||||
|
const { idx, focusManager } = registerFocus(focusIndex, {
|
||||||
|
focus: () => {
|
||||||
|
input?.focus()
|
||||||
|
return input != null
|
||||||
|
},
|
||||||
|
isFocus: () => document.activeElement === input
|
||||||
|
})
|
||||||
|
|
||||||
|
$: if (input) {
|
||||||
|
input.addEventListener('focus', () => {
|
||||||
|
focusManager?.setFocus(idx)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -74,6 +91,7 @@
|
|||||||
input.focus()
|
input.focus()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<!-- {focusIndex} -->
|
||||||
<div class="hidden-text {kind}" bind:this={text} />
|
<div class="hidden-text {kind}" bind:this={text} />
|
||||||
{#if label}<div class="label"><Label {label} /></div>{/if}
|
{#if label}<div class="label"><Label {label} /></div>{/if}
|
||||||
<div class="{kind} flex-row-center clear-mins">
|
<div class="{kind} flex-row-center clear-mins">
|
||||||
|
16
packages/ui/src/components/FocusHandler.svelte
Normal file
16
packages/ui/src/components/FocusHandler.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { FocusManager } from '../focus'
|
||||||
|
|
||||||
|
export let manager: FocusManager
|
||||||
|
|
||||||
|
function handleKey (evt: KeyboardEvent): void {
|
||||||
|
if (evt.code === 'Tab' && manager.hasFocus()) {
|
||||||
|
evt.preventDefault()
|
||||||
|
evt.stopPropagation()
|
||||||
|
manager.next(evt.shiftKey ? -1 : 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={(evt) => handleKey(evt)} />
|
||||||
|
<slot />
|
@ -13,11 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, afterUpdate, onMount } from 'svelte'
|
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
|
||||||
|
import ui from '../plugin'
|
||||||
import { Action } from '../types'
|
import { Action } from '../types'
|
||||||
import Icon from './Icon.svelte'
|
import Icon from './Icon.svelte'
|
||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
import ui from '../plugin'
|
|
||||||
|
|
||||||
export let actions: Action[] = []
|
export let actions: Action[] = []
|
||||||
export let ctx: any = undefined
|
export let ctx: any = undefined
|
||||||
@ -26,6 +26,9 @@
|
|||||||
const btns: HTMLButtonElement[] = []
|
const btns: HTMLButtonElement[] = []
|
||||||
|
|
||||||
const keyDown = (ev: KeyboardEvent, n: number): void => {
|
const keyDown = (ev: KeyboardEvent, n: number): void => {
|
||||||
|
if (ev.key === 'Tab') {
|
||||||
|
dispatch('close')
|
||||||
|
}
|
||||||
if (ev.key === 'ArrowDown') {
|
if (ev.key === 'ArrowDown') {
|
||||||
if (n === btns.length - 1) btns[0].focus()
|
if (n === btns.length - 1) btns[0].focus()
|
||||||
else btns[n + 1].focus()
|
else btns[n + 1].focus()
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if props.element !== 'content'}
|
{#if props.element !== 'content'}
|
||||||
<div class="modal-overlay" class:show on:click={() => escapeClose()} />
|
<div class="modal-overlay" class:show on:click={() => escapeClose()} on:keydown={() => {}} on:keyup={() => {}} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterUpdate, onDestroy } from 'svelte'
|
import { afterUpdate, onDestroy } from 'svelte'
|
||||||
import { tooltipstore as tooltip, closeTooltip, Component } from '..'
|
|
||||||
import type { TooltipAlignment } from '..'
|
import type { TooltipAlignment } from '..'
|
||||||
|
import { closeTooltip, Component, tooltipstore as tooltip } from '..'
|
||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
|
|
||||||
let tooltipHTML: HTMLElement
|
let tooltipHTML: HTMLElement
|
||||||
@ -151,6 +151,13 @@
|
|||||||
on:mousemove={(ev) => {
|
on:mousemove={(ev) => {
|
||||||
whileShow(ev)
|
whileShow(ev)
|
||||||
}}
|
}}
|
||||||
|
on:keydown={(evt) => {
|
||||||
|
if (($tooltip.component || $tooltip.label) && evt.key === 'Escape') {
|
||||||
|
evt.preventDefault()
|
||||||
|
evt.stopImmediatePropagation()
|
||||||
|
hideTooltip()
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{#if $tooltip.component}
|
{#if $tooltip.component}
|
||||||
<div class="popup-tooltip" class:doublePadding={$tooltip.label} bind:clientWidth={clWidth} bind:this={tooltipHTML}>
|
<div class="popup-tooltip" class:doublePadding={$tooltip.label} bind:clientWidth={clWidth} bind:this={tooltipHTML}>
|
||||||
|
115
packages/ui/src/focus.ts
Normal file
115
packages/ui/src/focus.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { getContext, setContext } from 'svelte'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface FocusManager {
|
||||||
|
next: (inc?: 1 | -1) => void
|
||||||
|
setFocus: (idx: number) => void
|
||||||
|
setFocusPos: (order: number) => void
|
||||||
|
updateFocus: (idx: number, order: number) => void
|
||||||
|
|
||||||
|
// Check if current manager has focus
|
||||||
|
hasFocus: () => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class FocusManagerImpl implements FocusManager {
|
||||||
|
counter = 0
|
||||||
|
elements: Array<{
|
||||||
|
id: number
|
||||||
|
order: number
|
||||||
|
focus: () => boolean
|
||||||
|
isFocus: () => boolean
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
current = 0
|
||||||
|
register (order: number, focus: () => boolean, isFocus: () => boolean): number {
|
||||||
|
const el = { id: this.counter++, order, focus, isFocus }
|
||||||
|
this.elements.push(el)
|
||||||
|
this.sort()
|
||||||
|
return el.id
|
||||||
|
}
|
||||||
|
|
||||||
|
sort (): void {
|
||||||
|
// this.needSort = 0
|
||||||
|
this.elements.sort((a, b) => {
|
||||||
|
return a.order - b.order
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
next (inc?: 1 | -1): void {
|
||||||
|
while (true) {
|
||||||
|
this.current = this.current + (inc ?? 1)
|
||||||
|
if (this.elements[Math.abs(this.current) % this.elements.length].focus()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFocus (idx: number): void {
|
||||||
|
this.current = this.elements.findIndex((it) => it.id === idx) ?? 0
|
||||||
|
this.elements[Math.abs(this.current) % this.elements.length].focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
setFocusPos (order: number): void {
|
||||||
|
const idx = this.elements.findIndex((it) => it.order === order)
|
||||||
|
if (idx !== undefined) {
|
||||||
|
this.current = idx
|
||||||
|
this.elements[Math.abs(this.current) % this.elements.length].focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFocus (idx: number, order: number): void {
|
||||||
|
const el = this.elements.find((it) => it.id === idx)
|
||||||
|
if (el !== undefined) {
|
||||||
|
if (el.order !== order) {
|
||||||
|
el.order = order
|
||||||
|
this.sort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFocus (): boolean {
|
||||||
|
for (const el of this.elements) {
|
||||||
|
if (el.isFocus()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function createFocusManager (): FocusManager {
|
||||||
|
const mgr = new FocusManagerImpl()
|
||||||
|
setFocusManager(mgr)
|
||||||
|
return mgr
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFocusManager (manager: FocusManager): void {
|
||||||
|
setContext('ui.focus.elements', manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function getFocusManager (): FocusManager | undefined {
|
||||||
|
return getContext('ui.focus.elements')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register new focus reciever if order !== -1
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function registerFocus (
|
||||||
|
order: number,
|
||||||
|
item: { focus: () => boolean, isFocus: () => boolean }
|
||||||
|
): { idx: number, focusManager?: FocusManager } {
|
||||||
|
const focusManager = getFocusManager() as FocusManagerImpl
|
||||||
|
if (order === -1) {
|
||||||
|
return { idx: -1, focusManager }
|
||||||
|
}
|
||||||
|
return { idx: focusManager?.register(order, item.focus, item.isFocus) ?? -1, focusManager }
|
||||||
|
}
|
@ -128,11 +128,22 @@ export { default as IconDetails } from './components/icons/Details.svelte'
|
|||||||
export { default as PanelInstance } from './components/PanelInstance.svelte'
|
export { default as PanelInstance } from './components/PanelInstance.svelte'
|
||||||
export { default as Panel } from './components/Panel.svelte'
|
export { default as Panel } from './components/Panel.svelte'
|
||||||
|
|
||||||
|
export { default as MonthCalendar } from './components/calendar/MonthCalendar.svelte'
|
||||||
|
export { default as YearCalendar } from './components/calendar/YearCalendar.svelte'
|
||||||
|
export { default as WeekCalendar } from './components/calendar/WeekCalendar.svelte'
|
||||||
|
|
||||||
|
export { default as FocusHandler } from './components/FocusHandler.svelte'
|
||||||
|
export { default as ListView } from './components/ListView.svelte'
|
||||||
|
|
||||||
|
export * from './types'
|
||||||
|
export * from './location'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export * from './popups'
|
export * from './popups'
|
||||||
export * from './tooltips'
|
export * from './tooltips'
|
||||||
export * from './panelup'
|
export * from './panelup'
|
||||||
export * from './components/calendar/internal/DateUtils'
|
export * from './components/calendar/internal/DateUtils'
|
||||||
|
export * from './colors'
|
||||||
|
export * from './focus'
|
||||||
|
|
||||||
export function createApp (target: HTMLElement): SvelteComponent {
|
export function createApp (target: HTMLElement): SvelteComponent {
|
||||||
return new Root({ target })
|
return new Root({ target })
|
||||||
@ -151,8 +162,3 @@ addStringsLoader(uiId, async (lang: string) => {
|
|||||||
addLocation(uiId, async () => ({ default: async () => ({}) }))
|
addLocation(uiId, async () => ({ default: async () => ({}) }))
|
||||||
|
|
||||||
export { default } from './plugin'
|
export { default } from './plugin'
|
||||||
export * from './colors'
|
|
||||||
|
|
||||||
export { default as MonthCalendar } from './components/calendar/MonthCalendar.svelte'
|
|
||||||
export { default as YearCalendar } from './components/calendar/YearCalendar.svelte'
|
|
||||||
export { default as WeekCalendar } from './components/calendar/WeekCalendar.svelte'
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { AnySvelteComponent, AnyComponent, LabelAndProps, TooltipAlignment } from './types'
|
|
||||||
import { IntlString } from '@anticrm/platform'
|
import { IntlString } from '@anticrm/platform'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
import { AnyComponent, AnySvelteComponent, LabelAndProps, TooltipAlignment } from './types'
|
||||||
|
|
||||||
export const tooltipstore = writable<LabelAndProps>({
|
const emptyTooltip: LabelAndProps = {
|
||||||
label: undefined,
|
label: undefined,
|
||||||
element: undefined,
|
element: undefined,
|
||||||
direction: undefined,
|
direction: undefined,
|
||||||
@ -10,7 +10,8 @@ export const tooltipstore = writable<LabelAndProps>({
|
|||||||
props: undefined,
|
props: undefined,
|
||||||
anchor: undefined,
|
anchor: undefined,
|
||||||
onUpdate: undefined
|
onUpdate: undefined
|
||||||
})
|
}
|
||||||
|
export const tooltipstore = writable<LabelAndProps>(emptyTooltip)
|
||||||
|
|
||||||
export function showTooltip (
|
export function showTooltip (
|
||||||
label: IntlString | undefined,
|
label: IntlString | undefined,
|
||||||
@ -33,13 +34,5 @@ export function showTooltip (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closeTooltip (): void {
|
export function closeTooltip (): void {
|
||||||
tooltipstore.set({
|
tooltipstore.set(emptyTooltip)
|
||||||
label: undefined,
|
|
||||||
element: undefined,
|
|
||||||
direction: undefined,
|
|
||||||
component: undefined,
|
|
||||||
props: undefined,
|
|
||||||
anchor: undefined,
|
|
||||||
onUpdate: undefined
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import type { Asset, IntlString } from '@anticrm/platform'
|
||||||
import { /* Metadata, Plugin, plugin, */ Resource /*, Service */ } from '@anticrm/platform'
|
import { /* Metadata, Plugin, plugin, */ Resource /*, Service */ } from '@anticrm/platform'
|
||||||
import { /* getContext, */ SvelteComponent } from 'svelte'
|
import { /* getContext, */ SvelteComponent } from 'svelte'
|
||||||
import type { Asset, IntlString } from '@anticrm/platform'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describe a browser URI location parsed to path, query and fragment.
|
* Describe a browser URI location parsed to path, query and fragment.
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { translate } from '@anticrm/platform'
|
import { translate } from '@anticrm/platform'
|
||||||
import { Button, IconClose, closeTooltip, IconBlueCheck } from '@anticrm/ui'
|
import { Button, IconClose, closeTooltip, IconBlueCheck, registerFocus, createFocusManager } from '@anticrm/ui'
|
||||||
import IconCopy from './icons/Copy.svelte'
|
import IconCopy from './icons/Copy.svelte'
|
||||||
|
import { FocusHandler } from '@anticrm/ui'
|
||||||
|
|
||||||
export let value: string = ''
|
export let value: string = ''
|
||||||
export let placeholder: IntlString
|
export let placeholder: IntlString
|
||||||
@ -31,8 +32,25 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (input) input.focus()
|
if (input) input.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mgr = createFocusManager()
|
||||||
|
|
||||||
|
const { idx } = registerFocus(1, {
|
||||||
|
focus: () => {
|
||||||
|
input?.focus()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
isFocus: () => document.activeElement === input
|
||||||
|
})
|
||||||
|
|
||||||
|
$: if (input) {
|
||||||
|
input.addEventListener('focus', () => {
|
||||||
|
mgr.setFocus(idx)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<FocusHandler manager={mgr} />
|
||||||
<div class="buttons-group xsmall-gap">
|
<div class="buttons-group xsmall-gap">
|
||||||
{#if editable}
|
{#if editable}
|
||||||
<input
|
<input
|
||||||
@ -51,6 +69,7 @@
|
|||||||
on:change
|
on:change
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={2}
|
||||||
kind={'transparent'}
|
kind={'transparent'}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
icon={IconClose}
|
icon={IconClose}
|
||||||
@ -66,6 +85,7 @@
|
|||||||
<span>{value}</span>
|
<span>{value}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={3}
|
||||||
kind={'transparent'}
|
kind={'transparent'}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
icon={IconCopy}
|
icon={IconCopy}
|
||||||
@ -75,6 +95,7 @@
|
|||||||
/>
|
/>
|
||||||
{#if editable}
|
{#if editable}
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={4}
|
||||||
kind={'transparent'}
|
kind={'transparent'}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
icon={IconBlueCheck}
|
icon={IconBlueCheck}
|
||||||
|
@ -14,17 +14,28 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import type { Channel, ChannelProvider } from '@anticrm/contact'
|
import type { Channel, ChannelProvider } from '@anticrm/contact'
|
||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import type { AttachedData, Doc, Ref, Timestamp } from '@anticrm/core'
|
import type { AttachedData, Doc, Ref, Timestamp } from '@anticrm/core'
|
||||||
|
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||||
import type { Asset, IntlString } from '@anticrm/platform'
|
import type { Asset, IntlString } from '@anticrm/platform'
|
||||||
import { AnyComponent, showPopup, Button, Menu, showTooltip, closeTooltip, eventToHTMLElement } from '@anticrm/ui'
|
|
||||||
import type { Action, ButtonKind, ButtonSize } from '@anticrm/ui'
|
|
||||||
import presentation from '@anticrm/presentation'
|
import presentation from '@anticrm/presentation'
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
AnyComponent,
|
||||||
|
Button,
|
||||||
|
ButtonKind,
|
||||||
|
ButtonSize,
|
||||||
|
closeTooltip,
|
||||||
|
eventToHTMLElement,
|
||||||
|
getFocusManager,
|
||||||
|
Menu,
|
||||||
|
showPopup,
|
||||||
|
showTooltip
|
||||||
|
} from '@anticrm/ui'
|
||||||
|
import { createEventDispatcher, tick } from 'svelte'
|
||||||
import { getChannelProviders } from '../utils'
|
import { getChannelProviders } from '../utils'
|
||||||
import ChannelEditor from './ChannelEditor.svelte'
|
import ChannelEditor from './ChannelEditor.svelte'
|
||||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
|
||||||
|
|
||||||
export let value: AttachedData<Channel>[] | Channel | null
|
export let value: AttachedData<Channel>[] | Channel | null
|
||||||
export let editable: boolean = false
|
export let editable: boolean = false
|
||||||
@ -33,6 +44,7 @@
|
|||||||
export let length: 'short' | 'full' = 'full'
|
export let length: 'short' | 'full' = 'full'
|
||||||
export let shape: 'circle' | undefined = undefined
|
export let shape: 'circle' | undefined = undefined
|
||||||
export let integrations: Set<Ref<Doc>> = new Set<Ref<Doc>>()
|
export let integrations: Set<Ref<Doc>> = new Set<Ref<Doc>>()
|
||||||
|
export let focusIndex = -1
|
||||||
|
|
||||||
const notificationClient = NotificationClientImpl.getClient()
|
const notificationClient = NotificationClientImpl.getClient()
|
||||||
const lastViews = notificationClient.getLastViews()
|
const lastViews = notificationClient.getLastViews()
|
||||||
@ -79,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: Map<Ref<Doc>, Timestamp>) {
|
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: Map<Ref<Doc>, Timestamp>) {
|
||||||
if (value === null) {
|
if (value == null) {
|
||||||
displayItems = []
|
displayItems = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -99,7 +111,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayItems = result
|
displayItems = result
|
||||||
updateMenu()
|
updateMenu(displayItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (value) update(value, $lastViews)
|
$: if (value) update(value, $lastViews)
|
||||||
@ -112,23 +124,30 @@
|
|||||||
let anchor: HTMLElement
|
let anchor: HTMLElement
|
||||||
|
|
||||||
function filterUndefined (channels: AttachedData<Channel>[]): AttachedData<Channel>[] {
|
function filterUndefined (channels: AttachedData<Channel>[]): AttachedData<Channel>[] {
|
||||||
return channels.filter((channel) => channel.value !== undefined && channel.value.length > 0)
|
return channels.filter((channel) => channel.value !== undefined)
|
||||||
}
|
}
|
||||||
|
const focusManager = getFocusManager()
|
||||||
|
|
||||||
getChannelProviders().then((pr) => (providers = pr))
|
getChannelProviders().then((pr) => (providers = pr))
|
||||||
|
|
||||||
const updateMenu = (): void => {
|
const updateMenu = (_displayItems: Item[]): void => {
|
||||||
actions = []
|
actions = []
|
||||||
providers.forEach((pr) => {
|
providers.forEach((pr) => {
|
||||||
if (displayItems.filter((it) => it.provider === pr._id).length === 0) {
|
if (_displayItems.filter((it) => it.provider === pr._id).length === 0) {
|
||||||
actions.push({
|
actions.push({
|
||||||
icon: pr.icon ?? contact.icon.SocialEdit,
|
icon: pr.icon ?? contact.icon.SocialEdit,
|
||||||
label: pr.label,
|
label: pr.label,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const provider = getProvider({ provider: pr._id, value: '' }, providers, $lastViews)
|
const provider = getProvider({ provider: pr._id, value: '' }, providers, $lastViews)
|
||||||
if (provider !== undefined) {
|
if (provider !== undefined) {
|
||||||
if (displayItems.filter((it) => it.provider === pr._id).length === 0) {
|
if (_displayItems.filter((it) => it.provider === pr._id).length === 0) {
|
||||||
displayItems = [...displayItems, provider]
|
displayItems = [..._displayItems, provider]
|
||||||
|
if (focusIndex !== -1) {
|
||||||
|
await tick()
|
||||||
|
focusManager?.setFocusPos(focusIndex + displayItems.length)
|
||||||
|
await tick()
|
||||||
|
editChannel(btns[displayItems.length - 1], displayItems.length - 1, provider)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +155,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$: if (providers) updateMenu()
|
$: if (providers) updateMenu(displayItems)
|
||||||
|
|
||||||
const dropItem = (n: number): Item[] => {
|
const dropItem = (n: number): Item[] => {
|
||||||
return displayItems.filter((it, i) => i !== n)
|
return displayItems.filter((it, i) => i !== n)
|
||||||
@ -144,11 +163,15 @@
|
|||||||
const saveItems = (): void => {
|
const saveItems = (): void => {
|
||||||
value = filterUndefined(displayItems)
|
value = filterUndefined(displayItems)
|
||||||
dispatch('change', value)
|
dispatch('change', value)
|
||||||
updateMenu()
|
updateMenu(displayItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
const showMenu = (ev: MouseEvent): void => {
|
const showMenu = (ev: MouseEvent): void => {
|
||||||
showPopup(Menu, { actions }, ev.target as HTMLElement)
|
showPopup(Menu, { actions }, ev.target as HTMLElement, (result) => {
|
||||||
|
if (result == null) {
|
||||||
|
focusManager?.setFocusPos(focusIndex + 2 + displayItems.length)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const editChannel = (el: HTMLElement, n: number, item: Item): void => {
|
const editChannel = (el: HTMLElement, n: number, item: Item): void => {
|
||||||
@ -157,13 +180,21 @@
|
|||||||
el,
|
el,
|
||||||
undefined,
|
undefined,
|
||||||
ChannelEditor,
|
ChannelEditor,
|
||||||
{ value: item.value, placeholder: item.placeholder, editable },
|
{
|
||||||
|
value: item.value,
|
||||||
|
placeholder: item.placeholder,
|
||||||
|
editable
|
||||||
|
},
|
||||||
anchor,
|
anchor,
|
||||||
(result) => {
|
(result) => {
|
||||||
if (result.detail !== undefined) {
|
if (result.detail != null) {
|
||||||
if (result.detail === '') displayItems = dropItem(n)
|
if (result.detail === '') {
|
||||||
else displayItems[n].value = result.detail
|
displayItems = dropItem(n)
|
||||||
|
} else {
|
||||||
|
displayItems[n].value = result.detail
|
||||||
|
}
|
||||||
saveItems()
|
saveItems()
|
||||||
|
focusManager?.setFocusPos(focusIndex + 1 + n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -183,6 +214,7 @@
|
|||||||
>
|
>
|
||||||
{#each displayItems as item, i}
|
{#each displayItems as item, i}
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 1 + i}
|
||||||
id={item.label}
|
id={item.label}
|
||||||
bind:input={btns[i]}
|
bind:input={btns[i]}
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
@ -193,18 +225,19 @@
|
|||||||
on:mousemove={(ev) => {
|
on:mousemove={(ev) => {
|
||||||
_focus(ev, i, item)
|
_focus(ev, i, item)
|
||||||
}}
|
}}
|
||||||
on:focus={(ev) => {
|
|
||||||
_focus(ev, i, item)
|
|
||||||
}}
|
|
||||||
on:click={(ev) => {
|
on:click={(ev) => {
|
||||||
if (editable) editChannel(eventToHTMLElement(ev), i, item)
|
if (editable) {
|
||||||
else closeTooltip()
|
editChannel(eventToHTMLElement(ev), i, item)
|
||||||
|
} else {
|
||||||
|
closeTooltip()
|
||||||
|
}
|
||||||
dispatch('open', item)
|
dispatch('open', item)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{#if actions.length > 0 && editable}
|
{#if actions.length > 0 && editable}
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 2 + displayItems.length}
|
||||||
id={presentation.string.AddSocialLinks}
|
id={presentation.string.AddSocialLinks}
|
||||||
bind:input={addBtn}
|
bind:input={addBtn}
|
||||||
icon={contact.icon.SocialEdit}
|
icon={contact.icon.SocialEdit}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"VacancyCreateLabel": "Vacancy",
|
"VacancyCreateLabel": "Vacancy",
|
||||||
"VacancyPlaceholder": "Software Engineer",
|
"VacancyPlaceholder": "Software Engineer",
|
||||||
"MakePrivateDescription": "Only members can see it",
|
"MakePrivateDescription": "Only members can see it",
|
||||||
"CreateAnApplication": "Create an Application",
|
"CreateAnApplication": "New Application",
|
||||||
"NoApplicationsForCandidate": "There are no applications for this candidate.",
|
"NoApplicationsForCandidate": "There are no applications for this candidate.",
|
||||||
"CreateApplication": "Create an Application",
|
"CreateApplication": "Create an Application",
|
||||||
"ApplicationCreateLabel": "Application",
|
"ApplicationCreateLabel": "Application",
|
||||||
@ -93,7 +93,8 @@
|
|||||||
"GotoSkills": "Go to Skills",
|
"GotoSkills": "Go to Skills",
|
||||||
"GotoAssigned": "Go to my Assigned",
|
"GotoAssigned": "Go to my Assigned",
|
||||||
"GotoApplicants": "Go to Applications",
|
"GotoApplicants": "Go to Applications",
|
||||||
"GotoRecruitApplication": "Switch to Recruit Application"
|
"GotoRecruitApplication": "Switch to Recruit Application",
|
||||||
|
"AddDropHere": "Add or drop resume"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"CandidateRequired": "Please select candidate",
|
"CandidateRequired": "Please select candidate",
|
||||||
|
@ -95,7 +95,8 @@
|
|||||||
"GotoSkills": "Перейти к навыкам",
|
"GotoSkills": "Перейти к навыкам",
|
||||||
"GotoAssigned": "Перейти к моим назначениям",
|
"GotoAssigned": "Перейти к моим назначениям",
|
||||||
"GotoApplicants": "Перейти к претендентам",
|
"GotoApplicants": "Перейти к претендентам",
|
||||||
"GotoRecruitApplication": "Перейти к Приложению Рекрутинг"
|
"GotoRecruitApplication": "Перейти к Приложению Рекрутинг",
|
||||||
|
"AddDropHere": "Добавить или перетянуть резюме"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"CandidateRequired": "Пожалуйста выберите кандидата",
|
"CandidateRequired": "Пожалуйста выберите кандидата",
|
||||||
|
@ -23,7 +23,9 @@
|
|||||||
import ui, {
|
import ui, {
|
||||||
Button,
|
Button,
|
||||||
ColorPopup,
|
ColorPopup,
|
||||||
|
createFocusManager,
|
||||||
eventToHTMLElement,
|
eventToHTMLElement,
|
||||||
|
FocusHandler,
|
||||||
getPlatformColor,
|
getPlatformColor,
|
||||||
showPopup,
|
showPopup,
|
||||||
Status as StatusControl
|
Status as StatusControl
|
||||||
@ -163,8 +165,11 @@
|
|||||||
{ sort: { rank: SortingOrder.Ascending } }
|
{ sort: { rank: SortingOrder.Ascending } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const manager = createFocusManager()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<FocusHandler {manager} />
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
label={recruit.string.CreateApplication}
|
label={recruit.string.CreateApplication}
|
||||||
okAction={createApplication}
|
okAction={createApplication}
|
||||||
@ -183,6 +188,7 @@
|
|||||||
<svelte:fragment slot="pool">
|
<svelte:fragment slot="pool">
|
||||||
{#if !preserveCandidate}
|
{#if !preserveCandidate}
|
||||||
<UserBox
|
<UserBox
|
||||||
|
focusIndex={1}
|
||||||
_class={contact.class.Person}
|
_class={contact.class.Person}
|
||||||
label={recruit.string.Candidate}
|
label={recruit.string.Candidate}
|
||||||
placeholder={recruit.string.Candidates}
|
placeholder={recruit.string.Candidates}
|
||||||
@ -192,6 +198,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<UserBox
|
<UserBox
|
||||||
|
focusIndex={2}
|
||||||
_class={contact.class.Employee}
|
_class={contact.class.Employee}
|
||||||
label={recruit.string.AssignRecruiter}
|
label={recruit.string.AssignRecruiter}
|
||||||
placeholder={recruit.string.Recruiters}
|
placeholder={recruit.string.Recruiters}
|
||||||
@ -203,6 +210,7 @@
|
|||||||
/>
|
/>
|
||||||
{#if states && doc.space}
|
{#if states && doc.space}
|
||||||
<Button
|
<Button
|
||||||
|
focusIndex={3}
|
||||||
width="min-content"
|
width="min-content"
|
||||||
size="small"
|
size="small"
|
||||||
kind="no-border"
|
kind="no-border"
|
||||||
@ -217,6 +225,7 @@
|
|||||||
selectedState = result
|
selectedState = result
|
||||||
selectedState.title = result.label
|
selectedState.title = result.label
|
||||||
}
|
}
|
||||||
|
manager.setFocusPos(3)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
@ -35,7 +35,9 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Component,
|
Component,
|
||||||
|
createFocusManager,
|
||||||
EditBox,
|
EditBox,
|
||||||
|
FocusHandler,
|
||||||
getColorNumberByText,
|
getColorNumberByText,
|
||||||
IconFile as FileIcon,
|
IconFile as FileIcon,
|
||||||
IconInfo,
|
IconInfo,
|
||||||
@ -351,6 +353,7 @@
|
|||||||
if (file !== undefined) {
|
if (file !== undefined) {
|
||||||
createAttachment(file)
|
createAttachment(file)
|
||||||
}
|
}
|
||||||
|
manager.setFocusPos(102)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAvatarDone (e: any) {
|
function onAvatarDone (e: any) {
|
||||||
@ -386,8 +389,12 @@
|
|||||||
function removeAvatar (): void {
|
function removeAvatar (): void {
|
||||||
avatar = undefined
|
avatar = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const manager = createFocusManager()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<FocusHandler {manager} />
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
label={recruit.string.CreateCandidate}
|
label={recruit.string.CreateCandidate}
|
||||||
okAction={createCandidate}
|
okAction={createCandidate}
|
||||||
@ -416,17 +423,31 @@
|
|||||||
kind={'large-style'}
|
kind={'large-style'}
|
||||||
maxWidth={'32rem'}
|
maxWidth={'32rem'}
|
||||||
focus
|
focus
|
||||||
|
focusIndex={1}
|
||||||
/>
|
/>
|
||||||
<EditBox
|
<EditBox
|
||||||
placeholder={recruit.string.PersonLastNamePlaceholder}
|
placeholder={recruit.string.PersonLastNamePlaceholder}
|
||||||
bind:value={lastName}
|
bind:value={lastName}
|
||||||
kind={'large-style'}
|
kind={'large-style'}
|
||||||
maxWidth={'32rem'}
|
maxWidth={'32rem'}
|
||||||
|
focusIndex={2}
|
||||||
/>
|
/>
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
<EditBox placeholder={recruit.string.Title} bind:value={object.title} kind={'small-style'} maxWidth={'32rem'} />
|
<EditBox
|
||||||
|
placeholder={recruit.string.Title}
|
||||||
|
bind:value={object.title}
|
||||||
|
kind={'small-style'}
|
||||||
|
maxWidth={'32rem'}
|
||||||
|
focusIndex={3}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<EditBox placeholder={recruit.string.Location} bind:value={object.city} kind={'small-style'} maxWidth={'32rem'} />
|
<EditBox
|
||||||
|
placeholder={recruit.string.Location}
|
||||||
|
bind:value={object.city}
|
||||||
|
kind={'small-style'}
|
||||||
|
maxWidth={'32rem'}
|
||||||
|
focusIndex={4}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
@ -439,12 +460,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="pool">
|
<svelte:fragment slot="pool">
|
||||||
<ChannelsDropdown bind:value={channels} editable />
|
<ChannelsDropdown focusIndex={10} bind:value={channels} editable />
|
||||||
<YesNo label={recruit.string.Onsite} tooltip={recruit.string.WorkLocationPreferences} bind:value={object.onsite} />
|
<YesNo
|
||||||
<YesNo label={recruit.string.Remote} tooltip={recruit.string.WorkLocationPreferences} bind:value={object.remote} />
|
focusIndex={100}
|
||||||
|
label={recruit.string.Onsite}
|
||||||
|
tooltip={recruit.string.WorkLocationPreferences}
|
||||||
|
bind:value={object.onsite}
|
||||||
|
/>
|
||||||
|
<YesNo
|
||||||
|
focusIndex={101}
|
||||||
|
label={recruit.string.Remote}
|
||||||
|
tooltip={recruit.string.WorkLocationPreferences}
|
||||||
|
bind:value={object.remote}
|
||||||
|
/>
|
||||||
<Component
|
<Component
|
||||||
is={tags.component.TagsDropdownEditor}
|
is={tags.component.TagsDropdownEditor}
|
||||||
props={{
|
props={{
|
||||||
|
focusIndex: 102,
|
||||||
items: skills,
|
items: skills,
|
||||||
key,
|
key,
|
||||||
targetClass: recruit.mixin.Candidate,
|
targetClass: recruit.mixin.Candidate,
|
||||||
@ -474,20 +506,26 @@
|
|||||||
on:drop|preventDefault|stopPropagation={drop}
|
on:drop|preventDefault|stopPropagation={drop}
|
||||||
>
|
>
|
||||||
{#if resume.uuid}
|
{#if resume.uuid}
|
||||||
<Link
|
<Button
|
||||||
label={resume.name}
|
kind={'transparent'}
|
||||||
|
focusIndex={103}
|
||||||
icon={FileIcon}
|
icon={FileIcon}
|
||||||
maxLenght={16}
|
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showPopup(PDFViewer, { file: resume.uuid, name: resume.name }, 'right')
|
showPopup(PDFViewer, { file: resume.uuid, name: resume.name }, 'right')
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<svelte:fragment slot="content">
|
||||||
|
{resume.name}
|
||||||
|
</svelte:fragment>
|
||||||
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<Link label={'Uploading...'} icon={Spinner} disabled />
|
<Link label={'Uploading...'} icon={Spinner} disabled />
|
||||||
{:else}
|
{:else}
|
||||||
<Link
|
<Button
|
||||||
label={'Add or drop resume'}
|
kind={'transparent'}
|
||||||
|
focusIndex={103}
|
||||||
|
label={recruit.string.AddDropHere}
|
||||||
icon={FileUpload}
|
icon={FileUpload}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
inputFile.click()
|
inputFile.click()
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { Label, Tooltip, Button } from '@anticrm/ui'
|
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
|
||||||
import type { TooltipAlignment, ButtonKind, ButtonSize } from '@anticrm/ui'
|
import { Button, Label, Tooltip } from '@anticrm/ui'
|
||||||
|
|
||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
export let tooltip: IntlString
|
export let tooltip: IntlString
|
||||||
@ -27,10 +27,13 @@
|
|||||||
export let size: ButtonSize = 'small'
|
export let size: ButtonSize = 'small'
|
||||||
export let justify: 'left' | 'center' = 'center'
|
export let justify: 'left' | 'center' = 'center'
|
||||||
export let width: string | undefined = 'fit-content'
|
export let width: string | undefined = 'fit-content'
|
||||||
|
|
||||||
|
export let focusIndex = -1
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tooltip direction={labelDirection} label={tooltip}>
|
<Tooltip direction={labelDirection} label={tooltip}>
|
||||||
<Button
|
<Button
|
||||||
|
{focusIndex}
|
||||||
{kind}
|
{kind}
|
||||||
{size}
|
{size}
|
||||||
{justify}
|
{justify}
|
||||||
|
@ -103,7 +103,8 @@ export default mergeIds(recruitId, recruit, {
|
|||||||
DueDate: '' as IntlString,
|
DueDate: '' as IntlString,
|
||||||
CandidateReviews: '' as IntlString,
|
CandidateReviews: '' as IntlString,
|
||||||
AddDescription: '' as IntlString,
|
AddDescription: '' as IntlString,
|
||||||
NumberSkills: '' as IntlString
|
NumberSkills: '' as IntlString,
|
||||||
|
AddDropHere: '' as IntlString
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
CandidatesPublic: '' as Ref<Space>
|
CandidatesPublic: '' as Ref<Space>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
import view from '../plugin'
|
import view from '../plugin'
|
||||||
import { focusStore, selectionStore } from '../selection'
|
import { focusStore, selectionStore } from '../selection'
|
||||||
import ActionContext from './ActionContext.svelte'
|
import ActionContext from './ActionContext.svelte'
|
||||||
import ListView from './ListView.svelte'
|
import { ListView } from '@anticrm/ui'
|
||||||
import ObjectPresenter from './ObjectPresenter.svelte'
|
import ObjectPresenter from './ObjectPresenter.svelte'
|
||||||
|
|
||||||
export let viewContext: ViewContext
|
export let viewContext: ViewContext
|
||||||
|
@ -438,6 +438,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div bind:this={cover} class="cover" />
|
<div bind:this={cover} class="cover" />
|
||||||
|
<TooltipInstance />
|
||||||
<PanelInstance bind:this={panelInstance} {contentPanel}>
|
<PanelInstance bind:this={panelInstance} {contentPanel}>
|
||||||
<svelte:fragment slot="panel-header">
|
<svelte:fragment slot="panel-header">
|
||||||
<ActionContext context={{ mode: 'panel' }} />
|
<ActionContext context={{ mode: 'panel' }} />
|
||||||
@ -448,7 +449,6 @@
|
|||||||
<ActionContext context={{ mode: 'popup' }} />
|
<ActionContext context={{ mode: 'popup' }} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Popup>
|
</Popup>
|
||||||
<TooltipInstance />
|
|
||||||
<DatePickerPopup />
|
<DatePickerPopup />
|
||||||
{:else}
|
{:else}
|
||||||
No client
|
No client
|
||||||
|
Loading…
Reference in New Issue
Block a user