/** * 家书 FamilyBook · 人员详情浮层(鸿蒙端) * * 点名册里的一个人 → 弹出他的完整资料(头像 / 姓名 / 称谓 / 单位职务 / * 手机 / 生日(阳历+农历)/ 邮箱 / 地址 / 座右铭 / 标签 / 备注)。 * * 🔴 为什么做成"浮层"而不是新页面: * 与观影的详情浮层同一个理由(见 `FbMovieDetailSheet` 的文件头): * · 用户在名册里"随手点开看看",看完就关 —— 跳页面会丢失列表的滚动位置, * 回来还得重新找; * · 浮层是**页面内的覆盖层**,不动导航栈,关掉即回到原处。 * ⚠️ 但它**不是**系统弹窗(`bindSheet`/`AlertDialog`)—— * 那些会带系统边框与动画,且塞不进我们要的自定义布局。 * * 数据来源(🔴 v52 重写,用户 2026-10-15): * 用户要求「**完全脱离电脑端**,所有信息都显示出来」⇒ 详情不再"用列表那份凑合": * · 列表(`/api/v1/roster`)只给画一栏要用的字段; * · 详情**单独拉 `GET /api/v1/people/`** —— 它多带 * **工作经历 / 补充联系方式(按类型)/ 私人备注 / 结构化地址五级 / * 身份证(打码) / 头像 URL / 能不能编辑**。 * ⚠️ 旧注释里写"`/people/` 字段并不比 roster 多" —— 那是当时的事实, * v52 起**已经不成立**(服务端这次把详情接口补全了)。 * ⚠️ 断开网络时退回"用列表里那份"(`person` 本来就是列表那份), * 只是缺那几项详情专属字段,界面不会空。 */ import { FbColors, FbMetric, fbTint, FbTint, fbAlpha } from '../common/Theme'; import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules'; import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo'; import { FbAvatar, FbTag } from './Widgets'; import { FbSheetRing } from './FbSheetRing'; import { FB_TOKEN_STORE } from '../data/FbTokenStore'; import { fbImageSrc } from '../data/FbAttachQueue'; /** * 贴底卡片的手势区缓冲(vp)。 * * 🔴 应用开了全屏布局且**隐藏了系统手势条**(见 EntryAbility)—— * 手势区不再有任何视觉提示,贴底的卡片必须自己让出这一段, * 否则最后一排按钮会被"从下往上滑"误触。 */ const FB_SHEET_BOTTOM_GAP: number = 16; /** * 「删除」用的红 —— 与 `resources/base/element/color.json` 的 `red` **同值**。 * * ⚠️ 不能直接用 `FbColors.red`(那是 `Resource`):本文件要拿它拼 * `fbAlpha(RED, 0.08)`(透明度后缀)做底色,`Resource` 拼不了字符串。 * ⚠️ color.json 改色时这里要同步改(只有这一个值,风险可控)。 * 🔴 2026-10-19 起删除按钮统一**红底白字**(`FbColors.red`), * 这个常量只剩"拼浅红底"的历史用途;没有那种用法时它就是个死常量, * 留着只为对照 color.json —— 新代码请直接用 `FbColors.red`。 */ const RED: string = '#C0533F'; @ComponentV2 export struct FbPersonSheet { /** 要看的人(null = 没打开) */ @Param person: FbRosterPerson | null = null; /** 关闭回调 */ @Event onClose: () => void = (): void => { }; /** * 「编辑」回调(用户要求:悬浮卡片上要有一个"编辑")。 * * ⚠️ 这里**只发事件、不自己做编辑** —— 编辑是另一个浮层(`FbPersonEditSheet`), * 由页面决定"关掉详情、打开编辑"。浮层之间不互相引用, * 免得出现"详情嵌编辑、编辑又嵌详情"的循环。 */ @Event onEdit: () => void = (): void => { }; /** * 「删除」回调。 * * 🔴🔴 用户 2026-10-18 原话: * 「点击按钮后弹出这个简单框,不要加删除按钮了,删除按钮太容易误触。 * 应该在**详情页面的最下面**加一个删除按钮。」 * * ⇒ 「删除」从"简要卡"(`FbPersonQuickSheet`)挪到了这里,并且放在 * **整张卡的最下面**、与编辑/关闭之间隔一条分隔线 —— * 这是"危险区"的通用做法:多一层遮挡、手指不会顺手扫到。 * * ⚠️ 与别的事件一样:**只发事件**。二次确认与调接口都在页面侧 * (那里才知道"删完怎么刷新列表")。 */ @Event onDelete: () => void = (): void => { }; /** * 是否显示「删除」按钮。 * * ⚠️ 顶栏那个「我的详情」(`TopBar.myDetailPanel`)要传 `false`: * 删除自己既没意义、又很危险(服务端是物理删除,删完连账号都进不去)。 * 默认 `true` = 名册里的详情卡(那里的删除是正常的)。 */ @Param canDelete: boolean = true; /** * 嵌入模式:宿主**自己已经是浮层**(如 `bindSheet`)时为 true。 * * 🔴🔴 为什么必须有这个开关(2026-10-18 真机踩出来的真 bug): * 本组件的根节点是 `Column().width('100%').height('100%')`(**铺满全屏**、 * 内部再垂直居中那张 92% 宽的卡)—— 因为它的设计宿主是 * "页面里叠一层遮罩 + Stack 居中"(名册页就是这么用的)。 * * 而顶栏点头像想复用这个组件时,我把它塞进了 `bindSheet`: * **sheet 要做"高度自适应内容"(`SheetSize.FIT_CONTENT`), * 内容却要求"铺满 100% 高度"** ⇒ 测量互相打架。 * 真机症状:**每次点击 `AceSheet` 都创建了,却紧接着 * `AceDrag: Drag gesture has been canceled` —— 浮层起来立刻被关掉** * (界面看上去"点了没反应",最容易被误判成"点击没接上")。 * * ⇒ `embedded = true` 时:去掉 `width/height 100%` 与内层居中, * 只留那张卡(宽度交给 sheet、高度自适应),sheet 自己负责 * 底部手势区与遮罩。 * ⚠️ 默认 `false` = 老行为,**名册/日程那几个调用点一个字都不用改**。 */ @Param embedded: boolean = false; /** * 卡片外圈描边环的颜色(hex)。 * * 不传则用**名册模块**的主题色 —— 本组件展示的是"人的资料", * 不管从哪个页面调起(名册 / 顶栏「我的资料」),环色都该是同一个色系。 * * ⚠️ 必须是字符串:`FbSheetRing` 的 `ringColor` 要直接当背景色用, * 而 `FbColors.brand` 是 `Resource`(不能做 `@Param` 默认值参与运算)。 */ @Param accent: string = ''; /** 环色:显式给了就用给的,否则取名册模块色 */ private accentColor(): string { if (this.accent !== '') { return this.accent; } const m: FbModuleDef | undefined = fbModuleOf('people'); return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand; } /** 一行的图标资源 */ private iconOf(name: string): Resource { switch (name) { case 'phone': return $r('app.media.fb_ic_user'); case 'org': return $r('app.media.fb_ic_people'); case 'birth': return $r('app.media.fb_ic_days'); case 'mail': return $r('app.media.fb_ic_notes'); case 'addr': return $r('app.media.fb_ic_resources'); case 'card': return $r('app.media.fb_ic_notes'); default: return $r('app.media.fb_ic_dot'); } } /** * 生日合并成一行:`阳历 11-16(农历 十月初五)`。 * * ⚠️ 阳历/农历**分开两个字段**,但并成一行更好读(两行都只有月日、很短)。 * 只有农历没阳历时也能显示。 */ private birthdayLine(): string { if (this.person === null) { return ''; } const solar: string = this.person.birthdaySolar; const lunar: string = this.person.birthdayLunar; if (solar !== '' && lunar !== '') { return solar + '(农历 ' + lunar + ')'; } if (solar !== '') { return solar; } if (lunar !== '') { return '农历 ' + lunar; } return ''; } /** * 多行文本块(标题 + 正文)。 * * ⚠️ 为什么不用 `infoRow`:工作经历/地址/备注这类**多行等宽信息** * 配一个小图标反而挤,标题一行、正文一段更清楚。 */ @Builder blockText(label: string, body: string) { Column({ space: 4 }) { Text(label) .fontSize(FbMetric.fontTiny) .fontColor(FbColors.faint) Text(body) .fontSize(FbMetric.fontSmall) .fontColor(FbColors.ink) .lineHeight(21) } .width('100%') .alignItems(HorizontalAlign.Start) .margin({ top: 12 }) } /** 工作经历:每段一行(单位|职务 · 职称(起止)) */ @Builder jobsBlock() { Column({ space: 6 }) { Text('工作经历') .fontSize(FbMetric.fontTiny) .fontColor(FbColors.faint) if (this.person !== null) { ForEach(this.person.jobs, (j: FbJobRow, idx: number) => { Column({ space: 2 }) { Text(this.jobText(j)) .fontSize(FbMetric.fontSmall) .fontColor(FbColors.ink) if (j.note !== '') { Text(j.note) .fontSize(FbMetric.fontTiny) .fontColor(FbColors.muted) } } .width('100%') .alignItems(HorizontalAlign.Start) }, (j: FbJobRow, idx: number): string => 'job_' + idx.toString()) } } .width('100%') .alignItems(HorizontalAlign.Start) .margin({ top: 12 }) } /** * 除主号之外的其它电话(逗号拼接)。 * * ⚠️ `phones[0]` 是主号,已经在「手机号」那一行单独显示过 ⇒ 这里从下标 1 开始, * 否则同一个号码会显示两遍。 * ⚠️ 服务端已保证"主号在前、去重",这里不再做去重(保持简单)。 */ private otherPhones(): string { if (this.person === null) { return ''; } const out: string[] = []; for (let i = 1; i < this.person.phones.length; i++) { out.push(this.person.phones[i]); } return out.join('、'); } /** * 头像图 URL(**空 = 用色块字回落**)。 * * 🔴🔴 v52 修的真 bug(用户 2026-10-15:「点击详情,弹出的卡片里面**为什么没有头像**呢?」): * 列表里画了一整天的头像,**详情浮层却没传 `imageUrl`** —— * `FbAvatar` 的 `imageUrl` 缺省是空串,于是**所有人在详情里都退化成色块字** * (有头像的人也显示不出来)。这是纯粹的漏传参数,不是数据问题。 * ⚠️ `avatar_url`(服务端下发的那个)是**相对路径**,`Image()` 直接用会 * 打到 App 自己的包名上 ⇒ 必须用 `fbImageSrc(key, token)` 拼成 * 带令牌的绝对地址(与列表同一套,见 `fbImageUrl` 的说明)。 */ private avatarUrl(): string { if (this.person === null) { return ''; } return fbImageSrc(this.person.avatar, FB_TOKEN_STORE.get()); } /** 拼多行联系方式的显示串(`label: value`,label 空就只显示 value) */ private contactText(rows: FbContactRow[]): string { const out: string[] = []; for (const r of rows) { out.push(r.label !== '' ? (r.label + ':' + r.value) : r.value); } return out.join('\n'); } /** 地址一整行(国家 省 市 区 + 详细;空段自动跳过) */ private fullAddress(): string { if (this.person === null) { return ''; } const parts: string[] = [ this.person.addrCountry, this.person.addrProvince, this.person.addrCity, this.person.addrDistrict ]; const out: string[] = []; for (const s of parts) { if (s !== '') { out.push(s); } } if (this.person.address !== '') { out.push(this.person.address); } return out.join(' '); } /** 一段工作经历的显示串(`单位 · 职务/职称(起止)`) */ private jobText(j: FbJobRow): string { const head: string[] = []; if (j.org !== '') { head.push(j.org); } // 职务与职称可能都有("副院长 / 副教授"),都可能没有 const roles: string[] = []; if (j.title !== '') { roles.push(j.title); } if (j.rankName !== '' && j.rankName !== j.title) { roles.push(j.rankName); } if (roles.length > 0) { head.push(roles.join(' · ')); } let line: string = head.join('|'); const span: string = this.jobSpan(j); if (span !== '') { line += '(' + span + ')'; } return line; } /** 一段工作经历的起止(`2020-09 至今` / `2015-07 ~ 2020-08`) */ private jobSpan(j: FbJobRow): string { const start: string = j.startYm; const end: string = j.endYm !== '' ? j.endYm : '至今'; if (start === '' && j.endYm === '') { return ''; } if (start === '') { return end; } return start + ' ~ ' + end; } /** * 信息行(图标 + 标签 + 值)。 * * ⚠️ `@Builder` 里**不加返回值**、参数要显式类型 —— 见项目里 ArkTS 约束。 */ @Builder infoRow(icon: string, label: string, value: string) { Row({ space: 10 }) { Image(this.iconOf(icon)) .width(15) .height(15) .renderMode(ImageRenderMode.Template) .colorFilter(fbTint(FbTint.faint)) Column({ space: 2 }) { Text(label) .fontSize(FbMetric.fontTiny) .fontColor(FbColors.faint) Text(value) .fontSize(FbMetric.fontSmall) .fontColor(FbColors.ink) .maxLines(3) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .alignItems(HorizontalAlign.Start) .layoutWeight(1) } .width('100%') .padding({ top: 9, bottom: 9 }) .alignItems(VerticalAlign.Top) } build() { // FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。 // ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 —— // 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。 // ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。 Column() { // ⚠️ 2026-10-19:**卡片外面那一圈主题色环**由共用组件 `FbSheetRing` 给 // (用户要求"所有弹框都加线,保持一致")。 // 这一层只负责**尺寸**(92% 宽 / 72% 高上限)—— 尺寸与外观分开, // 环组件里就不必再堆一堆 `if (embedded)`。 Column() { FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor(), /* embedded 是"塞进 bindSheet"的形态:宿主自己已有容器与投影, 再叠一层阴影会显脏(真机上看着像卡片边缘发黑)。 */ shadowRadius: this.embedded ? 0 : 24 }) { Column({ space: 0 }) { // ---- 顶部:头像 + 姓名 + 称谓 ---- // ⚠️ 这一块**不参与滚动**(它跟着卡片固定在顶部): // 用户往下滑看资料时,"这是谁"始终可见,不用滚回去确认。 Row({ space: 14 }) { FbAvatar({ name: this.person !== null ? this.person.shown : '', color: this.person !== null ? this.person.avatarColor : '#A84A28', dia: 60, fontSizeVp: 24, // 🔴 v52 补上(之前漏传 ⇒ 详情里永远看不到头像,见 avatarUrl 的说明) imageUrl: this.avatarUrl() }) Column({ space: 6 }) { Row({ space: 8 }) { Text(this.person !== null ? this.person.shown : '') .fontSize(FbMetric.fontTitle) .fontWeight(FontWeight.Medium) .fontColor(FbColors.ink) .maxLines(1) if (this.person !== null && this.person.isAdmin) { FbTag({ text: '管理员', tone: 'brand' }) } } .alignItems(VerticalAlign.Center) // 本名(昵称与不同名时才有意义) if (this.person !== null && this.person.name !== this.person.shown) { Text('本名 ' + this.person.name) .fontSize(FbMetric.fontTiny) .fontColor(FbColors.muted) } // 称谓(服务端按家族图谱推导) if (this.person !== null && this.person.label !== '') { FbTag({ text: this.person.label, tone: 'muted' }) } } .alignItems(HorizontalAlign.Start) .layoutWeight(1) } .width('100%') .alignItems(VerticalAlign.Center) // ---- 标签 ---- // ⚠️ 标签行**留在头部(不滚动)**:它很短,而且"这人是哪类人"是扫一眼就要的 // 信息,跟着资料一起滚走反而不方便。详情里的"长内容"是资料行(见下面)。 if (this.person !== null && this.person.tags.length > 0) { Row({ space: 6 }) { ForEach(this.person.tags, (t: string) => { FbTag({ text: t, tone: 'blue' }) }, (t: string): string => 'pt_' + t) } .width('100%') .margin({ top: 12 }) } Divider() .color(FbColors.line) .margin({ top: 14, bottom: 4 }) // 🔴 **资料区可滚动**(用户 2026-10-15:「显示详细信息,并且可以往下滑」)。 // // 为什么必须给它一个 `Scroll`: // 卡片整体限了 `maxHeight: 78%`,而资料行(手机/单位/职务/生日阴阳历/ // 邮箱/地址/座右铭/备注)加起来很容易超过这个高度 —— // 没有 `Scroll` 时**超出的部分直接被裁掉、看不到也够不着**。 // ⚠️ 用 `layoutWeight(1)` 让 Scroll 吃掉"头部与底部按钮之间的剩余高度", // 而不是给固定高度 —— 内容长短不同时都能正好铺满。 // ⚠️ 底部按钮**留在 Scroll 外面**:它们是"动作",不该跟着内容滚走 // (要滑到底才能点"编辑"是很糟的体验)。 Scroll() { Column({ space: 0 }) { // ---- 资料 ---- // ⚠️ 每条都**只在有值时显示** —— 空行会让浮层显得"很多没填", // 而实际上大部分人的手机/邮箱/地址本来就是空的。 if (this.person !== null && this.person.phone !== '') { this.infoRow('phone', '手机号', this.person.phone) } // 🔴 补充电话(服务端下发的 `phones`,主号已在上面单独显示) if (this.person !== null && this.person.phones.length > 1) { this.infoRow('phone', '其他电话', this.otherPhones()) } if (this.person !== null && this.person.org !== '') { this.infoRow('org', '单位', this.person.org) } if (this.person !== null && this.person.title !== '') { this.infoRow('org', '职务', this.person.title) } // 家中排行(只在有值时显示;1 = 老大) if (this.person !== null && this.person.birthRank !== '') { this.infoRow('org', '家中排行', '第 ' + this.person.birthRank + ' 个') } // 生日:阳历 + 农历(农历是服务端算的)—— 合并成一行显示更好读 if (this.person !== null && this.birthdayLine() !== '') { this.infoRow('birth', '生日', this.birthdayLine()) } if (this.person !== null && this.person.email !== '') { this.infoRow('mail', '邮箱', this.person.email) } // 地址(结构化五级 + 详细;旧数据只有 `addrCity` 也能显示) if (this.person !== null && this.fullAddress() !== '') { this.infoRow('addr', '地址', this.fullAddress()) } // 身份证(打码)—— 只显示,编辑时可改 if (this.person !== null && this.person.idCardMasked !== '') { this.infoRow('card', '身份证', this.person.idCardMasked) } // ---- 补充联系方式(vCard 对齐:邮箱/地址/网址,可多行)---- if (this.person !== null && this.person.contacts.email.length > 0) { this.blockText('其它邮箱', this.contactText(this.person.contacts.email)) } if (this.person !== null && this.person.contacts.address.length > 0) { this.blockText('其它地址', this.contactText(this.person.contacts.address)) } if (this.person !== null && this.person.contacts.url.length > 0) { this.blockText('网址', this.contactText(this.person.contacts.url)) } // ---- 工作经历(可多段)---- if (this.person !== null && this.person.jobs.length > 0) { this.jobsBlock() } // ---- 座右铭 / 公共备注 / 我的私人备注 ---- if (this.person !== null && this.person.slogan !== '') { this.blockText('座右铭', '「' + this.person.slogan + '」') } if (this.person !== null && this.person.note !== '') { this.blockText('备注', this.person.note) } // 🔴 「我的私人备注」与公共"备注"**是两块**(只有我看得到) if (this.person !== null && this.person.myNote !== '') { this.blockText('我的备注(仅自己可见)', this.person.myNote) } // 底部一点余量:最后一行不贴着按钮 Row().height(8) } .width('100%') .alignItems(HorizontalAlign.Start) } .layoutWeight(1) .width('100%') .scrollBar(BarState.Off) .edgeEffect(EdgeEffect.Spring) .align(Alignment.TopStart) // ---- 底部按钮:编辑 / 关闭 ---- Row({ space: 10 }) { Text('编辑') .fontSize(FbMetric.fontSmall) .fontColor(Color.White) .textAlign(TextAlign.Center) .layoutWeight(1) .height(40) .backgroundColor('#4A7C82') .borderRadius(FbMetric.radiusS) .onClick((): void => { this.onEdit(); }) Text('关闭') .fontSize(FbMetric.fontSmall) .fontColor(FbColors.muted) .textAlign(TextAlign.Center) .layoutWeight(1) .height(40) .backgroundColor(FbColors.inputBg) .borderRadius(FbMetric.radiusS) .onClick((): void => { this.onClose(); }) } .width('100%') .margin({ top: 18 }) /* ---- 危险区:删除联系人 ---- * * 🔴 用户 2026-10-18:「应该在详情页面的最下面加一个删除按钮。」 * ⇒ 位置在**编辑/关闭之下**、并且与它们之间隔一条分隔线: * 手指从上往下扫时不会顺手点到;想删必须**再往下够**一次。 * 🔴 用户 2026-10-19:「删除按钮全部用红色背景。」 * ⇒ 从"浅红底 + 红字"改成**红底白字**(与编辑/删除浮层里的 * 删除按钮同一规格;位置仍在最底部 + 有二次确认兜底)。 * ⚠️ 确认框在页面侧(`FbPeoplePage.confirmDelete`)—— 见 `onDelete` 的说明。 */ if (this.canDelete) { Divider() .color(FbColors.line) .margin({ top: 14 }) Row({ space: 6 }) { Image($r('app.media.fb_ic_trash')) .width(14) .height(14) /* ⚠️ PNG 图标必须 colorFilter(fillColor 只对 SVG 生效) */ .colorFilter(fbTint(FbTint.white)) Text('删除联系人') .fontSize(FbMetric.fontSmall) .fontColor(Color.White) .fontWeight(FontWeight.Medium) } .width('100%') .height(42) .justifyContent(FlexAlign.Center) .backgroundColor(FbColors.red) .borderRadius(FbMetric.radiusS) .margin({ top: 10 }) .onClick((): void => { this.onDelete(); }) } } // 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**…… // **不要让它左右充满整个屏幕,下面也不要挨着屏幕**, // 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」 // ⇒ 不再贴底满宽:左右留边(92%)、四角圆角、垂直居中(由宿主的 Stack 对齐)。 // // ⚠️ 宽度由**这一层**给(不是卡片自己):卡片在环里必须是 100%, // 否则 0.92×0.92 ≈ 84.6%,会比别的浮层窄一圈。 // ⚠️ `embedded`(宿主自己就是 sheet)时宽度交给 sheet 决定 —— // 再用 92% 会在 sheet 里又缩一次,看上去"卡片比容器窄一圈"。 .width('100%') /* 最多占屏幕 72%:上方留出可见列表,暗示"这是浮层、下面还有内容"。 ⚠️ 这个限制**必须写在卡片自己身上**(不能交给外面的环层): 卡片内部有 `Scroll().layoutWeight(1)`,它要靠**父容器的确定高度** 才能算出"剩下多少可以滚"。把 maxHeight 挪到外面那层, 内层高度就变成"由内容决定"⇒ `layoutWeight` 拿到 0 ⇒ **资料区直接塌成一条线**(编辑卡同理,别一起挪)。 */ .constraintSize({ maxHeight: this.embedded ? '100%' : '72%' }) // ⚠️ embedded 时**去掉底部手势区那段留白**:那 16 是给 // "贴底遮罩 + 隐藏手势条"的场景补的(见 FB_SHEET_BOTTOM_GAP), // 而 bindSheet 自己已经把手势区让出来了 ⇒ 再补就是白白多一截空白。 .padding({ left: 18, right: 18, top: 18, bottom: this.embedded ? 20 : 20 + FB_SHEET_BOTTOM_GAP }) .alignItems(HorizontalAlign.Start) } } .width(this.embedded ? 'auto' : '92%') } /* 🔴 embedded(宿主自己就是 sheet)时**不能再铺满 + 居中**: 内容是"按自适应高度"交给 sheet 的,铺 100% 高会与 `SheetSize.FIT_CONTENT` 互相打架 ⇒ 浮层起来立刻被关(真机实测)。 */ .width(this.embedded ? 'auto' : '100%') .height(this.embedded ? 'auto' : '100%') .justifyContent(this.embedded ? FlexAlign.Start : FlexAlign.Center) .hitTestBehavior(HitTestMode.Transparent) } }