@greypan/web-ui 8.0.0 → 9.0.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 (189) hide show
  1. package/README.CN.md +277 -146
  2. package/README.md +323 -191
  3. package/dist/assets/glass.js +1 -1
  4. package/dist/assets/glass.js.map +1 -1
  5. package/dist/assets/menu-portal.js +1 -1
  6. package/dist/assets/menu-portal.js.map +1 -1
  7. package/dist/assets/selection-control.js +1 -1
  8. package/dist/assets/selection-control.js.map +1 -1
  9. package/dist/assets/toast-containers.js +1 -1
  10. package/dist/assets/toast-containers.js.map +1 -1
  11. package/dist/components/autocomplete/index.d.ts +3 -2
  12. package/dist/components/autocomplete/index.js +46 -50
  13. package/dist/components/autocomplete/index.js.map +1 -1
  14. package/dist/components/autocomplete/style.js +1 -1
  15. package/dist/components/autocomplete/style.js.map +1 -1
  16. package/dist/components/avatar/index.js.map +1 -1
  17. package/dist/components/avatar/style.js +1 -1
  18. package/dist/components/avatar/style.js.map +1 -1
  19. package/dist/components/back-top/style.js +1 -1
  20. package/dist/components/back-top/style.js.map +1 -1
  21. package/dist/components/badge/style.js +1 -1
  22. package/dist/components/badge/style.js.map +1 -1
  23. package/dist/components/button/index.d.ts +368 -1
  24. package/dist/components/button/index.js +56 -36
  25. package/dist/components/button/index.js.map +1 -1
  26. package/dist/components/button/style.js +1 -1
  27. package/dist/components/button/style.js.map +1 -1
  28. package/dist/components/button-group/index.js +0 -1
  29. package/dist/components/button-group/index.js.map +1 -1
  30. package/dist/components/button-group/style.js +1 -1
  31. package/dist/components/button-group/style.js.map +1 -1
  32. package/dist/components/checkbox/index.d.ts +3 -0
  33. package/dist/components/checkbox/index.js +35 -37
  34. package/dist/components/checkbox/index.js.map +1 -1
  35. package/dist/components/checkbox/style.js +1 -1
  36. package/dist/components/checkbox/style.js.map +1 -1
  37. package/dist/components/checkbox-group/index.js +8 -8
  38. package/dist/components/checkbox-group/style.js +1 -1
  39. package/dist/components/checkbox-group/style.js.map +1 -1
  40. package/dist/components/context-menu/index.d.ts +167 -3
  41. package/dist/components/context-menu/index.js +162 -91
  42. package/dist/components/context-menu/index.js.map +1 -1
  43. package/dist/components/context-menu/scrim.js +6 -0
  44. package/dist/components/context-menu/scrim.js.map +1 -0
  45. package/dist/components/context-menu/style.js +1 -1
  46. package/dist/components/context-menu/style.js.map +1 -1
  47. package/dist/components/dialog/index.d.ts +9 -0
  48. package/dist/components/dialog/index.js +61 -46
  49. package/dist/components/dialog/index.js.map +1 -1
  50. package/dist/components/dialog/style.js +1 -1
  51. package/dist/components/dialog/style.js.map +1 -1
  52. package/dist/components/drawer/index.d.ts +45 -0
  53. package/dist/components/drawer/index.js +116 -52
  54. package/dist/components/drawer/index.js.map +1 -1
  55. package/dist/components/drawer/style.js +1 -1
  56. package/dist/components/drawer/style.js.map +1 -1
  57. package/dist/components/dropdown/index.d.ts +12 -0
  58. package/dist/components/dropdown/index.js +13 -10
  59. package/dist/components/dropdown/index.js.map +1 -1
  60. package/dist/components/dropdown-divider/style.js +1 -1
  61. package/dist/components/dropdown-divider/style.js.map +1 -1
  62. package/dist/components/dropdown-header/style.js +1 -1
  63. package/dist/components/dropdown-header/style.js.map +1 -1
  64. package/dist/components/dropdown-item/style.js +1 -1
  65. package/dist/components/dropdown-item/style.js.map +1 -1
  66. package/dist/components/editable-text/index.d.ts +41 -0
  67. package/dist/components/editable-text/index.js +55 -31
  68. package/dist/components/editable-text/index.js.map +1 -1
  69. package/dist/components/empty/index.d.ts +21 -4
  70. package/dist/components/empty/index.js +37 -32
  71. package/dist/components/empty/index.js.map +1 -1
  72. package/dist/components/empty/style.js +1 -1
  73. package/dist/components/empty/style.js.map +1 -1
  74. package/dist/components/icon/index.js +7 -7
  75. package/dist/components/icon/index.js.map +1 -1
  76. package/dist/components/image-preview/index.js +9 -2
  77. package/dist/components/image-preview/index.js.map +1 -1
  78. package/dist/components/image-preview/style.js +1 -1
  79. package/dist/components/image-preview/style.js.map +1 -1
  80. package/dist/components/index.js +23 -23
  81. package/dist/components/input/index.d.ts +7 -0
  82. package/dist/components/input/index.js +34 -29
  83. package/dist/components/input/index.js.map +1 -1
  84. package/dist/components/input/style.js +1 -1
  85. package/dist/components/input/style.js.map +1 -1
  86. package/dist/components/input-number/index.d.ts +1 -0
  87. package/dist/components/input-number/index.js +28 -26
  88. package/dist/components/input-number/index.js.map +1 -1
  89. package/dist/components/input-number/style.js +1 -1
  90. package/dist/components/input-number/style.js.map +1 -1
  91. package/dist/components/layout/index.d.ts +0 -3
  92. package/dist/components/layout/index.js +1 -1
  93. package/dist/components/layout/index.js.map +1 -1
  94. package/dist/components/layout/style.js +1 -1
  95. package/dist/components/layout/style.js.map +1 -1
  96. package/dist/components/option/style.js +1 -1
  97. package/dist/components/option/style.js.map +1 -1
  98. package/dist/components/popover/index.js.map +1 -1
  99. package/dist/components/popover/style.js +1 -1
  100. package/dist/components/popover/style.js.map +1 -1
  101. package/dist/components/radio/index.d.ts +3 -0
  102. package/dist/components/radio/index.js +23 -19
  103. package/dist/components/radio/index.js.map +1 -1
  104. package/dist/components/radio/style.js +1 -1
  105. package/dist/components/radio/style.js.map +1 -1
  106. package/dist/components/radio-group/index.js +8 -8
  107. package/dist/components/radio-group/style.js +1 -1
  108. package/dist/components/radio-group/style.js.map +1 -1
  109. package/dist/components/segmented/index.d.ts +5 -0
  110. package/dist/components/segmented/index.js +49 -32
  111. package/dist/components/segmented/index.js.map +1 -1
  112. package/dist/components/segmented/style.js +1 -1
  113. package/dist/components/segmented/style.js.map +1 -1
  114. package/dist/components/segmented-trigger/style.js +1 -1
  115. package/dist/components/segmented-trigger/style.js.map +1 -1
  116. package/dist/components/select/index.js +0 -1
  117. package/dist/components/select/index.js.map +1 -1
  118. package/dist/components/select/style.js +1 -1
  119. package/dist/components/select/style.js.map +1 -1
  120. package/dist/components/slider/index.js +9 -9
  121. package/dist/components/slider/style.js +1 -1
  122. package/dist/components/slider/style.js.map +1 -1
  123. package/dist/components/spinner/style.js +1 -1
  124. package/dist/components/spinner/style.js.map +1 -1
  125. package/dist/components/svg-draw-lines/index.js.map +1 -1
  126. package/dist/components/switch/index.d.ts +1 -0
  127. package/dist/components/switch/index.js +26 -24
  128. package/dist/components/switch/index.js.map +1 -1
  129. package/dist/components/switch/style.js +1 -1
  130. package/dist/components/switch/style.js.map +1 -1
  131. package/dist/components/textarea/index.d.ts +7 -1
  132. package/dist/components/textarea/index.js +45 -43
  133. package/dist/components/textarea/index.js.map +1 -1
  134. package/dist/components/textarea/style.js +1 -1
  135. package/dist/components/textarea/style.js.map +1 -1
  136. package/dist/components/theme/index.d.ts +9 -0
  137. package/dist/components/theme/index.js +128 -85
  138. package/dist/components/theme/index.js.map +1 -1
  139. package/dist/components/theme/style.js +1 -1
  140. package/dist/components/theme/style.js.map +1 -1
  141. package/dist/components/toast/style.js +1 -1
  142. package/dist/components/toast/style.js.map +1 -1
  143. package/dist/components/toast/toast.js +8 -8
  144. package/dist/components/toast/toast.js.map +1 -1
  145. package/dist/components/tooltip/index.js.map +1 -1
  146. package/dist/components/tooltip/style.js +1 -1
  147. package/dist/components/tooltip/style.js.map +1 -1
  148. package/dist/icons/generated/akar-icons-check.d.ts +6 -0
  149. package/dist/icons/generated/akar-icons-check.js +10 -0
  150. package/dist/icons/generated/akar-icons-check.js.map +1 -0
  151. package/dist/icons/generated/akar-icons-circle-check.d.ts +6 -0
  152. package/dist/icons/generated/akar-icons-circle-check.js +10 -0
  153. package/dist/icons/generated/akar-icons-circle-check.js.map +1 -0
  154. package/dist/icons/generated/bi-check-lg.d.ts +6 -0
  155. package/dist/icons/generated/bi-check-lg.js +10 -0
  156. package/dist/icons/generated/bi-check-lg.js.map +1 -0
  157. package/dist/icons/generated/bi-check.d.ts +6 -0
  158. package/dist/icons/generated/bi-check.js +10 -0
  159. package/dist/icons/generated/bi-check.js.map +1 -0
  160. package/dist/icons/generated/fluent-tag-search-24-regular.d.ts +6 -0
  161. package/dist/icons/generated/fluent-tag-search-24-regular.js +10 -0
  162. package/dist/icons/generated/fluent-tag-search-24-regular.js.map +1 -0
  163. package/dist/icons/generated/mdi-tag-search-outline.d.ts +6 -0
  164. package/dist/icons/generated/mdi-tag-search-outline.js +10 -0
  165. package/dist/icons/generated/mdi-tag-search-outline.js.map +1 -0
  166. package/dist/icons/index.d.ts +6 -0
  167. package/dist/icons/index.js +7 -1
  168. package/dist/shared/element-height/index.js +47 -0
  169. package/dist/shared/element-height/index.js.map +1 -0
  170. package/dist/shared/form-association/field-id.js +9 -0
  171. package/dist/shared/form-association/field-id.js.map +1 -0
  172. package/dist/shared/form-association/index.js +9 -1
  173. package/dist/shared/form-association/index.js.map +1 -1
  174. package/dist/shared/label-emptiness/index.js +45 -0
  175. package/dist/shared/label-emptiness/index.js.map +1 -0
  176. package/dist/shared/menu-portal/menu-portal.js +11 -11
  177. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  178. package/dist/shared/menu-portal/menu-tree.js +2 -2
  179. package/dist/shared/menu-portal/menu-tree.js.map +1 -1
  180. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  181. package/dist/shared/overlay/native-dialog-presence.js +23 -11
  182. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  183. package/dist/shared/overlay/nested-drawer-layers.js +30 -23
  184. package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
  185. package/dist/shared/scroll-lock/scroll-lock.js +3 -3
  186. package/dist/shared/scroll-lock/scroll-lock.js.map +1 -1
  187. package/dist/shared/theme/root-sync.js +24 -18
  188. package/dist/shared/theme/root-sync.js.map +1 -1
  189. package/package.json +8 -4
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/svg-draw-lines/index.ts"],"sourcesContent":["import { html, LitElement, nothing, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { normalizeNumber } from '@/shared/normalize'\nimport { prefersReducedMotion } from '@/shared/theme/reduced-motion'\n\nimport style from './style.css?inline'\n\nconst GEOMETRY_SELECTOR = 'path, rect, circle, line, polyline, polygon, ellipse'\n\ninterface AnimationRun {\n reverse: boolean\n animations: Animation[]\n /** 本次运行写过的「空白」dash 值,按元素记;收回收尾要把它留在 DOM 上。 */\n blankByElement: Map<SVGGeometryElement, string>\n patchedD: Map<SVGGeometryElement, string>\n}\n\nexport interface ReplayOptions {\n /** 沿原路径收回(从完整描边画回空白),默认 false 即揭示方向。 */\n reverse?: boolean\n}\n\n@customElement('web-ui-svg-draw-lines')\nexport class WebUiSvgDrawLines extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Number, reflect: true })\n get duration(): number {\n return this._duration\n }\n set duration(v: number) {\n const old = this._duration\n this._duration = normalizeNumber(v, 0, 30000, 1000)\n this.requestUpdate('duration', old)\n }\n private _duration = 1000\n\n @property({ type: String, reflect: true }) easing = 'linear'\n\n /** 关掉「首次内容稳定时自动播放一次」,交给调用方用 replay() 决定何时播。 */\n @property({ type: Boolean, reflect: true, attribute: 'no-autoplay' }) noAutoplay = false\n\n private _activeRun: AnimationRun | undefined\n private _hasAutoPlayed = false\n /*\n * 消费者自己写在目标元素上的 dash 值,只在第一次碰到该元素时取一次。运行会把这两个属性改成\n * 自己的空白值,所以不能从「上一次运行之后」的状态重新取,否则画入收尾会把空白当成原值。\n */\n private readonly _authoredDash = new WeakMap<SVGGeometryElement, { dasharray: string; dashoffset: string }>()\n /** 收回留在元素上的「空白」,只有内联那两个值都还是我们自己写的那一份时才算数。 */\n private readonly _stickyBlank = new WeakMap<SVGGeometryElement, { dasharray: string; dashoffset: string }>()\n\n /**\n * 停止当前播放并重新开始。每次调用重新收集子树中的几何元素。\n * 所有元素同时开始并行的 stroke-dashoffset 动画。\n * options.reverse 为 true 时沿原路径收回。\n * 无目标或当前主题范围启用 reduced motion 时立即 resolve。\n */\n async replay(options: ReplayOptions = {}): Promise<void> {\n const reverse = options.reverse === true\n\n this.cancelAll()\n\n const targets = this.collectGeometryElements()\n\n if (prefersReducedMotion(this)) {\n // 不播,但上一次收回提交的空白要清掉:reduced motion 下可见性完全交给消费者的 opacity,\n // 留着空白就会出现「aria-checked=true 却看不见勾」,而之后每次 replay 都会同样早退、无法自愈。\n for (const el of targets) this.clearStickyBlank(el)\n return\n }\n\n if (targets.length === 0) return\n\n const run: AnimationRun = {\n reverse,\n animations: [],\n blankByElement: new Map(),\n patchedD: new Map()\n }\n this._activeRun = run\n\n await Promise.all(targets.map(el => this.animateElement(el, run, reverse)))\n\n // A later replay owns the current DOM state and must not be cleaned up here.\n if (this._activeRun !== run) return\n\n this.finishRun(run)\n }\n\n private cancelAll() {\n if (this._activeRun) this.finishRun(this._activeRun)\n }\n\n /*\n * 收尾分两个方向:画线还原消费者自己写的 dash(描边完整可见),收回把空白留在内联样式上\n * (整条路径落进 dash 间隙,看不见)。收回末态必须是 DOM 状态,不能是「一条还活着的\n * fill:forwards 动画」——元素被摘走再挂回(列表 key 重排、teleport)时,还原成完整描边的\n * 那份内联值会让勾当场复现,而控件已经是未勾选。\n */\n private finishRun(run: AnimationRun) {\n run.animations.forEach(anim => anim.cancel())\n\n for (const [el, d] of run.patchedD) {\n el.setAttribute('d', d)\n }\n\n for (const [el, blank] of run.blankByElement) {\n const authored = this._authoredDash.get(el)\n if (run.reverse) {\n el.style.strokeDasharray = blank\n el.style.strokeDashoffset = blank\n // 存回读值而不是写入值:CSSOM 会按自己的精度序列化,撤销时要按同一份文本比对。\n this._stickyBlank.set(el, { dasharray: el.style.strokeDasharray, dashoffset: el.style.strokeDashoffset })\n } else {\n el.style.strokeDasharray = authored?.dasharray ?? ''\n el.style.strokeDashoffset = authored?.dashoffset ?? ''\n this._stickyBlank.delete(el)\n }\n }\n\n if (this._activeRun === run) this._activeRun = undefined\n }\n\n /** 只撤销我们自己写进去的那一份空白;消费者后来动过内联的任一个值就不碰。 */\n private clearStickyBlank(el: SVGGeometryElement) {\n const blank = this._stickyBlank.get(el)\n if (\n blank === undefined ||\n el.style.strokeDasharray !== blank.dasharray ||\n el.style.strokeDashoffset !== blank.dashoffset\n )\n return\n\n const authored = this._authoredDash.get(el)\n el.style.strokeDasharray = authored?.dasharray ?? ''\n el.style.strokeDashoffset = authored?.dashoffset ?? ''\n this._stickyBlank.delete(el)\n }\n\n /**\n * 深度遍历组件的 light DOM 子节点与开放 Shadow Root,\n * 收集所有 SVGGeometryElement。closed shadow root 跳过。\n */\n private collectGeometryElements(): SVGGeometryElement[] {\n const elements: SVGGeometryElement[] = []\n const seen = new Set<SVGGeometryElement>()\n\n const walk = (root: Node) => {\n if (root instanceof Element || root instanceof DocumentFragment) {\n root.querySelectorAll(GEOMETRY_SELECTOR).forEach(el => {\n // instanceof SVGGeometryElement 在 jsdom 中未定义,使用 duck-type 检查\n if (\n typeof (el as unknown as Record<string, unknown>).getTotalLength === 'function' &&\n !seen.has(el as unknown as SVGGeometryElement)\n ) {\n seen.add(el as unknown as SVGGeometryElement)\n elements.push(el as unknown as SVGGeometryElement)\n }\n })\n }\n\n if (root instanceof Element) {\n for (const child of root.children) walk(child)\n if (root.shadowRoot) walk(root.shadowRoot)\n } else if (root instanceof ShadowRoot) {\n for (const child of root.children) walk(child)\n }\n }\n\n for (const child of this.children) walk(child)\n return elements\n }\n\n private animateElement(el: SVGGeometryElement, run: AnimationRun, reverse: boolean): Promise<void> {\n return new Promise(resolve => {\n if (!this._authoredDash.has(el)) {\n this._authoredDash.set(el, {\n dasharray: el.style.strokeDasharray,\n dashoffset: el.style.strokeDashoffset\n })\n }\n\n // Gap fix for paths ending with Z/z\n if (el.tagName === 'path') {\n const d = el.getAttribute('d')\n if (d && /[Zz]\\s*$/.test(d) && !run.patchedD.has(el)) {\n run.patchedD.set(el, d)\n el.setAttribute('d', this.fixPathGap(el, d))\n }\n }\n\n const strokeWidth = parseFloat(getComputedStyle(el).strokeWidth) || 0\n const len = el.getTotalLength() + strokeWidth\n // 空白 = 整条路径落在 dash 间隙里;满 = 偏移归零。两个方向只是这两端换先后。\n const blank = `${len}`\n const full = '0'\n const from = reverse ? full : blank\n const to = reverse ? blank : full\n\n el.style.strokeDasharray = blank\n el.style.strokeDashoffset = from\n run.blankByElement.set(el, blank)\n\n const anim = el.animate([{ strokeDashoffset: from }, { strokeDashoffset: to }], {\n duration: this.duration,\n easing: this.easing,\n fill: 'forwards'\n })\n\n run.animations.push(anim)\n\n anim.onfinish = () => {\n resolve()\n }\n\n anim.oncancel = () => {\n resolve()\n }\n })\n }\n\n // Z → explicit line back to start + 0.1px extra, forcing render of the closing segment\n private fixPathGap(pathEl: SVGPathElement, d: string): string {\n pathEl.setAttribute('d', d)\n const p0 = pathEl.getPointAtLength(0)\n const p1 = pathEl.getPointAtLength(0.1)\n return d.replace(/[Zz]\\s*$/, `L${p0.x.toFixed(3)} ${p0.y.toFixed(3)} L${p1.x.toFixed(3)} ${p1.y.toFixed(3)}`)\n }\n\n // 首次 slot 内容稳定后自动播放一次;no-autoplay 下这一步交给调用方的 replay()。\n private handleSlotChange() {\n if (this.noAutoplay || this._hasAutoPlayed) return\n this._hasAutoPlayed = true\n void this.replay()\n }\n\n override disconnectedCallback() {\n this.cancelAll()\n super.disconnectedCallback()\n }\n\n override render() {\n return html`<slot @slotchange=${this.handleSlotChange}></slot>`\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-svg-draw-lines': WebUiSvgDrawLines\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/svg-draw-lines/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { normalizeNumber } from '@/shared/normalize'\nimport { prefersReducedMotion } from '@/shared/theme/reduced-motion'\n\nimport style from './style.css?inline'\n\nconst GEOMETRY_SELECTOR = 'path, rect, circle, line, polyline, polygon, ellipse'\n\ninterface AnimationRun {\n reverse: boolean\n animations: Animation[]\n /** 本次运行写过的「空白」dash 值,按元素记;收回收尾要把它留在 DOM 上。 */\n blankByElement: Map<SVGGeometryElement, string>\n patchedD: Map<SVGGeometryElement, string>\n}\n\nexport interface ReplayOptions {\n /** 沿原路径收回(从完整描边画回空白),默认 false 即揭示方向。 */\n reverse?: boolean\n}\n\n@customElement('web-ui-svg-draw-lines')\nexport class WebUiSvgDrawLines extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Number, reflect: true })\n get duration(): number {\n return this._duration\n }\n set duration(v: number) {\n const old = this._duration\n this._duration = normalizeNumber(v, 0, 30000, 1000)\n this.requestUpdate('duration', old)\n }\n private _duration = 1000\n\n @property({ type: String, reflect: true }) easing = 'linear'\n\n /** 关掉「首次内容稳定时自动播放一次」,交给调用方用 replay() 决定何时播。 */\n @property({ type: Boolean, reflect: true, attribute: 'no-autoplay' }) noAutoplay = false\n\n private _activeRun: AnimationRun | undefined\n private _hasAutoPlayed = false\n /*\n * 消费者自己写在目标元素上的 dash 值,只在第一次碰到该元素时取一次。运行会把这两个属性改成\n * 自己的空白值,所以不能从「上一次运行之后」的状态重新取,否则画入收尾会把空白当成原值。\n */\n private readonly _authoredDash = new WeakMap<SVGGeometryElement, { dasharray: string; dashoffset: string }>()\n /** 收回留在元素上的「空白」,只有内联那两个值都还是我们自己写的那一份时才算数。 */\n private readonly _stickyBlank = new WeakMap<SVGGeometryElement, { dasharray: string; dashoffset: string }>()\n\n /**\n * 停止当前播放并重新开始。每次调用重新收集子树中的几何元素。\n * 所有元素同时开始并行的 stroke-dashoffset 动画。\n * options.reverse 为 true 时沿原路径收回。\n * 无目标或当前主题范围启用 reduced motion 时立即 resolve。\n */\n async replay(options: ReplayOptions = {}): Promise<void> {\n const reverse = options.reverse === true\n\n this.cancelAll()\n\n const targets = this.collectGeometryElements()\n\n if (prefersReducedMotion(this)) {\n // 不播,但上一次收回提交的空白要清掉:reduced motion 下可见性完全交给消费者的 opacity,\n // 留着空白就会出现「aria-checked=true 却看不见勾」,而之后每次 replay 都会同样早退、无法自愈。\n for (const el of targets) this.clearStickyBlank(el)\n return\n }\n\n if (targets.length === 0) return\n\n const run: AnimationRun = {\n reverse,\n animations: [],\n blankByElement: new Map(),\n patchedD: new Map()\n }\n this._activeRun = run\n\n await Promise.all(targets.map(el => this.animateElement(el, run, reverse)))\n\n // A later replay owns the current DOM state and must not be cleaned up here.\n if (this._activeRun !== run) return\n\n this.finishRun(run)\n }\n\n private cancelAll() {\n if (this._activeRun) this.finishRun(this._activeRun)\n }\n\n /*\n * 收尾分两个方向:画线还原消费者自己写的 dash(描边完整可见),收回把空白留在内联样式上\n * (整条路径落进 dash 间隙,看不见)。收回末态必须是 DOM 状态,不能是「一条还活着的\n * fill:forwards 动画」——元素被摘走再挂回(列表 key 重排、teleport)时,还原成完整描边的\n * 那份内联值会让勾当场复现,而控件已经是未勾选。\n */\n private finishRun(run: AnimationRun) {\n run.animations.forEach(anim => anim.cancel())\n\n for (const [el, d] of run.patchedD) {\n el.setAttribute('d', d)\n }\n\n for (const [el, blank] of run.blankByElement) {\n const authored = this._authoredDash.get(el)\n if (run.reverse) {\n el.style.strokeDasharray = blank\n el.style.strokeDashoffset = blank\n // 存回读值而不是写入值:CSSOM 会按自己的精度序列化,撤销时要按同一份文本比对。\n this._stickyBlank.set(el, { dasharray: el.style.strokeDasharray, dashoffset: el.style.strokeDashoffset })\n } else {\n el.style.strokeDasharray = authored?.dasharray ?? ''\n el.style.strokeDashoffset = authored?.dashoffset ?? ''\n this._stickyBlank.delete(el)\n }\n }\n\n if (this._activeRun === run) this._activeRun = undefined\n }\n\n /** 只撤销我们自己写进去的那一份空白;消费者后来动过内联的任一个值就不碰。 */\n private clearStickyBlank(el: SVGGeometryElement) {\n const blank = this._stickyBlank.get(el)\n if (\n blank === undefined ||\n el.style.strokeDasharray !== blank.dasharray ||\n el.style.strokeDashoffset !== blank.dashoffset\n )\n return\n\n const authored = this._authoredDash.get(el)\n el.style.strokeDasharray = authored?.dasharray ?? ''\n el.style.strokeDashoffset = authored?.dashoffset ?? ''\n this._stickyBlank.delete(el)\n }\n\n /**\n * 深度遍历组件的 light DOM 子节点与开放 Shadow Root,\n * 收集所有 SVGGeometryElement。closed shadow root 跳过。\n */\n private collectGeometryElements(): SVGGeometryElement[] {\n const elements: SVGGeometryElement[] = []\n const seen = new Set<SVGGeometryElement>()\n\n const walk = (root: Node) => {\n if (root instanceof Element || root instanceof DocumentFragment) {\n root.querySelectorAll(GEOMETRY_SELECTOR).forEach(el => {\n // instanceof SVGGeometryElement 在 jsdom 中未定义,使用 duck-type 检查\n if (\n typeof (el as unknown as Record<string, unknown>).getTotalLength === 'function' &&\n !seen.has(el as unknown as SVGGeometryElement)\n ) {\n seen.add(el as unknown as SVGGeometryElement)\n elements.push(el as unknown as SVGGeometryElement)\n }\n })\n }\n\n if (root instanceof Element) {\n for (const child of root.children) walk(child)\n if (root.shadowRoot) walk(root.shadowRoot)\n } else if (root instanceof ShadowRoot) {\n for (const child of root.children) walk(child)\n }\n }\n\n for (const child of this.children) walk(child)\n return elements\n }\n\n private animateElement(el: SVGGeometryElement, run: AnimationRun, reverse: boolean): Promise<void> {\n return new Promise(resolve => {\n if (!this._authoredDash.has(el)) {\n this._authoredDash.set(el, {\n dasharray: el.style.strokeDasharray,\n dashoffset: el.style.strokeDashoffset\n })\n }\n\n if (el.tagName === 'path') {\n const d = el.getAttribute('d')\n if (d && /[Zz]\\s*$/.test(d) && !run.patchedD.has(el)) {\n run.patchedD.set(el, d)\n el.setAttribute('d', this.fixPathGap(el, d))\n }\n }\n\n const strokeWidth = parseFloat(getComputedStyle(el).strokeWidth) || 0\n const len = el.getTotalLength() + strokeWidth\n // 空白 = 整条路径落在 dash 间隙里;满 = 偏移归零。两个方向只是这两端换先后。\n const blank = `${len}`\n const full = '0'\n const from = reverse ? full : blank\n const to = reverse ? blank : full\n\n el.style.strokeDasharray = blank\n el.style.strokeDashoffset = from\n run.blankByElement.set(el, blank)\n\n const anim = el.animate([{ strokeDashoffset: from }, { strokeDashoffset: to }], {\n duration: this.duration,\n easing: this.easing,\n fill: 'forwards'\n })\n\n run.animations.push(anim)\n\n anim.onfinish = () => {\n resolve()\n }\n\n anim.oncancel = () => {\n resolve()\n }\n })\n }\n\n // Z → explicit line back to start + 0.1px extra, forcing render of the closing segment\n private fixPathGap(pathEl: SVGPathElement, d: string): string {\n pathEl.setAttribute('d', d)\n const p0 = pathEl.getPointAtLength(0)\n const p1 = pathEl.getPointAtLength(0.1)\n return d.replace(/[Zz]\\s*$/, `L${p0.x.toFixed(3)} ${p0.y.toFixed(3)} L${p1.x.toFixed(3)} ${p1.y.toFixed(3)}`)\n }\n\n // 首次 slot 内容稳定后自动播放一次;no-autoplay 下这一步交给调用方的 replay()。\n private handleSlotChange() {\n if (this.noAutoplay || this._hasAutoPlayed) return\n this._hasAutoPlayed = true\n void this.replay()\n }\n\n override disconnectedCallback() {\n this.cancelAll()\n super.disconnectedCallback()\n }\n\n override render() {\n return html`<slot @slotchange=${this.handleSlotChange}></slot>`\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-svg-draw-lines': WebUiSvgDrawLines\n }\n}\n"],"mappings":"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"}
@@ -381,6 +381,7 @@ export declare class WebUiSwitch extends WebUiSwitch_base {
381
381
  private _maxTravel;
382
382
  private _startOffset;
383
383
  private _dragGestureHandle;
384
+ private readonly _fieldId;
384
385
  disconnectedCallback(): void;
385
386
  private get _isDisabled();
386
387
  private readonly _formAssociation;
@@ -4,18 +4,19 @@ import t from "../../assets/glass.js";
4
4
  import n from "../../icons/generated/lucide-loader-circle.js";
5
5
  import { installPointerFocusSuppression as r } from "../../shared/focus/pointer-focus.js";
6
6
  import { FormAssociated as i, FormAssociationController as a, defineFormAssociation as o } from "../../shared/form-association/index.js";
7
- import { attachDragGesture as s } from "../../shared/gesture/drag-gesture.js";
8
- import { clamp as c, normalizeProgress as l } from "../../shared/gesture/physics.js";
9
- import u from "./style.js";
10
- import { LitElement as d, html as f, unsafeCSS as p } from "lit";
11
- import { customElement as m, property as h, state as g } from "lit/decorators.js";
12
- import { classMap as _ } from "lit/directives/class-map.js";
13
- import { styleMap as v } from "lit/directives/style-map.js";
7
+ import { createFieldId as s } from "../../shared/form-association/field-id.js";
8
+ import { attachDragGesture as c } from "../../shared/gesture/drag-gesture.js";
9
+ import { clamp as l, normalizeProgress as u } from "../../shared/gesture/physics.js";
10
+ import d from "./style.js";
11
+ import { LitElement as f, html as p, unsafeCSS as m } from "lit";
12
+ import { customElement as h, property as g, state as _ } from "lit/decorators.js";
13
+ import { classMap as v } from "lit/directives/class-map.js";
14
+ import { styleMap as y } from "lit/directives/style-map.js";
14
15
  //#region src/components/switch/index.ts
15
16
  r();
16
- var y = class extends i(d) {
17
+ var b = class extends i(f) {
17
18
  constructor(...e) {
18
- super(...e), this._checked = !1, this.name = "", this.value = "", this.disabled = !1, this.required = !1, this.loading = !1, this._pressed = !1, this._isDragging = !1, this._dragOffset = 0, this._dragProgress = 0, this._dragged = !1, this._maxTravel = 20, this._startOffset = 0, this._dragGestureHandle = null, this._formAssociation = o({
19
+ super(...e), this._checked = !1, this.name = "", this.value = "", this.disabled = !1, this.required = !1, this.loading = !1, this._pressed = !1, this._isDragging = !1, this._dragOffset = 0, this._dragProgress = 0, this._dragged = !1, this._maxTravel = 20, this._startOffset = 0, this._dragGestureHandle = null, this._fieldId = s("web-ui-switch"), this._formAssociation = o({
19
20
  host: this,
20
21
  initialize: () => {
21
22
  this.hasAttribute("checked") && (this._checked = !0);
@@ -33,7 +34,7 @@ var y = class extends i(d) {
33
34
  }).make(), this._formAssociationController = new a(this, this._formAssociation);
34
35
  }
35
36
  static {
36
- this.styles = [p(t), p(u)];
37
+ this.styles = [m(t), m(d)];
37
38
  }
38
39
  get checked() {
39
40
  return this._checked;
@@ -75,13 +76,13 @@ var y = class extends i(d) {
75
76
  let e = t.getBoundingClientRect().width || 40;
76
77
  this._maxTravel = Math.max(1, e - 4 - 20);
77
78
  } else this._maxTravel = 16;
78
- this._startOffset = this._checked ? this._maxTravel : 0, this._dragOffset = this._startOffset, this._dragProgress = +!!this._checked, this._dragGestureHandle?.destroy(), this._dragGestureHandle = s(e, {
79
+ this._startOffset = this._checked ? this._maxTravel : 0, this._dragOffset = this._startOffset, this._dragProgress = +!!this._checked, this._dragGestureHandle?.destroy(), this._dragGestureHandle = c(e, {
79
80
  axis: "x",
80
81
  threshold: 6,
81
82
  onMove: (e) => {
82
83
  this._isDragging = !0;
83
- let t = c(this._startOffset + e.deltaX, 0, this._maxTravel);
84
- this._dragOffset = t, this._dragProgress = l(t, 0, this._maxTravel), this.requestUpdate();
84
+ let t = l(this._startOffset + e.deltaX, 0, this._maxTravel);
85
+ this._dragOffset = t, this._dragProgress = u(t, 0, this._maxTravel), this.requestUpdate();
85
86
  },
86
87
  onEnd: (e) => {
87
88
  let t = this._isDragging;
@@ -122,10 +123,10 @@ var y = class extends i(d) {
122
123
  "wui-glass": this._pressed || this._isDragging,
123
124
  "is-pressed": this._pressed || this._isDragging
124
125
  }, r = this._isDragging ? { "--wui-switch-drag-offset": `${this._dragOffset}px` } : {};
125
- return f`
126
+ return p`
126
127
  <label
127
- class=${_(e)}
128
- style=${v(r)}
128
+ class=${v(e)}
129
+ style=${y(r)}
129
130
  role="switch"
130
131
  aria-checked=${String(this._checked)}
131
132
  @click=${this.handleClick}
@@ -135,13 +136,14 @@ var y = class extends i(d) {
135
136
  @pointerleave=${this.handlePointerLeave}
136
137
  >
137
138
  <input
139
+ id=${this._fieldId}
138
140
  type="checkbox"
139
141
  .checked=${this._checked}
140
142
  ?disabled=${this._isDisabled || this.loading}
141
143
  class="sr-only"
142
144
  />
143
- <div class=${_(t)}>
144
- ${this.loading ? f`<div class="wui-switch-loading">
145
+ <div class=${v(t)}>
146
+ ${this.loading ? p`<div class="wui-switch-loading">
145
147
  <web-ui-icon .icon=${n} size="14" color="#08f" spin></web-ui-icon>
146
148
  </div>` : ""}
147
149
  </div>
@@ -149,20 +151,20 @@ var y = class extends i(d) {
149
151
  `;
150
152
  }
151
153
  };
152
- e([g()], y.prototype, "_checked", void 0), e([h({
154
+ e([_()], b.prototype, "_checked", void 0), e([g({
153
155
  type: String,
154
156
  reflect: !0
155
- })], y.prototype, "name", void 0), e([h({ type: String })], y.prototype, "value", void 0), e([h({
157
+ })], b.prototype, "name", void 0), e([g({ type: String })], b.prototype, "value", void 0), e([g({
156
158
  type: Boolean,
157
159
  reflect: !0
158
- })], y.prototype, "disabled", void 0), e([h({
160
+ })], b.prototype, "disabled", void 0), e([g({
159
161
  type: Boolean,
160
162
  reflect: !0
161
- })], y.prototype, "required", void 0), e([h({
163
+ })], b.prototype, "required", void 0), e([g({
162
164
  type: Boolean,
163
165
  reflect: !0
164
- })], y.prototype, "loading", void 0), e([g()], y.prototype, "_pressed", void 0), e([g()], y.prototype, "_isDragging", void 0), e([g()], y.prototype, "_dragOffset", void 0), e([g()], y.prototype, "_dragProgress", void 0), y = e([m("web-ui-switch")], y);
166
+ })], b.prototype, "loading", void 0), e([_()], b.prototype, "_pressed", void 0), e([_()], b.prototype, "_isDragging", void 0), e([_()], b.prototype, "_dragOffset", void 0), e([_()], b.prototype, "_dragProgress", void 0), b = e([h("web-ui-switch")], b);
165
167
  //#endregion
166
- export { y as WebUiSwitch };
168
+ export { b as WebUiSwitch };
167
169
 
168
170
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/switch/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport '@/components/icon'\nimport glass from '@/assets/glass.css?inline'\nimport { lucideLoaderCircle } from '@/icons'\nimport { installPointerFocusSuppression } from '@/shared/focus/pointer-focus'\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\nimport { attachDragGesture, type DragGestureHandle } from '@/shared/gesture/drag-gesture'\nimport { clamp, normalizeProgress } from '@/shared/gesture/physics'\n\nimport style from './style.css?inline'\n\ninstallPointerFocusSuppression()\n\n@customElement('web-ui-switch')\nexport class WebUiSwitch extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n @state() private _checked = false\n\n get checked(): boolean {\n return this._checked\n }\n\n set checked(v: boolean) {\n const old = this._checked\n this._checked = v\n this._formAssociation.sync()\n this.requestUpdate('checked', old)\n }\n\n @property({ type: String, reflect: true }) name = ''\n @property({ type: String }) value = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Boolean, reflect: true }) loading = false\n @state() private _pressed = false\n @state() private _isDragging = false\n @state() private _dragOffset = 0\n @state() private _dragProgress = 0\n\n private _dragged = false\n private _maxTravel = 20\n private _startOffset = 0\n private _dragGestureHandle: DragGestureHandle | null = null\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._dragGestureHandle?.destroy()\n this._dragGestureHandle = null\n }\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n private readonly _formAssociation = defineFormAssociation<boolean>({\n host: this,\n initialize: () => {\n if (this.hasAttribute('checked')) this._checked = true\n },\n getState: () => this._checked,\n setState: checked => {\n this.checked = checked\n },\n getFormValue: () => (this._checked ? this.value || 'on' : null),\n getFormState: () => String(this._checked),\n restoreState: state => {\n if (typeof state === 'string') this.checked = state === 'true'\n },\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n private _syncValidity() {\n const internals = this._formAssociation.getInternals()\n if (!internals || typeof internals.setValidity !== 'function') return\n if (this._isDisabled || !this.required || this._checked) internals.setValidity({})\n else internals.setValidity({ valueMissing: true }, '请启用此项')\n }\n\n // 阻止 label 默认行为,避免原生 checkbox 重复触发点击。\n private handleClick(e: Event) {\n e.preventDefault()\n if (this._dragged) {\n this._dragged = false\n return\n }\n if (this._isDisabled || this.loading) return\n const old = this._checked\n this._checked = !old\n this._formAssociation.sync()\n this.requestUpdate('checked', old)\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private handlePointerDown(e: PointerEvent) {\n if (this._isDisabled || this.loading) return\n this._pressed = true\n\n const track = this.renderRoot.querySelector<HTMLElement>('.wui-switch-track')\n if (track) {\n const trackWidth = track.getBoundingClientRect().width || 40\n // 拖拽时 thumb 的 pressed 布局盒为 20px(scale(1.5) 视觉放大),因此行程按 20px 布局盒钳制;\n // 释放后的 checked 静止位移为 20px(16px thumb),两者由 transition 衔接。\n this._maxTravel = Math.max(1, trackWidth - 4 - 20)\n } else {\n this._maxTravel = 16\n }\n\n this._startOffset = this._checked ? this._maxTravel : 0\n this._dragOffset = this._startOffset\n this._dragProgress = this._checked ? 1 : 0\n\n this._dragGestureHandle?.destroy()\n this._dragGestureHandle = attachDragGesture(e, {\n axis: 'x',\n threshold: 6,\n onMove: info => {\n this._isDragging = true\n const current = clamp(this._startOffset + info.deltaX, 0, this._maxTravel)\n this._dragOffset = current\n this._dragProgress = normalizeProgress(current, 0, this._maxTravel)\n this.requestUpdate()\n },\n onEnd: info => {\n const wasDragging = this._isDragging\n this._isDragging = false\n this._pressed = false\n\n if (wasDragging) {\n this._dragged = true\n let targetChecked = this._checked\n if (info.velocityX > 300) {\n targetChecked = true\n } else if (info.velocityX < -300) {\n targetChecked = false\n } else {\n targetChecked = this._dragProgress >= 0.5\n }\n\n if (targetChecked !== this._checked) {\n const old = this._checked\n this._checked = targetChecked\n this._formAssociation.sync()\n this.requestUpdate('checked', old)\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n } else {\n this.requestUpdate()\n }\n }\n },\n onCancel: () => {\n this._isDragging = false\n this._pressed = false\n this.requestUpdate()\n }\n })\n }\n\n private handlePointerUp() {\n this._pressed = false\n }\n\n private handlePointerLeave() {\n if (!this._isDragging) {\n this._pressed = false\n }\n }\n\n override render() {\n const isOpen = this._isDragging ? this._dragProgress >= 0.5 : this._checked\n const trackCls = {\n 'wui-switch-track': true,\n 'is-open': isOpen,\n 'is-dragging': this._isDragging,\n 'is-disabled': this._isDisabled || this.loading\n }\n // wui-glass 条件化:静止态实体白 thumb,按压/拖拽时切换为玻璃(backdrop blur +\n // 无填充 + 描边)并放大,回到最初修复不跟手问题时期的视觉结构。\n const thumbCls = {\n 'wui-switch-thumb': true,\n 'wui-glass': this._pressed || this._isDragging,\n 'is-pressed': this._pressed || this._isDragging\n }\n const trackStyle = this._isDragging ? { '--wui-switch-drag-offset': `${this._dragOffset}px` } : {}\n\n return html`\n <label\n class=${classMap(trackCls)}\n style=${styleMap(trackStyle)}\n role=\"switch\"\n aria-checked=${String(this._checked)}\n @click=${this.handleClick}\n @pointerdown=${this.handlePointerDown}\n @pointerup=${this.handlePointerUp}\n @pointercancel=${this.handlePointerUp}\n @pointerleave=${this.handlePointerLeave}\n >\n <input\n type=\"checkbox\"\n .checked=${this._checked}\n ?disabled=${this._isDisabled || this.loading}\n class=\"sr-only\"\n />\n <div class=${classMap(thumbCls)}>\n ${\n this.loading\n ? html`<div class=\"wui-switch-loading\">\n <web-ui-icon .icon=${lucideLoaderCircle} size=\"14\" color=\"#08f\" spin></web-ui-icon>\n </div>`\n : ''\n }\n </div>\n </label>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-switch': WebUiSwitch\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/switch/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport '@/components/icon'\nimport glass from '@/assets/glass.css?inline'\nimport { lucideLoaderCircle } from '@/icons'\nimport { installPointerFocusSuppression } from '@/shared/focus/pointer-focus'\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\nimport { createFieldId } from '@/shared/form-association/field-id'\nimport { attachDragGesture, type DragGestureHandle } from '@/shared/gesture/drag-gesture'\nimport { clamp, normalizeProgress } from '@/shared/gesture/physics'\n\nimport style from './style.css?inline'\n\ninstallPointerFocusSuppression()\n\n@customElement('web-ui-switch')\nexport class WebUiSwitch extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n @state() private _checked = false\n\n get checked(): boolean {\n return this._checked\n }\n\n set checked(v: boolean) {\n const old = this._checked\n this._checked = v\n this._formAssociation.sync()\n this.requestUpdate('checked', old)\n }\n\n @property({ type: String, reflect: true }) name = ''\n @property({ type: String }) value = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Boolean, reflect: true }) loading = false\n @state() private _pressed = false\n @state() private _isDragging = false\n @state() private _dragOffset = 0\n @state() private _dragProgress = 0\n\n private _dragged = false\n private _maxTravel = 20\n private _startOffset = 0\n private _dragGestureHandle: DragGestureHandle | null = null\n private readonly _fieldId = createFieldId('web-ui-switch')\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._dragGestureHandle?.destroy()\n this._dragGestureHandle = null\n }\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n private readonly _formAssociation = defineFormAssociation<boolean>({\n host: this,\n initialize: () => {\n if (this.hasAttribute('checked')) this._checked = true\n },\n getState: () => this._checked,\n setState: checked => {\n this.checked = checked\n },\n getFormValue: () => (this._checked ? this.value || 'on' : null),\n getFormState: () => String(this._checked),\n restoreState: state => {\n if (typeof state === 'string') this.checked = state === 'true'\n },\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n private _syncValidity() {\n const internals = this._formAssociation.getInternals()\n if (!internals || typeof internals.setValidity !== 'function') return\n if (this._isDisabled || !this.required || this._checked) internals.setValidity({})\n else internals.setValidity({ valueMissing: true }, '请启用此项')\n }\n\n // 阻止 label 默认行为,避免原生 checkbox 重复触发点击。\n private handleClick(e: Event) {\n e.preventDefault()\n if (this._dragged) {\n this._dragged = false\n return\n }\n if (this._isDisabled || this.loading) return\n const old = this._checked\n this._checked = !old\n this._formAssociation.sync()\n this.requestUpdate('checked', old)\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private handlePointerDown(e: PointerEvent) {\n if (this._isDisabled || this.loading) return\n this._pressed = true\n\n const track = this.renderRoot.querySelector<HTMLElement>('.wui-switch-track')\n if (track) {\n const trackWidth = track.getBoundingClientRect().width || 40\n // 拖拽时 thumb 的 pressed 布局盒为 20px(scale(1.5) 视觉放大),因此行程按 20px 布局盒钳制;\n // 释放后的 checked 静止位移为 20px(16px thumb),两者由 transition 衔接。\n this._maxTravel = Math.max(1, trackWidth - 4 - 20)\n } else {\n this._maxTravel = 16\n }\n\n this._startOffset = this._checked ? this._maxTravel : 0\n this._dragOffset = this._startOffset\n this._dragProgress = this._checked ? 1 : 0\n\n this._dragGestureHandle?.destroy()\n this._dragGestureHandle = attachDragGesture(e, {\n axis: 'x',\n threshold: 6,\n onMove: info => {\n this._isDragging = true\n const current = clamp(this._startOffset + info.deltaX, 0, this._maxTravel)\n this._dragOffset = current\n this._dragProgress = normalizeProgress(current, 0, this._maxTravel)\n this.requestUpdate()\n },\n onEnd: info => {\n const wasDragging = this._isDragging\n this._isDragging = false\n this._pressed = false\n\n if (wasDragging) {\n this._dragged = true\n let targetChecked = this._checked\n if (info.velocityX > 300) {\n targetChecked = true\n } else if (info.velocityX < -300) {\n targetChecked = false\n } else {\n targetChecked = this._dragProgress >= 0.5\n }\n\n if (targetChecked !== this._checked) {\n const old = this._checked\n this._checked = targetChecked\n this._formAssociation.sync()\n this.requestUpdate('checked', old)\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n } else {\n this.requestUpdate()\n }\n }\n },\n onCancel: () => {\n this._isDragging = false\n this._pressed = false\n this.requestUpdate()\n }\n })\n }\n\n private handlePointerUp() {\n this._pressed = false\n }\n\n private handlePointerLeave() {\n if (!this._isDragging) {\n this._pressed = false\n }\n }\n\n override render() {\n const isOpen = this._isDragging ? this._dragProgress >= 0.5 : this._checked\n const trackCls = {\n 'wui-switch-track': true,\n 'is-open': isOpen,\n 'is-dragging': this._isDragging,\n 'is-disabled': this._isDisabled || this.loading\n }\n // wui-glass 条件化:静止态实体白 thumb,按压/拖拽时切换为玻璃(backdrop blur +\n // 无填充 + 描边)并放大,回到最初修复不跟手问题时期的视觉结构。\n const thumbCls = {\n 'wui-switch-thumb': true,\n 'wui-glass': this._pressed || this._isDragging,\n 'is-pressed': this._pressed || this._isDragging\n }\n const trackStyle = this._isDragging ? { '--wui-switch-drag-offset': `${this._dragOffset}px` } : {}\n\n return html`\n <label\n class=${classMap(trackCls)}\n style=${styleMap(trackStyle)}\n role=\"switch\"\n aria-checked=${String(this._checked)}\n @click=${this.handleClick}\n @pointerdown=${this.handlePointerDown}\n @pointerup=${this.handlePointerUp}\n @pointercancel=${this.handlePointerUp}\n @pointerleave=${this.handlePointerLeave}\n >\n <input\n id=${this._fieldId}\n type=\"checkbox\"\n .checked=${this._checked}\n ?disabled=${this._isDisabled || this.loading}\n class=\"sr-only\"\n />\n <div class=${classMap(thumbCls)}>\n ${\n this.loading\n ? html`<div class=\"wui-switch-loading\">\n <web-ui-icon .icon=${lucideLoaderCircle} size=\"14\" color=\"#08f\" spin></web-ui-icon>\n </div>`\n : ''\n }\n </div>\n </label>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-switch': WebUiSwitch\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  //#region src/components/switch/style.css?inline
2
- var e = "*,:before,:after{box-sizing:border-box}:host{touch-action:none;display:inline-block}:host([disabled]){cursor:not-allowed}.sr-only{white-space:nowrap;clip-path:inset(50%);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.wui-switch-track{touch-action:none;cursor:default;-webkit-user-select:none;user-select:none;border-radius:var(--wui-radius-control,calc(infinity * 1px));background-color:var(--wui-color-surface-track,#e5e5ea);outline:var(--wui-focus-ring-width,3px) solid transparent;outline-offset:2px;width:40px;height:20px;transition:background-color var(--wui-duration-trigger,.16s) ease;justify-content:space-between;align-items:center;display:flex;position:relative}.wui-switch-track:focus-within{outline-color:var(--wui-color-focus-ring,#08f6)}.wui-switch-track.is-open{background-color:var(--wui-color-accent,#08f)}.wui-switch-track.is-disabled{cursor:not-allowed;opacity:.4}.wui-switch-thumb{--wui-glass-corner-radius:calc(16px / 2);transform-origin:50%;border-radius:var(--wui-radius-control,calc(infinity * 1px));width:16px;height:16px;transition:width var(--wui-duration-press,80ms) cubic-bezier(.4, 0, .2, 1), transform var(--wui-duration-trigger,.16s) cubic-bezier(.4, 0, .2, 1), border-radius var(--wui-duration-press,80ms) cubic-bezier(.4, 0, .2, 1), background-color var(--wui-duration-press,80ms) ease, box-shadow var(--wui-duration-press,80ms) ease;position:absolute;top:2px;left:2px;overflow:hidden;transform:translate(0)scale(1)}.wui-switch-thumb.is-pressed{border-radius:var(--wui-radius-control,calc(infinity * 1px));width:20px;box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 38%, transparent), inset 0 1px .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 52%, transparent), inset 0 -.5px 1px #00000008, 0 2px 20px #0003;background-color:#0000;transform:translate(0)scale(1.5)}.wui-switch-thumb:not(.is-pressed){background-color:var(--wui-color-on-control,#fff);box-shadow:1px 2px 6px #0003}.wui-switch-track.is-open .wui-switch-thumb{transform:translate(20px)scale(1)}.wui-switch-track.is-open .wui-switch-thumb.is-pressed{transform:translate(16px)scale(1.5)}.wui-switch-track.is-dragging{cursor:grabbing}:host([data-wui-pointer-focus]) .wui-switch-track:focus-within{outline-color:#0000}.wui-switch-track.is-dragging .wui-switch-thumb,.wui-switch-track.is-dragging .wui-switch-thumb.is-pressed{transform:translateX(var(--wui-switch-drag-offset,0)) scale(1.5);transition:none}.wui-switch-loading{pointer-events:none;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}";
2
+ var e = "*,:before,:after{box-sizing:border-box}:host{touch-action:none;display:inline-block}:host([disabled]){cursor:not-allowed}.sr-only{white-space:nowrap;clip-path:inset(50%);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.wui-switch-track{touch-action:none;cursor:default;-webkit-user-select:none;user-select:none;border-radius:var(--wui-radius-control,calc(infinity * 1px));background-color:var(--wui-color-surface-track,#e5e5ea);outline:var(--wui-focus-ring-width,3px) solid transparent;outline-offset:2px;width:40px;height:20px;transition:background-color var(--wui-duration-trigger,.16s) ease;justify-content:space-between;align-items:center;display:flex;position:relative}.wui-switch-track:focus-within{outline-color:var(--wui-color-focus-ring,color-mix(in srgb, var(--wui-color-accent,#08f) 40%, transparent))}.wui-switch-track.is-open{background-color:var(--wui-color-accent,#08f)}.wui-switch-track.is-disabled{cursor:not-allowed;opacity:.4}.wui-switch-thumb{--wui-glass-corner-radius:calc(16px / 2);transform-origin:50%;border-radius:var(--wui-radius-control,calc(infinity * 1px));width:16px;height:16px;transition:width var(--wui-duration-press,80ms) cubic-bezier(.4, 0, .2, 1), transform var(--wui-duration-trigger,.16s) cubic-bezier(.4, 0, .2, 1), border-radius var(--wui-duration-press,80ms) cubic-bezier(.4, 0, .2, 1), background-color var(--wui-duration-press,80ms) ease, box-shadow var(--wui-duration-press,80ms) ease;position:absolute;top:2px;left:2px;overflow:hidden;transform:translate(0)scale(1)}.wui-switch-thumb.is-pressed{--wui-color-glass-ring:transparent;border-radius:var(--wui-radius-control,calc(infinity * 1px));width:20px;box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 38%, transparent), inset 0 1px .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 52%, transparent), inset 0 -.5px 1px #00000008, 0 2px 20px #0003;background-color:#0000;transform:translate(0)scale(1.5)}.wui-switch-thumb:not(.is-pressed){background-color:var(--wui-color-on-control,#fff);box-shadow:1px 2px 6px #0003}.wui-switch-track.is-open .wui-switch-thumb{transform:translate(20px)scale(1)}.wui-switch-track.is-open .wui-switch-thumb.is-pressed{transform:translate(16px)scale(1.5)}.wui-switch-track.is-dragging{cursor:grabbing}:host([data-wui-pointer-focus]) .wui-switch-track:focus-within{outline-color:#0000}.wui-switch-track.is-dragging .wui-switch-thumb,.wui-switch-track.is-dragging .wui-switch-thumb.is-pressed{transform:translateX(var(--wui-switch-drag-offset,0)) scale(1.5);transition:none}.wui-switch-loading{pointer-events:none;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}";
3
3
  //#endregion
4
4
  export { e as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/switch/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n touch-action: none;\n display: inline-block;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n/*\n标准视觉隐藏技术(sr-only):\n业内标准的“屏幕阅读器专用”隐藏方案。\n把复选框缩放到 1x1 像素,但保留在 dom 中,\n既让肉眼看不见,又能让读屏软件识别,并允许键盘用户通过空格键切换状态。\n*/\n.sr-only {\n position: absolute;\n\n overflow: hidden;\n\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n\n white-space: nowrap;\n\n clip-path: inset(50%);\n}\n\n.wui-switch-track {\n touch-action: none;\n cursor: default;\n user-select: none;\n\n position: relative;\n\n display: flex;\n align-items: center;\n justify-content: space-between;\n\n width: 40px;\n height: 20px;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n background-color: var(--wui-color-surface-track, #e5e5ea);\n outline: var(--wui-focus-ring-width, 3px) solid transparent;\n outline-offset: 2px;\n\n transition: background-color var(--wui-duration-trigger, 160ms) ease;\n\n /* 实际获得焦点的是内部 sr-only 的 checkbox,track 本身不可聚焦;\n :focus-visible 在 track 上永不匹配,需用 :focus-within 让键盘焦点环生效 */\n &:focus-within {\n outline-color: var(--wui-color-focus-ring, rgb(0 136 255 / 0.4));\n }\n\n &.is-open {\n background-color: var(--wui-color-accent, #08f);\n }\n\n &.is-disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n}\n\n.wui-switch-thumb {\n /* 按压态 thumb 是 16px 高的 pill(is-pressed 只加宽到 20px),取短边一半 */\n --wui-glass-corner-radius: calc(16px / 2);\n\n position: absolute;\n top: 2px;\n left: 2px;\n transform-origin: center center;\n transform: translateX(0) scale(1);\n\n overflow: hidden;\n\n width: 16px;\n height: 16px;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n transition:\n width var(--wui-duration-press, 80ms) cubic-bezier(0.4, 0, 0.2, 1),\n transform var(--wui-duration-trigger, 160ms) cubic-bezier(0.4, 0, 0.2, 1),\n border-radius var(--wui-duration-press, 80ms) cubic-bezier(0.4, 0, 0.2, 1),\n background-color var(--wui-duration-press, 80ms) ease,\n box-shadow var(--wui-duration-press, 80ms) ease;\n\n &.is-pressed {\n transform: translateX(0) scale(1.5);\n\n width: 20px;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n background-color: transparent;\n box-shadow:\n inset 0 0 0 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 38%, transparent),\n inset 0 1px 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 52%, transparent),\n inset 0 -0.5px 1px rgb(0 0 0 / 0.03),\n 0 2px 20px rgb(0 0 0 / 0.2);\n }\n\n &:not(.is-pressed) {\n background-color: var(--wui-color-on-control, #fff);\n box-shadow: 1px 2px 6px rgb(0 0 0 / 0.2);\n }\n}\n\n.wui-switch-track.is-open .wui-switch-thumb {\n transform: translateX(20px) scale(1);\n\n &.is-pressed {\n transform: translateX(16px) scale(1.5);\n }\n}\n\n.wui-switch-track.is-dragging {\n cursor: grabbing;\n}\n\n:host([data-wui-pointer-focus]) .wui-switch-track:focus-within {\n outline-color: transparent;\n}\n\n.wui-switch-track.is-dragging .wui-switch-thumb,\n.wui-switch-track.is-dragging .wui-switch-thumb.is-pressed {\n transform: translateX(var(--wui-switch-drag-offset, 0)) scale(1.5);\n transition: none;\n}\n\n.wui-switch-loading {\n pointer-events: none;\n\n position: absolute;\n inset: 0;\n\n display: flex;\n align-items: center;\n justify-content: center;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/switch/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n touch-action: none;\n display: inline-block;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n/*\n标准视觉隐藏技术(sr-only):\n业内标准的“屏幕阅读器专用”隐藏方案。\n把复选框缩放到 1x1 像素,但保留在 dom 中,\n既让肉眼看不见,又能让读屏软件识别,并允许键盘用户通过空格键切换状态。\n*/\n.sr-only {\n position: absolute;\n\n overflow: hidden;\n\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n\n white-space: nowrap;\n\n clip-path: inset(50%);\n}\n\n.wui-switch-track {\n touch-action: none;\n cursor: default;\n user-select: none;\n\n position: relative;\n\n display: flex;\n align-items: center;\n justify-content: space-between;\n\n width: 40px;\n height: 20px;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n background-color: var(--wui-color-surface-track, #e5e5ea);\n outline: var(--wui-focus-ring-width, 3px) solid transparent;\n outline-offset: 2px;\n\n transition: background-color var(--wui-duration-trigger, 160ms) ease;\n\n /* 实际获得焦点的是内部 sr-only 的 checkbox,track 本身不可聚焦;\n :focus-visible 在 track 上永不匹配,需用 :focus-within 让键盘焦点环生效 */\n &:focus-within {\n outline-color: var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n }\n\n &.is-open {\n background-color: var(--wui-color-accent, #08f);\n }\n\n &.is-disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n}\n\n.wui-switch-thumb {\n /* 按压态 thumb 是 16px 高的 pill(is-pressed 只加宽到 20px),取短边一半 */\n --wui-glass-corner-radius: calc(16px / 2);\n\n position: absolute;\n top: 2px;\n left: 2px;\n transform-origin: center center;\n transform: translateX(0) scale(1);\n\n overflow: hidden;\n\n width: 16px;\n height: 16px;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n transition:\n width var(--wui-duration-press, 80ms) cubic-bezier(0.4, 0, 0.2, 1),\n transform var(--wui-duration-trigger, 160ms) cubic-bezier(0.4, 0, 0.2, 1),\n border-radius var(--wui-duration-press, 80ms) cubic-bezier(0.4, 0, 0.2, 1),\n background-color var(--wui-duration-press, 80ms) ease,\n box-shadow var(--wui-duration-press, 80ms) ease;\n\n &.is-pressed {\n /* 环底色随按压态转 transparent:pill 的长边长不过 3R,底色不覆盖中段,\n 去掉它只是少一层均匀灰 tint(理由见 menu-portal.css)。 */\n --wui-color-glass-ring: transparent;\n transform: translateX(0) scale(1.5);\n\n width: 20px;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n background-color: transparent;\n box-shadow:\n inset 0 0 0 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 38%, transparent),\n inset 0 1px 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 52%, transparent),\n inset 0 -0.5px 1px rgb(0 0 0 / 0.03),\n 0 2px 20px rgb(0 0 0 / 0.2);\n }\n\n &:not(.is-pressed) {\n background-color: var(--wui-color-on-control, #fff);\n box-shadow: 1px 2px 6px rgb(0 0 0 / 0.2);\n }\n}\n\n.wui-switch-track.is-open .wui-switch-thumb {\n transform: translateX(20px) scale(1);\n\n &.is-pressed {\n transform: translateX(16px) scale(1.5);\n }\n}\n\n.wui-switch-track.is-dragging {\n cursor: grabbing;\n}\n\n:host([data-wui-pointer-focus]) .wui-switch-track:focus-within {\n outline-color: transparent;\n}\n\n.wui-switch-track.is-dragging .wui-switch-thumb,\n.wui-switch-track.is-dragging .wui-switch-thumb.is-pressed {\n transform: translateX(var(--wui-switch-drag-offset, 0)) scale(1.5);\n transition: none;\n}\n\n.wui-switch-loading {\n pointer-events: none;\n\n position: absolute;\n inset: 0;\n\n display: flex;\n align-items: center;\n justify-content: center;\n}\n"],"mappings":""}
@@ -386,6 +386,7 @@ export declare class WebUiTextarea extends WebUiTextarea_base {
386
386
  private _hasSuffix;
387
387
  private _textarea;
388
388
  private _resizeObserver;
389
+ private readonly _fieldId;
389
390
  get value(): string;
390
391
  set value(v: string);
391
392
  private get _isDisabled();
@@ -403,8 +404,13 @@ export declare class WebUiTextarea extends WebUiTextarea_base {
403
404
  private handleFocus;
404
405
  private handleBlur;
405
406
  private handleNativeChange;
406
- focus(): void;
407
+ focus(options?: FocusOptions): void;
407
408
  blur(): void;
409
+ /**
410
+ * 公共 API:全选当前值。
411
+ *
412
+ * `disabled` 时与 `focus()` 一致不产生效果;原生控件未渲染时安全 no-op。
413
+ */
408
414
  select(): void;
409
415
  private focusTextarea;
410
416
  private _onSlotChange;
@@ -2,18 +2,19 @@ import e from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate
2
2
  import "../icon/index.js";
3
3
  import t from "../../assets/glass.js";
4
4
  import n from "../../icons/generated/jam-close-circle-f.js";
5
- import "../button/index.js";
6
5
  import { FormAssociated as r, FormAssociationController as i, defineFormAssociation as a, forwardInputValidity as o } from "../../shared/form-association/index.js";
7
- import s from "./style.js";
8
- import { LitElement as c, html as l, nothing as u, unsafeCSS as d } from "lit";
9
- import { customElement as f, property as p, state as m } from "lit/decorators.js";
10
- import { ifDefined as h } from "lit/directives/if-defined.js";
11
- import { classMap as g } from "lit/directives/class-map.js";
12
- import { styleMap as _ } from "lit/directives/style-map.js";
6
+ import "../button/index.js";
7
+ import { createFieldId as s } from "../../shared/form-association/field-id.js";
8
+ import c from "./style.js";
9
+ import { LitElement as l, html as u, nothing as d, unsafeCSS as f } from "lit";
10
+ import { customElement as p, property as m, state as h } from "lit/decorators.js";
11
+ import { ifDefined as g } from "lit/directives/if-defined.js";
12
+ import { classMap as _ } from "lit/directives/class-map.js";
13
+ import { styleMap as v } from "lit/directives/style-map.js";
13
14
  //#region src/components/textarea/index.ts
14
- var v = class extends r(c) {
15
+ var y = class extends r(l) {
15
16
  constructor(...e) {
16
- super(...e), this.placeholder = "", this.name = "", this.disabled = !1, this.readonly = !1, this.required = !1, this.full = !1, this.clearable = !1, this.borderless = !1, this.rows = 3, this.autosize = !1, this.maxHeight = 0, this.ariaLabel = null, this._value = "", this._focused = !1, this._hasPrefix = !1, this._hasSuffix = !1, this._textarea = null, this._resizeObserver = null, this._formAssociation = a({
17
+ super(...e), this.placeholder = "", this.name = "", this.disabled = !1, this.readonly = !1, this.required = !1, this.full = !1, this.clearable = !1, this.borderless = !1, this.rows = 3, this.autosize = !1, this.maxHeight = 0, this.ariaLabel = null, this._value = "", this._focused = !1, this._hasPrefix = !1, this._hasSuffix = !1, this._textarea = null, this._resizeObserver = null, this._fieldId = s("web-ui-textarea"), this._formAssociation = a({
17
18
  host: this,
18
19
  getState: () => this._value,
19
20
  setState: (e) => {
@@ -28,7 +29,7 @@ var v = class extends r(c) {
28
29
  }).make(), this._formAssociationController = new i(this, this._formAssociation);
29
30
  }
30
31
  static {
31
- this.styles = [d(t), d(s)];
32
+ this.styles = [f(t), f(c)];
32
33
  }
33
34
  get value() {
34
35
  return this._value;
@@ -51,7 +52,7 @@ var v = class extends r(c) {
51
52
  let e = this._getTextarea();
52
53
  e && e.value !== this._value && (e.value = this._value);
53
54
  }
54
- e.has("autosize") && (this.autosize ? this._setupAutosize() : this._teardownAutosize(!0)), this._syncValidity(), this.toggleAttribute("focused", this._focused);
55
+ this.autosize && (e.has("_value") || e.has("rows")) && this._autosize(), e.has("autosize") && (this.autosize ? this._setupAutosize() : this._teardownAutosize(!0)), this._syncValidity(), this.toggleAttribute("focused", this._focused);
55
56
  }
56
57
  _getTextarea() {
57
58
  return this._textarea ??= this.shadowRoot?.querySelector("textarea") ?? null, this._textarea;
@@ -80,7 +81,7 @@ var v = class extends r(c) {
80
81
  this._textarea && this.autosize && (this._textarea.style.height = "auto", this._textarea.style.height = `${this._textarea.scrollHeight}px`);
81
82
  }
82
83
  handleInput(e) {
83
- e.target instanceof HTMLTextAreaElement && (this._value = e.target.value, this._formAssociation.sync(), this._syncValidity(), this._autosize());
84
+ this._isDisabled || this.readonly || e.target instanceof HTMLTextAreaElement && (this._value = e.target.value, this._formAssociation.sync(), this._syncValidity());
84
85
  }
85
86
  handleFocus() {
86
87
  this._isDisabled || (this._focused = !0);
@@ -89,19 +90,19 @@ var v = class extends r(c) {
89
90
  this._focused = !1;
90
91
  }
91
92
  handleNativeChange(e) {
92
- e.target instanceof HTMLTextAreaElement && (this._value = e.target.value, this._formAssociation.sync(), this._syncValidity(), this.dispatchEvent(new Event("change", {
93
+ this._isDisabled || this.readonly || e.target instanceof HTMLTextAreaElement && (this._value = e.target.value, this._formAssociation.sync(), this._syncValidity(), this.dispatchEvent(new Event("change", {
93
94
  bubbles: !0,
94
95
  composed: !0
95
96
  })));
96
97
  }
97
- focus() {
98
- this.shadowRoot?.querySelector("textarea")?.focus();
98
+ focus(e) {
99
+ this._isDisabled || this.shadowRoot?.querySelector("textarea")?.focus(e);
99
100
  }
100
101
  blur() {
101
102
  this.shadowRoot?.querySelector("textarea")?.blur();
102
103
  }
103
104
  select() {
104
- this.shadowRoot?.querySelector("textarea")?.select();
105
+ this._isDisabled || this.shadowRoot?.querySelector("textarea")?.select();
105
106
  }
106
107
  focusTextarea() {
107
108
  this._isDisabled || this.shadowRoot?.querySelector("textarea")?.focus();
@@ -122,28 +123,29 @@ var v = class extends r(c) {
122
123
  }
123
124
  render() {
124
125
  let e = this.clearable && this._value && !this.readonly;
125
- return l`
126
+ return u`
126
127
  <div class="wui-glass wui-textarea-inner" @click=${this.focusTextarea}>
127
- <slot name="prefix" class=${g({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>
128
+ <slot name="prefix" class=${_({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>
128
129
  <textarea
130
+ id=${this._fieldId}
129
131
  placeholder=${this.placeholder}
130
132
  name=${this.name}
131
- aria-label=${h(this.ariaLabel)}
132
- aria-labelledby=${h(this.ariaLabelledby)}
133
+ aria-label=${g(this.ariaLabel)}
134
+ aria-labelledby=${g(this.ariaLabelledby)}
133
135
  .rows=${this.rows}
134
- minlength=${h(this.minlength)}
135
- maxlength=${h(this.maxlength)}
136
+ minlength=${g(this.minlength)}
137
+ maxlength=${g(this.maxlength)}
136
138
  ?disabled=${this._isDisabled}
137
139
  ?readonly=${this.readonly}
138
140
  ?required=${this.required}
139
141
  .value=${this._value}
140
- style=${this.maxHeight > 0 ? _({ "max-height": `${this.maxHeight}px` }) : u}
142
+ style=${this.maxHeight > 0 ? v({ "max-height": `${this.maxHeight}px` }) : d}
141
143
  @input=${this.handleInput}
142
144
  @change=${this.handleNativeChange}
143
145
  @focus=${this.handleFocus}
144
146
  @blur=${this.handleBlur}
145
147
  ></textarea>
146
- ${e ? l`<web-ui-button
148
+ ${e ? u`<web-ui-button
147
149
  icon
148
150
  variant="ghost"
149
151
  size="20"
@@ -153,62 +155,62 @@ var v = class extends r(c) {
153
155
  >
154
156
  <web-ui-icon .icon=${n} size="14"></web-ui-icon>
155
157
  </web-ui-button>` : ""}
156
- <slot name="suffix" class=${g({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>
158
+ <slot name="suffix" class=${_({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>
157
159
  </div>
158
160
  `;
159
161
  }
160
162
  };
161
- e([p({
163
+ e([m({
162
164
  type: String,
163
165
  reflect: !0
164
- })], v.prototype, "placeholder", void 0), e([p({
166
+ })], y.prototype, "placeholder", void 0), e([m({
165
167
  type: String,
166
168
  reflect: !0
167
- })], v.prototype, "name", void 0), e([p({
169
+ })], y.prototype, "name", void 0), e([m({
168
170
  type: Boolean,
169
171
  reflect: !0
170
- })], v.prototype, "disabled", void 0), e([p({
172
+ })], y.prototype, "disabled", void 0), e([m({
171
173
  type: Boolean,
172
174
  reflect: !0
173
- })], v.prototype, "readonly", void 0), e([p({
175
+ })], y.prototype, "readonly", void 0), e([m({
174
176
  type: Boolean,
175
177
  reflect: !0
176
- })], v.prototype, "required", void 0), e([p({
178
+ })], y.prototype, "required", void 0), e([m({
177
179
  type: Boolean,
178
180
  reflect: !0
179
- })], v.prototype, "full", void 0), e([p({
181
+ })], y.prototype, "full", void 0), e([m({
180
182
  type: Boolean,
181
183
  reflect: !0
182
- })], v.prototype, "clearable", void 0), e([p({
184
+ })], y.prototype, "clearable", void 0), e([m({
183
185
  type: Boolean,
184
186
  reflect: !0
185
- })], v.prototype, "borderless", void 0), e([p({
187
+ })], y.prototype, "borderless", void 0), e([m({
186
188
  type: Number,
187
189
  reflect: !0
188
- })], v.prototype, "rows", void 0), e([p({
190
+ })], y.prototype, "rows", void 0), e([m({
189
191
  type: Number,
190
192
  reflect: !0
191
- })], v.prototype, "minlength", void 0), e([p({
193
+ })], y.prototype, "minlength", void 0), e([m({
192
194
  type: Number,
193
195
  reflect: !0
194
- })], v.prototype, "maxlength", void 0), e([p({
196
+ })], y.prototype, "maxlength", void 0), e([m({
195
197
  type: Boolean,
196
198
  reflect: !0
197
- })], v.prototype, "autosize", void 0), e([p({
199
+ })], y.prototype, "autosize", void 0), e([m({
198
200
  type: Number,
199
201
  reflect: !0,
200
202
  attribute: "max-height"
201
- })], v.prototype, "maxHeight", void 0), e([p({
203
+ })], y.prototype, "maxHeight", void 0), e([m({
202
204
  type: String,
203
205
  attribute: "aria-label"
204
- })], v.prototype, "ariaLabel", void 0), e([p({
206
+ })], y.prototype, "ariaLabel", void 0), e([m({
205
207
  type: String,
206
208
  attribute: "aria-labelledby"
207
- })], v.prototype, "ariaLabelledby", void 0), e([m()], v.prototype, "_value", void 0), e([m()], v.prototype, "_focused", void 0), e([m()], v.prototype, "_hasPrefix", void 0), e([m()], v.prototype, "_hasSuffix", void 0), e([p({
209
+ })], y.prototype, "ariaLabelledby", void 0), e([h()], y.prototype, "_value", void 0), e([h()], y.prototype, "_focused", void 0), e([h()], y.prototype, "_hasPrefix", void 0), e([h()], y.prototype, "_hasSuffix", void 0), e([m({
208
210
  type: String,
209
211
  reflect: !0
210
- })], v.prototype, "value", null), v = e([f("web-ui-textarea")], v);
212
+ })], y.prototype, "value", null), y = e([p("web-ui-textarea")], y);
211
213
  //#endregion
212
- export { v as WebUiTextarea };
214
+ export { y as WebUiTextarea };
213
215
 
214
216
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/textarea/index.ts"],"sourcesContent":["import { html, LitElement, nothing, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport '@/components/icon'\nimport '@/components/button'\nimport glass from '@/assets/glass.css?inline'\nimport { jamCloseCircleF } from '@/icons'\nimport {\n FormAssociated,\n defineFormAssociation,\n forwardInputValidity,\n FormAssociationController\n} from '@/shared/form-association'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-textarea')\nexport class WebUiTextarea extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) readonly = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Boolean, reflect: true }) full = false\n @property({ type: Boolean, reflect: true }) clearable = false\n @property({ type: Boolean, reflect: true }) borderless = false\n @property({ type: Number, reflect: true }) rows = 3\n @property({ type: Number, reflect: true }) minlength: number | undefined\n @property({ type: Number, reflect: true }) maxlength: number | undefined\n @property({ type: Boolean, reflect: true }) autosize = false\n // 自动高度上限(px);0 表示不限制,随内容无限增高\n @property({ type: Number, reflect: true, attribute: 'max-height' }) maxHeight = 0\n @property({ type: String, attribute: 'aria-label' }) override ariaLabel: string | null = null\n @property({ type: String, attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined\n\n @state() private _value = ''\n @state() private _focused = false\n @state() private _hasPrefix = false\n @state() private _hasSuffix = false\n\n private _textarea: HTMLTextAreaElement | null = null\n private _resizeObserver: ResizeObserver | null = null\n\n @property({ type: String, reflect: true })\n get value(): string {\n return this._value\n }\n\n set value(v: string) {\n const old = this._value\n this._value = v\n this._formAssociation.sync()\n this.requestUpdate('value', old)\n }\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n private readonly _formAssociation = defineFormAssociation<string>({\n host: this,\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => this._value,\n getFormState: () => this._value,\n restoreState: state => {\n if (typeof state === 'string') this.value = state\n },\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._teardownAutosize()\n }\n\n override firstUpdated() {\n this._textarea = this.shadowRoot?.querySelector('textarea') ?? null\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('_value')) {\n const textarea = this._getTextarea()\n if (textarea && textarea.value !== this._value) {\n textarea.value = this._value\n }\n }\n if (changed.has('autosize')) {\n if (this.autosize) {\n this._setupAutosize()\n } else {\n this._teardownAutosize(true)\n }\n }\n this._syncValidity()\n this.toggleAttribute('focused', this._focused)\n }\n\n private _getTextarea(): HTMLTextAreaElement | null {\n this._textarea ??= this.shadowRoot?.querySelector('textarea') ?? null\n return this._textarea\n }\n\n private _syncValidity() {\n const internals = this._formAssociation.getInternals()\n const textarea = this._getTextarea()\n if (!internals || !textarea || typeof internals.setValidity !== 'function') return\n\n forwardInputValidity(internals, textarea, ['valueMissing', 'tooShort', 'tooLong'], this._isDisabled)\n }\n\n private _setupAutosize() {\n this._teardownAutosize()\n if (typeof ResizeObserver === 'undefined') {\n this._autosize()\n return\n }\n this._resizeObserver = new ResizeObserver(() => {\n this._autosize()\n })\n if (this._textarea) this._resizeObserver.observe(this._textarea)\n this._autosize()\n }\n\n private _teardownAutosize(resetHeight = false) {\n this._resizeObserver?.disconnect()\n this._resizeObserver = null\n if (resetHeight) this._textarea?.style.removeProperty('height')\n }\n\n private _autosize() {\n if (!this._textarea || !this.autosize) return\n this._textarea.style.height = 'auto'\n // height:auto 时 rows 已驱动自然高,scrollHeight 恒 ≥ rows 行高,无需额外 floor;\n // 上限由 CSS max-height(maxHeight 属性)钳制,这里只负责随内容增高,默认无限\n this._textarea.style.height = `${this._textarea.scrollHeight}px`\n }\n\n private handleInput(e: Event) {\n if (!(e.target instanceof HTMLTextAreaElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n this._syncValidity()\n this._autosize()\n }\n\n private handleFocus() {\n if (!this._isDisabled) this._focused = true\n }\n\n private handleBlur() {\n this._focused = false\n }\n\n // 原生 change 不 composed,被 shadow root 挡住;这里补发 composed 事件,\n // 兑现 $events/README 声明的公共 change 契约。\n private handleNativeChange(e: Event) {\n if (!(e.target instanceof HTMLTextAreaElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n this._syncValidity()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n override focus() {\n this.shadowRoot?.querySelector('textarea')?.focus()\n }\n\n override blur() {\n this.shadowRoot?.querySelector('textarea')?.blur()\n }\n\n select() {\n this.shadowRoot?.querySelector('textarea')?.select()\n }\n\n private focusTextarea() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('textarea')?.focus()\n }\n\n private _onSlotChange(e: Event) {\n if (!(e.target instanceof HTMLSlotElement)) return\n const slot = e.target\n const hasContent = slot.assignedElements().length > 0\n if (slot.name === 'prefix') this._hasPrefix = hasContent\n if (slot.name === 'suffix') this._hasSuffix = hasContent\n }\n\n private handleClear() {\n if (this._isDisabled || this.readonly) return\n this._value = ''\n this._formAssociation.sync()\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n }\n\n private preventMouseDownBlur(e: Event) {\n e.preventDefault()\n }\n\n override render() {\n // readonly 下不可清除:清空按钮会修改值,与只读语义冲突\n const showClear = this.clearable && this._value && !this.readonly\n\n return html`\n <div class=\"wui-glass wui-textarea-inner\" @click=${this.focusTextarea}>\n <slot name=\"prefix\" class=${classMap({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>\n <textarea\n placeholder=${this.placeholder}\n name=${this.name}\n aria-label=${ifDefined(this.ariaLabel)}\n aria-labelledby=${ifDefined(this.ariaLabelledby)}\n .rows=${this.rows}\n minlength=${ifDefined(this.minlength)}\n maxlength=${ifDefined(this.maxlength)}\n ?disabled=${this._isDisabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n .value=${this._value}\n style=${this.maxHeight > 0 ? styleMap({ 'max-height': `${this.maxHeight}px` }) : nothing}\n @input=${this.handleInput}\n @change=${this.handleNativeChange}\n @focus=${this.handleFocus}\n @blur=${this.handleBlur}\n ></textarea>\n ${\n showClear\n ? html`<web-ui-button\n icon\n variant=\"ghost\"\n size=\"20\"\n aria-label=\"清除\"\n @pointerdown=${this.preventMouseDownBlur}\n @click=${this.handleClear}\n >\n <web-ui-icon .icon=${jamCloseCircleF} size=\"14\"></web-ui-icon>\n </web-ui-button>`\n : ''\n }\n <slot name=\"suffix\" class=${classMap({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n focus: FocusEvent\n blur: FocusEvent\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-textarea': WebUiTextarea\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/textarea/index.ts"],"sourcesContent":["import { html, LitElement, nothing, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport '@/components/icon'\nimport '@/components/button'\nimport glass from '@/assets/glass.css?inline'\nimport { jamCloseCircleF } from '@/icons'\nimport {\n FormAssociated,\n defineFormAssociation,\n forwardInputValidity,\n FormAssociationController\n} from '@/shared/form-association'\nimport { createFieldId } from '@/shared/form-association/field-id'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-textarea')\nexport class WebUiTextarea extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) readonly = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Boolean, reflect: true }) full = false\n @property({ type: Boolean, reflect: true }) clearable = false\n @property({ type: Boolean, reflect: true }) borderless = false\n @property({ type: Number, reflect: true }) rows = 3\n @property({ type: Number, reflect: true }) minlength: number | undefined\n @property({ type: Number, reflect: true }) maxlength: number | undefined\n @property({ type: Boolean, reflect: true }) autosize = false\n // 自动高度上限(px);0 表示不限制,随内容无限增高\n @property({ type: Number, reflect: true, attribute: 'max-height' }) maxHeight = 0\n @property({ type: String, attribute: 'aria-label' }) override ariaLabel: string | null = null\n @property({ type: String, attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined\n\n @state() private _value = ''\n @state() private _focused = false\n @state() private _hasPrefix = false\n @state() private _hasSuffix = false\n\n private _textarea: HTMLTextAreaElement | null = null\n private _resizeObserver: ResizeObserver | null = null\n private readonly _fieldId = createFieldId('web-ui-textarea')\n\n @property({ type: String, reflect: true })\n get value(): string {\n return this._value\n }\n\n set value(v: string) {\n const old = this._value\n this._value = v\n this._formAssociation.sync()\n this.requestUpdate('value', old)\n }\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n private readonly _formAssociation = defineFormAssociation<string>({\n host: this,\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => this._value,\n getFormState: () => this._value,\n restoreState: state => {\n if (typeof state === 'string') this.value = state\n },\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._teardownAutosize()\n }\n\n override firstUpdated() {\n this._textarea = this.shadowRoot?.querySelector('textarea') ?? null\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('_value')) {\n const textarea = this._getTextarea()\n if (textarea && textarea.value !== this._value) {\n textarea.value = this._value\n }\n }\n /*\n * 高度只在这里算,autosize 的两条改值路径(用户输入、外部改写)都汇到\n * changed.has('_value'),不必各自调一次。\n *\n * 不能等 ResizeObserver:autosize 把高度写成内联 px,换值时盒子尺寸没变,\n * 不会触发回调,于是旧高度会一直留着(例如同一个 textarea 随抽屉切换资源,\n * 从长备注切到短备注仍占着长备注的高度)。\n *\n * rows 同理:它决定 height:auto 时的自然高度,但 inline px 会盖掉它,\n * 改 rows 不会让盒子尺寸变化,ResizeObserver 同样看不到。\n *\n * 上面的 textarea.value 兜底在正常路径上是 no-op——render() 里的 .value 绑定\n * 已经先一步把新值写进 DOM 了;它留给 binding 未生效的场景。\n */\n if (this.autosize && (changed.has('_value') || changed.has('rows'))) this._autosize()\n if (changed.has('autosize')) {\n if (this.autosize) {\n this._setupAutosize()\n } else {\n this._teardownAutosize(true)\n }\n }\n this._syncValidity()\n this.toggleAttribute('focused', this._focused)\n }\n\n private _getTextarea(): HTMLTextAreaElement | null {\n this._textarea ??= this.shadowRoot?.querySelector('textarea') ?? null\n return this._textarea\n }\n\n private _syncValidity() {\n const internals = this._formAssociation.getInternals()\n const textarea = this._getTextarea()\n if (!internals || !textarea || typeof internals.setValidity !== 'function') return\n\n forwardInputValidity(internals, textarea, ['valueMissing', 'tooShort', 'tooLong'], this._isDisabled)\n }\n\n private _setupAutosize() {\n this._teardownAutosize()\n if (typeof ResizeObserver === 'undefined') {\n this._autosize()\n return\n }\n this._resizeObserver = new ResizeObserver(() => {\n this._autosize()\n })\n if (this._textarea) this._resizeObserver.observe(this._textarea)\n this._autosize()\n }\n\n private _teardownAutosize(resetHeight = false) {\n this._resizeObserver?.disconnect()\n this._resizeObserver = null\n if (resetHeight) this._textarea?.style.removeProperty('height')\n }\n\n private _autosize() {\n if (!this._textarea || !this.autosize) return\n this._textarea.style.height = 'auto'\n // height:auto 时 rows 已驱动自然高,scrollHeight 恒 ≥ rows 行高,无需额外 floor;\n // 上限由 CSS max-height(maxHeight 属性)钳制,这里只负责随内容增高,默认无限\n this._textarea.style.height = `${this._textarea.scrollHeight}px`\n }\n\n private handleInput(e: Event) {\n if (this._isDisabled || this.readonly) return\n if (!(e.target instanceof HTMLTextAreaElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n this._syncValidity()\n }\n\n private handleFocus() {\n if (!this._isDisabled) this._focused = true\n }\n\n private handleBlur() {\n this._focused = false\n }\n\n // 原生 change 不 composed,被 shadow root 挡住;这里补发 composed 事件,\n // 兑现 $events/README 声明的公共 change 契约。\n private handleNativeChange(e: Event) {\n if (this._isDisabled || this.readonly) return\n if (!(e.target instanceof HTMLTextAreaElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n this._syncValidity()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n override focus(options?: FocusOptions) {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('textarea')?.focus(options)\n }\n\n override blur() {\n this.shadowRoot?.querySelector('textarea')?.blur()\n }\n\n /**\n * 公共 API:全选当前值。\n *\n * `disabled` 时与 `focus()` 一致不产生效果;原生控件未渲染时安全 no-op。\n */\n select() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('textarea')?.select()\n }\n\n private focusTextarea() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('textarea')?.focus()\n }\n\n private _onSlotChange(e: Event) {\n if (!(e.target instanceof HTMLSlotElement)) return\n const slot = e.target\n const hasContent = slot.assignedElements().length > 0\n if (slot.name === 'prefix') this._hasPrefix = hasContent\n if (slot.name === 'suffix') this._hasSuffix = hasContent\n }\n\n private handleClear() {\n if (this._isDisabled || this.readonly) return\n this._value = ''\n this._formAssociation.sync()\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n }\n\n private preventMouseDownBlur(e: Event) {\n e.preventDefault()\n }\n\n override render() {\n // readonly 下不可清除:清空按钮会修改值,与只读语义冲突\n const showClear = this.clearable && this._value && !this.readonly\n\n return html`\n <div class=\"wui-glass wui-textarea-inner\" @click=${this.focusTextarea}>\n <slot name=\"prefix\" class=${classMap({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>\n <textarea\n id=${this._fieldId}\n placeholder=${this.placeholder}\n name=${this.name}\n aria-label=${ifDefined(this.ariaLabel)}\n aria-labelledby=${ifDefined(this.ariaLabelledby)}\n .rows=${this.rows}\n minlength=${ifDefined(this.minlength)}\n maxlength=${ifDefined(this.maxlength)}\n ?disabled=${this._isDisabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n .value=${this._value}\n style=${this.maxHeight > 0 ? styleMap({ 'max-height': `${this.maxHeight}px` }) : nothing}\n @input=${this.handleInput}\n @change=${this.handleNativeChange}\n @focus=${this.handleFocus}\n @blur=${this.handleBlur}\n ></textarea>\n ${\n showClear\n ? html`<web-ui-button\n icon\n variant=\"ghost\"\n size=\"20\"\n aria-label=\"清除\"\n @pointerdown=${this.preventMouseDownBlur}\n @click=${this.handleClear}\n >\n <web-ui-icon .icon=${jamCloseCircleF} size=\"14\"></web-ui-icon>\n </web-ui-button>`\n : ''\n }\n <slot name=\"suffix\" class=${classMap({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n focus: FocusEvent\n blur: FocusEvent\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-textarea': WebUiTextarea\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  //#region src/components/textarea/style.css?inline
2
- var e = "*,:before,:after{box-sizing:border-box}:host{--wui-textarea-clear-color:var(--wui-color-text-tertiary,color-mix(in srgb, var(--wui-color-text) 35%, transparent));display:block}:host([disabled]){cursor:not-allowed;opacity:.4}.wui-textarea-inner{--wui-glass-corner-radius:var(--wui-radius-menu,18px);box-sizing:border-box;width:var(--wui-textarea-width,200px);min-width:var(--wui-control-size,36px);min-height:var(--wui-control-size,36px);border-radius:var(--wui-radius-menu,18px);align-items:flex-start;gap:8px;padding:7.5px 12px;display:flex;position:relative}:host([full]) .wui-textarea-inner{width:100%}:host([borderless]) .wui-textarea-inner{-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:none;background:0 0;border:none}:host([borderless]) .wui-textarea-inner:before{content:none}textarea{resize:vertical;box-sizing:border-box;width:100%;font:inherit;font-size:var(--wui-font-size,14px);color:var(--wui-color-text,#1b1b1b);background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;line-height:1.5;display:block}textarea::placeholder{color:var(--wui-color-text-secondary,#6a6a6a)}textarea:disabled{cursor:not-allowed}textarea:read-only{cursor:default}@media (pointer:coarse){textarea{font-size:max(16px, var(--wui-font-size,14px))}}.wui-textarea-inner:after{pointer-events:none;content:\"\";border-radius:inherit;box-shadow:inset 0 0 0 1px transparent, 0 0 0 var(--wui-focus-ring-width,3px) transparent;transition:box-shadow var(--wui-duration-focus,.2s);position:absolute;inset:0}:host([focused]) .wui-textarea-inner:after{box-shadow:inset 0 0 0 1px var(--wui-color-accent,#08f), 0 0 0 var(--wui-focus-ring-width,3px) var(--wui-color-focus-ring,#08f6)}slot{font-size:var(--wui-font-size,14px);color:var(--wui-color-text-secondary,#6a6a6a);flex-shrink:0;align-items:center;display:flex}slot.empty{display:none}.wui-textarea-inner web-ui-button[variant=ghost]{--wui-button-color:var(--wui-textarea-clear-color);margin-top:calc((var(--wui-font-size,14px) * 1.5 - 24px) / 2);margin-left:-8px;margin-right:-4px}";
2
+ var e = "*,:before,:after{box-sizing:border-box}:host{--wui-textarea-clear-color:var(--wui-color-text-tertiary,color-mix(in srgb, var(--wui-color-text) 35%, transparent));display:block}:host([disabled]){cursor:not-allowed;opacity:.4}.wui-textarea-inner{--wui-glass-corner-radius:var(--wui-radius-menu,18px);gap:var(--wui-space-2,8px);box-sizing:border-box;width:var(--wui-textarea-width,200px);min-width:var(--wui-control-size,36px);min-height:var(--wui-control-size,36px);padding:7.5px var(--wui-space-3,12px);border-radius:var(--wui-radius-menu,18px);align-items:flex-start;display:flex;position:relative}:host([full]) .wui-textarea-inner{width:100%}:host([borderless]) .wui-textarea-inner{-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:none;background:0 0;border:none}:host([borderless]) .wui-textarea-inner:before{content:none}textarea{resize:vertical;box-sizing:border-box;width:100%;font:inherit;font-size:var(--wui-font-size,14px);line-height:var(--wui-line-height-normal,1.5);color:var(--wui-color-text,#1b1b1b);background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;display:block}textarea::placeholder{color:var(--wui-color-text-secondary,#6a6a6a)}textarea:disabled{cursor:not-allowed}textarea:read-only{cursor:default}:host([autosize]) textarea{resize:none}@media (pointer:coarse){textarea{font-size:max(16px, var(--wui-font-size,14px))}}.wui-textarea-inner:after{pointer-events:none;content:\"\";border-radius:inherit;box-shadow:inset 0 0 0 1px transparent, 0 0 0 var(--wui-focus-ring-width,3px) transparent;transition:box-shadow var(--wui-duration-focus,.2s);position:absolute;inset:0}:host([focused]) .wui-textarea-inner:after{box-shadow:inset 0 0 0 1px var(--wui-color-accent,#08f), 0 0 0 var(--wui-focus-ring-width,3px) var(--wui-color-focus-ring,color-mix(in srgb, var(--wui-color-accent,#08f) 40%, transparent))}slot{font-size:var(--wui-font-size,14px);color:var(--wui-color-text-secondary,#6a6a6a);flex-shrink:0;align-items:center;display:flex}slot.empty{display:none}.wui-textarea-inner web-ui-button[variant=ghost]{--wui-button-color:var(--wui-textarea-clear-color);margin-top:calc((var(--wui-font-size,14px) * 1.5 - 24px) / 2);margin-left:-8px;margin-right:-4px}";
3
3
  //#endregion
4
4
  export { e as default };
5
5