fix(app): align property item subcontrols
- Keep Fill and Effects settings inside their shared content rails\n- Use panel spacing tokens instead of ad-hoc offsets\n- Preserve selection when Escape dismisses overlays\n- Label the Font settings trigger for assistive technology
This commit is contained in:
parent
429a7e5ae2
commit
33d6a1efd1
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
### Fixes
|
||||
|
||||
- Align Fill and Effects sub-controls to their shared property-row content rails, and improve font-settings accessibility without clearing selection when dismissing overlays.
|
||||
- Restore the Variables dialog empty state, full-width table layout, unclipped type menu, and expected Escape handling in dialogs and popup menus.
|
||||
- Pin the patched `websocket-driver` release used through Trystero/Firebase collaboration to resolve a critical protocol-length advisory.
|
||||
- Preserve unrelated Figma prototype, library, export, and raw metadata when editing modeled `.fig` fields, while still overriding stale field-specific payloads.
|
||||
|
|
|
|||
|
|
@ -50,12 +50,23 @@ function opacityBindings(): ShortcutDefinition[] {
|
|||
}))
|
||||
}
|
||||
|
||||
const EDITOR_SHORTCUT_OVERLAY_SELECTOR =
|
||||
'[data-picker-content], [role="dialog"], [role="listbox"], [role="menu"]'
|
||||
|
||||
function originatedInOverlay(event: KeyboardEvent) {
|
||||
return event
|
||||
.composedPath()
|
||||
.some((target) => target instanceof Element && target.matches(EDITOR_SHORTCUT_OVERLAY_SELECTOR))
|
||||
}
|
||||
|
||||
function hasOpenDismissableLayer() {
|
||||
return document.querySelector('[data-dismissable-layer]') !== null
|
||||
}
|
||||
|
||||
function shouldIgnoreShortcut(event: KeyboardEvent, options: KeyboardShortcutOptions) {
|
||||
return (
|
||||
(event.target instanceof Element &&
|
||||
event.target.closest(
|
||||
'[data-picker-content], [role="dialog"], [role="listbox"], [role="menu"]'
|
||||
) !== null) ||
|
||||
hasOpenDismissableLayer() ||
|
||||
originatedInOverlay(event) ||
|
||||
isEditing(event) ||
|
||||
options.inputFocused.value ||
|
||||
!!options.store.state.editingTextId ||
|
||||
|
|
@ -177,7 +188,8 @@ export function registerKeyboardShortcuts(options: KeyboardShortcutOptions) {
|
|||
handler(event)
|
||||
}
|
||||
])
|
||||
)
|
||||
),
|
||||
{ capture: true }
|
||||
)
|
||||
|
||||
onScopeDispose(unsubscribe)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,11 @@ onMounted(() => {
|
|||
<template>
|
||||
<PopoverRoot v-model:open="popoverOpen" @update:open="setPopoverOpen">
|
||||
<Tip :label="dialogs.fontSettings" :disabled="popoverOpen">
|
||||
<PopoverTrigger data-test-id="font-settings-trigger" :class="trigger.base">
|
||||
<PopoverTrigger
|
||||
data-test-id="font-settings-trigger"
|
||||
:aria-label="dialogs.fontSettings"
|
||||
:class="trigger.base"
|
||||
>
|
||||
<icon-lucide-settings class="size-3.5" />
|
||||
</PopoverTrigger>
|
||||
</Tip>
|
||||
|
|
|
|||
|
|
@ -64,171 +64,176 @@ function effectPreview(effect: Effect): Fill {
|
|||
:index="index"
|
||||
:visibility-label="panels.toggleVisibility"
|
||||
:remove-label="panels.removeEffect"
|
||||
class="items-start"
|
||||
@remove="effectsCtx.adjustExpandedAfterRemove(index)"
|
||||
>
|
||||
<Tip
|
||||
:label="
|
||||
effectsCtx.expandedIndex.value === index
|
||||
? panels.collapseEffectSettings
|
||||
: panels.expandEffectSettings
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:aria-expanded="effectsCtx.expandedIndex.value === index"
|
||||
:aria-label="
|
||||
effectsCtx.expandedIndex.value === index
|
||||
? panels.collapseEffectSettings
|
||||
: panels.expandEffectSettings
|
||||
"
|
||||
data-property="effect-expand"
|
||||
class="flex size-5 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded border border-border bg-input p-0"
|
||||
@click="effectsCtx.toggleExpand(index)"
|
||||
>
|
||||
<FillSwatch
|
||||
v-if="effectsCtx.isShadow(effect.type)"
|
||||
:fill="effectPreview(effect)"
|
||||
class="size-full border-0"
|
||||
/>
|
||||
<icon-lucide-blend v-else class="size-3 text-muted" />
|
||||
</button>
|
||||
</Tip>
|
||||
|
||||
<AppSelect
|
||||
class="min-w-0 flex-1"
|
||||
:model-value="effect.type"
|
||||
:options="effectsCtx.effectOptions"
|
||||
:label="panels.effects"
|
||||
data-property="effect-type"
|
||||
@update:model-value="
|
||||
effectsCtx.updateType(actions.patch, activeNode, index, $event as Effect['type'])
|
||||
"
|
||||
/>
|
||||
</PropertyItemRow>
|
||||
|
||||
<div
|
||||
v-if="effectsCtx.expandedIndex.value === index"
|
||||
class="ml-[26px] flex flex-col gap-1.5 py-1.5"
|
||||
data-slot="effect-settings"
|
||||
>
|
||||
<PanelFieldGroup :label="panels.blendMode">
|
||||
<AppSelect
|
||||
:model-value="effect.blendMode ?? 'NORMAL'"
|
||||
:options="blendModeOptions"
|
||||
:label="panels.blendMode"
|
||||
data-property="effect-blend-mode"
|
||||
@update:model-value="
|
||||
commitDiscretePropertyListChange(flush, () =>
|
||||
actions.patch(index, { blendMode: $event as Effect['blendMode'] })
|
||||
)
|
||||
"
|
||||
/>
|
||||
</PanelFieldGroup>
|
||||
<template v-if="effectsCtx.isShadow(effect.type)">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Tip :label="panels.xAxis">
|
||||
<NumberField
|
||||
icon="X"
|
||||
:model-value="effect.offset.x"
|
||||
data-property="effect-offset-x"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, x: $event }
|
||||
})
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<div class="flex min-w-0 items-center gap-panel">
|
||||
<Tip
|
||||
:label="
|
||||
effectsCtx.expandedIndex.value === index
|
||||
? panels.collapseEffectSettings
|
||||
: panels.expandEffectSettings
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:aria-expanded="effectsCtx.expandedIndex.value === index"
|
||||
:aria-label="
|
||||
effectsCtx.expandedIndex.value === index
|
||||
? panels.collapseEffectSettings
|
||||
: panels.expandEffectSettings
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, x: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
data-property="effect-expand"
|
||||
class="flex size-5 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded border border-border bg-input p-0"
|
||||
@click="effectsCtx.toggleExpand(index)"
|
||||
>
|
||||
<FillSwatch
|
||||
v-if="effectsCtx.isShadow(effect.type)"
|
||||
:fill="effectPreview(effect)"
|
||||
class="size-full border-0"
|
||||
/>
|
||||
<icon-lucide-blend v-else class="size-3 text-muted" />
|
||||
</button>
|
||||
</Tip>
|
||||
<Tip :label="panels.yAxis">
|
||||
<NumberField
|
||||
icon="Y"
|
||||
:model-value="effect.offset.y"
|
||||
data-property="effect-offset-y"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, y: $event }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, y: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Tip :label="panels.radius">
|
||||
<NumberField
|
||||
icon="B"
|
||||
:model-value="effect.radius"
|
||||
:min="0"
|
||||
data-property="effect-radius"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, { radius: $event })
|
||||
"
|
||||
@commit="effectsCtx.commitEffect(activeNode, index, { radius: $event })"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.spread">
|
||||
<NumberField
|
||||
icon="S"
|
||||
:model-value="effect.spread"
|
||||
data-property="effect-spread"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, { spread: $event })
|
||||
"
|
||||
@commit="effectsCtx.commitEffect(activeNode, index, { spread: $event })"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<ColorInput
|
||||
<AppSelect
|
||||
class="min-w-0 flex-1"
|
||||
:color="effect.color"
|
||||
editable
|
||||
@update="effectsCtx.updateColor(actions.patch, index, $event)"
|
||||
:model-value="effect.type"
|
||||
:options="effectsCtx.effectOptions"
|
||||
:label="panels.effects"
|
||||
data-property="effect-type"
|
||||
@update:model-value="
|
||||
effectsCtx.updateType(actions.patch, activeNode, index, $event as Effect['type'])
|
||||
"
|
||||
/>
|
||||
<Tip :label="panels.opacity">
|
||||
<NumberField
|
||||
class="w-14"
|
||||
suffix="%"
|
||||
:model-value="Math.round(effect.color.a * 100)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
data-property="effect-opacity"
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="effectsCtx.expandedIndex.value === index"
|
||||
class="flex flex-col gap-panel py-panel"
|
||||
data-slot="effect-settings"
|
||||
>
|
||||
<PanelFieldGroup :label="panels.blendMode">
|
||||
<AppSelect
|
||||
:model-value="effect.blendMode ?? 'NORMAL'"
|
||||
:options="blendModeOptions"
|
||||
:label="panels.blendMode"
|
||||
data-property="effect-blend-mode"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, index, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
commitDiscretePropertyListChange(flush, () =>
|
||||
actions.patch(index, { blendMode: $event as Effect['blendMode'] })
|
||||
)
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
</template>
|
||||
</PanelFieldGroup>
|
||||
<template v-if="effectsCtx.isShadow(effect.type)">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Tip :label="panels.xAxis">
|
||||
<NumberField
|
||||
icon="X"
|
||||
:model-value="effect.offset.x"
|
||||
data-property="effect-offset-x"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, x: $event }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, x: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.yAxis">
|
||||
<NumberField
|
||||
icon="Y"
|
||||
:model-value="effect.offset.y"
|
||||
data-property="effect-offset-y"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, y: $event }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, index, {
|
||||
offset: { ...effect.offset, y: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<NumberField
|
||||
v-else
|
||||
class="w-24 flex-none"
|
||||
icon="B"
|
||||
:model-value="effect.radius"
|
||||
:min="0"
|
||||
data-property="effect-radius"
|
||||
@update:model-value="effectsCtx.scrubEffect(activeNode, index, { radius: $event })"
|
||||
@commit="effectsCtx.commitEffect(activeNode, index, { radius: $event })"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Tip :label="panels.radius">
|
||||
<NumberField
|
||||
icon="B"
|
||||
:model-value="effect.radius"
|
||||
:min="0"
|
||||
data-property="effect-radius"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, { radius: $event })
|
||||
"
|
||||
@commit="effectsCtx.commitEffect(activeNode, index, { radius: $event })"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.spread">
|
||||
<NumberField
|
||||
icon="S"
|
||||
:model-value="effect.spread"
|
||||
data-property="effect-spread"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, { spread: $event })
|
||||
"
|
||||
@commit="effectsCtx.commitEffect(activeNode, index, { spread: $event })"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<ColorInput
|
||||
class="min-w-0 flex-1"
|
||||
:color="effect.color"
|
||||
editable
|
||||
@update="effectsCtx.updateColor(actions.patch, index, $event)"
|
||||
/>
|
||||
<Tip :label="panels.opacity">
|
||||
<NumberField
|
||||
class="w-14"
|
||||
suffix="%"
|
||||
:model-value="Math.round(effect.color.a * 100)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
data-property="effect-opacity"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, index, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, index, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<NumberField
|
||||
v-else
|
||||
class="w-24 flex-none"
|
||||
icon="B"
|
||||
:model-value="effect.radius"
|
||||
:min="0"
|
||||
data-property="effect-radius"
|
||||
@update:model-value="effectsCtx.scrubEffect(activeNode, index, { radius: $event })"
|
||||
@commit="effectsCtx.commitEffect(activeNode, index, { radius: $event })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PropertyItemRow>
|
||||
</div>
|
||||
</PanelSection>
|
||||
</PropertyListRoot>
|
||||
|
|
|
|||
|
|
@ -87,84 +87,88 @@ function updateSolidColor(
|
|||
|
||||
<div v-for="(fill, index) in items" :key="`${index}:${fill.visible ? 'visible' : 'hidden'}`">
|
||||
<PropertyItemRow
|
||||
class="items-start"
|
||||
prop-key="fills"
|
||||
:index="index"
|
||||
:visibility-label="panels.toggleVisibility"
|
||||
:remove-label="panels.removeFill"
|
||||
>
|
||||
<BindableValueRoot
|
||||
v-slot="binding"
|
||||
:provider="colorProvider"
|
||||
:targets="paintBindingTargets(selectedNodeIds, 'fills', index)"
|
||||
:value="fill.color"
|
||||
batch-label="Change fill color"
|
||||
>
|
||||
<PaintField
|
||||
:opacity="fill.opacity"
|
||||
:opacity-label="panels.opacity"
|
||||
@update:opacity="actions.patch(index, { opacity: $event })"
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-panel">
|
||||
<BindableValueRoot
|
||||
v-slot="binding"
|
||||
:provider="colorProvider"
|
||||
:targets="paintBindingTargets(selectedNodeIds, 'fills', index)"
|
||||
:value="fill.color"
|
||||
batch-label="Change fill color"
|
||||
>
|
||||
<template #preview>
|
||||
<FillPicker
|
||||
:fill="displayFill(fill, binding.resolvedValue)"
|
||||
:okhcl="createFillOkhclAdapter(okhcl, activeNode, index)"
|
||||
@update="
|
||||
updatePickerFill(binding.actions, flush, $event, (next) =>
|
||||
actions.update(index, next)
|
||||
)
|
||||
"
|
||||
@open-change="!$event && commitPaintMutation(binding.actions)"
|
||||
@cancel="cancelPaintMutation(binding.actions)"
|
||||
/>
|
||||
</template>
|
||||
<PaintField
|
||||
class="w-full flex-none"
|
||||
:opacity="fill.opacity"
|
||||
:opacity-label="panels.opacity"
|
||||
@update:opacity="actions.patch(index, { opacity: $event })"
|
||||
>
|
||||
<template #preview>
|
||||
<FillPicker
|
||||
:fill="displayFill(fill, binding.resolvedValue)"
|
||||
:okhcl="createFillOkhclAdapter(okhcl, activeNode, index)"
|
||||
@update="
|
||||
updatePickerFill(binding.actions, flush, $event, (next) =>
|
||||
actions.update(index, next)
|
||||
)
|
||||
"
|
||||
@open-change="!$event && commitPaintMutation(binding.actions)"
|
||||
@cancel="cancelPaintMutation(binding.actions)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #value>
|
||||
<PaintValue
|
||||
v-if="fill.type === 'SOLID'"
|
||||
:color="fill.color"
|
||||
:resolved-color="binding.resolvedValue"
|
||||
:variable-name="binding.variable?.name"
|
||||
:label="panels.fill"
|
||||
@update="
|
||||
updateSolidColor(binding.actions, flush, fill, $event, (next) =>
|
||||
actions.update(index, next)
|
||||
)
|
||||
"
|
||||
/>
|
||||
<span v-else class="min-w-0 flex-1 truncate font-mono text-xs text-surface">
|
||||
{{ fillLabel(fill) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #value>
|
||||
<PaintValue
|
||||
v-if="fill.type === 'SOLID'"
|
||||
:color="fill.color"
|
||||
:resolved-color="binding.resolvedValue"
|
||||
:variable-name="binding.variable?.name"
|
||||
:label="panels.fill"
|
||||
@update="
|
||||
updateSolidColor(binding.actions, flush, fill, $event, (next) =>
|
||||
actions.update(index, next)
|
||||
)
|
||||
"
|
||||
/>
|
||||
<span v-else class="min-w-0 flex-1 truncate font-mono text-xs text-surface">
|
||||
{{ fillLabel(fill) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="fill.type === 'SOLID'" #binding>
|
||||
<VariableBindingPicker
|
||||
:trigger-label="panels.applyVariable"
|
||||
:search-placeholder="dialogs.search"
|
||||
:empty-label="panels.noVariablesFound"
|
||||
:detach-label="panels.detachVariable"
|
||||
:create-label="
|
||||
panels.createColorVariable({ value: `#${colorToHexRaw(fill.color)}` })
|
||||
"
|
||||
:create-name-placeholder="panels.variableName"
|
||||
:create-submit-label="panels.create"
|
||||
/>
|
||||
</template>
|
||||
</PaintField>
|
||||
</BindableValueRoot>
|
||||
<template v-if="fill.type === 'SOLID'" #binding>
|
||||
<VariableBindingPicker
|
||||
:trigger-label="panels.applyVariable"
|
||||
:search-placeholder="dialogs.search"
|
||||
:empty-label="panels.noVariablesFound"
|
||||
:detach-label="panels.detachVariable"
|
||||
:create-label="
|
||||
panels.createColorVariable({ value: `#${colorToHexRaw(fill.color)}` })
|
||||
"
|
||||
:create-name-placeholder="panels.variableName"
|
||||
:create-submit-label="panels.create"
|
||||
/>
|
||||
</template>
|
||||
</PaintField>
|
||||
</BindableValueRoot>
|
||||
<PanelFieldGroup :label="panels.blendMode">
|
||||
<AppSelect
|
||||
:model-value="fill.blendMode ?? 'NORMAL'"
|
||||
:options="blendModeOptions"
|
||||
:label="panels.blendMode"
|
||||
data-property="fill-blend-mode"
|
||||
@update:model-value="
|
||||
commitDiscretePropertyListChange(flush, () =>
|
||||
actions.patch(index, { blendMode: $event as Fill['blendMode'] })
|
||||
)
|
||||
"
|
||||
/>
|
||||
</PanelFieldGroup>
|
||||
</div>
|
||||
</PropertyItemRow>
|
||||
<PanelFieldGroup :label="panels.blendMode" class="ml-[26px] mt-1.5">
|
||||
<AppSelect
|
||||
:model-value="fill.blendMode ?? 'NORMAL'"
|
||||
:options="blendModeOptions"
|
||||
:label="panels.blendMode"
|
||||
data-property="fill-blend-mode"
|
||||
@update:model-value="
|
||||
commitDiscretePropertyListChange(flush, () =>
|
||||
actions.patch(index, { blendMode: $event as Fill['blendMode'] })
|
||||
)
|
||||
"
|
||||
/>
|
||||
</PanelFieldGroup>
|
||||
</div>
|
||||
</PanelSection>
|
||||
</PropertyListRoot>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,19 @@ test('effect settings expand semantically and row remove reveals on hover', asyn
|
|||
await expect(expand).toHaveAttribute('aria-expanded', 'false')
|
||||
await expand.click()
|
||||
await expect(expand).toHaveAttribute('aria-expanded', 'true')
|
||||
await expect(editor.page.locator('[data-slot="effect-settings"]')).toBeVisible()
|
||||
const effectSettings = editor.page.locator('[data-slot="effect-settings"]')
|
||||
await expect(effectSettings).toBeVisible()
|
||||
const effectTypeBox = expectDefined(
|
||||
await effectItem.locator('[data-property="effect-type"]').boundingBox(),
|
||||
'effect type bounds'
|
||||
)
|
||||
const effectBlendBox = expectDefined(
|
||||
await effectSettings.locator('[data-property="effect-blend-mode"]').boundingBox(),
|
||||
'effect blend mode bounds'
|
||||
)
|
||||
expect(
|
||||
Math.abs(effectBlendBox.x + effectBlendBox.width - (effectTypeBox.x + effectTypeBox.width))
|
||||
).toBeLessThan(1)
|
||||
|
||||
const remove = effectItem.getByRole('button', { name: 'Remove effect' })
|
||||
await expect(remove).toHaveCSS('opacity', '1')
|
||||
|
|
@ -177,6 +189,20 @@ test('paint effect and export rows share compact visual anatomy', async () => {
|
|||
}
|
||||
await editor.page.mouse.move(0, 0)
|
||||
|
||||
const fillItem = propertyItems(editor.page, 'fills').first()
|
||||
const paintFieldBox = expectDefined(
|
||||
await fillItem.locator('[data-slot="paint-field"]').boundingBox(),
|
||||
'fill paint field bounds'
|
||||
)
|
||||
const fillBlendBox = expectDefined(
|
||||
await editor.page.locator('[data-property="fill-blend-mode"]').boundingBox(),
|
||||
'fill blend mode bounds'
|
||||
)
|
||||
expect(Math.abs(fillBlendBox.x - paintFieldBox.x)).toBeLessThan(1)
|
||||
expect(
|
||||
Math.abs(fillBlendBox.x + fillBlendBox.width - (paintFieldBox.x + paintFieldBox.width))
|
||||
).toBeLessThan(1)
|
||||
|
||||
await expect(designPanel()).toHaveScreenshot('design-panel-paint-effects-export.png')
|
||||
|
||||
for (const sectionName of ['Position', 'Layout', 'Appearance']) {
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
|
@ -25,7 +25,8 @@ test('font settings popover exposes web font access without desktop-only cache a
|
|||
await expect(
|
||||
typography.getByRole('img', { name: /Missing font: Missing Test Sans/ })
|
||||
).toBeVisible()
|
||||
const fontSettings = page.getByTestId('font-settings-trigger')
|
||||
const fontSettings = page.getByRole('button', { name: 'Font settings' })
|
||||
await expect(fontSettings).toHaveAttribute('data-test-id', 'font-settings-trigger')
|
||||
await fontSettings.hover()
|
||||
await expect(page.locator('[role=tooltip]').filter({ hasText: 'Font settings' })).toBeVisible()
|
||||
await fontSettings.click()
|
||||
|
|
@ -47,4 +48,8 @@ test('font settings popover exposes web font access without desktop-only cache a
|
|||
await expect(page.getByTestId('font-settings-refresh-cache')).toHaveCount(0)
|
||||
await expect(page.getByTestId('font-settings-clear-cache')).toHaveCount(0)
|
||||
await expect(page.getByText('Download CJK and Arabic fallbacks')).toHaveCount(0)
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(page.getByTestId('font-settings-panel')).toBeHidden()
|
||||
await expect(typography).toBeVisible()
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue