@greypan/web-ui 10.2.0 → 11.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 (133) hide show
  1. package/README.CN.md +68 -68
  2. package/README.md +43 -43
  3. package/dist/assets/glass.js.map +1 -1
  4. package/dist/assets/menu-portal.js.map +1 -1
  5. package/dist/assets/modal-surface.js.map +1 -1
  6. package/dist/assets/overlay-motion.js.map +1 -1
  7. package/dist/assets/selection-control.js.map +1 -1
  8. package/dist/components/autocomplete/index.d.ts +6 -6
  9. package/dist/components/autocomplete/index.js +1 -1
  10. package/dist/components/autocomplete/index.js.map +1 -1
  11. package/dist/components/autocomplete/style.js.map +1 -1
  12. package/dist/components/avatar/index.js.map +1 -1
  13. package/dist/components/back-top/index.js.map +1 -1
  14. package/dist/components/back-top/style.js.map +1 -1
  15. package/dist/components/badge/index.js +1 -1
  16. package/dist/components/badge/index.js.map +1 -1
  17. package/dist/components/button/index.js.map +1 -1
  18. package/dist/components/button/style.js.map +1 -1
  19. package/dist/components/checkbox/index.d.ts +1 -1
  20. package/dist/components/checkbox/index.js +1 -1
  21. package/dist/components/checkbox/index.js.map +1 -1
  22. package/dist/components/checkbox/style.js.map +1 -1
  23. package/dist/components/checkbox-group/index.d.ts +9 -0
  24. package/dist/components/checkbox-group/index.js +8 -2
  25. package/dist/components/checkbox-group/index.js.map +1 -1
  26. package/dist/components/collapse/index.d.ts +5 -5
  27. package/dist/components/collapse/index.js.map +1 -1
  28. package/dist/components/collapse/style.js.map +1 -1
  29. package/dist/components/context-menu/index.d.ts +60 -60
  30. package/dist/components/context-menu/index.js +2 -2
  31. package/dist/components/context-menu/index.js.map +1 -1
  32. package/dist/components/context-menu/scrim.js.map +1 -1
  33. package/dist/components/context-menu/style.js.map +1 -1
  34. package/dist/components/dialog/index.d.ts +4 -4
  35. package/dist/components/dialog/index.js +1 -1
  36. package/dist/components/dialog/index.js.map +1 -1
  37. package/dist/components/dialog/style.js.map +1 -1
  38. package/dist/components/drawer/index.d.ts +8 -8
  39. package/dist/components/drawer/index.js +1 -1
  40. package/dist/components/drawer/index.js.map +1 -1
  41. package/dist/components/drawer/style.js.map +1 -1
  42. package/dist/components/dropdown/index.d.ts +6 -6
  43. package/dist/components/dropdown/index.js.map +1 -1
  44. package/dist/components/dropdown-item/index.js.map +1 -1
  45. package/dist/components/editable-text/index.d.ts +2 -2
  46. package/dist/components/editable-text/index.js.map +1 -1
  47. package/dist/components/editable-text/style.js.map +1 -1
  48. package/dist/components/empty/index.d.ts +3 -3
  49. package/dist/components/empty/index.js.map +1 -1
  50. package/dist/components/image-preview/index.d.ts +5 -5
  51. package/dist/components/image-preview/index.js +7 -7
  52. package/dist/components/image-preview/index.js.map +1 -1
  53. package/dist/components/image-preview/style.js.map +1 -1
  54. package/dist/components/input/index.js +4 -1
  55. package/dist/components/input/index.js.map +1 -1
  56. package/dist/components/input/style.js.map +1 -1
  57. package/dist/components/input-number/index.d.ts +6 -0
  58. package/dist/components/input-number/index.js +27 -21
  59. package/dist/components/input-number/index.js.map +1 -1
  60. package/dist/components/layout/index.d.ts +7 -7
  61. package/dist/components/layout/index.js +4 -4
  62. package/dist/components/layout/index.js.map +1 -1
  63. package/dist/components/layout/style.js.map +1 -1
  64. package/dist/components/middle-ellipsis/index.d.ts +1 -1
  65. package/dist/components/middle-ellipsis/index.js.map +1 -1
  66. package/dist/components/middle-ellipsis/truncate.d.ts +1 -1
  67. package/dist/components/middle-ellipsis/truncate.js.map +1 -1
  68. package/dist/components/popover/index.d.ts +1 -1
  69. package/dist/components/popover/index.js.map +1 -1
  70. package/dist/components/popover/style.js.map +1 -1
  71. package/dist/components/radio/index.d.ts +1 -1
  72. package/dist/components/radio/index.js +1 -1
  73. package/dist/components/radio/index.js.map +1 -1
  74. package/dist/components/radio-group/index.d.ts +7 -0
  75. package/dist/components/radio-group/index.js +21 -3
  76. package/dist/components/radio-group/index.js.map +1 -1
  77. package/dist/components/segmented/index.js.map +1 -1
  78. package/dist/components/segmented/style.js.map +1 -1
  79. package/dist/components/segmented-trigger/style.js.map +1 -1
  80. package/dist/components/select/index.js +1 -1
  81. package/dist/components/select/index.js.map +1 -1
  82. package/dist/components/select/style.js.map +1 -1
  83. package/dist/components/slider/index.js +3 -3
  84. package/dist/components/slider/index.js.map +1 -1
  85. package/dist/components/slider/style.js +1 -1
  86. package/dist/components/slider/style.js.map +1 -1
  87. package/dist/components/spinner/index.js +1 -1
  88. package/dist/components/spinner/index.js.map +1 -1
  89. package/dist/components/switch/index.d.ts +3 -0
  90. package/dist/components/switch/index.js +26 -8
  91. package/dist/components/switch/index.js.map +1 -1
  92. package/dist/components/switch/style.js.map +1 -1
  93. package/dist/components/textarea/index.js +1 -1
  94. package/dist/components/textarea/index.js.map +1 -1
  95. package/dist/components/textarea/style.js.map +1 -1
  96. package/dist/components/theme/index.js.map +1 -1
  97. package/dist/components/theme/style.js.map +1 -1
  98. package/dist/components/toast/style.js.map +1 -1
  99. package/dist/components/toast/toast.js +1 -1
  100. package/dist/components/toast/toast.js.map +1 -1
  101. package/dist/components/tooltip/index.d.ts +1 -1
  102. package/dist/components/tooltip/index.js.map +1 -1
  103. package/dist/components/tooltip/style.js.map +1 -1
  104. package/dist/scrollbars/index.d.ts +8 -8
  105. package/dist/scrollbars/index.js.map +1 -1
  106. package/dist/scrollbars/internal.js.map +1 -1
  107. package/dist/shared/element-height/index.js.map +1 -1
  108. package/dist/shared/focus/pointer-focus.js.map +1 -1
  109. package/dist/shared/group-management/index.js +5 -1
  110. package/dist/shared/group-management/index.js.map +1 -1
  111. package/dist/shared/label-emptiness/index.js.map +1 -1
  112. package/dist/shared/menu-behavior/index.js.map +1 -1
  113. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  114. package/dist/shared/menu-portal/menu-tree.js.map +1 -1
  115. package/dist/shared/option-portal/combobox-shell.js.map +1 -1
  116. package/dist/shared/option-portal/index.js.map +1 -1
  117. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  118. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  119. package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
  120. package/dist/shared/overlay/open-overlay.js.map +1 -1
  121. package/dist/shared/overlay/overlay.js.map +1 -1
  122. package/dist/shared/overlay/portal.js.map +1 -1
  123. package/dist/shared/overlay/presence.js.map +1 -1
  124. package/dist/shared/scroll-lock/scroll-lock.js.map +1 -1
  125. package/dist/shared/theme/root-sync.js.map +1 -1
  126. package/package.json +2 -2
  127. package/dist/icons/generated/heroicons-arrow-down-16-solid.d.ts +0 -6
  128. package/dist/icons/generated/heroicons-arrow-up-16-solid.d.ts +0 -6
  129. package/dist/icons/generated/jam-volume.d.ts +0 -8
  130. package/dist/icons/generated/jam-watch-f.d.ts +0 -8
  131. package/dist/icons/generated/ooui-check.d.ts +0 -6
  132. package/dist/icons/generated/radix-icons-check.d.ts +0 -6
  133. package/dist/icons/generated/si-check-alt-fill.d.ts +0 -6
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.js","names":[],"sources":["../../../src/components/middle-ellipsis/truncate.ts"],"sourcesContent":["/**\n * 单行「中间省略」的切分运算。\n *\n * 目标:把 `text` 拆成 `head + marker + tail`,两端各自在原串里连续、中间那段被丢掉,\n * 且合串不超过可用行内空间。切点只落在**字素簇**边界上,代理对、组合符序列和 ZWJ emoji\n * 都不会被从中间劈开——按 UTF-16 下标切一定会。\n *\n * 为什么这一层不能由 CSS 承担:`text-overflow` 渲染的是行盒里**一段连续**的内容,结构上\n * 不可能同时显示首尾而丢掉中间。能靠 `direction` / 双值写法挪动的只是省略号落在行的哪一\n * 端,而且 `direction: rtl` 那条路还会按双向算法重排内容(`2026-report.pdf` → `report.pdf-2026`)。\n * 完整调研见 docs/research/web-ui-middle-ellipsis-261007.md。\n *\n * 宽度口径:两端额度按 `position` 从可用空间里分出来,各自用「前 i 簇 / 后 i 簇」的**实测**\n * 宽度二分逼近。实测走调用方给的探针(Range 读取,不写 DOM),所以重算一次只有若干次读取,\n * 没有回流——面板里同时存在成百上千个这样的单元格时,这个差别就是「一帧一次布局」与\n * 「一个单元格一次布局」的差别。\n *\n * 分段宽度不蕴含合串宽度:`head|marker` 与 `marker|tail` 两个接缝的字距和连字是分段量看不见\n * 的。这里不做合串实测,因为那要写一次 DOM 再读、把上面省下的回流又付回去;复核交给调用方\n * 对着**渲染出来的真实盒子**做一次(见组件的 `updated()`),那才是「放得下」的直接证据。\n */\n\n/** 默认省略标记:U+2026 HORIZONTAL ELLIPSIS。 */\nexport const DEFAULT_MARKER = '…'\n\nlet segmenter: Intl.Segmenter | undefined\n\n/**\n * 每个字素簇的 UTF-16 结束偏移(升序,不含 0),末项恒等于 `text.length`。\n *\n * `Intl.Segmenter` 的构造不便宜,单例复用。目标浏览器(见 package.json 的 browserslist)\n * 与本地 node 测试环境都自带它,因此不写按码点切的退化分支——那会在部分环境里悄悄改变\n * 切分口径,而这正是本模块唯一的正确性来源。\n */\nexport function clusterEnds(text: string): number[] {\n if (text === '') return []\n\n segmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\n const ends: number[] = []\n for (const { index, segment } of segmenter.segment(text)) ends.push(index + segment.length)\n return ends\n}\n\n/**\n * 宽度探针。\n *\n * 三段都必须是**真实排版**下量到的宽度:字体回退、字距、连字、`letter-spacing`、\n * `text-transform` 都会改变字形的推进量,任何独立的估算(例如用 canvas 的 `measureText`)\n * 都要自己复刻这一整套解析,复刻不全就是静默的量错。\n */\nexport interface WidthProbe {\n /** 原文前 `units` 个 UTF-16 单元的渲染宽度。 */\n prefix(units: number): number\n /** 原文后 `units` 个 UTF-16 单元的渲染宽度。 */\n suffix(units: number): number\n /** 标记自身的渲染宽度。 */\n marker(): number\n}\n\nexport interface MiddleTruncation {\n /** 保留的原文前缀。 */\n readonly head: string\n /** 保留的原文后缀。 */\n readonly tail: string\n /** 是否真的丢掉了内容;`false` 时合串就是原文,标记不出现。 */\n readonly truncated: boolean\n}\n\n/** 宽度对簇数单调不减,因此可以二分求预算内放得下的最大簇数。 */\nfunction fitCount(total: number, fits: (count: number) => boolean): number {\n let lo = 0\n let hi = total\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (fits(mid)) lo = mid\n else hi = mid - 1\n }\n return lo\n}\n\n/**\n * 在 `available` 内把 `text` 切成 `head + marker + tail`。\n *\n * `position` 与 CSSWG css-overflow-5 的 `text-overflow: ellipsis <length-percentage>` 同向:\n * 量的是标记结束边距行末边的距离占可用行内空间的比例。`0` 把标记贴在行末(只剩头部,等价于\n * 末尾省略),`100` 贴在行首(只剩尾部),`50` 居中;两端额度因此是 `(1 - p)` 与 `p`。\n */\nexport function truncateMiddle(\n text: string,\n marker: string,\n position: number,\n available: number,\n probe: WidthProbe\n): MiddleTruncation {\n if (text === '') return { head: '', tail: '', truncated: false }\n\n // 放得下就不截断,一个字符都不动——这既是正确性也是「短文本不该出现省略号」的体检点。\n if (probe.prefix(text.length) <= available) return { head: text, tail: '', truncated: false }\n\n const ends = clusterEnds(text)\n const total = ends.length\n\n /*\n * 标记自己都放不下的快速路径:让标记溢出、由 overflow 把它裁掉(与 text-overflow 的既有语义\n * 一致),而不是退回全文——退回全文会把标记挤出盒外,用户连「这里被截断了」的信号都看不到。\n *\n * 这一条不是正确性分支:预算为负时下面的通用路径同样得到 head / tail 都为空(`prefix(0) = 0`\n * 不满足 `<= 负数`,两次二分都返回 0)。变异验证过——把它改成 `available <= 0`,整套测试仍全绿。\n * 留着只是省掉两次二分,并把「标记放不下」这个意图写在明面上。\n */\n const space = available - probe.marker()\n if (space <= 0) return { head: '', tail: '', truncated: true }\n\n const ratio = Math.min(1, Math.max(0, position / 100))\n const headBudget = space * (1 - ratio)\n const tailBudget = space * ratio\n\n /** 前 `count` 个簇在原串里的结束偏移。 */\n const headEnd = (count: number): number => (count === 0 ? 0 : (ends[count - 1] ?? 0))\n /** 后 `count` 个簇在原串里的起始偏移。 */\n const tailStart = (count: number): number => (count === 0 ? text.length : (ends[total - count - 1] ?? 0))\n\n let headCount = fitCount(total, count => probe.prefix(headEnd(count)) <= headBudget)\n let tailCount = fitCount(total, count => probe.suffix(text.length - tailStart(count)) <= tailBudget)\n\n /*\n * 两端选中的簇加起来超过原文长度时,说明合串其实放得下,与上面的早退矛盾——那一步已经\n * 用同一个探针量过全文。浮点误差仍可能把两个预算都推过界一点点,这里按头部优先夹紧,\n * 保证 head 与 tail 不重叠。\n */\n if (headCount + tailCount > total) {\n headCount = Math.min(headCount, total)\n tailCount = total - headCount\n }\n\n return {\n head: text.slice(0, headEnd(headCount)),\n tail: text.slice(tailStart(tailCount)),\n truncated: true\n }\n}\n"],"mappings":";AAyBA,IAAI;AASJ,SAAgB,EAAY,GAAwB;CAClD,IAAI,MAAS,IAAI,OAAO,CAAC;CAEzB,MAAc,IAAI,KAAK,UAAU,KAAA,GAAW,EAAE,aAAa,WAAW,CAAC;CAEvE,IAAM,IAAiB,CAAC;CACxB,KAAK,IAAM,EAAE,UAAO,gBAAa,EAAU,QAAQ,CAAI,GAAG,EAAK,KAAK,IAAQ,EAAQ,MAAM;CAC1F,OAAO;AACT;AA4BA,SAAS,EAAS,GAAe,GAA0C;CACzE,IAAI,IAAK,GACL,IAAK;CACT,OAAO,IAAK,IAAI;EACd,IAAM,IAAO,IAAK,IAAK,KAAM;EAC7B,AAAI,EAAK,CAAG,IAAG,IAAK,IACf,IAAK,IAAM;CAClB;CACA,OAAO;AACT;AASA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACkB;CAClB,IAAI,MAAS,IAAI,OAAO;EAAE,MAAM;EAAI,MAAM;EAAI,WAAW;CAAM;CAG/D,IAAI,EAAM,OAAO,EAAK,MAAM,KAAK,GAAW,OAAO;EAAE,MAAM;EAAM,MAAM;EAAI,WAAW;CAAM;CAE5F,IAAM,IAAO,EAAY,CAAI,GACvB,IAAQ,EAAK,QAUb,IAAQ,IAAY,EAAM,OAAO;CACvC,IAAI,KAAS,GAAG,OAAO;EAAE,MAAM;EAAI,MAAM;EAAI,WAAW;CAAK;CAE7D,IAAM,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAW,GAAG,CAAC,GAC/C,IAAa,KAAS,IAAI,IAC1B,IAAa,IAAQ,GAGrB,KAAW,MAA2B,MAAU,IAAI,IAAK,EAAK,IAAQ,MAAM,GAE5E,KAAa,MAA2B,MAAU,IAAI,EAAK,SAAU,EAAK,IAAQ,IAAQ,MAAM,GAElG,IAAY,EAAS,IAAO,MAAS,EAAM,OAAO,EAAQ,CAAK,CAAC,KAAK,CAAU,GAC/E,IAAY,EAAS,IAAO,MAAS,EAAM,OAAO,EAAK,SAAS,EAAU,CAAK,CAAC,KAAK,CAAU;CAYnG,OALI,IAAY,IAAY,MAC1B,IAAY,KAAK,IAAI,GAAW,CAAK,GACrC,IAAY,IAAQ,IAGf;EACL,MAAM,EAAK,MAAM,GAAG,EAAQ,CAAS,CAAC;EACtC,MAAM,EAAK,MAAM,EAAU,CAAS,CAAC;EACrC,WAAW;CACb;AACF"}
1
+ {"version":3,"file":"truncate.js","names":[],"sources":["../../../src/components/middle-ellipsis/truncate.ts"],"sourcesContent":["/**\n * 单行「中间省略」的切分运算。\n *\n * 目标:把 `text` 拆成 `head + marker + tail`,两端各自在原串里连续、中间那段被丢掉,\n * 且合串不超过可用行内空间。切点只落在**字素簇**边界上,代理对、组合符序列和 ZWJ emoji\n * 都不会被从中间劈开——按 UTF-16 下标切一定会。\n *\n * 为什么这一层不能由 CSS 承担:`text-overflow` 渲染的是行盒里**一段连续**的内容,结构上\n * 不可能同时显示首尾而丢掉中间。能靠 `direction` / 双值写法挪动的只是省略号落在行的哪一\n * 端,而且 `direction: rtl` 那条路还会按双向算法重排内容(`2026-report.pdf` → `report.pdf-2026`)。\n * 完整调研见 docs/research/web-ui-middle-ellipsis-261007.md。\n *\n * 宽度口径:两端额度按 `position` 从可用空间里分出来,各自用「前 i 簇 / 后 i 簇」的**实测**\n * 宽度二分逼近。实测走调用方给的探针(Range 读取,不写 DOM),所以重算一次只有若干次读取,\n * 没有回流——panel 里同时存在成百上千个这样的单元格时,这个差别就是「一帧一次布局」与\n * 「一个单元格一次布局」的差别。\n *\n * 分段宽度不蕴含合串宽度:`head|marker` 与 `marker|tail` 两个接缝的字距和连字是分段量看不见\n * 的。这里不做合串实测,因为那要写一次 DOM 再读、把上面省下的回流又付回去;复核交给调用方\n * 对着**渲染出来的真实盒子**做一次(见组件的 `updated()`),那才是「放得下」的直接证据。\n */\n\n/** 默认省略标记:U+2026 HORIZONTAL ELLIPSIS。 */\nexport const DEFAULT_MARKER = '…'\n\nlet segmenter: Intl.Segmenter | undefined\n\n/**\n * 每个字素簇的 UTF-16 结束偏移(升序,不含 0),末项恒等于 `text.length`。\n *\n * `Intl.Segmenter` 的构造不便宜,单例复用。目标浏览器(见 package.json 的 browserslist)\n * 与本地 node 测试环境都自带它,因此不写按码点切的退化分支——那会在部分环境里悄悄改变\n * 切分口径,而这正是本模块唯一的正确性来源。\n */\nexport function clusterEnds(text: string): number[] {\n if (text === '') return []\n\n segmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\n const ends: number[] = []\n for (const { index, segment } of segmenter.segment(text)) ends.push(index + segment.length)\n return ends\n}\n\n/**\n * 宽度探针。\n *\n * 三段都必须是**真实排版**下量到的宽度:字体回退、字距、连字、`letter-spacing`、\n * `text-transform` 都会改变字形的推进量,任何独立的估算(例如用 canvas 的 `measureText`)\n * 都要自己复刻这一整套解析,复刻不全就是静默的量错。\n */\nexport interface WidthProbe {\n /** 原文前 `units` 个 UTF-16 单元的渲染宽度。 */\n prefix(units: number): number\n /** 原文后 `units` 个 UTF-16 单元的渲染宽度。 */\n suffix(units: number): number\n /** 标记自身的渲染宽度。 */\n marker(): number\n}\n\nexport interface MiddleTruncation {\n /** 保留的原文前缀。 */\n readonly head: string\n /** 保留的原文后缀。 */\n readonly tail: string\n /** 是否真的丢掉了内容;`false` 时合串就是原文,标记不出现。 */\n readonly truncated: boolean\n}\n\n/** 宽度对簇数单调不减,因此可以二分求预算内放得下的最大簇数。 */\nfunction fitCount(total: number, fits: (count: number) => boolean): number {\n let lo = 0\n let hi = total\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (fits(mid)) lo = mid\n else hi = mid - 1\n }\n return lo\n}\n\n/**\n * 在 `available` 内把 `text` 切成 `head + marker + tail`。\n *\n * `position` 与 CSSWG css-overflow-5 的 `text-overflow: ellipsis <length-percentage>` 同向:\n * 量的是标记结束边距行末边的距离占可用行内空间的比例。`0` 把标记贴在行末(只剩头部,等价于\n * 末尾省略),`100` 贴在行首(只剩尾部),`50` 居中;两端额度因此是 `(1 - p)` 与 `p`。\n */\nexport function truncateMiddle(\n text: string,\n marker: string,\n position: number,\n available: number,\n probe: WidthProbe\n): MiddleTruncation {\n if (text === '') return { head: '', tail: '', truncated: false }\n\n // 放得下就不截断,一个字符都不动——这既是正确性也是「短文本不该出现省略号」的体检点。\n if (probe.prefix(text.length) <= available) return { head: text, tail: '', truncated: false }\n\n const ends = clusterEnds(text)\n const total = ends.length\n\n /*\n * 标记自己都放不下的快速路径:让标记溢出、由 overflow 把它裁掉(与 text-overflow 的既有语义\n * 一致),而不是退回全文——退回全文会把标记挤出盒外,用户连「这里被截断了」的信号都看不到。\n *\n * 这一条不是正确性分支:预算为负时下面的通用路径同样得到 head / tail 都为空(`prefix(0) = 0`\n * 不满足 `<= 负数`,两次二分都返回 0)。变异验证过——把它改成 `available <= 0`,整套测试仍全绿。\n * 留着只是省掉两次二分,并把「标记放不下」这个意图写在明面上。\n */\n const space = available - probe.marker()\n if (space <= 0) return { head: '', tail: '', truncated: true }\n\n const ratio = Math.min(1, Math.max(0, position / 100))\n const headBudget = space * (1 - ratio)\n const tailBudget = space * ratio\n\n /** 前 `count` 个簇在原串里的结束偏移。 */\n const headEnd = (count: number): number => (count === 0 ? 0 : (ends[count - 1] ?? 0))\n /** 后 `count` 个簇在原串里的起始偏移。 */\n const tailStart = (count: number): number => (count === 0 ? text.length : (ends[total - count - 1] ?? 0))\n\n let headCount = fitCount(total, count => probe.prefix(headEnd(count)) <= headBudget)\n let tailCount = fitCount(total, count => probe.suffix(text.length - tailStart(count)) <= tailBudget)\n\n /*\n * 两端选中的簇加起来超过原文长度时,说明合串其实放得下,与上面的早退矛盾——那一步已经\n * 用同一个探针量过全文。浮点误差仍可能把两个预算都推过界一点点,这里按头部优先夹紧,\n * 保证 head 与 tail 不重叠。\n */\n if (headCount + tailCount > total) {\n headCount = Math.min(headCount, total)\n tailCount = total - headCount\n }\n\n return {\n head: text.slice(0, headEnd(headCount)),\n tail: text.slice(tailStart(tailCount)),\n truncated: true\n }\n}\n"],"mappings":";AAyBA,IAAI;AASJ,SAAgB,EAAY,GAAwB;CAClD,IAAI,MAAS,IAAI,OAAO,CAAC;CAEzB,MAAc,IAAI,KAAK,UAAU,KAAA,GAAW,EAAE,aAAa,WAAW,CAAC;CAEvE,IAAM,IAAiB,CAAC;CACxB,KAAK,IAAM,EAAE,UAAO,gBAAa,EAAU,QAAQ,CAAI,GAAG,EAAK,KAAK,IAAQ,EAAQ,MAAM;CAC1F,OAAO;AACT;AA4BA,SAAS,EAAS,GAAe,GAA0C;CACzE,IAAI,IAAK,GACL,IAAK;CACT,OAAO,IAAK,IAAI;EACd,IAAM,IAAO,IAAK,IAAK,KAAM;EAC7B,AAAI,EAAK,CAAG,IAAG,IAAK,IACf,IAAK,IAAM;CAClB;CACA,OAAO;AACT;AASA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACkB;CAClB,IAAI,MAAS,IAAI,OAAO;EAAE,MAAM;EAAI,MAAM;EAAI,WAAW;CAAM;CAG/D,IAAI,EAAM,OAAO,EAAK,MAAM,KAAK,GAAW,OAAO;EAAE,MAAM;EAAM,MAAM;EAAI,WAAW;CAAM;CAE5F,IAAM,IAAO,EAAY,CAAI,GACvB,IAAQ,EAAK,QAUb,IAAQ,IAAY,EAAM,OAAO;CACvC,IAAI,KAAS,GAAG,OAAO;EAAE,MAAM;EAAI,MAAM;EAAI,WAAW;CAAK;CAE7D,IAAM,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAW,GAAG,CAAC,GAC/C,IAAa,KAAS,IAAI,IAC1B,IAAa,IAAQ,GAGrB,KAAW,MAA2B,MAAU,IAAI,IAAK,EAAK,IAAQ,MAAM,GAE5E,KAAa,MAA2B,MAAU,IAAI,EAAK,SAAU,EAAK,IAAQ,IAAQ,MAAM,GAElG,IAAY,EAAS,IAAO,MAAS,EAAM,OAAO,EAAQ,CAAK,CAAC,KAAK,CAAU,GAC/E,IAAY,EAAS,IAAO,MAAS,EAAM,OAAO,EAAK,SAAS,EAAU,CAAK,CAAC,KAAK,CAAU;CAYnG,OALI,IAAY,IAAY,MAC1B,IAAY,KAAK,IAAI,GAAW,CAAK,GACrC,IAAY,IAAQ,IAGf;EACL,MAAM,EAAK,MAAM,GAAG,EAAQ,CAAS,CAAC;EACtC,MAAM,EAAK,MAAM,EAAU,CAAS,CAAC;EACrC,WAAW;CACb;AACF"}
@@ -23,7 +23,7 @@ export declare class WebUiPopover extends LitElement {
23
23
  private _panelId;
24
24
  private _portal?;
25
25
  /**
26
- * 开启态浮层(issue #120 Block 1):本组件不再自行监听 keydown,只声明「我开着」,
26
+ * 开启态 overlay(issue #120 Block 1):本组件不再自行监听 keydown,只声明「我开着」,
27
27
  * 由唯一仲裁者决定一次 Escape 关谁。实例作用域的帧事务入口也在这里(合并了原
28
28
  * lifecycle)—— 它的 lifetime 长于一次开合(跨 `suspend()`/`resume()`),
29
29
  * 但不长于实例本身。
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/popover/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOpenOverlay } from '@/shared/overlay/open-overlay'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal, type OverlayContainer, type OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_TRIGGERS = ['click', 'hover', 'manual'] as const\n\nlet popoverIdCounter = 0\n\n@customElement('web-ui-popover')\nexport class WebUiPopover extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(overlayMotion), unsafeCSS(style)]\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: String, reflect: true })\n get placement(): Placement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, FLOATING_PLACEMENTS, 'bottom')\n this.requestUpdate('placement', old)\n }\n private _placement: Placement = 'bottom'\n\n @property({ type: Number })\n get offset(): number {\n return this._offset\n }\n set offset(v: number) {\n const old = this._offset\n this._offset = normalizeNumber(v, 0, 100, 8)\n this.requestUpdate('offset', old)\n }\n private _offset = 8\n\n @property({ type: String, reflect: true })\n get trigger(): 'click' | 'hover' | 'manual' {\n return this._trigger\n }\n set trigger(v: string) {\n const old = this._trigger\n this._trigger = normalizeLiteral(v, ALLOWED_TRIGGERS, 'click')\n this.requestUpdate('trigger', old)\n }\n private _trigger: 'click' | 'hover' | 'manual' = 'click'\n\n @property({ type: Boolean, reflect: true }) portal = false\n @property({ attribute: false }) overlayContainer?: OverlayContainer\n\n private _showTimer?: ReturnType<typeof setTimeout>\n private _hideTimer?: ReturnType<typeof setTimeout>\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n\n private _panelId = `wui-popover-panel-${++popoverIdCounter}`\n private _portal?: OverlayPortal\n /**\n * 开启态浮层(issue #120 Block 1):本组件不再自行监听 keydown,只声明「我开着」,\n * 由唯一仲裁者决定一次 Escape 关谁。实例作用域的帧事务入口也在这里(合并了原\n * lifecycle)—— 它的 lifetime 长于一次开合(跨 `suspend()`/`resume()`),\n * 但不长于实例本身。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => {\n this._userOpenChange.mark()\n this.open = false\n },\n isConnected: () => this.isConnected\n })\n private readonly _panel = defineAnchoredPanel().make({\n getAnchor: () => this.shadowRoot?.querySelector<HTMLElement>('.popover-trigger') ?? null,\n getLocalPanel: () => this.shadowRoot?.querySelector<HTMLElement>('.popover-panel') ?? null,\n getPositioning: () => ({\n placement: this.placement,\n offset: this.offset,\n strategy: this.portal ? 'fixed' : 'absolute'\n }),\n isPortal: () => this.portal,\n createPortal: () => this._createPortal(),\n openOverlay: this._overlay\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._overlay.resume()\n document.addEventListener('click', this._onClickOutside)\n this.addEventListener('focusout', this._onFocusOut)\n this._syncTriggerListeners()\n }\n\n override firstUpdated() {\n // trigger slot 内容增删不触发宿主响应式更新:显式请求一轮渲染,\n // 由 updated() 的 ARIA 回写覆盖晚到的 trigger 元素。\n this.shadowRoot\n ?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]')\n ?.addEventListener('slotchange', () => this.requestUpdate())\n\n if (this.open) {\n // 建立会话的那一帧没有句柄可作 liveness 凭据:关闭路径会 invalidate()(见 updated),\n // 微任务先于 rAF 排空,因此该帧不可能跨过一次关闭执行。\n this._overlay.scheduleFrame(() => {\n this._openOverlay(this._shouldOpenInstantly)\n })\n this._shouldOpenInstantly = true\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n document.removeEventListener('click', this._onClickOutside)\n this.removeEventListener('focusout', this._onFocusOut)\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n // 停止帧调度;撤销登记由 _panel.dispose() 完成(句柄归它持有)。\n this._overlay.suspend()\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._panel.dispose()\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // ARIA 回写不依赖 open 分支,任何渲染后都保持与宿主状态同步。\n this._syncTriggerAria()\n\n if (changed.has('portal') || changed.has('overlayContainer')) {\n // 同帧 open + reconfigure 可能排两个回调;先结束旧事务,让 reconfigure 成为本帧唯一入口。\n this._overlay.invalidate()\n this._overlay.scheduleFrame(() => this._reconfigureOverlay())\n } else if (changed.has('placement') || changed.has('offset'))\n requestAnimationFrame(() => this._panel.updatePosition())\n\n if (changed.has('open')) {\n if (this.open) {\n const isInstant = this._shouldOpenInstantly\n this._shouldOpenInstantly = true\n this._overlay.scheduleFrame(() => {\n this._openOverlay(isInstant)\n })\n if (this._userOpenChange.consume()) this._dispatchChange(true)\n this._focusPanel()\n } else {\n this._overlay.invalidate()\n this._returnFocus()\n void this._closeOverlay()\n if (this._userOpenChange.consume()) this._dispatchChange(false)\n }\n }\n\n if (changed.has('trigger')) {\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._syncTriggerListeners()\n }\n this._syncOverlayInert()\n }\n\n show() {\n if (this.disabled || this.open) return\n this.open = true\n }\n\n close() {\n if (!this.open) return\n this.open = false\n }\n\n toggle() {\n if (this.open) this.close()\n else this.show()\n }\n\n private _isInsideShadowRoot(e: MouseEvent): boolean {\n for (const node of e.composedPath()) {\n if (node instanceof Node && node.getRootNode() === this.shadowRoot) return true\n }\n return false\n }\n\n private _syncTriggerListeners() {\n if (this.trigger === 'hover') {\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n }\n }\n\n /*\n * `manual` 触发器的 popover 不由用户关闭,但仍是候选:Escape 被它吞掉(同时压掉\n * 原生 cancel),只是不走关闭入口。与旧的「跳过候选」语义不同 —— 旧语义放任 Escape\n * 落到下层浮层,把外层一起关掉。trigger 可在开启期间改写,所以走动态通道同步。\n */\n private _syncOverlayInert() {\n this._panel.getHandle()?.setInert(this.trigger === 'manual')\n }\n\n private _openOverlay(isInstant = false) {\n /*\n * 登记与仲裁归属由 anchored panel 完成(claim 要求 panel 已就位,祖先链才判得对)。\n * 原实现在这里额外以宿主为 owner 再登记一次:anchor 是宿主 shadow 内的\n * `.popover-trigger` 包装 div(不是会被 slot 重定向的 trigger 内容),其祖先链上\n * 首个已开启浮层与从宿主上溯结果一致,因此那一次是冗余的。\n */\n this._panel.open(isInstant)\n this._syncOverlayInert()\n }\n\n private _migratableContentNodes(nodes: Node[]): Node[] {\n // 框架注释锚点(v-if/v-for 占位)必须留在宿主:面板内容是实时渲染契约,\n // 框架后续 patch 以宿主内锚点为插入基准,锚点进面板会让插入落空。\n return nodes.filter(node => !(node instanceof Comment) && !(node instanceof HTMLElement && node.slot === 'trigger'))\n }\n\n private _createPortal(): OverlayPortal {\n const portal = defineOverlayPortal().make({\n container: this.overlayContainer,\n target: this,\n style: `${glass}\\n${overlayMotion}\\n${style}`,\n className: 'popover-panel portal wui-glass wui-floating-panel',\n // 已迁移节点被框架删除后的解除追踪是 portal 内建默认行为,这里无需 onContentChange\n migrateAddedNodes: addedNodes => this._migratableContentNodes(addedNodes)\n })\n this._portal = portal\n portal.panel.id = this._panelId\n portal.panel.setAttribute('role', 'dialog')\n portal.panel.tabIndex = -1\n portal.panel.addEventListener('pointerenter', this._onPanelPointerEnter)\n portal.panel.addEventListener('pointerleave', this._onPanelPointerLeave)\n portal.moveContent(this._migratableContentNodes(Array.from(this.childNodes)))\n return portal\n }\n\n private async _closeOverlay() {\n await this._panel.close(() => this.open)\n }\n\n private _reconfigureOverlay() {\n // portal 变更也会登记 rAF;宿主卸载后不得再通过 reconfigure 重建面板。\n if (!this.isConnected) return\n this._panel.reconfigure(this.open)\n /*\n * reconfigure 重新 claim = 新会话,inert 回到 false。这里的重推是**承重的**,不是\n * 防御性备份:`reconfigure` 不触发 Lit 渲染,`updated()` 那次同步不会跟着来。\n * 实测摘掉本行,「portal 变更后 Escape 仍不关闭」立刻转红(select / autocomplete 的\n * 同类重推则被各自的渲染掩盖,摘掉仍绿)。\n */\n this._syncOverlayInert()\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n /*\n * trigger 包装 div 不可聚焦,AT 读不到其 aria 状态:把 aria-expanded /\n * aria-controls 同步回写到 trigger slot 的首个 assigned element(Q8a)。\n * 包装 div 上的同名属性保留(additive,兼容既有查询)。\n */\n private _syncTriggerAria() {\n const trigger = this._queryTrigger()\n if (!trigger) return\n trigger.setAttribute('aria-expanded', String(this.open))\n trigger.setAttribute('aria-controls', this._panelId)\n }\n\n private _focusPanel() {\n this._overlay.scheduleFrame(() => {\n const panel = this._panel.getPanel()\n const autofocus = panel?.querySelector<HTMLElement>('[autofocus]')\n if (autofocus && !autofocus.matches(':disabled, [disabled]')) autofocus.focus()\n })\n }\n\n private _returnFocus() {\n const panel = this._panel.getPanel()\n if (!panel?.matches(':focus-within')) return\n const trigger = this._queryTrigger()\n trigger?.focus()\n }\n\n private _queryTrigger(): HTMLElement | null {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]')\n const el = slot?.assignedElements()[0]\n return el instanceof HTMLElement ? el : null\n }\n\n private _onTriggerClick = (event: MouseEvent) => {\n if (this.disabled) return\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n if (this.trigger === 'hover') return\n if (!this.open) this._shouldOpenInstantly = event.detail === 0\n this._userOpenChange.mark()\n this.toggle()\n }\n\n private _onClickOutside = (e: MouseEvent) => {\n if (!this.open) return\n if (this.trigger === 'manual' || this.trigger === 'hover') return\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n if (this._panel.getHandle()?.containsEvent(e)) return\n if (this._isInsideShadowRoot(e)) return\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _onFocusOut = () => {\n if (this.trigger === 'manual' || this.trigger === 'hover') return\n\n this._overlay.scheduleFrame(() => {\n if (!this.matches(':focus-within') && !this._panel.getHandle()?.hasFocusWithin()) {\n this._userOpenChange.mark()\n this.open = false\n }\n })\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled || this.trigger !== 'hover') return\n clearTimeout(this._hideTimer)\n this._showTimer = setTimeout(() => {\n this._shouldOpenInstantly = false\n if (this.open) return\n this._userOpenChange.mark()\n this.show()\n }, 100)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled || this.trigger !== 'hover') return\n clearTimeout(this._showTimer)\n this._hideTimer = setTimeout(() => {\n if (!this.open) return\n this._userOpenChange.mark()\n this.close()\n }, 100)\n }\n\n private _onPanelPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.trigger !== 'hover') return\n clearTimeout(this._hideTimer)\n }\n\n private _onPanelPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.trigger !== 'hover') return\n this._hideTimer = setTimeout(() => {\n if (!this.open) return\n this._userOpenChange.mark()\n this.close()\n }, 100)\n }\n\n override render() {\n return html`\n <div class=\"popover-anchor\">\n <div\n class=\"popover-trigger\"\n aria-expanded=${String(this.open)}\n aria-controls=${this._panelId}\n @click=${this._onTriggerClick}\n >\n <slot name=\"trigger\"></slot>\n </div>\n <div\n id=${this._panelId}\n class=\"popover-panel wui-floating-panel wui-glass\"\n hidden\n role=\"dialog\"\n tabindex=\"-1\"\n @pointerenter=${this._onPanelPointerEnter}\n @pointerleave=${this._onPanelPointerLeave}\n >\n <slot></slot>\n </div>\n </div>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-popover': WebUiPopover\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/popover/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOpenOverlay } from '@/shared/overlay/open-overlay'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal, type OverlayContainer, type OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_TRIGGERS = ['click', 'hover', 'manual'] as const\n\nlet popoverIdCounter = 0\n\n@customElement('web-ui-popover')\nexport class WebUiPopover extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(overlayMotion), unsafeCSS(style)]\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: String, reflect: true })\n get placement(): Placement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, FLOATING_PLACEMENTS, 'bottom')\n this.requestUpdate('placement', old)\n }\n private _placement: Placement = 'bottom'\n\n @property({ type: Number })\n get offset(): number {\n return this._offset\n }\n set offset(v: number) {\n const old = this._offset\n this._offset = normalizeNumber(v, 0, 100, 8)\n this.requestUpdate('offset', old)\n }\n private _offset = 8\n\n @property({ type: String, reflect: true })\n get trigger(): 'click' | 'hover' | 'manual' {\n return this._trigger\n }\n set trigger(v: string) {\n const old = this._trigger\n this._trigger = normalizeLiteral(v, ALLOWED_TRIGGERS, 'click')\n this.requestUpdate('trigger', old)\n }\n private _trigger: 'click' | 'hover' | 'manual' = 'click'\n\n @property({ type: Boolean, reflect: true }) portal = false\n @property({ attribute: false }) overlayContainer?: OverlayContainer\n\n private _showTimer?: ReturnType<typeof setTimeout>\n private _hideTimer?: ReturnType<typeof setTimeout>\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n\n private _panelId = `wui-popover-panel-${++popoverIdCounter}`\n private _portal?: OverlayPortal\n /**\n * 开启态 overlay(issue #120 Block 1):本组件不再自行监听 keydown,只声明「我开着」,\n * 由唯一仲裁者决定一次 Escape 关谁。实例作用域的帧事务入口也在这里(合并了原\n * lifecycle)—— 它的 lifetime 长于一次开合(跨 `suspend()`/`resume()`),\n * 但不长于实例本身。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => {\n this._userOpenChange.mark()\n this.open = false\n },\n isConnected: () => this.isConnected\n })\n private readonly _panel = defineAnchoredPanel().make({\n getAnchor: () => this.shadowRoot?.querySelector<HTMLElement>('.popover-trigger') ?? null,\n getLocalPanel: () => this.shadowRoot?.querySelector<HTMLElement>('.popover-panel') ?? null,\n getPositioning: () => ({\n placement: this.placement,\n offset: this.offset,\n strategy: this.portal ? 'fixed' : 'absolute'\n }),\n isPortal: () => this.portal,\n createPortal: () => this._createPortal(),\n openOverlay: this._overlay\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._overlay.resume()\n document.addEventListener('click', this._onClickOutside)\n this.addEventListener('focusout', this._onFocusOut)\n this._syncTriggerListeners()\n }\n\n override firstUpdated() {\n // trigger slot 内容增删不触发宿主响应式更新:显式请求一轮渲染,\n // 由 updated() 的 ARIA 回写覆盖晚到的 trigger 元素。\n this.shadowRoot\n ?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]')\n ?.addEventListener('slotchange', () => this.requestUpdate())\n\n if (this.open) {\n // 建立会话的那一帧没有 handle 可作 liveness 凭据:关闭路径会 invalidate()(见 updated),\n // 微任务先于 rAF 排空,因此该帧不可能跨过一次关闭执行。\n this._overlay.scheduleFrame(() => {\n this._openOverlay(this._shouldOpenInstantly)\n })\n this._shouldOpenInstantly = true\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n document.removeEventListener('click', this._onClickOutside)\n this.removeEventListener('focusout', this._onFocusOut)\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n // 停止帧调度;撤销登记由 _panel.dispose() 完成(handle 归它持有)。\n this._overlay.suspend()\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._panel.dispose()\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // ARIA 回写不依赖 open 分支,任何渲染后都保持与宿主状态同步。\n this._syncTriggerAria()\n\n if (changed.has('portal') || changed.has('overlayContainer')) {\n // 同帧 open + reconfigure 可能排两个回调;先结束旧事务,让 reconfigure 成为本帧唯一入口。\n this._overlay.invalidate()\n this._overlay.scheduleFrame(() => this._reconfigureOverlay())\n } else if (changed.has('placement') || changed.has('offset'))\n requestAnimationFrame(() => this._panel.updatePosition())\n\n if (changed.has('open')) {\n if (this.open) {\n const isInstant = this._shouldOpenInstantly\n this._shouldOpenInstantly = true\n this._overlay.scheduleFrame(() => {\n this._openOverlay(isInstant)\n })\n if (this._userOpenChange.consume()) this._dispatchChange(true)\n this._focusPanel()\n } else {\n this._overlay.invalidate()\n this._returnFocus()\n void this._closeOverlay()\n if (this._userOpenChange.consume()) this._dispatchChange(false)\n }\n }\n\n if (changed.has('trigger')) {\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._syncTriggerListeners()\n }\n this._syncOverlayInert()\n }\n\n show() {\n if (this.disabled || this.open) return\n this.open = true\n }\n\n close() {\n if (!this.open) return\n this.open = false\n }\n\n toggle() {\n if (this.open) this.close()\n else this.show()\n }\n\n private _isInsideShadowRoot(e: MouseEvent): boolean {\n for (const node of e.composedPath()) {\n if (node instanceof Node && node.getRootNode() === this.shadowRoot) return true\n }\n return false\n }\n\n private _syncTriggerListeners() {\n if (this.trigger === 'hover') {\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n }\n }\n\n /*\n * `manual` trigger 的 popover 不由用户关闭,但仍是候选:Escape 被它吞掉(同时压掉\n * 原生 cancel),只是不走关闭入口。与旧的「跳过候选」语义不同 —— 旧语义放任 Escape\n * 落到下层 overlay,把外层一起关掉。trigger 可在开启期间改写,所以走动态通道同步。\n */\n private _syncOverlayInert() {\n this._panel.getHandle()?.setInert(this.trigger === 'manual')\n }\n\n private _openOverlay(isInstant = false) {\n /*\n * 登记与仲裁归属由 anchored panel 完成(claim 要求 panel 已就位,祖先链才判得对)。\n * 原实现在这里额外以宿主为 owner 再登记一次:anchor 是宿主 shadow 内的\n * `.popover-trigger` 包装 div(不是会被 slot 重定向的 trigger 内容),其祖先链上\n * 首个已开启 overlay 与从宿主上溯结果一致,因此那一次是冗余的。\n */\n this._panel.open(isInstant)\n this._syncOverlayInert()\n }\n\n private _migratableContentNodes(nodes: Node[]): Node[] {\n // 框架注释锚点(v-if/v-for 占位)必须留在宿主:panel 内容是实时渲染契约,\n // 框架后续 patch 以宿主内锚点为插入基准,锚点进 panel 会让插入落空。\n return nodes.filter(node => !(node instanceof Comment) && !(node instanceof HTMLElement && node.slot === 'trigger'))\n }\n\n private _createPortal(): OverlayPortal {\n const portal = defineOverlayPortal().make({\n container: this.overlayContainer,\n target: this,\n style: `${glass}\\n${overlayMotion}\\n${style}`,\n className: 'popover-panel portal wui-glass wui-floating-panel',\n // 已迁移节点被框架删除后的解除追踪是 portal 内建默认行为,这里无需 onContentChange\n migrateAddedNodes: addedNodes => this._migratableContentNodes(addedNodes)\n })\n this._portal = portal\n portal.panel.id = this._panelId\n portal.panel.setAttribute('role', 'dialog')\n portal.panel.tabIndex = -1\n portal.panel.addEventListener('pointerenter', this._onPanelPointerEnter)\n portal.panel.addEventListener('pointerleave', this._onPanelPointerLeave)\n portal.moveContent(this._migratableContentNodes(Array.from(this.childNodes)))\n return portal\n }\n\n private async _closeOverlay() {\n await this._panel.close(() => this.open)\n }\n\n private _reconfigureOverlay() {\n // portal 变更也会登记 rAF;宿主卸载后不得再通过 reconfigure 重建 panel。\n if (!this.isConnected) return\n this._panel.reconfigure(this.open)\n /*\n * reconfigure 重新 claim = 新会话,inert 回到 false。这里的重推是**承重的**,不是\n * 防御性备份:`reconfigure` 不触发 Lit 渲染,`updated()` 那次同步不会跟着来。\n * 实测摘掉本行,「portal 变更后 Escape 仍不关闭」立刻转红(select / autocomplete 的\n * 同类重推则被各自的渲染掩盖,摘掉仍绿)。\n */\n this._syncOverlayInert()\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n /*\n * trigger 包装 div 不可聚焦,AT 读不到其 aria 状态:把 aria-expanded /\n * aria-controls 同步回写到 trigger slot 的首个 assigned element(Q8a)。\n * 包装 div 上的同名属性保留(additive,兼容既有查询)。\n */\n private _syncTriggerAria() {\n const trigger = this._queryTrigger()\n if (!trigger) return\n trigger.setAttribute('aria-expanded', String(this.open))\n trigger.setAttribute('aria-controls', this._panelId)\n }\n\n private _focusPanel() {\n this._overlay.scheduleFrame(() => {\n const panel = this._panel.getPanel()\n const autofocus = panel?.querySelector<HTMLElement>('[autofocus]')\n if (autofocus && !autofocus.matches(':disabled, [disabled]')) autofocus.focus()\n })\n }\n\n private _returnFocus() {\n const panel = this._panel.getPanel()\n if (!panel?.matches(':focus-within')) return\n const trigger = this._queryTrigger()\n trigger?.focus()\n }\n\n private _queryTrigger(): HTMLElement | null {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]')\n const el = slot?.assignedElements()[0]\n return el instanceof HTMLElement ? el : null\n }\n\n private _onTriggerClick = (event: MouseEvent) => {\n if (this.disabled) return\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n if (this.trigger === 'hover') return\n if (!this.open) this._shouldOpenInstantly = event.detail === 0\n this._userOpenChange.mark()\n this.toggle()\n }\n\n private _onClickOutside = (e: MouseEvent) => {\n if (!this.open) return\n if (this.trigger === 'manual' || this.trigger === 'hover') return\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n if (this._panel.getHandle()?.containsEvent(e)) return\n if (this._isInsideShadowRoot(e)) return\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _onFocusOut = () => {\n if (this.trigger === 'manual' || this.trigger === 'hover') return\n\n this._overlay.scheduleFrame(() => {\n if (!this.matches(':focus-within') && !this._panel.getHandle()?.hasFocusWithin()) {\n this._userOpenChange.mark()\n this.open = false\n }\n })\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled || this.trigger !== 'hover') return\n clearTimeout(this._hideTimer)\n this._showTimer = setTimeout(() => {\n this._shouldOpenInstantly = false\n if (this.open) return\n this._userOpenChange.mark()\n this.show()\n }, 100)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled || this.trigger !== 'hover') return\n clearTimeout(this._showTimer)\n this._hideTimer = setTimeout(() => {\n if (!this.open) return\n this._userOpenChange.mark()\n this.close()\n }, 100)\n }\n\n private _onPanelPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.trigger !== 'hover') return\n clearTimeout(this._hideTimer)\n }\n\n private _onPanelPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.trigger !== 'hover') return\n this._hideTimer = setTimeout(() => {\n if (!this.open) return\n this._userOpenChange.mark()\n this.close()\n }, 100)\n }\n\n override render() {\n return html`\n <div class=\"popover-anchor\">\n <div\n class=\"popover-trigger\"\n aria-expanded=${String(this.open)}\n aria-controls=${this._panelId}\n @click=${this._onTriggerClick}\n >\n <slot name=\"trigger\"></slot>\n </div>\n <div\n id=${this._panelId}\n class=\"popover-panel wui-floating-panel wui-glass\"\n hidden\n role=\"dialog\"\n tabindex=\"-1\"\n @pointerenter=${this._onPanelPointerEnter}\n @pointerleave=${this._onPanelPointerLeave}\n >\n <slot></slot>\n </div>\n </div>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-popover': WebUiPopover\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/popover/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: inline-flex;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n.popover-trigger {\n display: inline-flex;\n}\n\n.popover-anchor {\n position: relative;\n display: inline-flex;\n}\n\n.popover-panel {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n\n /* 面板宽远大于角半径,环底色铺出的整圈铺底会在长边中段读成一条比四角更重的\n 直边描边;转 transparent 后只剩四段角弧,见 menu-portal.css 同处注释。 */\n --wui-color-glass-ring: transparent;\n position: absolute;\n z-index: var(--wui-layer-inline-overlay, 1);\n top: -9999px;\n left: -9999px;\n\n box-sizing: border-box;\n width: max-content;\n max-width: min(90vw, 400px);\n padding: var(--wui-space-2, 8px) var(--wui-space-3, 12px);\n border-radius: var(--wui-radius-menu, 18px);\n\n font-size: var(--wui-font-size, 14px);\n line-height: var(--wui-line-height-normal, 1.5);\n color: var(--wui-color-text, #1b1b1b);\n\n /* 玻璃背景:wui-glass 在面板自身,这里覆盖其默认玻璃底为菜单表面色 */\n background-color: var(--wui-color-surface-menu, rgb(250 250 250 / 0.76));\n\n &:where([hidden]) {\n display: none;\n }\n\n &:focus-visible {\n outline: var(--wui-focus-ring-width, 3px) solid\n var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n outline-offset: 2px;\n }\n}\n\n.popover-panel.portal {\n position: fixed;\n z-index: var(--wui-layer-menu, 100);\n}\n\n:host([portal]) .popover-panel {\n display: none;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/popover/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: inline-flex;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n.popover-trigger {\n display: inline-flex;\n}\n\n.popover-anchor {\n position: relative;\n display: inline-flex;\n}\n\n.popover-panel {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n\n /* panel 宽远大于角半径,环底色铺出的整圈铺底会在长边中段读成一条比四角更重的\n 直边描边;转 transparent 后只剩四段角弧,见 menu-portal.css 同处注释。 */\n --wui-color-glass-ring: transparent;\n position: absolute;\n z-index: var(--wui-layer-inline-overlay, 1);\n top: -9999px;\n left: -9999px;\n\n box-sizing: border-box;\n width: max-content;\n max-width: min(90vw, 400px);\n padding: var(--wui-space-2, 8px) var(--wui-space-3, 12px);\n border-radius: var(--wui-radius-menu, 18px);\n\n font-size: var(--wui-font-size, 14px);\n line-height: var(--wui-line-height-normal, 1.5);\n color: var(--wui-color-text, #1b1b1b);\n\n /* 玻璃背景:wui-glass 在 panel 自身,这里覆盖其默认玻璃底为 menu surface 色 */\n background-color: var(--wui-color-surface-menu, rgb(250 250 250 / 0.76));\n\n &:where([hidden]) {\n display: none;\n }\n\n &:focus-visible {\n outline: var(--wui-focus-ring-width, 3px) solid\n var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n outline-offset: 2px;\n }\n}\n\n.popover-panel.portal {\n position: fixed;\n z-index: var(--wui-layer-menu, 100);\n}\n\n:host([portal]) .popover-panel {\n display: none;\n}\n"],"mappings":""}
@@ -367,7 +367,7 @@ export declare class WebUiRadio extends WebUiRadio_base {
367
367
  static styles: import('lit').CSSResult[];
368
368
  private readonly _groupManagement;
369
369
  private _checked;
370
- /** 标签没有可渲染内容(空槽位,或只挂着一个无障碍名)时收掉 gap,见 LabelEmptinessController。 */
370
+ /** label 没有可渲染内容(空槽位,或只挂着一个无障碍名)时收掉 gap,见 LabelEmptinessController。 */
371
371
  private _labelEmpty;
372
372
  get checked(): boolean;
373
373
  set checked(v: boolean);
@@ -58,7 +58,7 @@ var _ = class extends n(u) {
58
58
  e.setValidity({});
59
59
  return;
60
60
  }
61
- e.setValidity({ valueMissing: !0 }, "请选择一项");
61
+ e.setValidity({ valueMissing: !0 }, "Select an option");
62
62
  }
63
63
  }
64
64
  handleClick() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/radio/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'\n\nimport selectionControl from '@/assets/selection-control.css?inline'\nimport { installPointerFocusSuppression } from '@/shared/focus/pointer-focus'\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\nimport { defineGroupManaged, selectionGroupContextKey, type SelectionGroupContext } from '@/shared/group-management'\nimport { LabelEmptinessController } from '@/shared/label-emptiness'\n\nimport style from './style.css?inline'\n\ninstallPointerFocusSuppression()\n\n@customElement('web-ui-radio')\nexport class WebUiRadio extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(selectionControl), unsafeCSS(style)]\n private readonly _groupManagement = defineGroupManaged<SelectionGroupContext>(this, {\n context: selectionGroupContextKey,\n requestUpdate: () => this.requestUpdate()\n }).make()\n\n @state() private _checked = false\n\n /** 标签没有可渲染内容(空槽位,或只挂着一个无障碍名)时收掉 gap,见 LabelEmptinessController。 */\n @state() private _labelEmpty = 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 }) value = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n\n private get _isDisabled(): boolean {\n return (\n this.disabled || this._formAssociation.isFormDisabled() || this._groupManagement.getContext()?.disabled === true\n )\n }\n\n private get _isManagedByGroup(): boolean {\n return this._groupManagement.getContext() !== undefined\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._isManagedByGroup && 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 isStateManaged: () => this._isManagedByGroup,\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n private readonly _labelEmptiness = new LabelEmptinessController(this, '.wui-radio-label', empty => {\n this._labelEmpty = empty\n })\n\n private _syncValidity() {\n const internals = this._formAssociation.getInternals()\n if (!internals || typeof internals.setValidity !== 'function') return\n if (this._isDisabled || this._isManagedByGroup || !this.required || this._checked) {\n internals.setValidity({})\n return\n }\n internals.setValidity({ valueMissing: true }, '请选择一项')\n }\n\n private handleClick() {\n if (this._isDisabled || this._checked) return\n this._checked = true\n this._formAssociation.sync()\n this.requestUpdate('checked', false)\n // group-managed 时事件不冒泡/不组合,由 group 统一派发一次 host 事件\n const opts: EventInit = this._isManagedByGroup\n ? { bubbles: false, composed: false }\n : { bubbles: true, composed: true }\n this.dispatchEvent(new Event('input', opts))\n this.dispatchEvent(new Event('change', opts))\n }\n\n private handleKeyDown(e: KeyboardEvent) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n this.handleClick()\n }\n }\n\n override render() {\n const cls = {\n 'wui-radio': true,\n 'is-checked': this._checked,\n 'is-label-empty': this._labelEmpty,\n 'is-disabled': this._isDisabled\n }\n\n return html`\n <label\n class=${classMap(cls)}\n tabindex=${this._isDisabled ? '-1' : '0'}\n role=\"radio\"\n aria-checked=${String(this._checked)}\n aria-disabled=${String(this._isDisabled)}\n @click=${this.handleClick}\n @keydown=${this.handleKeyDown}\n >\n <span class=\"wui-radio-circle\">\n <span class=\"wui-radio-dot\"></span>\n </span>\n <span class=\"wui-radio-label\"><slot></slot></span>\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-radio': WebUiRadio\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/radio/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'\n\nimport selectionControl from '@/assets/selection-control.css?inline'\nimport { installPointerFocusSuppression } from '@/shared/focus/pointer-focus'\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\nimport { defineGroupManaged, selectionGroupContextKey, type SelectionGroupContext } from '@/shared/group-management'\nimport { LabelEmptinessController } from '@/shared/label-emptiness'\n\nimport style from './style.css?inline'\n\ninstallPointerFocusSuppression()\n\n@customElement('web-ui-radio')\nexport class WebUiRadio extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(selectionControl), unsafeCSS(style)]\n private readonly _groupManagement = defineGroupManaged<SelectionGroupContext>(this, {\n context: selectionGroupContextKey,\n requestUpdate: () => this.requestUpdate()\n }).make()\n\n @state() private _checked = false\n\n /** label 没有可渲染内容(空槽位,或只挂着一个无障碍名)时收掉 gap,见 LabelEmptinessController。 */\n @state() private _labelEmpty = 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 }) value = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n\n private get _isDisabled(): boolean {\n return (\n this.disabled || this._formAssociation.isFormDisabled() || this._groupManagement.getContext()?.disabled === true\n )\n }\n\n private get _isManagedByGroup(): boolean {\n return this._groupManagement.getContext() !== undefined\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._isManagedByGroup && 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 isStateManaged: () => this._isManagedByGroup,\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n private readonly _labelEmptiness = new LabelEmptinessController(this, '.wui-radio-label', empty => {\n this._labelEmpty = empty\n })\n\n private _syncValidity() {\n const internals = this._formAssociation.getInternals()\n if (!internals || typeof internals.setValidity !== 'function') return\n if (this._isDisabled || this._isManagedByGroup || !this.required || this._checked) {\n internals.setValidity({})\n return\n }\n internals.setValidity({ valueMissing: true }, 'Select an option')\n }\n\n private handleClick() {\n if (this._isDisabled || this._checked) return\n this._checked = true\n this._formAssociation.sync()\n this.requestUpdate('checked', false)\n // group-managed 时事件不冒泡/不组合,由 group 统一派发一次 host 事件\n const opts: EventInit = this._isManagedByGroup\n ? { bubbles: false, composed: false }\n : { bubbles: true, composed: true }\n this.dispatchEvent(new Event('input', opts))\n this.dispatchEvent(new Event('change', opts))\n }\n\n private handleKeyDown(e: KeyboardEvent) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n this.handleClick()\n }\n }\n\n override render() {\n const cls = {\n 'wui-radio': true,\n 'is-checked': this._checked,\n 'is-label-empty': this._labelEmpty,\n 'is-disabled': this._isDisabled\n }\n\n return html`\n <label\n class=${classMap(cls)}\n tabindex=${this._isDisabled ? '-1' : '0'}\n role=\"radio\"\n aria-checked=${String(this._checked)}\n aria-disabled=${String(this._isDisabled)}\n @click=${this.handleClick}\n @keydown=${this.handleKeyDown}\n >\n <span class=\"wui-radio-circle\">\n <span class=\"wui-radio-dot\"></span>\n </span>\n <span class=\"wui-radio-label\"><slot></slot></span>\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-radio': WebUiRadio\n }\n}\n"],"mappings":"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"}
