• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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