huibo-ui 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cjs/hb-form-item.cjs.entry.js +59 -9
  2. package/dist/cjs/hb-form-item.cjs.entry.js.map +1 -1
  3. package/dist/cjs/hb-form.cjs.entry.js +105 -2
  4. package/dist/cjs/hb-form.cjs.entry.js.map +1 -1
  5. package/dist/cjs/hb-select.cjs.entry.js +82 -10
  6. package/dist/cjs/hb-select.cjs.entry.js.map +1 -1
  7. package/dist/cjs/hb-steps.cjs.entry.js +1 -1
  8. package/dist/cjs/hb-steps.cjs.entry.js.map +1 -1
  9. package/dist/cjs/hb-table.cjs.entry.js +195 -27
  10. package/dist/cjs/hb-table.cjs.entry.js.map +1 -1
  11. package/dist/cjs/huibo-ui.cjs.js +1 -1
  12. package/dist/cjs/loader.cjs.js +1 -1
  13. package/dist/collection/components/Form/Form.js +205 -2
  14. package/dist/collection/components/Form/Form.js.map +1 -1
  15. package/dist/collection/components/Form/FormItem.js +117 -10
  16. package/dist/collection/components/Form/FormItem.js.map +1 -1
  17. package/dist/collection/components/Select/Select.js +105 -10
  18. package/dist/collection/components/Select/Select.js.map +1 -1
  19. package/dist/collection/components/Table/Table.js +273 -27
  20. package/dist/collection/components/Table/Table.js.map +1 -1
  21. package/dist/collection/utils/virtual-scroll.js +39 -0
  22. package/dist/collection/utils/virtual-scroll.js.map +1 -0
  23. package/dist/components/hb-form-item.js +62 -9
  24. package/dist/components/hb-form-item.js.map +1 -1
  25. package/dist/components/hb-form.js +110 -2
  26. package/dist/components/hb-form.js.map +1 -1
  27. package/dist/components/hb-select.js +87 -11
  28. package/dist/components/hb-select.js.map +1 -1
  29. package/dist/components/hb-steps.js +1 -1
  30. package/dist/components/hb-steps.js.map +1 -1
  31. package/dist/components/hb-table.js +203 -29
  32. package/dist/components/hb-table.js.map +1 -1
  33. package/dist/esm/hb-form-item.entry.js +59 -9
  34. package/dist/esm/hb-form-item.entry.js.map +1 -1
  35. package/dist/esm/hb-form.entry.js +105 -2
  36. package/dist/esm/hb-form.entry.js.map +1 -1
  37. package/dist/esm/hb-select.entry.js +82 -10
  38. package/dist/esm/hb-select.entry.js.map +1 -1
  39. package/dist/esm/hb-steps.entry.js +1 -1
  40. package/dist/esm/hb-steps.entry.js.map +1 -1
  41. package/dist/esm/hb-table.entry.js +195 -27
  42. package/dist/esm/hb-table.entry.js.map +1 -1
  43. package/dist/esm/huibo-ui.js +1 -1
  44. package/dist/esm/loader.js +1 -1
  45. package/dist/huibo-ui/huibo-ui.esm.js +1 -1
  46. package/dist/huibo-ui/huibo-ui.esm.js.map +1 -1
  47. package/dist/huibo-ui/{p-79b24b83.entry.js → p-2cf5bf20.entry.js} +2 -2
  48. package/dist/huibo-ui/{p-79b24b83.entry.js.map → p-2cf5bf20.entry.js.map} +1 -1
  49. package/dist/huibo-ui/p-4148d875.entry.js +2 -0
  50. package/dist/huibo-ui/p-4148d875.entry.js.map +1 -0
  51. package/dist/huibo-ui/{p-54a28052.entry.js → p-6bfe1954.entry.js} +2 -2
  52. package/dist/huibo-ui/p-6bfe1954.entry.js.map +1 -0
  53. package/dist/huibo-ui/{p-ac18c68b.entry.js → p-e8824b2c.entry.js} +2 -2
  54. package/dist/huibo-ui/p-e8824b2c.entry.js.map +1 -0
  55. package/dist/huibo-ui/p-f69599fa.entry.js +2 -0
  56. package/dist/huibo-ui/p-f69599fa.entry.js.map +1 -0
  57. package/dist/types/components/Form/Form.d.ts +57 -0
  58. package/dist/types/components/Form/FormItem.d.ts +23 -0
  59. package/dist/types/components/Select/Select.d.ts +19 -0
  60. package/dist/types/components/Table/Table.d.ts +103 -8
  61. package/dist/types/components.d.ts +148 -2
  62. package/dist/types/utils/virtual-scroll.d.ts +38 -0
  63. package/package.json +1 -1
  64. package/dist/huibo-ui/p-29092b85.entry.js +0 -2
  65. package/dist/huibo-ui/p-29092b85.entry.js.map +0 -1
  66. package/dist/huibo-ui/p-2bc30b1b.entry.js +0 -2
  67. package/dist/huibo-ui/p-2bc30b1b.entry.js.map +0 -1
  68. package/dist/huibo-ui/p-54a28052.entry.js.map +0 -1
  69. package/dist/huibo-ui/p-ac18c68b.entry.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"names":["selectCss","HbSelectStyle0","Select","modelValue","options","placeholder","disabled","size","clearable","multiple","filterable","filterMethod","defaultFirstOption","allowCreate","collapseTags","maxCollapseTags","isOpen","inputValue","searchValue","filteredOptions","activeOptionIndex","listboxId","Math","random","toString","slice","hbChange","hbVisibleChange","clickOutside","createClickOutsideHandler","host","this","onClose","emit","componentDidLoad","el","connect","updateInputValue","disconnectedCallback","disconnect","handleValueChange","handleOptionsChange","Array","isArray","labels","map","val","option","find","opt","value","label","String","join","handleInputClick","max","findIndex","o","handleWrapperKeydown","e","displayOptions","key","preventDefault","action","handleListKeyboard","activeIndex","itemCount","length","loop","type","index","handleSelect","currentValue","indexOf","splice","push","handleClear","stopPropagation","undefined","handleSearch","target","filter","toLowerCase","includes","isSelected","createOption","q","trim","exists","some","handleCreate","arr","render","showClear","h","class","role","onClick","onKeyDown","tabIndex","activationClickHandler","viewBox","fill","stroke","d","readonly","onInput","id","divided"],"sources":["src/components/Select/select.css?tag=hb-select&encapsulation=shadow","src/components/Select/Select.tsx"],"sourcesContent":[":host {\n display: inline-block;\n position: relative;\n --hb-select-font-size: 14px;\n --hb-select-height: 32px;\n --hb-select-border-color: var(--hb-border-color, #dcdfe6);\n --hb-select-border-color-hover: var(--hb-color-primary);\n --hb-select-bg-color: var(--hb-color-white, #ffffff);\n --hb-select-text-color: var(--hb-color-text-regular, #606266);\n --hb-select-placeholder-color: var(--hb-color-text-placeholder, #c0c4cc);\n}\n\n.hb-select {\n display: inline-block;\n position: relative;\n width: 240px;\n}\n\n.hb-select__input-wrapper {\n position: relative;\n display: inline-block;\n width: 100%;\n cursor: pointer;\n}\n\n.hb-select__input {\n display: inline-block;\n width: 100%;\n height: var(--hb-select-height);\n line-height: var(--hb-select-height);\n padding: 0 30px 0 15px;\n font-size: var(--hb-select-font-size);\n color: var(--hb-select-text-color);\n background-color: var(--hb-select-bg-color);\n border: 1px solid var(--hb-select-border-color);\n border-radius: 4px;\n box-sizing: border-box;\n cursor: pointer;\n outline: none;\n transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);\n}\n\n.hb-select__input::placeholder {\n color: var(--hb-select-placeholder-color);\n}\n\n.hb-select__input:hover:not(:disabled) {\n border-color: var(--hb-select-border-color-hover);\n}\n\n.hb-select__input:focus {\n border-color: var(--hb-select-border-color-hover);\n}\n\n.hb-select__tags {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n padding: 0 30px 0 5px;\n min-height: var(--hb-select-height);\n border: 1px solid var(--hb-select-border-color);\n border-radius: 4px;\n background-color: var(--hb-select-bg-color);\n}\n\n.hb-select__tag {\n display: inline-flex;\n align-items: center;\n height: 24px;\n padding: 0 8px;\n margin: 2px 0 2px 6px;\n background-color: var(--hb-fill-color-light, #f0f2f5);\n border: 1px solid var(--hb-border-color-lighter, #e4e7ed);\n border-radius: 4px;\n font-size: 12px;\n color: var(--hb-color-text-regular, #606266);\n}\n\n.hb-select__tag-text {\n margin-right: 4px;\n}\n\n.hb-select__tag-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 12px;\n height: 12px;\n cursor: pointer;\n color: var(--hb-color-text-placeholder, #c0c4cc);\n transition: color 0.2s;\n}\n\n.hb-select__tag-close svg {\n width: 100%;\n height: 100%;\n}\n\n.hb-select__tag-close:hover {\n color: var(--hb-color-text-regular, #606266);\n}\n\n.hb-select__suffix {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n display: flex;\n align-items: center;\n color: var(--hb-select-text-color);\n}\n\n.hb-select__clear {\n margin-right: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n cursor: pointer;\n color: var(--hb-select-placeholder-color);\n transition: color 0.2s;\n}\n\n.hb-select__clear svg {\n width: 100%;\n height: 100%;\n}\n\n.hb-select__clear:hover {\n color: var(--hb-select-text-color);\n}\n\n.hb-select__arrow {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 12px;\n height: 12px;\n color: var(--hb-select-placeholder-color);\n transition: transform 0.3s;\n}\n\n.hb-select__arrow svg {\n width: 100%;\n height: 100%;\n}\n\n.hb-select--open .hb-select__arrow {\n transform: rotate(180deg);\n}\n\n.hb-select__dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n margin-top: 4px;\n background-color: var(--hb-select-bg-color);\n border: 1px solid var(--hb-select-border-color);\n border-radius: 4px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 1000;\n min-width: 100%;\n max-height: 300px;\n overflow-y: auto;\n}\n\n.hb-select__menu {\n margin: 0;\n padding: 6px 0;\n list-style: none;\n}\n\n.hb-select__menu-item {\n display: flex;\n align-items: center;\n padding: 8px 20px;\n font-size: var(--hb-select-font-size);\n color: var(--hb-select-text-color);\n cursor: pointer;\n transition: background-color 0.2s;\n}\n\n.hb-select__menu-item:hover:not(.hb-select__menu-item--disabled):not(.hb-select__menu-item--divided) {\n background-color: var(--hb-fill-color-light, #f5f7fa);\n}\n\n.hb-select__menu-item--selected {\n color: var(--hb-color-primary);\n font-weight: 600;\n}\n\n.hb-select__menu-item--disabled {\n color: var(--hb-color-text-disabled, #c0c4cc);\n cursor: not-allowed;\n}\n\n.hb-select__menu-item--divided {\n border-top: 1px solid var(--hb-border-color-lighter, #e4e7ed);\n margin-top: 6px;\n padding-top: 6px;\n}\n\n.hb-select__menu-item--empty {\n color: var(--hb-select-placeholder-color);\n cursor: default;\n}\n\n.hb-select__menu-item-checkbox {\n margin-right: 8px;\n width: 14px;\n height: 14px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--hb-color-primary, #1677ff);\n}\n\n.hb-select__menu-item-checkbox svg {\n width: 100%;\n height: 100%;\n}\n\n.hb-select__menu-item--create {\n color: var(--hb-color-primary, #1677ff);\n font-weight: 500;\n}\n\n.hb-select__menu-item--create:hover {\n background-color: var(--hb-color-primary-bg, #e6f4ff);\n}\n\n.hb-select__menu-item-label {\n flex: 1;\n}\n\n.hb-select--small {\n font-size: 12px;\n}\n\n.hb-select--small .hb-select__input {\n height: 24px;\n line-height: 24px;\n font-size: 12px;\n padding: 0 25px 0 12px;\n}\n\n.hb-select--large {\n font-size: 16px;\n}\n\n.hb-select--large .hb-select__input {\n height: 40px;\n line-height: 40px;\n font-size: 16px;\n padding: 0 35px 0 18px;\n}\n\n.hb-select--disabled {\n cursor: not-allowed;\n}\n\n.hb-select--disabled .hb-select__input {\n background-color: var(--hb-fill-color-light, #f5f7fa);\n border-color: var(--hb-select-border-color);\n color: var(--hb-color-text-disabled, #c0c4cc);\n cursor: not-allowed;\n}\n","import { Component, h, Prop, Event, EventEmitter, State, Element, Watch } from '@stencil/core';\nimport { createClickOutsideHandler } from '../../utils/click-outside';\nimport { handleListKeyboard, activationClickHandler } from '../../utils/a11y';\n\nexport interface SelectOption {\n value: string | number;\n label: string;\n disabled?: boolean;\n divided?: boolean;\n}\n\n/**\n * Select 选择器组件\n * 当选项过多时,使用下拉菜单展示并选择内容\n */\n@Component({\n tag: 'hb-select',\n styleUrl: 'select.css',\n shadow: true,\n})\nexport class Select {\n @Element() el: HTMLElement;\n\n /**\n * 绑定值\n */\n @Prop({ mutable: true }) modelValue?: string | number | (string | number)[];\n\n /**\n * 可选项数据源\n */\n @Prop() options: SelectOption[] = [];\n\n /**\n * 输入框占位文本\n */\n @Prop() placeholder: string = '请选择';\n\n /**\n * 是否禁用\n * @default false\n */\n @Prop() disabled: boolean = false;\n\n /**\n * 输入框尺寸\n */\n @Prop() size: 'large' | 'default' | 'small' = 'default';\n\n /**\n * 是否可清空\n * @default false\n */\n @Prop() clearable: boolean = false;\n\n /**\n * 是否多选\n * @default false\n */\n @Prop() multiple: boolean = false;\n\n /**\n * 是否可搜索\n * @default false\n */\n @Prop() filterable: boolean = false;\n\n /**\n * 自定义过滤方法\n */\n @Prop() filterMethod?: (query: string) => void;\n\n /**\n * 是否默认展开\n * @default false\n */\n @Prop() defaultFirstOption: boolean = false;\n\n /**\n * 是否允许创建新条目\n * @default false\n */\n @Prop() allowCreate: boolean = false;\n\n /**\n * 多选时是否将选中值按文字的形式展示\n * @default false\n */\n @Prop() collapseTags: boolean = false;\n\n /**\n * 多选时最多显示多少个tag\n */\n @Prop() maxCollapseTags?: number;\n\n @State() isOpen: boolean = false;\n @State() inputValue: string = '';\n @State() searchValue: string = '';\n @State() filteredOptions: SelectOption[] = [];\n /** 键盘高亮的选项索引(-1 表示无) */\n @State() activeOptionIndex: number = -1;\n\n /**\n * O4:实例级稳定 id,用于 combobox 的 aria-controls / aria-activedescendant 关联,\n * 让读屏在键盘导航时播报当前高亮项。\n */\n private listboxId = `hb-select-listbox-${Math.random().toString(36).slice(2, 11)}`;\n\n /**\n * 值改变事件\n */\n @Event() hbChange: EventEmitter<string | number | (string | number)[]>;\n\n /**\n * 下拉框出现/隐藏时触发\n */\n @Event() hbVisibleChange: EventEmitter<boolean>;\n\n private clickOutside = createClickOutsideHandler({\n host: null as any,\n isOpen: () => this.isOpen,\n onClose: () => {\n this.isOpen = false;\n this.hbVisibleChange.emit(false);\n },\n });\n\n componentDidLoad() {\n this.clickOutside = createClickOutsideHandler({\n host: this.el,\n isOpen: () => this.isOpen,\n onClose: () => {\n this.isOpen = false;\n this.hbVisibleChange.emit(false);\n },\n });\n this.clickOutside.connect();\n this.updateInputValue();\n if (this.filterable) {\n this.filteredOptions = this.options;\n }\n }\n\n disconnectedCallback() {\n this.clickOutside.disconnect();\n }\n\n @Watch('modelValue')\n handleValueChange() {\n this.updateInputValue();\n }\n\n @Watch('options')\n handleOptionsChange() {\n if (this.filterable) {\n this.filteredOptions = this.options;\n }\n }\n\n private updateInputValue() {\n if (!this.modelValue) {\n this.inputValue = '';\n return;\n }\n\n if (this.multiple && Array.isArray(this.modelValue)) {\n const labels = this.modelValue.map(val => {\n const option = this.options.find(opt => opt.value === val);\n return option ? option.label : String(val);\n });\n this.inputValue = labels.join(', ');\n } else {\n const option = this.options.find(opt => opt.value === this.modelValue);\n this.inputValue = option ? option.label : String(this.modelValue);\n }\n }\n\n private handleInputClick = () => {\n if (this.disabled) return;\n this.isOpen = !this.isOpen;\n if (this.isOpen) {\n // defaultFirstOption=true:打开时自动高亮第一个可选(非禁用)项\n this.activeOptionIndex = this.defaultFirstOption\n ? Math.max(\n -1,\n this.options.findIndex(o => !o.disabled),\n )\n : -1;\n }\n this.hbVisibleChange.emit(this.isOpen);\n };\n\n private handleWrapperKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return;\n const displayOptions = this.filterable ? this.filteredOptions : this.options;\n\n if (!this.isOpen) {\n // 关闭态:Enter / Space / 方向键 展开\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar' || e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this.isOpen = true;\n this.activeOptionIndex = -1;\n this.hbVisibleChange.emit(true);\n }\n return;\n }\n\n const action = handleListKeyboard(e, {\n activeIndex: this.activeOptionIndex,\n itemCount: displayOptions.length,\n loop: true,\n });\n switch (action.type) {\n case 'navigate':\n e.preventDefault();\n this.activeOptionIndex = action.index;\n break;\n case 'select': {\n e.preventDefault();\n const opt = displayOptions[action.index];\n if (opt && !opt.disabled) this.handleSelect(opt);\n break;\n }\n case 'close':\n e.preventDefault();\n this.isOpen = false;\n this.hbVisibleChange.emit(false);\n break;\n default:\n break;\n }\n };\n\n private handleSelect = (option: SelectOption) => {\n if (option.disabled) return;\n\n if (this.multiple) {\n const currentValue = (this.modelValue as (string | number)[]) || [];\n const index = currentValue.indexOf(option.value);\n\n if (index > -1) {\n currentValue.splice(index, 1);\n } else {\n currentValue.push(option.value);\n }\n\n this.modelValue = [...currentValue];\n } else {\n this.modelValue = option.value;\n this.isOpen = false;\n this.hbVisibleChange.emit(false);\n }\n\n this.updateInputValue();\n this.hbChange.emit(this.modelValue);\n };\n\n private handleClear = (e: Event) => {\n e.stopPropagation();\n this.modelValue = this.multiple ? [] : undefined;\n this.inputValue = '';\n this.isOpen = false;\n this.hbChange.emit(this.modelValue);\n this.hbVisibleChange.emit(false);\n };\n\n private handleSearch = (e: Event) => {\n const target = e.target as HTMLInputElement;\n this.searchValue = target.value;\n\n if (this.filterMethod) {\n this.filterMethod(target.value);\n } else {\n this.filteredOptions = this.options.filter(opt => opt.label.toLowerCase().includes(target.value.toLowerCase()));\n }\n };\n\n private isSelected(option: SelectOption): boolean {\n if (this.multiple && Array.isArray(this.modelValue)) {\n return this.modelValue.includes(option.value);\n }\n return this.modelValue === option.value;\n }\n\n /**\n * allowCreate + filterable:当用户输入的文本在已有选项中不存在时,\n * 渲染一个\"创建 xxx\"项。返回待创建的文本(已 trim);无需创建返回 null。\n */\n private get createOption(): SelectOption | null {\n if (!this.allowCreate || !this.filterable || !this.isOpen) return null;\n const q = this.searchValue.trim();\n if (!q) return null;\n const exists = this.options.some(o => String(o.label).toLowerCase() === q.toLowerCase() || String(o.value).toLowerCase() === q.toLowerCase());\n if (exists) return null;\n return { value: q, label: q };\n }\n\n private handleCreate = () => {\n const opt = this.createOption;\n if (!opt) return;\n if (this.multiple) {\n const arr = ((this.modelValue as (string | number)[]) || []).slice();\n if (!arr.includes(opt.value)) arr.push(opt.value);\n this.modelValue = arr;\n } else {\n this.modelValue = opt.value;\n this.isOpen = false;\n this.hbVisibleChange.emit(false);\n }\n this.searchValue = '';\n this.filteredOptions = this.options;\n this.updateInputValue();\n this.hbChange.emit(this.modelValue);\n };\n\n render() {\n const displayOptions = this.filterable ? this.filteredOptions : this.options;\n const showClear = this.clearable && this.modelValue !== undefined && (this.multiple ? (this.modelValue as any[]).length > 0 : true);\n\n return (\n <div\n class={{\n 'hb-select': true,\n 'hb-select--open': this.isOpen,\n 'hb-select--disabled': this.disabled,\n [`hb-select--${this.size}`]: true,\n }}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={this.isOpen ? 'true' : 'false'}\n aria-controls={this.isOpen ? this.listboxId : undefined}\n aria-activedescendant={this.isOpen && this.activeOptionIndex >= 0 ? `${this.listboxId}-opt-${this.activeOptionIndex}` : undefined}\n >\n <div class=\"hb-select__input-wrapper\" onClick={this.handleInputClick} onKeyDown={this.handleWrapperKeydown}>\n {this.multiple && Array.isArray(this.modelValue) && this.modelValue.length > 0 ? (\n <div class=\"hb-select__tags\">\n {this.modelValue.slice(0, this.collapseTags && this.maxCollapseTags ? this.maxCollapseTags : undefined).map(val => {\n const option = this.options.find(opt => opt.value === val);\n return (\n <span class=\"hb-select__tag\">\n <span class=\"hb-select__tag-text\">{option ? option.label : val}</span>\n <span\n class=\"hb-select__tag-close\"\n role=\"button\"\n aria-label=\"移除标签\"\n tabIndex={0}\n onClick={e => {\n e.stopPropagation();\n this.handleSelect(option || { value: val, label: String(val) });\n }}\n onKeyDown={activationClickHandler}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width={2} stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 6l12 12M18 6L6 18\" />\n </svg>\n </span>\n </span>\n );\n })}\n {this.collapseTags && this.maxCollapseTags && this.modelValue.length > this.maxCollapseTags && (\n <span class=\"hb-select__tag\">\n <span class=\"hb-select__tag-text\">+{this.modelValue.length - this.maxCollapseTags}</span>\n </span>\n )}\n </div>\n ) : (\n <input\n type=\"text\"\n class=\"hb-select__input\"\n placeholder={this.placeholder}\n value={this.filterable && this.isOpen ? this.searchValue : this.inputValue}\n disabled={this.disabled}\n readonly={!this.filterable || !this.isOpen}\n onInput={this.handleSearch}\n aria-haspopup=\"listbox\"\n aria-expanded={this.isOpen ? 'true' : 'false'}\n role=\"combobox\"\n />\n )}\n <span class=\"hb-select__suffix\">\n {showClear && (\n <span class=\"hb-select__clear\" role=\"button\" aria-label=\"清空\" tabIndex={0} onClick={this.handleClear} onKeyDown={activationClickHandler}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width={2} stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 6l12 12M18 6L6 18\" />\n </svg>\n </span>\n )}\n <span class=\"hb-select__arrow\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width={2} stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </span>\n </span>\n </div>\n {this.isOpen && (\n <div class=\"hb-select__dropdown\">\n <ul class=\"hb-select__menu\" role=\"listbox\" id={this.listboxId}>\n {/* allowCreate:用户输入文本不存在时,渲染创建项(置顶) */}\n {this.createOption && (\n <li class={{ 'hb-select__menu-item': true, 'hb-select__menu-item--create': true }} role=\"option\" aria-selected=\"false\" onClick={this.handleCreate}>\n <span class=\"hb-select__menu-item-label\">创建「{this.createOption.label}」</span>\n </li>\n )}\n {displayOptions.length === 0 && !this.createOption ? (\n <li class=\"hb-select__menu-item hb-select__menu-item--empty\" aria-disabled=\"true\">\n 无数据\n </li>\n ) : (\n displayOptions.map((option, index) => (\n <li\n id={`${this.listboxId}-opt-${index}`}\n class={{\n 'hb-select__menu-item': true,\n 'hb-select__menu-item--selected': this.isSelected(option),\n 'hb-select__menu-item--disabled': option.disabled,\n 'hb-select__menu-item--divided': option.divided,\n 'hb-select__menu-item--active': index === this.activeOptionIndex,\n }}\n role=\"option\"\n aria-selected={this.isSelected(option) ? 'true' : 'false'}\n aria-disabled={option.disabled ? 'true' : undefined}\n onClick={() => this.handleSelect(option)}\n >\n {this.multiple && (\n <span class=\"hb-select__menu-item-checkbox\" aria-hidden=\"true\">\n {this.isSelected(option) ? (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width={3} stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12l5 5L20 7\" />\n </svg>\n ) : null}\n </span>\n )}\n <span class=\"hb-select__menu-item-label\">{option.label}</span>\n </li>\n ))\n )}\n </ul>\n </div>\n )}\n </div>\n );\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1 +0,0 @@
1
- {"version":3,"names":["formItemCss","HbFormItemStyle0","FormItem","prop","label","labelWidth","required","rules","size","errors","isvalidating","componentDidLoad","this","el","dispatchEvent","CustomEvent","detail","validate","resetValue","getValue","bubbles","composed","addEventListener","handleChildChange","disconnectedCallback","removeEventListener","length","formRules","form","closest","formLevelRules","isRequired","some","r","resolvedLabelPosition","labelPosition","async","value","rule","isEmpty","undefined","Array","isArray","push","message","pattern","ok","RegExp","test","String","type","min","max","validator","err","input","querySelector","modelValue","render","labelTop","hasError","labelId","h","key","class","id","style","width","title","role"],"sources":["src/components/Form/form-item.css?tag=hb-form-item&encapsulation=shadow","src/components/Form/FormItem.tsx"],"sourcesContent":[":host {\n display: block;\n}\n\n.hb-form-item {\n display: flex;\n align-items: flex-start;\n gap: var(--hb-spacing-sm);\n}\n\n/* label 位置:顶部(对齐 antd label-position=\"top\")——\n 需要 Form 通过 class 下发,但 FormItem 在独立使用时也支持自身包装。\n 这里用 :host([data-label-position=\"top\"]) 兜底,Form 可通过 setting attribute 生效。 */\n:host([data-label-top]) .hb-form-item,\n.hb-form-item--label-top {\n flex-direction: column;\n align-items: stretch;\n gap: var(--hb-spacing-xs);\n}\n\n.hb-form-item__label {\n display: inline-flex;\n align-items: center;\n justify-content: flex-end;\n height: var(--hb-size-default, 32px);\n font-size: var(--hb-font-size-sm, 14px);\n color: var(--hb-color-text);\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* label-position=top 时标签左对齐 */\n:host([data-label-top]) .hb-form-item__label,\n.hb-form-item--label-top .hb-form-item__label {\n justify-content: flex-start;\n height: auto;\n padding-bottom: var(--hb-spacing-xxs, 2px);\n}\n\n/* label-position=left 时标签左对齐 */\n.hb-form-item--label-left .hb-form-item__label {\n justify-content: flex-start;\n}\n\n.hb-form-item__required {\n color: var(--hb-color-danger);\n margin-right: 4px;\n font-size: var(--hb-font-size-sm, 14px);\n font-family: SimSun, sans-serif;\n}\n\n.hb-form-item__content {\n flex: 1;\n position: relative;\n min-width: 0;\n}\n\n.hb-form-item__error {\n font-size: var(--hb-font-size-xs, 12px);\n color: var(--hb-color-danger);\n margin-top: 4px;\n line-height: 1.5;\n min-height: 0;\n animation: hb-form-error-fade-in 0.2s ease-out;\n}\n\n@keyframes hb-form-error-fade-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* ============================================================\n * 响应式(移动端):小屏强制 label 顶部布局,\n * 避免标签列在窄屏被挤压。覆盖 hb-form-item--label-left/right。\n * ============================================================ */\n@media (max-width: 575.98px) {\n .hb-form-item {\n flex-direction: column !important;\n align-items: stretch !important;\n gap: var(--hb-spacing-xs) !important;\n }\n\n .hb-form-item__label {\n justify-content: flex-start !important;\n height: auto !important;\n padding-bottom: var(--hb-spacing-xxs, 2px) !important;\n }\n}\n","import { Component, h, Prop, State, Element } from '@stencil/core';\n\n/**\n * FormItem 表单项组件\n * 表单中的每一项\n */\n@Component({\n tag: 'hb-form-item',\n styleUrl: 'form-item.css',\n shadow: true,\n})\nexport class FormItem {\n @Element() el: HTMLElement;\n\n /** 字段名 */\n @Prop() prop: string = '';\n\n /** 标签文本 */\n @Prop() label: string = '';\n\n /** 标签宽度 */\n @Prop() labelWidth?: string;\n\n /** 是否必填 */\n @Prop() required: boolean = false;\n\n /** 该表单项的验证规则 */\n @Prop() rules: any[] = [];\n\n /** 表单尺寸 */\n @Prop() size: 'large' | 'default' | 'small' = 'default';\n\n @State() errors: string[] = [];\n @State() isvalidating: boolean = false;\n\n /** hb-form 在 componentWillLoad 注册的事件监听;FormItem 在 componentDidLoad 派发,\n * 但 componentWillLoad 早于子组件的 componentDidLoad,因此注册一定先到。\n * 这里还监听 hbFieldReset,便于 Form.resetFields 主动广播。 */\n\n componentDidLoad() {\n // 向父级 hb-form 注册\n this.el.dispatchEvent(\n new CustomEvent('hbFormFieldRegister', {\n detail: {\n prop: this.prop,\n validate: this.validate,\n resetValue: this.resetValue,\n getValue: this.getValue,\n },\n bubbles: true,\n composed: true, // 穿越 Shadow DOM 边界(FormItem 自身 shadow -> Form host)\n }),\n );\n\n // 子控件值变化时清错(对齐 antd:合法输入即移除错误提示)\n this.el.addEventListener('hbChange', this.handleChildChange);\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('hbChange', this.handleChildChange);\n }\n\n /** 子控件 hbChange 时,若当前有错误则重新校验以尽早清除错误提示 */\n private handleChildChange = () => {\n if (this.errors.length > 0) {\n void this.validate();\n }\n };\n\n private get formRules(): any[] {\n // 合并 prop 对应的 form rules 和自身 rules\n const form = this.el.closest('hb-form');\n const formLevelRules = form ? (form as any).rules?.[this.prop] || [] : [];\n return [...formLevelRules, ...this.rules];\n }\n\n /** 是否渲染必填星号:显式 required 或任一规则带 required(对齐 antd 自动推断) */\n private get isRequired(): boolean {\n if (this.required) return true;\n return this.formRules.some(r => r && r.required);\n }\n\n /** 继承父 hb-form 的 labelPosition(FormItem 自身无此 prop,避免与 Form 冲突时以 Form 为准) */\n private get resolvedLabelPosition(): 'left' | 'right' | 'top' {\n const form = this.el.closest('hb-form') as any;\n return (form && form.labelPosition) || 'right';\n }\n\n /**\n * 验证此字段\n */\n @Prop() validate = async (): Promise<string[]> => {\n const value = this.getValue();\n const rules = this.formRules;\n\n if (rules.length === 0) {\n this.errors = [];\n return [];\n }\n\n this.isvalidating = true;\n const errors: string[] = [];\n\n for (const rule of rules) {\n // 必填校验:空字符串 / null / undefined / 空数组均视为\"未填\"\n const isEmpty = value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0);\n if (rule.required && isEmpty) {\n errors.push(rule.message || `${this.label || this.prop}不能为空`);\n continue;\n }\n // 非必填且为空时,跳过后续格式校验(与 antd 行为一致)\n if (isEmpty) continue;\n if (rule.pattern) {\n const ok = typeof rule.pattern === 'string' ? new RegExp(rule.pattern).test(String(value)) : rule.pattern.test(String(value));\n if (!ok) {\n errors.push(rule.message || `${this.label || this.prop}格式不正确`);\n continue;\n }\n }\n if (rule.type === 'email') {\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(String(value))) {\n errors.push(rule.message || `${this.label || this.prop}格式不正确`);\n continue;\n }\n }\n if (rule.min !== undefined && typeof value === 'string' && value.length < rule.min) {\n errors.push(rule.message || `${this.label || this.prop}最少${rule.min}个字符`);\n continue;\n }\n if (rule.max !== undefined && typeof value === 'string' && value.length > rule.max) {\n errors.push(rule.message || `${this.label || this.prop}最多${rule.max}个字符`);\n continue;\n }\n if (rule.validator) {\n try {\n await rule.validator(value, this.formRules);\n } catch (err: any) {\n errors.push(err.message || rule.message || `${this.label || this.prop}验证失败`);\n }\n }\n }\n\n this.errors = errors;\n this.isvalidating = false;\n return errors;\n };\n\n @Prop() resetValue = () => {\n this.errors = [];\n };\n\n @Prop() getValue = (): any => {\n // 覆盖所有表单控件:input/select/cascader/date 系列/数字/checkbox/radio/switch/slider/color-picker/rate/upload\n const input = this.el.querySelector(\n 'hb-input, hb-select, hb-cascader, hb-date-picker, hb-date-time-picker, hb-date-range-picker, ' +\n 'hb-input-number, hb-textarea, hb-checkbox, hb-checkbox-group, hb-radio-group, hb-switch, ' +\n 'hb-slider, hb-color-picker, hb-rate, hb-time-picker, hb-time-select',\n );\n if (input) {\n return (input as any).modelValue;\n }\n return undefined;\n };\n\n render() {\n const labelTop = this.resolvedLabelPosition === 'top';\n const hasError = this.errors.length > 0;\n // A4:label 与内容关联。子控件是 slot(id 未知),无法用 label.for;\n // 改用 content 容器 role=group + aria-labelledby 指向 label id,\n // 把 label 语义绑到整组;并用 aria-required / aria-invalid 暴露必填与校验态(读屏可感知,\n // 不再仅靠红色 + 隐藏星号)。\n const labelId = this.label ? `hb-form-item__label-${this.prop || 'x'}` : undefined;\n return (\n <div\n class={{\n 'hb-form-item': true,\n 'hb-form-item--error': hasError,\n 'hb-form-item--validating': this.isvalidating,\n 'hb-form-item--required': this.isRequired,\n 'hb-form-item--label-top': labelTop,\n 'hb-form-item--label-left': this.resolvedLabelPosition === 'left',\n }}\n >\n {this.label && (\n <label id={labelId} class=\"hb-form-item__label\" style={this.labelWidth ? { width: this.labelWidth } : undefined} title={this.label}>\n {this.isRequired && (\n <span class=\"hb-form-item__required\" aria-hidden=\"true\">\n *\n </span>\n )}\n {this.label}\n </label>\n )}\n <div class=\"hb-form-item__content\" role=\"group\" aria-labelledby={labelId} aria-required={this.isRequired ? 'true' : undefined} aria-invalid={hasError ? 'true' : undefined}>\n <slot />\n {hasError && (\n <div class=\"hb-form-item__error\" role=\"alert\">\n {this.errors[0]}\n </div>\n )}\n </div>\n </div>\n );\n }\n}\n"],"mappings":"kDAAA,MAAMA,EAAc,s/NACpB,MAAAC,EAAeD,E,MCUFE,EAAQ,M,iDAIXC,KAAe,GAGfC,MAAgB,GAGhBC,WAGAC,SAAoB,MAGpBC,MAAe,GAGfC,KAAsC,UAErCC,OAAmB,GACnBC,aAAwB,MAMjC,gBAAAC,GAEEC,KAAKC,GAAGC,cACN,IAAIC,YAAY,sBAAuB,CACrCC,OAAQ,CACNb,KAAMS,KAAKT,KACXc,SAAUL,KAAKK,SACfC,WAAYN,KAAKM,WACjBC,SAAUP,KAAKO,UAEjBC,QAAS,KACTC,SAAU,QAKdT,KAAKC,GAAGS,iBAAiB,WAAYV,KAAKW,kB,CAG5C,oBAAAC,GACEZ,KAAKC,GAAGY,oBAAoB,WAAYb,KAAKW,kB,CAIvCA,kBAAoB,KAC1B,GAAIX,KAAKH,OAAOiB,OAAS,EAAG,MACrBd,KAAKK,U,GAId,aAAYU,GAEV,MAAMC,EAAOhB,KAAKC,GAAGgB,QAAQ,WAC7B,MAAMC,EAAiBF,EAAQA,EAAarB,QAAQK,KAAKT,OAAS,GAAK,GACvE,MAAO,IAAI2B,KAAmBlB,KAAKL,M,CAIrC,cAAYwB,GACV,GAAInB,KAAKN,SAAU,OAAO,KAC1B,OAAOM,KAAKe,UAAUK,MAAKC,GAAKA,GAAKA,EAAE3B,U,CAIzC,yBAAY4B,GACV,MAAMN,EAAOhB,KAAKC,GAAGgB,QAAQ,WAC7B,OAAQD,GAAQA,EAAKO,eAAkB,O,CAMjClB,SAAWmB,UACjB,MAAMC,EAAQzB,KAAKO,WACnB,MAAMZ,EAAQK,KAAKe,UAEnB,GAAIpB,EAAMmB,SAAW,EAAG,CACtBd,KAAKH,OAAS,GACd,MAAO,E,CAGTG,KAAKF,aAAe,KACpB,MAAMD,EAAmB,GAEzB,IAAK,MAAM6B,KAAQ/B,EAAO,CAExB,MAAMgC,EAAUF,IAAUG,WAAaH,IAAU,MAAQA,IAAU,IAAOI,MAAMC,QAAQL,IAAUA,EAAMX,SAAW,EACnH,GAAIY,EAAKhC,UAAYiC,EAAS,CAC5B9B,EAAOkC,KAAKL,EAAKM,SAAW,GAAGhC,KAAKR,OAASQ,KAAKT,YAClD,Q,CAGF,GAAIoC,EAAS,SACb,GAAID,EAAKO,QAAS,CAChB,MAAMC,SAAYR,EAAKO,UAAY,SAAW,IAAIE,OAAOT,EAAKO,SAASG,KAAKC,OAAOZ,IAAUC,EAAKO,QAAQG,KAAKC,OAAOZ,IACtH,IAAKS,EAAI,CACPrC,EAAOkC,KAAKL,EAAKM,SAAW,GAAGhC,KAAKR,OAASQ,KAAKT,aAClD,Q,EAGJ,GAAImC,EAAKY,OAAS,QAAS,CACzB,IAAK,6BAA6BF,KAAKC,OAAOZ,IAAS,CACrD5B,EAAOkC,KAAKL,EAAKM,SAAW,GAAGhC,KAAKR,OAASQ,KAAKT,aAClD,Q,EAGJ,GAAImC,EAAKa,MAAQX,kBAAoBH,IAAU,UAAYA,EAAMX,OAASY,EAAKa,IAAK,CAClF1C,EAAOkC,KAAKL,EAAKM,SAAW,GAAGhC,KAAKR,OAASQ,KAAKT,SAASmC,EAAKa,UAChE,Q,CAEF,GAAIb,EAAKc,MAAQZ,kBAAoBH,IAAU,UAAYA,EAAMX,OAASY,EAAKc,IAAK,CAClF3C,EAAOkC,KAAKL,EAAKM,SAAW,GAAGhC,KAAKR,OAASQ,KAAKT,SAASmC,EAAKc,UAChE,Q,CAEF,GAAId,EAAKe,UAAW,CAClB,UACQf,EAAKe,UAAUhB,EAAOzB,KAAKe,U,CACjC,MAAO2B,GACP7C,EAAOkC,KAAKW,EAAIV,SAAWN,EAAKM,SAAW,GAAGhC,KAAKR,OAASQ,KAAKT,W,GAKvES,KAAKH,OAASA,EACdG,KAAKF,aAAe,MACpB,OAAOD,CAAM,EAGPS,WAAa,KACnBN,KAAKH,OAAS,EAAE,EAGVU,SAAW,KAEjB,MAAMoC,EAAQ3C,KAAKC,GAAG2C,cACpB,gGACE,4FACA,uEAEJ,GAAID,EAAO,CACT,OAAQA,EAAcE,U,CAExB,OAAOjB,SAAS,EAGlB,MAAAkB,GACE,MAAMC,EAAW/C,KAAKsB,wBAA0B,MAChD,MAAM0B,EAAWhD,KAAKH,OAAOiB,OAAS,EAKtC,MAAMmC,EAAUjD,KAAKR,MAAQ,uBAAuBQ,KAAKT,MAAQ,MAAQqC,UACzE,OACEsB,EAAA,OAAAC,IAAA,2CACEC,MAAO,CACL,eAAgB,KAChB,sBAAuBJ,EACvB,2BAA4BhD,KAAKF,aACjC,yBAA0BE,KAAKmB,WAC/B,0BAA2B4B,EAC3B,2BAA4B/C,KAAKsB,wBAA0B,SAG5DtB,KAAKR,OACJ0D,EAAA,SAAAC,IAAA,2CAAOE,GAAIJ,EAASG,MAAM,sBAAsBE,MAAOtD,KAAKP,WAAa,CAAE8D,MAAOvD,KAAKP,YAAemC,UAAW4B,MAAOxD,KAAKR,OAC1HQ,KAAKmB,YACJ+B,EAAA,QAAAC,IAAA,2CAAMC,MAAM,yBAAwB,cAAa,QAAM,KAIxDpD,KAAKR,OAGV0D,EAAA,OAAAC,IAAA,2CAAKC,MAAM,wBAAwBK,KAAK,QAAO,kBAAkBR,EAAO,gBAAiBjD,KAAKmB,WAAa,OAASS,UAAS,eAAgBoB,EAAW,OAASpB,WAC/JsB,EAAA,QAAAC,IAAA,6CACCH,GACCE,EAAA,OAAAC,IAAA,2CAAKC,MAAM,sBAAsBK,KAAK,SACnCzD,KAAKH,OAAO,K","ignoreList":[]}
@@ -1 +0,0 @@
1
- {"version":3,"names":["formCss","HbFormStyle0","Form","model","rules","labelPosition","labelWidth","inline","size","disabled","fields","fieldRegistry","Map","handleFieldRegister","e","prop","validate","resetValue","getValue","detail","this","set","componentWillLoad","el","addEventListener","disconnectedCallback","removeEventListener","async","results","Promise","all","Array","from","values","map","field","errors","length","every","Boolean","resetFields","forEach","render","h","key","class","onSubmit","preventDefault","novalidate"],"sources":["src/components/Form/form.css?tag=hb-form&encapsulation=shadow","src/components/Form/Form.tsx"],"sourcesContent":[":host {\n display: block;\n}\n\n.hb-form {\n display: flex;\n flex-direction: column;\n gap: var(--hb-spacing-md);\n}\n\n.hb-form--inline {\n flex-direction: row;\n flex-wrap: wrap;\n gap: var(--hb-spacing-sm) var(--hb-spacing-lg);\n align-items: flex-start;\n}\n","import { Component, h, Prop, State, Element } from '@stencil/core';\n\ninterface FormFieldRegistration {\n prop: string;\n validate: () => Promise<string[]>;\n resetValue: () => void;\n getValue: () => any;\n}\n\n/**\n * Form 表单组件\n * 由输入框、选择器、单选框、多选框等控件组成,配合表单校验\n */\n@Component({\n tag: 'hb-form',\n styleUrl: 'form.css',\n shadow: true,\n})\nexport class Form {\n @Element() el: HTMLElement;\n\n /** 表单数据对象 */\n @Prop() model: Record<string, any> = {};\n\n /** 表单验证规则 */\n @Prop() rules: Record<string, any[]> = {};\n\n /** 标签位置 */\n @Prop() labelPosition: 'left' | 'right' | 'top' = 'right';\n\n /** 标签宽度 */\n @Prop() labelWidth: string = '80px';\n\n /** 是否行内表单 */\n @Prop() inline: boolean = false;\n\n /** 表单尺寸 */\n @Prop() size: 'large' | 'default' | 'small' = 'default';\n\n /** 是否禁用 */\n @Prop() disabled: boolean = false;\n\n @State() fields: FormFieldRegistration[] = [];\n\n private fieldRegistry = new Map<string, FormFieldRegistration>();\n\n // 用 componentWillLoad 而非 componentDidLoad 注册监听器:\n // Stencil 生命周期里子组件(FormItem)的 componentDidLoad 先于父组件(Form)触发,\n // 若在 componentDidLoad 才挂监听,会错过子项派发的 hbFormFieldRegister,\n // 导致 fieldRegistry 为空、validate() 永远返回 true(真实浏览器里复现,mock-doc 被 waitForChanges 掩盖)。\n // L1:抽命名 handler 以便 disconnectedCallback 正确 remove(修复前匿名箭头无法 remove)。\n private handleFieldRegister = ((e: CustomEvent) => {\n const { prop, validate, resetValue, getValue } = e.detail;\n this.fieldRegistry.set(prop, { prop, validate, resetValue, getValue });\n }) as EventListener;\n\n componentWillLoad() {\n this.el.addEventListener('hbFormFieldRegister', this.handleFieldRegister);\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('hbFormFieldRegister', this.handleFieldRegister);\n }\n\n /**\n * 验证整个表单\n * @returns 是否验证通过\n */\n @Prop() validate = async (): Promise<boolean> => {\n const results = await Promise.all(\n Array.from(this.fieldRegistry.values()).map(async field => {\n const errors = await field.validate();\n return errors.length === 0;\n }),\n );\n return results.every(Boolean);\n };\n\n /**\n * 重置表单\n */\n @Prop() resetFields = (): void => {\n this.fieldRegistry.forEach(field => field.resetValue());\n };\n\n render() {\n return (\n <form\n class={{\n 'hb-form': true,\n [`hb-form--label-${this.labelPosition}`]: true,\n 'hb-form--inline': this.inline,\n }}\n onSubmit={e => e.preventDefault()}\n novalidate\n >\n <slot />\n </form>\n );\n }\n}\n"],"mappings":"kDAAA,MAAMA,EAAU,8wLAChB,MAAAC,EAAeD,E,MCiBFE,EAAI,M,iDAIPC,MAA6B,GAG7BC,MAA+B,GAG/BC,cAA0C,QAG1CC,WAAqB,OAGrBC,OAAkB,MAGlBC,KAAsC,UAGtCC,SAAoB,MAEnBC,OAAkC,GAEnCC,cAAgB,IAAIC,IAOpBC,oBAAwBC,IAC9B,MAAMC,KAAEA,EAAIC,SAAEA,EAAQC,WAAEA,EAAUC,SAAEA,GAAaJ,EAAEK,OACnDC,KAAKT,cAAcU,IAAIN,EAAM,CAAEA,OAAMC,WAAUC,aAAYC,YAC5D,EAED,iBAAAI,GACEF,KAAKG,GAAGC,iBAAiB,sBAAuBJ,KAAKP,oB,CAGvD,oBAAAY,GACEL,KAAKG,GAAGG,oBAAoB,sBAAuBN,KAAKP,oB,CAOlDG,SAAWW,UACjB,MAAMC,QAAgBC,QAAQC,IAC5BC,MAAMC,KAAKZ,KAAKT,cAAcsB,UAAUC,KAAIP,MAAMQ,IAChD,MAAMC,QAAeD,EAAMnB,WAC3B,OAAOoB,EAAOC,SAAW,CAAC,KAG9B,OAAOT,EAAQU,MAAMC,QAAQ,EAMvBC,YAAc,KACpBpB,KAAKT,cAAc8B,SAAQN,GAASA,EAAMlB,cAAa,EAGzD,MAAAyB,GACE,OACEC,EAAA,QAAAC,IAAA,2CACEC,MAAO,CACL,UAAW,KACX,CAAC,kBAAkBzB,KAAKf,iBAAkB,KAC1C,kBAAmBe,KAAKb,QAE1BuC,SAAUhC,GAAKA,EAAEiC,iBACjBC,WAAU,MAEVL,EAAA,QAAAC,IAAA,6C","ignoreList":[]}