• 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_small_icon_size')).value as Length,
366      normalSymbolFontSize: LengthMetrics.resource($r('sys.float.chip_normal_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 =
853        (this.chipNodeOnFocus || this.isHover) && !this.isSetActiveChipBgColor() ? themeChipNode.focusActivatedBgColor :
854          this.chipNodeActivatedBackgroundColor ?? this.theme.chipNode.activatedBackgroundColor
855    } else {
856      currentColor =
857        (this.chipNodeOnFocus || this.isHover) && !this.isSetNormalChipBgColor() ? themeChipNode.focusBgColor :
858          this.chipNodeBackgroundColor ?? this.theme.chipNode.backgroundColor
859    }
860    let sourceColor: ColorMetrics;
861    try {
862      sourceColor = ColorMetrics.resourceColor(currentColor);
863    } catch (err) {
864      hilog.error(0x3900, 'Ace', `Chip resourceColor, error: ${err.toString()}`);
865      sourceColor = ColorMetrics.resourceColor(Color.Transparent);
866    }
867    if (!this.isShowPressedBackGroundColor) {
868      return sourceColor.color
869    }
870    return sourceColor
871      .blendColor(ColorMetrics.resourceColor("#19000000"))
872      .color
873  }
874
875  private getChipNodeHeight(): Length {
876    if (this.isChipSizeEnum()) {
877      return this.chipSize === ChipSize.SMALL ? this.theme.chipNode.smallHeight : this.theme.chipNode.normalHeight
878    } else {
879      this.chipNodeSize = this.chipSize as SizeOptions
880      return (this.chipNodeSize?.height !== void (0) && this.toVp(this.chipNodeSize?.height) >= 0) ?
881      this.toVp(this.chipNodeSize?.height) : this.theme.chipNode.normalHeight
882    }
883  }
884
885  private getLabelWidth(): number {
886    return px2vp(measure.measureText({
887      textContent: this.label?.text ?? "",
888      fontSize: this.getLabelFontSize(),
889      fontFamily: this.label?.fontFamily ?? this.theme.label.fontFamily,
890      fontWeight: this.getLabelFontWeight(),
891      maxLines: 1,
892      overflow: TextOverflow.Ellipsis,
893      textAlign: TextAlign.Center
894    }))
895  }
896
897  private getCalculateChipNodeWidth(): number {
898    let calWidth: number = 0
899    let startEndVp: LocalizedMargin = this.getLabelStartEndVp()
900    calWidth += this.getChipNodeBorderWidth() * 2;
901    calWidth += this.getChipNodePadding().start?.value ?? 0;
902    calWidth += this.toVp(this.getPrefixChipWidth())
903    calWidth += this.toVp(startEndVp.start?.value ?? 0)
904    calWidth += this.getLabelWidth()
905    calWidth += this.toVp(startEndVp.end?.value ?? 0)
906    calWidth += this.toVp(this.getSuffixChipWidth())
907    calWidth += this.getChipNodePadding().end?.value ?? 0
908    return calWidth
909  }
910
911  private getPrefixChipWidth(): Length | undefined {
912    if (this.prefixSymbol?.normal || this.prefixSymbol?.activated) {
913      return this.prefixSymbolWidth
914    } else if (this.prefixIcon?.src) {
915      return this.getPrefixIconSize().width
916    } else {
917      return 0
918    }
919  }
920
921  private getSuffixChipWidth(): Length | undefined {
922    if (this.suffixSymbol?.normal || this.suffixSymbol?.activated) {
923      return this.suffixSymbolWidth
924    } else if (this.suffixIcon?.src) {
925      return this.getSuffixIconSize().width
926    } else if (!this.suffixIcon?.src && (this.allowClose ?? true)) {
927      return this.allowCloseSymbolWidth
928    } else {
929      return 0
930    }
931  }
932
933  private getReserveChipNodeWidth(): number {
934    return this.getCalculateChipNodeWidth() - this.getLabelWidth() + (this.theme.chipNode.minLabelWidth as number)
935  }
936
937  private getChipEnable(): boolean {
938    return this.chipEnabled || this.chipEnabled === void (0)
939  }
940
941  private getChipActive(): boolean {
942    if (typeof this.chipActivated === 'undefined') {
943      return false
944    }
945    return this.chipActivated
946  }
947
948  private getChipNodeOpacity(): number {
949    return this.chipOpacity
950  }
951
952  private handleTouch(event: TouchEvent) {
953    if (!this.getChipEnable()) {
954      return
955    }
956    if (this.isHover) {
957      if (event.type === TouchType.Down || event.type === TouchType.Move) {
958        this.isShowPressedBackGroundColor = true
959      } else if (event.type === TouchType.Up) {
960        this.isShowPressedBackGroundColor = false
961      } else {
962        this.isShowPressedBackGroundColor = false
963      }
964    } else {
965      if (event.type === TouchType.Down || event.type === TouchType.Move) {
966        this.isShowPressedBackGroundColor = true
967      } else if (event.type === TouchType.Up) {
968        this.isShowPressedBackGroundColor = false
969      } else {
970        this.isShowPressedBackGroundColor = false
971      }
972    }
973  }
974
975  private hoverAnimate(isHover: boolean) {
976    if (!this.getChipEnable()) {
977      return
978    }
979    this.isHover = isHover
980    if (this.isHover) {
981      this.isShowPressedBackGroundColor = true
982    } else {
983      this.isShowPressedBackGroundColor = false
984    }
985  }
986
987  private deleteChipNodeAnimate() {
988    animateTo({ duration: 150, curve: Curve.Sharp }, () => {
989      this.chipOpacity = 0
990      this.chipBlendColor = Color.Transparent
991    })
992    animateTo({
993      duration: 150, curve: Curve.FastOutLinearIn, onFinish: () => {
994        this.deleteChip = true
995      }
996    },
997      () => {
998        this.chipScale = { x: 0.85, y: 0.85 }
999      })
1000  }
1001
1002  private getSuffixIconSrc(): ResourceStr | undefined {
1003    this.useDefaultSuffixIcon = !this.suffixIcon?.src && (this.allowClose ?? true)
1004    return this.useDefaultSuffixIcon ? this.theme.suffixIcon.defaultDeleteIcon : (this.suffixIcon?.src ?? void (0))
1005  }
1006
1007  private getChipNodeWidth(): Length {
1008    if (!this.isChipSizeEnum()) {
1009      this.chipNodeSize = this.chipSize as SizeOptions
1010      if (this.chipNodeSize?.width !== void (0) && this.toVp(this.chipNodeSize.width) >= 0) {
1011        return this.toVp(this.chipNodeSize.width)
1012      }
1013    }
1014    let constraintWidth: ConstraintSizeOptions = this.getChipConstraintWidth()
1015    return Math.min(Math.max(this.getCalculateChipNodeWidth(),
1016      constraintWidth.minWidth as number), constraintWidth.maxWidth as number);
1017  }
1018
1019  private getFocusOverlaySize(): SizeOptions {
1020    return {
1021      width: Math.max(this.getChipNodeWidth() as number, this.getChipConstraintWidth().minWidth as number) + 8,
1022      height: this.getChipNodeHeight() as number + 8
1023    }
1024  }
1025
1026  private getChipConstraintWidth(): ConstraintSizeOptions {
1027    let calcMinWidth: number = this.getReserveChipNodeWidth()
1028
1029    let constraintWidth: number = this.getCalculateChipNodeWidth()
1030    let constraintSize: ConstraintSizeOptions
1031    switch (this.chipBreakPoints) {
1032      case BreakPointsType.SM:
1033        constraintSize = {
1034          minWidth: calcMinWidth,
1035          maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointSmMaxWidth)
1036        }
1037        break
1038      case BreakPointsType.MD:
1039        constraintSize = {
1040          minWidth: Math.max(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMinWidth),
1041          maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMdMaxWidth)
1042        }
1043        break
1044      case BreakPointsType.LG:
1045        constraintSize = {
1046          minWidth: Math.max(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointMinWidth),
1047          maxWidth: Math.min(constraintWidth, this.theme.chipNode.breakPointConstraintWidth.breakPointLgMaxWidth)
1048        }
1049        break
1050      default:
1051        constraintSize = { minWidth: calcMinWidth, maxWidth: constraintWidth }
1052        break
1053    }
1054    constraintSize.minWidth = Math.min(Math.max(this.getCalculateChipNodeWidth(),
1055      constraintSize.minWidth as number), constraintSize.maxWidth as number)
1056    constraintSize.minHeight = this.getChipNodeHeight()
1057    if (!this.isChipSizeEnum() && this.chipNodeSize?.height !== void (0) && this.toVp(this.chipNodeSize?.height) >= 0) {
1058      constraintSize.maxHeight = this.toVp(this.chipNodeSize.height)
1059      constraintSize.minHeight = this.toVp(this.chipNodeSize.height)
1060    }
1061    if (!this.isChipSizeEnum() && this.chipNodeSize?.width !== void (0) && this.toVp(this.chipNodeSize?.width) >= 0) {
1062      constraintSize.minWidth = this.toVp(this.chipNodeSize.width)
1063      constraintSize.maxWidth = this.toVp(this.chipNodeSize.width)
1064    } else if (this.toVp(this.fontSizeScale) >= this.theme.chipNode.suitAgeScale) {
1065      constraintSize.minWidth = void (0)
1066      constraintSize.maxWidth = void (0)
1067    }
1068    return constraintSize
1069  }
1070
1071  @Builder
1072  focusOverlay() {
1073    Stack() {
1074      if (this.chipNodeOnFocus && !this.suffixIconOnFocus) {
1075        Stack()
1076          .direction(this.chipDirection)
1077          .borderRadius(this.toVp(this.getChipNodeRadius()) + 4)
1078          .size(this.getFocusOverlaySize())
1079          .borderColor(this.theme.chipNode.focusOutlineColor)
1080          .borderWidth(this.theme.chipNode.borderWidth)
1081      }
1082    }
1083    .direction(this.chipDirection)
1084    .size({ width: 1, height: 1 })
1085    .align(Alignment.Center)
1086  }
1087
1088  @Styles
1089  suffixIconFocusStyles() {
1090    .borderColor(this.theme.chipNode.focusOutlineColor)
1091    .borderWidth(this.getSuffixIconFocusable() ? this.theme.chipNode.borderWidth : 0)
1092  }
1093
1094  @Styles
1095  suffixIconNormalStyles() {
1096    .borderColor(Color.Transparent)
1097    .borderWidth(0)
1098  }
1099
1100  aboutToAppear() {
1101    let uiContent: UIContext = this.getUIContext();
1102    this.fontSizeScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
1103
1104    this.smListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => {
1105      if (mediaQueryResult.matches) {
1106        this.chipBreakPoints = BreakPointsType.SM
1107      }
1108    })
1109    this.mdListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => {
1110      if (mediaQueryResult.matches) {
1111        this.chipBreakPoints = BreakPointsType.MD
1112      }
1113    })
1114    this.lgListener.on("change", (mediaQueryResult: mediaquery.MediaQueryResult) => {
1115      if (mediaQueryResult.matches) {
1116        this.chipBreakPoints = BreakPointsType.LG
1117      }
1118    })
1119    this.callbackId = this.getUIContext()
1120      .getHostContext()
1121    ?.getApplicationContext()
1122    ?.on('environment', this.callbacks);
1123  }
1124
1125  private getVisibility(): Visibility {
1126    if (this.toVp(this.getChipNodeHeight()) > 0) {
1127      return Visibility.Visible
1128    } else {
1129      return Visibility.None
1130    }
1131  }
1132
1133  private isSetActiveChipBgColor(): boolean {
1134    if (!this.chipNodeActivatedBackgroundColor) {
1135      return false;
1136    }
1137    try {
1138      return ColorMetrics.resourceColor(this.chipNodeActivatedBackgroundColor).color !==
1139      ColorMetrics.resourceColor(this.theme.chipNode.activatedBackgroundColor).color;
1140    } catch (error) {
1141      console.error(`[Chip] failed to get resourceColor`);
1142      return false;
1143    }
1144  }
1145
1146  private isSetNormalChipBgColor(): boolean {
1147    if (!this.chipNodeBackgroundColor) {
1148      return false;
1149    }
1150    try {
1151      return ColorMetrics.resourceColor(this.chipNodeBackgroundColor).color !==
1152      ColorMetrics.resourceColor(this.theme.chipNode.backgroundColor).color;
1153    } catch (error) {
1154      console.error(`[Chip] failed to get resourceColor`);
1155      return false;
1156    }
1157  }
1158
1159  private getShadowStyles(): ShadowStyle | undefined {
1160    if (!this.chipNodeOnFocus) {
1161      return undefined;
1162    }
1163    return this.resourceToNumber(this.isChipSizeEnum() && this.chipSize == ChipSize.SMALL ?
1164    this.theme.chipNode.smallShadowStyle :
1165    this.theme.chipNode.normalShadowStyle, -1) as ShadowStyle;
1166  }
1167
1168  aboutToDisappear() {
1169    this.smListener.off("change")
1170    this.mdListener.off("change")
1171    this.lgListener.off("change")
1172    if (this.callbackId) {
1173      this.getUIContext()
1174        ?.getHostContext()
1175      ?.getApplicationContext()
1176      ?.off('environment', this.callbackId);
1177      this.callbackId = void (0)
1178    }
1179  }
1180
1181  @Builder
1182  chipBuilder() {
1183    Button() {
1184      Row() {
1185        if (this.prefixSymbol?.normal || this.prefixSymbol?.activated) {
1186          SymbolGlyph()
1187            .fontSize(this.defaultSymbolFontsize())
1188            .fontColor(this.getDefaultSymbolColor(IconType.PREFIX_SYMBOL))
1189            .attributeModifier(this.getPrefixSymbolModifier())
1190            .effectStrategy(SymbolEffectStrategy.NONE)
1191            .symbolEffect(this.symbolEffect, false)
1192            .symbolEffect(this.symbolEffect, this.theme.defaultSymbol.defaultEffect)
1193            .onSizeChange((oldValue, newValue) => {
1194              this.prefixSymbolWidth = newValue?.width
1195            })
1196            .key('PrefixSymbolGlyph')
1197        } else if (this.prefixIcon?.src !== "") {
1198          Image(this.prefixIcon?.src)
1199            .direction(this.chipDirection)
1200            .opacity(this.getChipNodeOpacity())
1201            .size(this.getPrefixIconSize())
1202            .fillColor(this.getPrefixIconFilledColor())
1203            .enabled(this.getChipEnable())
1204            .objectFit(ImageFit.Cover)
1205            .focusable(false)
1206            .flexShrink(0)
1207            .visibility(this.getVisibility())
1208            .draggable(false)
1209        }
1210
1211        Text(this.label?.text ?? "")
1212          .direction(this.chipDirection)
1213          .opacity(this.getChipNodeOpacity())
1214          .fontSize(this.getLabelFontSize())
1215          .fontColor(this.getLabelFontColor())
1216          .fontFamily(this.getLabelFontFamily())
1217          .fontWeight(this.getLabelFontWeight())
1218          .margin(this.getActualLabelMargin())
1219          .enabled(this.getChipEnable())
1220          .maxLines(1)
1221          .textOverflow({ overflow: TextOverflow.Ellipsis })
1222          .flexShrink(1)
1223          .focusable(true)
1224          .textAlign(TextAlign.Center)
1225          .visibility(this.getVisibility())
1226          .draggable(false)
1227
1228        if (this.suffixSymbol?.normal || this.suffixSymbol?.activated) {
1229          Button({ type: ButtonType.Normal }) {
1230            SymbolGlyph()
1231              .fontSize(this.defaultSymbolFontsize())
1232              .fontColor(this.getDefaultSymbolColor(IconType.SUFFIX_SYMBOL))
1233              .attributeModifier(this.getSuffixSymbolModifier())
1234              .effectStrategy(SymbolEffectStrategy.NONE)
1235              .symbolEffect(this.symbolEffect, false)
1236              .symbolEffect(this.symbolEffect, this.theme.defaultSymbol.defaultEffect)
1237              .onSizeChange((oldValue, newValue) => {
1238                this.suffixSymbolWidth = newValue?.width
1239              })
1240              .key('SuffixSymbolGlyph')
1241          }
1242          .onClick(this.getSuffixSymbolAction())
1243          .accessibilityText(this.getSuffixSymbolAccessibilityText())
1244          .accessibilityDescription(this.getSuffixSymbolAccessibilityDescription())
1245          .accessibilityLevel(this.getSuffixSymbolAccessibilityLevel())
1246          .backgroundColor(Color.Transparent)
1247          .borderRadius(0)
1248          .padding(0)
1249          .stateEffect(false)
1250          .focusable(!this.isSuffixIconFocusStyleCustomized)
1251          .hoverEffect(this.setHoverEffect())
1252        } else if (this.suffixIcon?.src !== "") {
1253          Button({ type: ButtonType.Normal }) {
1254            Image(this.getSuffixIconSrc())
1255              .direction(this.chipDirection)
1256              .opacity(this.getChipNodeOpacity())
1257              .size(this.getSuffixIconSize())
1258              .fillColor(this.getSuffixIconFilledColor())
1259              .enabled(this.getChipEnable())
1260              .objectFit(ImageFit.Cover)
1261              .flexShrink(0)
1262              .visibility(this.getVisibility())
1263              .draggable(false)
1264              .onFocus(() => {
1265                this.suffixIconOnFocus = true
1266              })
1267              .onBlur(() => {
1268                this.suffixIconOnFocus = false
1269              })
1270          }
1271          .backgroundColor(Color.Transparent)
1272          .borderRadius(0)
1273          .padding(0)
1274          .size(this.getSuffixIconSize())
1275          .accessibilityText(this.getSuffixIconAccessibilityText())
1276          .accessibilityDescription(this.getSuffixIconAccessibilityDescription())
1277          .accessibilityLevel(this.getSuffixIconAccessibilityLevel())
1278          .onClick(() => {
1279            if (!this.getChipEnable()) {
1280              return
1281            }
1282            if (this.suffixIcon?.action) {
1283              this.suffixIcon.action()
1284              return
1285            }
1286            if ((this.allowClose ?? true) && this.useDefaultSuffixIcon) {
1287              this.onClose()
1288              this.deleteChipNodeAnimate()
1289              return
1290            }
1291            this.onClicked()
1292          })
1293          .focusable(this.getSuffixIconFocusable())
1294          .hoverEffect(this.setHoverEffect())
1295          .stateEffect(this.setPressEffect())
1296        } else if (this.allowClose ?? true) {
1297          Button({ type: ButtonType.Normal }) {
1298            SymbolGlyph($r('sys.symbol.xmark'))
1299              .fontSize(this.defaultSymbolFontsize())
1300              .fontColor(this.getDefaultSymbolColor(IconType.SUFFIX_SYMBOL))
1301              .onSizeChange((oldValue, newValue) => {
1302                this.allowCloseSymbolWidth = newValue?.width
1303              })
1304              .key('AllowCloseSymbolGlyph')
1305          }
1306          .backgroundColor(Color.Transparent)
1307          .borderRadius(0)
1308          .padding(0)
1309          .accessibilityText(this.getCloseIconAccessibilityText())
1310          .accessibilityDescription(this.getCloseIconAccessibilityDescription())
1311          .accessibilityLevel(this.getCloseIconAccessibilityLevel())
1312          .onClick(() => {
1313            if (!this.getChipEnable()) {
1314              return
1315            }
1316            this.onClose()
1317            this.deleteChipNodeAnimate()
1318          })
1319          .focusable(!this.isSuffixIconFocusStyleCustomized)
1320          .hoverEffect(this.setHoverEffect())
1321          .stateEffect(this.setPressEffect())
1322        }
1323      }
1324      .direction(this.chipDirection)
1325      .alignItems(VerticalAlign.Center)
1326      .justifyContent(FlexAlign.Center)
1327      .padding(this.getChipNodePadding())
1328      .constraintSize(this.getChipConstraintWidth())
1329    }
1330    .constraintSize(this.getChipConstraintWidth())
1331    .direction(this.chipDirection)
1332    .type(ButtonType.Normal)
1333    .clip(false)
1334    .backgroundColor(this.getChipNodeBackGroundColor())
1335    .borderRadius(this.getChipNodeRadius())
1336    .borderWidth(this.getChipNodeBorderWidth())
1337    .borderColor(this.getChipNodeBorderColor())
1338    .enabled(this.getChipEnable())
1339    .scale(this.chipScale)
1340    .focusable(true)
1341    .opacity(this.getChipNodeOpacity())
1342    .shadow(this.getShadowStyles())
1343    .padding(0)
1344    .accessibilityGroup(true)
1345    .accessibilityDescription(this.getAccessibilityDescription())
1346    .accessibilityLevel(this.getAccessibilityLevel())
1347    .accessibilityChecked(this.getAccessibilityChecked())
1348    .accessibilitySelected(this.getAccessibilitySelected())
1349    .onFocus(() => {
1350      this.chipNodeOnFocus = true
1351      this.chipZoomIn();
1352    })
1353    .onBlur(() => {
1354      this.chipNodeOnFocus = false
1355      this.chipZoomOut();
1356    })
1357    .onTouch((event) => {
1358      this.handleTouch(event)
1359    })
1360    .onHover((isHover: boolean) => {
1361      if (isHover) {
1362        this.isShowPressedBackGroundColor = true;
1363        this.chipZoomIn();
1364      } else {
1365        if (!this.isShowPressedBackGroundColor && isHover) {
1366          this.isShowPressedBackGroundColor = true
1367        } else {
1368          this.isShowPressedBackGroundColor = false
1369        }
1370        this.chipZoomOut();
1371      }
1372      this.isHover = isHover;
1373    })
1374    .onKeyEvent((event) => {
1375      if (!event || event.type === null || event.type !== KeyType.Down) {
1376        return;
1377      }
1378      let isDeleteChip = event.keyCode === KeyCode.KEYCODE_FORWARD_DEL &&
1379        !this.suffixIconOnFocus;
1380      let isEnterDeleteChip = event.keyCode === KeyCode.KEYCODE_ENTER && this.allowClose !== false &&
1381        !this.suffixIcon?.src && this.isSuffixIconFocusStyleCustomized;
1382      if (isDeleteChip || isEnterDeleteChip) {
1383        this.deleteChipNodeAnimate();
1384      }
1385    })
1386    .onClick(this.onClicked === noop ? undefined : this.onClicked.bind(this))
1387  }
1388
1389  private setHoverEffect(): HoverEffect | undefined {
1390    return this.isSuffixIconFocusStyleCustomized ? HoverEffect.None : undefined;
1391  }
1392
1393  private setPressEffect(): boolean | undefined {
1394    return this.isSuffixIconFocusStyleCustomized ? false : undefined;
1395  }
1396
1397  private chipZoomOut() {
1398    if (this.isSuffixIconFocusStyleCustomized) {
1399      this.chipScale = {
1400        x: 1, y: 1
1401      };
1402    }
1403  }
1404
1405  private chipZoomIn() {
1406    if (this.isSuffixIconFocusStyleCustomized) {
1407      this.chipScale = {
1408        x: this.resourceToNumber(this.theme.chipNode.focusBtnScaleX, 1),
1409        y: this.resourceToNumber(this.theme.chipNode.focusBtnScaleY, 1),
1410      };
1411    }
1412  }
1413
1414  getSuffixSymbolAccessibilityLevel(): string {
1415    if (this.getChipActive()) {
1416      if (this.suffixSymbolOptions?.activatedAccessibility?.accessibilityLevel === 'no' ||
1417        this.suffixSymbolOptions?.activatedAccessibility?.accessibilityLevel === 'no-hide-descendants') {
1418        return this.suffixSymbolOptions.activatedAccessibility.accessibilityLevel;
1419      }
1420      return this.suffixSymbolOptions?.action ? 'yes' : 'no';
1421    }
1422    if (this.suffixSymbolOptions?.normalAccessibility?.accessibilityLevel === 'no' ||
1423      this.suffixSymbolOptions?.normalAccessibility?.accessibilityLevel === 'no-hide-descendants') {
1424      return this.suffixSymbolOptions.normalAccessibility.accessibilityLevel;
1425    }
1426    return this.suffixSymbolOptions?.action ? 'yes' : 'no';
1427  }
1428
1429  getSuffixSymbolAccessibilityDescription(): Resource | undefined {
1430    if (this.getChipActive()) {
1431      if (typeof this.suffixSymbolOptions?.activatedAccessibility?.accessibilityDescription !== 'undefined') {
1432        return this.suffixSymbolOptions.activatedAccessibility.accessibilityDescription as Resource;
1433      }
1434      return undefined;
1435    }
1436    if (typeof this.suffixSymbolOptions?.normalAccessibility?.accessibilityDescription !== 'undefined') {
1437      return this.suffixSymbolOptions.normalAccessibility.accessibilityDescription as Resource;
1438    }
1439    return undefined;
1440  }
1441
1442  getSuffixSymbolAccessibilityText(): Resource | undefined {
1443    if (this.getChipActive()) {
1444      if (typeof this.suffixSymbolOptions?.activatedAccessibility?.accessibilityText !== 'undefined') {
1445        return this.suffixSymbolOptions.activatedAccessibility.accessibilityText as Resource;
1446      }
1447      return undefined;
1448    }
1449    if (typeof this.suffixSymbolOptions?.normalAccessibility?.accessibilityText !== 'undefined') {
1450      return this.suffixSymbolOptions.normalAccessibility.accessibilityText as Resource;
1451    }
1452    return undefined;
1453  }
1454
1455  getSuffixSymbolAction(): Callback<ClickEvent> | undefined {
1456    if (typeof this.suffixSymbolOptions?.action === 'undefined') {
1457      return undefined;
1458    }
1459    return () => {
1460      if (!this.getChipEnable()) {
1461        return;
1462      }
1463      this.suffixSymbolOptions?.action?.();
1464    };
1465  }
1466
1467  private getAccessibilitySelected(): boolean | undefined {
1468    if (this.getChipAccessibilitySelectedType() === AccessibilitySelectedType.SELECTED) {
1469      return this.getChipActive();
1470    }
1471    return undefined;
1472  }
1473
1474  private getAccessibilityChecked(): boolean | undefined {
1475    if (this.getChipAccessibilitySelectedType() === AccessibilitySelectedType.CHECKED) {
1476      return this.getChipActive();
1477    }
1478    return undefined;
1479  }
1480
1481  private getChipAccessibilitySelectedType(): AccessibilitySelectedType {
1482    if (typeof this.chipActivated === 'undefined') {
1483      return AccessibilitySelectedType.CLICKED;
1484    }
1485    return this.chipAccessibilitySelectedType ?? AccessibilitySelectedType.CHECKED;
1486  }
1487
1488  private getCloseIconAccessibilityLevel(): string {
1489    if (this.closeOptions?.accessibilityLevel === 'no' || this.closeOptions?.accessibilityLevel === 'no-hide-descendants') {
1490      return this.closeOptions.accessibilityLevel;
1491    }
1492    return 'yes';
1493  }
1494
1495  private getCloseIconAccessibilityDescription(): Resource | undefined {
1496    if (typeof this.closeOptions?.accessibilityDescription === 'undefined') {
1497      return undefined;
1498    }
1499    return this.closeOptions.accessibilityDescription as Resource;
1500  }
1501
1502  private getCloseIconAccessibilityText(): Resource {
1503    if (typeof this.closeOptions?.accessibilityText === 'undefined') {
1504      return $r('sys.string.delete_used_for_accessibility_text')
1505    }
1506    return this.closeOptions.accessibilityText as ESObject as Resource;
1507  }
1508
1509  private getSuffixIconAccessibilityLevel(): string {
1510    if (this.suffixIcon?.accessibilityLevel === 'no' || this.suffixIcon?.accessibilityLevel === 'no-hide-descendants') {
1511      return this.suffixIcon.accessibilityLevel;
1512    }
1513    return this.suffixIcon?.action ? 'yes' : 'no';
1514  }
1515
1516  private getSuffixIconAccessibilityDescription(): Resource | undefined {
1517    if (typeof this.suffixIcon?.accessibilityDescription === 'undefined') {
1518      return undefined;
1519    }
1520    return this.suffixIcon.accessibilityDescription as ESObject as Resource;
1521  }
1522
1523  private getSuffixIconAccessibilityText(): Resource | undefined {
1524    if (typeof this.suffixIcon?.accessibilityText === 'undefined') {
1525      return undefined;
1526    }
1527
1528    return this.suffixIcon.accessibilityText as ESObject as Resource;
1529  }
1530
1531  private getAccessibilityLevel(): string | undefined {
1532    return this.chipAccessibilityLevel;
1533  }
1534
1535  private getAccessibilityDescription(): Resource | undefined {
1536    if (typeof this.chipAccessibilityDescription === 'undefined') {
1537      return undefined;
1538    }
1539    return this.chipAccessibilityDescription as ESObject as Resource;
1540  }
1541
1542  resourceToNumber(resource: Resource, defaultValue: number): number {
1543    if (!resource || !resource.type) {
1544      console.error('[Chip] failed: resource get fail.');
1545      return defaultValue;
1546    }
1547    const resourceManager = this.getUIContext().getHostContext()?.resourceManager;
1548    if (!resourceManager) {
1549      console.error('[Chip] failed to get resourceManager.');
1550      return defaultValue;
1551    }
1552    switch (resource.type) {
1553      case RESOURCE_TYPE_FLOAT:
1554      case RESOURCE_TYPE_INTEGER:
1555        try {
1556          if (resource.id !== -1) {
1557            return resourceManager.getNumber(resource);
1558          }
1559          return resourceManager.getNumberByName((resource.params as string[])[0].split('.')[2]);
1560        } catch (error) {
1561          console.error(`[Chip] get resource error, return defaultValue`);
1562          return defaultValue;
1563        }
1564      default:
1565        return defaultValue;
1566    }
1567  }
1568
1569  build() {
1570    if (!this.deleteChip) {
1571      this.chipBuilder()
1572    }
1573  }
1574}
1575