@greypan/web-ui 9.0.0 → 10.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 (50) hide show
  1. package/README.CN.md +102 -31
  2. package/README.md +115 -42
  3. package/dist/assets/modal-surface.js +6 -0
  4. package/dist/assets/modal-surface.js.map +1 -0
  5. package/dist/components/context-menu/index.d.ts +15 -0
  6. package/dist/components/context-menu/index.js +50 -46
  7. package/dist/components/context-menu/index.js.map +1 -1
  8. package/dist/components/context-menu/scrim.js +1 -1
  9. package/dist/components/context-menu/scrim.js.map +1 -1
  10. package/dist/components/dialog/index.d.ts +30 -5
  11. package/dist/components/dialog/index.js +53 -33
  12. package/dist/components/dialog/index.js.map +1 -1
  13. package/dist/components/dialog/style.js +1 -1
  14. package/dist/components/dialog/style.js.map +1 -1
  15. package/dist/components/drawer/index.js +54 -49
  16. package/dist/components/drawer/index.js.map +1 -1
  17. package/dist/components/drawer/style.js +1 -1
  18. package/dist/components/drawer/style.js.map +1 -1
  19. package/dist/components/image-preview/index.js +66 -61
  20. package/dist/components/image-preview/index.js.map +1 -1
  21. package/dist/components/image-preview/style.js +1 -1
  22. package/dist/components/image-preview/style.js.map +1 -1
  23. package/dist/components/index.d.ts +1 -0
  24. package/dist/components/index.js +17 -16
  25. package/dist/components/input/style.js +1 -1
  26. package/dist/components/input/style.js.map +1 -1
  27. package/dist/components/input-number/style.js +1 -1
  28. package/dist/components/input-number/style.js.map +1 -1
  29. package/dist/components/layout/index.d.ts +22 -4
  30. package/dist/components/layout/index.js +46 -26
  31. package/dist/components/layout/index.js.map +1 -1
  32. package/dist/components/layout/style.js +1 -1
  33. package/dist/components/layout/style.js.map +1 -1
  34. package/dist/components/middle-ellipsis/index.d.ts +100 -0
  35. package/dist/components/middle-ellipsis/index.js +112 -0
  36. package/dist/components/middle-ellipsis/index.js.map +1 -0
  37. package/dist/components/middle-ellipsis/style.js +6 -0
  38. package/dist/components/middle-ellipsis/style.js.map +1 -0
  39. package/dist/components/middle-ellipsis/truncate.d.ts +62 -0
  40. package/dist/components/middle-ellipsis/truncate.js +45 -0
  41. package/dist/components/middle-ellipsis/truncate.js.map +1 -0
  42. package/dist/components/textarea/style.js +1 -1
  43. package/dist/components/textarea/style.js.map +1 -1
  44. package/dist/scrollbars/index.d.ts +33 -0
  45. package/dist/scrollbars/index.js +18 -0
  46. package/dist/scrollbars/index.js.map +1 -0
  47. package/dist/scrollbars/style.css +10 -0
  48. package/dist/shared/element-width/index.js +37 -0
  49. package/dist/shared/element-width/index.js.map +1 -0
  50. package/package.json +14 -2
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 单行「中间省略」的切分运算。
3
+ *
4
+ * 目标:把 `text` 拆成 `head + marker + tail`,两端各自在原串里连续、中间那段被丢掉,
5
+ * 且合串不超过可用行内空间。切点只落在**字素簇**边界上,代理对、组合符序列和 ZWJ emoji
6
+ * 都不会被从中间劈开——按 UTF-16 下标切一定会。
7
+ *
8
+ * 为什么这一层不能由 CSS 承担:`text-overflow` 渲染的是行盒里**一段连续**的内容,结构上
9
+ * 不可能同时显示首尾而丢掉中间。能靠 `direction` / 双值写法挪动的只是省略号落在行的哪一
10
+ * 端,而且 `direction: rtl` 那条路还会按双向算法重排内容(`2026-report.pdf` → `report.pdf-2026`)。
11
+ * 完整调研见 docs/research/web-ui-middle-ellipsis-261007.md。
12
+ *
13
+ * 宽度口径:两端额度按 `position` 从可用空间里分出来,各自用「前 i 簇 / 后 i 簇」的**实测**
14
+ * 宽度二分逼近。实测走调用方给的探针(Range 读取,不写 DOM),所以重算一次只有若干次读取,
15
+ * 没有回流——面板里同时存在成百上千个这样的单元格时,这个差别就是「一帧一次布局」与
16
+ * 「一个单元格一次布局」的差别。
17
+ *
18
+ * 分段宽度不蕴含合串宽度:`head|marker` 与 `marker|tail` 两个接缝的字距和连字是分段量看不见
19
+ * 的。这里不做合串实测,因为那要写一次 DOM 再读、把上面省下的回流又付回去;复核交给调用方
20
+ * 对着**渲染出来的真实盒子**做一次(见组件的 `updated()`),那才是「放得下」的直接证据。
21
+ */
22
+ /** 默认省略标记:U+2026 HORIZONTAL ELLIPSIS。 */
23
+ export declare const DEFAULT_MARKER = "\u2026";
24
+ /**
25
+ * 每个字素簇的 UTF-16 结束偏移(升序,不含 0),末项恒等于 `text.length`。
26
+ *
27
+ * `Intl.Segmenter` 的构造不便宜,单例复用。目标浏览器(见 package.json 的 browserslist)
28
+ * 与本地 node 测试环境都自带它,因此不写按码点切的退化分支——那会在部分环境里悄悄改变
29
+ * 切分口径,而这正是本模块唯一的正确性来源。
30
+ */
31
+ export declare function clusterEnds(text: string): number[];
32
+ /**
33
+ * 宽度探针。
34
+ *
35
+ * 三段都必须是**真实排版**下量到的宽度:字体回退、字距、连字、`letter-spacing`、
36
+ * `text-transform` 都会改变字形的推进量,任何独立的估算(例如用 canvas 的 `measureText`)
37
+ * 都要自己复刻这一整套解析,复刻不全就是静默的量错。
38
+ */
39
+ export interface WidthProbe {
40
+ /** 原文前 `units` 个 UTF-16 单元的渲染宽度。 */
41
+ prefix(units: number): number;
42
+ /** 原文后 `units` 个 UTF-16 单元的渲染宽度。 */
43
+ suffix(units: number): number;
44
+ /** 标记自身的渲染宽度。 */
45
+ marker(): number;
46
+ }
47
+ export interface MiddleTruncation {
48
+ /** 保留的原文前缀。 */
49
+ readonly head: string;
50
+ /** 保留的原文后缀。 */
51
+ readonly tail: string;
52
+ /** 是否真的丢掉了内容;`false` 时合串就是原文,标记不出现。 */
53
+ readonly truncated: boolean;
54
+ }
55
+ /**
56
+ * 在 `available` 内把 `text` 切成 `head + marker + tail`。
57
+ *
58
+ * `position` 与 CSSWG css-overflow-5 的 `text-overflow: ellipsis <length-percentage>` 同向:
59
+ * 量的是标记结束边距行末边的距离占可用行内空间的比例。`0` 把标记贴在行末(只剩头部,等价于
60
+ * 末尾省略),`100` 贴在行首(只剩尾部),`50` 居中;两端额度因此是 `(1 - p)` 与 `p`。
61
+ */
62
+ export declare function truncateMiddle(text: string, marker: string, position: number, available: number, probe: WidthProbe): MiddleTruncation;
@@ -0,0 +1,45 @@
1
+ //#region src/components/middle-ellipsis/truncate.ts
2
+ var e;
3
+ function t(t) {
4
+ if (t === "") return [];
5
+ e ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
6
+ let n = [];
7
+ for (let { index: r, segment: i } of e.segment(t)) n.push(r + i.length);
8
+ return n;
9
+ }
10
+ function n(e, t) {
11
+ let n = 0, r = e;
12
+ for (; n < r;) {
13
+ let e = n + r + 1 >> 1;
14
+ t(e) ? n = e : r = e - 1;
15
+ }
16
+ return n;
17
+ }
18
+ function r(e, r, i, a, o) {
19
+ if (e === "") return {
20
+ head: "",
21
+ tail: "",
22
+ truncated: !1
23
+ };
24
+ if (o.prefix(e.length) <= a) return {
25
+ head: e,
26
+ tail: "",
27
+ truncated: !1
28
+ };
29
+ let s = t(e), c = s.length, l = a - o.marker();
30
+ if (l <= 0) return {
31
+ head: "",
32
+ tail: "",
33
+ truncated: !0
34
+ };
35
+ let u = Math.min(1, Math.max(0, i / 100)), d = l * (1 - u), f = l * u, p = (e) => e === 0 ? 0 : s[e - 1] ?? 0, m = (t) => t === 0 ? e.length : s[c - t - 1] ?? 0, h = n(c, (e) => o.prefix(p(e)) <= d), g = n(c, (t) => o.suffix(e.length - m(t)) <= f);
36
+ return h + g > c && (h = Math.min(h, c), g = c - h), {
37
+ head: e.slice(0, p(h)),
38
+ tail: e.slice(m(g)),
39
+ truncated: !0
40
+ };
41
+ }
42
+ //#endregion
43
+ export { t as clusterEnds, r as truncateMiddle };
44
+
45
+ //# sourceMappingURL=truncate.js.map
@@ -0,0 +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,5 +1,5 @@
1
1
  //#region src/components/textarea/style.css?inline
