fix(kiwi): resolve badge avatar overrides

This commit is contained in:
Danila Poyarkov 2026-04-25 21:45:38 +03:00
parent 4fbca976fd
commit 6c6671bb7a
4 changed files with 87 additions and 28 deletions

View file

@ -95,12 +95,14 @@ function buildOverrideContext(
}
const propDefaults = new Map<string, ComponentPropValue>()
const propNames = new Map<string, string>()
for (const [, nc] of changeMap) {
if (!nc.componentPropDefs?.length) continue
for (const def of nc.componentPropDefs) {
if (def.id && def.initialValue) {
propDefaults.set(guidToString(def.id), def.initialValue)
}
if (!def.id) continue
const id = guidToString(def.id)
if (def.initialValue) propDefaults.set(id, def.initialValue)
if (def.name) propNames.set(id, def.name)
}
}
@ -120,6 +122,7 @@ function buildOverrideContext(
overrideKeyToGuid,
nodeIdToGuid,
propDefaults,
propNames,
preComputedRoot: new Map(),
componentIdRoot: new Map(),
swappedInstances: new Set(),

View file

@ -8,6 +8,10 @@ import type {
ComponentPropValue
} from './types'
function normalizePropName(value: string): string {
return value.toLowerCase().replace(/[^a-z0-9]/g, '')
}
function isEmptyPropValue(v: ComponentPropValue): boolean {
return v.boolValue === undefined && v.textValue === undefined && v.guidValue === undefined
}
@ -69,6 +73,25 @@ function assignmentsToValueMap(
* Recursively apply prop assignments to children of a parent node.
* Handles VISIBLE toggles and OVERRIDDEN_SYMBOL_ID (instance swap).
*/
function fallbackRefsForChild(
ctx: OverrideContext,
childName: string,
valueByDef: Map<string, ComponentPropValue>
): ComponentPropRef[] | undefined {
const normalizedChildName = normalizePropName(childName)
const refs: ComponentPropRef[] = []
for (const defId of valueByDef.keys()) {
const propName = ctx.propNames.get(defId)
if (propName && normalizePropName(propName) === normalizedChildName) {
refs.push({
defID: { sessionID: Number(defId.split(':')[0]), localID: Number(defId.split(':')[1]) },
componentPropNodeField: 'VISIBLE'
})
}
}
return refs.length > 0 ? refs : undefined
}
function applyPropAssignments(
ctx: OverrideContext,
parentId: string,
@ -86,7 +109,9 @@ function applyPropAssignments(
continue
}
const refs = findPropRefs(ctx, child.componentId, propRefsMap)
const refs =
findPropRefs(ctx, child.componentId, propRefsMap) ??
fallbackRefsForChild(ctx, child.name, valueByDef)
if (refs) {
for (const ref of refs) {
if (!ref.defID) continue

View file

@ -2,7 +2,7 @@ import { guidToString } from '../convert'
import type { SceneNode } from '../../scene-graph'
import type { GUID } from '../codec'
import type { OverrideContext } from './types'
import type { InstanceNodeChange, OverrideContext } from './types'
const MAX_CHAIN_DEPTH = 20
const siblingIndexCache = new WeakMap<OverrideContext, Map<string, number | null>>()
@ -141,6 +141,29 @@ function sourceSiblingIndex(ctx: OverrideContext, figmaGuid: string): number | n
return result
}
function findNodeByNameAndType(
ctx: OverrideContext,
parentId: string,
name: string | undefined,
type: string | undefined
): string | null {
if (!name || !type) return null
let match: string | null = null
let count = 0
const visit = (id: string) => {
if (count > 1) return
const node = ctx.graph.getNode(id)
if (!node) return
if (node.name === name && node.type === type) {
count++
match = id
}
for (const childId of node.childIds) visit(childId)
}
visit(parentId)
return count === 1 ? match : null
}
function findNodeBySourceSiblingIndex(
ctx: OverrideContext,
parentId: string,
@ -195,10 +218,7 @@ export function findNodeByComponentId(
if (cache.has(cacheKey)) return cache.get(cacheKey) ?? null
const parent = ctx.graph.getNode(parentId)
if (!parent) {
cache.set(cacheKey, null)
return null
}
if (!parent) return null
for (const childId of parent.childIds) {
const child = ctx.graph.getNode(childId)
@ -238,7 +258,6 @@ export function findNodeByComponentId(
return deep
}
}
cache.set(cacheKey, null)
return null
}
@ -248,6 +267,25 @@ export function findNodeByComponentId(
* Each GUID in the path identifies an overrideKey → figmaGuid → graph node.
* The chain walks from the instance down to the target.
*/
function resolveOverrideStep(
ctx: OverrideContext,
currentId: string,
figmaGuid: string,
remapped: string | undefined,
targetNc: InstanceNodeChange | undefined
): string | null {
if (!remapped) return findNodeByNameAndType(ctx, currentId, targetNc?.name, targetNc?.type)
const current = ctx.graph.getNode(currentId)
if (current?.componentId === remapped) return currentId
return (
findNodeByComponentId(ctx, currentId, remapped) ??
findNodeBySourceSiblingIndex(ctx, currentId, remapped, figmaGuid) ??
findNodeByNameAndType(ctx, currentId, targetNc?.name, targetNc?.type)
)
}
export function resolveOverrideTarget(
ctx: OverrideContext,
instanceId: string,
@ -257,27 +295,18 @@ export function resolveOverrideTarget(
for (let index = 0; index < guids.length; index++) {
const key = guidToString(guids[index])
const figmaGuid = ctx.overrideKeyToGuid.get(key) ?? key
const remapped = ctx.guidToNodeId.get(figmaGuid)
if (!remapped) return null
const current = ctx.graph.getNode(currentId)
if (current?.componentId === remapped) continue
const found = findNodeByComponentId(ctx, currentId, remapped)
if (found) {
currentId = found
const targetNc = ctx.changeMap.get(figmaGuid)
const symbolGuid = targetNc?.symbolData?.symbolID
? guidToString(targetNc.symbolData.symbolID)
: null
const remapped =
ctx.guidToNodeId.get(figmaGuid) ?? (symbolGuid ? ctx.guidToNodeId.get(symbolGuid) : undefined)
const resolved = resolveOverrideStep(ctx, currentId, figmaGuid, remapped, targetNc)
if (resolved) {
currentId = resolved
continue
}
const indexed = findNodeBySourceSiblingIndex(ctx, currentId, remapped, figmaGuid)
if (indexed) {
currentId = indexed
continue
}
// Some .fig DSD paths include an intermediate source instance while the
// imported tree has a single wrapper clone in that slot. Descend into the
// wrapper and retry the same guid before giving up.
const parent = ctx.graph.getNode(currentId)
if (parent?.childIds.length === 1) {
currentId = parent.childIds[0]

View file

@ -47,6 +47,7 @@ export interface ComponentPropDef {
export interface InstanceNodeChange {
type?: string
name?: string
guid?: GUID
parentIndex?: { guid?: GUID }
transform?: Matrix
@ -76,6 +77,7 @@ export interface OverrideContext {
overrideKeyToGuid: Map<string, string>
nodeIdToGuid: Map<string, string>
propDefaults: Map<string, ComponentPropValue>
propNames: Map<string, string>
preComputedRoot: Map<string, string>
componentIdRoot: Map<string, string>
swappedInstances: Set<string>