@@ -373,6 +373,13 @@ export declare class WebUiRadioGroup extends WebUiRadioGroup_base {
373
373
  private _direction;
374
374
  private _value;
375
375
  private readonly _groupController;
376
+ /**
377
+ * 成员被移除后,组值若仍指向它就成了幽灵值(表单照旧提交一个不存在的 option)。
378
+ * 剪枝条件是「曾经出现在成员里、现在消失了」——不能用「不在现存成员里就剪」,
379
+ * 否则 v-if / 异步渲染时先于成员到达的合法组值会被误清。
380
+ */
381
+ private _knownMemberValues;
382
+ private _pruneRemovedMembers;
376
383
  get value(): string;
377
384
  set value(v: string);
378
385
  private get _isDisabled();
@@ -17,13 +17,24 @@ var m = ["horizontal", "vertical"], h = class extends t(c) {
17
17
  },
18
18
  getDisabled: () => this._isDisabled,
19
19
  isItem: (e) => e instanceof HTMLElement && e.matches("web-ui-radio"),
20
- isItemSelected: (e, t) => e.value === t,
20
+ isItemSelected: (e, t) => {
21
+ if (!t) return !1;
22
+ let n;
23
+ for (let e of this.querySelectorAll("web-ui-radio")) if (e.value === t && (n ||= e, e.checked)) {
24
+ n = e;
25
+ break;
26
+ }
27
+ return e === n;
28
+ },
21
29
  getNextValue: (e, t) => e.checked ? e.value : t,
22
30
  valuesEqual: (e, t) => e === t,
23
31
  copyValue: (e) => e,
24
32
  setItemSelected: (e, t) => {
25
33
  e.checked = t;
26
34
  },
35
+ resyncOnUnchangedValue: (e) => {
36
+ for (let t of this.querySelectorAll("web-ui-radio")) t.checked = t === e;
37
+ },
27
38
  dispatchValueChange: () => {
28
39
  this.dispatchEvent(new Event("input", {
29
40
  bubbles: !0,
@@ -33,7 +44,7 @@ var m = ["horizontal", "vertical"], h = class extends t(c) {
33
44
  composed: !0
34
45
  }));
35
46
  }
36
- }).make()), this._formAssociation = r({
47
+ }).make(), { afterSync: () => this._pruneRemovedMembers() }), this._knownMemberValues = /* @__PURE__ */ new Set(), this._formAssociation = r({
37
48
  host: this,
38
49
  initialize: () => {
39
50
  let e = this.getAttribute("value");
@@ -61,6 +72,13 @@ var m = ["horizontal", "vertical"], h = class extends t(c) {
61
72
  let t = this._direction;
62
73
  this._direction = o(e, m, "vertical"), this.requestUpdate("direction", t);
63
74
  }
75
+ _pruneRemovedMembers() {
76
+ if (!this._value) return;
77
+ let e = [...this.querySelectorAll("web-ui-radio")];
78
+ if (e.length === 0) return;
79
+ let t = new Set(e.map((e) => e.value));
80
+ !t.has(this._value) && this._knownMemberValues.has(this._value) && (this.value = ""), this._knownMemberValues = t;
81
+ }
64
82
  get value() {
65
83
  return this._value;
66
84
  }
@@ -81,7 +99,7 @@ var m = ["horizontal", "vertical"], h = class extends t(c) {
81
99
  e.setValidity({});
82
100
  return;
83
101
  }
84
- e.setValidity({ valueMissing: !0 }, "请选择一项");
102
+ e.setValidity({ valueMissing: !0 }, "Select an option");
85
103
  }
86
104
  }
87
105
  render() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/radio-group/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\nimport { defineGroupCoordinator, GroupController } from '@/shared/group-management'\nimport { normalizeLiteral } from '@/shared/normalize'\n\nimport type { WebUiRadio } from '../radio'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_DIRECTIONS = ['horizontal', 'vertical'] as const\n\n@customElement('web-ui-radio-group')\nexport class WebUiRadioGroup extends FormAssociated(LitElement) {\n static override styles = unsafeCSS(style)\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n\n @property({ type: String, reflect: true })\n get direction(): 'horizontal' | 'vertical' {\n return this._direction\n }\n set direction(v: string) {\n const old = this._direction\n this._direction = normalizeLiteral(v, ALLOWED_DIRECTIONS, 'vertical')\n this.requestUpdate('direction', old)\n }\n private _direction: 'horizontal' | 'vertical' = 'vertical'\n\n @state() private _value = ''\n\n private readonly _groupController = new GroupController(\n this,\n defineGroupCoordinator<WebUiRadio, string>({\n host: this,\n getItems: () => [...this.querySelectorAll<WebUiRadio>('web-ui-radio')],\n getValue: () => this._value,\n setValue: value => {\n this.value = value\n },\n getDisabled: () => this._isDisabled,\n isItem: (target): target is WebUiRadio => target instanceof HTMLElement && target.matches('web-ui-radio'),\n isItemSelected: (radio, value) => radio.value === value,\n getNextValue: (radio, value) => (radio.checked ? radio.value : value),\n valuesEqual: (a, b) => a === b,\n copyValue: value => value,\n setItemSelected: (radio, selected) => {\n radio.checked = selected\n },\n dispatchValueChange: () => {\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n }).make()\n )\n\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 initialize: () => {\n const attributeValue = this.getAttribute('value')\n if (attributeValue !== null) this.value = attributeValue\n },\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => (this.name && this._value ? this._value : null),\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 formDisabledCallback(disabled: boolean) {\n this._formAssociation.setDisabled(disabled)\n this._groupController.sync()\n }\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._value) {\n internals.setValidity({})\n return\n }\n internals.setValidity({ valueMissing: true }, '请选择一项')\n }\n\n override render() {\n return html`<div class=\"wui-radio-group\"><slot></slot></div>`\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-radio-group': WebUiRadioGroup\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/radio-group/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\nimport { defineGroupCoordinator, GroupController } from '@/shared/group-management'\nimport { normalizeLiteral } from '@/shared/normalize'\n\nimport type { WebUiRadio } from '../radio'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_DIRECTIONS = ['horizontal', 'vertical'] as const\n\n@customElement('web-ui-radio-group')\nexport class WebUiRadioGroup extends FormAssociated(LitElement) {\n static override styles = unsafeCSS(style)\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n\n @property({ type: String, reflect: true })\n get direction(): 'horizontal' | 'vertical' {\n return this._direction\n }\n set direction(v: string) {\n const old = this._direction\n this._direction = normalizeLiteral(v, ALLOWED_DIRECTIONS, 'vertical')\n this.requestUpdate('direction', old)\n }\n private _direction: 'horizontal' | 'vertical' = 'vertical'\n\n @state() private _value = ''\n\n private readonly _groupController = new GroupController(\n this,\n defineGroupCoordinator<WebUiRadio, string>({\n host: this,\n getItems: () => [...this.querySelectorAll<WebUiRadio>('web-ui-radio')],\n getValue: () => this._value,\n setValue: value => {\n this.value = value\n },\n getDisabled: () => this._isDisabled,\n isItem: (target): target is WebUiRadio => target instanceof HTMLElement && target.matches('web-ui-radio'),\n // 选中判定按元素身份而非 value 相等:value 重复时按值比较会把多个成员\n // 同时标为选中,破坏单选不变式。重复值取第一个匹配(或已选中的那个,\n // 避免每次 sync 把选中项跳回第一个)。\n isItemSelected: (radio, value) => {\n if (!value) return false\n let selected: WebUiRadio | undefined\n for (const item of this.querySelectorAll<WebUiRadio>('web-ui-radio')) {\n if (item.value !== value) continue\n if (!selected) selected = item\n if (item.checked) {\n selected = item\n break\n }\n }\n return radio === selected\n },\n getNextValue: (radio, value) => (radio.checked ? radio.value : value),\n valuesEqual: (a, b) => a === b,\n copyValue: value => value,\n setItemSelected: (radio, selected) => {\n radio.checked = selected\n },\n // 点击同值成员时 value 不变,仍需把选中态收到被点击的那一项\n resyncOnUnchangedValue: clicked => {\n for (const radio of this.querySelectorAll<WebUiRadio>('web-ui-radio')) {\n radio.checked = radio === clicked\n }\n },\n dispatchValueChange: () => {\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n }).make(),\n // 每次成员集合同步后把组值剪到现存成员范围内\n { afterSync: () => this._pruneRemovedMembers() }\n )\n\n /**\n * 成员被移除后,组值若仍指向它就成了幽灵值(表单照旧提交一个不存在的 option)。\n * 剪枝条件是「曾经出现在成员里、现在消失了」——不能用「不在现存成员里就剪」,\n * 否则 v-if / 异步渲染时先于成员到达的合法组值会被误清。\n */\n private _knownMemberValues = new Set<string>()\n\n private _pruneRemovedMembers() {\n if (!this._value) return\n const items = [...this.querySelectorAll<WebUiRadio>('web-ui-radio')]\n if (items.length === 0) return\n const alive = new Set(items.map(item => item.value))\n if (!alive.has(this._value) && this._knownMemberValues.has(this._value)) this.value = ''\n this._knownMemberValues = alive\n }\n\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 initialize: () => {\n const attributeValue = this.getAttribute('value')\n if (attributeValue !== null) this.value = attributeValue\n },\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => (this.name && this._value ? this._value : null),\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 formDisabledCallback(disabled: boolean) {\n this._formAssociation.setDisabled(disabled)\n this._groupController.sync()\n }\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._value) {\n internals.setValidity({})\n return\n }\n internals.setValidity({ valueMissing: true }, 'Select an option')\n }\n\n override render() {\n return html`<div class=\"wui-radio-group\"><slot></slot></div>`\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-radio-group': WebUiRadioGroup\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/segmented/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'\n\nimport glass from '@/assets/glass.css?inline'\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, snapToNearest } from '@/shared/gesture/physics'\nimport { defineGroupCoordinator, GroupController } from '@/shared/group-management'\nimport { normalizeLiteral } from '@/shared/normalize'\nimport { parseDuration } from '@/shared/theme/duration'\n\nimport type { WebUiSegmentedTrigger } from '../segmented-trigger'\n\nimport style from './style.css?inline'\n\ninstallPointerFocusSuppression()\n\nconst ALLOWED_VARIANTS = ['inset', 'raised'] as const\n\n/*\n * 指示器移动时长取 `--wui-duration-trigger`,与 CSS 里 left / width 那两条 transition 同一个值。\n * 读不到 token 或格式不符时兜底 160(CSS 的 fallback 同值),负值钳到 0。\n *\n * reduced-motion 下不需要额外分支:theme 已把该 token 归零,settle 窗口随之变成 0,\n * 落定门自动退化成「每一步都直接落定」,而那本来就是 0ms 过渡的观感。\n */\nfunction triggerDuration(el: HTMLElement): number {\n const parsed = parseDuration(getComputedStyle(el).getPropertyValue('--wui-duration-trigger'))\n return parsed === null ? 160 : Math.max(0, parsed)\n}\n\n@customElement('web-ui-segmented')\nexport class WebUiSegmented extends FormAssociated(LitElement) {\n // 轨道是不透明实体面:底色取 surface(浅色即 page,深色即 surface-raised 档)、无\n // backdrop blur,描边环与投影仍由\n // .wui-glass 提供且全状态同值;指示器恒挂同一配方,静止态由 surface-segmented 实色\n // 盖住玻璃输出,按压与拖拽态才透出玻璃。\n // variant=\"raised\" 切回经典形态:flat 灰轨道(surface-segmented、无环无投影)+\n // 实体白指示器(surface-selected + 柔投影),按压/拖拽态两变体同为透明玻璃;\n // 视觉差异全部由 :host([variant=...]) 规则承载,见 style.css。\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n @property({ type: String, reflect: true }) name = ''\n\n @property({ type: String, reflect: true })\n get variant(): 'inset' | 'raised' {\n return this._variant\n }\n set variant(v: string) {\n const old = this._variant\n this._variant = normalizeLiteral(v, ALLOWED_VARIANTS, 'inset')\n this.requestUpdate('variant', old)\n }\n private _variant: 'inset' | 'raised' = 'inset'\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n\n @state() private _value = ''\n @state() private _indicatorReady = false\n @state() private _settling = false\n @state() private _pressed = false\n @state() private _isDragging = false\n\n private _dragGestureHandle: DragGestureHandle | null = null\n /*\n * 飞行令牌 + 世代号(与 web-ui-theme 的 View Transition 飞行门同构)。\n *\n * 缺陷形态:指示器的 left / width 带着 160ms 过渡,而快速连点会连着改 value。若每次改值\n * 都照常起一段过渡,第二段过渡要从**第一段的插值中途**重新起步——指示器于是永远慢半拍,\n * 连点结束时还要再等一整轮才停到终值,与「已经选到第几项」对不上。\n *\n * 门的作用:上一段移动还在飞行时到达的新值**直接落定**——不排队、不重启插值。\n * 落定这一趟自己也要占一段飞行窗口,否则第三次连点又会在落定途中起一段新过渡。\n * 世代号用来丢弃过期回调:窗口到期时若已经有更新的飞行在跑(快速连点会连开窗口),\n * 旧定时器只能提前退出,否则它会把新一轮的落定状态一并清掉、门形同虚设。\n */\n private _settleFlight: object | null = null\n private _settleRequest = 0\n private _settleTimer: number | undefined\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._dragGestureHandle?.destroy()\n this._dragGestureHandle = null\n clearTimeout(this._settleTimer)\n this._settleTimer = undefined\n }\n\n private readonly _groupController = new GroupController(\n this,\n defineGroupCoordinator<WebUiSegmentedTrigger, string>({\n host: this,\n getItems: () => [...this.querySelectorAll<WebUiSegmentedTrigger>('web-ui-segmented-trigger')],\n getValue: () => this._value,\n setValue: value => {\n this.value = value\n },\n getDisabled: () => this._isDisabled,\n isItem: (target): target is WebUiSegmentedTrigger =>\n target instanceof HTMLElement && target.matches('web-ui-segmented-trigger'),\n isItemSelected: (trigger, value) => trigger.value === value,\n getNextValue: (trigger, value) => (trigger.checked ? trigger.value : value),\n valuesEqual: (a, b) => a === b,\n copyValue: value => value,\n setItemSelected: (trigger, selected) => {\n trigger.checked = selected\n },\n dispatchValueChange: () => {\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n }).make(),\n { afterSync: () => requestAnimationFrame(() => void this._updateIndicator()) }\n )\n\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 async _updateIndicator() {\n const triggers = this.querySelectorAll<WebUiSegmentedTrigger>('web-ui-segmented-trigger')\n let left = 0\n let width = 0\n\n triggers.forEach(trigger => {\n if (trigger.value === this._value) {\n const triggerRect = trigger.getBoundingClientRect()\n const groupRect = this.getBoundingClientRect()\n left = triggerRect.left - groupRect.left\n width = triggerRect.width\n }\n })\n\n // 上一段移动还在飞行 → 这次直接落定(_settling 关掉 left / width 过渡,见 style.css)。\n // 落定这一趟自己也要占飞行窗口,所以放在写定位**之前**开窗:窗口一开,第三次连点\n // 落到的仍是本趟的终值,而不是一个刚起步的插值。\n //\n // 首帧定位不开窗:那是初始化而不是一次「移动」,没有前一段飞行可言。开了窗反而会让\n // 「挂载后 160ms 内的第一次点击」直接落定——那次点击本该是用户看到的第一段动画。\n const landing = this._indicatorReady ? this._openSettleFlight() : false\n\n /*\n * 落定这一趟必须等 `.is-settling` 真的进了 DOM 再写定位。\n *\n * `_settling` 是 Lit 的 reactive state:`classMap` 要等一次异步更新才落到元素上,而\n * `--indicator-left` 是同步写的内联样式。顺序反了的话就是「先改位置、过渡仍开着、\n * 下一帧才补上关过渡的 class」——浏览器已经按插值起跑,异步补上的 class 只是把一段\n * 插值中途掐掉,落定表现为一次跳变而不是直接落定,且掐在哪一帧取决于调度。\n */\n if (landing) await this.updateComplete\n\n this.style.setProperty('--indicator-left', `${left}px`)\n this.style.setProperty('--indicator-width', `${width}px`)\n\n // 首帧只完成定位:先把当前定位以「未启用 left/width transition」状态提交为 before 值,\n // 再打开移动动画。若同一趟里 set 定位与启用 transition,浏览器会把 0→选项 当作一次过渡。\n if (!this._indicatorReady) {\n const indicator = this.renderRoot.querySelector<HTMLElement>('.wui-segmented-indicator')\n void indicator?.offsetWidth\n this._indicatorReady = true\n }\n }\n\n /*\n * 开一段飞行窗口:窗口内到达的改值一律落定,窗口到期才恢复插值过渡。\n *\n * 返回本次是否**落定**(飞行中到达的新值)。\n *\n * 时长取 `--wui-duration-trigger`(与 CSS 里 left / width 那两条过渡同一个值),所以\n * reduced-motion 下它就是 0ms——窗口立即到期,落定门自动退化成「每步都落定」,\n * 那正是 0ms 过渡本来的观感,不需要额外分支。\n *\n * 窗口每次改值都从零重开一整段时长(清掉旧定时器、世代号前进),而不是接着算剩余时长:\n * 剩余时长会让连续落定的窗口越缩越短,最后一次连点反倒拿到一段完整过渡——正是这个门\n * 要消除的那种观感。\n */\n private _openSettleFlight(): boolean {\n const wasFlying = this._settleTimer !== undefined\n const flight = {}\n this._settleFlight = flight\n const request = ++this._settleRequest\n clearTimeout(this._settleTimer)\n\n const duration = triggerDuration(this)\n if (duration === 0) {\n // 没有飞行就没有门可守:直接清掉落定态,让后续改值照常走 CSS 的过渡判定\n //(此时该过渡本身就是 0ms)。\n this._settleTimer = undefined\n this._settleFlight = null\n this._settling = false\n return false\n }\n\n // 已经在飞行中 → 这一趟是落定,保持落定态;否则是正常起一段过渡,先退出落定态。\n this._settling = wasFlying\n this._settleTimer = setTimeout(() => {\n this._settleTimer = undefined\n // 世代号校验:窗口被后续改值重开过,旧回调不得清掉新一轮的落定态。\n if (this._settleRequest !== request || this._settleFlight !== flight) return\n this._settleFlight = null\n this._settling = false\n }, duration)\n return wasFlying\n }\n\n private handlePointerDown(e: PointerEvent) {\n if (this._isDisabled) return\n const triggers = [...this.querySelectorAll<WebUiSegmentedTrigger>('web-ui-segmented-trigger')]\n const enabledTriggers = triggers.filter(t => !t.disabled)\n if (enabledTriggers.length === 0) return\n\n const activeTrigger = triggers.find(t => t.value === this._value)\n if (!activeTrigger || activeTrigger.disabled) return\n\n const activeTriggerRect = activeTrigger.getBoundingClientRect()\n const isPressedOnActive =\n e.clientX >= activeTriggerRect.left &&\n e.clientX <= activeTriggerRect.right &&\n e.clientY >= activeTriggerRect.top &&\n e.clientY <= activeTriggerRect.bottom\n\n if (!isPressedOnActive) return\n\n this._pressed = true\n\n const groupRect = this.getBoundingClientRect()\n const initialTriggerRect = activeTrigger.getBoundingClientRect()\n const initialLeft = initialTriggerRect.left - groupRect.left\n const initialWidth = initialTriggerRect.width\n // 拇指中心跟手:left 与宽度分开写(宽度取 hover 项的),中心才是稳定的那个量——\n // 宽度一变,center = left + width/2 就会漂,用它去选吸附目标会跟着抖。\n const initialCenter = initialLeft + initialWidth / 2\n\n const firstTriggerRect = triggers[0].getBoundingClientRect()\n const lastTriggerRect = triggers[triggers.length - 1].getBoundingClientRect()\n const minLeft = firstTriggerRect.left - groupRect.left\n const trackRight = lastTriggerRect.right - groupRect.left\n\n const enabledSlots = enabledTriggers.map(t => {\n const r = t.getBoundingClientRect()\n return { center: r.left - groupRect.left + r.width / 2, width: r.width }\n })\n const enabledCenters = enabledSlots.map(slot => slot.center)\n\n /*\n * 某一帧的拇指几何。\n *\n * 宽度跟随当前 hover 的可用项(#222):trigger 宽度不等是常态(interweave 设置\n * dialog 顶部即此形),宽度恒等于按下那一项会让拇指与它滑过的那项始终对不齐。\n * 取「离拇指中心最近的可用项」而不是「离指针最近的项」:松手吸附用的是同一个判据,\n * 于是拖拽中预览到的宽度就是松手会落到的那一项,不会在松手时二次变形。\n *\n * 宽度变了之后 maxLeft 必须跟着换算(`trackRight − width`),否则更宽的拇指会被\n * 钳在按初始宽度算出的位置上,从轨道右端探出去。\n *\n * 等宽时 width 恒为 initialWidth,`initialCenter + deltaX − initialWidth/2` 恒等于\n * `initialLeft + deltaX`,这一整套与改动前的逐字行为一致。\n */\n const frameAt = (deltaX: number) => {\n const center = initialCenter + deltaX\n const nearest = snapToNearest(center, enabledCenters)\n const width = nearest === -1 ? initialWidth : (enabledSlots[nearest]?.width ?? initialWidth)\n const left = clamp(center - width / 2, minLeft, trackRight - width)\n return { left, width, center: left + width / 2 }\n }\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 { left, width } = frameAt(info.deltaX)\n this.style.setProperty('--indicator-left', `${left}px`)\n this.style.setProperty('--indicator-width', `${width}px`)\n },\n onEnd: info => {\n const wasDragging = this._isDragging\n this._isDragging = false\n this._pressed = false\n\n if (wasDragging) {\n const currentCenter = frameAt(info.deltaX).center\n\n let targetTrigger = activeTrigger\n if (info.velocityX > 300) {\n const currentIdx = enabledTriggers.indexOf(activeTrigger)\n if (currentIdx !== -1 && currentIdx < enabledTriggers.length - 1) {\n targetTrigger = enabledTriggers[currentIdx + 1]\n } else {\n targetTrigger = enabledTriggers[enabledTriggers.length - 1]\n }\n } else if (info.velocityX < -300) {\n const currentIdx = enabledTriggers.indexOf(activeTrigger)\n if (currentIdx > 0) {\n targetTrigger = enabledTriggers[currentIdx - 1]\n } else {\n targetTrigger = enabledTriggers[0]\n }\n } else {\n const nearestIdx = snapToNearest(currentCenter, enabledCenters)\n if (nearestIdx !== -1 && enabledTriggers[nearestIdx]) {\n targetTrigger = enabledTriggers[nearestIdx]\n }\n }\n\n if (targetTrigger.value !== this._value) {\n this.value = targetTrigger.value\n this._groupController.sync()\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n } else {\n void this._updateIndicator()\n }\n }\n this.requestUpdate()\n },\n onCancel: () => {\n this._isDragging = false\n this._pressed = false\n void this._updateIndicator()\n this.requestUpdate()\n }\n })\n }\n\n private handlePointerUp() {\n this._pressed = false\n }\n\n private handlePointerLeave() {\n if (!this._dragGestureHandle?.isDragging()) {\n this._pressed = false\n }\n }\n\n private readonly _formAssociation = defineFormAssociation<string>({\n host: this,\n initialize: () => {\n const attrValue = this.getAttribute('value')\n if (attrValue !== null && this._value === '') this._value = attrValue\n },\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => (this.name && this._value ? this._value : null),\n getFormState: () => this._value,\n restoreState: state => {\n if (typeof state === 'string') this.value = state\n }\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override formDisabledCallback(disabled: boolean) {\n this._formAssociation.setDisabled(disabled)\n this._groupController.sync()\n }\n\n override render() {\n return html`\n <div\n class=${classMap({\n 'wui-glass': true,\n 'wui-segmented': true,\n 'is-disabled': this._isDisabled,\n 'is-pressed': this._pressed,\n 'is-dragging': this._isDragging,\n 'is-indicator-ready': this._indicatorReady,\n 'is-settling': this._settling\n })}\n role=\"listbox\"\n aria-orientation=\"horizontal\"\n @pointerdown=${this.handlePointerDown}\n @pointerup=${this.handlePointerUp}\n @pointercancel=${this.handlePointerUp}\n @pointerleave=${this.handlePointerLeave}\n >\n <span class=\"wui-glass wui-segmented-indicator\"></span>\n <slot></slot>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-segmented': WebUiSegmented\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAiBA,EAA+B;AAE/B,IAAM,IAAmB,CAAC,SAAS,QAAQ;AAS3C,SAAS,EAAgB,GAAyB;CAChD,IAAM,IAAS,EAAc,iBAAiB,CAAE,CAAC,CAAC,iBAAiB,wBAAwB,CAAC;CAC5F,OAAO,MAAW,OAAO,MAAM,KAAK,IAAI,GAAG,CAAM;AACnD;AAGO,IAAM,IAAN,cAA6B,EAAe,CAAU,CAAC,CAAC;;EA4Uf,aAnUI,KAAA,OAAA,IAWX,KAAA,WAAA,SAEgB,KAAA,WAAA,IACA,KAAA,WAAA,IAE7B,KAAA,SAAA,IACS,KAAA,kBAAA,IACN,KAAA,YAAA,IACD,KAAA,WAAA,IACG,KAAA,cAAA,IAEwB,KAAA,qBAAA,MAahB,KAAA,gBAAA,MACd,KAAA,iBAAA,GAWW,KAAA,mBAAA,IAAI,EACtC,MACA,EAAsD;GACpD,MAAM;GACN,gBAAgB,CAAC,GAAG,KAAK,iBAAwC,0BAA0B,CAAC;GAC5F,gBAAgB,KAAK;GACrB,WAAU,MAAS;IACjB,KAAK,QAAQ;GACf;GACA,mBAAmB,KAAK;GACxB,SAAS,MACP,aAAkB,eAAe,EAAO,QAAQ,0BAA0B;GAC5E,iBAAiB,GAAS,MAAU,EAAQ,UAAU;GACtD,eAAe,GAAS,MAAW,EAAQ,UAAU,EAAQ,QAAQ;GACrE,cAAc,GAAG,MAAM,MAAM;GAC7B,YAAW,MAAS;GACpB,kBAAkB,GAAS,MAAa;IACtC,EAAQ,UAAU;GACpB;GACA,2BAA2B;IAEzB,AADA,KAAK,cAAc,IAAI,MAAM,SAAS;KAAE,SAAS;KAAM,UAAU;IAAK,CAAC,CAAC,GACxE,KAAK,cAAc,IAAI,MAAM,UAAU;KAAE,SAAS;KAAM,UAAU;IAAK,CAAC,CAAC;GAC3E;EACF,CAAC,CAAC,CAAC,KAAK,GACR,EAAE,iBAAiB,4BAA4B,KAAK,KAAK,iBAAiB,CAAC,EAAE,CAC/E,GA0OoC,KAAA,mBAAA,EAA8B;GAChE,MAAM;GACN,kBAAkB;IAChB,IAAM,IAAY,KAAK,aAAa,OAAO;IAC3C,AAAI,MAAc,QAAQ,KAAK,WAAW,OAAI,KAAK,SAAS;GAC9D;GACA,gBAAgB,KAAK;GACrB,WAAU,MAAS;IACjB,KAAK,QAAQ;GACf;GACA,oBAAqB,KAAK,QAAQ,KAAK,SAAS,KAAK,SAAS;GAC9D,oBAAoB,KAAK;GACzB,eAAc,MAAS;IACrB,AAAI,OAAO,KAAU,aAAU,KAAK,QAAQ;GAC9C;EACF,CAAC,CAAC,CAAC,KAAK,GAEsC,KAAA,6BAAA,IAAI,EAA0B,MAAM,KAAK,gBAAgB;;;EApU9E,KAAA,SAAA,CAAC,EAAU,CAAK,GAAG,EAAU,CAAK,CAAC;;CAG5D,IACI,UAA8B;EAChC,OAAO,KAAK;CACd;CACA,IAAI,QAAQ,GAAW;EACrB,IAAM,IAAM,KAAK;EAEjB,AADA,KAAK,WAAW,EAAiB,GAAG,GAAkB,OAAO,GAC7D,KAAK,cAAc,WAAW,CAAG;CACnC;CA6BA,uBAAgC;EAK9B,AAJA,MAAM,qBAAqB,GAC3B,KAAK,oBAAoB,QAAQ,GACjC,KAAK,qBAAqB,MAC1B,aAAa,KAAK,YAAY,GAC9B,KAAK,eAAe,KAAA;CACtB;CA6BA,IAAI,QAAgB;EAClB,OAAO,KAAK;CACd;CAEA,IAAI,MAAM,GAAW;EACnB,IAAM,IAAM,KAAK;EAGjB,AAFA,KAAK,SAAS,GACd,KAAK,iBAAiB,KAAK,GAC3B,KAAK,cAAc,SAAS,CAAG;CACjC;CAEA,IAAY,cAAuB;EACjC,OAAO,KAAK,YAAY,KAAK,iBAAiB,eAAe;CAC/D;CAEA,MAAc,mBAAmB;EAC/B,IAAM,IAAW,KAAK,iBAAwC,0BAA0B,GACpF,IAAO,GACP,IAAQ;EAkCZ,AAhCA,EAAS,SAAQ,MAAW;GAC1B,IAAI,EAAQ,UAAU,KAAK,QAAQ;IACjC,IAAM,IAAc,EAAQ,sBAAsB,GAC5C,IAAY,KAAK,sBAAsB;IAE7C,AADA,IAAO,EAAY,OAAO,EAAU,MACpC,IAAQ,EAAY;GACtB;EACF,CAAC,GAQe,KAAK,mBAAkB,KAAK,kBAAkB,KAUjD,MAAM,KAAK,gBAExB,KAAK,MAAM,YAAY,oBAAoB,GAAG,EAAK,GAAG,GACtD,KAAK,MAAM,YAAY,qBAAqB,GAAG,EAAM,GAAG,GAIxD,AAGE,KAAK,qBADL,KADuB,WAAW,cAA2B,0BACxD,CAAA,EAAW,aACO;CAE3B;CAeA,oBAAqC;EACnC,IAAM,IAAY,KAAK,iBAAiB,KAAA,GAClC,IAAS,CAAC;EAChB,KAAK,gBAAgB;EACrB,IAAM,IAAU,EAAE,KAAK;EACvB,aAAa,KAAK,YAAY;EAE9B,IAAM,IAAW,EAAgB,IAAI;EAmBrC,OAlBI,MAAa,KAGf,KAAK,eAAe,KAAA,GACpB,KAAK,gBAAgB,MACrB,KAAK,YAAY,IACV,OAIT,KAAK,YAAY,GACjB,KAAK,eAAe,iBAAiB;GACnC,KAAK,eAAe,KAAA,GAEhB,KAAK,mBAAmB,KAAW,KAAK,kBAAkB,MAC9D,KAAK,gBAAgB,MACrB,KAAK,YAAY;EACnB,GAAG,CAAQ,GACJ;CACT;CAEA,kBAA0B,GAAiB;EACzC,IAAI,KAAK,aAAa;EACtB,IAAM,IAAW,CAAC,GAAG,KAAK,iBAAwC,0BAA0B,CAAC,GACvF,IAAkB,EAAS,QAAO,MAAK,CAAC,EAAE,QAAQ;EACxD,IAAI,EAAgB,WAAW,GAAG;EAElC,IAAM,IAAgB,EAAS,MAAK,MAAK,EAAE,UAAU,KAAK,MAAM;EAChE,IAAI,CAAC,KAAiB,EAAc,UAAU;EAE9C,IAAM,IAAoB,EAAc,sBAAsB;EAO9D,IAAI,EALF,EAAE,WAAW,EAAkB,QAC/B,EAAE,WAAW,EAAkB,SAC/B,EAAE,WAAW,EAAkB,OAC/B,EAAE,WAAW,EAAkB,SAET;EAExB,KAAK,WAAW;EAEhB,IAAM,IAAY,KAAK,sBAAsB,GACvC,IAAqB,EAAc,sBAAsB,GACzD,IAAc,EAAmB,OAAO,EAAU,MAClD,IAAe,EAAmB,OAGlC,IAAgB,IAAc,IAAe,GAE7C,IAAmB,EAAS,EAAE,CAAC,sBAAsB,GACrD,IAAkB,EAAS,EAAS,SAAS,EAAE,CAAC,sBAAsB,GACtE,IAAU,EAAiB,OAAO,EAAU,MAC5C,IAAa,EAAgB,QAAQ,EAAU,MAE/C,IAAe,EAAgB,KAAI,MAAK;GAC5C,IAAM,IAAI,EAAE,sBAAsB;GAClC,OAAO;IAAE,QAAQ,EAAE,OAAO,EAAU,OAAO,EAAE,QAAQ;IAAG,OAAO,EAAE;GAAM;EACzE,CAAC,GACK,IAAiB,EAAa,KAAI,MAAQ,EAAK,MAAM,GAgBrD,KAAW,MAAmB;GAClC,IAAM,IAAS,IAAgB,GACzB,IAAU,EAAc,GAAQ,CAAc,GAC9C,IAAQ,MAAY,KAAK,IAAgB,EAAa,EAAQ,EAAE,SAAS,GACzE,IAAO,EAAM,IAAS,IAAQ,GAAG,GAAS,IAAa,CAAK;GAClE,OAAO;IAAE;IAAM;IAAO,QAAQ,IAAO,IAAQ;GAAE;EACjD;EAGA,AADA,KAAK,oBAAoB,QAAQ,GACjC,KAAK,qBAAqB,EAAkB,GAAG;GAC7C,MAAM;GACN,WAAW;GACX,SAAQ,MAAQ;IACd,KAAK,cAAc;IACnB,IAAM,EAAE,SAAM,aAAU,EAAQ,EAAK,MAAM;IAE3C,AADA,KAAK,MAAM,YAAY,oBAAoB,GAAG,EAAK,GAAG,GACtD,KAAK,MAAM,YAAY,qBAAqB,GAAG,EAAM,GAAG;GAC1D;GACA,QAAO,MAAQ;IACb,IAAM,IAAc,KAAK;IAIzB,IAHA,KAAK,cAAc,IACnB,KAAK,WAAW,IAEZ,GAAa;KACf,IAAM,IAAgB,EAAQ,EAAK,MAAM,CAAC,CAAC,QAEvC,IAAgB;KACpB,IAAI,EAAK,YAAY,KAAK;MACxB,IAAM,IAAa,EAAgB,QAAQ,CAAa;MACxD,AAGE,IAHE,MAAe,MAAM,IAAa,EAAgB,SAAS,IAC7C,EAAgB,IAAa,KAE7B,EAAgB,EAAgB,SAAS;KAE7D,OAAO,IAAI,EAAK,YAAY,MAAM;MAChC,IAAM,IAAa,EAAgB,QAAQ,CAAa;MACxD,AAGE,IAHE,IAAa,IACC,EAAgB,IAAa,KAE7B,EAAgB;KAEpC,OAAO;MACL,IAAM,IAAa,EAAc,GAAe,CAAc;MAC9D,AAAI,MAAe,MAAM,EAAgB,OACvC,IAAgB,EAAgB;KAEpC;KAEA,AAAI,EAAc,UAAU,KAAK,SAM/B,KAAU,iBAAiB,KAL3B,KAAK,QAAQ,EAAc,OAC3B,KAAK,iBAAiB,KAAK,GAC3B,KAAK,cAAc,IAAI,MAAM,SAAS;MAAE,SAAS;MAAM,UAAU;KAAK,CAAC,CAAC,GACxE,KAAK,cAAc,IAAI,MAAM,UAAU;MAAE,SAAS;MAAM,UAAU;KAAK,CAAC,CAAC;IAI7E;IACA,KAAK,cAAc;GACrB;GACA,gBAAgB;IAId,AAHA,KAAK,cAAc,IACnB,KAAK,WAAW,IAChB,KAAU,iBAAiB,GAC3B,KAAK,cAAc;GACrB;EACF,CAAC;CACH;CAEA,kBAA0B;EACxB,KAAK,WAAW;CAClB;CAEA,qBAA6B;EAC3B,AAAK,KAAK,oBAAoB,WAAW,MACvC,KAAK,WAAW;CAEpB;CAqBA,qBAA8B,GAAmB;EAE/C,AADA,KAAK,iBAAiB,YAAY,CAAQ,GAC1C,KAAK,iBAAiB,KAAK;CAC7B;CAEA,SAAkB;EAChB,OAAO,CAAI;;gBAEC,EAAS;GACf,aAAa;GACb,iBAAiB;GACjB,eAAe,KAAK;GACpB,cAAc,KAAK;GACnB,eAAe,KAAK;GACpB,sBAAsB,KAAK;GAC3B,eAAe,KAAK;EACtB,CAAC,EAAE;;;uBAGY,KAAK,kBAAkB;qBACzB,KAAK,gBAAgB;yBACjB,KAAK,gBAAgB;wBACtB,KAAK,mBAAmB;;;;;;CAM9C;AAMF;AAvWG,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GAExC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,WAAA,IAAA,GAWxC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GACzC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GAEzC,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,UAAA,KAAA,CAAA,GACN,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,mBAAA,KAAA,CAAA,GACN,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,aAAA,KAAA,CAAA,GACN,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GACN,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,eAAA,KAAA,CAAA,GA9BR,IAAA,EAAA,CAAA,EAAc,kBAAkB,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/segmented/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'\n\nimport glass from '@/assets/glass.css?inline'\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, snapToNearest } from '@/shared/gesture/physics'\nimport { defineGroupCoordinator, GroupController } from '@/shared/group-management'\nimport { normalizeLiteral } from '@/shared/normalize'\nimport { parseDuration } from '@/shared/theme/duration'\n\nimport type { WebUiSegmentedTrigger } from '../segmented-trigger'\n\nimport style from './style.css?inline'\n\ninstallPointerFocusSuppression()\n\nconst ALLOWED_VARIANTS = ['inset', 'raised'] as const\n\n/*\n * indicator 移动时长取 `--wui-duration-trigger`,与 CSS 里 left / width 那两条 transition 同一个值。\n * 读不到 token 或格式不符时兜底 160(CSS 的 fallback 同值),负值钳到 0。\n *\n * reduced-motion 下不需要额外分支:theme 已把该 token 归零,settle 窗口随之变成 0,\n * 落定门自动退化成「每一步都直接落定」,而那本来就是 0ms 过渡的观感。\n */\nfunction triggerDuration(el: HTMLElement): number {\n const parsed = parseDuration(getComputedStyle(el).getPropertyValue('--wui-duration-trigger'))\n return parsed === null ? 160 : Math.max(0, parsed)\n}\n\n@customElement('web-ui-segmented')\nexport class WebUiSegmented extends FormAssociated(LitElement) {\n // track 是不透明实体面:底色取 surface(浅色即 page,深色即 surface-raised 档)、无\n // backdrop blur,描边环与投影仍由\n // .wui-glass 提供且全状态同值;indicator 恒挂同一配方,静止态由 surface-segmented 实色\n // 盖住玻璃输出,按压与拖拽态才透出玻璃。\n // variant=\"raised\" 切回经典形态:flat 灰 track(surface-segmented、无环无投影)+\n // 实体白 indicator(surface-selected + 柔投影),按压/拖拽态两变体同为透明玻璃;\n // 视觉差异全部由 :host([variant=...]) 规则承载,见 style.css。\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n @property({ type: String, reflect: true }) name = ''\n\n @property({ type: String, reflect: true })\n get variant(): 'inset' | 'raised' {\n return this._variant\n }\n set variant(v: string) {\n const old = this._variant\n this._variant = normalizeLiteral(v, ALLOWED_VARIANTS, 'inset')\n this.requestUpdate('variant', old)\n }\n private _variant: 'inset' | 'raised' = 'inset'\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) required = false\n\n @state() private _value = ''\n @state() private _indicatorReady = false\n @state() private _settling = false\n @state() private _pressed = false\n @state() private _isDragging = false\n\n private _dragGestureHandle: DragGestureHandle | null = null\n /*\n * 飞行令牌 + 世代号(与 web-ui-theme 的 View Transition 飞行门同构)。\n *\n * 缺陷形态:indicator 的 left / width 带着 160ms 过渡,而快速连点会连着改 value。若每次改值\n * 都照常起一段过渡,第二段过渡要从**第一段的插值中途**重新起步——indicator 于是永远慢半拍,\n * 连点结束时还要再等一整轮才停到终值,与「已经选到第几项」对不上。\n *\n * 门的作用:上一段移动还在飞行时到达的新值**直接落定**——不排队、不重启插值。\n * 落定这一趟自己也要占一段飞行窗口,否则第三次连点又会在落定途中起一段新过渡。\n * 世代号用来丢弃过期回调:窗口到期时若已经有更新的飞行在跑(快速连点会连开窗口),\n * 旧定时器只能提前退出,否则它会把新一轮的落定状态一并清掉、门形同虚设。\n */\n private _settleFlight: object | null = null\n private _settleRequest = 0\n private _settleTimer: number | undefined\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._dragGestureHandle?.destroy()\n this._dragGestureHandle = null\n clearTimeout(this._settleTimer)\n this._settleTimer = undefined\n }\n\n private readonly _groupController = new GroupController(\n this,\n defineGroupCoordinator<WebUiSegmentedTrigger, string>({\n host: this,\n getItems: () => [...this.querySelectorAll<WebUiSegmentedTrigger>('web-ui-segmented-trigger')],\n getValue: () => this._value,\n setValue: value => {\n this.value = value\n },\n getDisabled: () => this._isDisabled,\n isItem: (target): target is WebUiSegmentedTrigger =>\n target instanceof HTMLElement && target.matches('web-ui-segmented-trigger'),\n isItemSelected: (trigger, value) => trigger.value === value,\n getNextValue: (trigger, value) => (trigger.checked ? trigger.value : value),\n valuesEqual: (a, b) => a === b,\n copyValue: value => value,\n setItemSelected: (trigger, selected) => {\n trigger.checked = selected\n },\n dispatchValueChange: () => {\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n }).make(),\n { afterSync: () => requestAnimationFrame(() => void this._updateIndicator()) }\n )\n\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 async _updateIndicator() {\n const triggers = this.querySelectorAll<WebUiSegmentedTrigger>('web-ui-segmented-trigger')\n let left = 0\n let width = 0\n\n triggers.forEach(trigger => {\n if (trigger.value === this._value) {\n const triggerRect = trigger.getBoundingClientRect()\n const groupRect = this.getBoundingClientRect()\n left = triggerRect.left - groupRect.left\n width = triggerRect.width\n }\n })\n\n // 上一段移动还在飞行 → 这次直接落定(_settling 关掉 left / width 过渡,见 style.css)。\n // 落定这一趟自己也要占飞行窗口,所以放在写定位**之前**开窗:窗口一开,第三次连点\n // 落到的仍是本趟的终值,而不是一个刚起步的插值。\n //\n // 首帧定位不开窗:那是初始化而不是一次「移动」,没有前一段飞行可言。开了窗反而会让\n // 「挂载后 160ms 内的第一次点击」直接落定——那次点击本该是用户看到的第一段动画。\n const landing = this._indicatorReady ? this._openSettleFlight() : false\n\n /*\n * 落定这一趟必须等 `.is-settling` 真的进了 DOM 再写定位。\n *\n * `_settling` 是 Lit 的 reactive state:`classMap` 要等一次异步更新才落到元素上,而\n * `--indicator-left` 是同步写的内联样式。顺序反了的话就是「先改位置、过渡仍开着、\n * 下一帧才补上关过渡的 class」——浏览器已经按插值起跑,异步补上的 class 只是把一段\n * 插值中途掐掉,落定表现为一次跳变而不是直接落定,且掐在哪一帧取决于调度。\n */\n if (landing) await this.updateComplete\n\n this.style.setProperty('--indicator-left', `${left}px`)\n this.style.setProperty('--indicator-width', `${width}px`)\n\n // 首帧只完成定位:先把当前定位以「未启用 left/width transition」状态提交为 before 值,\n // 再打开移动动画。若同一趟里 set 定位与启用 transition,浏览器会把 0→option 当作一次过渡。\n if (!this._indicatorReady) {\n const indicator = this.renderRoot.querySelector<HTMLElement>('.wui-segmented-indicator')\n void indicator?.offsetWidth\n this._indicatorReady = true\n }\n }\n\n /*\n * 开一段飞行窗口:窗口内到达的改值一律落定,窗口到期才恢复插值过渡。\n *\n * 返回本次是否**落定**(飞行中到达的新值)。\n *\n * 时长取 `--wui-duration-trigger`(与 CSS 里 left / width 那两条过渡同一个值),所以\n * reduced-motion 下它就是 0ms——窗口立即到期,落定门自动退化成「每步都落定」,\n * 那正是 0ms 过渡本来的观感,不需要额外分支。\n *\n * 窗口每次改值都从零重开一整段时长(清掉旧定时器、世代号前进),而不是接着算剩余时长:\n * 剩余时长会让连续落定的窗口越缩越短,最后一次连点反倒拿到一段完整过渡——正是这个门\n * 要消除的那种观感。\n */\n private _openSettleFlight(): boolean {\n const wasFlying = this._settleTimer !== undefined\n const flight = {}\n this._settleFlight = flight\n const request = ++this._settleRequest\n clearTimeout(this._settleTimer)\n\n const duration = triggerDuration(this)\n if (duration === 0) {\n // 没有飞行就没有门可守:直接清掉落定态,让后续改值照常走 CSS 的过渡判定\n //(此时该过渡本身就是 0ms)。\n this._settleTimer = undefined\n this._settleFlight = null\n this._settling = false\n return false\n }\n\n // 已经在飞行中 → 这一趟是落定,保持落定态;否则是正常起一段过渡,先退出落定态。\n this._settling = wasFlying\n this._settleTimer = setTimeout(() => {\n this._settleTimer = undefined\n // 世代号校验:窗口被后续改值重开过,旧回调不得清掉新一轮的落定态。\n if (this._settleRequest !== request || this._settleFlight !== flight) return\n this._settleFlight = null\n this._settling = false\n }, duration)\n return wasFlying\n }\n\n private handlePointerDown(e: PointerEvent) {\n if (this._isDisabled) return\n const triggers = [...this.querySelectorAll<WebUiSegmentedTrigger>('web-ui-segmented-trigger')]\n const enabledTriggers = triggers.filter(t => !t.disabled)\n if (enabledTriggers.length === 0) return\n\n const activeTrigger = triggers.find(t => t.value === this._value)\n if (!activeTrigger || activeTrigger.disabled) return\n\n const activeTriggerRect = activeTrigger.getBoundingClientRect()\n const isPressedOnActive =\n e.clientX >= activeTriggerRect.left &&\n e.clientX <= activeTriggerRect.right &&\n e.clientY >= activeTriggerRect.top &&\n e.clientY <= activeTriggerRect.bottom\n\n if (!isPressedOnActive) return\n\n this._pressed = true\n\n const groupRect = this.getBoundingClientRect()\n const initialTriggerRect = activeTrigger.getBoundingClientRect()\n const initialLeft = initialTriggerRect.left - groupRect.left\n const initialWidth = initialTriggerRect.width\n // indicator 中心跟手:left 与宽度分开写(宽度取 hover 项的),中心才是稳定的那个量——\n // 宽度一变,center = left + width/2 就会漂,用它去选吸附目标会跟着抖。\n const initialCenter = initialLeft + initialWidth / 2\n\n const firstTriggerRect = triggers[0].getBoundingClientRect()\n const lastTriggerRect = triggers[triggers.length - 1].getBoundingClientRect()\n const minLeft = firstTriggerRect.left - groupRect.left\n const trackRight = lastTriggerRect.right - groupRect.left\n\n const enabledSlots = enabledTriggers.map(t => {\n const r = t.getBoundingClientRect()\n return { center: r.left - groupRect.left + r.width / 2, width: r.width }\n })\n const enabledCenters = enabledSlots.map(slot => slot.center)\n\n /*\n * 某一帧的 indicator 几何。\n *\n * 宽度跟随当前 hover 的可用项(#222):trigger 宽度不等是常态(interweave 设置\n * dialog 顶部即此形),宽度恒等于按下那一项会让 indicator 与它滑过的那项始终对不齐。\n * 取「离 indicator 中心最近的可用项」而不是「离指针最近的项」:松手吸附用的是同一个判据,\n * 于是拖拽中预览到的宽度就是松手会落到的那一项,不会在松手时二次变形。\n *\n * 宽度变了之后 maxLeft 必须跟着换算(`trackRight − width`),否则更宽的 indicator 会被\n * 钳在按初始宽度算出的位置上,从 track 右端探出去。\n *\n * 等宽时 width 恒为 initialWidth,`initialCenter + deltaX − initialWidth/2` 恒等于\n * `initialLeft + deltaX`,这一整套与改动前的逐字行为一致。\n */\n const frameAt = (deltaX: number) => {\n const center = initialCenter + deltaX\n const nearest = snapToNearest(center, enabledCenters)\n const width = nearest === -1 ? initialWidth : (enabledSlots[nearest]?.width ?? initialWidth)\n const left = clamp(center - width / 2, minLeft, trackRight - width)\n return { left, width, center: left + width / 2 }\n }\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 { left, width } = frameAt(info.deltaX)\n this.style.setProperty('--indicator-left', `${left}px`)\n this.style.setProperty('--indicator-width', `${width}px`)\n },\n onEnd: info => {\n const wasDragging = this._isDragging\n this._isDragging = false\n this._pressed = false\n\n if (wasDragging) {\n const currentCenter = frameAt(info.deltaX).center\n\n let targetTrigger = activeTrigger\n if (info.velocityX > 300) {\n const currentIdx = enabledTriggers.indexOf(activeTrigger)\n if (currentIdx !== -1 && currentIdx < enabledTriggers.length - 1) {\n targetTrigger = enabledTriggers[currentIdx + 1]\n } else {\n targetTrigger = enabledTriggers[enabledTriggers.length - 1]\n }\n } else if (info.velocityX < -300) {\n const currentIdx = enabledTriggers.indexOf(activeTrigger)\n if (currentIdx > 0) {\n targetTrigger = enabledTriggers[currentIdx - 1]\n } else {\n targetTrigger = enabledTriggers[0]\n }\n } else {\n const nearestIdx = snapToNearest(currentCenter, enabledCenters)\n if (nearestIdx !== -1 && enabledTriggers[nearestIdx]) {\n targetTrigger = enabledTriggers[nearestIdx]\n }\n }\n\n if (targetTrigger.value !== this._value) {\n this.value = targetTrigger.value\n this._groupController.sync()\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n } else {\n void this._updateIndicator()\n }\n }\n this.requestUpdate()\n },\n onCancel: () => {\n this._isDragging = false\n this._pressed = false\n void this._updateIndicator()\n this.requestUpdate()\n }\n })\n }\n\n private handlePointerUp() {\n this._pressed = false\n }\n\n private handlePointerLeave() {\n if (!this._dragGestureHandle?.isDragging()) {\n this._pressed = false\n }\n }\n\n private readonly _formAssociation = defineFormAssociation<string>({\n host: this,\n initialize: () => {\n const attrValue = this.getAttribute('value')\n if (attrValue !== null && this._value === '') this._value = attrValue\n },\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => (this.name && this._value ? this._value : null),\n getFormState: () => this._value,\n restoreState: state => {\n if (typeof state === 'string') this.value = state\n }\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override formDisabledCallback(disabled: boolean) {\n this._formAssociation.setDisabled(disabled)\n this._groupController.sync()\n }\n\n override render() {\n return html`\n <div\n class=${classMap({\n 'wui-glass': true,\n 'wui-segmented': true,\n 'is-disabled': this._isDisabled,\n 'is-pressed': this._pressed,\n 'is-dragging': this._isDragging,\n 'is-indicator-ready': this._indicatorReady,\n 'is-settling': this._settling\n })}\n role=\"listbox\"\n aria-orientation=\"horizontal\"\n @pointerdown=${this.handlePointerDown}\n @pointerup=${this.handlePointerUp}\n @pointercancel=${this.handlePointerUp}\n @pointerleave=${this.handlePointerLeave}\n >\n <span class=\"wui-glass wui-segmented-indicator\"></span>\n <slot></slot>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-segmented': WebUiSegmented\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/segmented/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n touch-action: none;\n user-select: none;\n display: inline-block;\n}\n\n.wui-segmented {\n /* 玻璃描边的角点半径与控件圆角同源:胶囊取短边一半(button-group 同法)。 */\n --wui-glass-corner-radius: calc(var(--wui-control-size, 36px) / 2);\n\n touch-action: none;\n cursor: default;\n\n position: relative;\n\n display: inline-flex;\n align-items: stretch;\n\n padding: var(--wui-space-1, 4px);\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n /* inset 变体(默认,「凹」):轨道是不透明实体面——底色取 --wui-color-surface、\n backdrop-filter 移除,描边环(::before)与投影由 .wui-glass 提供且静止/按压/拖拽\n 全状态同值。\n 为什么不是 surface-raised:#169 追加验收要求浅色下轨道与页面同色——surface-raised\n 的 #f2f2f7 在纯白页面上像一颗浅灰药丸凸出来,凹感不足。浅色 surface 与 page 同为\n #fff,轨道因此与页面同色,深度只由常驻的 1px 描边环与投影承担;深色 surface 与\n surface-raised 同为 #2c2c2e,「比页面高一档」的 elevation 原样保留(theme 里这两个\n token 在深色下同值,选 surface 不改变深色观感)。\n 实体化的取证依据:thumb 的 backdrop 采样面整个落在轨道之内(slider thumb 探出\n track 外、能采到页面背景,segmented 没有这个条件),玻璃轨道会把背后的条纹/色斑\n 连同自身边缘一起模糊进采样图——均匀深底上全透明 thumb 与相邻 track 仅差 +0.5 阶\n (42.8 vs 42.3)、本体不可感知,条纹底实测 thumb 内部灰度 std 1.89(背景渗透)。\n 轨道改实色后同一采样 std 归零,弧线与两条横线随磨砂边界一同消失,look3/4/5 为\n 压制这些采样痕迹引入的按压态特判(inset 高光淡出、环半隐、6% tint)全部移除。 */\n background-color: var(--wui-color-surface, #fff);\n backdrop-filter: none;\n\n &.is-disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n &.is-disabled .wui-segmented-trigger.is-disabled {\n opacity: 1;\n }\n}\n\n/* raised 变体(「凸」):经典形态的 flat 灰轨道——surface-segmented 实色,无描边环、无投影\n (.wui-glass 的 box-shadow 与 ::before 环整体关掉),三种状态下保持同值。 */\n:host([variant='raised']) .wui-segmented {\n background-color: var(--wui-color-surface-segmented, #e5e5ea);\n box-shadow: none;\n}\n\n:host([variant='raised']) .wui-segmented::before {\n opacity: 0;\n}\n\n.wui-segmented-indicator {\n /* 玻璃描边的角点半径与控件圆角同源:胶囊取短边一半(switch thumb 同法)。 */\n --wui-glass-corner-radius: calc(var(--wui-control-size, 36px) / 2);\n\n position: absolute;\n\n /* 栈序契约:thumb 恒在 track 之上、文字之下。track 的 ::before 描边环按树序先画,\n trigger 的 z-index: 1 又压在 thumb 之上,所以 thumb 既不盖字也不被轨道盖住。 */\n z-index: 0;\n top: 4px;\n bottom: 4px;\n left: var(--indicator-left, 0);\n transform-origin: center center;\n transform: scale(1);\n\n display: block;\n\n width: var(--indicator-width, 0);\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n /* inset 变体(默认)静止态是纯灰底:.wui-glass 恒挂只为复用按压态配方,这里把它的\n glass-only 输出逐条关掉——backdrop blur、::before 描边环、inset 高光与投影,\n 只留 surface-segmented 实色。 */\n background-color: var(--wui-color-surface-segmented, #e5e5ea);\n backdrop-filter: none;\n box-shadow: none;\n\n transition:\n transform 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 /* transition 只在首帧定位后且非拖拽状态启用,避免组件进入视口时触发初始化动画或拖拽跟手产生延迟;\n 其中的 background-color 只服务松开方向——按下态自己不含该属性,见下方按压/拖拽规则。\n backdrop-filter 不进任何过渡列表:静止态必须是 none(验收要求),而 none ↔ blur() 不可插值\n (dialog 的生硬跳变根因),离散过渡只会在 50% 处翻转,按下后前 40ms 会留下「已变色但无 blur」\n 的空洞帧;改为瞬时切换后,按下第一帧就是完整的按压态玻璃。 */\n .is-indicator-ready:not(.is-dragging) & {\n transition:\n left var(--wui-duration-trigger, 160ms) var(--wui-ease-slide, cubic-bezier(0.77, 0, 0.175, 1)),\n width var(--wui-duration-trigger, 160ms) var(--wui-ease-slide, cubic-bezier(0.77, 0, 0.175, 1)),\n transform 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}\n\n/*\n * 落定门:上一段指示器移动还在飞行时到达的新值直接落到终值,不排队也不重启插值。\n * 做法是把 left / width 从过渡列表里摘掉——位置一变即刻生效,其余过渡(按压玻璃那几条)\n * 原样保留,所以这个门只关位置动画,不会把按压反馈一起关掉。\n *\n * 单独成规则而**不是**给上面那条加 `:not(.is-settling)`:那样特异性从 (0,3,0) 升到 (0,4,0),\n * 于是下方同为 (0,3,0) 的按压态规则(`:163`)与 raised 变体(`:135`)都改不动它了——按压态\n * 正是靠同特异性的后置顺序才赢过 ready 规则,被反超后 background-color 会从「瞬时切到透明」\n * 变回有插值(ready 规则把该属性列进了过渡),玻璃按压反馈整个变形。stylelint 的\n * no-descending-specificity 同样直接把这三条判成「应排在它之前」,是这条推理的机器旁证。\n * 放在 ready 之后、press 之前:同特异性下后置者赢,于是 ready 被压掉、press 仍压得住本条。\n */\n.is-indicator-ready.is-settling .wui-segmented-indicator {\n transition:\n transform 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\n/* raised 变体静止态:实体白指示器 + 柔投影(经典形态;dark 主题 surface-selected 取\n #5c5c5e 凸起灰,同为实体档)。仅换底与投影,几何与过渡和 inset 变体同源。\n 注意源顺序:本规则与下方按压/拖拽规则特异性相同((0,3,0)),按压态必须排在更后面,\n 才能把实体底/柔投影覆盖成透明玻璃配方。 */\n:host([variant='raised']) .wui-segmented-indicator {\n background-color: var(--wui-color-surface-selected, #fff);\n box-shadow:\n 0 1px 3px rgb(0 0 0 / 0.12),\n 0 1px 2px rgb(0 0 0 / 0.08);\n}\n\n/* 静止态不绘制玻璃描边环:.wui-glass::before 是角点环 + 对角光泽层,纯灰底不需要它。\n 用 opacity 而不是 display:none,按压态才能让环随过渡淡入,而不是整层跳出。 */\n.wui-segmented-indicator::before {\n opacity: 0;\n transition: opacity var(--wui-duration-press, 80ms) ease;\n}\n\n/* 按压/拖拽时 thumb 转为玻璃态:静止态关掉的玻璃输出在这一态全部打开——backdrop blur、\n ::before 描边环、inset 高光,投影叠加更实一档并放大 1.5x。\n 底色全透明:轨道实体化后 thumb 的采样面拿到的是均匀实色(条纹底实测内部 std 归零),\n 本体可感知性由实色轨道与 1.5x 放大/环/投影共同承担,不再需要 tint 打底,也不需要\n 为采样痕迹去压制轨道的边缘装饰——那些按压态特判已全部移除,轨道装饰全状态恒定。\n transition 不含 background-color:按下即落到全透明,不产生灰→透明的插值中间帧;\n 松开回到静止态规则,由那里的 background-color 过渡把透明平滑淡回实体底。\n backdrop-filter 直接写死值(与 drawer/dialog/toast 的玻璃态同法):静止态是 none,\n glass 配方由这一态自己声明,不随 .wui-glass 类是否挂载而变;该属性同样不进 transition,\n 理由见静止态规则上方的说明。\n box-shadow 直接写死值(不经 --wui-internal-glass-shadow 中转):iOS Safari 对 var\n 离散切换驱动的 box-shadow 不触发过渡且可能整体不生效(slider thumb 同法),直接写值\n 后是普通属性变化,transition 正常插值;三条 inset 高光复制自 .wui-glass,避免整段\n 覆盖后按压态丢失玻璃描边。 */\n.wui-segmented.is-pressed .wui-segmented-indicator,\n.wui-segmented.is-dragging .wui-segmented-indicator {\n /* 环底色随按压/拖拽态转 transparent(理由见 menu-portal.css)。 */\n --wui-color-glass-ring: transparent;\n transform: scale(1.5);\n\n background-color: transparent;\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\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 transition:\n transform var(--wui-duration-press, 80ms) cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow var(--wui-duration-press, 80ms) ease;\n}\n\n.wui-segmented.is-pressed .wui-segmented-indicator::before,\n.wui-segmented.is-dragging .wui-segmented-indicator::before {\n opacity: 1;\n}\n\n.wui-segmented.is-dragging:not(.is-disabled) {\n --wui-internal-segmented-trigger-cursor: grabbing;\n cursor: grabbing;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/segmented/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n touch-action: none;\n user-select: none;\n display: inline-block;\n}\n\n.wui-segmented {\n /* 玻璃描边的角点半径与控件圆角同源:胶囊取短边一半(button-group 同法)。 */\n --wui-glass-corner-radius: calc(var(--wui-control-size, 36px) / 2);\n\n touch-action: none;\n cursor: default;\n\n position: relative;\n\n display: inline-flex;\n align-items: stretch;\n\n padding: var(--wui-space-1, 4px);\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n /* inset 变体(默认,「凹」):track 是不透明实体面——底色取 --wui-color-surface、\n backdrop-filter 移除,描边环(::before)与投影由 .wui-glass 提供且静止/按压/拖拽\n 全状态同值。\n 为什么不是 surface-raised:#169 追加验收要求浅色下 track 与页面同色——surface-raised\n 的 #f2f2f7 在纯白页面上像一颗浅灰药丸凸出来,凹感不足。浅色 surface 与 page 同为\n #fff,track 因此与页面同色,深度只由常驻的 1px 描边环与投影承担;深色 surface 与\n surface-raised 同为 #2c2c2e,「比页面高一档」的 elevation 原样保留(theme 里这两个\n token 在深色下同值,选 surface 不改变深色观感)。\n 实体化的取证依据:indicator 的 backdrop 采样面整个落在 track 之内(slider thumb 探出\n track 外、能采到页面背景,segmented 没有这个条件),玻璃 track 会把背后的条纹/色斑\n 连同自身边缘一起模糊进采样图——均匀深底上全透明 indicator 与相邻 track 仅差 +0.5 阶\n (42.8 vs 42.3)、本体不可感知,条纹底实测 indicator 内部灰度 std 1.89(背景渗透)。\n track 改实色后同一采样 std 归零,弧线与两条横线随磨砂边界一同消失,look3/4/5 为\n 压制这些采样痕迹引入的按压态特判(inset 高光淡出、环半隐、6% tint)全部移除。 */\n background-color: var(--wui-color-surface, #fff);\n backdrop-filter: none;\n\n &.is-disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n &.is-disabled .wui-segmented-trigger.is-disabled {\n opacity: 1;\n }\n}\n\n/* raised 变体(「凸」):经典形态的 flat 灰 track——surface-segmented 实色,无描边环、无投影\n (.wui-glass 的 box-shadow 与 ::before 环整体关掉),三种状态下保持同值。 */\n:host([variant='raised']) .wui-segmented {\n background-color: var(--wui-color-surface-segmented, #e5e5ea);\n box-shadow: none;\n}\n\n:host([variant='raised']) .wui-segmented::before {\n opacity: 0;\n}\n\n.wui-segmented-indicator {\n /* 玻璃描边的角点半径与控件圆角同源:胶囊取短边一半(switch thumb 同法)。 */\n --wui-glass-corner-radius: calc(var(--wui-control-size, 36px) / 2);\n\n position: absolute;\n\n /* 栈序契约:indicator 恒在 track 之上、文字之下。track 的 ::before 描边环按树序先画,\n trigger 的 z-index: 1 又压在 indicator 之上,所以 indicator 既不盖字也不被 track 盖住。 */\n z-index: 0;\n top: 4px;\n bottom: 4px;\n left: var(--indicator-left, 0);\n transform-origin: center center;\n transform: scale(1);\n\n display: block;\n\n width: var(--indicator-width, 0);\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n /* inset 变体(默认)静止态是纯灰底:.wui-glass 恒挂只为复用按压态配方,这里把它的\n glass-only 输出逐条关掉——backdrop blur、::before 描边环、inset 高光与投影,\n 只留 surface-segmented 实色。 */\n background-color: var(--wui-color-surface-segmented, #e5e5ea);\n backdrop-filter: none;\n box-shadow: none;\n\n transition:\n transform 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 /* transition 只在首帧定位后且非拖拽状态启用,避免组件进入视口时触发初始化动画或拖拽跟手产生延迟;\n 其中的 background-color 只服务松开方向——按下态自己不含该属性,见下方按压/拖拽规则。\n backdrop-filter 不进任何过渡列表:静止态必须是 none(验收要求),而 none ↔ blur() 不可插值\n (dialog 的生硬跳变根因),离散过渡只会在 50% 处翻转,按下后前 40ms 会留下「已变色但无 blur」\n 的空洞帧;改为瞬时切换后,按下第一帧就是完整的按压态玻璃。 */\n .is-indicator-ready:not(.is-dragging) & {\n transition:\n left var(--wui-duration-trigger, 160ms) var(--wui-ease-slide, cubic-bezier(0.77, 0, 0.175, 1)),\n width var(--wui-duration-trigger, 160ms) var(--wui-ease-slide, cubic-bezier(0.77, 0, 0.175, 1)),\n transform 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}\n\n/*\n * 落定门:上一段 indicator 移动还在飞行时到达的新值直接落到终值,不排队也不重启插值。\n * 做法是把 left / width 从过渡列表里摘掉——位置一变即刻生效,其余过渡(按压玻璃那几条)\n * 原样保留,所以这个门只关位置动画,不会把按压反馈一起关掉。\n *\n * 单独成规则而**不是**给上面那条加 `:not(.is-settling)`:那样特异性从 (0,3,0) 升到 (0,4,0),\n * 于是下方同为 (0,3,0) 的按压态规则(`:163`)与 raised 变体(`:135`)都改不动它了——按压态\n * 正是靠同特异性的后置顺序才赢过 ready 规则,被反超后 background-color 会从「瞬时切到透明」\n * 变回有插值(ready 规则把该属性列进了过渡),玻璃按压反馈整个变形。stylelint 的\n * no-descending-specificity 同样直接把这三条判成「应排在它之前」,是这条推理的机器旁证。\n * 放在 ready 之后、press 之前:同特异性下后置者赢,于是 ready 被压掉、press 仍压得住本条。\n */\n.is-indicator-ready.is-settling .wui-segmented-indicator {\n transition:\n transform 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\n/* raised 变体静止态:实体白 indicator + 柔投影(经典形态;dark 主题 surface-selected 取\n #5c5c5e 凸起灰,同为实体档)。仅换底与投影,几何与过渡和 inset 变体同源。\n 注意源顺序:本规则与下方按压/拖拽规则特异性相同((0,3,0)),按压态必须排在更后面,\n 才能把实体底/柔投影覆盖成透明玻璃配方。 */\n:host([variant='raised']) .wui-segmented-indicator {\n background-color: var(--wui-color-surface-selected, #fff);\n box-shadow:\n 0 1px 3px rgb(0 0 0 / 0.12),\n 0 1px 2px rgb(0 0 0 / 0.08);\n}\n\n/* 静止态不绘制玻璃描边环:.wui-glass::before 是角点环 + 对角光泽层,纯灰底不需要它。\n 用 opacity 而不是 display:none,按压态才能让环随过渡淡入,而不是整层跳出。 */\n.wui-segmented-indicator::before {\n opacity: 0;\n transition: opacity var(--wui-duration-press, 80ms) ease;\n}\n\n/* 按压/拖拽时 indicator 转为玻璃态:静止态关掉的玻璃输出在这一态全部打开——backdrop blur、\n ::before 描边环、inset 高光,投影叠加更实一档并放大 1.5x。\n 底色全透明:track 实体化后 indicator 的采样面拿到的是均匀实色(条纹底实测内部 std 归零),\n 本体可感知性由实色 track 与 1.5x 放大/环/投影共同承担,不再需要 tint 打底,也不需要\n 为采样痕迹去压制 track 的边缘装饰——那些按压态特判已全部移除,track 装饰全状态恒定。\n transition 不含 background-color:按下即落到全透明,不产生灰→透明的插值中间帧;\n 松开回到静止态规则,由那里的 background-color 过渡把透明平滑淡回实体底。\n backdrop-filter 直接写死值(与 drawer/dialog/toast 的玻璃态同法):静止态是 none,\n glass 配方由这一态自己声明,不随 .wui-glass 类是否挂载而变;该属性同样不进 transition,\n 理由见静止态规则上方的说明。\n box-shadow 直接写死值(不经 --wui-internal-glass-shadow 中转):iOS Safari 对 var\n 离散切换驱动的 box-shadow 不触发过渡且可能整体不生效(slider thumb 同法),直接写值\n 后是普通属性变化,transition 正常插值;三条 inset 高光复制自 .wui-glass,避免整段\n 覆盖后按压态丢失玻璃描边。 */\n.wui-segmented.is-pressed .wui-segmented-indicator,\n.wui-segmented.is-dragging .wui-segmented-indicator {\n /* 环底色随按压/拖拽态转 transparent(理由见 menu-portal.css)。 */\n --wui-color-glass-ring: transparent;\n transform: scale(1.5);\n\n background-color: transparent;\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\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 transition:\n transform var(--wui-duration-press, 80ms) cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow var(--wui-duration-press, 80ms) ease;\n}\n\n.wui-segmented.is-pressed .wui-segmented-indicator::before,\n.wui-segmented.is-dragging .wui-segmented-indicator::before {\n opacity: 1;\n}\n\n.wui-segmented.is-dragging:not(.is-disabled) {\n --wui-internal-segmented-trigger-cursor: grabbing;\n cursor: grabbing;\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/segmented-trigger/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n --wui-segmented-trigger-bg-hover: var(\n --wui-color-state-layer-hover,\n color-mix(in srgb, var(--wui-color-text, #1b1b1b) 6%, transparent)\n );\n --wui-segmented-trigger-bg-active: var(\n --wui-color-state-layer-active,\n color-mix(in srgb, var(--wui-color-text, #1b1b1b) 15%, transparent)\n );\n touch-action: none;\n display: inline-block;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n.wui-segmented-trigger {\n cursor: var(--wui-internal-segmented-trigger-cursor, default);\n user-select: none;\n\n position: relative;\n z-index: 1;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n height: calc(var(--wui-control-size, 36px) - 8px);\n padding: 0 var(--wui-segmented-trigger-px, var(--wui-space-3, 12px));\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n font-size: var(--wui-font-size, 14px);\n line-height: 1;\n\n /* 选中与未选中同档灰:区分度只由玻璃指示器承担,未选中项无底色。 */\n color: var(--wui-color-text-secondary, #6a6a6a);\n white-space: nowrap;\n\n background-color: transparent;\n\n transition: color var(--wui-duration-focus, 200ms) ease;\n\n &:focus-visible {\n outline: var(--wui-focus-ring-width, 3px) solid\n var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n outline-offset: 2px;\n }\n}\n\n@media (hover: hover) and (pointer: fine) {\n /* state layer 只服务未选中项:选中项的按压/悬停反馈由玻璃指示器(thumb)承载,\n 已选中项再叠一层 6%/15% 灰底会盖在透明玻璃 thumb 之上(trigger z-index: 1 >\n thumb z-index: 0),按住已选项到拖拽结束前 :active 一直不撤,整段手势都是\n 「灰底蒙在玻璃上」——即 #150「灰底在 thumb 层」要消除的残留。 */\n .wui-segmented-trigger:hover:not(.is-checked, .is-disabled) {\n background-color: var(--wui-segmented-trigger-bg-hover);\n }\n}\n\n.wui-segmented-trigger:active:not(.is-checked, .is-disabled) {\n background-color: var(--wui-segmented-trigger-bg-active);\n}\n\n.wui-segmented-trigger.is-disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n:host([data-wui-pointer-focus]) .wui-segmented-trigger:focus-visible {\n outline-color: transparent;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/segmented-trigger/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n --wui-segmented-trigger-bg-hover: var(\n --wui-color-state-layer-hover,\n color-mix(in srgb, var(--wui-color-text, #1b1b1b) 6%, transparent)\n );\n --wui-segmented-trigger-bg-active: var(\n --wui-color-state-layer-active,\n color-mix(in srgb, var(--wui-color-text, #1b1b1b) 15%, transparent)\n );\n touch-action: none;\n display: inline-block;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n.wui-segmented-trigger {\n cursor: var(--wui-internal-segmented-trigger-cursor, default);\n user-select: none;\n\n position: relative;\n z-index: 1;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n height: calc(var(--wui-control-size, 36px) - 8px);\n padding: 0 var(--wui-segmented-trigger-px, var(--wui-space-3, 12px));\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n\n font-size: var(--wui-font-size, 14px);\n line-height: 1;\n\n /* 选中与未选中同档灰:区分度只由玻璃 indicator 承担,未选中项无底色。 */\n color: var(--wui-color-text-secondary, #6a6a6a);\n white-space: nowrap;\n\n background-color: transparent;\n\n transition: color var(--wui-duration-focus, 200ms) ease;\n\n &:focus-visible {\n outline: var(--wui-focus-ring-width, 3px) solid\n var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n outline-offset: 2px;\n }\n}\n\n@media (hover: hover) and (pointer: fine) {\n /* state layer 只服务未选中项:选中项的按压/悬停反馈由玻璃 indicator(indicator)承载,\n 已选中项再叠一层 6%/15% 灰底会盖在透明玻璃 indicator 之上(trigger z-index: 1 >\n indicator z-index: 0),按住已 option 到拖拽结束前 :active 一直不撤,整段手势都是\n 「灰底蒙在玻璃上」——即 #150「灰底在 indicator 层」要消除的残留。 */\n .wui-segmented-trigger:hover:not(.is-checked, .is-disabled) {\n background-color: var(--wui-segmented-trigger-bg-hover);\n }\n}\n\n.wui-segmented-trigger:active:not(.is-checked, .is-disabled) {\n background-color: var(--wui-segmented-trigger-bg-active);\n}\n\n.wui-segmented-trigger.is-disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n:host([data-wui-pointer-focus]) .wui-segmented-trigger:focus-visible {\n outline-color: transparent;\n}\n"],"mappings":""}
@@ -172,7 +172,7 @@ var k = class extends i(x) {
172
172
  }
173
173
  _syncValidity() {
174
174
  let e = this._formAssociation.getInternals();
175
- e && typeof e.setValidity == "function" && (this._isDisabled || !this.required || this.value ? e.setValidity({}) : e.setValidity({ valueMissing: !0 }, "请选择一项"));
175
+ e && typeof e.setValidity == "function" && (this._isDisabled || !this.required || this.value ? e.setValidity({}) : e.setValidity({ valueMissing: !0 }, "Select an option"));
176
176
  }
177
177
  _syncOpenAttribute() {
178
178
  this.toggleAttribute("open", this._isOpen);