2
- var e = "*,:before,:after{box-sizing:border-box}:host{--wui-textarea-clear-color:var(--wui-color-text-tertiary,color-mix(in srgb, var(--wui-color-text) 35%, transparent));display:block}:host([disabled]){cursor:not-allowed;opacity:.4}.wui-textarea-inner{--wui-glass-corner-radius:var(--wui-radius-menu,18px);gap:var(--wui-space-2,8px);box-sizing:border-box;width:var(--wui-textarea-width,200px);min-width:var(--wui-control-size,36px);min-height:var(--wui-control-size,36px);padding:7.5px var(--wui-space-3,12px);border-radius:var(--wui-radius-menu,18px);align-items:flex-start;display:flex;position:relative}:host([full]) .wui-textarea-inner{width:100%}:host([borderless]) .wui-textarea-inner{-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:none;background:0 0;border:none}:host([borderless]) .wui-textarea-inner:before{content:none}textarea{resize:vertical;box-sizing:border-box;width:100%;font:inherit;font-size:var(--wui-font-size,14px);line-height:var(--wui-line-height-normal,1.5);color:var(--wui-color-text,#1b1b1b);background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;display:block}textarea::placeholder{color:var(--wui-color-text-secondary,#6a6a6a)}textarea:disabled{cursor:not-allowed}textarea:read-only{cursor:default}:host([autosize]) textarea{resize:none}@media (pointer:coarse){textarea{font-size:max(16px, var(--wui-font-size,14px))}}.wui-textarea-inner:after{pointer-events:none;content:\"\";border-radius:inherit;box-shadow:inset 0 0 0 1px transparent, 0 0 0 var(--wui-focus-ring-width,3px) transparent;transition:box-shadow var(--wui-duration-focus,.2s);position:absolute;inset:0}:host([focused]) .wui-textarea-inner:after{box-shadow:inset 0 0 0 1px var(--wui-color-accent,#08f), 0 0 0 var(--wui-focus-ring-width,3px) var(--wui-color-focus-ring,color-mix(in srgb, var(--wui-color-accent,#08f) 40%, transparent))}slot{font-size:var(--wui-font-size,14px);color:var(--wui-color-text-secondary,#6a6a6a);flex-shrink:0;align-items:center;display:flex}slot.empty{display:none}.wui-textarea-inner web-ui-button[variant=ghost]{--wui-button-color:var(--wui-textarea-clear-color);margin-top:calc((var(--wui-font-size,14px) * 1.5 - 24px) / 2);margin-left:-8px;margin-right:-4px}";
2
+ var e = "*,:before,:after{box-sizing:border-box}:host{--wui-textarea-clear-color:var(--wui-color-text-tertiary,color-mix(in srgb, var(--wui-color-text) 35%, transparent));display:block}:host([disabled]){cursor:not-allowed;opacity:.4}.wui-textarea-inner{--wui-glass-corner-radius:var(--wui-radius-menu,18px);gap:var(--wui-space-2,8px);box-sizing:border-box;width:var(--wui-textarea-width,200px);min-width:var(--wui-control-size,36px);min-height:var(--wui-control-size,36px);padding:7.5px var(--wui-space-3,12px);border-radius:var(--wui-radius-menu,18px);align-items:flex-start;display:flex;position:relative}:host([full]) .wui-textarea-inner{width:100%}:host([borderless]) .wui-textarea-inner{-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:none;background:0 0;border:none}:host([borderless]) .wui-textarea-inner:before{content:none}textarea{resize:vertical;box-sizing:border-box;width:100%;font:inherit;font-size:var(--wui-font-size,14px);line-height:var(--wui-line-height-normal,1.5);color:var(--wui-color-text,#1b1b1b);background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;display:block}textarea::placeholder{font:inherit;color:var(--wui-color-text-secondary,#6a6a6a)}textarea:disabled{cursor:not-allowed}textarea:read-only{cursor:default}:host([autosize]) textarea{resize:none}@media (pointer:coarse){textarea{font-size:max(16px, var(--wui-font-size,14px))}}.wui-textarea-inner:after{pointer-events:none;content:\"\";border-radius:inherit;box-shadow:inset 0 0 0 1px transparent, 0 0 0 var(--wui-focus-ring-width,3px) transparent;transition:box-shadow var(--wui-duration-focus,.2s);position:absolute;inset:0}:host([focused]) .wui-textarea-inner:after{box-shadow:inset 0 0 0 1px var(--wui-color-accent,#08f), 0 0 0 var(--wui-focus-ring-width,3px) var(--wui-color-focus-ring,color-mix(in srgb, var(--wui-color-accent,#08f) 40%, transparent))}slot{font-size:var(--wui-font-size,14px);color:var(--wui-color-text-secondary,#6a6a6a);flex-shrink:0;align-items:center;display:flex}slot.empty{display:none}.wui-textarea-inner web-ui-button[variant=ghost]{--wui-button-color:var(--wui-textarea-clear-color);margin-top:calc((var(--wui-font-size,14px) * 1.5 - 24px) / 2);margin-left:-8px;margin-right:-4px}";
3
3
  //#endregion
