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'; 20import { ColorMetrics, LengthMetrics, LengthUnit } from '@ohos.arkui.node'; 21import EnvironmentCallback from '@ohos.app.ability.EnvironmentCallback'; 22import { SymbolGlyphModifier } from '@ohos.arkui.modifier'; 23import componentUtils from '@ohos.arkui.componentUtils'; 24import hilog from '@ohos.hilog'; 25import common from '@ohos.app.ability.common'; 26 27const RESOURCE_TYPE_STRING = 10003; 28const RESOURCE_TYPE_FLOAT = 10002; 29const RESOURCE_TYPE_INTEGER = 10007; 30 31export enum ChipSize { 32 NORMAL = "NORMAL", 33 SMALL = "SMALL" 34} 35 36enum IconType { 37 PREFIX_ICON = "PREFIXICON", 38 SUFFIX_ICON = "SUFFIXICON", 39 PREFIX_SYMBOL = "PREFIXSYMBOL", 40 SUFFIX_SYMBOL = "SUFFIXSYMBOL", 41} 42 43enum BreakPointsType { 44 SM = "SM", 45 MD = "MD", 46 LG = "LG" 47} 48 49export enum AccessibilitySelectedType { 50 CLICKED = 0, 51 CHECKED = 1, 52 SELECTED = 2, 53} 54 55export interface IconCommonOptions { 56 src: ResourceStr; 57 size?: SizeOptions; 58 fillColor?: ResourceColor; 59 activatedFillColor?: ResourceColor; 60} 61 62export interface SuffixIconOptions extends IconCommonOptions { 63 action?: () => void; 64 accessibilityText?: ResourceStr; 65 accessibilityDescription?: ResourceStr; 66 accessibilityLevel?: string; 67} 68 69export interface PrefixIconOptions extends IconCommonOptions {} 70 71export interface AccessibilityOptions { 72 accessibilityLevel?: string; 73 accessibilityText?: ResourceStr; 74 accessibilityDescription?: ResourceStr; 75} 76 77export interface CloseOptions extends AccessibilityOptions {} 78 79export interface ChipSymbolGlyphOptions { 80 normal?: SymbolGlyphModifier; 81 activated?: SymbolGlyphModifier; 82} 83 84export interface ChipSuffixSymbolGlyphOptions { 85 normalAccessibility?: AccessibilityOptions; 86 activatedAccessibility?: AccessibilityOptions; 87 action?: VoidCallback; 88} 89 90export interface LabelMarginOptions { 91 left?: Dimension; 92 right?: Dimension; 93} 94 95export interface LocalizedLabelMarginOptions { 96 start?: LengthMetrics; 97 end?: LengthMetrics; 98} 99 100export interface LabelOptions { 101 text: string; 102 fontSize?: Dimension; 103 fontColor?: ResourceColor; 104 activatedFontColor?: ResourceColor; 105 fontFamily?: string; 106 labelMargin?: LabelMarginOptions; 107 localizedLabelMargin?: LocalizedLabelMarginOptions; 108} 109 110interface IconTheme { 111 normalSize: SizeOptions; 112 smallSize: SizeOptions; 113 fillColor: ResourceColor; 114 activatedFillColor: ResourceColor; 115 focusFillColor: ResourceColor; 116 focusActivatedColor: ResourceColor; 117} 118 119interface PrefixIconTheme extends IconTheme {} 120 121interface SuffixIconTheme extends IconTheme { 122 defaultDeleteIcon: ResourceStr; 123 focusable: boolean; 124 isShowMargin: Resource; 125} 126 127interface DefaultSymbolTheme { 128 normalFontColor: Array<ResourceColor>; 129 activatedFontColor: Array<ResourceColor>; 130 smallSymbolFontSize: Length; 131 normalSymbolFontSize: Length; 132 defaultEffect: number; 133} 134 135interface LabelTheme { 136 normalFontSize: Dimension; 137 smallFontSize: Dimension; 138 focusFontColor: ResourceColor; 139 focusActiveFontColor: ResourceColor; 140 fontColor: ResourceColor; 141 activatedFontColor: ResourceColor; 142 fontFamily: string; 143 fontWeight: Resource; 144 normalMargin: Margin; 145 localizedNormalMargin: LocalizedMargin; 146 smallMargin: Margin; 147 localizedSmallMargin: LocalizedMargin; 148 defaultFontSize: Dimension; 149} 150 151interface ChipNodeOpacity { 152 normal: number; 153 hover: number; 154 pressed: number; 155} 156 157interface ChipNodeConstraintWidth { 158 breakPointMinWidth: number, 159 breakPointSmMaxWidth: number, 160 breakPointMdMaxWidth: number, 161 breakPointLgMaxWidth: number, 162} 163 164interface ChipNodeTheme { 165 suitAgeScale: number; 166 minLabelWidth: Dimension; 167 normalHeight: Dimension; 168 smallHeight: Dimension; 169 enabled: boolean; 170 activated: boolean; 171 backgroundColor: ResourceColor; 172 activatedBackgroundColor: ResourceColor; 173 focusOutlineColor: ResourceColor; 174 borderColor: ResourceColor, 175 defaultBorderWidth: Resource; 176 activatedBorderColor: ResourceColor; 177 focusBtnScaleX: Resource; 178 focusBtnScaleY: Resource; 179 focusBgColor: ResourceColor; 180 focusActivatedBgColor: ResourceColor; 181 normalShadowStyle: Resource; 182 smallShadowStyle: Resource; 183 focusOutlineMargin: number; 184 normalBorderRadius: Dimension; 185 smallBorderRadius: Dimension; 186 borderWidth: number; 187 localizedNormalPadding: LocalizedPadding; 188 localizedSmallPadding: LocalizedPadding; 189 hoverBlendColor: ResourceColor; 190 pressedBlendColor: ResourceColor; 191 opacity: ChipNodeOpacity; 192 breakPointConstraintWidth: ChipNodeConstraintWidth; 193} 194 195interface ChipTheme { 196 prefixIcon: PrefixIconTheme; 197 label: LabelTheme; 198 suffixIcon: SuffixIconTheme; 199 defaultSymbol: DefaultSymbolTheme; 200 chipNode: ChipNodeTheme; 201} 202 203const noop = () => { 204}; 205 206interface ChipOptions { 207 prefixIcon?: PrefixIconOptions; 208 prefixSymbol?: ChipSymbolGlyphOptions; 209 label: LabelOptions; 210 suffixIcon?: SuffixIconOptions; 211 suffixSymbol?: ChipSymbolGlyphOptions; 212 suffixSymbolOptions?: ChipSuffixSymbolGlyphOptions; 213 allowClose?: boolean; 214 closeOptions?: CloseOptions; 215 enabled?: boolean; 216 activated?: boolean; 217 backgroundColor?: ResourceColor; 218 activatedBackgroundColor?: ResourceColor; 219 borderRadius?: Dimension; 220 size?: ChipSize | SizeOptions; 221 direction?: Direction; 222 accessibilitySelectedType?: AccessibilitySelectedType; 223 accessibilityDescription?: ResourceStr; 224 accessibilityLevel?: string; 225 onClose?: () => void 226 onClicked?: () => void 227} 228 229@Builder 230export function Chip(options: ChipOptions) { 231 ChipComponent({ 232 chipSize: options.size, 233 prefixIcon: options.prefixIcon, 234 prefixSymbol: options.prefixSymbol, 235 label: options.label, 236 suffixIcon: options.suffixIcon, 237 suffixSymbol: options.suffixSymbol, 238 suffixSymbolOptions: options.suffixSymbolOptions, 239 allowClose: options.allowClose, 240 closeOptions: options.closeOptions, 241 chipEnabled: options.enabled, 242 chipActivated: options.activated, 243 chipNodeBackgroundColor: options.backgroundColor, 244 chipNodeActivatedBackgroundColor: options.activatedBackgroundColor, 245 chipNodeRadius: options.borderRadius, 246 chipDirection: options.direction, 247 chipAccessibilitySelectedType: options.accessibilitySelectedType, 248 chipAccessibilityDescription: options.accessibilityDescription, 249 chipAccessibilityLevel: options.accessibilityLevel, 250 onClose: options.onClose, 251 onClicked: options.onClicked, 252 }) 253} 254 255function isValidString(dimension: string, regex: RegExp): boolean { 256 const matches = dimension.match(regex); 257 if (!matches || matches.length < 3) { 258 return false; 259 } 260 const value = Number.parseFloat(matches[1]); 261 return value >= 0; 262} 263 264function isValidDimensionString(dimension: string): boolean { 265 return isValidString(dimension, new RegExp('(-?\\d+(?:\\.\\d+)?)_?(fp|vp|px|lpx|%)?$', 'i')); 266} 267 268function isValidResource(context: Context | undefined, value: Resource) { 269 const resourceManager = context?.resourceManager; 270 if (value === void (0) || value === null || resourceManager === void (0)) { 271 return false; 272 } 273 if (value.type !== RESOURCE_TYPE_STRING && value.type !== RESOURCE_TYPE_INTEGER && 274 value.type !== RESOURCE_TYPE_FLOAT) { 275 return false; 276 } 277 278 if (value.type === RESOURCE_TYPE_INTEGER || value.type === RESOURCE_TYPE_FLOAT) { 279 if (resourceManager.getNumber(value.id) >= 0) { 280 return true; 281 } else { 282 return false; 283 } 284 } 285 286 if (value.type === RESOURCE_TYPE_STRING && !isValidDimensionString(resourceManager.getStringSync(value.id))) { 287 return false; 288 } else { 289 return true; 290 } 291} 292 293@Component 294export struct ChipComponent { 295 private theme: ChipTheme = { 296 prefixIcon: { 297 normalSize: { 298 width: $r('sys.float.chip_normal_icon_size'), 299 height: $r('sys.float.chip_normal_icon_size') 300 }, 301 smallSize: { 302 width: $r('sys.float.chip_small_icon_size'), 303 height: $r('sys.float.chip_small_icon_size') 304 }, 305 fillColor: $r('sys.color.chip_usually_icon_color'), 306 activatedFillColor: $r('sys.color.chip_active_icon_color'), 307 focusFillColor: $r('sys.color.chip_icon_focus_fill'), 308 focusActivatedColor: $r('sys.color.chip_icon_activated_focus_color'), 309 }, 310 label: { 311 normalFontSize: $r('sys.float.chip_normal_font_size'), 312 smallFontSize: $r('sys.float.chip_small_font_size'), 313 focusFontColor: $r('sys.color.chip_focus_text'), 314 focusActiveFontColor: $r('sys.color.chip_activated_focus_font_color'), 315 fontColor: $r('sys.color.chip_font_color'), 316 activatedFontColor: $r('sys.color.chip_activated_fontcolor'), 317 fontFamily: "HarmonyOS Sans", 318 fontWeight: $r('sys.float.chip_text_font_weight'), 319 normalMargin: { 320 left: 6, 321 right: 6, 322 top: 0, 323 bottom: 0 324 }, 325 smallMargin: { 326 left: 4, 327 right: 4, 328 top: 0, 329 bottom: 0 330 }, 331 defaultFontSize: 14, 332 localizedNormalMargin: { 333 start: LengthMetrics.resource($r('sys.float.chip_normal_text_margin')), 334 end: LengthMetrics.resource($r('sys.float.chip_normal_text_margin')), 335 top: LengthMetrics.vp(0), 336 bottom: LengthMetrics.vp(0) 337 }, 338 localizedSmallMargin: { 339 start: LengthMetrics.resource($r('sys.float.chip_small_text_margin')), 340 end: LengthMetrics.resource($r('sys.float.chip_small_text_margin')), 341 top: LengthMetrics.vp(0), 342 bottom: LengthMetrics.vp(0), 343 } 344 }, 345 suffixIcon: { 346 normalSize: { 347 width: $r('sys.float.chip_normal_icon_size'), 348 height: $r('sys.float.chip_normal_icon_size') 349 }, 350 smallSize: { 351 width: $r('sys.float.chip_small_icon_size'), 352 height: $r('sys.float.chip_small_icon_size') 353 }, 354 fillColor: $r('sys.color.chip_usually_icon_color'), 355 activatedFillColor: $r('sys.color.chip_active_icon_color'), 356 focusFillColor: $r('sys.color.chip_icon_focus_fill'), 357 focusActivatedColor: $r('sys.color.chip_icon_activated_focus_color'), 358 defaultDeleteIcon: $r('sys.media.ohos_ic_public_cancel', 16, 16), 359 focusable: false, 360 isShowMargin: $r('sys.float.chip_show_close_icon_margin'), 361 }, 362 defaultSymbol: { 363 normalFontColor: [$r('sys.color.ohos_id_color_secondary')], 364 activatedFontColor: [$r('sys.color.ohos_id_color_text_primary_contrary')], 365 smallSymbolFontSize: LengthMetrics.resource($r('sys.float.chip_normal_icon_size')).value as Length, 366 normalSymbolFontSize: LengthMetrics.resource($r('sys.float.chip_small_icon_size')).value as Length, 367 defaultEffect: -1, 368 }, 369 chipNode: { 370 suitAgeScale: 1.75, 371 minLabelWidth: 12, 372 normalHeight: $r('sys.float.chip_normal_height'), 373 smallHeight: $r('sys.float.chip_small_height'), 374 enabled: true, 375 activated: false, 376 backgroundColor: $r('sys.color.chip_background_color'), 377 activatedBackgroundColor: $r('sys.color.chip_container_activated_color'), 378 focusOutlineColor: $r('sys.color.ohos_id_color_focused_outline'), 379 focusOutlineMargin: 2, 380 borderColor: $r('sys.color.chip_border_color'), 381 defaultBorderWidth: $r('sys.float.chip_border_width'), 382 activatedBorderColor: $r('sys.color.chip_activated_border_color'), 383 normalBorderRadius: $r('sys.float.chip_border_radius_normal'), 384 smallBorderRadius: $r('sys.float.chip_border_radius_small'), 385 borderWidth: 2, 386 focusBtnScaleX: $r('sys.float.chip_focused_btn_scale'), 387 focusBtnScaleY: $r('sys.float.chip_focused_btn_scale'), 388 localizedNormalPadding: { 389 start: LengthMetrics.resource($r('sys.float.chip_normal_text_padding')), 390 end: LengthMetrics.resource($r('sys.float.chip_normal_text_padding')), 391 top: LengthMetrics.vp(4), 392 bottom: LengthMetrics.vp(4) 393 }, 394 localizedSmallPadding: { 395 start: LengthMetrics.resource($r('sys.float.chip_small_text_padding')), 396 end: LengthMetrics.resource($r('sys.float.chip_small_text_padding')), 397 top: LengthMetrics.vp(4), 398 bottom: LengthMetrics.vp(4) 399 }, 400 hoverBlendColor: $r('sys.color.chip_hover_color'), 401 pressedBlendColor: $r('sys.color.chip_press_color'), 402 focusBgColor: $r('sys.color.chip_focus_color'), 403 focusActivatedBgColor: $r('sys.color.chip_container_activated_focus_color'), 404 opacity: { normal: 1, hover: 0.95, pressed: 0.9 }, 405 normalShadowStyle: $r('sys.float.chip_normal_shadow_style'), 406 smallShadowStyle: $r('sys.float.chip_small_shadow_style'), 407 breakPointConstraintWidth: { 408 breakPointMinWidth: 128, 409 breakPointSmMaxWidth: 156, 410 breakPointMdMaxWidth: 280, 411 breakPointLgMaxWidth: 400 412 } 413 } 414 }; 415 @Prop chipSize: ChipSize | SizeOptions = ChipSize.NORMAL 416 @Prop allowClose: boolean = true 417 @Prop closeOptions?: CloseOptions 418 @Prop chipDirection: Direction = Direction.Auto 419 @Prop prefixIcon: PrefixIconOptions = { src: "" } 420 @Prop prefixSymbol: ChipSymbolGlyphOptions 421 @Prop label: LabelOptions = { text: "" } 422 @Prop suffixIcon: SuffixIconOptions = { src: "" } 423 @Prop suffixSymbol?: ChipSymbolGlyphOptions 424 @Prop suffixSymbolOptions?: ChipSuffixSymbolGlyphOptions 425 @Prop chipNodeBackgroundColor: ResourceColor = this.theme.chipNode.backgroundColor 426 @Prop chipNodeActivatedBackgroundColor: ResourceColor = this.theme.chipNode.activatedBackgroundColor 427 @State isHovering: boolean = false; 428 @Prop chipNodeRadius: Dimension | undefined = void (0) 429 @Prop chipEnabled: boolean = true 430 @Prop chipActivated?: boolean 431 @Prop chipAccessibilitySelectedType?: AccessibilitySelectedType 432 @Prop chipAccessibilityDescription?: ResourceStr 433 @Prop chipAccessibilityLevel?: string 434 @State isHover: boolean = false 435 @State chipScale: ScaleOptions = { x: 1, y: 1 } 436 @State chipOpacity: number = 1 437 @State chipBlendColor: ResourceColor = Color.Transparent 438 @State deleteChip: boolean = false 439 @State chipNodeOnFocus: boolean = false 440 @State useDefaultSuffixIcon: boolean = false 441 private chipNodeSize: SizeOptions = {} 442 private onClose: () => void = noop 443 private onClicked: () => void = noop 444 @State suffixIconOnFocus: boolean = false 445 @State chipBreakPoints: BreakPointsType = BreakPointsType.SM 446 private smListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(0vp<width) and (width<600vp)') 447 private mdListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(600vp<=width) and (width<840vp)') 448 private lgListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(840vp<=width)') 449 private isSuffixIconFocusStyleCustomized: boolean = 450 this.resourceToNumber(this.theme.suffixIcon.isShowMargin, 0) !== 0; 451 @State private isShowPressedBackGroundColor: boolean = false 452 @State fontSizeScale: number | undefined = 0 453 @State fontWeightScale: number | undefined = 0 454 private callbacks: EnvironmentCallback = { 455 onConfigurationUpdated: (configuration) => { 456 this.fontSizeScale = configuration.fontSizeScale; 457 this.fontWeightScale = configuration.fontWeightScale; 458 }, onMemoryLevel() { 459 } 460 } 461 private callbackId: number | undefined = undefined 462 @State prefixSymbolWidth: Length | undefined = 463 this.toVp(componentUtils.getRectangleById('PrefixSymbolGlyph')?.size?.width); 464 @State suffixSymbolWidth: Length | undefined = 465 this.toVp(componentUtils.getRectangleById('SuffixSymbolGlyph')?.size?.width); 466 @State allowCloseSymbolWidth: Length | undefined = 467 this.toVp(componentUtils.getRectangleById('AllowCloseSymbolGlyph')?.size?.width); 468 @State symbolEffect: SymbolEffect = new SymbolEffect(); 469 470 private isChipSizeEnum(): boolean { 471 return typeof (this.chipSize) === 'string' 472 } 473 474 private isShowCloseIconMargin(): boolean { 475 return this.resourceToNumber(this.theme.suffixIcon.isShowMargin, 0) !== 0 && this.allowClose; 476 } 477 478 private getLabelFontSize(): Dimension { 479 if (this.label?.fontSize !== void (0) && this.toVp(this.label.fontSize) >= 0) { 480 return this.label.fontSize 481 } else { 482 if (this.isChipSizeEnum() && this.chipSize === ChipSize.SMALL) { 483 try { 484 resourceManager.getSystemResourceManager() 485 .getNumberByName((((this.theme.label.smallFontSize as Resource).params as string[])[0]).split('.')[2]) 486 return this.theme.label.smallFontSize 487 } catch (error) { 488 return this.theme.label.defaultFontSize 489 } 490 } else { 491 try { 492 resourceManager.getSystemResourceManager() 493 .getNumberByName((((this.theme.label.normalFontSize as Resource).params as string[])[0]).split('.')[2]) 494 return this.theme.label.normalFontSize 495 } catch (error) { 496 return this.theme.label.defaultFontSize 497 } 498 } 499 } 500 } 501 502 private defaultSymbolFontsize(): Length { 503 return this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ? this.theme.defaultSymbol.smallSymbolFontSize : 504 this.theme.defaultSymbol.normalSymbolFontSize; 505 } 506 507 private resourceToVp(resource: Resource): number { 508 let metrics = LengthMetrics.resource(resource); 509 return this.lengthMetricsToVp(metrics); 510 } 511 512 private getActiveFontColor(): ResourceColor { 513 return this.chipNodeOnFocus ? this.theme.label.focusActiveFontColor : this.theme.label.activatedFontColor; 514 } 515 516 private getFontColor(): ResourceColor { 517 return this.chipNodeOnFocus ? this.theme.label.focusFontColor : this.theme.label.fontColor; 518 } 519 520 private getLabelFontColor(): ResourceColor { 521 if (this.getChipActive()) { 522 return this.label?.activatedFontColor ?? this.getActiveFontColor(); 523 } 524 return this.label?.fontColor ?? this.getFontColor(); 525 } 526 527 private getLabelFontFamily(): string { 528 return this.label?.fontFamily ?? this.theme.label.fontFamily 529 } 530 531 private getLabelFontWeight(): FontWeight { 532 if (this.getChipActive()) { 533 return FontWeight.Medium; 534 } 535 return this.resourceToNumber(this.theme.label.fontWeight, FontWeight.Regular) as FontWeight; 536 } 537 538 private lengthMetricsToVp(lengthMetrics?: LengthMetrics): number { 539 let defaultValue: number = 0; 540 if (lengthMetrics) { 541 switch (lengthMetrics.unit) { 542 case LengthUnit.PX: 543 return px2vp(lengthMetrics.value) 544 case LengthUnit.VP: 545 return lengthMetrics.value 546 case LengthUnit.FP: 547 return px2vp(fp2px(lengthMetrics.value)) 548 case LengthUnit.PERCENT: 549 return Number.NEGATIVE_INFINITY 550 case LengthUnit.LPX: 551 return px2vp(lpx2px(lengthMetrics.value)) 552 } 553 } 554 return defaultValue; 555 } 556 557 private toVp(value: Dimension | Length | undefined): number { 558 if (value === void (0)) { 559 return Number.NEGATIVE_INFINITY 560 } 561 switch (typeof (value)) { 562 case 'number': 563 return value as number 564 case 'object': 565 try { 566 let returnValue = this.lengthMetricsToVp(LengthMetrics.resource(value)); 567 if (returnValue === 0 && 568 !isValidResource(getContext(this), value)) { 569 return Number.NEGATIVE_INFINITY; 570 } 571 return returnValue; 572 } catch (error) { 573 return Number.NEGATIVE_INFINITY 574 } 575 case 'string': 576 let regex: RegExp = new RegExp("(-?\\d+(?:\\.\\d+)?)_?(fp|vp|px|lpx|%)?$", "i"); 577 let matches: RegExpMatchArray | null = value.match(regex); 578 if (!matches) { 579 return Number.NEGATIVE_INFINITY 580 } 581 let length: number = Number(matches?.[1] ?? 0); 582 let unit: string = matches?.[2] ?? 'vp' 583 switch (unit.toLowerCase()) { 584 case 'px': 585 length = px2vp(length) 586 break 587 case 'fp': 588 length = px2vp(fp2px(length)) 589 break 590 case 'lpx': 591 length = px2vp(lpx2px(length)) 592 break 593 case '%': 594 length = Number.NEGATIVE_INFINITY 595 break 596 case 'vp': 597 break 598 default: 599 break 600 } 601 return length 602 default: 603 return Number.NEGATIVE_INFINITY 604 } 605 } 606 607 private getChipNodeBorderWidth(): number { 608 return this.resourceToVp(this.theme.chipNode.defaultBorderWidth); 609 } 610 611 private getChipNodeBorderColor(): ResourceColor { 612 let themeChipNode = this.theme.chipNode; 613 return this.getChipActive() ? themeChipNode.activatedBorderColor : themeChipNode.borderColor; 614 } 615 616 private getLabelMargin(): Margin { 617 let labelMargin: Margin = { left: 0, right: 0 } 618 if (this.label?.labelMargin?.left !== void (0) && this.toVp(this.label.labelMargin.left) >= 0) { 619 labelMargin.left = this.label?.labelMargin?.left 620 } else if ((this.prefixSymbol?.normal || this.prefixSymbol?.activated) || this.prefixIcon?.src) { 621 if (this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL) { 622 labelMargin.left = this.theme.label.smallMargin.left 623 } else { 624 labelMargin.left = this.theme.label.normalMargin.left 625 } 626 } 627 if (this.label?.labelMargin?.right !== void (0) && this.toVp(this.label.labelMargin.right) >= 0) { 628 labelMargin.right = this.label?.labelMargin?.right 629 } else if ((this.suffixSymbol?.normal || this.suffixSymbol?.activated) || 630 this.suffixIcon?.src || this.useDefaultSuffixIcon) { 631 if (this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL) { 632 labelMargin.right = this.theme.label.smallMargin.right 633 } else { 634 labelMargin.right = this.theme.label.normalMargin.right 635 } 636 } 637 return labelMargin 638 } 639 640 private getLocalizedLabelMargin(): LocalizedMargin { 641 let localizedLabelMargin: LocalizedMargin = { start: LengthMetrics.vp(0), end: LengthMetrics.vp(0) } 642 if (this.label?.localizedLabelMargin?.start?.value !== void (0) && 643 this.lengthMetricsToVp(this.label.localizedLabelMargin.start) >= 0) { 644 localizedLabelMargin.start = this.label?.localizedLabelMargin?.start 645 } else if ((this.prefixSymbol?.normal || this.prefixSymbol?.activated) || this.prefixIcon?.src) { 646 if (this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL) { 647 localizedLabelMargin.start = this.theme.label.localizedSmallMargin.start 648 } else { 649 localizedLabelMargin.start = this.theme.label.localizedNormalMargin.start 650 } 651 } 652 if (this.label?.localizedLabelMargin?.end?.value !== void (0) && 653 this.lengthMetricsToVp(this.label.localizedLabelMargin.end) >= 0) { 654 localizedLabelMargin.end = this.label?.localizedLabelMargin?.end 655 } else if ((this.suffixSymbol?.normal || this.suffixSymbol?.activated) || 656 this.suffixIcon?.src || this.useDefaultSuffixIcon || this.isShowCloseIconMargin()) { 657 if (this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL) { 658 localizedLabelMargin.end = this.theme.label.localizedSmallMargin.end 659 } else { 660 localizedLabelMargin.end = this.theme.label.localizedNormalMargin.end 661 } 662 } 663 return localizedLabelMargin 664 } 665 666 private getLabelStartEndVp(): LocalizedMargin { 667 let labelMargin: LocalizedMargin = this.getLocalizedLabelMargin() 668 if (this.label && (this.label.labelMargin !== void (0)) && (this.label.localizedLabelMargin === void (0))) { 669 let margin: Margin = this.getLabelMargin() 670 return { 671 start: LengthMetrics.vp(this.toVp(margin.left)), 672 end: LengthMetrics.vp(this.toVp(margin.right)) 673 } 674 } 675 return { 676 start: LengthMetrics.vp(this.lengthMetricsToVp(labelMargin.start)), 677 end: LengthMetrics.vp(this.lengthMetricsToVp(labelMargin.end)) 678 } 679 } 680 681 private getActualLabelMargin(): Margin | LocalizedMargin { 682 let localizedLabelMargin: LocalizedMargin = this.getLocalizedLabelMargin() 683 if (this.label && this.label.localizedLabelMargin !== void (0)) { 684 return localizedLabelMargin 685 } 686 if (this.label && this.label.labelMargin !== void (0)) { 687 return this.getLabelMargin() 688 } 689 return localizedLabelMargin 690 } 691 692 private getSuffixIconSize(): SizeOptions { 693 let suffixIconSize: SizeOptions = { width: 0, height: 0 } 694 if (this.suffixIcon?.size?.width !== void (0) && this.toVp(this.suffixIcon?.size?.width) >= 0) { 695 suffixIconSize.width = this.suffixIcon?.size?.width 696 } else { 697 if (this.getSuffixIconSrc()) { 698 suffixIconSize.width = this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ? 699 this.theme.suffixIcon.smallSize.width : this.theme.suffixIcon.normalSize.width; 700 } else { 701 suffixIconSize.width = 0 702 } 703 } 704 if (this.suffixIcon?.size?.height !== void (0) && this.toVp(this.suffixIcon?.size?.height) >= 0) { 705 suffixIconSize.height = this.suffixIcon?.size?.height 706 } else { 707 if (this.getSuffixIconSrc()) { 708 suffixIconSize.height = this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ? 709 this.theme.suffixIcon.smallSize.height : this.theme.suffixIcon.normalSize.height; 710 } else { 711 suffixIconSize.height = 0 712 } 713 } 714 return suffixIconSize 715 } 716 717 private getPrefixIconSize(): SizeOptions { 718 let prefixIconSize: SizeOptions = { width: 0, height: 0 } 719 if (this.prefixIcon?.size?.width !== void (0) && this.toVp(this.prefixIcon?.size?.width) >= 0) { 720 prefixIconSize.width = this.prefixIcon?.size?.width 721 } else { 722 if (this.prefixIcon?.src) { 723 prefixIconSize.width = 724 this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ? this.theme.prefixIcon.smallSize.width : 725 this.theme.prefixIcon.normalSize.width; 726 } else { 727 prefixIconSize.width = 0 728 } 729 } 730 if (this.prefixIcon?.size?.height !== void (0) && this.toVp(this.prefixIcon?.size?.height) >= 0) { 731 prefixIconSize.height = this.prefixIcon?.size?.height 732 } else { 733 if (this.prefixIcon?.src) { 734 prefixIconSize.height = 735 this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ? this.theme.prefixIcon.smallSize.height : 736 this.theme.prefixIcon.normalSize.height; 737 } else { 738 prefixIconSize.height = 0 739 } 740 } 741 return prefixIconSize 742 } 743 744 private getDefaultActiveIconColor(iconType: string): ResourceColor { 745 if (iconType === IconType.PREFIX_ICON) { 746 return this.chipNodeOnFocus ? this.theme.prefixIcon.focusActivatedColor : 747 this.theme.prefixIcon.activatedFillColor; 748 } else { 749 return this.chipNodeOnFocus ? this.theme.suffixIcon.focusActivatedColor : 750 this.theme.suffixIcon.activatedFillColor; 751 } 752 } 753 754 private getDefaultFillIconColor(iconType: string): ResourceColor { 755 if (iconType === IconType.PREFIX_ICON) { 756 return this.chipNodeOnFocus ? this.theme.prefixIcon.focusFillColor : this.theme.prefixIcon.fillColor; 757 } else { 758 return this.chipNodeOnFocus ? this.theme.suffixIcon.focusFillColor : this.theme.suffixIcon.fillColor; 759 } 760 } 761 762 private getSymbolActiveColor(iconType?: string): Array<ResourceColor> { 763 if (iconType === IconType.PREFIX_SYMBOL) { 764 return this.getColorArray(this.prefixIcon?.activatedFillColor, 765 this.theme.prefixIcon.focusActivatedColor, this.theme.prefixIcon.activatedFillColor); 766 } else if (iconType === IconType.SUFFIX_SYMBOL) { 767 return this.getColorArray(this.suffixIcon?.activatedFillColor, 768 this.theme.suffixIcon.focusActivatedColor, this.theme.suffixIcon.activatedFillColor); 769 } else { 770 return this.theme.defaultSymbol.activatedFontColor; 771 } 772 } 773 774 private getSymbolFillColor(iconType?: string): Array<ResourceColor> { 775 if (iconType === IconType.PREFIX_SYMBOL) { 776 return this.getColorArray(this.prefixIcon?.fillColor, 777 this.theme.prefixIcon.focusFillColor, this.theme.prefixIcon.fillColor); 778 } else if (iconType === IconType.SUFFIX_SYMBOL) { 779 return this.getColorArray(this.suffixIcon?.fillColor, 780 this.theme.suffixIcon.focusFillColor, this.theme.suffixIcon.fillColor); 781 } else { 782 return this.theme.defaultSymbol.normalFontColor; 783 } 784 } 785 786 private getColorArray(userDefined: ResourceColor | undefined, focusColor: ResourceColor, 787 normalColor: ResourceColor): Array<ResourceColor> { 788 if (userDefined) { 789 return [userDefined]; 790 } 791 return this.chipNodeOnFocus ? [focusColor] : [normalColor]; 792 } 793 794 private getPrefixIconFilledColor(): ResourceColor { 795 if (this.getChipActive()) { 796 return this.prefixIcon?.activatedFillColor ?? this.getDefaultActiveIconColor(IconType.PREFIX_ICON); 797 } 798 return this.prefixIcon?.fillColor ?? this.getDefaultFillIconColor(IconType.PREFIX_ICON); 799 } 800 801 private getSuffixIconFilledColor(): ResourceColor { 802 if (this.getChipActive()) { 803 return this.suffixIcon?.activatedFillColor ?? this.getDefaultActiveIconColor(IconType.SUFFIX_ICON); 804 } 805 return this.suffixIcon?.fillColor ?? this.getDefaultFillIconColor(IconType.SUFFIX_ICON); 806 } 807 808 private getDefaultSymbolColor(iconType?: string): Array<ResourceColor> { 809 if (this.getChipActive()) { 810 return this.getSymbolActiveColor(iconType); 811 } 812 return this.getSymbolFillColor(iconType); 813 } 814 815 private getPrefixSymbolModifier(): SymbolGlyphModifier | undefined { 816 if (this.getChipActive()) { 817 return this.prefixSymbol?.activated 818 } 819 return this.prefixSymbol?.normal 820 } 821 822 private getSuffixSymbolModifier(): SymbolGlyphModifier | undefined { 823 if (this.getChipActive()) { 824 return this.suffixSymbol?.activated 825 } 826 return this.suffixSymbol?.normal 827 } 828 829 private getSuffixIconFocusable(): boolean { 830 return !this.isSuffixIconFocusStyleCustomized && ((this.useDefaultSuffixIcon && (this.allowClose ?? true)) || 831 this.suffixIcon?.action !== void (0)); 832 } 833 834 private getChipNodePadding(): LocalizedPadding { 835 return (this.isChipSizeEnum() && this.chipSize === ChipSize.SMALL) ? this.theme.chipNode.localizedSmallPadding : 836 this.theme.chipNode.localizedNormalPadding 837 } 838 839 private getChipNodeRadius(): Dimension { 840 if (this.chipNodeRadius !== void (0) && this.toVp(this.chipNodeRadius) >= 0) { 841 return this.chipNodeRadius as Dimension; 842 } else { 843 return ((this.isChipSizeEnum() && this.chipSize === ChipSize.SMALL) ? 844 this.theme.chipNode.smallBorderRadius : this.theme.chipNode.normalBorderRadius); 845 } 846 } 847 848 private getChipNodeBackGroundColor(): ResourceColor { 849 let currentColor: ResourceColor; 850 let themeChipNode = this.theme.chipNode; 851 if (this.getChipActive()) { 852 currentColor = this.chipNodeOnFocus && !this.isSetActiveChipBgColor() ? themeChipNode.focusActivatedBgColor : 853 this.chipNodeActivatedBackgroundColor ?? this.theme.chipNode.activatedBackgroundColor 854 } else { 855 currentColor = this.chipNodeOnFocus && !this.isSetNormalChipBgColor() ? themeChipNode.focusBgColor : 856 this.chipNodeBackgroundColor ?? this.theme.chipNode.backgroundColor 857 } 858 let sourceColor: ColorMetrics; 859 try { 860 sourceColor = ColorMetrics.resourceColor(currentColor); 861 } catch (err) { 862 hilog.error(0x3900, 'Ace', `Chip resourceColor, error: ${err.toString()}`); 863 sourceColor = ColorMetrics.resourceColor(Color.Transparent); 864 } 865 if (!this.isShowPressedBackGroundColor) { 866 return sourceColor.color 867 } 868 return sourceColor 869 .blendColor(ColorMetrics.resourceColor("#19000000")) 870 .color 871 } 872 873 private getChipNodeHeight(): Length { 874 if (this.isChipSizeEnum()) { 875 return this.chipSize === ChipSize.SMALL ? this.theme.chipNode.smallHeight : this.theme.chipNode.normalHeight 876 } else { 877 this.chipNodeSize = this.chipSize as SizeOptions 878 return (this.chipNodeSize?.height !== void (0) && this.toVp(this.chipNodeSize?.height) >= 0) ? 879 this.toVp(this.chipNodeSize?.height) : this.theme.chipNode.normalHeight 880 } 881 } 882 883 private getLabelWidth(): number { 884 return px2vp(measure.measureText({ 885 textContent: this.label?.text ?? "", 886 fontSize: this.getLabelFontSize(), 887 fontFamily: this.label?.fontFamily ?? this.theme.label.fontFamily, 888 fontWeight: this.getLabelFontWeight(), 889 maxLines: 1, 890 overflow: TextOverflow.Ellipsis, 891 textAlign: TextAlign.Center 892 })) 893 } 894 895 private getCalculateChipNodeWidth(): number { 896 let calWidth: number = 0 897 let startEndVp: LocalizedMargin = this.getLabelStartEndVp() 898 calWidth += this.getChipNodeBorderWidth() * 2; 899 calWidth += this.getChipNodePadding().start?.value ?? 0; 900 calWidth += this.toVp(this.getPrefixChipWidth()) 901 calWidth += this.toVp(startEndVp.start?.value ?? 0) 902 calWidth += this.getLabelWidth() 903 calWidth += this.toVp(startEndVp.end?.value ?? 0) 904 calWidth += this.toVp(this.getSuffixChipWidth()) 905 calWidth += this.getChipNodePadding().end?.value ?? 0 906 return calWidth 907 } 908 909 private getPrefixChipWidth(): Length | undefined { 910 if (this.prefixSymbol?.normal || this.prefixSymbol?.activated) { 911 return this.prefixSymbolWidth 912 } else if (this.prefixIcon?.src) { 913 return this.getPrefixIconSize().width 914 } else { 915 return 0 916 } 917 } 918 919 private getSuffixChipWidth(): Length | undefined { 920 if (this.suffixSymbol?.normal || this.suffixSymbol?.activated) { 921 return this.suffixSymbolWidth 922 } else if (this.suffixIcon?.src) { 923 return this.getSuffixIconSize().width 924 } else if (!this.suffixIcon?.src && (this.allowClose ?? true)) { 925 return this.allowCloseSymbolWidth 926 } else { 927 return 0 928 } 929 } 930 931 private getReserveChipNodeWidth(): number { 932 return this.getCalculateChipNodeWidth() - this.getLabelWidth() + (this.theme.chipNode.minLabelWidth as number) 933 } 934 935 private getChipEnable(): boolean { 936 return this.chipEnabled || this.chipEnabled === void (0) 937 } 938 939 private getChipActive(): boolean { 940 if (typeof this.chipActivated === 'undefined') { 941 return false 942 } 943 return this.chipActivated 944 } 945 946 private getChipNodeOpacity(): number { 947 return this.chipOpacity 948 } 949 950 private handleTouch(event: TouchEvent) { 951 if (!this.getChipEnable()) { 952 return 953 } 954 if (this.isHover) { 955 if (event.type === TouchType.Down || event.type === TouchType.Move) { 956 this.isShowPressedBackGroundColor = true 957 } else if (event.type === TouchType.Up) { 958 this.isShowPressedBackGroundColor = false 959 } else { 960 this.isShowPressedBackGroundColor = false 961 } 962 } else { 963 if (event.type === TouchType.Down || event.type === TouchType.Move) { 964 this.isShowPressedBackGroundColor = true 965 } else if (event.type === TouchType.Up) { 966 this.isShowPressedBackGroundColor = false 967 } else { 968 this.isShowPressedBackGroundColor = false 969 } 970 } 971 } 972 973 private hoverAnimate(isHover: boolean) { 974 if (!this.getChipEnable()) { 975 return 976 } 977 this.isHover = isHover 978 if (this.isHover) { 979 this.isShowPressedBackGroundColor = true 980 } else { 981 this.isShowPressedBackGroundColor = false 982 } 983 } 984 985 private deleteChipNodeAnimate() { 986 animateTo({ duration: 150, curve: Curve.Sharp }, () => { 987 this.chipOpacity = 0 988 this.chipBlendColor = Color.Transparent 989 }) 990 animateTo({ 991 duration: 150, curve: Curve.FastOutLinearIn, onFinish: () => { 992 this.deleteChip = true 993 } 994 }, 995 () => { 996 this.chipScale = { x: 0.85, y: 0.85 } 997 }) 998 } 999 1000 private getSuffixIconSrc(): ResourceStr | undefined { 1001 this.useDefaultSuffixIcon = !this.suffixIcon?.src && (this.allowClose ?? true) 1002 return this.useDefaultSuffixIcon ? this.theme.suffixIcon.defaultDeleteIcon : (this.suffixIcon?.src ?? void (0)) 1003 } 1004 1005 private getChipNodeWidth(): Length { 1006 if (!this.isChipSizeEnum()) { 1007 this.chipNodeSize = this.chipSize as SizeOptions 1008 if (this.chipNodeSize?.width !== void (0) && this.toVp(this.chipNodeSize.width) >= 0) { 1009 return this.toVp(this.chipNodeSize.width) 1010 } 1011 } 1012 let constraintWidth: ConstraintSizeOptions = this.getChipConstraintWidth() 1013 return Math.min(Math.max(this.getCalculateChipNodeWidth(), 1014 constraintWidth.minWidth as number), constraintWidth.maxWidth as number); 1015 } 1016 1017 private getFocusOverlaySize(): SizeOptions { 1018 return { 1019 width: Math.max(this.getChipNodeWidth() as number, this.getChipConstraintWidth().minWidth as number) + 8, 1020 height: this.getChipNodeHeight() as number + 8 1021 } 1022 } 1023 1024 private getChipConstraintWidth(): ConstraintSizeOptions { 1025 let calcMinWidth: number = this.getReserveChipNodeWidth() 1026 1027 let constraintWidth: number = this.getCalculateChipNodeWidth() 1028 let constraintSize: ConstraintSizeOptions 1029 switch (this.chipBreakPoints) { 1030 case BreakPointsType.SM: 1031 constraintSize = { 1032 minWidth: calcMinWidth, 1033 maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointSmMaxWidth) 1034 } 1035 break 1036 case BreakPointsType.MD: 1037 constraintSize = { 1038 minWidth: Math.max(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMinWidth), 1039 maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMdMaxWidth) 1040 } 1041 break 1042 case BreakPointsType.LG: 1043 constraintSize = { 1044 minWidth: Math.max(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMinWidth), 1045 maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointLgMaxWidth) 1046 } 1047 break 1048 default: 1049 constraintSize = { minWidth: calcMinWidth, maxWidth: constraintWidth } 1050 break 1051 } 1052 constraintSize.minWidth = Math.min(Math.max(this.getCalculateChipNodeWidth(), 1053 constraintSize.minWidth as number), constraintSize.maxWidth as number) 1054 constraintSize.minHeight = this.getChipNodeHeight() 1055 if (!this.isChipSizeEnum() && this.chipNodeSize?.height !== void (0) && this.toVp(this.chipNodeSize?.height) >= 0) { 1056 constraintSize.maxHeight = this.toVp(this.chipNodeSize.height) 1057 constraintSize.minHeight = this.toVp(this.chipNodeSize.height) 1058 } 1059 if (!this.isChipSizeEnum() && this.chipNodeSize?.width !== void (0) && this.toVp(this.chipNodeSize?.width) >= 0) { 1060 constraintSize.minWidth = this.toVp(this.chipNodeSize.width) 1061 constraintSize.maxWidth = this.toVp(this.chipNodeSize.width) 1062 } else if (this.toVp(this.fontSizeScale) >= this.theme.chipNode.suitAgeScale) { 1063 constraintSize.minWidth = void (0) 1064 constraintSize.maxWidth = void (0) 1065 } 1066 return constraintSize 1067 } 1068 1069 @Builder 1070 focusOverlay() { 1071 Stack() { 1072 if (this.chipNodeOnFocus && !this.suffixIconOnFocus) { 1073 Stack() 1074 .direction(this.chipDirection) 1075 .borderRadius(this.toVp(this.getChipNodeRadius()) + 4) 1076 .size(this.getFocusOverlaySize()) 1077 .borderColor(this.theme.chipNode.focusOutlineColor) 1078 .borderWidth(this.theme.chipNode.borderWidth) 1079 } 1080 } 1081 .direction(this.chipDirection) 1082 .size({ width: 1, height: 1 }) 1083 .align(Alignment.Center) 1084 } 1085 1086 @Styles 1087 suffixIconFocusStyles() { 1088 .borderColor(this.theme.chipNode.focusOutlineColor) 1089 .borderWidth(this.getSuffixIconFocusable() ? this.theme.chipNode.borderWidth : 0) 1090 } 1091 1092 @Styles 1093 suffixIconNormalStyles() { 1094 .borderColor(Color.Transparent) 1095 .borderWidth(0) 1096 } 1097 1098 aboutToAppear() { 1099 let uiContent: UIContext = this.getUIContext(); 1100 this.fontSizeScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 1101 1102 this.smListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => { 1103 if (mediaQueryResult.matches) { 1104 this.chipBreakPoints = BreakPointsType.SM 1105 } 1106 }) 1107 this.mdListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => { 1108 if (mediaQueryResult.matches) { 1109 this.chipBreakPoints = BreakPointsType.MD 1110 } 1111 }) 1112 this.lgListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => { 1113 if (mediaQueryResult.matches) { 1114 this.chipBreakPoints = BreakPointsType.LG 1115 } 1116 }) 1117 this.callbackId = this.getUIContext() 1118 .getHostContext() 1119 ?.getApplicationContext() 1120 ?.on('environment', this.callbacks); 1121 } 1122 1123 private getVisibility(): Visibility { 1124 if (this.toVp(this.getChipNodeHeight()) > 0) { 1125 return Visibility.Visible 1126 } else { 1127 return Visibility.None 1128 } 1129 } 1130 1131 private isSetActiveChipBgColor(): boolean { 1132 if (!this.chipNodeActivatedBackgroundColor) { 1133 return false; 1134 } 1135 try { 1136 return ColorMetrics.resourceColor(this.chipNodeActivatedBackgroundColor).color !== 1137 ColorMetrics.resourceColor(this.theme.chipNode.activatedBackgroundColor).color; 1138 } catch (error) { 1139 console.error(`[Chip] failed to get resourceColor`); 1140 return false; 1141 } 1142 } 1143 1144 private isSetNormalChipBgColor(): boolean { 1145 if (!this.chipNodeBackgroundColor) { 1146 return false; 1147 } 1148 try { 1149 return ColorMetrics.resourceColor(this.chipNodeBackgroundColor).color !== 1150 ColorMetrics.resourceColor(this.theme.chipNode.backgroundColor).color; 1151 } catch (error) { 1152 console.error(`[Chip] failed to get resourceColor`); 1153 return false; 1154 } 1155 } 1156 1157 private getShadowStyles(): ShadowStyle | undefined { 1158 if (!this.chipNodeOnFocus) { 1159 return undefined; 1160 } 1161 return this.resourceToNumber(this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ? 1162 this.theme.chipNode.smallShadowStyle : 1163 this.theme.chipNode.normalShadowStyle, -1) as ShadowStyle; 1164 } 1165 1166 aboutToDisappear() { 1167 this.smListener.off("change") 1168 this.mdListener.off("change") 1169 this.lgListener.off("change") 1170 if (this.callbackId) { 1171 this.getUIContext() 1172 ?.getHostContext() 1173 ?.getApplicationContext() 1174 ?.off('environment', this.callbackId); 1175 this.callbackId = void (0) 1176 } 1177 } 1178 1179 @Builder 1180 chipBuilder() { 1181 Button() { 1182 Row() { 1183 if (this.prefixSymbol?.normal || this.prefixSymbol?.activated) { 1184 SymbolGlyph() 1185 .fontSize(this.defaultSymbolFontsize()) 1186 .fontColor(this.getDefaultSymbolColor(IconType.PREFIX_SYMBOL)) 1187 .attributeModifier(this.getPrefixSymbolModifier()) 1188 .effectStrategy(SymbolEffectStrategy.NONE) 1189 .symbolEffect(this.symbolEffect, false) 1190 .symbolEffect(this.symbolEffect, this.theme.defaultSymbol.defaultEffect) 1191 .onSizeChange((oldValue, newValue) => { 1192 this.prefixSymbolWidth = newValue?.width 1193 }) 1194 .key('PrefixSymbolGlyph') 1195 } else if (this.prefixIcon?.src !== "") { 1196 Image(this.prefixIcon?.src) 1197 .direction(this.chipDirection) 1198 .opacity(this.getChipNodeOpacity()) 1199 .size(this.getPrefixIconSize()) 1200 .fillColor(this.getPrefixIconFilledColor()) 1201 .enabled(this.getChipEnable()) 1202 .objectFit(ImageFit.Cover) 1203 .focusable(false) 1204 .flexShrink(0) 1205 .visibility(this.getVisibility()) 1206 .draggable(false) 1207 } 1208 1209 Text(this.label?.text ?? "") 1210 .direction(this.chipDirection) 1211 .opacity(this.getChipNodeOpacity()) 1212 .fontSize(this.getLabelFontSize()) 1213 .fontColor(this.getLabelFontColor()) 1214 .fontFamily(this.getLabelFontFamily()) 1215 .fontWeight(this.getLabelFontWeight()) 1216 .margin(this.getActualLabelMargin()) 1217 .enabled(this.getChipEnable()) 1218 .maxLines(1) 1219 .textOverflow({ overflow: TextOverflow.Ellipsis }) 1220 .flexShrink(1) 1221 .focusable(true) 1222 .textAlign(TextAlign.Center) 1223 .visibility(this.getVisibility()) 1224 .draggable(false) 1225 1226 if (this.suffixSymbol?.normal || this.suffixSymbol?.activated) { 1227 Button({ type: ButtonType.Normal }) { 1228 SymbolGlyph() 1229 .fontSize(this.defaultSymbolFontsize()) 1230 .fontColor(this.getDefaultSymbolColor(IconType.SUFFIX_SYMBOL)) 1231 .attributeModifier(this.getSuffixSymbolModifier()) 1232 .effectStrategy(SymbolEffectStrategy.NONE) 1233 .symbolEffect(this.symbolEffect, false) 1234 .symbolEffect(this.symbolEffect, this.theme.defaultSymbol.defaultEffect) 1235 .onSizeChange((oldValue, newValue) => { 1236 this.suffixSymbolWidth = newValue?.width 1237 }) 1238 .key('SuffixSymbolGlyph') 1239 } 1240 .onClick(this.getSuffixSymbolAction()) 1241 .accessibilityText(this.getSuffixSymbolAccessibilityText()) 1242 .accessibilityDescription(this.getSuffixSymbolAccessibilityDescription()) 1243 .accessibilityLevel(this.getSuffixSymbolAccessibilityLevel()) 1244 .backgroundColor(Color.Transparent) 1245 .borderRadius(0) 1246 .padding(0) 1247 .stateEffect(false) 1248 .focusable(!this.isSuffixIconFocusStyleCustomized) 1249 } else if (this.suffixIcon?.src !== "") { 1250 Button({ type: ButtonType.Normal }) { 1251 Image(this.getSuffixIconSrc()) 1252 .direction(this.chipDirection) 1253 .opacity(this.getChipNodeOpacity()) 1254 .size(this.getSuffixIconSize()) 1255 .fillColor(this.getSuffixIconFilledColor()) 1256 .enabled(this.getChipEnable()) 1257 .objectFit(ImageFit.Cover) 1258 .flexShrink(0) 1259 .visibility(this.getVisibility()) 1260 .draggable(false) 1261 .onFocus(() => { 1262 this.suffixIconOnFocus = true 1263 }) 1264 .onBlur(() => { 1265 this.suffixIconOnFocus = false 1266 }) 1267 } 1268 .backgroundColor(Color.Transparent) 1269 .borderRadius(0) 1270 .padding(0) 1271 .size(this.getSuffixIconSize()) 1272 .accessibilityText(this.getSuffixIconAccessibilityText()) 1273 .accessibilityDescription(this.getSuffixIconAccessibilityDescription()) 1274 .accessibilityLevel(this.getSuffixIconAccessibilityLevel()) 1275 .onClick(() => { 1276 if (!this.getChipEnable()) { 1277 return 1278 } 1279 if (this.suffixIcon?.action) { 1280 this.suffixIcon.action() 1281 return 1282 } 1283 if ((this.allowClose ?? true) && this.useDefaultSuffixIcon) { 1284 this.onClose() 1285 this.deleteChipNodeAnimate() 1286 return 1287 } 1288 this.onClicked() 1289 }) 1290 .focusable(this.getSuffixIconFocusable()) 1291 } else if (this.allowClose ?? true) { 1292 Button({ type: ButtonType.Normal }) { 1293 SymbolGlyph($r('sys.symbol.xmark')) 1294 .fontSize(this.defaultSymbolFontsize()) 1295 .fontColor(this.getDefaultSymbolColor(IconType.SUFFIX_SYMBOL)) 1296 .onSizeChange((oldValue, newValue) => { 1297 this.allowCloseSymbolWidth = newValue?.width 1298 }) 1299 .key('AllowCloseSymbolGlyph') 1300 } 1301 .backgroundColor(Color.Transparent) 1302 .borderRadius(0) 1303 .padding(0) 1304 .accessibilityText(this.getCloseIconAccessibilityText()) 1305 .accessibilityDescription(this.getCloseIconAccessibilityDescription()) 1306 .accessibilityLevel(this.getCloseIconAccessibilityLevel()) 1307 .onClick(() => { 1308 if (!this.getChipEnable()) { 1309 return 1310 } 1311 this.onClose() 1312 this.deleteChipNodeAnimate() 1313 }) 1314 .focusable(!this.isSuffixIconFocusStyleCustomized) 1315 } 1316 } 1317 .direction(this.chipDirection) 1318 .alignItems(VerticalAlign.Center) 1319 .justifyContent(FlexAlign.Center) 1320 .padding(this.getChipNodePadding()) 1321 .constraintSize(this.getChipConstraintWidth()) 1322 } 1323 .constraintSize(this.getChipConstraintWidth()) 1324 .direction(this.chipDirection) 1325 .type(ButtonType.Normal) 1326 .clip(false) 1327 .backgroundColor(this.getChipNodeBackGroundColor()) 1328 .borderRadius(this.getChipNodeRadius()) 1329 .borderWidth(this.getChipNodeBorderWidth()) 1330 .borderColor(this.getChipNodeBorderColor()) 1331 .enabled(this.getChipEnable()) 1332 .scale(this.chipScale) 1333 .focusable(true) 1334 .opacity(this.getChipNodeOpacity()) 1335 .shadow(this.getShadowStyles()) 1336 .padding(0) 1337 .accessibilityGroup(true) 1338 .accessibilityDescription(this.getAccessibilityDescription()) 1339 .accessibilityLevel(this.getAccessibilityLevel()) 1340 .accessibilityChecked(this.getAccessibilityChecked()) 1341 .accessibilitySelected(this.getAccessibilitySelected()) 1342 .onFocus(() => { 1343 this.chipNodeOnFocus = true 1344 if (this.isSuffixIconFocusStyleCustomized) { 1345 this.chipScale = { 1346 x: this.resourceToNumber(this.theme.chipNode.focusBtnScaleX, 1), 1347 y: this.resourceToNumber(this.theme.chipNode.focusBtnScaleY, 1), 1348 }; 1349 } 1350 }) 1351 .onBlur(() => { 1352 this.chipNodeOnFocus = false 1353 if (this.isSuffixIconFocusStyleCustomized) { 1354 this.chipScale = { 1355 x: 1, y: 1 1356 }; 1357 } 1358 }) 1359 .onTouch((event) => { 1360 this.handleTouch(event) 1361 }) 1362 .onHover((isHover: boolean) => { 1363 if (isHover) { 1364 this.isShowPressedBackGroundColor = true 1365 } else { 1366 if (!this.isShowPressedBackGroundColor && isHover) { 1367 this.isShowPressedBackGroundColor = true 1368 } else { 1369 this.isShowPressedBackGroundColor = false 1370 } 1371 } 1372 }) 1373 .onKeyEvent((event) => { 1374 if (!event || event.type === null || event.type !== KeyType.Down) { 1375 return; 1376 } 1377 let isDeleteChip = event.keyCode === KeyCode.KEYCODE_FORWARD_DEL && 1378 !this.suffixIconOnFocus; 1379 let isEnterDeleteChip = event.keyCode === KeyCode.KEYCODE_ENTER && this.allowClose !== false && 1380 !this.suffixIcon?.src && this.isSuffixIconFocusStyleCustomized; 1381 if (isDeleteChip || isEnterDeleteChip) { 1382 this.deleteChipNodeAnimate(); 1383 } 1384 }) 1385 .onClick(this.onClicked === noop ? undefined : this.onClicked.bind(this)) 1386 } 1387 1388 getSuffixSymbolAccessibilityLevel(): string { 1389 if (this.getChipActive()) { 1390 if (this.suffixSymbolOptions?.activatedAccessibility?.accessibilityLevel === 'no' || 1391 this.suffixSymbolOptions?.activatedAccessibility?.accessibilityLevel === 'no-hide-descendants') { 1392 return this.suffixSymbolOptions.activatedAccessibility.accessibilityLevel; 1393 } 1394 return this.suffixSymbolOptions?.action ? 'yes' : 'no'; 1395 } 1396 if (this.suffixSymbolOptions?.normalAccessibility?.accessibilityLevel === 'no' || 1397 this.suffixSymbolOptions?.normalAccessibility?.accessibilityLevel === 'no-hide-descendants') { 1398 return this.suffixSymbolOptions.normalAccessibility.accessibilityLevel; 1399 } 1400 return this.suffixSymbolOptions?.action ? 'yes' : 'no'; 1401 } 1402 1403 getSuffixSymbolAccessibilityDescription(): Resource | undefined { 1404 if (this.getChipActive()) { 1405 if (typeof this.suffixSymbolOptions?.activatedAccessibility?.accessibilityDescription !== 'undefined') { 1406 return this.suffixSymbolOptions.activatedAccessibility.accessibilityDescription as Resource; 1407 } 1408 return undefined; 1409 } 1410 if (typeof this.suffixSymbolOptions?.normalAccessibility?.accessibilityDescription !== 'undefined') { 1411 return this.suffixSymbolOptions.normalAccessibility.accessibilityDescription as Resource; 1412 } 1413 return undefined; 1414 } 1415 1416 getSuffixSymbolAccessibilityText(): Resource | undefined { 1417 if (this.getChipActive()) { 1418 if (typeof this.suffixSymbolOptions?.activatedAccessibility?.accessibilityText !== 'undefined') { 1419 return this.suffixSymbolOptions.activatedAccessibility.accessibilityText as Resource; 1420 } 1421 return undefined; 1422 } 1423 if (typeof this.suffixSymbolOptions?.normalAccessibility?.accessibilityText !== 'undefined') { 1424 return this.suffixSymbolOptions.normalAccessibility.accessibilityText as Resource; 1425 } 1426 return undefined; 1427 } 1428 1429 getSuffixSymbolAction(): Callback<ClickEvent> | undefined { 1430 if (typeof this.suffixSymbolOptions?.action === 'undefined') { 1431 return undefined; 1432 } 1433 return () => { 1434 if (!this.getChipEnable()) { 1435 return; 1436 } 1437 this.suffixSymbolOptions?.action?.(); 1438 }; 1439 } 1440 1441 private getAccessibilitySelected(): boolean | undefined { 1442 if (this.getChipAccessibilitySelectedType() === AccessibilitySelectedType.SELECTED) { 1443 return this.getChipActive(); 1444 } 1445 return undefined; 1446 } 1447 1448 private getAccessibilityChecked(): boolean | undefined { 1449 if (this.getChipAccessibilitySelectedType() === AccessibilitySelectedType.CHECKED) { 1450 return this.getChipActive(); 1451 } 1452 return undefined; 1453 } 1454 1455 private getChipAccessibilitySelectedType(): AccessibilitySelectedType { 1456 if (typeof this.chipActivated === 'undefined') { 1457 return AccessibilitySelectedType.CLICKED; 1458 } 1459 return this.chipAccessibilitySelectedType ?? AccessibilitySelectedType.CHECKED; 1460 } 1461 1462 private getCloseIconAccessibilityLevel(): string { 1463 if (this.closeOptions?.accessibilityLevel === 'no' || this.closeOptions?.accessibilityLevel === 'no-hide-descendants') { 1464 return this.closeOptions.accessibilityLevel; 1465 } 1466 return 'yes'; 1467 } 1468 1469 private getCloseIconAccessibilityDescription(): Resource | undefined { 1470 if (typeof this.closeOptions?.accessibilityDescription === 'undefined') { 1471 return undefined; 1472 } 1473 return this.closeOptions.accessibilityDescription as Resource; 1474 } 1475 1476 private getCloseIconAccessibilityText(): Resource { 1477 if (typeof this.closeOptions?.accessibilityText === 'undefined') { 1478 return $r('sys.string.delete_used_for_accessibility_text') 1479 } 1480 return this.closeOptions.accessibilityText as ESObject as Resource; 1481 } 1482 1483 private getSuffixIconAccessibilityLevel(): string { 1484 if (this.suffixIcon?.accessibilityLevel === 'no' || this.suffixIcon?.accessibilityLevel === 'no-hide-descendants') { 1485 return this.suffixIcon.accessibilityLevel; 1486 } 1487 return this.suffixIcon?.action ? 'yes' : 'no'; 1488 } 1489 1490 private getSuffixIconAccessibilityDescription(): Resource | undefined { 1491 if (typeof this.suffixIcon?.accessibilityDescription === 'undefined') { 1492 return undefined; 1493 } 1494 return this.suffixIcon.accessibilityDescription as ESObject as Resource; 1495 } 1496 1497 private getSuffixIconAccessibilityText(): Resource | undefined { 1498 if (typeof this.suffixIcon?.accessibilityText === 'undefined') { 1499 return undefined; 1500 } 1501 1502 return this.suffixIcon.accessibilityText as ESObject as Resource; 1503 } 1504 1505 private getAccessibilityLevel(): string | undefined { 1506 return this.chipAccessibilityLevel; 1507 } 1508 1509 private getAccessibilityDescription(): Resource | undefined { 1510 if (typeof this.chipAccessibilityDescription === 'undefined') { 1511 return undefined; 1512 } 1513 return this.chipAccessibilityDescription as ESObject as Resource; 1514 } 1515 1516 resourceToNumber(resource: Resource, defaultValue: number): number { 1517 if (!resource || !resource.type) { 1518 console.error('[Chip] failed: resource get fail.'); 1519 return defaultValue; 1520 } 1521 const resourceManager = this.getUIContext().getHostContext()?.resourceManager; 1522 if (!resourceManager) { 1523 console.error('[Chip] failed to get resourceManager.'); 1524 return defaultValue; 1525 } 1526 switch (resource.type) { 1527 case RESOURCE_TYPE_FLOAT: 1528 case RESOURCE_TYPE_INTEGER: 1529 try { 1530 if (resource.id !== -1) { 1531 return resourceManager.getNumber(resource); 1532 } 1533 return resourceManager.getNumberByName((resource.params as string[])[0].split('.')[2]); 1534 } catch (error) { 1535 console.error(`[Chip] get resource error, return defaultValue`); 1536 return defaultValue; 1537 } 1538 default: 1539 return defaultValue; 1540 } 1541 } 1542 1543 build() { 1544 if (!this.deleteChip) { 1545 this.chipBuilder() 1546 } 1547 } 1548} 1549