fix(properties): simplify Effect styles and equivalent corners
This commit is contained in:
parent
510b953777
commit
0dd1014c2a
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
### Changed
|
||||
|
||||
- Keep applied and available Effect styles concise, and collapse equal independent corner fields when all four use the same variable.
|
||||
- Keep pixel-grid rounding invisible while showing alignment guides only for real geometry, objects, and canvas/layout guides.
|
||||
- Copy selections as Figma-compatible clipboard data while preserving typed geometry, text sizing, images, components, variables, modes, and shared styles for lossless in-app paste.
|
||||
- Choose whether interface animations follow the system motion preference or stay off, with live updates and a persistent override.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,16 @@ type AppearanceActionOptions = AppearanceStateOptions & {
|
|||
editor: Editor
|
||||
}
|
||||
|
||||
function cornersHaveEquivalentBindings(node: SceneNode): boolean {
|
||||
const first = node.boundVariables.topLeftRadius
|
||||
if (!first) return false
|
||||
return [
|
||||
node.boundVariables.topRightRadius,
|
||||
node.boundVariables.bottomRightRadius,
|
||||
node.boundVariables.bottomLeftRadius
|
||||
].every((id) => id === first)
|
||||
}
|
||||
|
||||
function hasUnequalCorners(node: SceneNode) {
|
||||
return !(
|
||||
node.topLeftRadius === node.topRightRadius &&
|
||||
|
|
@ -48,7 +58,10 @@ export function createAppearanceState({ node, nodes, isMulti, merged }: Appearan
|
|||
const showIndependentCorners = computed(() => {
|
||||
if (isMulti.value) return false
|
||||
const selected = node.value
|
||||
return selected ? selected.independentCorners || hasUnequalCorners(selected) : false
|
||||
return selected
|
||||
? hasUnequalCorners(selected) ||
|
||||
(selected.independentCorners && !cornersHaveEquivalentBindings(selected))
|
||||
: false
|
||||
})
|
||||
|
||||
const cornerRadiusValue = computed(() => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@/components/properties/blend-mode/use'
|
||||
import PropertyItemRow from '@/components/properties/item-list/PropertyItemRow.vue'
|
||||
import PropertyListRoot from '@/components/properties/PropertyListRoot.vue'
|
||||
import SharedStyleField from '@/components/properties/shared-style/SharedStyleField.vue'
|
||||
import { useSharedStylePicker } from '@/components/properties/shared-style/useSharedStylePicker'
|
||||
import IconButton from '@/components/ui/button/IconButton.vue'
|
||||
import Tip from '@/components/ui/overlay/Tip.vue'
|
||||
import FillSwatch from '@/components/ui/paint/FillSwatch.vue'
|
||||
|
|
@ -21,6 +21,13 @@ import AppSelect from '@/components/ui/select/AppSelect.vue'
|
|||
const effectsCtx = useEffectsControls()
|
||||
const { panels } = useI18n()
|
||||
const blendModeOptions = useBlendModeOptions()
|
||||
const {
|
||||
visible: stylesVisible,
|
||||
hasStyle,
|
||||
value: styleValue,
|
||||
options: styleOptions,
|
||||
update: updateStyle
|
||||
} = useSharedStylePicker('effect')
|
||||
|
||||
function effectPreview(effect: Effect): Fill {
|
||||
return {
|
||||
|
|
@ -40,6 +47,17 @@ function effectPreview(effect: Effect): Fill {
|
|||
>
|
||||
<PanelSection :label="panels.effects" :empty="!isMixed && items.length === 0">
|
||||
<template #actions>
|
||||
<AppSelect
|
||||
v-if="stylesVisible && !hasStyle"
|
||||
:model-value="styleValue"
|
||||
:options="styleOptions"
|
||||
@update:model-value="updateStyle"
|
||||
>
|
||||
<template #trigger
|
||||
><IconButton :label="panels.effectStyle" data-property="effect-style"
|
||||
><icon-lucide-layout-grid class="size-3.5" /></IconButton
|
||||
></template>
|
||||
</AppSelect>
|
||||
<IconButton
|
||||
:label="panels.addEffect"
|
||||
@click="actions.add(effectsCtx.createDefaultEffect())"
|
||||
|
|
@ -48,7 +66,15 @@ function effectPreview(effect: Effect): Fill {
|
|||
</IconButton>
|
||||
</template>
|
||||
|
||||
<SharedStyleField kind="effect" :label="panels.effectStyle" />
|
||||
<AppSelect
|
||||
v-if="stylesVisible && hasStyle"
|
||||
:model-value="styleValue"
|
||||
:options="styleOptions"
|
||||
:label="panels.effectStyle"
|
||||
data-property="effect-style"
|
||||
class="mb-1.5"
|
||||
@update:model-value="updateStyle"
|
||||
/>
|
||||
|
||||
<p v-if="isMixed" class="text-[11px] text-muted">{{ panels.mixedEffectsHelp }}</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,8 @@ async function targetStyles(id = targetId) {
|
|||
test('unbound paint styles use one header action without redundant field labels', async () => {
|
||||
for (const [section, label] of [
|
||||
['Fill', 'Fill style'],
|
||||
['Stroke', 'Stroke style']
|
||||
['Stroke', 'Stroke style'],
|
||||
['Effects', 'Effect style']
|
||||
]) {
|
||||
const panel = propertySection(page, section)
|
||||
await expect(panel.getByRole('combobox', { name: label })).toHaveCount(1)
|
||||
|
|
@ -129,7 +130,7 @@ test('applies fill, stroke, effect, and grid styles from local definitions', asy
|
|||
await chooseStyle('Effects', 'Effect style', 'Effects/Card')
|
||||
await chooseStyle('Layout guide', 'Grid style', 'Grid/12 columns')
|
||||
await expect(propertySection(page, 'Fill')).toHaveScreenshot('fill-applied-style.png')
|
||||
await expect(propertySection(page, 'Stroke')).toHaveScreenshot('stroke-applied-style.png')
|
||||
await expect(propertySection(page, 'Effects')).toHaveScreenshot('effects-applied-style.png')
|
||||
|
||||
expect(await targetStyles()).toMatchObject({
|
||||
fillStyleId: '1:100',
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -47,6 +47,30 @@ describe('appearance control state', () => {
|
|||
expect(state.showIndependentCorners.value).toBe(true)
|
||||
})
|
||||
|
||||
test('collapses equal independent corners when they share one binding', () => {
|
||||
const node = rectangle()
|
||||
node.independentCorners = true
|
||||
node.boundVariables = {
|
||||
topLeftRadius: 'radius',
|
||||
topRightRadius: 'radius',
|
||||
bottomRightRadius: 'radius',
|
||||
bottomLeftRadius: 'radius'
|
||||
}
|
||||
expect(appearanceState(node).showIndependentCorners.value).toBe(false)
|
||||
})
|
||||
|
||||
test('keeps equal independent corners expanded for distinct bindings', () => {
|
||||
const node = rectangle()
|
||||
node.independentCorners = true
|
||||
node.boundVariables = {
|
||||
topLeftRadius: 'radius-a',
|
||||
topRightRadius: 'radius-b',
|
||||
bottomRightRadius: 'radius-a',
|
||||
bottomLeftRadius: 'radius-a'
|
||||
}
|
||||
expect(appearanceState(node).showIndependentCorners.value).toBe(true)
|
||||
})
|
||||
|
||||
test('expands imported unequal corners when the explicit flag is stale', () => {
|
||||
const node = rectangle()
|
||||
node.independentCorners = false
|
||||
|
|
|
|||
Loading…
Reference in a new issue