mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 16:56:07 +00: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.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<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'
|
import PopupInstance from './PopupInstance.svelte'
|
||||||
|
|
||||||
export let contentPanel: HTMLElement | undefined = undefined
|
export let contentPanel: HTMLElement | undefined = undefined
|
||||||
@ -24,13 +26,13 @@
|
|||||||
instances.forEach((p) => p.fitPopupInstance())
|
instances.forEach((p) => p.fitPopupInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
$: instances.length = $modal.filter((p) => p.dock !== true).length
|
$: instances.length = $popups.filter((p) => p.dock !== true).length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $modal.length > 0}
|
{#if $popups.length > 0}
|
||||||
<slot name="popup-header" />
|
<slot name="popup-header" />
|
||||||
{/if}
|
{/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
|
<PopupInstance
|
||||||
bind:this={instances[i]}
|
bind:this={instances[i]}
|
||||||
is={popup.is}
|
is={popup.is}
|
||||||
@ -38,8 +40,8 @@
|
|||||||
element={popup.element}
|
element={popup.element}
|
||||||
onClose={popup.onClose}
|
onClose={popup.onClose}
|
||||||
onUpdate={popup.onUpdate}
|
onUpdate={popup.onUpdate}
|
||||||
zIndex={(i + 1) * 500}
|
zIndex={($modals.findIndex((modal) => modal.type === 'popup' && modal.id === popup.id) ?? i) + 10000}
|
||||||
top={$modal.length - 1 === i}
|
top={$popups.length - 1 === i}
|
||||||
close={popup.close}
|
close={popup.close}
|
||||||
{contentPanel}
|
{contentPanel}
|
||||||
overlay={popup.options.overlay}
|
overlay={popup.options.overlay}
|
||||||
|
@ -280,7 +280,7 @@
|
|||||||
class:testing
|
class:testing
|
||||||
class:anim={(element === 'float' || element === 'centered') && !testing && !drag}
|
class:anim={(element === 'float' || element === 'centered') && !testing && !drag}
|
||||||
bind:this={modalHTML}
|
bind:this={modalHTML}
|
||||||
style={`z-index: ${zIndex + 1};`}
|
style={`z-index: ${zIndex};`}
|
||||||
style:top={options?.props?.top}
|
style:top={options?.props?.top}
|
||||||
style:bottom={options?.props?.bottom}
|
style:bottom={options?.props?.bottom}
|
||||||
style:left={options?.props?.left}
|
style:left={options?.props?.left}
|
||||||
@ -331,7 +331,7 @@
|
|||||||
class="modal-overlay"
|
class="modal-overlay"
|
||||||
class:testing
|
class:testing
|
||||||
class:antiOverlay={options?.showOverlay && !drag}
|
class:antiOverlay={options?.showOverlay && !drag}
|
||||||
style={`z-index: ${zIndex};`}
|
style={`z-index: ${zIndex - 1};`}
|
||||||
on:click={handleOverlayClick}
|
on:click={handleOverlayClick}
|
||||||
on:keydown|stopPropagation|preventDefault={() => {}}
|
on:keydown|stopPropagation|preventDefault={() => {}}
|
||||||
/>
|
/>
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterUpdate, onDestroy } from 'svelte'
|
import { afterUpdate, onDestroy } from 'svelte'
|
||||||
import { resizeObserver } from '../resize'
|
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 type { TooltipAlignment } from '../types'
|
||||||
import Component from './Component.svelte'
|
import Component from './Component.svelte'
|
||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
@ -259,6 +260,7 @@
|
|||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
class="modal-overlay antiOverlay"
|
class="modal-overlay antiOverlay"
|
||||||
|
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
|
||||||
on:click|stopPropagation|preventDefault={() => {
|
on:click|stopPropagation|preventDefault={() => {
|
||||||
closeTooltip()
|
closeTooltip()
|
||||||
}}
|
}}
|
||||||
@ -301,6 +303,7 @@
|
|||||||
style:width={options.width}
|
style:width={options.width}
|
||||||
style:height={options.height}
|
style:height={options.height}
|
||||||
style:transform={options.transform}
|
style:transform={options.transform}
|
||||||
|
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
|
||||||
bind:this={tooltipHTML}
|
bind:this={tooltipHTML}
|
||||||
>
|
>
|
||||||
{#if $tooltip.label}
|
{#if $tooltip.label}
|
||||||
@ -319,13 +322,18 @@
|
|||||||
this={$tooltip.component}
|
this={$tooltip.component}
|
||||||
{...$tooltip.props}
|
{...$tooltip.props}
|
||||||
on:tooltip={(evt) => {
|
on:tooltip={(evt) => {
|
||||||
$tooltip = { ...$tooltip, ...evt.detail }
|
$modals = [...$modals.filter((t) => t.type !== 'tooltip'), { ...$tooltip, ...evt.detail }]
|
||||||
}}
|
}}
|
||||||
on:update={onUpdate !== undefined ? onUpdate : async () => {}}
|
on:update={onUpdate !== undefined ? onUpdate : async () => {}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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'}
|
{:else if $tooltip.label && $tooltip.kind !== 'submenu'}
|
||||||
<div
|
<div
|
||||||
class="tooltip {dir ?? ''} {options.classList}"
|
class="tooltip {dir ?? ''} {options.classList}"
|
||||||
@ -337,6 +345,7 @@
|
|||||||
style:width={options.width}
|
style:width={options.width}
|
||||||
style:height={options.height}
|
style:height={options.height}
|
||||||
style:transform={options.transform}
|
style:transform={options.transform}
|
||||||
|
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
|
||||||
>
|
>
|
||||||
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
||||||
{#if $tooltip.keys !== undefined}
|
{#if $tooltip.keys !== undefined}
|
||||||
@ -372,6 +381,7 @@
|
|||||||
style:width={options.width}
|
style:width={options.width}
|
||||||
style:height={options.height}
|
style:height={options.height}
|
||||||
style:transform={options.transform}
|
style:transform={options.transform}
|
||||||
|
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
|
||||||
bind:this={tooltipHTML}
|
bind:this={tooltipHTML}
|
||||||
>
|
>
|
||||||
{#if typeof $tooltip.component === 'string'}
|
{#if typeof $tooltip.component === 'string'}
|
||||||
@ -396,7 +406,6 @@
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
z-index: 10000;
|
|
||||||
}
|
}
|
||||||
.popup-tooltip {
|
.popup-tooltip {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -412,7 +421,6 @@
|
|||||||
box-shadow: var(--theme-popup-shadow);
|
box-shadow: var(--theme-popup-shadow);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
&.doublePadding {
|
&.doublePadding {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@ -425,7 +433,6 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
&::after,
|
&::after,
|
||||||
&::before {
|
&::before {
|
||||||
@ -522,7 +529,6 @@
|
|||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
box-shadow: var(--theme-popup-shadow);
|
box-shadow: var(--theme-popup-shadow);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: 10000;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@ -545,8 +551,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
z-index: 10000;
|
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 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 { getResource } from '@hcengineering/platform'
|
||||||
import { type ComponentType } from 'svelte'
|
import { type ComponentType } from 'svelte'
|
||||||
import { derived, get, writable } from 'svelte/store'
|
import { derived, get } from 'svelte/store'
|
||||||
import type {
|
import type {
|
||||||
AnyComponent,
|
AnyComponent,
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
@ -13,8 +13,10 @@ import type {
|
|||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
|
import { modalStore } from './modals'
|
||||||
|
|
||||||
export interface CompAndProps {
|
export interface CompAndProps {
|
||||||
|
type?: 'popup'
|
||||||
id: string
|
id: string
|
||||||
is: AnySvelteComponent | ComponentType
|
is: AnySvelteComponent | ComponentType
|
||||||
props: any
|
props: any
|
||||||
@ -41,26 +43,30 @@ export interface PopupResult {
|
|||||||
update: (props: Record<string, any>) => void
|
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) => {
|
export const dockStore = derived(modalStore, (modals) => {
|
||||||
return popups.find((popup) => popup.dock)
|
return (modals.filter((m) => m.type === 'popup') as CompAndProps[]).find((popup: CompAndProps) => popup.dock)
|
||||||
})
|
})
|
||||||
|
|
||||||
export function updatePopup (id: string, props: Partial<CompAndProps>): void {
|
export function updatePopup (id: string, props: Partial<CompAndProps>): void {
|
||||||
popupstore.update((popups) => {
|
modalStore.update((modals) => {
|
||||||
const popupIndex = popups.findIndex((p) => p.id === id)
|
const popupIndex = (modals.filter((m) => m.type === 'popup') as CompAndProps[]).findIndex(
|
||||||
|
(p: CompAndProps) => p.id === id
|
||||||
|
)
|
||||||
if (popupIndex !== -1) {
|
if (popupIndex !== -1) {
|
||||||
popups[popupIndex].update?.(props)
|
;(modals[popupIndex] as CompAndProps).update?.(props)
|
||||||
}
|
}
|
||||||
return popups
|
return modals
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPopup (props: CompAndProps): void {
|
function addPopup (props: CompAndProps): void {
|
||||||
popupstore.update((popups) => {
|
modalStore.update((modals) => {
|
||||||
popups.push(props)
|
modals.push(props)
|
||||||
return popups
|
return modals
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,8 +99,8 @@ export function showPopup (
|
|||||||
): PopupResult {
|
): PopupResult {
|
||||||
const id = `${popupId++}`
|
const id = `${popupId++}`
|
||||||
const closePopupOp = (): void => {
|
const closePopupOp = (): void => {
|
||||||
popupstore.update((popups) => {
|
modalStore.update((popups) => {
|
||||||
const pos = popups.findIndex((p) => p.id === id)
|
const pos = popups.findIndex((p) => (p as CompAndProps).id === id && p.type === 'popup')
|
||||||
if (pos !== -1) {
|
if (pos !== -1) {
|
||||||
popups.splice(pos, 1)
|
popups.splice(pos, 1)
|
||||||
}
|
}
|
||||||
@ -109,7 +115,8 @@ export function showPopup (
|
|||||||
onClose,
|
onClose,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
close: closePopupOp,
|
close: closePopupOp,
|
||||||
options
|
options,
|
||||||
|
type: 'popup'
|
||||||
}
|
}
|
||||||
if (checkDockPosition(options.refId)) {
|
if (checkDockPosition(options.refId)) {
|
||||||
data.dock = true
|
data.dock = true
|
||||||
@ -136,19 +143,22 @@ export function showPopup (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closePopup (category?: string): void {
|
export function closePopup (category?: string): void {
|
||||||
popupstore.update((popups) => {
|
modalStore.update((popups) => {
|
||||||
if (category !== undefined) {
|
if (category !== undefined) {
|
||||||
popups = popups.filter((p) => p.options.category !== category)
|
popups = popups.filter((p) => p.type === 'popup' && p.options.category !== category)
|
||||||
} else {
|
} else {
|
||||||
for (let i = popups.length - 1; i >= 0; i--) {
|
for (let i = popups.length - 1; i >= 0; i--) {
|
||||||
if (popups[i].options.fixed !== true) {
|
if (popups[i].type !== 'popup') continue
|
||||||
const isClosing = popups[i].closing ?? false
|
if ((popups[i] as CompAndProps).options.fixed !== true) {
|
||||||
popups[i].closing = true
|
const isClosing = (popups[i] as CompAndProps).closing ?? false
|
||||||
|
if (popups[i].type === 'popup') {
|
||||||
|
;(popups[i] as CompAndProps).closing = true
|
||||||
|
}
|
||||||
if (!isClosing) {
|
if (!isClosing) {
|
||||||
// To prevent possible recursion, we need to check if we call some code from popup close, to do close.
|
// 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)
|
popups.splice(i, 1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -448,9 +458,10 @@ export function getEventPositionElement (evt: MouseEvent): PopupAlignment | unde
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function pin (id: string): void {
|
export function pin (id: string): void {
|
||||||
popupstore.update((popups) => {
|
modalStore.update((popups) => {
|
||||||
const current = popups.find((p) => p.id === id)
|
const currentPopups = popups.filter((m) => m.type === 'popup') as CompAndProps[]
|
||||||
popups.forEach((p) => (p.dock = p.id === id))
|
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) {
|
if (current?.options.refId !== undefined) {
|
||||||
localStorage.setItem('dock-popup', current.options.refId)
|
localStorage.setItem('dock-popup', current.options.refId)
|
||||||
}
|
}
|
||||||
@ -459,8 +470,8 @@ export function pin (id: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function unpin (): void {
|
export function unpin (): void {
|
||||||
popupstore.update((popups) => {
|
modalStore.update((popups) => {
|
||||||
popups.forEach((p) => (p.dock = false))
|
;(popups.filter((m) => m.type === 'popup') as CompAndProps[]).forEach((p) => (p.dock = false))
|
||||||
return popups
|
return popups
|
||||||
})
|
})
|
||||||
localStorage.removeItem('dock-popup')
|
localStorage.removeItem('dock-popup')
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { type IntlString } from '@hcengineering/platform'
|
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 type { AnyComponent, AnySvelteComponent, LabelAndProps, TooltipAlignment } from './types'
|
||||||
|
import { modalStore } from './modals'
|
||||||
|
|
||||||
const emptyTooltip: LabelAndProps = {
|
const emptyTooltip: LabelAndProps = {
|
||||||
label: undefined,
|
label: undefined,
|
||||||
@ -14,7 +15,13 @@ const emptyTooltip: LabelAndProps = {
|
|||||||
kind: 'tooltip'
|
kind: 'tooltip'
|
||||||
}
|
}
|
||||||
let storedValue: LabelAndProps = emptyTooltip
|
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
|
let toHandler: any
|
||||||
export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||||
@ -39,7 +46,7 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
|||||||
opt.kind,
|
opt.kind,
|
||||||
opt.keys
|
opt.keys
|
||||||
)
|
)
|
||||||
}, 250)
|
}, 10)
|
||||||
} else {
|
} else {
|
||||||
showTooltip(
|
showTooltip(
|
||||||
opt.label,
|
opt.label,
|
||||||
@ -107,23 +114,29 @@ export function showTooltip (
|
|||||||
anchor,
|
anchor,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
kind,
|
kind,
|
||||||
keys
|
keys,
|
||||||
|
type: 'tooltip'
|
||||||
}
|
}
|
||||||
tooltipstore.update((old) => {
|
modalStore.update((old) => {
|
||||||
if (old.component === storedValue.component) {
|
const tooltip = old.find((m) => m?.type === 'tooltip') as LabelAndProps | undefined
|
||||||
if (old.kind !== undefined && storedValue.kind === undefined) {
|
if (tooltip !== undefined && tooltip.component === storedValue.component) {
|
||||||
storedValue.kind = old.kind
|
if (tooltip.kind !== undefined && storedValue.kind === undefined) {
|
||||||
|
storedValue.kind = tooltip.kind
|
||||||
}
|
}
|
||||||
if (storedValue.kind === undefined) {
|
if (storedValue.kind === undefined) {
|
||||||
storedValue.kind = 'tooltip'
|
storedValue.kind = 'tooltip'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return storedValue
|
old.push(storedValue)
|
||||||
|
return old
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeTooltip (): void {
|
export function closeTooltip (): void {
|
||||||
clearTimeout(toHandler)
|
clearTimeout(toHandler)
|
||||||
storedValue = emptyTooltip
|
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 {
|
export interface LabelAndProps {
|
||||||
|
type?: 'tooltip'
|
||||||
label?: IntlString
|
label?: IntlString
|
||||||
element?: HTMLElement
|
element?: HTMLElement
|
||||||
direction?: TooltipAlignment
|
direction?: TooltipAlignment
|
||||||
|
Loading…
Reference in New Issue
Block a user