mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 21:50:34 +03:00
add single store for both popups and tooltip (#5808)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
caf3fedd9c
commit
649c16fe13
@ -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}
|
||||
|
@ -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={() => {}}
|
||||
/>
|
||||
|
@ -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;
|
||||
|
5
packages/ui/src/modals.ts
Normal file
5
packages/ui/src/modals.ts
Normal 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>>([])
|
@ -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')
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -282,6 +282,7 @@ export interface DateOrShift {
|
||||
}
|
||||
|
||||
export interface LabelAndProps {
|
||||
type?: 'tooltip'
|
||||
label?: IntlString
|
||||
element?: HTMLElement
|
||||
direction?: TooltipAlignment
|
||||
|
Loading…
Reference in New Issue
Block a user