diff --git a/dev/prod/webpack.config.js b/dev/prod/webpack.config.js index 3669c434cd..2dce1b098f 100644 --- a/dev/prod/webpack.config.js +++ b/dev/prod/webpack.config.js @@ -277,7 +277,7 @@ module.exports = [ plugins: [ new HtmlWebpackPlugin({ meta: { - viewport: 'width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=1' + viewport: 'width=device-width, initial-scale=1' } }), ...(prod ? [new CompressionPlugin()] : []), diff --git a/packages/theme/styles/common.scss b/packages/theme/styles/common.scss index 3509ad2b06..6ea42526e3 100644 --- a/packages/theme/styles/common.scss +++ b/packages/theme/styles/common.scss @@ -149,8 +149,7 @@ width: 17.5rem; } &.right { - overflow: hidden; - border-radius: var(--medium-BorderRadius) 0 0 var(--medium-BorderRadius); + border-radius: var(--medium-BorderRadius); } } @@ -162,7 +161,7 @@ &:not(.right) { top: calc(var(--status-bar-height) + 1px); - height: calc(100% - var(--status-bar-height) - 1px); + height: calc(100% - var(--status-bar-height) - 2px); filter: drop-shadow(2px 0 5px rgba(0, 0, 0, .2)); &.portrait { left: 0; } @@ -171,12 +170,28 @@ &.right { top: var(--status-bar-height); right: 0; - height: calc(100% - var(--status-bar-height) - 1px); + height: calc(100% - var(--status-bar-height)); background-color: var(--theme-statusbar-color); filter: drop-shadow(-2px 0 5px rgba(0, 0, 0, .2)); } } } +@media (max-width: 480px) { + .mobile-theme { + .mobile-wrapper, + .antiPanel-navigator:not(.right) { + overflow: hidden; + border: 1px solid var(--theme-divider-color); + border-radius: var(--medium-BorderRadius); + } + .antiPanel-navigator:not(.right) { + top: var(--status-bar-height); + height: calc(100% - var(--status-bar-height)); + + .antiSeparator { display: none; } + } + } +} .antiPanel-component { overflow: hidden; flex-direction: column; diff --git a/packages/theme/styles/dialogs.scss b/packages/theme/styles/dialogs.scss index 5867c2727f..b4441ddc4b 100644 --- a/packages/theme/styles/dialogs.scss +++ b/packages/theme/styles/dialogs.scss @@ -304,7 +304,7 @@ display: flex; flex-direction: column; height: 100%; - min-width: 25rem; + min-width: 20rem; min-height: 0; background-color: var(--theme-popup-color); border-radius: .5rem; diff --git a/packages/theme/styles/global.scss b/packages/theme/styles/global.scss index a7f883a923..801bf1d7f0 100644 --- a/packages/theme/styles/global.scss +++ b/packages/theme/styles/global.scss @@ -147,7 +147,7 @@ body { height: var(--app-height); width: 100%; overflow: hidden; - touch-action: none; + // touch-action: none; @media print { overflow: visible !important; diff --git a/packages/theme/styles/panel.scss b/packages/theme/styles/panel.scss index 44e2af9301..0702583cd8 100644 --- a/packages/theme/styles/panel.scss +++ b/packages/theme/styles/panel.scss @@ -293,8 +293,6 @@ min-width: 0; min-height: 0; border-radius: var(--small-focus-BorderRadius); - border-top-right-radius: 0; - border-bottom-right-radius:0 ; &:not(.rowContent) { flex-direction: column; } .panel-instance & { @@ -590,5 +588,6 @@ } } .popup.fullsize { + align-items: center; transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19) !important; } diff --git a/packages/ui/src/components/Dialog.svelte b/packages/ui/src/components/Dialog.svelte index d9f9851e3f..9cc185ad8f 100644 --- a/packages/ui/src/components/Dialog.svelte +++ b/packages/ui/src/components/Dialog.svelte @@ -16,7 +16,17 @@ <script lang="ts"> import type { IntlString } from '@hcengineering/platform' import { createEventDispatcher } from 'svelte' - import { Button, Label, IconClose, IconScale, IconScaleFull, resizeObserver, tooltip } from '..' + import { + Button, + Label, + IconClose, + IconScale, + IconScaleFull, + resizeObserver, + tooltip, + deviceOptionsStore as deviceInfo, + checkAdaptiveMatching + } from '..' export let label: IntlString | undefined = undefined export let isFullSize: boolean = false @@ -25,6 +35,12 @@ const dispatch = createEventDispatcher() let fullSize: boolean = false + let toggleFullSize: boolean = fullSize + $: needFullSize = checkAdaptiveMatching($deviceInfo.size, 'md') + $: if ((needFullSize && !fullSize) || (!needFullSize && (fullSize || toggleFullSize))) { + fullSize = toggleFullSize ? true : needFullSize + dispatch('fullsize', fullSize) + } </script> <form @@ -47,20 +63,18 @@ {#if $$slots.utils} <slot name="utils" /> {/if} - {#if $$slots.utils && isFullSize} + {#if $$slots.utils && isFullSize && !needFullSize} <div class="buttons-divider" /> {/if} - {#if isFullSize} + {#if isFullSize && !needFullSize} <Button focusIndex={100010} icon={fullSize ? IconScale : IconScaleFull} kind={'ghost'} size={'medium'} selected={fullSize} - on:click={() => { - fullSize = !fullSize - dispatch('fullsize') - }} + id={'btnDialogFullScreen'} + on:click={() => (toggleFullSize = !toggleFullSize)} /> {/if} </div> diff --git a/packages/ui/src/components/Header.svelte b/packages/ui/src/components/Header.svelte index edf9ae97d5..6fe72d133a 100644 --- a/packages/ui/src/components/Header.svelte +++ b/packages/ui/src/components/Header.svelte @@ -38,8 +38,9 @@ export let overflowExtra: boolean = false export let noPrint: boolean = false export let freezeBefore: boolean = false - export let doubleRowWidth = 768 + export let doubleRowWidth: number = 768 export let closeOnEscape: boolean = true + export let realWidth: number | undefined = undefined const dispatch = createEventDispatcher() @@ -78,6 +79,7 @@ <!-- svelte-ignore a11y-no-static-element-interactions --> <div use:resizeObserver={(element) => { + realWidth = element.clientWidth if (!doubleRow && element.clientWidth <= doubleRowWidth) doubleRow = true else if (doubleRow && element.clientWidth > doubleRowWidth) doubleRow = false }} diff --git a/packages/ui/src/components/PopupInstance.svelte b/packages/ui/src/components/PopupInstance.svelte index fa78c370ec..bb36c19e07 100644 --- a/packages/ui/src/components/PopupInstance.svelte +++ b/packages/ui/src/components/PopupInstance.svelte @@ -15,7 +15,7 @@ --> <script lang="ts"> import { onMount } from 'svelte' - import { deviceOptionsStore as deviceInfo, resizeObserver, testing } from '..' + import { deviceOptionsStore as deviceInfo, resizeObserver, testing, checkAdaptiveMatching } from '..' import { CompAndProps, fitPopupElement, pin } from '../popups' import type { AnySvelteComponent, DeviceOptions, PopupAlignment, PopupOptions, PopupPositionElement } from '../types' @@ -75,6 +75,7 @@ } $: document.body.style.cursor = drag ? 'all-scroll' : 'default' + $: docSize = checkAdaptiveMatching($deviceInfo.size, 'md') function _update (result: any): void { if (onUpdate !== undefined) onUpdate(result) @@ -247,9 +248,6 @@ } } - $: if ($deviceInfo.docWidth <= 900 && !docSize) docSize = true - $: if ($deviceInfo.docWidth > 900 && docSize) docSize = false - onMount(() => { windowSize.width = $deviceInfo.docWidth windowSize.height = $deviceInfo.docHeight @@ -310,8 +308,9 @@ on:close={(ev) => { _close(ev?.detail) }} - on:fullsize={() => { - fullSize = !fullSize + on:fullsize={(ev) => { + if (ev.detail === undefined) return + fullSize = ev.detail fitPopup(modalHTML, element, contentPanel) }} on:dock={() => { diff --git a/packages/ui/src/components/Separator.svelte b/packages/ui/src/components/Separator.svelte index 7ab8e37f72..c00f94984d 100644 --- a/packages/ui/src/components/Separator.svelte +++ b/packages/ui/src/components/Separator.svelte @@ -518,6 +518,7 @@ let checkElements: boolean = false const resizeDocument = (): void => { + if (checkFullWidth()) checkSizes() if (parentElement == null || checkElements || sState !== SeparatorState.NORMAL) return checkElements = true setTimeout(() => { diff --git a/packages/ui/src/components/icons/ToDetails.svelte b/packages/ui/src/components/icons/ToDetails.svelte new file mode 100644 index 0000000000..338474c339 --- /dev/null +++ b/packages/ui/src/components/icons/ToDetails.svelte @@ -0,0 +1,28 @@ +<!-- +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +--> +<script lang="ts"> + export let size: 'x-small' | 'small' | 'medium' | 'large' + export let fill: string = 'currentColor' +</script> + +<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <path + d="M10.6,17.3H3.3c-.3,0-.7-.1-.9-.4-.2-.2-.4-.6-.4-.9s.1-.7.4-.9c.2-.2.6-.4.9-.4h7.3l-1.4-1.4c-.2-.2-.4-.6-.4-.9,0-.3.1-.7.4-.9.2-.2.6-.4.9-.4.3,0,.7.1.9.4l3.7,3.7c.1.1.2.3.3.4,0,.2,0,.3,0,.5s0,.3,0,.5c0,.2-.2.3-.3.4l-3.7,3.7c-.2.2-.6.4-.9.4-.3,0-.7-.1-.9-.4-.2-.2-.4-.6-.4-.9,0-.3.1-.7.3-.9l1.4-1.5Z" + /> + <path + d="M26,4H6c-2.2,0-4,1.8-4,4v4c0,.6.4,1,1,1s1-.4,1-1v-4c0-1.1.9-2,2-2h14v20H6c-1.1,0-2-.9-2-2v-4c0-.6-.4-1-1-1s-1,.4-1,1v4c0,2.2,1.8,4,4,4h20c2.2,0,4-1.8,4-4V8c0-2.2-1.8-4-4-4Z" + /> +</svg> diff --git a/packages/ui/src/components/internal/Root.svelte b/packages/ui/src/components/internal/Root.svelte index 8356f9006f..49858d6374 100644 --- a/packages/ui/src/components/internal/Root.svelte +++ b/packages/ui/src/components/internal/Root.svelte @@ -151,7 +151,7 @@ <svelte:window bind:innerWidth={docWidth} bind:innerHeight={docHeight} /> <Theme> - <div id="ui-root"> + <div id="ui-root" class:mobile-theme={isMobile}> <div class="antiStatusBar"> <div class="flex-row-center h-full content-color gap-3 pl-4"> {#if desktopPlatform} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 774d8d5492..135709bed7 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -237,6 +237,7 @@ export { default as IconKeyShift } from './components/icons/KeyShift.svelte' export { default as IconFolderCollapsed } from './components/icons/FolderCollapsed.svelte' export { default as IconFolderExpanded } from './components/icons/FolderExpanded.svelte' export { default as IconCheckmark } from './components/icons/Checkmark.svelte' +export { default as IconToDetails } from './components/icons/ToDetails.svelte' export { default as PanelInstance } from './components/PanelInstance.svelte' export { default as Panel } from './components/Panel.svelte' diff --git a/packages/ui/src/popups.ts b/packages/ui/src/popups.ts index 5f2bca9fd3..d679c18598 100644 --- a/packages/ui/src/popups.ts +++ b/packages/ui/src/popups.ts @@ -366,10 +366,10 @@ export function fitPopupElement ( show = false } else if (element === 'full' && contentPanel !== undefined) { const rect = contentPanel.getBoundingClientRect() - newProps.top = `${rect.top + 4}px` - newProps.bottom = '4px' - newProps.left = '4px' - newProps.right = '4px' + newProps.top = `${rect.top + 1}px` + newProps.bottom = '1px' + newProps.left = '1px' + newProps.right = '1px' show = true } else if (element === 'content' && contentPanel !== undefined) { const rect = contentPanel.getBoundingClientRect() diff --git a/plugins/attachment-resources/src/components/PreviewPopupActions.svelte b/plugins/attachment-resources/src/components/PreviewPopupActions.svelte index 635f23b40b..e270ef3f6d 100644 --- a/plugins/attachment-resources/src/components/PreviewPopupActions.svelte +++ b/plugins/attachment-resources/src/components/PreviewPopupActions.svelte @@ -15,7 +15,7 @@ <script lang="ts"> import type { Blob, Ref } from '@hcengineering/core' import { BlobMetadata } from '@hcengineering/presentation' - import { Button, closePopup, closeTooltip, IconDetailsFilled } from '@hcengineering/ui' + import { Button, closePopup, closeTooltip, IconToDetails } from '@hcengineering/ui' import workbench from '@hcengineering/workbench' import { openFilePreviewInSidebar } from '../utils' @@ -28,7 +28,7 @@ {#if file} <Button - icon={IconDetailsFilled} + icon={IconToDetails} kind="icon" on:click={() => { if (file === undefined) return diff --git a/plugins/chunter-resources/src/components/ChannelHeader.svelte b/plugins/chunter-resources/src/components/ChannelHeader.svelte index d59c9cc794..a55d3b975b 100644 --- a/plugins/chunter-resources/src/components/ChannelHeader.svelte +++ b/plugins/chunter-resources/src/components/ChannelHeader.svelte @@ -20,7 +20,6 @@ import { ActivityMessagesFilter, WithReferences } from '@hcengineering/activity' import contact from '@hcengineering/contact' import view from '@hcengineering/view' - import Header from './Header.svelte' import chunter from '../plugin' import { getObjectIcon, getChannelName } from '../utils' @@ -43,6 +42,7 @@ let title: string | undefined = undefined let description: string | undefined = undefined + let realWidth: number $: void updateDescription(_id, _class, object) @@ -82,10 +82,18 @@ {withSearch} {canOpenInSidebar} {closeOnEscape} + bind:realWidth on:aside-toggled on:close > {#if object} - <PinnedMessages {_id} {_class} space={object.space} withRefs={(object.references ?? 0) > 0} on:select /> + <PinnedMessages + {_id} + {_class} + space={object.space} + withRefs={(object.references ?? 0) > 0} + iconOnly={realWidth < 380} + on:select + /> {/if} </Header> diff --git a/plugins/chunter-resources/src/components/Header.svelte b/plugins/chunter-resources/src/components/Header.svelte index abc31095c1..595dda9962 100644 --- a/plugins/chunter-resources/src/components/Header.svelte +++ b/plugins/chunter-resources/src/components/Header.svelte @@ -24,7 +24,7 @@ Header, HeaderAdaptive, IconSettings, - IconDetailsFilled + IconToDetails } from '@hcengineering/ui' import { createEventDispatcher } from 'svelte' import view from '@hcengineering/view' @@ -58,6 +58,7 @@ export let hideActions: boolean = false export let canOpenInSidebar: boolean = false export let closeOnEscape: boolean = true + export let realWidth: number | undefined = undefined const client = getClient() const dispatch = createEventDispatcher() @@ -74,6 +75,7 @@ hideDescription={!description} adaptive={adaptive !== 'default' ? adaptive : withFilters ? 'freezeActions' : 'disabled'} {closeOnEscape} + bind:realWidth on:click on:close > @@ -139,7 +141,7 @@ <slot name="actions" {doubleRow} /> {#if canOpenInSidebar} <Button - icon={IconDetailsFilled} + icon={IconToDetails} iconProps={{ size: 'small' }} kind={'icon'} showTooltip={{ label: workbench.string.OpenInSidebar }} diff --git a/plugins/chunter-resources/src/components/PinnedMessages.svelte b/plugins/chunter-resources/src/components/PinnedMessages.svelte index 26db3d7052..0331946985 100644 --- a/plugins/chunter-resources/src/components/PinnedMessages.svelte +++ b/plugins/chunter-resources/src/components/PinnedMessages.svelte @@ -13,13 +13,12 @@ // limitations under the License. --> <script lang="ts"> - import { eventToHTMLElement, Label, ModernButton, showPopup, Icon } from '@hcengineering/ui' + import { eventToHTMLElement, Label, ModernButton, showPopup, Icon, ButtonIcon } from '@hcengineering/ui' import PinnedMessagesPopup from './PinnedMessagesPopup.svelte' import { createQuery } from '@hcengineering/presentation' - import activity, { ActivityMessage } from '@hcengineering/activity' + import activity from '@hcengineering/activity' import { Class, Doc, Ref, Space } from '@hcengineering/core' import view from '@hcengineering/view' - import { ThreadMessage } from '@hcengineering/chunter' import { createEventDispatcher } from 'svelte' import chunter from '../plugin' @@ -28,7 +27,8 @@ export let space: Ref<Space> export let _class: Ref<Class<Doc>> export let _id: Ref<Doc> - export let withRefs = false + export let withRefs: boolean = false + export let iconOnly: boolean = false const dispatch = createEventDispatcher() const pinnedQuery = createQuery() @@ -85,8 +85,12 @@ </script> {#if count > 0} - <ModernButton size={'small'} on:click={openMessagesPopup}> - <Icon icon={view.icon.Pin} size={'x-small'} /> - <span class="text-sm"><Label label={chunter.string.PinnedCount} params={{ count }} /></span> - </ModernButton> + {#if iconOnly} + <ButtonIcon icon={view.icon.Pin} size={'small'} on:click={openMessagesPopup} /> + {:else} + <ModernButton size={'small'} on:click={openMessagesPopup}> + <Icon icon={view.icon.Pin} size={'x-small'} /> + <span class="text-sm"><Label label={chunter.string.PinnedCount} params={{ count }} /></span> + </ModernButton> + {/if} {/if} diff --git a/plugins/time-resources/src/components/PlanView.svelte b/plugins/time-resources/src/components/PlanView.svelte index c015668bb5..260432bd1f 100644 --- a/plugins/time-resources/src/components/PlanView.svelte +++ b/plugins/time-resources/src/components/PlanView.svelte @@ -88,7 +88,11 @@ <ToDosNavigator bind:mode bind:tag bind:currentDate /> <Separator name={'time'} float={$deviceInfo.navigator.float} index={0} color={'var(--theme-divider-color)'} /> {/if} -<div class="flex-col w-full clear-mins" class:left-divider={!$deviceInfo.navigator.visible} bind:this={mainPanel}> +<div + class="flex-col w-full clear-mins mobile-wrapper" + class:left-divider={!$deviceInfo.navigator.visible} + bind:this={mainPanel} +> <ToDos {mode} {tag} bind:currentDate /> </div> {#if visibleCalendar} diff --git a/plugins/workbench-resources/src/components/Applications.svelte b/plugins/workbench-resources/src/components/Applications.svelte index 9c2c06e807..912af46c1b 100644 --- a/plugins/workbench-resources/src/components/Applications.svelte +++ b/plugins/workbench-resources/src/components/Applications.svelte @@ -15,7 +15,7 @@ <script lang="ts"> import core, { getCurrentAccount, type Ref } from '@hcengineering/core' import { createQuery } from '@hcengineering/presentation' - import { Scroller } from '@hcengineering/ui' + import { Scroller, resizeObserver } from '@hcengineering/ui' import { NavLink } from '@hcengineering/view-resources' import type { Application } from '@hcengineering/workbench' import workbench from '@hcengineering/workbench' @@ -55,6 +55,7 @@ gap={direction === 'horizontal' ? 'gap-1' : 'gapV-1'} horizontal={direction === 'horizontal'} contentDirection={direction} + align={direction === 'horizontal' ? 'center' : 'start'} buttons={'union'} > {#each topApps as app} diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte index 24f5ad5b49..2ba673ab9c 100644 --- a/plugins/workbench-resources/src/components/Workbench.svelte +++ b/plugins/workbench-resources/src/components/Workbench.svelte @@ -1055,7 +1055,11 @@ border-radius: var(--medium-BorderRadius); pointer-events: none; } - .antiPanel-application { + .antiPanel-application.horizontal { + border-radius: 0 0 var(--medium-BorderRadius) var(--medium-BorderRadius); + border-top: none; + } + .antiPanel-application:not(.horizontal) { border-radius: var(--medium-BorderRadius) 0 0 var(--medium-BorderRadius); border-right: none; } diff --git a/plugins/workbench-resources/src/components/sidebar/Sidebar.svelte b/plugins/workbench-resources/src/components/sidebar/Sidebar.svelte index b761d46c4f..f488453817 100644 --- a/plugins/workbench-resources/src/components/sidebar/Sidebar.svelte +++ b/plugins/workbench-resources/src/components/sidebar/Sidebar.svelte @@ -76,9 +76,10 @@ <style lang="scss"> .sidebar-container { + overflow: hidden; flex-direction: row; min-width: 25rem; - border-radius: 0 var(--medium-BorderRadius) var(--medium-BorderRadius) 0; + border-radius: var(--medium-BorderRadius); &.mini:not(.float) { width: 3.5rem !important; diff --git a/plugins/workbench-resources/src/sidebar.ts b/plugins/workbench-resources/src/sidebar.ts index d81acaf50a..bb34b18d54 100644 --- a/plugins/workbench-resources/src/sidebar.ts +++ b/plugins/workbench-resources/src/sidebar.ts @@ -15,7 +15,7 @@ import { WorkbenchEvents, type Widget, type WidgetTab } from '@hcengineering/workbench' import { type Class, type Doc, getCurrentAccount, type Ref } from '@hcengineering/core' import { get, writable } from 'svelte/store' -import { getCurrentLocation } from '@hcengineering/ui' +import { getCurrentLocation, deviceOptionsStore as deviceInfo } from '@hcengineering/ui' import { getResource } from '@hcengineering/platform' import { workspaceStore } from './utils' @@ -252,6 +252,8 @@ export function createWidgetTab (widget: Widget, tab: WidgetTab, newTab = false) widgetsState, variant: SidebarVariant.EXPANDED }) + const devInfo = get(deviceInfo) + if (devInfo.navigator.float && !devInfo.aside.visible) deviceInfo.set({ ...devInfo, aside: { visible: true } }) } export function pinWidgetTab (widget: Widget, tabId: string): void { diff --git a/tests/sanity/tests/documents/documents-content.spec.ts b/tests/sanity/tests/documents/documents-content.spec.ts index b6dfd3f1b8..f0a7bf2211 100644 --- a/tests/sanity/tests/documents/documents-content.spec.ts +++ b/tests/sanity/tests/documents/documents-content.spec.ts @@ -199,8 +199,10 @@ test.describe('Content in the Documents tests', () => { await documentContentPage.addImageToDocument(page) const imageSrc = await documentContentPage.firstImageInDocument().getAttribute('src') - await test.step('User can open image in fullscreen on current page', async () => { + await test.step('User can open image on current page', async () => { await documentContentPage.clickImageFullscreenButton() + await expect(documentContentPage.imageInPopup()).toBeVisible() + await documentContentPage.fullscreenButton().click() await expect(documentContentPage.fullscreenImage()).toBeVisible() await documentContentPage.page.keyboard.press('Escape') await expect(documentContentPage.fullscreenImage()).toBeHidden() diff --git a/tests/sanity/tests/model/documents/document-content-page.ts b/tests/sanity/tests/model/documents/document-content-page.ts index f5ec05bb84..84e679bffe 100644 --- a/tests/sanity/tests/model/documents/document-content-page.ts +++ b/tests/sanity/tests/model/documents/document-content-page.ts @@ -23,6 +23,8 @@ export class DocumentContentPage extends CommonPage { readonly tooltipImageTools = (): Locator => this.page.locator('.tippy-box') readonly fullscreenImage = (): Locator => this.page.locator('.popup.fullsize img') + readonly fullscreenButton = (): Locator => this.page.locator('.popup #btnDialogFullScreen') + readonly imageInPopup = (): Locator => this.page.locator('.popup img') readonly proseTableColumnHandle = (col: number): Locator => this.page.locator('table.proseTable').locator('tr').first().locator('td').nth(col).locator('div.table-col-handle')