4
4
  export { e as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/textarea/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n --wui-textarea-clear-color: var(\n --wui-color-text-tertiary,\n color-mix(in srgb, var(--wui-color-text) 35%, transparent)\n );\n display: block;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.wui-textarea-inner {\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n position: relative;\n\n display: flex;\n gap: var(--wui-space-2, 8px);\n align-items: flex-start;\n\n box-sizing: border-box;\n width: var(--wui-textarea-width, 200px);\n min-width: var(--wui-control-size, 36px);\n min-height: var(--wui-control-size, 36px);\n padding: 7.5px var(--wui-space-3, 12px);\n border-radius: var(--wui-radius-menu, 18px);\n}\n\n:host([full]) .wui-textarea-inner {\n width: 100%;\n}\n\n:host([borderless]) .wui-textarea-inner {\n border: none;\n background: transparent;\n backdrop-filter: none;\n box-shadow: none;\n}\n\n/* borderless 必须移除 glass 描边环(.wui-glass::before 底环 + 对角光泽),\n 否则无框后仍残留 1px 描边 */\n:host([borderless]) .wui-textarea-inner::before {\n content: none;\n}\n\ntextarea {\n resize: vertical;\n\n display: block;\n flex: 1;\n\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n padding: 0;\n border: none;\n\n /* 表单控件不继承字体;显式继承以对齐宿主排版,避免 UA 默认字体差异 */\n font: inherit;\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 background: transparent;\n outline: none;\n\n &::placeholder {\n color: var(--wui-color-text-secondary, #6a6a6a);\n }\n\n &:disabled {\n cursor: not-allowed;\n }\n\n &:read-only {\n cursor: default;\n }\n}\n\n/*\n * autosize 由组件用 JS 独占高度(_autosize 写内联 height),原生 resize 拖拽与它是\n * 两套高度来源:用户拖高后,下一次内容变化又会被 autosize 拉回去,拖拽变成假动作。\n * 所以 autosize 下关掉 resize grip,需要上限高度请用 max-height(CSS 钳制)。\n */\n:host([autosize]) textarea {\n resize: none;\n}\n\n/* Coarse pointers include iOS Safari, where a sub-16px font triggers focus zoom. */\n@media (pointer: coarse) {\n textarea {\n font-size: max(16px, var(--wui-font-size, 14px));\n }\n}\n\n/* Focus ring 独立成 ::after 层:base/focused 都是显式 box-shadow,保证所有变体都能过渡 */\n.wui-textarea-inner::after {\n pointer-events: none;\n content: '';\n\n position: absolute;\n inset: 0;\n\n border-radius: inherit;\n\n box-shadow:\n inset 0 0 0 1px transparent,\n 0 0 0 var(--wui-focus-ring-width, 3px) transparent;\n\n transition: box-shadow var(--wui-duration-focus, 200ms);\n}\n\n:host([focused]) .wui-textarea-inner::after {\n box-shadow:\n inset 0 0 0 1px var(--wui-color-accent, #08f),\n 0 0 0 var(--wui-focus-ring-width, 3px)\n var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n}\n\nslot {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n\n font-size: var(--wui-font-size, 14px);\n color: var(--wui-color-text-secondary, #6a6a6a);\n}\n\nslot.empty {\n display: none;\n}\n\n/* 复用 web-ui-button icon ghost:仅覆盖颜色为 faint 并对齐首行,保持弱意图清除图标观感;\n 负 margin-left 抵消容器 flex gap,让清除按钮紧贴文本域内容\n 负 margin-right 抵消 button padding,让清除按钮右侧 spacing 视觉上协调 */\n.wui-textarea-inner web-ui-button[variant='ghost'] {\n --wui-button-color: var(--wui-textarea-clear-color);\n\n /* 与首行文字垂直居中对齐:容器 align-items: flex-start 使按钮贴内容区顶部,\n 首行中心 = padding-top + 行高/2;24px 按钮需相对内容区上移 (行高 − 按钮高度) / 2 */\n margin-top: calc((var(--wui-font-size, 14px) * 1.5 - 24px) / 2);\n margin-right: -4px;\n margin-left: -8px;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/textarea/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n --wui-textarea-clear-color: var(\n --wui-color-text-tertiary,\n color-mix(in srgb, var(--wui-color-text) 35%, transparent)\n );\n display: block;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.wui-textarea-inner {\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n position: relative;\n\n display: flex;\n gap: var(--wui-space-2, 8px);\n align-items: flex-start;\n\n box-sizing: border-box;\n width: var(--wui-textarea-width, 200px);\n min-width: var(--wui-control-size, 36px);\n min-height: var(--wui-control-size, 36px);\n padding: 7.5px var(--wui-space-3, 12px);\n border-radius: var(--wui-radius-menu, 18px);\n}\n\n:host([full]) .wui-textarea-inner {\n width: 100%;\n}\n\n:host([borderless]) .wui-textarea-inner {\n border: none;\n background: transparent;\n backdrop-filter: none;\n box-shadow: none;\n}\n\n/* borderless 必须移除 glass 描边环(.wui-glass::before 底环 + 对角光泽),\n 否则无框后仍残留 1px 描边 */\n:host([borderless]) .wui-textarea-inner::before {\n content: none;\n}\n\ntextarea {\n resize: vertical;\n\n display: block;\n flex: 1;\n\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n padding: 0;\n border: none;\n\n /* 表单控件不继承字体;显式继承以对齐宿主排版,避免 UA 默认字体差异 */\n font: inherit;\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 background: transparent;\n outline: none;\n\n &::placeholder {\n /* 同 input:占位文本的排版属于控件,不在这里另写一份 */\n font: inherit;\n color: var(--wui-color-text-secondary, #6a6a6a);\n }\n\n &:disabled {\n cursor: not-allowed;\n }\n\n &:read-only {\n cursor: default;\n }\n}\n\n/*\n * autosize 由组件用 JS 独占高度(_autosize 写内联 height),原生 resize 拖拽与它是\n * 两套高度来源:用户拖高后,下一次内容变化又会被 autosize 拉回去,拖拽变成假动作。\n * 所以 autosize 下关掉 resize grip,需要上限高度请用 max-height(CSS 钳制)。\n */\n:host([autosize]) textarea {\n resize: none;\n}\n\n/* Coarse pointers include iOS Safari, where a sub-16px font triggers focus zoom. */\n@media (pointer: coarse) {\n textarea {\n font-size: max(16px, var(--wui-font-size, 14px));\n }\n}\n\n/* Focus ring 独立成 ::after 层:base/focused 都是显式 box-shadow,保证所有变体都能过渡 */\n.wui-textarea-inner::after {\n pointer-events: none;\n content: '';\n\n position: absolute;\n inset: 0;\n\n border-radius: inherit;\n\n box-shadow:\n inset 0 0 0 1px transparent,\n 0 0 0 var(--wui-focus-ring-width, 3px) transparent;\n\n transition: box-shadow var(--wui-duration-focus, 200ms);\n}\n\n:host([focused]) .wui-textarea-inner::after {\n box-shadow:\n inset 0 0 0 1px var(--wui-color-accent, #08f),\n 0 0 0 var(--wui-focus-ring-width, 3px)\n var(--wui-color-focus-ring, color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent));\n}\n\nslot {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n\n font-size: var(--wui-font-size, 14px);\n color: var(--wui-color-text-secondary, #6a6a6a);\n}\n\nslot.empty {\n display: none;\n}\n\n/* 复用 web-ui-button icon ghost:仅覆盖颜色为 faint 并对齐首行,保持弱意图清除图标观感;\n 负 margin-left 抵消容器 flex gap,让清除按钮紧贴文本域内容\n 负 margin-right 抵消 button padding,让清除按钮右侧 spacing 视觉上协调 */\n.wui-textarea-inner web-ui-button[variant='ghost'] {\n --wui-button-color: var(--wui-textarea-clear-color);\n\n /* 与首行文字垂直居中对齐:容器 align-items: flex-start 使按钮贴内容区顶部,\n 首行中心 = padding-top + 行高/2;24px 按钮需相对内容区上移 (行高 − 按钮高度) / 2 */\n margin-top: calc((var(--wui-font-size, 14px) * 1.5 - 24px) / 2);\n margin-right: -4px;\n margin-left: -8px;\n}\n"],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import { PartialOptions } from 'overlayscrollbars';
2
+ /**
3
+ * `scrollbars.theme` 的取值,同时也是 `@greypan/web-ui/scrollbars.css` 里主题类的名字。
4
+ */
5
+ export declare const WEB_UI_SCROLLBARS_THEME = "os-theme-macos";
6
+ /**
7
+ * 全仓统一的滚动条行为:外观对齐 macOS、自动隐藏、滚动时短暂显示、track 点击直达。
8
+ *
9
+ * 内容尺寸或平台变化都不需要改这里,只有三条产品要求本身变了才改。覆盖层要实现的是
10
+ * 同一套手势语言,所以用法是把它整个传给 OverlayScrollbars 初始化,而不是逐项拼接:
11
+ *
12
+ * ```ts
13
+ * import { webUiScrollbarsOptions } from '@greypan/web-ui/scrollbars'
14
+ * import '@greypan/web-ui/scrollbars.css'
15
+ * ```
16
+ *
17
+ * - `autoHide: 'scroll'`:滚动条只在滚动时出现,停下 `autoHideDelay` 之后自动隐去。用户明确选了
18
+ * 这一档而不是 `'move'`——`'move'` 会让指针在滚动区域内移动就唤出,比 macOS 原生积极得多。
19
+ * 代价写清楚:**hover 本身不再唤出滚动条**(隐藏态的元素不可命中,指针根本落不到它上面),
20
+ * 所以 issue 里「hover 的时候短暂显示」那条只以「滚动时短暂显示」的形式满足。
21
+ * - `autoHideDelay: 2000`:这一档同时是「track 点击跳转」的可用窗口——滚动条隐去后它就不可命中。
22
+ * 指针从滚动的位置移到轨道、再点下去,实测预算在 1s 上下,默认的 1300ms 太贴边;2000ms 留出
23
+ * 余量,又不至于让半透明滚动条在静止画面上久留(macOS 原生的淡出也在 2s 这个量级)。
24
+ * - `autoHideSuspend` 保持关闭:打开会让滚动条在首次滚动前一直可见,与「自动隐藏」相反。
25
+ * - `clickScroll` 走函数形态:`true` 只是「按一个视口步进」,跳到的是当前位置的下/上一屏,
26
+ * 与点击处的距离无关。`clickScrollDistance: 0` 才是「以点击处为目标」——库把 0 定义为
27
+ * 「距离等于目标距离」。函数形态是必需的:这个选项只在实现里经过 `isFunction` 分支才会被读到,
28
+ * 直接传对象会被静默忽略。
29
+ * - `clickScrollDuration: 0`:库默认用 200ms 缓动滚到点击处,在渲染虚拟列表的资源列表上会卡顿,
30
+ * 这里改成即时落位。0 是合法时长而不是「非法值被忽略」:`animateNumber` 先用 `Math.max(0, ms)`
31
+ * 得到时长 0,首帧(同步执行、不经 rAF)就判定已到期并落到终点。
32
+ */
33
+ export declare const webUiScrollbarsOptions: PartialOptions;
@@ -0,0 +1,18 @@
1
+ /* empty css */
2
+ import { ClickScrollPlugin as e, OverlayScrollbars as t } from "overlayscrollbars";
3
+ //#region src/scrollbars/index.ts
4
+ var n = "os-theme-macos";
5
+ t.plugin(e);
6
+ var r = { scrollbars: {
7
+ theme: n,
8
+ autoHide: "scroll",
9
+ autoHideDelay: 2e3,
10
+ clickScroll: () => ({
11
+ clickScrollDistance: 0,
12
+ clickScrollDuration: 0
13
+ })
14
+ } };
15
+ //#endregion
16
+ export { n as WEB_UI_SCROLLBARS_THEME, r as webUiScrollbarsOptions };
17
+
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/scrollbars/index.ts"],"sourcesContent":["import { ClickScrollPlugin, OverlayScrollbars, type PartialOptions } from 'overlayscrollbars'\n\nimport './style.css'\n\n/**\n * `scrollbars.theme` 的取值,同时也是 `@greypan/web-ui/scrollbars.css` 里主题类的名字。\n */\nexport const WEB_UI_SCROLLBARS_THEME = 'os-theme-macos'\n\n/*\n * track 点击滚动由 ClickScrollPlugin 提供;插件未注册时 `clickScroll` 静默失效——\n * 不报错、不打日志,轨道只是没有反应。注册写在模块作用域,只要消费方用了本模块,\n * 插件就一定在位,不会因为漏掉一行 bootstrap 而退化。\n */\nOverlayScrollbars.plugin(ClickScrollPlugin)\n\n/**\n * 全仓统一的滚动条行为:外观对齐 macOS、自动隐藏、滚动时短暂显示、track 点击直达。\n *\n * 内容尺寸或平台变化都不需要改这里,只有三条产品要求本身变了才改。覆盖层要实现的是\n * 同一套手势语言,所以用法是把它整个传给 OverlayScrollbars 初始化,而不是逐项拼接:\n *\n * ```ts\n * import { webUiScrollbarsOptions } from '@greypan/web-ui/scrollbars'\n * import '@greypan/web-ui/scrollbars.css'\n * ```\n *\n * - `autoHide: 'scroll'`:滚动条只在滚动时出现,停下 `autoHideDelay` 之后自动隐去。用户明确选了\n * 这一档而不是 `'move'`——`'move'` 会让指针在滚动区域内移动就唤出,比 macOS 原生积极得多。\n * 代价写清楚:**hover 本身不再唤出滚动条**(隐藏态的元素不可命中,指针根本落不到它上面),\n * 所以 issue 里「hover 的时候短暂显示」那条只以「滚动时短暂显示」的形式满足。\n * - `autoHideDelay: 2000`:这一档同时是「track 点击跳转」的可用窗口——滚动条隐去后它就不可命中。\n * 指针从滚动的位置移到轨道、再点下去,实测预算在 1s 上下,默认的 1300ms 太贴边;2000ms 留出\n * 余量,又不至于让半透明滚动条在静止画面上久留(macOS 原生的淡出也在 2s 这个量级)。\n * - `autoHideSuspend` 保持关闭:打开会让滚动条在首次滚动前一直可见,与「自动隐藏」相反。\n * - `clickScroll` 走函数形态:`true` 只是「按一个视口步进」,跳到的是当前位置的下/上一屏,\n * 与点击处的距离无关。`clickScrollDistance: 0` 才是「以点击处为目标」——库把 0 定义为\n * 「距离等于目标距离」。函数形态是必需的:这个选项只在实现里经过 `isFunction` 分支才会被读到,\n * 直接传对象会被静默忽略。\n * - `clickScrollDuration: 0`:库默认用 200ms 缓动滚到点击处,在渲染虚拟列表的资源列表上会卡顿,\n * 这里改成即时落位。0 是合法时长而不是「非法值被忽略」:`animateNumber` 先用 `Math.max(0, ms)`\n * 得到时长 0,首帧(同步执行、不经 rAF)就判定已到期并落到终点。\n */\nexport const webUiScrollbarsOptions: PartialOptions = {\n scrollbars: {\n theme: WEB_UI_SCROLLBARS_THEME,\n autoHide: 'scroll',\n autoHideDelay: 2000,\n clickScroll: () => ({ clickScrollDistance: 0, clickScrollDuration: 0 })\n }\n}\n"],"mappings":";;;AAOA,IAAa,IAA0B;AAOvC,EAAkB,OAAO,CAAiB;AA6B1C,IAAa,IAAyC,EACpD,YAAY;CACV,OAAO;CACP,UAAU;CACV,eAAe;CACf,oBAAoB;EAAE,qBAAqB;EAAG,qBAAqB;CAAE;AACvE,EACF"}
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * OverlayScrollbars
3
+ * Version: 2.16.0
4
+ *
5
+ * Copyright (c) Rene Haas | KingSora.
6
+ * https://github.com/KingSora
7
+ *
8
+ * Released under the MIT license.
9
+ */
10
+ .os-size-observer,.os-size-observer-listener{direction:inherit;pointer-events:none;visibility:hidden;box-sizing:border-box;overflow:hidden;scroll-behavior:auto!important}.os-size-observer,.os-size-observer-listener,.os-size-observer-listener-item,.os-size-observer-listener-item-final{writing-mode:horizontal-tb;position:absolute;top:0;left:0}.os-size-observer{z-index:-1;contain:strict;padding:inherit;border:inherit;box-sizing:inherit;flex-flow:row;margin:-133px;display:flex;inset:0;transform:scale(.1)}.os-size-observer:before{content:"";box-sizing:inherit;flex:none;width:10px;height:10px;padding:10px}.os-size-observer-appear{animation:1ms forwards os-size-observer-appear-animation}.os-size-observer-listener{box-sizing:border-box;padding:inherit;border:inherit;flex:auto;margin:-133px;position:relative;transform:scale(10)}.os-size-observer-listener.ltr{margin-left:0;margin-right:-266px}.os-size-observer-listener.rtl{margin-left:-266px;margin-right:0}.os-size-observer-listener:empty:before{content:"";width:100%;height:100%}.os-size-observer-listener:empty:before,.os-size-observer-listener>.os-size-observer-listener-item{padding:inherit;border:inherit;box-sizing:content-box;flex:auto;display:block;position:relative}.os-size-observer-listener-scroll{box-sizing:border-box;display:flex}.os-size-observer-listener-item{direction:ltr;flex:none;bottom:0;right:0;overflow:hidden}.os-size-observer-listener-item-final{transition:none}@keyframes os-size-observer-appear-animation{0%{cursor:auto}to{cursor:none}}.os-trinsic-observer{box-sizing:border-box;z-index:-1;contain:strict;border:none;flex:none;max-width:0;height:0;max-height:1px;margin:0;padding:0;position:relative;top:calc(100% + 1px);overflow:hidden}.os-trinsic-observer:not(:empty){height:calc(100% + 1px);top:-1px}.os-trinsic-observer:not(:empty)>.os-size-observer{width:1000%;min-width:1px;height:1000%;min-height:1px}html[data-overlayscrollbars-body]{overflow:hidden}html[data-overlayscrollbars-body],html[data-overlayscrollbars-body]>body{width:100%;height:100%;margin:0}html[data-overlayscrollbars-body]>body{margin:0;overflow:visible}[data-overlayscrollbars]{position:relative}[data-overlayscrollbars~=host],[data-overlayscrollbars-padding]{display:flex;scroll-behavior:auto!important;flex-flow:row!important;align-items:stretch!important}[data-overlayscrollbars-padding],[data-overlayscrollbars-viewport]:not([data-overlayscrollbars]){box-sizing:inherit;z-index:0;border:none;flex:auto;width:100%;min-width:0;height:auto;margin:0;padding:0;position:relative}[data-overlayscrollbars-viewport]:not([data-overlayscrollbars]){--os-vaw:0;--os-vah:0;outline:none}[data-overlayscrollbars-viewport]:not([data-overlayscrollbars]):focus{outline:none}[data-overlayscrollbars-viewport][data-overlayscrollbars-viewport~=arrange]:before{content:"";pointer-events:none;z-index:-1;min-width:1px;min-height:1px;width:var(--os-vaw);height:var(--os-vah);position:absolute}[data-overlayscrollbars~=host],[data-overlayscrollbars-padding]{overflow:hidden!important}[data-overlayscrollbars~=host][data-overlayscrollbars~=noClipping],[data-overlayscrollbars-padding~=noClipping]{overflow:visible!important}[data-overlayscrollbars-viewport]{--os-viewport-overflow-x:hidden;--os-viewport-overflow-y:hidden;overflow-x:var(--os-viewport-overflow-x);overflow-y:var(--os-viewport-overflow-y)}[data-overlayscrollbars-viewport~=overflowXVisible]{--os-viewport-overflow-x:visible}[data-overlayscrollbars-viewport~=overflowXHidden]{--os-viewport-overflow-x:hidden}[data-overlayscrollbars-viewport~=overflowXScroll]{--os-viewport-overflow-x:scroll}[data-overlayscrollbars-viewport~=overflowYVisible]{--os-viewport-overflow-y:visible}[data-overlayscrollbars-viewport~=overflowYHidden]{--os-viewport-overflow-y:hidden}[data-overlayscrollbars-viewport~=overflowYScroll]{--os-viewport-overflow-y:scroll}[data-overlayscrollbars-viewport~=overflowImportant]{overflow-x:var(--os-viewport-overflow-x)!important;overflow-y:var(--os-viewport-overflow-y)!important}[data-overlayscrollbars-viewport~=noContent]:not(#osFakeId){font-size:0!important;line-height:0!important}[data-overlayscrollbars-viewport~=noContent]:not(#osFakeId):before,[data-overlayscrollbars-viewport~=noContent]:not(#osFakeId):after,[data-overlayscrollbars-viewport~=noContent]:not(#osFakeId)>:not(#osFakeId){clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border-width:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;display:none!important;position:absolute!important;overflow:hidden!important}[data-overlayscrollbars-viewport~=measuring],[data-overlayscrollbars-viewport~=scrolling]{scroll-behavior:auto!important;scroll-snap-type:none!important}[data-overlayscrollbars-viewport~=measuring][data-overlayscrollbars-viewport~=overflowXVisible]{overflow-x:hidden!important}[data-overlayscrollbars-viewport~=measuring][data-overlayscrollbars-viewport~=overflowYVisible]{overflow-y:hidden!important}[data-overlayscrollbars-content]{box-sizing:inherit}[data-overlayscrollbars-contents]:not(#osFakeId):not([data-overlayscrollbars-padding]):not([data-overlayscrollbars-viewport]):not([data-overlayscrollbars-content]){display:contents}[data-overlayscrollbars-grid],[data-overlayscrollbars-grid] [data-overlayscrollbars-padding]{grid-template:1fr/1fr;display:grid}[data-overlayscrollbars-grid]>[data-overlayscrollbars-padding],[data-overlayscrollbars-grid]>[data-overlayscrollbars-viewport],[data-overlayscrollbars-grid]>[data-overlayscrollbars-padding]>[data-overlayscrollbars-viewport]{width:auto!important;height:auto!important}@property --os-scroll-percent{syntax:"<number>";inherits:true;initial-value:0}@property --os-viewport-percent{syntax:"<number>";inherits:true;initial-value:0}.os-scrollbar{--os-viewport-percent:0;--os-scroll-percent:0;--os-scroll-direction:0;--os-scroll-percent-directional:calc(var(--os-scroll-percent) - (var(--os-scroll-percent) + (1 - var(--os-scroll-percent)) * -1) * var(--os-scroll-direction));contain:size layout;contain:size layout style;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .15s,visibility .15s,top .15s,right .15s,bottom .15s,left .15s;position:absolute}body>.os-scrollbar{z-index:99999;position:fixed}.os-scrollbar-transitionless{transition:none!important}.os-scrollbar-track{position:relative;border:none!important;padding:0!important}.os-scrollbar-handle{position:absolute}.os-scrollbar-track,.os-scrollbar-handle{pointer-events:none;width:100%;height:100%}.os-scrollbar.os-scrollbar-track-interactive .os-scrollbar-track,.os-scrollbar.os-scrollbar-handle-interactive .os-scrollbar-handle{pointer-events:auto;touch-action:none}.os-scrollbar-horizontal{bottom:0;left:0}.os-scrollbar-vertical{top:0;right:0}.os-scrollbar-rtl.os-scrollbar-horizontal{right:0}.os-scrollbar-rtl.os-scrollbar-vertical{left:0;right:auto}.os-scrollbar-visible{opacity:1;visibility:visible}.os-scrollbar-auto-hide.os-scrollbar-auto-hide-hidden{opacity:0;visibility:hidden}.os-scrollbar-interaction.os-scrollbar-visible{opacity:1;visibility:visible}.os-scrollbar-unusable,.os-scrollbar-unusable *,.os-scrollbar-wheel,.os-scrollbar-wheel *{pointer-events:none!important}.os-scrollbar-unusable .os-scrollbar-handle{opacity:0!important;transition:none!important}.os-scrollbar-horizontal .os-scrollbar-handle{bottom:0;left:calc(var(--os-scroll-percent-directional) * 100%);transform:translateX(calc(var(--os-scroll-percent-directional) * -100%));width:calc(var(--os-viewport-percent) * 100%)}.os-scrollbar-vertical .os-scrollbar-handle{right:0;top:calc(var(--os-scroll-percent-directional) * 100%);transform:translateY(calc(var(--os-scroll-percent-directional) * -100%));height:calc(var(--os-viewport-percent) * 100%)}@supports (container-type:size){.os-scrollbar-track{container-type:size}.os-scrollbar-horizontal .os-scrollbar-handle{transform:translateX(calc(var(--os-scroll-percent-directional) * 100cqw + var(--os-scroll-percent-directional) * -100%));left:auto}.os-scrollbar-vertical .os-scrollbar-handle{transform:translateY(calc(var(--os-scroll-percent-directional) * 100cqh + var(--os-scroll-percent-directional) * -100%));top:auto}.os-scrollbar-rtl.os-scrollbar-horizontal .os-scrollbar-handle{left:0;right:auto}}.os-scrollbar-rtl.os-scrollbar-vertical .os-scrollbar-handle{left:0;right:auto}.os-scrollbar.os-scrollbar-horizontal.os-scrollbar-cornerless,.os-scrollbar.os-scrollbar-horizontal.os-scrollbar-cornerless.os-scrollbar-rtl{left:0;right:0}.os-scrollbar.os-scrollbar-vertical.os-scrollbar-cornerless,.os-scrollbar.os-scrollbar-vertical.os-scrollbar-cornerless.os-scrollbar-rtl{top:0;bottom:0}@media print{.os-scrollbar{display:none}}.os-scrollbar{--os-size:0;--os-padding-perpendicular:0;--os-padding-axis:0;--os-track-border-radius:0;--os-track-bg:none;--os-track-bg-hover:none;--os-track-bg-active:none;--os-track-border:none;--os-track-border-hover:none;--os-track-border-active:none;--os-handle-border-radius:0;--os-handle-bg:none;--os-handle-bg-hover:none;--os-handle-bg-active:none;--os-handle-border:none;--os-handle-border-hover:none;--os-handle-border-active:none;--os-handle-min-size:33px;--os-handle-max-size:none;--os-handle-perpendicular-size:100%;--os-handle-perpendicular-size-hover:100%;--os-handle-perpendicular-size-active:100%;--os-handle-interactive-area-offset:0}.os-scrollbar-track{border:var(--os-track-border);border-radius:var(--os-track-border-radius);background:var(--os-track-bg);transition:opacity .15s,background-color .15s,border-color .15s}.os-scrollbar-track:hover{border:var(--os-track-border-hover);background:var(--os-track-bg-hover)}.os-scrollbar-track:active{border:var(--os-track-border-active);background:var(--os-track-bg-active)}.os-scrollbar-handle{border:var(--os-handle-border);border-radius:var(--os-handle-border-radius);background:var(--os-handle-bg)}.os-scrollbar-handle:hover{border:var(--os-handle-border-hover);background:var(--os-handle-bg-hover)}.os-scrollbar-handle:active{border:var(--os-handle-border-active);background:var(--os-handle-bg-active)}.os-scrollbar-track:before,.os-scrollbar-handle:before{content:"";display:block;position:absolute;inset:0}.os-scrollbar-horizontal{padding:var(--os-padding-perpendicular) var(--os-padding-axis);right:var(--os-size);height:var(--os-size)}.os-scrollbar-horizontal.os-scrollbar-rtl{left:var(--os-size);right:0}.os-scrollbar-horizontal .os-scrollbar-track:before{top:calc(var(--os-padding-perpendicular) * -1);bottom:calc(var(--os-padding-perpendicular) * -1)}.os-scrollbar-horizontal .os-scrollbar-handle{min-width:var(--os-handle-min-size);max-width:var(--os-handle-max-size);height:var(--os-handle-perpendicular-size);transition:opacity .15s,background-color .15s,border-color .15s,height .15s}.os-scrollbar-horizontal .os-scrollbar-handle:before{top:calc((var(--os-padding-perpendicular) + var(--os-handle-interactive-area-offset)) * -1);bottom:calc(var(--os-padding-perpendicular) * -1)}.os-scrollbar-horizontal:hover .os-scrollbar-handle{height:var(--os-handle-perpendicular-size-hover)}.os-scrollbar-horizontal:active .os-scrollbar-handle{height:var(--os-handle-perpendicular-size-active)}.os-scrollbar-vertical{padding:var(--os-padding-axis) var(--os-padding-perpendicular);bottom:var(--os-size);width:var(--os-size)}.os-scrollbar-vertical .os-scrollbar-track:before{left:calc(var(--os-padding-perpendicular) * -1);right:calc(var(--os-padding-perpendicular) * -1)}.os-scrollbar-vertical .os-scrollbar-handle{min-height:var(--os-handle-min-size);max-height:var(--os-handle-max-size);width:var(--os-handle-perpendicular-size);transition:opacity .15s,background-color .15s,border-color .15s,width .15s}.os-scrollbar-vertical .os-scrollbar-handle:before{left:calc((var(--os-padding-perpendicular) + var(--os-handle-interactive-area-offset)) * -1);right:calc(var(--os-padding-perpendicular) * -1)}.os-scrollbar-vertical.os-scrollbar-rtl .os-scrollbar-handle:before{right:calc((var(--os-padding-perpendicular) + var(--os-handle-interactive-area-offset)) * -1);left:calc(var(--os-padding-perpendicular) * -1)}.os-scrollbar-vertical:hover .os-scrollbar-handle{width:var(--os-handle-perpendicular-size-hover)}.os-scrollbar-vertical:active .os-scrollbar-handle{width:var(--os-handle-perpendicular-size-active)}[data-overlayscrollbars-viewport~=measuring]>.os-scrollbar,.os-theme-none.os-scrollbar{display:none!important}.os-theme-dark,.os-theme-light{box-sizing:border-box;--os-size:10px;--os-padding-perpendicular:2px;--os-padding-axis:2px;--os-track-border-radius:10px;--os-handle-interactive-area-offset:4px;--os-handle-border-radius:10px}.os-theme-dark{--os-handle-bg:#00000070;--os-handle-bg-hover:#0000008c;--os-handle-bg-active:#000000a8}.os-theme-light{--os-handle-bg:#ffffff70;--os-handle-bg-hover:#ffffff8c;--os-handle-bg-active:#ffffffa8}[data-overlayscrollbars-initialize]:not([data-overlayscrollbars-viewport]),[data-overlayscrollbars-viewport~=scrollbarHidden],html[data-overlayscrollbars-viewport~=scrollbarHidden]>body{scrollbar-width:none!important}[data-overlayscrollbars-initialize]:not([data-overlayscrollbars-viewport])::-webkit-scrollbar{appearance:none!important;width:0!important;height:0!important;display:none!important}[data-overlayscrollbars-initialize]:not([data-overlayscrollbars-viewport])::-webkit-scrollbar-corner{appearance:none!important;width:0!important;height:0!important;display:none!important}[data-overlayscrollbars-viewport~=scrollbarHidden]::-webkit-scrollbar{appearance:none!important;width:0!important;height:0!important;display:none!important}[data-overlayscrollbars-viewport~=scrollbarHidden]::-webkit-scrollbar-corner{appearance:none!important;width:0!important;height:0!important;display:none!important}html[data-overlayscrollbars-viewport~=scrollbarHidden]>body::-webkit-scrollbar{appearance:none!important;width:0!important;height:0!important;display:none!important}html[data-overlayscrollbars-viewport~=scrollbarHidden]>body::-webkit-scrollbar-corner{appearance:none!important;width:0!important;height:0!important;display:none!important}[data-overlayscrollbars-initialize]:not([data-overlayscrollbars]):not(html):not(body){overflow:auto}.os-theme-macos{--os-size:13px;--os-padding-perpendicular:3px;--os-padding-axis:2px;--os-track-border-radius:100px;--os-handle-border-radius:100px;--os-handle-interactive-area-offset:4px;--os-track-bg:none;--os-track-bg-hover:none;--os-track-bg-active:none;--os-handle-bg:var(--wui-scrollbar-handle,#00000080);--os-handle-bg-hover:var(--wui-scrollbar-handle-hover,#0000009e);--os-handle-bg-active:var(--wui-scrollbar-handle-active,#000000bd);box-sizing:border-box}web-ui-theme[resolved-appearance=light]{--wui-scrollbar-handle:#00000080;--wui-scrollbar-handle-hover:#0000009e;--wui-scrollbar-handle-active:#000000bd}web-ui-theme[resolved-appearance=dark]{--wui-scrollbar-handle:#ffffff80;--wui-scrollbar-handle-hover:#ffffff9e;--wui-scrollbar-handle-active:#ffffffbd}html:has(web-ui-theme[resolved-appearance=dark]:not(web-ui-theme *)){--wui-scrollbar-handle:#ffffff80;--wui-scrollbar-handle-hover:#ffffff9e;--wui-scrollbar-handle-active:#ffffffbd}
@@ -0,0 +1,37 @@
1
+ //#region src/shared/element-width/index.ts
2
+ var e = /* @__PURE__ */ new WeakMap(), t;
3
+ function n() {
4
+ return typeof ResizeObserver > "u" ? null : t || (t = new ResizeObserver((t) => {
5
+ for (let n of t) {
6
+ let t = e.get(n.target);
7
+ if (!t) continue;
8
+ let r = n.contentRect.width;
9
+ t.width !== r && (t.width = r, t.notify(r));
10
+ }
11
+ }), t);
12
+ }
13
+ function r(t, r) {
14
+ let i = n();
15
+ i && (e.set(t, {
16
+ notify: r,
17
+ width: -1
18
+ }), i.observe(t, { box: "content-box" }));
19
+ }
20
+ function i(n) {
21
+ e.delete(n), t?.unobserve(n);
22
+ }
23
+ var a = class {
24
+ constructor(e, t) {
25
+ this.host = e, this.notify = t, e.addController(this);
26
+ }
27
+ hostConnected() {
28
+ r(this.host, this.notify);
29
+ }
30
+ hostDisconnected() {
31
+ i(this.host);
32
+ }
33
+ };
34
+ //#endregion
35
+ export { a as ElementWidthController };
36
+
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/element-width/index.ts"],"sourcesContent":["/**\n * 跟踪宿主的内容盒行内尺寸(可用行内空间)。\n *\n * 用途是「渲染结果必须按容器宽度算」的组件:中间省略的切点由可用空间决定,而容器宽度\n * 只由外部布局给出,组件自己算不出来。观察宿主本身即可——宿主的内容盒就是它的可用行内\n * 空间,不需要再往里指定子元素。\n *\n * 口径取 `contentRect.width` 而不是 `clientWidth`:后者是整数,最多丢掉 1px;中间省略\n * 是在预算边界上取最长前缀的,少给 1px 会让最后被选中的那个簇贴边溢出,而多算 1px 则\n * 是白白丢掉一个字符。\n */\nimport type { LitElement, ReactiveController, ReactiveControllerHost } from 'lit'\n\ntype WidthHost = ReactiveControllerHost & LitElement\n\ninterface Watched {\n readonly notify: (width: number) => void\n width: number\n}\n\nconst watched = new WeakMap<Element, Watched>()\nlet observer: ResizeObserver | undefined\n\n/**\n * 全模块共用一个 ResizeObserver:一张表里可能有成百上千个这样的单元格,逐实例建观察者要\n * 给每个单元格单独排一次尺寸回调。没有 ResizeObserver 的环境(jsdom)没有布局可测,直接不接。\n */\nfunction getObserver(): ResizeObserver | null {\n if (typeof ResizeObserver === 'undefined') return null\n if (observer) return observer\n\n observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n const target = watched.get(entry.target)\n if (!target) continue\n\n const width = entry.contentRect.width\n if (target.width === width) continue\n\n target.width = width\n target.notify(width)\n }\n })\n\n return observer\n}\n\nfunction watch(target: Element, notify: (width: number) => void) {\n const shared = getObserver()\n if (!shared) return\n\n // 初值取 -1 而不是 0:0 是一个合法的宽度(`display: none` 就是 0),用它当初值会把\n // 「宽度真的是 0」与「还没量过」混成同一种状态,而这两种状态的处置相反——前者必须跳过。\n watched.set(target, { notify, width: -1 })\n shared.observe(target, { box: 'content-box' })\n}\n\nfunction unwatch(target: Element) {\n watched.delete(target)\n observer?.unobserve(target)\n}\n\n/** 把宿主内容盒的行内尺寸变化报给宿主。 */\nexport class ElementWidthController implements ReactiveController {\n constructor(\n private readonly host: WidthHost,\n private readonly notify: (width: number) => void\n ) {\n host.addController(this)\n }\n\n /*\n * 只接断开/重连:观察目标是宿主自己,它不会像查询出来的子元素那样在渲染之间被换掉,\n * 因此不需要 hostUpdated 补挂。断开时观察被拆掉,重连必须接回来,否则组件被移出\n * 文档再放回去之后宽度就再也不会更新。\n */\n hostConnected() {\n watch(this.host, this.notify)\n }\n\n hostDisconnected() {\n unwatch(this.host)\n }\n}\n"],"mappings":";AAoBA,IAAM,oBAAU,IAAI,QAA0B,GAC1C;AAMJ,SAAS,IAAqC;CAiB5C,OAhBI,OAAO,iBAAmB,MAAoB,OAC9C,MAEJ,IAAW,IAAI,gBAAe,MAAW;EACvC,KAAK,IAAM,KAAS,GAAS;GAC3B,IAAM,IAAS,EAAQ,IAAI,EAAM,MAAM;GACvC,IAAI,CAAC,GAAQ;GAEb,IAAM,IAAQ,EAAM,YAAY;GAC5B,EAAO,UAAU,MAErB,EAAO,QAAQ,GACf,EAAO,OAAO,CAAK;EACrB;CACF,CAAC,GAEM;AACT;AAEA,SAAS,EAAM,GAAiB,GAAiC;CAC/D,IAAM,IAAS,EAAY;CACtB,MAIL,EAAQ,IAAI,GAAQ;EAAE;EAAQ,OAAO;CAAG,CAAC,GACzC,EAAO,QAAQ,GAAQ,EAAE,KAAK,cAAc,CAAC;AAC/C;AAEA,SAAS,EAAQ,GAAiB;CAEhC,AADA,EAAQ,OAAO,CAAM,GACrB,GAAU,UAAU,CAAM;AAC5B;AAGA,IAAa,IAAb,MAAkE;CAChE,YACE,GACA,GACA;EACA,AAHiB,KAAA,OAAA,GACA,KAAA,SAAA,GAEjB,EAAK,cAAc,IAAI;CACzB;CAOA,gBAAgB;EACd,EAAM,KAAK,MAAM,KAAK,MAAM;CAC9B;CAEA,mBAAmB;EACjB,EAAQ,KAAK,IAAI;CACnB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greypan/web-ui",
3
- "version": "9.0.0",
3
+ "version": "10.0.0",
4
4
  "description": "Lit-based web components for React, Vue, and vanilla JS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,7 +13,9 @@
13
13
  ],
14
14
  "sideEffects": [
15
15
  "./src/components/**",
16
- "./dist/components/**"
16
+ "./dist/components/**",
17
+ "./src/scrollbars/**",
18
+ "./dist/scrollbars/**"
17
19
  ],
18
20
  "exports": {
19
21
  ".": {
@@ -28,6 +30,11 @@
28
30
  "types": "./dist/icons/index.d.ts",
29
31
  "import": "./dist/icons/index.js"
30
32
  },
33
+ "./scrollbars": {
34
+ "types": "./dist/scrollbars/index.d.ts",
35
+ "import": "./dist/scrollbars/index.js"
36
+ },
37
+ "./scrollbars.css": "./dist/scrollbars/style.css",
31
38
  "./types/*": "./dist/types/*.d.ts",
32
39
  "./package.json": "./package.json"
33
40
  },
@@ -58,6 +65,7 @@
58
65
  "@types/react": "^19.2.17",
59
66
  "@types/react-dom": "^19.2.5",
60
67
  "lit": "^3.3.3",
68
+ "overlayscrollbars": "^2.16.0",
61
69
  "react": "^19.2.7",
62
70
  "react-dom": "^19.2.7",
63
71
  "vite-plugin-dts": "^5.0.3",
@@ -68,12 +76,16 @@
68
76
  "peerDependencies": {
69
77
  "@types/react": ">=19",
70
78
  "lit": "^3.3.3",
79
+ "overlayscrollbars": "^2.16.0",
71
80
  "vue": ">=3.5"
72
81
  },
73
82
  "peerDependenciesMeta": {
74
83
  "@types/react": {
75
84
  "optional": true
76
85
  },
86
+ "overlayscrollbars": {
87
+ "optional": true
88
+ },
77
89
  "vue": {
78
90
  "optional": true
79
91
  }