1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { KeyCode } from '@ohos.multimodalInput.keyCode'; 17import measure from '@ohos.measure'; 18import mediaquery from '@ohos.mediaquery'; 19import resourceManager from '@ohos.resourceManager'; 20 21export enum ChipSize { 22 NORMAL = "NORMAL", 23 SMALL = "SMALL" 24} 25 26enum BreakPointsType { 27 SM = "SM", 28 MD = "MD", 29 LG = "LG" 30} 31 32export interface IconCommonOptions { 33 src: ResourceStr; 34 size?: SizeOptions; 35 fillColor?: ResourceColor 36} 37 38export interface SuffixIconOptions extends IconCommonOptions { 39 action?: () => void; 40} 41 42export interface PrefixIconOptions extends IconCommonOptions {} 43 44export interface LabelMarginOptions { 45 left?: Dimension; 46 right?: Dimension; 47} 48 49export interface LabelOptions { 50 text: string; 51 fontSize?: Dimension; 52 fontColor?: ResourceColor; 53 fontFamily?: string; 54 labelMargin?: LabelMarginOptions; 55} 56 57interface IconTheme { 58 size: SizeOptions; 59 fillColor: ResourceColor; 60} 61 62interface PrefixIconTheme extends IconTheme {} 63 64interface SuffixIconTheme extends IconTheme { 65 defaultDeleteIcon: ResourceStr; 66 focusable: boolean; 67} 68 69interface LabelTheme { 70 normalFontSize: Dimension; 71 smallFontSize: Dimension; 72 fontColor: ResourceColor; 73 fontFamily: string; 74 normalMargin: Margin; 75 smallMargin: Margin; 76} 77 78interface ChipNodeOpacity { 79 normal: number; 80 hover: number; 81 pressed: number; 82 disabled: number; 83} 84 85interface ChipNodeConstraintWidth { 86 breakPointMinWidth: number, 87 breakPointSmMaxWidth: number, 88 breakPointMdMaxWidth: number, 89 breakPointLgMaxWidth: number, 90} 91 92interface ChipNodeTheme { 93 minLabelWidth: Dimension; 94 normalHeight: Dimension; 95 smallHeight: Dimension; 96 enabled: boolean; 97 backgroundColor: ResourceColor; 98 focusOutlineColor: ResourceColor; 99 normalBorderRadius: Dimension; 100 smallBorderRadius: Dimension; 101 borderWidth: number; 102 normalPadding: Padding; 103 smallPadding: Padding; 104 hoverBlendColor: ResourceColor; 105 pressedBlendColor: ResourceColor; 106 opacity: ChipNodeOpacity; 107 breakPointConstraintWidth: ChipNodeConstraintWidth; 108} 109 110interface ChipTheme { 111 prefixIcon: PrefixIconTheme; 112 label: LabelTheme; 113 suffixIcon: SuffixIconTheme; 114 chipNode: ChipNodeTheme; 115} 116 117export const defaultTheme: ChipTheme = { 118 prefixIcon: { 119 size: { width: 16, height: 16 }, 120 fillColor: $r('sys.color.ohos_id_color_secondary'), 121 }, 122 label: { 123 normalFontSize: $r('sys.float.ohos_id_text_size_button2'), 124 smallFontSize: $r('sys.float.ohos_id_text_size_button3'), 125 fontColor: $r('sys.color.ohos_id_color_text_primary'), 126 fontFamily: "HarmonyOS Sans", 127 normalMargin: { left: 6, right: 6, top: 0, bottom: 0 }, 128 smallMargin: { left: 4, right: 4, top: 0, bottom: 0 }, 129 }, 130 suffixIcon: { 131 size: { width: 16, height: 16 }, 132 fillColor: $r('sys.color.ohos_id_color_primary'), 133 defaultDeleteIcon: $r('sys.media.ohos_ic_public_cancel', 16, 16), 134 focusable: false, 135 }, 136 chipNode: { 137 minLabelWidth: 12, 138 normalHeight: 36, 139 smallHeight: 28, 140 enabled: true, 141 backgroundColor: $r('sys.color.ohos_id_color_button_normal'), 142 focusOutlineColor: $r('sys.color.ohos_id_color_focused_outline'), 143 normalBorderRadius: $r('sys.float.ohos_id_corner_radius_tips_instant_tip'), 144 smallBorderRadius: $r('sys.float.ohos_id_corner_radius_piece'), 145 borderWidth: 2, 146 normalPadding: { left: 16, right: 16, top: 0, bottom: 0 }, 147 smallPadding: { left: 12, right: 12, top: 0, bottom: 0 }, 148 hoverBlendColor: $r('sys.color.ohos_id_color_hover'), 149 pressedBlendColor: $r('sys.color.ohos_id_color_click_effect'), 150 opacity: { normal: 1, hover: 0.95, pressed: 0.9, disabled: 0.4 }, 151 breakPointConstraintWidth: { 152 breakPointMinWidth: 128, 153 breakPointSmMaxWidth: 156, 154 breakPointMdMaxWidth: 280, 155 breakPointLgMaxWidth: 400 156 } 157 } 158}; 159 160const noop = () => { 161}; 162 163interface ChipOptions { 164 prefixIcon?: PrefixIconOptions; 165 label: LabelOptions; 166 suffixIcon?: SuffixIconOptions; 167 allowClose?: boolean; 168 enabled?: boolean; 169 backgroundColor?: ResourceColor; 170 borderRadius?: Dimension; 171 size?: ChipSize | SizeOptions; 172 onClose?: () => void 173} 174 175@Builder 176export function Chip(options: ChipOptions) { 177 ChipComponent({ 178 chipSize: options.size, 179 prefixIcon: options.prefixIcon, 180 label: options.label, 181 suffixIcon: options.suffixIcon, 182 allowClose: options.allowClose, 183 chipEnabled: options.enabled, 184 chipNodeBackgroundColor: options.backgroundColor, 185 chipNodeRadius: options.borderRadius, 186 onClose: options.onClose 187 }) 188} 189 190@Component 191export struct ChipComponent { 192 private theme: ChipTheme = defaultTheme; 193 @Prop chipSize: ChipSize | SizeOptions = ChipSize.NORMAL 194 @Prop allowClose: boolean = true 195 @Prop prefixIcon: PrefixIconOptions = { src: "" } 196 @Prop label: LabelOptions = { text: "" } 197 @Prop suffixIcon: SuffixIconOptions = { src: "" } 198 @Prop chipNodeBackgroundColor: ResourceColor = this.theme.chipNode.backgroundColor 199 @Prop chipNodeRadius: Dimension | undefined = void (0) 200 @Prop chipEnabled: boolean = true 201 @State isHover: boolean = false 202 @State chipScale: ScaleOptions = { x: 1, y: 1 } 203 @State chipOpacity: number = 1 204 @State chipBlendColor: ResourceColor = Color.Transparent 205 @State deleteChip: boolean = false 206 @State chipNodeOnFocus: boolean = false 207 @State useDefaultSuffixIcon: boolean = false 208 private chipNodeSize: SizeOptions = {} 209 private onClose: () => void = noop 210 @State suffixIconOnFocus: boolean = false 211 @State chipBreakPoints: BreakPointsType = BreakPointsType.SM 212 private smListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync("0vp<width<600vp") 213 private mdListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync("600vp<=width<840vp") 214 private lgListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync("840vp<=width") 215 216 private isChipSizeEnum(): boolean { 217 return typeof (this.chipSize) === 'string' 218 } 219 220 private getLabelFontSize(): Dimension { 221 try { 222 if (this.label?.fontSize !== void (0) && this.toVp(this.label.fontSize) >= 0) { 223 return this.label.fontSize 224 } else { 225 if (this.isChipSizeEnum() && this.chipSize === ChipSize.SMALL) { 226 return resourceManager.getSystemResourceManager() 227 .getNumberByName((((this.theme.label.smallFontSize as Resource).params as string[])[0]).split('.')[2]) 228 } else { 229 return resourceManager.getSystemResourceManager() 230 .getNumberByName((((this.theme.label.normalFontSize as Resource).params as string[])[0]).split('.')[2]) 231 } 232 } 233 } 234 catch (error) { 235 return 0 236 } 237 238 } 239 240 private getLabelFontColor(): ResourceColor { 241 return this.label?.fontColor ?? this.theme.label.fontColor 242 } 243 244 private getLabelFontFamily(): string { 245 return this.label?.fontFamily ?? this.theme.label.fontFamily 246 } 247 248 private toVp(value: Dimension | Length | undefined): number { 249 if (value === void (0)) { 250 return Number.NEGATIVE_INFINITY 251 } 252 switch (typeof (value)) { 253 case 'number': 254 return value as number 255 case 'object': 256 try { 257 if ((value as Resource).id !== -1) { 258 return px2vp(getContext(this).resourceManager.getNumber((value as Resource).id)) 259 } else { 260 return px2vp(getContext(this) 261 .resourceManager 262 .getNumberByName(((value.params as string[])[0]).split('.')[2])) 263 } 264 } catch (error) { 265 return Number.NEGATIVE_INFINITY 266 } 267 case 'string': 268 let regex: RegExp = new RegExp("(-?\\d+(?:\\.\\d+)?)_?(fp|vp|px|lpx|%)?$", "i"); 269 let matches: RegExpMatchArray | null = value.match(regex); 270 if (!matches) { 271 return Number.NEGATIVE_INFINITY 272 } 273 let length: number = Number(matches?.[1] ?? 0); 274 let unit: string = matches?.[2] ?? 'vp' 275 switch (unit.toLowerCase()) { 276 case 'px': 277 length = px2vp(length) 278 break 279 case 'fp': 280 length = px2vp(fp2px(length)) 281 break 282 case 'lpx': 283 length = px2vp(lpx2px(length)) 284 break 285 case '%': 286 length = Number.NEGATIVE_INFINITY 287 break 288 case 'vp': 289 break 290 default: 291 break 292 } 293 return length 294 default: 295 return Number.NEGATIVE_INFINITY 296 } 297 } 298 299 private getLabelMargin(): Margin { 300 let labelMargin: Margin = { left: 0, right: 0 } 301 if (this.label?.labelMargin?.left !== void (0) && this.toVp(this.label.labelMargin.left) >= 0) { 302 labelMargin.left = this.label?.labelMargin?.left 303 } else if (this.prefixIcon?.src) { 304 if (this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL) { 305 labelMargin.left = this.theme.label.smallMargin.left 306 } else { 307 labelMargin.left = this.theme.label.normalMargin.left 308 } 309 } 310 if (this.label?.labelMargin?.right !== void (0) && this.toVp(this.label.labelMargin.right) >= 0) { 311 labelMargin.right = this.label?.labelMargin?.right 312 } else if (this.suffixIcon?.src || this.useDefaultSuffixIcon) { 313 if (this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL) { 314 labelMargin.right = this.theme.label.smallMargin.right 315 } else { 316 labelMargin.right = this.theme.label.normalMargin.right 317 } 318 319 } 320 return labelMargin 321 } 322 323 private getSuffixIconSize(): SizeOptions { 324 let suffixIconSize: SizeOptions = { width: 0, height: 0 } 325 if (this.suffixIcon?.size?.width !== void (0) && this.toVp(this.suffixIcon?.size?.width) >= 0) { 326 suffixIconSize.width = this.suffixIcon?.size?.width 327 } else { 328 if (this.getSuffixIconSrc()) { 329 suffixIconSize.width = this.theme.suffixIcon.size.width 330 } else { 331 suffixIconSize.width = 0 332 } 333 } 334 if (this.suffixIcon?.size?.height !== void (0) && this.toVp(this.suffixIcon?.size?.height) >= 0) { 335 suffixIconSize.height = this.suffixIcon?.size?.height 336 } else { 337 if (this.getSuffixIconSrc()) { 338 suffixIconSize.height = this.theme.suffixIcon.size.height 339 } else { 340 suffixIconSize.height = 0 341 } 342 } 343 return suffixIconSize 344 } 345 346 private getPrefixIconSize(): SizeOptions { 347 let prefixIconSize: SizeOptions = { width: 0, height: 0 } 348 if (this.prefixIcon?.size?.width !== void (0) && this.toVp(this.prefixIcon?.size?.width) >= 0) { 349 prefixIconSize.width = this.prefixIcon?.size?.width 350 } else { 351 if (this.prefixIcon?.src) { 352 prefixIconSize.width = this.theme.prefixIcon.size.width 353 } else { 354 prefixIconSize.width = 0 355 } 356 } 357 if (this.prefixIcon?.size?.height !== void (0) && this.toVp(this.prefixIcon?.size?.height) >= 0) { 358 prefixIconSize.height = this.prefixIcon?.size?.height 359 } else { 360 if (this.prefixIcon?.src) { 361 prefixIconSize.height = this.theme.prefixIcon.size.height 362 } else { 363 prefixIconSize.height = 0 364 } 365 } 366 return prefixIconSize 367 } 368 369 private getPrefixIconFilledColor(): ResourceColor { 370 return this.prefixIcon?.fillColor ?? this.theme.prefixIcon.fillColor 371 } 372 373 private getSuffixIconFilledColor(): ResourceColor { 374 return this.suffixIcon?.fillColor ?? this.theme.suffixIcon.fillColor 375 } 376 377 private getSuffixIconFocusable(): boolean { 378 return (this.useDefaultSuffixIcon && this.allowClose) || this.suffixIcon?.action !== void (0) 379 } 380 381 private getChipNodePadding(): Padding { 382 return (this.isChipSizeEnum() && this.chipSize === ChipSize.SMALL) ? this.theme.chipNode.smallPadding : this.theme.chipNode.normalPadding 383 } 384 385 private getChipNodeRadius(): Dimension { 386 if (this.chipNodeRadius !== void (0) && this.toVp(this.chipNodeRadius) >= 0) { 387 return this.chipNodeRadius as Dimension 388 } else { 389 return ((this.isChipSizeEnum() && this.chipSize === ChipSize.SMALL) ? this.theme.chipNode.smallBorderRadius : this.theme.chipNode.normalBorderRadius) 390 } 391 } 392 393 private getChipNodeBackGroundColor(): ResourceColor { 394 return this.chipNodeBackgroundColor ?? this.theme.chipNode.backgroundColor 395 } 396 397 private getChipNodeHeight(): Length { 398 if (this.isChipSizeEnum()) { 399 return this.chipSize === ChipSize.SMALL ? this.theme.chipNode.smallHeight : this.theme.chipNode.normalHeight 400 } else { 401 this.chipNodeSize = this.chipSize as SizeOptions 402 return (this.chipNodeSize?.height !== void (0) && this.toVp(this.chipNodeSize?.height) >= 0) ? this.toVp(this.chipNodeSize?.height) : this.theme.chipNode.normalHeight 403 } 404 } 405 406 private getLabelWidth(): number { 407 return px2vp(measure.measureText({ 408 textContent: this.label?.text ?? "", 409 fontSize: this.getLabelFontSize(), 410 fontFamily: this.label?.fontFamily ?? this.theme.label.fontFamily, 411 maxLines: 1, 412 overflow: TextOverflow.Ellipsis, 413 textAlign: TextAlign.Center 414 })) 415 } 416 417 private getCalculateChipNodeWidth(): number { 418 let calWidth: number = 0 419 calWidth += this.getChipNodePadding().left as number 420 calWidth += this.toVp(this.getPrefixIconSize().width) 421 calWidth += this.toVp(this.getLabelMargin().left) 422 calWidth += this.getLabelWidth() 423 calWidth += this.toVp(this.getLabelMargin().right) 424 calWidth += this.toVp(this.getSuffixIconSize().width) 425 calWidth += this.getChipNodePadding().right as number 426 return calWidth 427 } 428 429 private getReserveChipNodeWidth(): number { 430 return this.getCalculateChipNodeWidth() - this.getLabelWidth() + (this.theme.chipNode.minLabelWidth as number) 431 } 432 433 private getChipEnable(): boolean { 434 return this.chipEnabled || this.chipEnabled === void (0) 435 } 436 437 private getChipNodeOpacity(): number { 438 return this.getChipEnable() ? this.chipOpacity : this.theme.chipNode.opacity.disabled 439 } 440 441 private handleTouch(event: TouchEvent) { 442 if (!this.getChipEnable()) { 443 return 444 } 445 if (this.isHover) { 446 if (event.type === TouchType.Down) { 447 animateTo({ curve: Curve.Sharp, duration: 100 }, () => { 448 this.chipBlendColor = this.theme.chipNode.pressedBlendColor 449 this.chipOpacity = this.theme.chipNode.opacity.pressed 450 }) 451 } else if (event.type === TouchType.Up) { 452 animateTo({ curve: Curve.Sharp, duration: 100 }, () => { 453 this.chipBlendColor = this.theme.chipNode.hoverBlendColor 454 this.chipOpacity = this.theme.chipNode.opacity.hover 455 }) 456 } 457 } else { 458 if (event.type === TouchType.Down) { 459 animateTo({ curve: Curve.Friction, duration: 350 }, () => { 460 this.chipBlendColor = this.theme.chipNode.pressedBlendColor 461 this.chipOpacity = this.theme.chipNode.opacity.pressed 462 }) 463 } else if (event.type === TouchType.Up) { 464 animateTo({ curve: Curve.Friction, duration: 350 }, () => { 465 this.chipBlendColor = Color.Transparent 466 this.chipOpacity = this.theme.chipNode.opacity.normal 467 }) 468 } 469 } 470 } 471 472 private hoverAnimate(isHover: boolean) { 473 if (!this.getChipEnable()) { 474 return 475 } 476 this.isHover = isHover 477 animateTo({ 478 curve: Curve.Friction, 479 duration: 250 480 }, () => { 481 if (isHover) { 482 this.chipBlendColor = this.theme.chipNode.hoverBlendColor 483 this.chipOpacity = this.theme.chipNode.opacity.hover 484 } else { 485 this.chipBlendColor = Color.Transparent 486 this.chipOpacity = this.theme.chipNode.opacity.normal 487 } 488 }) 489 } 490 491 private deleteChipNodeAnimate() { 492 animateTo({ duration: 150, curve: Curve.Sharp }, () => { 493 this.chipOpacity = 0 494 this.chipBlendColor = Color.Transparent 495 }) 496 animateTo({ duration: 150, curve: Curve.FastOutLinearIn, onFinish: () => { 497 this.deleteChip = true 498 } }, 499 () => { 500 this.chipScale = { x: 0.85, y: 0.85 } 501 }) 502 } 503 504 private getSuffixIconSrc(): ResourceStr | undefined { 505 this.useDefaultSuffixIcon = !this.suffixIcon?.src && this.allowClose 506 return this.useDefaultSuffixIcon ? this.theme.suffixIcon.defaultDeleteIcon : (this.suffixIcon?.src ?? void (0)) 507 } 508 509 private getChipNodeWidth(): Length { 510 if (!this.isChipSizeEnum()) { 511 this.chipNodeSize = this.chipSize as SizeOptions 512 if (this.chipNodeSize?.width !== void (0) && this.toVp(this.chipNodeSize.width) >= 0) { 513 return this.toVp(this.chipNodeSize.width) 514 } 515 } 516 let constraintWidth: ConstraintSizeOptions = this.getChipConstraintWidth() 517 return Math.min(Math.max(this.getCalculateChipNodeWidth(), constraintWidth.minWidth as number), constraintWidth.maxWidth as number); 518 } 519 520 private getFocusOverlaySize(): SizeOptions { 521 return { 522 width: Math.max(this.getChipNodeWidth() as number, this.getChipConstraintWidth().minWidth as number) + 8, 523 height: this.getChipNodeHeight() as number + 8 524 } 525 } 526 527 private getChipConstraintWidth(): ConstraintSizeOptions { 528 let calcMinWidth: number = this.getReserveChipNodeWidth() 529 if (!this.isChipSizeEnum()) { 530 this.chipNodeSize = this.chipSize as SizeOptions 531 if (this.chipNodeSize?.width !== void (0) && this.toVp(this.chipNodeSize?.width) >= 0) { 532 return { minWidth: calcMinWidth, maxWidth: this.chipNodeSize.width as number } 533 } 534 } 535 536 let constraintWidth: number = this.getCalculateChipNodeWidth() 537 switch (this.chipBreakPoints) { 538 case BreakPointsType.SM: 539 return { 540 minWidth: calcMinWidth, 541 maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointSmMaxWidth) 542 } 543 case BreakPointsType.MD: 544 return { 545 minWidth: Math.max(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMinWidth), 546 maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMdMaxWidth) 547 } 548 549 case BreakPointsType.LG: 550 return { 551 minWidth: Math.max(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMinWidth), 552 maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointLgMaxWidth) 553 } 554 default: 555 return { minWidth: calcMinWidth, maxWidth: constraintWidth } 556 } 557 } 558 559 @Builder 560 focusOverlay() { 561 Stack() { 562 if (this.chipNodeOnFocus && !this.suffixIconOnFocus) { 563 Stack() 564 .borderRadius(this.toVp(this.getChipNodeRadius()) + 4) 565 .size(this.getFocusOverlaySize()) 566 .borderColor(this.theme.chipNode.focusOutlineColor) 567 .borderWidth(this.theme.chipNode.borderWidth) 568 } 569 } 570 .size({ width: 1, height: 1 }) 571 .align(Alignment.Center) 572 } 573 574 @Styles 575 suffixIconFocusStyles() { 576 .borderColor(this.theme.chipNode.focusOutlineColor) 577 .borderWidth(this.getSuffixIconFocusable() ? this.theme.chipNode.borderWidth : 0) 578 } 579 580 @Styles 581 suffixIconNormalStyles() { 582 .borderColor(Color.Transparent) 583 .borderWidth(0) 584 } 585 586 aboutToAppear() { 587 this.smListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => { 588 if (mediaQueryResult.matches) { 589 this.chipBreakPoints = BreakPointsType.SM 590 } 591 }) 592 this.mdListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => { 593 if (mediaQueryResult.matches) { 594 this.chipBreakPoints = BreakPointsType.MD 595 } 596 }) 597 this.lgListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => { 598 if (mediaQueryResult.matches) { 599 this.chipBreakPoints = BreakPointsType.LG 600 } 601 }) 602 } 603 604 private getVisibility(): Visibility { 605 if (this.toVp(this.getChipNodeHeight()) > 0) { 606 return Visibility.Visible 607 } else { 608 return Visibility.None 609 } 610 } 611 612 aboutToDisappear() { 613 this.smListener.off("change") 614 this.mdListener.off("change") 615 this.lgListener.off("change") 616 } 617 618 @Builder 619 chipBuilder() { 620 Row() { 621 if (this.prefixIcon?.src !== "") { 622 Image(this.prefixIcon?.src) 623 .opacity(this.getChipNodeOpacity()) 624 .size(this.getPrefixIconSize()) 625 .fillColor(this.getPrefixIconFilledColor()) 626 .enabled(this.getChipEnable()) 627 .objectFit(ImageFit.Cover) 628 .focusable(false) 629 .flexShrink(0) 630 .visibility(this.getVisibility()) 631 .draggable(false) 632 } 633 634 Text(this.label?.text ?? "") 635 .opacity(this.getChipNodeOpacity()) 636 .fontSize(this.getLabelFontSize()) 637 .fontColor(this.getLabelFontColor()) 638 .fontFamily(this.getLabelFontFamily()) 639 .margin(this.getLabelMargin()) 640 .enabled(this.getChipEnable()) 641 .maxLines(1) 642 .textOverflow({ overflow: TextOverflow.Ellipsis }) 643 .flexShrink(1) 644 .focusable(true) 645 .textAlign(TextAlign.Center) 646 .visibility(this.getVisibility()) 647 .draggable(false) 648 .stateStyles({ 649 focused: {} 650 }) 651 652 Image(this.getSuffixIconSrc()) 653 .opacity(this.getChipNodeOpacity()) 654 .size(this.getSuffixIconSize()) 655 .fillColor(this.getSuffixIconFilledColor()) 656 .enabled(this.getChipEnable()) 657 .focusable(this.getSuffixIconFocusable()) 658 .objectFit(ImageFit.Cover) 659 .flexShrink(0) 660 .visibility(this.getVisibility()) 661 .draggable(false) 662 .onFocus(() => { 663 this.suffixIconOnFocus = true 664 }) 665 .onBlur(() => { 666 this.suffixIconOnFocus = false 667 }) 668 .onClick(() => { 669 if (!this.getChipEnable()) { 670 return 671 } 672 if (this.suffixIcon?.action) { 673 this.suffixIcon.action() 674 return 675 } 676 if (this.allowClose && this.useDefaultSuffixIcon) { 677 this.onClose() 678 this.deleteChipNodeAnimate() 679 return 680 } 681 }) 682 } 683 .alignItems(VerticalAlign.Center) 684 .justifyContent(FlexAlign.Center) 685 .clip(false) 686 .padding(this.getChipNodePadding()) 687 .height(this.getChipNodeHeight()) 688 .width(this.getChipNodeWidth()) 689 .constraintSize(this.getChipConstraintWidth()) 690 .backgroundColor(this.getChipNodeBackGroundColor()) 691 .borderRadius(this.getChipNodeRadius()) 692 .enabled(this.getChipEnable()) 693 .scale(this.chipScale) 694 .focusable(true) 695 .colorBlend(this.chipBlendColor) 696 .opacity(this.getChipNodeOpacity()) 697 .overlay(this.focusOverlay, { align: Alignment.Center }) 698 .onFocus(() => { 699 this.chipNodeOnFocus = true 700 }) 701 .onBlur(() => { 702 this.chipNodeOnFocus = false 703 }) 704 .onTouch((event) => { 705 this.handleTouch(event) 706 }) 707 .onHover((isHover: boolean) => { 708 this.hoverAnimate(isHover) 709 }) 710 .onKeyEvent((event) => { 711 if (event.type === KeyType.Down && event.keyCode === KeyCode.KEYCODE_FORWARD_DEL && !this.suffixIconOnFocus) { 712 this.deleteChipNodeAnimate() 713 } 714 }) 715 } 716 717 build() { 718 if (!this.deleteChip) { 719 this.chipBuilder() 720 } 721 } 722}