fix(kiwi): resolve badge avatar overrides
This commit is contained in:
parent
4fbca976fd
commit
6c6671bb7a
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue