add single store for both popups and tooltip (#5808)

Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
Vyacheslav Tumanov 2024-06-21 14:38:06 +05:00 committed by GitHub
parent caf3fedd9c
commit 649c16fe13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 89 additions and 53 deletions

View File

@ -13,7 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import { popupstore as modal } from '../popups'
import { popupstore as popups } from '../popups'
import { modalStore as modals } from '../modals'
import PopupInstance from './PopupInstance.svelte'
export let contentPanel: HTMLElement | undefined = undefined
@ -24,13 +26,13 @@
instances.forEach((p) => p.fitPopupInstance())
}
$: instances.length = $modal.filter((p) => p.dock !== true).length
$: instances.length = $popups.filter((p) => p.dock !== true).length
</script>
{#if $modal.length > 0}
{#if $popups.length > 0}
<slot name="popup-header" />
{/if}
{#each $modal.filter((p) => p.dock !== true) as popup, i (popup.id)}
{#each $popups.filter((p) => p.dock !== true) as popup, i (popup.id)}
<PopupInstance
bind:this={instances[i]}
is={popup.is}
@ -38,8 +40,8 @@
element={popup.element}
onClose={popup.onClose}
onUpdate={popup.onUpdate}
zIndex={(i + 1) * 500}
top={$modal.length - 1 === i}
zIndex={($modals.findIndex((modal) => modal.type === 'popup' && modal.id === popup.id) ?? i) + 10000}
top={$popups.length - 1 === i}
close={popup.close}
{contentPanel}
overlay={popup.options.overlay}

View File

@ -280,7 +280,7 @@
class:testing
class:anim={(element === 'float' || element === 'centered') && !testing && !drag}
bind:this={modalHTML}
style={`z-index: ${zIndex + 1};`}
style={`z-index: ${zIndex};`}
style:top={options?.props?.top}
style:bottom={options?.props?.bottom}
style:left={options?.props?.left}
@ -331,7 +331,7 @@
class="modal-overlay"
class:testing
class:antiOverlay={options?.showOverlay && !drag}
style={`z-index: ${zIndex};`}
style={`z-index: ${zIndex - 1};`}
on:click={handleOverlayClick}
on:keydown|stopPropagation|preventDefault={() => {}}
/>

View File

@ -15,7 +15,8 @@
<script lang="ts">
import { afterUpdate, onDestroy } from 'svelte'
import { resizeObserver } from '../resize'
import { closeTooltip, showTooltip, tooltipstore as tooltip } from '../tooltips'
import { closeTooltip, tooltipstore as tooltip } from '../tooltips'
import { modalStore as modals } from '../modals'
import type { TooltipAlignment } from '../types'
import Component from './Component.svelte'
import Label from './Label.svelte'
@ -259,6 +260,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="modal-overlay antiOverlay"
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
on:click|stopPropagation|preventDefault={() => {
closeTooltip()
}}
@ -301,6 +303,7 @@
style:width={options.width}
style:height={options.height}
style:transform={options.transform}
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
bind:this={tooltipHTML}
>
{#if $tooltip.label}
@ -319,13 +322,18 @@
this={$tooltip.component}
{...$tooltip.props}
on:tooltip={(evt) => {
$tooltip = { ...$tooltip, ...evt.detail }
$modals = [...$modals.filter((t) => t.type !== 'tooltip'), { ...$tooltip, ...evt.detail }]
}}
on:update={onUpdate !== undefined ? onUpdate : async () => {}}
/>
{/if}
</div>
<div bind:this={nubHTML} class="nub {nubDirection ?? ''}" class:shown />
<div
bind:this={nubHTML}
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
class="nub {nubDirection ?? ''}"
class:shown
/>
{:else if $tooltip.label && $tooltip.kind !== 'submenu'}
<div
class="tooltip {dir ?? ''} {options.classList}"
@ -337,6 +345,7 @@
style:width={options.width}
style:height={options.height}
style:transform={options.transform}
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
>
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
{#if $tooltip.keys !== undefined}
@ -372,6 +381,7 @@
style:width={options.width}
style:height={options.height}
style:transform={options.transform}
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
bind:this={tooltipHTML}
>
{#if typeof $tooltip.component === 'string'}
@ -396,7 +406,6 @@
width: auto;
height: auto;
border-radius: 0.5rem;
z-index: 10000;
}
.popup-tooltip {
overflow: hidden;
@ -412,7 +421,6 @@
box-shadow: var(--theme-popup-shadow);
user-select: none;
opacity: 0;
z-index: 10000;
&.doublePadding {
padding: 1rem;
@ -425,7 +433,6 @@
user-select: none;
pointer-events: none;
opacity: 0;
z-index: 10000;
&::after,
&::before {
@ -522,7 +529,6 @@
border-radius: 0.25rem;
box-shadow: var(--theme-popup-shadow);
user-select: none;
z-index: 10000;
display: flex;
align-items: center;
@ -545,8 +551,6 @@
}
}
.modal-overlay {
z-index: 10000;
position: fixed;
top: 0;
left: 0;

View File

@ -0,0 +1,5 @@
import { writable } from 'svelte/store'
import { type CompAndProps } from './popups'
import { type LabelAndProps } from './types'
export const modalStore = writable<Array<LabelAndProps | CompAndProps>>([])

View File

@ -1,6 +1,6 @@
import { getResource } from '@hcengineering/platform'
import { type ComponentType } from 'svelte'
import { derived, get, writable } from 'svelte/store'
import { derived, get } from 'svelte/store'
import type {
AnyComponent,
AnySvelteComponent,
@ -13,8 +13,10 @@ import type {
} from './types'
import { Analytics } from '@hcengineering/analytics'
import { modalStore } from './modals'
export interface CompAndProps {
type?: 'popup'
id: string
is: AnySvelteComponent | ComponentType
props: any
@ -41,26 +43,30 @@ export interface PopupResult {
update: (props: Record<string, any>) => void
}
export const popupstore = writable<CompAndProps[]>([])
export const popupstore = derived(modalStore, (modals) => {
return modals.filter((m) => m.type === 'popup') as CompAndProps[]
})
export const dockStore = derived(popupstore, (popups) => {
return popups.find((popup) => popup.dock)
export const dockStore = derived(modalStore, (modals) => {
return (modals.filter((m) => m.type === 'popup') as CompAndProps[]).find((popup: CompAndProps) => popup.dock)
})
export function updatePopup (id: string, props: Partial<CompAndProps>): void {
popupstore.update((popups) => {
const popupIndex = popups.findIndex((p) => p.id === id)
modalStore.update((modals) => {
const popupIndex = (modals.filter((m) => m.type === 'popup') as CompAndProps[]).findIndex(
(p: CompAndProps) => p.id === id
)
if (popupIndex !== -1) {
popups[popupIndex].update?.(props)
;(modals[popupIndex] as CompAndProps).update?.(props)
}
return popups
return modals
})
}
function addPopup (props: CompAndProps): void {
popupstore.update((popups) => {
popups.push(props)
return popups
modalStore.update((modals) => {
modals.push(props)
return modals
})
}
@ -93,8 +99,8 @@ export function showPopup (
): PopupResult {
const id = `${popupId++}`
const closePopupOp = (): void => {
popupstore.update((popups) => {
const pos = popups.findIndex((p) => p.id === id)
modalStore.update((popups) => {
const pos = popups.findIndex((p) => (p as CompAndProps).id === id && p.type === 'popup')
if (pos !== -1) {
popups.splice(pos, 1)
}
@ -109,7 +115,8 @@ export function showPopup (
onClose,
onUpdate,
close: closePopupOp,
options
options,
type: 'popup'
}
if (checkDockPosition(options.refId)) {
data.dock = true
@ -136,19 +143,22 @@ export function showPopup (
}
export function closePopup (category?: string): void {
popupstore.update((popups) => {
modalStore.update((popups) => {
if (category !== undefined) {
popups = popups.filter((p) => p.options.category !== category)
popups = popups.filter((p) => p.type === 'popup' && p.options.category !== category)
} else {
for (let i = popups.length - 1; i >= 0; i--) {
if (popups[i].options.fixed !== true) {
const isClosing = popups[i].closing ?? false
popups[i].closing = true
if (popups[i].type !== 'popup') continue
if ((popups[i] as CompAndProps).options.fixed !== true) {
const isClosing = (popups[i] as CompAndProps).closing ?? false
if (popups[i].type === 'popup') {
;(popups[i] as CompAndProps).closing = true
}
if (!isClosing) {
// To prevent possible recursion, we need to check if we call some code from popup close, to do close.
popups[i].onClose?.(undefined)
;(popups[i] as CompAndProps).onClose?.(undefined)
}
popups[i].closing = false
;(popups[i] as CompAndProps).closing = false
popups.splice(i, 1)
break
}
@ -448,9 +458,10 @@ export function getEventPositionElement (evt: MouseEvent): PopupAlignment | unde
}
export function pin (id: string): void {
popupstore.update((popups) => {
const current = popups.find((p) => p.id === id)
popups.forEach((p) => (p.dock = p.id === id))
modalStore.update((popups) => {
const currentPopups = popups.filter((m) => m.type === 'popup') as CompAndProps[]
const current = currentPopups.find((p) => p.id === id) as CompAndProps
;(popups.filter((m) => m.type === 'popup') as CompAndProps[]).forEach((p) => (p.dock = p.id === id))
if (current?.options.refId !== undefined) {
localStorage.setItem('dock-popup', current.options.refId)
}
@ -459,8 +470,8 @@ export function pin (id: string): void {
}
export function unpin (): void {
popupstore.update((popups) => {
popups.forEach((p) => (p.dock = false))
modalStore.update((popups) => {
;(popups.filter((m) => m.type === 'popup') as CompAndProps[]).forEach((p) => (p.dock = false))
return popups
})
localStorage.removeItem('dock-popup')

View File

@ -1,6 +1,7 @@
import { type IntlString } from '@hcengineering/platform'
import { writable } from 'svelte/store'
import { derived } from 'svelte/store'
import type { AnyComponent, AnySvelteComponent, LabelAndProps, TooltipAlignment } from './types'
import { modalStore } from './modals'
const emptyTooltip: LabelAndProps = {
label: undefined,
@ -14,7 +15,13 @@ const emptyTooltip: LabelAndProps = {
kind: 'tooltip'
}
let storedValue: LabelAndProps = emptyTooltip
export const tooltipstore = writable<LabelAndProps>(emptyTooltip)
export const tooltipstore = derived(modalStore, (modals) => {
if (modals.length === 0) {
return emptyTooltip
}
const tooltip = modals.filter((m) => m?.type === 'tooltip')
return tooltip.length > 0 ? (tooltip[0] as LabelAndProps) : emptyTooltip
})
let toHandler: any
export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
@ -39,7 +46,7 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
opt.kind,
opt.keys
)
}, 250)
}, 10)
} else {
showTooltip(
opt.label,
@ -107,23 +114,29 @@ export function showTooltip (
anchor,
onUpdate,
kind,
keys
keys,
type: 'tooltip'
}
tooltipstore.update((old) => {
if (old.component === storedValue.component) {
if (old.kind !== undefined && storedValue.kind === undefined) {
storedValue.kind = old.kind
modalStore.update((old) => {
const tooltip = old.find((m) => m?.type === 'tooltip') as LabelAndProps | undefined
if (tooltip !== undefined && tooltip.component === storedValue.component) {
if (tooltip.kind !== undefined && storedValue.kind === undefined) {
storedValue.kind = tooltip.kind
}
if (storedValue.kind === undefined) {
storedValue.kind = 'tooltip'
}
}
return storedValue
old.push(storedValue)
return old
})
}
export function closeTooltip (): void {
clearTimeout(toHandler)
storedValue = emptyTooltip
tooltipstore.set(emptyTooltip)
modalStore.update((old) => {
old = old.filter((m) => m?.type !== 'tooltip')
return old
})
}

View File

@ -282,6 +282,7 @@ export interface DateOrShift {
}
export interface LabelAndProps {
type?: 'tooltip'
label?: IntlString
element?: HTMLElement
direction?: TooltipAlignment