diff --git a/packages/ui/src/popups.ts b/packages/ui/src/popups.ts index f0721471da..dac677d121 100644 --- a/packages/ui/src/popups.ts +++ b/packages/ui/src/popups.ts @@ -1,4 +1,4 @@ -import { AnySvelteComponent, AnyComponent, PopupAlignment } from './types' +import { AnySvelteComponent, AnyComponent, PopupAlignment, PopupPositionElement } from './types' import { getResource } from '@anticrm/platform' import { writable } from 'svelte/store' @@ -30,7 +30,7 @@ export function showPopup ( const id = `${popupId++}` const closePopupOp = (): void => { popupstore.update((popups) => { - const pos = popups.findIndex(p => p.id === id) + const pos = popups.findIndex((p) => p.id === id) if (pos !== -1) { popups.splice(pos, 1) } @@ -38,7 +38,9 @@ export function showPopup ( }) } if (typeof component === 'string') { - getResource(component).then((resolved) => addPopup({ id, is: resolved, props, element, onClose, onUpdate, close: closePopupOp })).catch((err) => console.log(err)) + getResource(component) + .then((resolved) => addPopup({ id, is: resolved, props, element, onClose, onUpdate, close: closePopupOp })) + .catch((err) => console.log(err)) } else { addPopup({ id, is: component, props, element, onClose, onUpdate, close: closePopupOp }) } @@ -104,6 +106,50 @@ export function closeDatePopup (): void { }) } +/** + * @public + * + * Place element based on position and element. + * + * return boolean to show or not modal overlay. + */ +export function fitPopupPositionedElement (modalHTML: HTMLElement, alignment: PopupPositionElement): boolean { + const rect = alignment.getBoundingClientRect() + const rectPopup = modalHTML.getBoundingClientRect() + modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = '' + modalHTML.style.maxHeight = modalHTML.style.height = '' + if (alignment.position) { + if (alignment.position.v === 'top') { + modalHTML.style.top = `${rect.top}px` + } else if (alignment.position.v === 'bottom') { + modalHTML.style.top = `${rect.bottom - rectPopup.height}px` + } + + if (alignment.position.h === 'right') { + modalHTML.style.left = `calc(${rect.right}px + .125rem)` + } else if (alignment.position.h === 'left') { + modalHTML.style.left = `calc(${rect.left - rectPopup.width}px - .125rem)` + } + } else { + // Vertical + if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) { + modalHTML.style.top = `calc(${rect.bottom}px + .125rem)` + } else if (rectPopup.height + 28 < rect.top) { + modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + .125rem)` + } else { + modalHTML.style.top = modalHTML.style.bottom = '1rem' + } + + // Horizontal + if (rect.left + rectPopup.width + 16 > document.body.clientWidth) { + modalHTML.style.right = `${document.body.clientWidth - rect.right}px` + } else { + modalHTML.style.left = `${rect.left}px` + } + } + return false +} + /** * @public * @@ -118,24 +164,7 @@ export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignmen modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = '' modalHTML.style.maxHeight = modalHTML.style.height = '' if (typeof element !== 'string') { - const el = element as HTMLElement - const rect = el.getBoundingClientRect() - const rectPopup = modalHTML.getBoundingClientRect() - // Vertical - if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) { - modalHTML.style.top = `calc(${rect.bottom}px + .125rem)` - } else if (rectPopup.height + 28 < rect.top) { - modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + .125rem)` - } else { - modalHTML.style.top = modalHTML.style.bottom = '1rem' - } - - // Horizontal - if (rect.left + rectPopup.width + 16 > document.body.clientWidth) { - modalHTML.style.right = `${document.body.clientWidth - rect.right}px` - } else { - modalHTML.style.left = `${rect.left}px` - } + return fitPopupPositionedElement(modalHTML, element) } else if (element === 'right' && contentPanel !== undefined) { const rect = contentPanel.getBoundingClientRect() modalHTML.style.top = `calc(${rect.top}px + 0.5rem)` diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index c5f6d77051..f0b9e50033 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -67,9 +67,15 @@ export type ButtonKind = 'primary' | 'secondary' | 'no-border' | 'transparent' | export type ButtonSize = 'small' | 'medium' | 'large' | 'x-large' export interface PopupPositionElement { getBoundingClientRect: () => DOMRect + position?: { + v: VerticalAlignment + h: HorizontalAlignment + } } export type PopupAlignment = PopupPositionElement | null | 'right' | 'top' | 'account' | 'full' | 'content' | 'middle' export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right' +export type VerticalAlignment = 'top' | 'bottom' +export type HorizontalAlignment = 'left' | 'right' export interface LabelAndProps { label: IntlString | undefined diff --git a/plugins/board-resources/src/components/KanbanCard.svelte b/plugins/board-resources/src/components/KanbanCard.svelte index 2ee5c27f02..93df09c519 100644 --- a/plugins/board-resources/src/components/KanbanCard.svelte +++ b/plugins/board-resources/src/components/KanbanCard.svelte @@ -21,10 +21,11 @@ import type { Ref, WithLookup } from '@anticrm/core' import notification from '@anticrm/notification' import { getClient, UserBoxList } from '@anticrm/presentation' - import { Button, Component, IconEdit, IconMoreH, Label, showPanel, showPopup } from '@anticrm/ui' + import { Button, Component, IconEdit, Label, showPanel, showPopup } from '@anticrm/ui' import { ContextMenu } from '@anticrm/view-resources' import board from '../plugin' import { hasDate } from '../utils/CardUtils' + import { getElementPopupAlignment } from '../utils/PopupUtils' import CardLabels from './editor/CardLabels.svelte' import DatePresenter from './presenters/DatePresenter.svelte' @@ -33,11 +34,12 @@ let loadingAttachment = 0 let dragoverAttachment = false + let ref: HTMLElement const client = getClient() - function showMenu (ev?: Event): void { - showPopup(ContextMenu, { object }, (ev as MouseEvent).target as HTMLElement) + function showMenu (): void { + showPopup(ContextMenu, { object }, getElementPopupAlignment(ref, { h: 'right', v: 'top' })) } function showCard () { @@ -65,7 +67,7 @@ objectId={object._id} space={object.space} canDrop={canDropAttachment}> - <div class="relative flex-col pt-2 pb-1 pr-2 pl-2"> + <div class="relative flex-col pt-2 pb-1 pr-2 pl-2" bind:this={ref}> {#if dragoverAttachment} <div style:pointer-events="none" class="abs-full-content h-full w-full flex-center fs-title"> <Label label={board.string.DropFileToUpload} /> diff --git a/plugins/board-resources/src/components/KanbanView.svelte b/plugins/board-resources/src/components/KanbanView.svelte index 6093e754c1..e53c6415e6 100644 --- a/plugins/board-resources/src/components/KanbanView.svelte +++ b/plugins/board-resources/src/components/KanbanView.svelte @@ -116,7 +116,7 @@ {space} {search} {options} - query={{ doneState: null, isArchived: {$nin: [true]} }} + query={{ doneState: null, isArchived: { $nin: [true] } }} {states} fieldName={'state'} rankFieldName={'rank'} diff --git a/plugins/board-resources/src/utils/PopupUtils.ts b/plugins/board-resources/src/utils/PopupUtils.ts index e3fdeb950d..fc8e3d1ac0 100644 --- a/plugins/board-resources/src/utils/PopupUtils.ts +++ b/plugins/board-resources/src/utils/PopupUtils.ts @@ -1,14 +1,20 @@ import { PopupAlignment } from '@anticrm/ui' +import { HorizontalAlignment, VerticalAlignment } from '@anticrm/ui/src/types' -export function getPopupAlignment (e?: Event): PopupAlignment | undefined { +export function getPopupAlignment (e?: Event, position?: {v: VerticalAlignment, h: HorizontalAlignment}): PopupAlignment | undefined { if (!e || !e.target) { return undefined } const target = e.target as HTMLElement - if (target.getBoundingClientRect) { - const result = target.getBoundingClientRect() + return getElementPopupAlignment(target, position) +} + +export function getElementPopupAlignment (el: HTMLElement, position?: {v: VerticalAlignment, h: HorizontalAlignment}): PopupAlignment | undefined { + if (el?.getBoundingClientRect) { + const result = el.getBoundingClientRect() return { - getBoundingClientRect: () => result + getBoundingClientRect: () => result, + position } }