mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-29 11:32:15 -05:00
feat: rework selector playground ui (#20076)
This commit is contained in:
@@ -2,6 +2,7 @@ import 'regenerator-runtime/runtime'
|
||||
import 'cypress-real-events/support'
|
||||
import '@percy/cypress'
|
||||
import './storybook'
|
||||
import 'normalize.css/normalize.css'
|
||||
|
||||
// Need to register these once per app. Depending which components are consumed
|
||||
// from @cypress/design-system, different icons are required.
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
@use 'derived/export.scss';
|
||||
@use 'typography';
|
||||
|
||||
@use 'normalize.css/normalize.css';
|
||||
|
||||
// probably should leave this for the consumer to set?
|
||||
body, html {
|
||||
font-size: typography.text(m);
|
||||
|
||||
@@ -29,6 +29,29 @@ describe('Cypress In Cypress', { viewportWidth: 1200 }, () => {
|
||||
.should('be.visible')
|
||||
|
||||
cy.percySnapshot('viewport info open')
|
||||
|
||||
cy.get('body').click()
|
||||
|
||||
cy.findByTestId('playground-activator').click()
|
||||
cy.findByTestId('playground-selector').clear().type('#__cy_root')
|
||||
|
||||
cy.percySnapshot('cy.get selector')
|
||||
|
||||
cy.findByTestId('playground-num-elements').contains('1 Match')
|
||||
|
||||
cy.window().then((win) => cy.spy(win.console, 'log'))
|
||||
cy.findByTestId('playground-print').click().window().then((win) => {
|
||||
expect(win.console.log).to.have.been.calledWith('%cCommand: ', 'font-weight: bold', 'cy.get(\'#__cy_root\')')
|
||||
})
|
||||
|
||||
cy.findByLabelText('Selector Methods').click()
|
||||
cy.findByRole('menuitem', { name: 'cy.contains' }).click()
|
||||
|
||||
cy.findByTestId('playground-selector').clear().type('Component Test')
|
||||
|
||||
cy.percySnapshot('cy.contains selector')
|
||||
|
||||
cy.findByTestId('playground-num-elements').contains('1 Match')
|
||||
})
|
||||
|
||||
it('navigation between specs and other parts of the app works', () => {
|
||||
|
||||
@@ -29,6 +29,29 @@ describe('Cypress In Cypress', { viewportWidth: 1200 }, () => {
|
||||
.should('be.visible')
|
||||
|
||||
cy.percySnapshot('viewport info open')
|
||||
|
||||
cy.get('body').click()
|
||||
|
||||
cy.findByTestId('playground-activator').click()
|
||||
cy.findByTestId('playground-selector').clear().type('li')
|
||||
|
||||
cy.percySnapshot('cy.get selector')
|
||||
|
||||
cy.findByTestId('playground-num-elements').contains('3 Matches')
|
||||
|
||||
cy.findByLabelText('Selector Methods').click()
|
||||
cy.findByRole('menuitem', { name: 'cy.contains' }).click()
|
||||
|
||||
cy.findByTestId('playground-selector').clear().type('Item 1')
|
||||
|
||||
cy.percySnapshot('cy.contains selector')
|
||||
|
||||
cy.findByTestId('playground-num-elements').contains('1 Match')
|
||||
|
||||
cy.window().then((win) => cy.spy(win.console, 'log'))
|
||||
cy.findByTestId('playground-print').click().window().then((win) => {
|
||||
expect(win.console.log).to.have.been.calledWith('%cCommand: ', 'font-weight: bold', 'cy.contains(\'Item 1\')')
|
||||
})
|
||||
})
|
||||
|
||||
it('navigation between specs and other parts of the app works', () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
id="spec-runner-header"
|
||||
class="min-h-64px px-16px text-14px"
|
||||
class="min-h-64px text-14px"
|
||||
:style="{ width: `${props.width}px` }"
|
||||
>
|
||||
<div class="flex flex-grow flex-wrap py-16px gap-12px justify-end">
|
||||
<div class="flex flex-wrap justify-end flex-grow p-16px gap-12px">
|
||||
<!--
|
||||
TODO: Studio. Out of scope for GA.
|
||||
<Button
|
||||
@@ -20,7 +20,7 @@
|
||||
<div
|
||||
v-if="props.gql.currentTestingType === 'e2e'"
|
||||
data-cy="aut-url"
|
||||
class="border rounded flex flex-grow border-1px border-gray-100 h-32px align-middle overflow-hidden"
|
||||
class="flex flex-grow overflow-hidden align-middle border border-gray-100 rounded border-1px h-32px"
|
||||
:class="{
|
||||
'bg-gray-50': autStore.isLoadingUrl
|
||||
}"
|
||||
@@ -28,7 +28,7 @@
|
||||
<Button
|
||||
data-cy="playground-activator"
|
||||
:disabled="isDisabled"
|
||||
class="rounded-none border-r-1px border-gray-100 mr-12px"
|
||||
class="border-gray-100 rounded-none border-r-1px mr-12px"
|
||||
variant="text"
|
||||
@click="togglePlayground"
|
||||
>
|
||||
@@ -45,7 +45,7 @@
|
||||
<Button
|
||||
data-cy="playground-activator"
|
||||
:disabled="isDisabled"
|
||||
class=" border-gray-100 mr-12px"
|
||||
class="border-gray-100 mr-12px"
|
||||
variant="outline"
|
||||
@click="togglePlayground"
|
||||
>
|
||||
@@ -65,7 +65,7 @@
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="max-h-50vh overflow-auto">
|
||||
<div class="overflow-auto max-h-50vh">
|
||||
<VerticalBrowserListItems
|
||||
:gql="props.gql"
|
||||
:spec-path="activeSpecPath"
|
||||
@@ -82,13 +82,13 @@
|
||||
<span class="whitespace-nowrap">{{ autStore.viewportWidth }}x{{ autStore.viewportHeight }}</span>
|
||||
<span
|
||||
v-if="displayScale"
|
||||
class="-ml-6px text-gray-500"
|
||||
class="text-gray-500 -ml-6px"
|
||||
>
|
||||
({{ displayScale }})
|
||||
</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="max-h-50vw p-16px text-gray-600 leading-24px w-400px overflow-auto">
|
||||
<div class="overflow-auto text-gray-600 max-h-50vw p-16px leading-24px w-400px">
|
||||
<!-- TODO: This copy is a placeholder based on the old message for this, we should confirm the exact copy and then move to i18n -->
|
||||
<p class="mb-16px">
|
||||
The
|
||||
@@ -103,7 +103,7 @@
|
||||
</p>
|
||||
|
||||
<ShikiHighlight
|
||||
class="rounded border-1 border-gray-200 mb-16px"
|
||||
class="border-gray-200 rounded border-1 mb-16px"
|
||||
lang="javascript"
|
||||
:code="code"
|
||||
/>
|
||||
@@ -117,15 +117,11 @@
|
||||
</SpecRunnerDropdown>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<SelectorPlayground
|
||||
v-if="selectorPlaygroundStore.show"
|
||||
class="mt-8px"
|
||||
>
|
||||
<SelectorPlayground
|
||||
:get-aut-iframe="getAutIframe"
|
||||
:event-manager="eventManager"
|
||||
/>
|
||||
</div>
|
||||
:get-aut-iframe="getAutIframe"
|
||||
:event-manager="eventManager"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
v-if="props.gql.currentProject"
|
||||
v-show="runnerUiStore.isSpecsListOpen"
|
||||
id="inline-spec-list"
|
||||
class="h-full bg-gray-1000 border-r-1 border-gray-900 force-dark"
|
||||
class="h-full border-gray-900 bg-gray-1000 border-r-1 force-dark"
|
||||
:class="{'pointer-events-none': isDragging}"
|
||||
>
|
||||
<InlineSpecList
|
||||
|
||||
@@ -11,10 +11,12 @@ describe('SelectorPlayground', () => {
|
||||
return {
|
||||
autIframe,
|
||||
element: cy.mount(() => (
|
||||
<SelectorPlayground
|
||||
eventManager={eventManager}
|
||||
getAutIframe={() => autIframe}
|
||||
/>
|
||||
<div class="py-64px">
|
||||
<SelectorPlayground
|
||||
eventManager={eventManager}
|
||||
getAutIframe={() => autIframe}
|
||||
/>
|
||||
</div>
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -23,7 +25,7 @@ describe('SelectorPlayground', () => {
|
||||
const { autIframe } = mountSelectorPlayground()
|
||||
|
||||
cy.spy(autIframe, 'toggleSelectorHighlight')
|
||||
cy.get('[data-cy="playground-method"]').should('contain', 'cy.get')
|
||||
cy.get('[data-cy="selected-playground-method"]').should('contain', 'cy.get')
|
||||
cy.get('[data-cy="playground-selector"]').should('have.value', 'body')
|
||||
|
||||
cy.percySnapshot()
|
||||
@@ -53,27 +55,42 @@ describe('SelectorPlayground', () => {
|
||||
cy.spy(autIframe, 'toggleSelectorHighlight')
|
||||
expect(selectorPlaygroundStore.method).to.eq('get')
|
||||
|
||||
cy.get('[data-cy="playground-method"]').as('method')
|
||||
cy.get('@method').contains('cy.get').click()
|
||||
cy.get('li').contains('cy.contains').click().then(() => {
|
||||
cy.get('[aria-label="Selector Methods"]').click()
|
||||
cy.findByRole('menuitem', { name: 'cy.contains' }).click().then(() => {
|
||||
expect(selectorPlaygroundStore.method).to.eq('contains')
|
||||
expect(autIframe.toggleSelectorHighlight).to.have.been.called
|
||||
cy.get('@method').contains('cy.contains')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="selected-playground-method"]').should('contain', 'cy.contains')
|
||||
})
|
||||
|
||||
it('shows query and number of found elements', () => {
|
||||
const selectorPlaygroundStore = useSelectorPlaygroundStore()
|
||||
|
||||
selectorPlaygroundStore.setNumElements(10)
|
||||
selectorPlaygroundStore.setNumElements(0)
|
||||
|
||||
mountSelectorPlayground()
|
||||
cy.get('[data-cy="playground-num-elements"]').contains('10')
|
||||
cy.get('[data-cy="playground-num-elements"]').contains('No Matches')
|
||||
|
||||
cy.then(() => selectorPlaygroundStore.setNumElements(1))
|
||||
|
||||
cy.get('[data-cy="playground-num-elements"]').contains('1 Match')
|
||||
|
||||
cy.then(() => selectorPlaygroundStore.setNumElements(10))
|
||||
|
||||
cy.get('[data-cy="playground-num-elements"]').contains('10 Matches')
|
||||
|
||||
cy.percySnapshot()
|
||||
|
||||
cy.then(() => selectorPlaygroundStore.setValidity(false))
|
||||
|
||||
cy.get('[data-cy="playground-num-elements"]').contains('Invalid')
|
||||
|
||||
cy.percySnapshot('Invalid playground selector')
|
||||
})
|
||||
|
||||
it('focuses and copies selector text', () => {
|
||||
// TODO: UNIFY-999 Solve "write permission denied" error to test this in run mode
|
||||
it.skip('focuses and copies selector text', () => {
|
||||
const { autIframe } = mountSelectorPlayground()
|
||||
|
||||
cy.spy(autIframe, 'toggleSelectorHighlight')
|
||||
@@ -83,10 +100,10 @@ describe('SelectorPlayground', () => {
|
||||
cy.get('@copy').click()
|
||||
cy.get('@copy').should('be.focused')
|
||||
|
||||
cy.spy(document, 'execCommand')
|
||||
cy.get('[data-cy="playground-copy"]').click().then(() => {
|
||||
expect(document.execCommand).to.be.calledWith('copy')
|
||||
})
|
||||
cy.spy(navigator.clipboard, 'writeText').as('clipboardSpy')
|
||||
cy.get('[data-cy="playground-copy"]').click()
|
||||
cy.get('[data-cy="playground-copy-tooltip"]').should('be.visible').contains('Copied to clipboard')
|
||||
cy.get('@clipboardSpy').should('have.been.called')
|
||||
})
|
||||
|
||||
it('prints nothing to console when no selected elements found', () => {
|
||||
@@ -101,6 +118,8 @@ describe('SelectorPlayground', () => {
|
||||
Yielded: 'Nothing',
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy="playground-print-tooltip"]').should('be.visible').contains('Printed to console')
|
||||
})
|
||||
|
||||
it('prints elements when selected elements found', () => {
|
||||
|
||||
@@ -1,83 +1,129 @@
|
||||
<template>
|
||||
<div
|
||||
id="selector-playground"
|
||||
class="bg-white flex items-center"
|
||||
class="border-t border-b bg-gray-50 border-gray-200 h-56px grid py-12px px-16px gap-12px grid-cols-[40px,1fr,auto] items-center "
|
||||
>
|
||||
<button
|
||||
:class="{ 'bg-blue-100': selectorPlaygroundStore.isEnabled }"
|
||||
class="rounded-md h-full px-8px"
|
||||
class="flex items-center justify-center h-full text-white transition duration-150 border rounded-md outline-none w-40px hover:default-ring"
|
||||
:class="[ selectorPlaygroundStore.isEnabled ? 'default-ring' : 'border-gray-200']"
|
||||
data-cy="playground-toggle"
|
||||
@click="toggleEnabled"
|
||||
>
|
||||
<Icon
|
||||
:icon="IconCursorDefaultOutline"
|
||||
height="18px"
|
||||
width="18px"
|
||||
/>
|
||||
<i-cy-selector_x16 :class="{ 'icon-dark-indigo-500': selectorPlaygroundStore.isEnabled, 'icon-dark-gray-500': !selectorPlaygroundStore.isEnabled }" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex h-full flex-1 mx-2 items-center"
|
||||
class="relative flex items-center flex-1 w-full h-full"
|
||||
@mouseover="setShowingHighlight"
|
||||
>
|
||||
<Select
|
||||
:options="methods"
|
||||
item-value="display"
|
||||
data-cy="playground-method"
|
||||
:model-value="{
|
||||
value: selectorPlaygroundStore.method,
|
||||
display: `cy.${selectorPlaygroundStore.method}`
|
||||
}"
|
||||
class="w-120px"
|
||||
@update:model-value="({ value }) => selectorPlaygroundStore.setMethod(value)"
|
||||
/>
|
||||
|
||||
<span class="mx-5px">('</span>
|
||||
<input
|
||||
ref="copyText"
|
||||
v-model="selector"
|
||||
class="border rounded-md border-gray-500 flex-1 py-8px px-1 pl-4 text-blue-500"
|
||||
data-cy="playground-selector"
|
||||
>
|
||||
')
|
||||
<Menu #="{ open }">
|
||||
<MenuButton
|
||||
:aria-label="t('runner.selectorPlayground.selectorMethodsLabel')"
|
||||
class="flex items-center justify-center h-full text-white border border-gray-200 outline-none rounded-l-md w-40px hocus-default border-r-transparent"
|
||||
@click.stop
|
||||
>
|
||||
<i-cy-chevron-down-small_x16
|
||||
class="transition duration-300 transition-color"
|
||||
:class="open ? 'icon-dark-indigo-500' : 'icon-dark-gray-500'"
|
||||
/>
|
||||
</MenuButton>
|
||||
<MenuItems
|
||||
class="absolute z-40 flex flex-col overflow-scroll text-white bg-gray-900 rounded outline-transparent top-34px"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="method in methods"
|
||||
:key="method.display"
|
||||
#="{ active }"
|
||||
>
|
||||
<button
|
||||
:class="{ 'bg-gray-700': active }"
|
||||
class="text-left border-b border-b-gray-800 py-8px px-16px"
|
||||
@click="selectorPlaygroundStore.setMethod(method.value)"
|
||||
>
|
||||
{{ method.display }}
|
||||
</button>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
<code class="relative flex-1 h-full">
|
||||
<span
|
||||
ref="ghostLeft"
|
||||
class="absolute inset-y-0 flex items-center text-gray-600 pointer-events-none pl-12px"
|
||||
data-cy="selected-playground-method"
|
||||
>
|
||||
<span class="text-gray-800">cy</span>.<span class="text-purple-500">{{ selectorPlaygroundStore.method }}</span>(‘
|
||||
</span>
|
||||
<span
|
||||
ref="ghostRight"
|
||||
class="font-medium left-[-9999px] absolute inline-block"
|
||||
>{{ selector.replace(/\s/g, ' ') }}</span>
|
||||
<span
|
||||
class="absolute inset-y-0 flex items-center text-gray-600 pointer-events-none"
|
||||
:style="{left: inputRightOffset + 'px'}"
|
||||
>‘)</span>
|
||||
<input
|
||||
ref="copyText"
|
||||
v-model="selector"
|
||||
data-cy="playground-selector"
|
||||
:style="{paddingLeft: inputLeftOffset + 'px', paddingRight: matcherWidth + 32 + 24 + 'px'}"
|
||||
class="w-full h-full font-medium text-indigo-500 border border-gray-200 outline-none rounded-r-md hocus-default overflow-ellipsis"
|
||||
:class="{'hocus-default': selectorPlaygroundStore.isValid, 'hocus-error': !selectorPlaygroundStore.isValid}"
|
||||
>
|
||||
<div
|
||||
ref="match"
|
||||
class="absolute inset-y-0 right-0 flex items-center font-sans text-gray-600 border-l border-l-gray-200 my-6px px-16px"
|
||||
data-cy="playground-num-elements"
|
||||
>
|
||||
<template v-if="!selectorPlaygroundStore.isValid">
|
||||
<span class="text-error-400">{{ t('runner.selectorPlayground.invalidSelector') }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('runner.selectorPlayground.matches', selectorPlaygroundStore.numElements) }}
|
||||
</template>
|
||||
</div>
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-cy="playground-num-elements"
|
||||
class="rounded-md flex h-full bg-gray-400 mx-1 text-white px-3 items-center"
|
||||
>
|
||||
{{ selectorPlaygroundStore.numElements }}
|
||||
<div class="flex gap-12px">
|
||||
<SelectorPlaygroundTooltip v-if="isSupported">
|
||||
<Button
|
||||
size="md"
|
||||
variant="outline"
|
||||
data-cy="playground-copy"
|
||||
class="override-border"
|
||||
@click="copyToClipboard"
|
||||
>
|
||||
<i-cy-copy-clipboard_x16 class="icon-dark-gray-500" />
|
||||
</Button>
|
||||
<template #popper>
|
||||
<div
|
||||
class="whitespace-nowrap"
|
||||
data-cy="playground-copy-tooltip"
|
||||
>
|
||||
{{ t('runner.selectorPlayground.copyTooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
</SelectorPlaygroundTooltip>
|
||||
|
||||
<SelectorPlaygroundTooltip>
|
||||
<Button
|
||||
size="md"
|
||||
variant="outline"
|
||||
data-cy="playground-print"
|
||||
class="override-border"
|
||||
@click="printSelected"
|
||||
>
|
||||
<i-cy-technology-terminal_x16 class="icon-dark-gray-600" />
|
||||
</Button>
|
||||
<template #popper>
|
||||
<div
|
||||
class="whitespace-nowrap"
|
||||
data-cy="playground-print-tooltip"
|
||||
>
|
||||
{{ t('runner.selectorPlayground.printTooltip') }}
|
||||
</div>
|
||||
</template>
|
||||
</SelectorPlaygroundTooltip>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md flex h-full divide-x-1 divide-gray-500 border-1 border-gray-500 mr-10px items-center">
|
||||
<button
|
||||
data-cy="playground-copy"
|
||||
class="h-full px-8px"
|
||||
@click="copySelector"
|
||||
>
|
||||
<Icon :icon="IconCopy" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-cy="playground-print"
|
||||
class="h-full px-8px"
|
||||
@click="printSelected"
|
||||
>
|
||||
<Icon :icon="IconConsoleLine" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="flex text-blue-500 items-center"
|
||||
href="https://on.cypress.io/selector-playground"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span class="mr-5px">
|
||||
<Icon :icon="IconHelpCircle" />
|
||||
</span>
|
||||
Learn more
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -86,13 +132,13 @@ import { computed, ref, watch } from 'vue'
|
||||
import { useSelectorPlaygroundStore } from '../../store/selector-playground-store'
|
||||
import type { AutIframe } from '../aut-iframe'
|
||||
import type { EventManager } from '../event-manager'
|
||||
import IconCopy from '~icons/mdi/content-copy'
|
||||
import Icon from '@packages/frontend-shared/src/components/Icon.vue'
|
||||
import IconCursorDefaultOutline from '~icons/mdi/cursor-default-outline'
|
||||
import IconHelpCircle from '~icons/mdi/help-circle'
|
||||
import SelectorPlaygroundSelectMethod from './SelectorPlaygroundSelectMethod.vue'
|
||||
import Select from '@packages/frontend-shared/src/components/Select.vue'
|
||||
import IconConsoleLine from '~icons/mdi/console-line'
|
||||
import Button from '@packages/frontend-shared/src/components/Button.vue'
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
import { useClipboard, useElementSize } from '@vueuse/core'
|
||||
import SelectorPlaygroundTooltip from './SelectorPlaygroundTooltip.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
eventManager: EventManager
|
||||
@@ -107,9 +153,11 @@ const methods = [
|
||||
display: 'cy.contains',
|
||||
value: 'contains',
|
||||
},
|
||||
]
|
||||
] as const
|
||||
|
||||
const selectorPlaygroundStore = useSelectorPlaygroundStore()
|
||||
const match = ref<HTMLDivElement>()
|
||||
const { width: matcherWidth } = useElementSize(match)
|
||||
|
||||
const copyText = ref<HTMLInputElement>()
|
||||
|
||||
@@ -136,6 +184,23 @@ const selector = computed({
|
||||
},
|
||||
})
|
||||
|
||||
const inputSize = useElementSize(copyText)
|
||||
|
||||
// spooky
|
||||
const ghostLeft = ref<HTMLSpanElement>()
|
||||
const { width: ghostLeftWidth } = useElementSize(ghostLeft)
|
||||
const inputLeftOffset = computed(() => ghostLeftWidth.value + 12)
|
||||
|
||||
const ghostRight = ref<HTMLSpanElement>()
|
||||
const { width: ghostRightWidth } = useElementSize(ghostRight)
|
||||
const inputRightOffset = computed(() => {
|
||||
const leftOffset = inputLeftOffset.value
|
||||
const combined = leftOffset + ghostRightWidth.value
|
||||
const max = inputSize.width.value + leftOffset
|
||||
|
||||
return combined <= max ? combined : max
|
||||
})
|
||||
|
||||
function setShowingHighlight () {
|
||||
selectorPlaygroundStore.setShowingHighlight(true)
|
||||
props.getAutIframe().toggleSelectorHighlight(true)
|
||||
@@ -153,19 +218,9 @@ function printSelected () {
|
||||
props.getAutIframe().printSelectorElementsToConsole()
|
||||
}
|
||||
|
||||
function copySelector () {
|
||||
try {
|
||||
copyText.value?.select()
|
||||
const successful = document.execCommand('copy')
|
||||
|
||||
if (successful) {
|
||||
// Copied!
|
||||
} else {
|
||||
// Oops, unable to copy
|
||||
}
|
||||
} catch (e) {
|
||||
// Oops, unable to copy
|
||||
}
|
||||
const { copy, isSupported } = useClipboard({ copiedDuring: 2000 })
|
||||
const copyToClipboard = () => {
|
||||
copy(selector.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -173,4 +228,13 @@ function copySelector () {
|
||||
#selector-playground {
|
||||
height: 40px;
|
||||
}
|
||||
button.override-border {
|
||||
@apply border-gray-200
|
||||
}
|
||||
button.override-border:hover {
|
||||
@apply border-indigo-300
|
||||
}
|
||||
button.override-border:focus {
|
||||
@apply border-indigo-300
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative"
|
||||
@click="start()"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
ref="tooltip"
|
||||
class="rounded flex bg-gray-900 text-center opacity-0 p-8px transform transition-opacity top-0 left-[50%] text-gray-300 translate-y-[calc(-100%-4px)] translate-x-[-50%] duration-250 absolute pointer-events-none"
|
||||
role="tooltip"
|
||||
:class="[tooltipClass, {'opacity-100': isPending}]"
|
||||
>
|
||||
<slot name="popper" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const tooltip = ref<HTMLDivElement>()
|
||||
|
||||
const tooltipClass = computed(() => {
|
||||
if (!tooltip.value) return ''
|
||||
|
||||
const { right } = tooltip.value.getBoundingClientRect()
|
||||
const outerWidth = window.outerWidth
|
||||
|
||||
return (right > outerWidth) ? 'tooltip-overflow' : ''
|
||||
})
|
||||
|
||||
const { start, isPending } = useTimeoutFn(() => {}, 2000, { immediate: false })
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Created a class to override !important via greater specificity */
|
||||
div.tooltip-overflow {
|
||||
@apply left-auto right-0 translate-x-0
|
||||
}
|
||||
</style>
|
||||
@@ -38,9 +38,13 @@ export const togglePlayground = (autIframe: AutIframe) => {
|
||||
selectorPlaygroundStore.setShow(false)
|
||||
autIframe.toggleSelectorPlayground(false)
|
||||
selectorPlaygroundStore.setEnabled(false)
|
||||
selectorPlaygroundStore.setShowingHighlight(false)
|
||||
autIframe.toggleSelectorHighlight(false)
|
||||
} else {
|
||||
selectorPlaygroundStore.setShow(true)
|
||||
autIframe.toggleSelectorPlayground(true)
|
||||
selectorPlaygroundStore.setEnabled(true)
|
||||
selectorPlaygroundStore.setShowingHighlight(true)
|
||||
autIframe.toggleSelectorHighlight(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 5V4C14 2.89543 13.1046 2 12 2H4C2.89543 2 2 2.89543 2 4V12C2 13.1046 2.89543 14 4 14H5" stroke="#4956E3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-dark"/>
|
||||
<path d="M10 11L13 14L14 13L11 10L12.5 8.5L7 7L8.5 12.5L10 11Z" fill="#4956E3" stroke="#4956E3" stroke-width="2" stroke-linejoin="round" class="icon-dark"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 462 B |
@@ -628,6 +628,13 @@
|
||||
"defaultTitle": "DOM Snapshot",
|
||||
"pinnedTitle": "Pinned",
|
||||
"studioActiveError": "Cannot show Snapshot while creating commands in Studio"
|
||||
},
|
||||
"selectorPlayground": {
|
||||
"matches": "No Matches | {n} Match | {n} Matches",
|
||||
"copyTooltip": "Copied to clipboard",
|
||||
"printTooltip": "Printed to console",
|
||||
"invalidSelector": "Invalid",
|
||||
"selectorMethodsLabel": "Selector Methods"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user