• 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 { ColorMetrics, LengthMetrics } from '@ohos.arkui.node';
17import { SymbolGlyphModifier } from '@ohos.arkui.modifier';
18import { KeyCode } from '@kit.InputKit';
19import mediaquery from '@ohos.mediaquery';
20import EnvironmentCallback from '@ohos.app.ability.EnvironmentCallback';
21import common from '@ohos.app.ability.common';
22
23export enum ChipSize {
24  NORMAL = 'NORMAL',
25  SMALL = 'SMALL'
26}
27
28enum IconType {
29  PREFIX_ICON = 'PREFIXICON',
30  SUFFIX_ICON = 'SUFFIXICON',
31  PREFIX_SYMBOL = 'PREFIXSYMBOL',
32  SUFFIX_SYMBOL = 'SUFFIXSYMBOL',
33}
34
35enum BreakPointsType {
36  SM = 'SM',
37  MD = 'MD',
38  LG = 'LG'
39}
40
41export enum AccessibilitySelectedType {
42  CLICKED = 0,
43  CHECKED = 1,
44  SELECTED = 2,
45}
46
47export interface IconCommonOptions {
48  src: ResourceStr;
49  size?: SizeOptions;
50  fillColor?: ResourceColor;
51  activatedFillColor?: ResourceColor;
52}
53
54export interface SuffixIconOptions extends IconCommonOptions {
55  action?: () => void;
56  accessibilityText?: ResourceStr;
57  accessibilityDescription?: ResourceStr;
58  accessibilityLevel?: string;
59}
60
61export interface PrefixIconOptions extends IconCommonOptions {}
62
63export interface AccessibilityOptions {
64  accessibilityLevel?: string;
65  accessibilityText?: ResourceStr;
66  accessibilityDescription?: ResourceStr;
67}
68
69export interface CloseOptions extends AccessibilityOptions {}
70
71export interface ChipSymbolGlyphOptions {
72  normal?: SymbolGlyphModifier;
73  activated?: SymbolGlyphModifier;
74}
75
76export interface ChipSuffixSymbolGlyphOptions {
77  normalAccessibility?: AccessibilityOptions;
78  activatedAccessibility?: AccessibilityOptions;
79  action?: VoidCallback;
80}
81
82export interface LabelMarginOptions {
83  left?: Dimension;
84  right?: Dimension;
85}
86
87export interface LocalizedLabelMarginOptions {
88  start?: LengthMetrics;
89  end?: LengthMetrics;
90}
91
92export interface LabelOptions {
93  text: string;
94  fontSize?: Dimension;
95  fontColor?: ResourceColor;
96  activatedFontColor?: ResourceColor;
97  fontFamily?: string;
98  labelMargin?: LabelMarginOptions;
99  localizedLabelMargin?: LocalizedLabelMarginOptions;
100}
101
102interface IconTheme {
103  normalSize: SizeOptions;
104  smallSize: SizeOptions;
105  fillColor: ResourceColor;
106  activatedFillColor: ResourceColor;
107  focusFillColor: ResourceColor;
108  focusActivatedColor: ResourceColor;
109}
110
111interface PrefixIconTheme extends IconTheme {}
112
113interface SuffixIconTheme extends IconTheme {
114  defaultDeleteIcon: ResourceStr;
115  focusable: boolean;
116  isShowMargin: Resource;
117}
118
119interface DefaultSymbolTheme {
120  normalFontColor: Array<ResourceColor>;
121  activatedFontColor: Array<ResourceColor>;
122  smallSymbolFontSize: Length;
123  normalSymbolFontSize: Length;
124  defaultEffect: number;
125}
126
127interface LabelTheme {
128  normalFontSize: Dimension;
129  smallFontSize: Dimension;
130  focusFontColor: ResourceColor;
131  focusActiveFontColor: ResourceColor;
132  fontColor: ResourceColor;
133  activatedFontColor: ResourceColor;
134  fontFamily: string;
135  normalMargin: Margin;
136  localizedNormalMargin: LocalizedMargin;
137  smallMargin: Margin;
138  localizedSmallMargin: LocalizedMargin;
139  defaultFontSize: Dimension;
140  fontWeight: Resource;
141}
142
143interface ChipNodeOpacity {
144  normal: number;
145  hover: number;
146  pressed: number;
147}
148
149interface ChipNodeConstraintWidth {
150  breakPointMinWidth: number,
151  breakPointSmMaxWidth: number,
152  breakPointMdMaxWidth: number,
153  breakPointLgMaxWidth: number,
154}
155
156interface ChipNodeTheme {
157  suitAgeScale: number;
158  minLabelWidth: Dimension;
159  normalHeight: Dimension;
160  smallHeight: Dimension;
161  enabled: boolean;
162  activated: boolean;
163  backgroundColor: ResourceColor;
164  activatedBackgroundColor: ResourceColor;
165  focusOutlineColor: ResourceColor;
166  borderColor: ResourceColor,
167  defaultBorderWidth: Dimension;
168  activatedBorderColor: ResourceColor;
169  focusBtnScaleX: Resource;
170  focusBtnScaleY: Resource;
171  focusBgColor: ResourceColor;
172  focusActivatedBgColor: ResourceColor;
173  normalShadowStyle: Resource;
174  smallShadowStyle: Resource;
175  focusOutlineMargin: number;
176  normalBorderRadius: Dimension;
177  smallBorderRadius: Dimension;
178  borderWidth: number;
179  localizedNormalPadding: LocalizedPadding;
180  localizedSmallPadding: LocalizedPadding;
181  hoverBlendColor: ResourceColor;
182  pressedBlendColor: ResourceColor;
183  opacity: ChipNodeOpacity;
184  breakPointConstraintWidth: ChipNodeConstraintWidth;
185}
186
187interface ChipTheme {
188  prefixIcon: PrefixIconTheme;
189  label: LabelTheme;
190  suffixIcon: SuffixIconTheme;
191  defaultSymbol: DefaultSymbolTheme;
192  chipNode: ChipNodeTheme;
193}
194
195const RESOURCE_TYPE_STRING = 10003;
196const RESOURCE_TYPE_FLOAT = 10002;
197const RESOURCE_TYPE_INTEGER = 10007;
198
199interface ChipOptions {
200  prefixIcon?: PrefixIconOptions;
201  prefixSymbol?: ChipSymbolGlyphOptions;
202  label: LabelOptions;
203  suffixIcon?: SuffixIconOptions;
204  suffixSymbol?: ChipSymbolGlyphOptions;
205  suffixSymbolOptions?: ChipSuffixSymbolGlyphOptions;
206  allowClose?: boolean;
207  closeOptions?: CloseOptions;
208  enabled?: boolean;
209  activated?: boolean;
210  backgroundColor?: ResourceColor;
211  activatedBackgroundColor?: ResourceColor;
212  borderRadius?: Dimension;
213  size?: ChipSize | SizeOptions;
214  direction?: Direction;
215  accessibilitySelectedType?: AccessibilitySelectedType;
216  accessibilityDescription?: ResourceStr;
217  accessibilityLevel?: string;
218  onClose?: () => void
219  onClicked?: () => void
220}
221
222@Builder
223export function Chip(options: ChipOptions) {
224  ChipComponent({
225    chipSize: options.size,
226    prefixIcon: options.prefixIcon,
227    prefixSymbol: options.prefixSymbol,
228    label: options.label,
229    suffixIcon: options.suffixIcon,
230    suffixSymbol: options.suffixSymbol,
231    suffixSymbolOptions: options.suffixSymbolOptions,
232    allowClose: options.allowClose,
233    closeOptions: options.closeOptions,
234    chipEnabled: options.enabled,
235    chipActivated: options.activated,
236    chipNodeBackgroundColor: options.backgroundColor,
237    chipNodeActivatedBackgroundColor: options.activatedBackgroundColor,
238    chipNodeRadius: options.borderRadius,
239    chipDirection: options.direction,
240    chipAccessibilitySelectedType: options.accessibilitySelectedType,
241    chipAccessibilityDescription: options.accessibilityDescription,
242    chipAccessibilityLevel: options.accessibilityLevel,
243    onClose: options.onClose,
244    onClicked: options.onClicked,
245  })
246}
247
248
249@Component
250export struct ChipComponent {
251  private theme: ChipTheme = {
252    prefixIcon: {
253      normalSize: {
254        width: $r('sys.float.chip_normal_icon_size'),
255        height: $r('sys.float.chip_normal_icon_size')
256      },
257      smallSize: {
258        width: $r('sys.float.chip_small_icon_size'),
259        height: $r('sys.float.chip_small_icon_size')
260      },
261      fillColor: $r('sys.color.chip_usually_icon_color'),
262      activatedFillColor: $r('sys.color.chip_active_icon_color'),
263      focusFillColor: $r('sys.color.chip_icon_focus_fill'),
264      focusActivatedColor: $r('sys.color.chip_icon_activated_focus_color'),
265    },
266    label: {
267      normalFontSize: $r('sys.float.chip_normal_font_size'),
268      smallFontSize: $r('sys.float.chip_small_font_size'),
269      focusFontColor: $r('sys.color.chip_focus_text'),
270      focusActiveFontColor: $r('sys.color.chip_activated_focus_font_color'),
271      fontColor: $r('sys.color.chip_font_color'),
272      activatedFontColor: $r('sys.color.chip_activated_fontcolor'),
273      fontFamily: 'HarmonyOS Sans',
274      fontWeight: $r('sys.float.chip_text_font_weight'),
275      normalMargin: {
276        left: 6,
277        right: 6,
278        top: 0,
279        bottom: 0
280      },
281      smallMargin: {
282        left: 4,
283        right: 4,
284        top: 0,
285        bottom: 0
286      },
287      defaultFontSize: 14,
288      localizedNormalMargin: {
289        start: LengthMetrics.resource($r('sys.float.chip_normal_text_margin')),
290        end: LengthMetrics.resource($r('sys.float.chip_normal_text_margin')),
291        top: LengthMetrics.vp(0),
292        bottom: LengthMetrics.vp(0)
293      },
294      localizedSmallMargin: {
295        start: LengthMetrics.resource($r('sys.float.chip_small_text_margin')),
296        end: LengthMetrics.resource($r('sys.float.chip_small_text_margin')),
297        top: LengthMetrics.vp(0),
298        bottom: LengthMetrics.vp(0),
299      }
300    },
301    suffixIcon: {
302      normalSize: {
303        width: $r('sys.float.chip_normal_icon_size'),
304        height: $r('sys.float.chip_normal_icon_size')
305      },
306      smallSize: {
307        width: $r('sys.float.chip_small_icon_size'),
308        height: $r('sys.float.chip_small_icon_size')
309      },
310      fillColor: $r('sys.color.chip_usually_icon_color'),
311      activatedFillColor: $r('sys.color.chip_active_icon_color'),
312      focusFillColor: $r('sys.color.chip_icon_focus_fill'),
313      focusActivatedColor: $r('sys.color.chip_icon_activated_focus_color'),
314      defaultDeleteIcon: $r('sys.media.ohos_ic_public_cancel', 16, 16),
315      focusable: false,
316      isShowMargin: $r('sys.float.chip_show_close_icon_margin'),
317    },
318    defaultSymbol: {
319      normalFontColor: [$r('sys.color.chip_usually_icon_color')],
320      activatedFontColor: [$r('sys.color.chip_active_icon_color')],
321      normalSymbolFontSize: LengthMetrics.resource($r('sys.float.chip_normal_icon_size')).value as Length,
322      smallSymbolFontSize: LengthMetrics.resource($r('sys.float.chip_small_icon_size')).value as Length,
323      defaultEffect: -1,
324    },
325    chipNode: {
326      suitAgeScale: 1.75,
327      minLabelWidth: 12,
328      normalHeight: $r('sys.float.chip_normal_height'),
329      smallHeight: $r('sys.float.chip_small_height'),
330      enabled: true,
331      activated: false,
332      backgroundColor: $r('sys.color.chip_background_color'),
333      activatedBackgroundColor: $r('sys.color.chip_container_activated_color'),
334      focusOutlineColor: $r('sys.color.ohos_id_color_focused_outline'),
335      focusOutlineMargin: 2,
336      borderColor: $r('sys.color.chip_border_color'),
337      defaultBorderWidth: $r('sys.float.chip_border_width'),
338      activatedBorderColor: $r('sys.color.chip_activated_border_color'),
339      normalBorderRadius: $r('sys.float.chip_border_radius_normal'),
340      smallBorderRadius: $r('sys.float.chip_border_radius_small'),
341      borderWidth: 2,
342      focusBtnScaleX: $r('sys.float.chip_focused_btn_scale'),
343      focusBtnScaleY: $r('sys.float.chip_focused_btn_scale'),
344      localizedNormalPadding: {
345        start: LengthMetrics.resource($r('sys.float.chip_normal_text_padding')),
346        end: LengthMetrics.resource($r('sys.float.chip_normal_text_padding')),
347        top: LengthMetrics.vp(4),
348        bottom: LengthMetrics.vp(4)
349      },
350      localizedSmallPadding: {
351        start: LengthMetrics.resource($r('sys.float.chip_small_text_padding')),
352        end: LengthMetrics.resource($r('sys.float.chip_small_text_padding')),
353        top: LengthMetrics.vp(4),
354        bottom: LengthMetrics.vp(4)
355      },
356      hoverBlendColor: $r('sys.color.chip_hover_color'),
357      pressedBlendColor: $r('sys.color.chip_press_color'),
358      focusBgColor: $r('sys.color.chip_focus_color'),
359      focusActivatedBgColor: $r('sys.color.chip_container_activated_focus_color'),
360      opacity: { normal: 1, hover: 0.95, pressed: 0.9 },
361      normalShadowStyle: $r('sys.float.chip_normal_shadow_style'),
362      smallShadowStyle: $r('sys.float.chip_small_shadow_style'),
363      breakPointConstraintWidth: {
364        breakPointMinWidth: 128,
365        breakPointSmMaxWidth: 156,
366        breakPointMdMaxWidth: 280,
367        breakPointLgMaxWidth: 400
368      }
369    }
370  };
371  @Prop chipSize?: ChipSize | SizeOptions = ChipSize.NORMAL;
372  @Prop allowClose?: boolean;
373  @Prop closeOptions?: CloseOptions;
374  @Prop chipDirection?: Direction = Direction.Auto;
375  @Prop prefixIcon?: PrefixIconOptions;
376  @Prop prefixSymbol?: ChipSymbolGlyphOptions;
377  @Require @Prop label: LabelOptions;
378  @Prop suffixIcon?: SuffixIconOptions;
379  @Prop suffixSymbol?: ChipSymbolGlyphOptions;
380  @Prop suffixSymbolOptions?: ChipSuffixSymbolGlyphOptions;
381  @Prop chipNodeBackgroundColor?: ResourceColor;
382  @Prop chipNodeActivatedBackgroundColor?: ResourceColor;
383  @Prop chipNodeRadius?: Dimension;
384  @Prop chipEnabled?: boolean = true;
385  @Prop chipActivated?: boolean;
386  @Prop chipAccessibilitySelectedType?: AccessibilitySelectedType;
387  @Prop chipAccessibilityDescription?: ResourceStr;
388  @Prop chipAccessibilityLevel?: string;
389  @State isChipExist: boolean = true;
390  @State chipScale: ScaleOptions = { x: 1, y: 1 };
391  @State chipOpacity: number = 1;
392  @State suffixSymbolHeight: number = 0;
393  @State suffixSymbolWidth: number = 0;
394  @State breakPoint: BreakPointsType = BreakPointsType.SM;
395  @State fontSizeScale: number = 1;
396  private isSuffixIconFocusStyleCustomized = this.resourceToNumber(this.theme.suffixIcon.isShowMargin, 0) !== 0;
397  private isSuffixIconFocusable = this.resourceToNumber(this.theme.suffixIcon.isShowMargin, 0) !== 0;
398  public onClose?: VoidCallback;
399  public onClicked?: VoidCallback;
400  @State chipNodeInFocus: boolean = false;
401  private smListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(0vp<width) and (width<600vp)');
402  private mdListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(600vp<=width) and (width<840vp)');
403  private lgListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(840vp<=width)');
404  private symbolEffect: SymbolEffect = new SymbolEffect();
405  private environmentCallbackID?: number = undefined;
406  private environmentCallback: EnvironmentCallback = {
407    onConfigurationUpdated: (configuration) => {
408      this.fontSizeScale = configuration.fontSizeScale ?? 1;
409    },
410    onMemoryLevel() {
411    }
412  };
413
414  aboutToAppear(): void {
415    this.smListener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => {
416      if (mediaQueryResult.matches) {
417        this.breakPoint = BreakPointsType.SM;
418      }
419    });
420    this.mdListener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => {
421      if (mediaQueryResult.matches) {
422        this.breakPoint = BreakPointsType.MD;
423      }
424    });
425    this.lgListener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => {
426      if (mediaQueryResult.matches) {
427        this.breakPoint = BreakPointsType.LG;
428      }
429    });
430    let abilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
431    if (abilityContext) {
432      this.fontSizeScale = abilityContext.config?.fontSizeScale ?? 1;
433      this.environmentCallbackID = abilityContext.getApplicationContext().on('environment', this.environmentCallback);
434    }
435  }
436
437  aboutToDisappear(): void {
438    this.smListener.off('change');
439    this.mdListener.off('change');
440    this.lgListener.off('change');
441    if (this.environmentCallbackID) {
442      this.getUIContext().getHostContext()?.getApplicationContext().off('environment', this.environmentCallbackID);
443      this.environmentCallbackID = void 0;
444    }
445  }
446
447  private isSetActiveChipBgColor(): boolean {
448    if (this.chipNodeActivatedBackgroundColor) {
449      return false;
450    }
451    try {
452      return ColorMetrics.resourceColor(this.chipNodeActivatedBackgroundColor).color !==
453      ColorMetrics.resourceColor(this.theme.chipNode.activatedBackgroundColor).color;
454    } catch (error) {
455      console.error(`[Chip] failed to get ColorMetrics.resourceColor`);
456       return false;
457    }
458  }
459
460  private isSetNormalChipBgColor(): boolean {
461    if (this.chipNodeBackgroundColor) {
462      return false;
463    }
464    try {
465      return ColorMetrics.resourceColor(this.chipNodeBackgroundColor).color !==
466      ColorMetrics.resourceColor(this.theme.chipNode.backgroundColor).color;
467    } catch (error) {
468      console.error(`[Chip] failed to get resourceColor`);
469       return false;
470    }
471  }
472
473  private getShadowStyles(): ShadowStyle | undefined {
474    if (!this.chipNodeInFocus) {
475      return undefined;
476    }
477    return this.resourceToNumber(this.isSmallChipSize() ? this.theme.chipNode.smallShadowStyle :
478      this.theme.chipNode.normalShadowStyle, -1);
479  }
480
481  @Builder
482  ChipBuilder() {
483    Button({ type: ButtonType.Normal }) {
484      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
485        if (this.hasPrefixSymbol()) {
486          SymbolGlyph()
487            .fontSize(this.defaultSymbolFontsize())
488            .fontColor(this.getDefaultSymbolColor(IconType.PREFIX_SYMBOL))
489            .flexShrink(0)
490            .attributeModifier(this.getPrefixSymbolModifier())
491            .effectStrategy(SymbolEffectStrategy.NONE)
492            .symbolEffect(this.symbolEffect, false)
493            .symbolEffect(this.symbolEffect, this.theme.defaultSymbol.defaultEffect)
494        } else if (this.prefixIcon?.src) {
495          Image(this.prefixIcon.src)
496            .direction(this.chipDirection)
497            .size(this.getPrefixIconSize())
498            .fillColor(this.getPrefixIconFilledColor())
499            .objectFit(ImageFit.Cover)
500            .focusable(false)
501            .flexShrink(0)
502            .draggable(false)
503        }
504
505        Text(this.getChipText())
506          .draggable(false)
507          .flexShrink(1)
508          .focusable(true)
509          .maxLines(1)
510          .textOverflow({ overflow: TextOverflow.Ellipsis })
511          .textAlign(TextAlign.Center)
512          .direction(this.chipDirection)
513          .fontSize(this.getLabelFontSize())
514          .fontColor(this.getLabelFontColor())
515          .fontFamily(this.getLabelFontFamily())
516          .fontWeight(this.getLabelFontWeight())
517          .margin(this.getLabelMargin())
518
519        if (this.hasSuffixSymbol()) {
520          Button({ type: ButtonType.Normal }) {
521            SymbolGlyph()
522              .fontSize(this.defaultSymbolFontsize())
523              .fontColor(this.getDefaultSymbolColor(IconType.SUFFIX_SYMBOL))
524              .attributeModifier(this.getSuffixSymbolModifier())
525              .effectStrategy(SymbolEffectStrategy.NONE)
526              .symbolEffect(this.symbolEffect, false)
527              .symbolEffect(this.symbolEffect, this.theme.defaultSymbol.defaultEffect)
528          }
529          .onClick(this.getSuffixSymbolAction())
530          .accessibilityText(this.getSuffixSymbolAccessibilityText())
531          .accessibilityDescription(this.getSuffixSymbolAccessibilityDescription())
532          .accessibilityLevel(this.getSuffixSymbolAccessibilityLevel())
533          .flexShrink(0)
534          .backgroundColor(Color.Transparent)
535          .borderRadius(0)
536          .padding(0)
537          .stateEffect(false)
538          .hoverEffect(HoverEffect.None)
539          .focusable(this.isSuffixIconFocusable)
540        } else if (this.suffixIcon?.src) {
541          Button({ type: ButtonType.Normal }) {
542            Image(this.suffixIcon.src)
543              .direction(this.chipDirection)
544              .size(this.getSuffixIconSize())
545              .fillColor(this.getSuffixIconFilledColor())
546              .objectFit(ImageFit.Cover)
547              .draggable(false)
548          }
549          .backgroundColor(Color.Transparent)
550          .borderRadius(0)
551          .padding(0)
552          .flexShrink(0)
553          .stateEffect(false)
554          .hoverEffect(HoverEffect.None)
555          .size(this.getSuffixIconSize())
556          .accessibilityText(this.getSuffixIconAccessibilityText())
557          .accessibilityDescription(this.getSuffixIconAccessibilityDescription())
558          .accessibilityLevel(this.getSuffixIconAccessibilityLevel())
559          .onClick(this.getSuffixIconAction())
560          .focusable(this.isSuffixIconFocusable)
561        } else if (this.isClosable()) {
562          Button({ type: ButtonType.Normal }) {
563            SymbolGlyph($r('sys.symbol.xmark'))
564              .fontSize(this.defaultSymbolFontsize())
565              .fontColor(this.getDefaultSymbolColor(IconType.SUFFIX_SYMBOL))
566          }
567          .backgroundColor(Color.Transparent)
568          .borderRadius(0)
569          .padding(0)
570          .flexShrink(0)
571          .stateEffect(false)
572          .hoverEffect(HoverEffect.None)
573          .accessibilityText(this.getCloseIconAccessibilityText())
574          .accessibilityDescription(this.getCloseIconAccessibilityDescription())
575          .accessibilityLevel(this.getCloseIconAccessibilityLevel())
576          .onClick(() => {
577            if (!this.isChipEnabled()) {
578              return;
579            }
580            this.onClose?.();
581            this.deleteChip();
582          })
583          .focusable(this.isSuffixIconFocusable)
584        }
585      }
586      .direction(this.chipDirection)
587      .padding(this.getChipPadding())
588      .size(this.getChipSize())
589      .constraintSize(this.getChipConstraintSize())
590    }
591    .clip(false)
592    .shadow(this.getShadowStyles())
593    .padding(0)
594    .focusable(true)
595    .size(this.getChipSize())
596    .enabled(this.isChipEnabled())
597    .direction(this.chipDirection)
598    .backgroundColor(this.getChipBackgroundColor())
599    .borderWidth(this.theme.chipNode.defaultBorderWidth)
600    .borderColor(this.getChipNodeBorderColor())
601    .borderRadius(this.getChipBorderRadius())
602    .scale(this.chipScale)
603    .opacity(this.chipOpacity)
604    .accessibilityGroup(true)
605    .accessibilityDescription(this.getAccessibilityDescription())
606    .accessibilityLevel(this.getAccessibilityLevel())
607    .accessibilityChecked(this.getAccessibilityChecked())
608    .accessibilitySelected(this.getAccessibilitySelected())
609    .onClick(this.getChipOnClicked())
610    .onKeyEvent((event) => {
611      if (!event || event.type === null || event.type !== KeyType.Down) {
612        return;
613      }
614      let isDeleteChip = event.keyCode === KeyCode.KEYCODE_FORWARD_DEL;
615      let isEnterDeleteChip = event.keyCode === KeyCode.KEYCODE_ENTER && this.allowClose !== false &&
616        !this.suffixIcon?.src && this.isSuffixIconFocusStyleCustomized;
617      if (isDeleteChip || isEnterDeleteChip) {
618        this.deleteChip();
619      }
620    })
621    .onFocus(() => {
622      if (this.isSuffixIconFocusStyleCustomized) {
623        this.chipNodeInFocus = true;
624        this.chipScale = {
625          x: this.resourceToNumber(this.theme.chipNode.focusBtnScaleX, 1),
626          y: this.resourceToNumber(this.theme.chipNode.focusBtnScaleY, 1),
627        };
628      }
629    })
630    .onBlur(() => {
631      if (this.isSuffixIconFocusStyleCustomized) {
632        this.chipNodeInFocus = false;
633        this.chipScale = {
634          x: 1, y: 1
635        };
636      }
637    })
638  }
639
640  private getCloseIconAccessibilityLevel(): string {
641    if (this.closeOptions?.accessibilityLevel === 'no' || this.closeOptions?.accessibilityLevel === 'no-hide-descendants') {
642      return this.closeOptions.accessibilityLevel;
643    }
644    return 'yes';
645  }
646
647  private getCloseIconAccessibilityDescription(): Resource | undefined {
648    if (typeof this.closeOptions?.accessibilityDescription === 'undefined') {
649      return void 0;
650    }
651    return this.closeOptions.accessibilityDescription as Resource;
652  }
653
654  private getCloseIconAccessibilityText(): Resource {
655    if (typeof this.closeOptions?.accessibilityText === 'undefined') {
656      return $r('sys.string.delete_used_for_accessibility_text');
657    }
658    return this.closeOptions.accessibilityText as ESObject as Resource;
659  }
660
661  getSuffixIconAction(): Callback<ClickEvent> | undefined {
662    if (this.suffixIcon?.src) {
663      if (!this.suffixIcon?.action) {
664        return void 0;
665      }
666      return () => {
667        if (this.isChipEnabled()) {
668          this.suffixIcon?.action?.();
669        }
670      };
671    }
672    return void 0;
673  }
674
675  getSuffixIconFilledColor(): ResourceColor {
676    if (this.isChipActivated()) {
677      return this.suffixIcon?.activatedFillColor ?? this.getDefaultActiveIconColor(IconType.PREFIX_ICON);
678    }
679    return this.suffixIcon?.fillColor ?? this.getDefaultFillIconColor(IconType.SUFFIX_ICON);
680  }
681
682  getSuffixIconSize(): SizeOptions {
683    let suffixIconSize: SizeOptions = { width: 0, height: 0 };
684    if (typeof this.suffixIcon?.size?.width !== 'undefined' && this.isValidLength(this.suffixIcon.size.width)) {
685      suffixIconSize.width = this.suffixIcon.size.width;
686    } else {
687      suffixIconSize.width =
688        this.isSmallChipSize() ? this.theme.suffixIcon.smallSize.width : this.theme.suffixIcon.normalSize.width;
689    }
690    if (typeof this.suffixIcon?.size?.height !== 'undefined' && this.isValidLength(this.suffixIcon.size.height)) {
691      suffixIconSize.height = this.suffixIcon.size.height;
692    } else {
693      suffixIconSize.height =
694        this.isSmallChipSize() ? this.theme.suffixIcon.smallSize.height : this.theme.suffixIcon.normalSize.height;
695    }
696    return suffixIconSize;
697  }
698
699  getSuffixIconAccessibilityLevel(): string {
700    if (this.suffixIcon?.accessibilityLevel === 'no' || this.suffixIcon?.accessibilityLevel === 'no-hide-descendants') {
701      return this.suffixIcon.accessibilityLevel;
702    }
703    return this.suffixIcon?.action ? 'yes' : 'no';
704  }
705
706  getSuffixIconAccessibilityDescription(): Resource | undefined {
707    if (typeof this.suffixIcon?.accessibilityDescription === 'undefined') {
708      return void 0;
709    }
710    return this.suffixIcon.accessibilityDescription as ESObject as Resource;
711  }
712
713  getSuffixIconAccessibilityText(): Resource | undefined {
714    if (typeof this.suffixIcon?.accessibilityText === 'undefined') {
715      return void 0;
716    }
717
718    return this.suffixIcon.accessibilityText as ESObject as Resource;
719  }
720
721  isClosable(): boolean {
722    return this.allowClose ?? true;
723  }
724
725  getSuffixSymbolModifier(): SymbolGlyphModifier | undefined {
726    if (this.isChipActivated()) {
727      return this.suffixSymbol?.activated;
728    }
729    return this.suffixSymbol?.normal;
730  }
731
732  getSuffixSymbolAccessibilityLevel(): string {
733    if (this.isChipActivated()) {
734      if (this.suffixSymbolOptions?.activatedAccessibility?.accessibilityLevel === 'no' ||
735        this.suffixSymbolOptions?.activatedAccessibility?.accessibilityLevel === 'no-hide-descendants') {
736        return this.suffixSymbolOptions.activatedAccessibility.accessibilityLevel;
737      }
738      return this.suffixSymbolOptions?.action ? 'yes' : 'no';
739    }
740    if (this.suffixSymbolOptions?.normalAccessibility?.accessibilityLevel === 'no' ||
741      this.suffixSymbolOptions?.normalAccessibility?.accessibilityLevel === 'no-hide-descendants') {
742      return this.suffixSymbolOptions.normalAccessibility.accessibilityLevel;
743    }
744    return this.suffixSymbolOptions?.action ? 'yes' : 'no';
745  }
746
747  getSuffixSymbolAccessibilityDescription(): Resource | undefined {
748    if (this.isChipActivated()) {
749      if (typeof this.suffixSymbolOptions?.activatedAccessibility?.accessibilityDescription !== 'undefined') {
750        return this.suffixSymbolOptions.activatedAccessibility.accessibilityDescription as Resource;
751      }
752      return void 0;
753    }
754    if (typeof this.suffixSymbolOptions?.normalAccessibility?.accessibilityDescription !== 'undefined') {
755      return this.suffixSymbolOptions.normalAccessibility.accessibilityDescription as Resource;
756    }
757    return void 0;
758  }
759
760  getSuffixSymbolAccessibilityText(): Resource | undefined {
761    if (this.isChipActivated()) {
762      if (typeof this.suffixSymbolOptions?.activatedAccessibility?.accessibilityText !== 'undefined') {
763        return this.suffixSymbolOptions.activatedAccessibility.accessibilityText as Resource;
764      }
765      return void 0;
766    }
767    if (typeof this.suffixSymbolOptions?.normalAccessibility?.accessibilityText !== 'undefined') {
768      return this.suffixSymbolOptions.normalAccessibility.accessibilityText as Resource;
769    }
770    return void 0;
771  }
772
773  getSuffixSymbolAction(): Callback<ClickEvent> | undefined {
774    if (typeof this.suffixSymbolOptions?.action === 'undefined') {
775      return void 0;
776    }
777    return () => {
778      if (!this.isChipEnabled()) {
779        return;
780      }
781      this.suffixSymbolOptions?.action?.();
782    };
783  }
784
785  hasSuffixSymbol(): boolean {
786    return !!(this.suffixSymbol?.normal || this.suffixSymbol?.activated);
787  }
788
789  getPrefixIconFilledColor(): ResourceColor {
790    if (this.isChipActivated()) {
791      return this.prefixIcon?.activatedFillColor ?? this.getDefaultActiveIconColor(IconType.PREFIX_ICON);
792    }
793    return this.prefixIcon?.fillColor ?? this.getDefaultFillIconColor(IconType.PREFIX_ICON);
794  }
795
796  getPrefixIconSize(): SizeOptions {
797    let prefixIconSize: SizeOptions = { width: 0, height: 0 };
798    if (typeof this.prefixIcon?.size?.width !== 'undefined' && this.isValidLength(this.prefixIcon.size.width)) {
799      prefixIconSize.width = this.prefixIcon.size.width;
800    } else {
801      prefixIconSize.width =
802        this.isSmallChipSize() ? this.theme.prefixIcon.smallSize.width : this.theme.prefixIcon.normalSize.width;
803    }
804    if (typeof this.prefixIcon?.size?.height !== 'undefined' && this.isValidLength(this.prefixIcon.size.height)) {
805      prefixIconSize.height = this.prefixIcon.size.height;
806    } else {
807      prefixIconSize.height =
808        this.isSmallChipSize() ? this.theme.prefixIcon.smallSize.height : this.theme.prefixIcon.normalSize.height;
809    }
810    return prefixIconSize;
811  }
812
813  getPrefixSymbolModifier(): SymbolGlyphModifier | undefined {
814    if (this.isChipActivated()) {
815      return this.prefixSymbol?.activated;
816    }
817    return this.prefixSymbol?.normal;
818  }
819
820  getDefaultSymbolColor(iconType: string): ResourceColor[] {
821    return this.isChipActivated() ? this.getSymbolActiveColor(iconType) :
822    this.getSymbolFillColor(iconType);
823  }
824
825  private getDefaultActiveIconColor(iconType: string): ResourceColor {
826    if (iconType === IconType.PREFIX_ICON) {
827      return this.chipNodeInFocus ? this.theme.prefixIcon.focusActivatedColor :
828      this.theme.prefixIcon.activatedFillColor;
829    } else {
830      return this.chipNodeInFocus ? this.theme.suffixIcon.focusActivatedColor :
831      this.theme.suffixIcon.activatedFillColor;
832    }
833  }
834
835  private getDefaultFillIconColor(iconType: string): ResourceColor {
836    if (iconType === IconType.PREFIX_ICON) {
837      return this.chipNodeInFocus ? this.theme.prefixIcon.focusFillColor : this.theme.prefixIcon.fillColor;
838    } else {
839      return this.chipNodeInFocus ? this.theme.suffixIcon.focusFillColor : this.theme.suffixIcon.fillColor;
840    }
841  }
842
843  private getSymbolActiveColor(iconType: string): ResourceColor[] {
844    if (!this.chipNodeInFocus) {
845      return this.theme.defaultSymbol.activatedFontColor;
846    }
847    if (iconType === IconType.PREFIX_SYMBOL) {
848      return [this.theme.prefixIcon.focusActivatedColor];
849    }
850    if (iconType === IconType.SUFFIX_SYMBOL) {
851      return [this.theme.suffixIcon.focusActivatedColor];
852    }
853    return this.theme.defaultSymbol.activatedFontColor;
854  }
855
856  private getSymbolFillColor(iconType?: string): ResourceColor[] {
857    if (!this.chipNodeInFocus) {
858      return this.theme.defaultSymbol.normalFontColor;
859    }
860    if (iconType === IconType.PREFIX_SYMBOL) {
861      return [this.theme.prefixIcon.focusFillColor];
862    }
863    if (iconType === IconType.SUFFIX_SYMBOL) {
864      return [this.theme.suffixIcon.focusFillColor];
865    }
866    return this.theme.defaultSymbol.normalFontColor;
867  }
868
869  hasPrefixSymbol(): boolean {
870    return !!(this.prefixSymbol?.normal || this.prefixSymbol?.activated);
871  }
872
873  getChipConstraintSize(): ConstraintSizeOptions | undefined {
874    const constraintSize: ConstraintSizeOptions = {};
875    if (typeof this.chipSize === 'string') {
876      constraintSize.maxWidth = this.getChipMaxWidth();
877      constraintSize.minHeight =
878        this.chipSize === ChipSize.SMALL ? this.theme.chipNode.smallHeight : this.theme.chipNode.normalHeight;
879    } else {
880      if (typeof this.chipSize?.width === 'undefined' || !this.isValidLength(this.chipSize.width)) {
881        constraintSize.maxWidth = this.getChipMaxWidth();
882      }
883      if (typeof this.chipSize?.height === 'undefined' || !this.isValidLength(this.chipSize.height)) {
884        constraintSize.minHeight = this.theme.chipNode.normalHeight;
885      }
886    }
887    return constraintSize;
888  }
889
890  getChipMaxWidth(): Length | undefined {
891    if (this.fontSizeScale >= this.theme.chipNode.suitAgeScale) {
892      return void 0;
893    }
894    if (this.breakPoint === BreakPointsType.SM) {
895      return this.theme.chipNode.breakPointConstraintWidth.breakPointSmMaxWidth;
896    }
897    if (this.breakPoint === BreakPointsType.MD) {
898      return this.theme.chipNode.breakPointConstraintWidth.breakPointMdMaxWidth;
899    }
900    if (this.breakPoint === BreakPointsType.LG) {
901      return this.theme.chipNode.breakPointConstraintWidth.breakPointLgMaxWidth;
902    }
903    return void 0;
904  }
905
906  getChipSize(): SizeOptions | undefined {
907    const chipSize: SizeOptions = {
908      width: 'auto',
909      height: 'auto'
910    };
911
912    if (typeof this.chipSize !== 'string') {
913      if (typeof this.chipSize?.width !== 'undefined' && this.isValidLength(this.chipSize.width)) {
914        chipSize.width = this.chipSize.width;
915      }
916      if (typeof this.chipSize?.height !== 'undefined' && this.isValidLength(this.chipSize.height)) {
917        chipSize.height = this.chipSize.height;
918      }
919    }
920
921    return chipSize;
922  }
923
924  getChipPadding(): Length | Padding | LocalizedPadding {
925    return this.isSmallChipSize() ? this.theme.chipNode.localizedSmallPadding :
926    this.theme.chipNode.localizedNormalPadding;
927  }
928
929  getLabelMargin(): Length | Padding | LocalizedPadding {
930    const localizedLabelMargin: LocalizedMargin = {
931      start: LengthMetrics.vp(0),
932      end: LengthMetrics.vp(0),
933    };
934    const defaultLocalizedMargin =
935      this.isSmallChipSize() ? this.theme.label.localizedSmallMargin : this.theme.label.localizedNormalMargin;
936
937    if (typeof this.label?.localizedLabelMargin?.start !== 'undefined' &&
938      this.label.localizedLabelMargin.start.value >= 0) {
939      localizedLabelMargin.start = this.label.localizedLabelMargin.start;
940    } else if (this.hasPrefix()) {
941      localizedLabelMargin.start = defaultLocalizedMargin.start;
942    }
943
944    if (typeof this.label?.localizedLabelMargin?.end !== 'undefined' &&
945      this.label.localizedLabelMargin.end.value >= 0) {
946      localizedLabelMargin.end = this.label.localizedLabelMargin.end;
947    } else if (this.hasSuffix()) {
948      localizedLabelMargin.end = defaultLocalizedMargin.end;
949    }
950    if (typeof this.label?.localizedLabelMargin === 'object') {
951      return localizedLabelMargin;
952    }
953    if (typeof this.label.labelMargin === 'object') {
954      const labelMargin: Margin = { left: 0, right: 0 };
955      const defaultLabelMargin: Margin =
956        this.isSmallChipSize() ? this.theme.label.smallMargin : this.theme.label.normalMargin;
957      if (typeof this.label?.labelMargin?.left !== 'undefined' && this.isValidLength(this.label.labelMargin.left)) {
958        labelMargin.left = this.label.labelMargin.left;
959      } else if (this.hasPrefix()) {
960        labelMargin.left = defaultLabelMargin.left;
961      }
962      if (typeof this.label?.labelMargin?.right !== 'undefined' && this.isValidLength(this.label.labelMargin.right)) {
963        labelMargin.right = this.label.labelMargin.right;
964      } else if (this.hasSuffix()) {
965        labelMargin.right = defaultLabelMargin.right;
966      }
967      return labelMargin;
968    }
969    return localizedLabelMargin;
970  }
971
972  hasSuffix(): boolean {
973    if (this.suffixIcon?.src) {
974      return true;
975    }
976    return this.isChipActivated() ? !!this.suffixSymbol?.activated : !!this.suffixSymbol?.normal;
977  }
978
979  private hasPrefix(): boolean {
980    if (this.prefixIcon?.src) {
981      return true;
982    }
983    return this.isChipActivated() ? !!this.prefixSymbol?.activated : !!this.prefixSymbol?.normal;
984  }
985
986  getLabelFontWeight(): string | number | FontWeight {
987    if (this.isChipActivated()) {
988      return FontWeight.Medium;
989    }
990    return this.resourceToNumber(this.theme.label.fontWeight, FontWeight.Regular) as FontWeight;
991  }
992
993  getLabelFontFamily(): ResourceStr {
994    return this.label?.fontFamily ?? this.theme.label.fontFamily;
995  }
996
997  private defaultSymbolFontsize(): Length {
998    return this.isSmallChipSize() ? this.theme.defaultSymbol.smallSymbolFontSize :
999    this.theme.defaultSymbol.normalSymbolFontSize;
1000  }
1001
1002  private getActiveFontColor(): ResourceColor {
1003    return this.chipNodeInFocus ? this.theme.label.focusActiveFontColor : this.theme.label.activatedFontColor;
1004  }
1005
1006  private getFontColor(): ResourceColor {
1007    return this.chipNodeInFocus ? this.theme.label.focusFontColor : this.theme.label.fontColor;
1008  }
1009
1010  private getChipNodeBorderColor(): ResourceColor {
1011    let themeChipNode = this.theme.chipNode;
1012    return this.isChipActivated() ? themeChipNode.activatedBorderColor : themeChipNode.borderColor;
1013  }
1014
1015  getLabelFontColor(): ResourceColor {
1016    if (this.isChipActivated()) {
1017      return this.label?.activatedFontColor ?? this.getActiveFontColor();
1018    }
1019    return this.label?.fontColor ?? this.getFontColor();
1020  }
1021
1022  getLabelFontSize(): Dimension {
1023    if (typeof this.label.fontSize !== 'undefined' && this.isValidLength(this.label.fontSize)) {
1024      return this.label.fontSize;
1025    }
1026    if (this.isSmallChipSize()) {
1027      return this.theme.label.smallFontSize;
1028    }
1029    return this.theme.label.normalFontSize;
1030  }
1031
1032  getChipText(): ResourceStr {
1033    return this.label?.text ?? '';
1034  }
1035
1036  deleteChip() {
1037    animateTo({ curve: Curve.Sharp, duration: 150 }, () => {
1038      this.chipOpacity = 0;
1039    });
1040    animateTo({
1041      curve: Curve.FastOutLinearIn,
1042      duration: 150,
1043      onFinish: () => {
1044        this.isChipExist = false;
1045      }
1046    }, () => {
1047      this.chipScale = { x: 0.85, y: 0.85 };
1048    })
1049  }
1050
1051  getChipOnClicked(): Callback<ClickEvent> | undefined {
1052    if (this.onClicked) {
1053      return this.onClicked.bind(this);
1054    }
1055    return void 0;
1056  }
1057
1058  private getAccessibilitySelected(): boolean | undefined {
1059    if (this.getChipAccessibilitySelectedType() === AccessibilitySelectedType.SELECTED) {
1060      return this.isChipActivated();
1061    }
1062    return void 0;
1063  }
1064
1065  private getAccessibilityChecked(): boolean | undefined {
1066    if (this.getChipAccessibilitySelectedType() === AccessibilitySelectedType.CHECKED) {
1067      return this.isChipActivated();
1068    }
1069    return void 0;
1070  }
1071
1072  private getChipAccessibilitySelectedType(): AccessibilitySelectedType {
1073    if (typeof this.chipActivated === 'undefined') {
1074      return AccessibilitySelectedType.CLICKED;
1075    }
1076    return this.chipAccessibilitySelectedType ?? AccessibilitySelectedType.CHECKED;
1077  }
1078
1079  private getAccessibilityLevel(): string | undefined {
1080    return this.chipAccessibilityLevel;
1081  }
1082
1083  private getAccessibilityDescription(): Resource | undefined {
1084    if (typeof this.chipAccessibilityDescription === 'undefined') {
1085      return void 0;
1086    }
1087    return this.chipAccessibilityDescription as ESObject as Resource;
1088  }
1089
1090  isChipEnabled(): boolean {
1091    return this.chipEnabled ?? true;
1092  }
1093
1094  getChipBorderRadius(): Dimension {
1095    if (typeof this.chipNodeRadius !== 'undefined' && this.isValidLength(this.chipNodeRadius)) {
1096      return this.chipNodeRadius;
1097    }
1098    return this.isSmallChipSize() ? this.theme.chipNode.smallBorderRadius : this.theme.chipNode.normalBorderRadius;
1099  }
1100
1101  isSmallChipSize() {
1102    return typeof this.chipSize === 'string' && this.chipSize === ChipSize.SMALL;
1103  }
1104
1105  getChipBackgroundColor(): ResourceColor {
1106    let themeChipNode = this.theme.chipNode;
1107    if (this.isChipActivated()) {
1108      return this.chipNodeInFocus && !this.isSetActiveChipBgColor() ? themeChipNode.focusActivatedBgColor :
1109      this.getColor(this.chipNodeActivatedBackgroundColor, themeChipNode.activatedBackgroundColor);
1110    }
1111    return this.chipNodeInFocus && !this.isSetNormalChipBgColor() ? themeChipNode.focusBgColor :
1112    this.getColor(this.chipNodeBackgroundColor, this.theme.chipNode.backgroundColor);
1113  }
1114
1115  getColor(color: ResourceColor | undefined, defaultColor: ResourceColor): ResourceColor {
1116    if (!color) {
1117      return defaultColor;
1118    }
1119    try {
1120      ColorMetrics.resourceColor(color).color;
1121      return color;
1122    } catch (e) {
1123      console.error(`[Chip] failed to get color`);
1124      return Color.Transparent;
1125    }
1126  }
1127
1128  isChipActivated() {
1129    return this.chipActivated ?? false;
1130  }
1131
1132  resourceToNumber(resource: Resource, defaultValue: number): number {
1133    if (!resource || !resource.type) {
1134      console.error('[Chip] failed: resource get fail');
1135      return defaultValue;
1136    }
1137    const resourceManager = this.getUIContext().getHostContext()?.resourceManager;
1138    if (!resourceManager) {
1139      console.error('[Chip] failed to get resourceManager');
1140      return defaultValue;
1141    }
1142    switch (resource.type) {
1143      case RESOURCE_TYPE_FLOAT:
1144      case RESOURCE_TYPE_INTEGER:
1145        try {
1146          if (resource.id !== -1) {
1147            return resourceManager.getNumber(resource);
1148          }
1149          return resourceManager.getNumberByName((resource.params as string[])[0].split('.')[2]);
1150        } catch (error) {
1151          console.error(`[Chip] get resource error, return defaultValue`);
1152          return defaultValue;
1153        }
1154      default:
1155        return defaultValue;
1156    }
1157  }
1158
1159  isValidLength(length: Length): boolean {
1160    if (typeof length === 'number') {
1161      return length >= 0;
1162    } else if (typeof length === 'string') {
1163      return this.isValidLengthString(length);
1164    } else if (typeof length === 'object') {
1165      const resource = length as Resource;
1166      const resourceManager = this.getUIContext().getHostContext()?.resourceManager;
1167      if (!resourceManager) {
1168        console.error('[Chip] failed to get resourceManager.');
1169        return false;
1170      }
1171      switch (resource.type) {
1172        case RESOURCE_TYPE_FLOAT:
1173        case RESOURCE_TYPE_INTEGER:
1174          return resourceManager.getNumber(resource) >= 0;
1175        case RESOURCE_TYPE_STRING:
1176          return this.isValidLengthString(resourceManager.getStringSync(resource));
1177        default:
1178          return false;
1179      }
1180    }
1181    return false;
1182  }
1183
1184  isValidLengthString(length: string): boolean {
1185    const matches = length.match(/(-?\d+(?:\.\d+)?)_?(fp|vp|px|lpx)?$/i);
1186    if (!matches || matches.length < 3) {
1187      return false;
1188    }
1189    return Number.parseInt(matches[1], 10) >= 0;
1190  }
1191
1192  build() {
1193    if (this.isChipExist) {
1194      this.ChipBuilder()
1195    }
1196  }
1197}
1198