@greypan/web-ui 12.0.1 → 12.2.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.
- package/README.CN.md +4 -0
- package/README.md +4 -0
- package/dist/assets/menu-portal.js +1 -1
- package/dist/assets/menu-portal.js.map +1 -1
- package/dist/assets/toast-containers.js +1 -1
- package/dist/assets/toast-containers.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +5 -0
- package/dist/components/autocomplete/index.d.ts.map +1 -1
- package/dist/components/autocomplete/index.js +18 -2
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/autocomplete/style.js +1 -1
- package/dist/components/autocomplete/style.js.map +1 -1
- package/dist/components/back-top/style.js +1 -1
- package/dist/components/back-top/style.js.map +1 -1
- package/dist/components/button-group/style.js +1 -1
- package/dist/components/button-group/style.js.map +1 -1
- package/dist/components/dialog/index.d.ts +5 -1
- package/dist/components/dialog/index.d.ts.map +1 -1
- package/dist/components/dialog/index.js +16 -4
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/dialog/style.js +1 -1
- package/dist/components/dialog/style.js.map +1 -1
- package/dist/components/drawer/index.d.ts +11 -0
- package/dist/components/drawer/index.d.ts.map +1 -1
- package/dist/components/drawer/index.js +23 -1
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/drawer/page-shift.js +113 -0
- package/dist/components/drawer/page-shift.js.map +1 -0
- package/dist/components/dropdown/index.js +1 -1
- package/dist/components/grouped-text/grouping.d.ts +25 -0
- package/dist/components/grouped-text/grouping.d.ts.map +1 -0
- package/dist/components/grouped-text/grouping.js +147 -0
- package/dist/components/grouped-text/grouping.js.map +1 -0
- package/dist/components/grouped-text/index.d.ts +36 -0
- package/dist/components/grouped-text/index.d.ts.map +1 -0
- package/dist/components/grouped-text/index.js +28 -0
- package/dist/components/grouped-text/index.js.map +1 -0
- package/dist/components/grouped-text/style.js +5 -0
- package/dist/components/grouped-text/style.js.map +1 -0
- package/dist/components/icon/index.d.ts +1 -1
- package/dist/components/icon/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -1
- package/dist/components/layout/index.d.ts +8 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +24 -13
- package/dist/components/layout/index.js.map +1 -1
- package/dist/components/layout/style.js +1 -1
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/middle-ellipsis/index.js +1 -1
- package/dist/components/popover/index.d.ts +5 -2
- package/dist/components/popover/index.d.ts.map +1 -1
- package/dist/components/popover/index.js +32 -21
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/popover/style.js +1 -1
- package/dist/components/popover/style.js.map +1 -1
- package/dist/components/progress/index.d.ts +30 -0
- package/dist/components/progress/index.d.ts.map +1 -0
- package/dist/components/progress/index.js +103 -0
- package/dist/components/progress/index.js.map +1 -0
- package/dist/components/progress/style.js +5 -0
- package/dist/components/progress/style.js.map +1 -0
- package/dist/components/segmented-trigger/index.d.ts +2 -0
- package/dist/components/segmented-trigger/index.d.ts.map +1 -1
- package/dist/components/segmented-trigger/index.js +7 -0
- package/dist/components/segmented-trigger/index.js.map +1 -1
- package/dist/components/select/index.d.ts +13 -0
- package/dist/components/select/index.d.ts.map +1 -1
- package/dist/components/select/index.js +41 -3
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/select/style.js +1 -1
- package/dist/components/select/style.js.map +1 -1
- package/dist/components/shake/index.d.ts +43 -0
- package/dist/components/shake/index.d.ts.map +1 -0
- package/dist/components/shake/index.js +103 -0
- package/dist/components/shake/index.js.map +1 -0
- package/dist/components/spinner/index.d.ts +0 -3
- package/dist/components/spinner/index.d.ts.map +1 -1
- package/dist/components/spinner/index.js +5 -20
- package/dist/components/spinner/index.js.map +1 -1
- package/dist/components/spinner/style.js +1 -1
- package/dist/components/spinner/style.js.map +1 -1
- package/dist/components/theme/style.js +1 -1
- package/dist/components/theme/style.js.map +1 -1
- package/dist/components/tooltip/index.d.ts +8 -0
- package/dist/components/tooltip/index.d.ts.map +1 -1
- package/dist/components/tooltip/index.js +60 -10
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/style.js +1 -1
- package/dist/components/tooltip/style.js.map +1 -1
- package/dist/icons/generated/ant-design-sync-outlined.d.ts +7 -0
- package/dist/icons/generated/ant-design-sync-outlined.d.ts.map +1 -0
- package/dist/icons/generated/ant-design-sync-outlined.js +9 -0
- package/dist/icons/generated/ant-design-sync-outlined.js.map +1 -0
- package/dist/icons/generated/ic-baseline-phonelink.d.ts +7 -0
- package/dist/icons/generated/ic-baseline-phonelink.d.ts.map +1 -0
- package/dist/icons/generated/ic-baseline-phonelink.js +9 -0
- package/dist/icons/generated/ic-baseline-phonelink.js.map +1 -0
- package/dist/icons/generated/ic-outline-motion-photos-off.d.ts +7 -0
- package/dist/icons/generated/ic-outline-motion-photos-off.d.ts.map +1 -0
- package/dist/icons/generated/ic-outline-motion-photos-off.js +9 -0
- package/dist/icons/generated/ic-outline-motion-photos-off.js.map +1 -0
- package/dist/icons/generated/ic-outline-motion-photos-on.d.ts +7 -0
- package/dist/icons/generated/ic-outline-motion-photos-on.d.ts.map +1 -0
- package/dist/icons/generated/ic-outline-motion-photos-on.js +9 -0
- package/dist/icons/generated/ic-outline-motion-photos-on.js.map +1 -0
- package/dist/icons/generated/ic-round-menu-open.d.ts +7 -0
- package/dist/icons/generated/ic-round-menu-open.d.ts.map +1 -0
- package/dist/icons/generated/ic-round-menu-open.js +9 -0
- package/dist/icons/generated/ic-round-menu-open.js.map +1 -0
- package/dist/icons/generated/lucide-accessibility.d.ts +7 -0
- package/dist/icons/generated/lucide-accessibility.d.ts.map +1 -0
- package/dist/icons/generated/lucide-accessibility.js +9 -0
- package/dist/icons/generated/lucide-accessibility.js.map +1 -0
- package/dist/icons/generated/lucide-boxes.d.ts +7 -0
- package/dist/icons/generated/lucide-boxes.d.ts.map +1 -0
- package/dist/icons/generated/lucide-boxes.js +9 -0
- package/dist/icons/generated/lucide-boxes.js.map +1 -0
- package/dist/icons/generated/lucide-contrast.d.ts +7 -0
- package/dist/icons/generated/lucide-contrast.d.ts.map +1 -0
- package/dist/icons/generated/lucide-contrast.js +9 -0
- package/dist/icons/generated/lucide-contrast.js.map +1 -0
- package/dist/icons/generated/lucide-keyboard.d.ts +7 -0
- package/dist/icons/generated/lucide-keyboard.d.ts.map +1 -0
- package/dist/icons/generated/lucide-keyboard.js +9 -0
- package/dist/icons/generated/lucide-keyboard.js.map +1 -0
- package/dist/icons/generated/lucide-languages.d.ts +7 -0
- package/dist/icons/generated/lucide-languages.d.ts.map +1 -0
- package/dist/icons/generated/lucide-languages.js +9 -0
- package/dist/icons/generated/lucide-languages.js.map +1 -0
- package/dist/icons/generated/lucide-list.d.ts +7 -0
- package/dist/icons/generated/lucide-list.d.ts.map +1 -0
- package/dist/icons/generated/lucide-list.js +9 -0
- package/dist/icons/generated/lucide-list.js.map +1 -0
- package/dist/icons/generated/lucide-moon.d.ts +7 -0
- package/dist/icons/generated/lucide-moon.d.ts.map +1 -0
- package/dist/icons/generated/lucide-moon.js +9 -0
- package/dist/icons/generated/lucide-moon.js.map +1 -0
- package/dist/icons/generated/lucide-sparkles.d.ts +7 -0
- package/dist/icons/generated/lucide-sparkles.d.ts.map +1 -0
- package/dist/icons/generated/lucide-sparkles.js +9 -0
- package/dist/icons/generated/lucide-sparkles.js.map +1 -0
- package/dist/icons/generated/lucide-sun.d.ts +7 -0
- package/dist/icons/generated/lucide-sun.d.ts.map +1 -0
- package/dist/icons/generated/lucide-sun.js +9 -0
- package/dist/icons/generated/lucide-sun.js.map +1 -0
- package/dist/icons/generated/tabler-brand-typescript.d.ts +7 -0
- package/dist/icons/generated/tabler-brand-typescript.d.ts.map +1 -0
- package/dist/icons/generated/tabler-brand-typescript.js +9 -0
- package/dist/icons/generated/tabler-brand-typescript.js.map +1 -0
- package/dist/icons/index.d.ts +87 -72
- package/dist/icons/index.js +16 -1
- package/dist/shared/locale/builtin-labels.js +2 -1
- package/dist/shared/locale/builtin-labels.js.map +1 -1
- package/dist/shared/overlay/anchored-panel.js +2 -2
- package/dist/shared/overlay/anchored-panel.js.map +1 -1
- package/dist/shared/overlay/presence.js +2 -2
- package/dist/shared/overlay/presence.js.map +1 -1
- package/dist/shared/overlay/trigger-props.d.ts +10 -0
- package/dist/shared/overlay/trigger-props.d.ts.map +1 -0
- package/dist/shared/overlay/trigger-props.js +42 -0
- package/dist/shared/overlay/trigger-props.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { clusterEnds } from "../middle-ellipsis/truncate.js";
|
|
2
|
+
// src/components/grouped-text/grouping.ts
|
|
3
|
+
const CARD_SIZES = [4];
|
|
4
|
+
const PHONE_SIZES = [
|
|
5
|
+
3,
|
|
6
|
+
4,
|
|
7
|
+
4
|
|
8
|
+
];
|
|
9
|
+
const AMOUNT_INTEGER_SIZE = 3;
|
|
10
|
+
/**
|
|
11
|
+
* 从左按 `sizes` 逐组推进。位数用完还剩字符时沿用**最后一档**继续切,末组放剩下的部分,
|
|
12
|
+
* 因此一个字符都不会丢,也不会有空组。
|
|
13
|
+
*/
|
|
14
|
+
function leftSpans(total, sizes) {
|
|
15
|
+
const spans = [];
|
|
16
|
+
let from = 0;
|
|
17
|
+
let index = 0;
|
|
18
|
+
while (from < total) {
|
|
19
|
+
const size = sizes[Math.min(index, sizes.length - 1)];
|
|
20
|
+
const to = Math.min(from + size, total);
|
|
21
|
+
spans.push({
|
|
22
|
+
from,
|
|
23
|
+
to
|
|
24
|
+
});
|
|
25
|
+
from = to;
|
|
26
|
+
index += 1;
|
|
27
|
+
}
|
|
28
|
+
return spans;
|
|
29
|
+
}
|
|
30
|
+
/** 把 `[from, to)` 这段簇从右往左按 `size` 位一组切开;最左一组可以不满。 */
|
|
31
|
+
function rightSpans(from, to, size) {
|
|
32
|
+
const spans = [];
|
|
33
|
+
let end = to;
|
|
34
|
+
while (end > from) {
|
|
35
|
+
const start = Math.max(from, end - size);
|
|
36
|
+
spans.unshift({
|
|
37
|
+
from: start,
|
|
38
|
+
to: end
|
|
39
|
+
});
|
|
40
|
+
end = start;
|
|
41
|
+
}
|
|
42
|
+
return spans;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 金额的小数点位置:最后一个**后面还有内容**的 `.` 所在簇。
|
|
46
|
+
*
|
|
47
|
+
* 只认 `.`。区域分隔符(`,`、`U+00A0`、`U+202F`)属于内容,复制会原样带出,那是格式化字符串的
|
|
48
|
+
* 事,不由这个只负责排版分组的组件代替。
|
|
49
|
+
*/
|
|
50
|
+
function separatorIndex(value, ends) {
|
|
51
|
+
for (let index = ends.length - 2; index >= 0; index -= 1) {
|
|
52
|
+
const start = index === 0 ? 0 : ends[index - 1];
|
|
53
|
+
if (value.slice(start, ends[index]) === ".") return index;
|
|
54
|
+
}
|
|
55
|
+
return -1;
|
|
56
|
+
}
|
|
57
|
+
function amountSpans(value, ends) {
|
|
58
|
+
const total = ends.length;
|
|
59
|
+
const dot = separatorIndex(value, ends);
|
|
60
|
+
if (dot <= 0) return rightSpans(0, total, AMOUNT_INTEGER_SIZE);
|
|
61
|
+
return [...rightSpans(0, dot, AMOUNT_INTEGER_SIZE), {
|
|
62
|
+
from: dot,
|
|
63
|
+
to: total
|
|
64
|
+
}];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 把 `[from, to)` 这段簇拆成 `body`(除末位簇)与 `gap`(末位簇)。
|
|
68
|
+
*
|
|
69
|
+
* 只有一组时 `gap` 为空串——组间间隙是「这一组与下一组之间」的事,最后一组后面没有下一组。
|
|
70
|
+
*/
|
|
71
|
+
function runOf(value, ends, span, hasGap) {
|
|
72
|
+
const start = span.from === 0 ? 0 : ends[span.from - 1];
|
|
73
|
+
const end = ends[span.to - 1];
|
|
74
|
+
if (!hasGap) return {
|
|
75
|
+
body: value.slice(start, end),
|
|
76
|
+
gap: ""
|
|
77
|
+
};
|
|
78
|
+
const split = span.to - span.from === 1 ? start : ends[span.to - 2];
|
|
79
|
+
return {
|
|
80
|
+
body: value.slice(start, split),
|
|
81
|
+
gap: value.slice(split, end)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function toRuns(value, ends, spans) {
|
|
85
|
+
const last = spans.length - 1;
|
|
86
|
+
return spans.map((span, index) => runOf(value, ends, span, index < last));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 把 `value` 按 `groups` 切成首尾相接的组。
|
|
90
|
+
*
|
|
91
|
+
* `groups` 省略、为空或读不出时整串自成一组:不分组、没有间隙。这一档刻意不猜组形——身份证按
|
|
92
|
+
* 4 位切开、大额金额按 4 位切开都是看得见的错切,而错切比不分组的可见成本高得多。
|
|
93
|
+
*/
|
|
94
|
+
function groupedRuns(value, groups) {
|
|
95
|
+
if (value === "") return [];
|
|
96
|
+
const ends = clusterEnds(value);
|
|
97
|
+
if (groups === "amount") return toRuns(value, ends, amountSpans(value, ends));
|
|
98
|
+
const sizes = sanitizeSizes(presetSizes(groups));
|
|
99
|
+
if (sizes.length === 0) return [{
|
|
100
|
+
body: value,
|
|
101
|
+
gap: ""
|
|
102
|
+
}];
|
|
103
|
+
return toRuns(value, ends, leftSpans(ends.length, sizes));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 预设名到逐组位数的映射;显式数组原样返回。
|
|
107
|
+
*
|
|
108
|
+
* 参数里不含 `amount`:它在 `groupedRuns` 里按金额规则单独分派,不走这张预设表。
|
|
109
|
+
*/
|
|
110
|
+
function presetSizes(groups) {
|
|
111
|
+
if (groups === void 0) return [];
|
|
112
|
+
if (groups === "card") return CARD_SIZES;
|
|
113
|
+
if (groups === "phone") return PHONE_SIZES;
|
|
114
|
+
return groups;
|
|
115
|
+
}
|
|
116
|
+
/** 只保留正整数位数;`0`、负数、`NaN`、小数与非有限值都丢掉。 */
|
|
117
|
+
function sanitizeSizes(sizes) {
|
|
118
|
+
return sizes.filter((size) => Number.isInteger(size) && size > 0);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* `groups` 的字符串写法解析:预设名,或以逗号(空格也可)分隔的正整数位数表。
|
|
122
|
+
*
|
|
123
|
+
* 逐组位数写成 `4,4,4,4`;读不出的写法返回 `undefined`,即不分组。预设名与数字表混排
|
|
124
|
+
* (`card,4`)同样判为读不出,不做「取能用的那半截」这种静默降级。attribute 与 property
|
|
125
|
+
* 两条通道共用这一份解析,口径必须一致。
|
|
126
|
+
*/
|
|
127
|
+
function parseGroupsAttribute(raw) {
|
|
128
|
+
const text = raw?.trim();
|
|
129
|
+
if (!text) return void 0;
|
|
130
|
+
if (text === "card" || text === "phone" || text === "amount") return text;
|
|
131
|
+
const parts = text.split(/[\s,]+/).filter(Boolean);
|
|
132
|
+
if (parts.length === 0) return void 0;
|
|
133
|
+
const sizes = parts.map(Number);
|
|
134
|
+
return sizes.every((size) => Number.isInteger(size) && size > 0) ? sizes : void 0;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 把写入形态(见 `GroupedTextGroupsInput`)收成 `groupedRuns` 要的组形。
|
|
138
|
+
*
|
|
139
|
+
* 字符串一律走 `parseGroupsAttribute`:预设名原样留下,位数表解析成数组,读不出的得到 `undefined`
|
|
140
|
+
* (不分组)。数组本身就是组形,直接透传。
|
|
141
|
+
*/
|
|
142
|
+
function resolveGroups(input) {
|
|
143
|
+
return typeof input === "string" ? parseGroupsAttribute(input) : input;
|
|
144
|
+
}
|
|
145
|
+
export { groupedRuns, parseGroupsAttribute, resolveGroups };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=grouping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping.js","names":[],"sources":["../../../src/components/grouped-text/grouping.ts"],"sourcesContent":["import { clusterEnds } from '@/components/middle-ellipsis/truncate'\n\n/**\n * 「组块」的切分:决定组间间隙落在原文的哪些位置,不改变文本本身。\n *\n * 切分结果首尾相接、拼回去恰好等于原串——组与组之间不含任何补位字符。这不是洁癖:复制路径读的是\n * 文本节点的 data,分隔符一旦进了文本节点,复制、页内查找、双击选词三件事同时坏掉。间隙因此只能\n * 是布局(挂在每组末位簇自己的字距上),不能是字符。实测边界见\n * docs/research/web-ui-grouped-text-261102.md。\n *\n * 切点一律落在字素簇边界上,与 `web-ui-middle-ellipsis` 同一个口径:按 UTF-16 下标切会把代理对与\n * ZWJ 序列从中间劈开,屏幕上出现半个 emoji 比「这一组少一位」严重得多。\n */\n\n/** 预设组形。 */\nexport type GroupedTextPreset = 'card' | 'phone' | 'amount'\n\n/** 组形:预设名,或逐组的字素簇数(如 `[4, 4, 4, 4]`)。 */\nexport type GroupedTextGroups = GroupedTextPreset | readonly number[]\n\n/**\n * 组形的**写入**形态:预设名与位数表都落到 `string`,逐组位数保持 `readonly number[]`。\n *\n * 它比 `GroupedTextGroups` 宽一档,是因为 attribute 与 property 两条通道必须给出同一个组形:Vue 这类\n * 框架发现 `'groups' in el` 成立就直接赋 property,Lit 的 `fromAttribute` 转换器一次都不会跑。字符串\n * 只走 attribute 那条路的话,模板里写 `groups=\"4,4,4,4\"` 传进来的就是原样字符串,位数表读不出来。\n * 收到的值统一由 `resolveGroups` 收成 `GroupedTextGroups`。\n */\nexport type GroupedTextGroupsInput = string | readonly number[]\n\n/**\n * 一组的渲染输入。\n *\n * `gap` 是这一组末位字素簇——只有它单独成盒并带上字距,间隙才会属于该字符自己的文本片段,\n * 选区高亮才会在组间连续。最后一组的 `gap` 是空串:它后面没有下一组,不留多余字距。\n */\nexport interface GroupedRun {\n readonly body: string\n readonly gap: string\n}\n\n/** 一组覆盖的字素簇区间,`[from, to)`。 */\ninterface ClusterSpan {\n readonly from: number\n readonly to: number\n}\n\nconst CARD_SIZES = [4]\nconst PHONE_SIZES = [3, 4, 4]\nconst AMOUNT_INTEGER_SIZE = 3\n\n/**\n * 从左按 `sizes` 逐组推进。位数用完还剩字符时沿用**最后一档**继续切,末组放剩下的部分,\n * 因此一个字符都不会丢,也不会有空组。\n */\nfunction leftSpans(total: number, sizes: readonly number[]): ClusterSpan[] {\n const spans: ClusterSpan[] = []\n\n let from = 0\n let index = 0\n while (from < total) {\n const size = sizes[Math.min(index, sizes.length - 1)]\n const to = Math.min(from + size, total)\n spans.push({ from, to })\n from = to\n index += 1\n }\n\n return spans\n}\n\n/** 把 `[from, to)` 这段簇从右往左按 `size` 位一组切开;最左一组可以不满。 */\nfunction rightSpans(from: number, to: number, size: number): ClusterSpan[] {\n const spans: ClusterSpan[] = []\n\n let end = to\n while (end > from) {\n const start = Math.max(from, end - size)\n spans.unshift({ from: start, to: end })\n end = start\n }\n\n return spans\n}\n\n/**\n * 金额的小数点位置:最后一个**后面还有内容**的 `.` 所在簇。\n *\n * 只认 `.`。区域分隔符(`,`、`U+00A0`、`U+202F`)属于内容,复制会原样带出,那是格式化字符串的\n * 事,不由这个只负责排版分组的组件代替。\n */\nfunction separatorIndex(value: string, ends: number[]): number {\n for (let index = ends.length - 2; index >= 0; index -= 1) {\n const start = index === 0 ? 0 : ends[index - 1]\n if (value.slice(start, ends[index]) === '.') return index\n }\n return -1\n}\n\nfunction amountSpans(value: string, ends: number[]): ClusterSpan[] {\n const total = ends.length\n const dot = separatorIndex(value, ends)\n\n // 没有小数点(或整串就是小数点)时没有可归组的小数段,按整数串从右往左三位一组。\n if (dot <= 0) return rightSpans(0, total, AMOUNT_INTEGER_SIZE)\n\n // 小数点连着小数部分自成一组:`1234567.89` → `1|234|567|.89`。\n // 小数点单独成组会让它两侧都被撑开,屏幕上读成 `567 . 89`,把分隔符从自己的数字上拆开了。\n return [...rightSpans(0, dot, AMOUNT_INTEGER_SIZE), { from: dot, to: total }]\n}\n\n/**\n * 把 `[from, to)` 这段簇拆成 `body`(除末位簇)与 `gap`(末位簇)。\n *\n * 只有一组时 `gap` 为空串——组间间隙是「这一组与下一组之间」的事,最后一组后面没有下一组。\n */\nfunction runOf(value: string, ends: number[], span: ClusterSpan, hasGap: boolean): GroupedRun {\n const start = span.from === 0 ? 0 : ends[span.from - 1]\n const end = ends[span.to - 1]\n\n if (!hasGap) return { body: value.slice(start, end), gap: '' }\n\n const split = span.to - span.from === 1 ? start : ends[span.to - 2]\n return { body: value.slice(start, split), gap: value.slice(split, end) }\n}\n\nfunction toRuns(value: string, ends: number[], spans: ClusterSpan[]): GroupedRun[] {\n const last = spans.length - 1\n return spans.map((span, index) => runOf(value, ends, span, index < last))\n}\n\n/**\n * 把 `value` 按 `groups` 切成首尾相接的组。\n *\n * `groups` 省略、为空或读不出时整串自成一组:不分组、没有间隙。这一档刻意不猜组形——身份证按\n * 4 位切开、大额金额按 4 位切开都是看得见的错切,而错切比不分组的可见成本高得多。\n */\nexport function groupedRuns(value: string, groups: GroupedTextGroups | undefined): GroupedRun[] {\n if (value === '') return []\n\n const ends = clusterEnds(value)\n\n if (groups === 'amount') return toRuns(value, ends, amountSpans(value, ends))\n\n const sizes = sanitizeSizes(presetSizes(groups))\n if (sizes.length === 0) return [{ body: value, gap: '' }]\n\n return toRuns(value, ends, leftSpans(ends.length, sizes))\n}\n\n/**\n * 预设名到逐组位数的映射;显式数组原样返回。\n *\n * 参数里不含 `amount`:它在 `groupedRuns` 里按金额规则单独分派,不走这张预设表。\n */\nfunction presetSizes(groups: Exclude<GroupedTextGroups, 'amount'> | undefined): readonly number[] {\n if (groups === undefined) return []\n if (groups === 'card') return CARD_SIZES\n if (groups === 'phone') return PHONE_SIZES\n return groups\n}\n\n/** 只保留正整数位数;`0`、负数、`NaN`、小数与非有限值都丢掉。 */\nfunction sanitizeSizes(sizes: readonly number[]): number[] {\n return sizes.filter(size => Number.isInteger(size) && size > 0)\n}\n\n/**\n * `groups` 的字符串写法解析:预设名,或以逗号(空格也可)分隔的正整数位数表。\n *\n * 逐组位数写成 `4,4,4,4`;读不出的写法返回 `undefined`,即不分组。预设名与数字表混排\n * (`card,4`)同样判为读不出,不做「取能用的那半截」这种静默降级。attribute 与 property\n * 两条通道共用这一份解析,口径必须一致。\n */\nexport function parseGroupsAttribute(raw: string | null): GroupedTextGroups | undefined {\n const text = raw?.trim()\n if (!text) return undefined\n if (text === 'card' || text === 'phone' || text === 'amount') return text\n\n const parts = text.split(/[\\s,]+/).filter(Boolean)\n if (parts.length === 0) return undefined\n\n const sizes = parts.map(Number)\n return sizes.every(size => Number.isInteger(size) && size > 0) ? sizes : undefined\n}\n\n/**\n * 把写入形态(见 `GroupedTextGroupsInput`)收成 `groupedRuns` 要的组形。\n *\n * 字符串一律走 `parseGroupsAttribute`:预设名原样留下,位数表解析成数组,读不出的得到 `undefined`\n * (不分组)。数组本身就是组形,直接透传。\n */\nexport function resolveGroups(input: GroupedTextGroupsInput | undefined): GroupedTextGroups | undefined {\n return typeof input === 'string' ? parseGroupsAttribute(input) : input\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { GroupedTextGroups, GroupedTextGroupsInput, GroupedTextPreset } from "./grouping.js";
|
|
2
|
+
import { LitElement } from "lit";
|
|
3
|
+
/**
|
|
4
|
+
* 组块显示:把长数字或字符串按组撑开肉眼可见的间隙,同时让「选中并复制」拿到的仍是**原始连续串**。
|
|
5
|
+
*
|
|
6
|
+
* 适用面是复制出去要能直接用的连续标识符——银行卡号、手机号、大额金额。间隙走字距而不走字符或
|
|
7
|
+
* 外边距:分隔符一旦写进文本节点,复制、页内查找、双击选词三件事同时坏掉;`margin` 撑出的间隙落在
|
|
8
|
+
* 两个文本片段之外,选区高亮会在每个组间断开一条与间隙等宽的洞,而 `::selection` 用不了布局属性,
|
|
9
|
+
* 补不掉。实测边界与取证见 docs/research/web-ui-grouped-text-261102.md。
|
|
10
|
+
*
|
|
11
|
+
* 只读显示:没有事件、没有插槽、不参与表单。`value` 始终是那串原文,渲染只是把它切成首尾相接的组
|
|
12
|
+
* 并给每组末位挂上字距,`renderRoot.textContent` 拼回去等于 `value`。
|
|
13
|
+
*/
|
|
14
|
+
export declare class WebUiGroupedText extends LitElement {
|
|
15
|
+
static styles: import("lit").CSSResult[];
|
|
16
|
+
/** 要显示的原始连续串。组件只读它,不回写。 */
|
|
17
|
+
value: string;
|
|
18
|
+
/**
|
|
19
|
+
* 组形:`'card'`(每 4 位一组)、`'phone'`(3-4-4)、`'amount'`(整数部分从右往左每 3 位一组,
|
|
20
|
+
* 小数点连同小数部分自成一组),或显式的逐组字素簇数数组(如 `[4, 4, 4, 4]`)。
|
|
21
|
+
*
|
|
22
|
+
* 字符串写法在 attribute 与 property 两条通道都认:`groups="card"`、`groups="4,4,4,4"`。Vue 这类
|
|
23
|
+
* 框架发现 `'groups' in el` 成立就直接赋 property,`fromAttribute` 转换器不会跑,所以解析不能只挂在
|
|
24
|
+
* attribute 那条路上。省略或读不出时**不分组**——猜一个组形会把身份证按 4 位切开,那种错切比
|
|
25
|
+
* 不分组的可见成本高得多。
|
|
26
|
+
*/
|
|
27
|
+
groups?: GroupedTextGroupsInput;
|
|
28
|
+
render(): import("lit").TemplateResult<1>;
|
|
29
|
+
}
|
|
30
|
+
declare global {
|
|
31
|
+
interface HTMLElementTagNameMap {
|
|
32
|
+
'web-ui-grouped-text': WebUiGroupedText;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export type { GroupedTextGroups, GroupedTextGroupsInput, GroupedTextPreset };
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/grouped-text/index.ts"],"mappings":";;;;;;;;;;;;;qBAoCa,yBAAyB;SACpB,sBAAM;;EAGM;;;;;;;;;;EAWsC,SAAS;EAElE,wBAAM;;QAKT;YACI;IACR,uBAAuB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
2
|
+
import { groupedRuns, parseGroupsAttribute, resolveGroups } from "./grouping.js";
|
|
3
|
+
import style_default from "./style.js";
|
|
4
|
+
import { LitElement, html, unsafeCSS } from "lit";
|
|
5
|
+
import { customElement, property } from "lit/decorators.js";
|
|
6
|
+
// src/components/grouped-text/index.ts
|
|
7
|
+
function renderRun(run) {
|
|
8
|
+
if (run.gap === "") return html`<span class="group">${run.body}</span>`;
|
|
9
|
+
return html`<span class="group">${run.body}<span class="gap">${run.gap}</span></span>`;
|
|
10
|
+
}
|
|
11
|
+
let WebUiGroupedText = class WebUiGroupedText extends LitElement {
|
|
12
|
+
constructor(..._args) {
|
|
13
|
+
super(..._args);
|
|
14
|
+
this.value = "";
|
|
15
|
+
}
|
|
16
|
+
static {
|
|
17
|
+
this.styles = [unsafeCSS(style_default)];
|
|
18
|
+
}
|
|
19
|
+
render() {
|
|
20
|
+
return html`${groupedRuns(this.value, resolveGroups(this.groups)).map(renderRun)}`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
__decorate([property({ type: String })], WebUiGroupedText.prototype, "value", void 0);
|
|
24
|
+
__decorate([property({ converter: { fromAttribute: parseGroupsAttribute } })], WebUiGroupedText.prototype, "groups", void 0);
|
|
25
|
+
WebUiGroupedText = __decorate([customElement("web-ui-grouped-text")], WebUiGroupedText);
|
|
26
|
+
export { WebUiGroupedText };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["style"],"sources":["../../../src/components/grouped-text/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport {\n groupedRuns,\n parseGroupsAttribute,\n resolveGroups,\n type GroupedRun,\n type GroupedTextGroupsInput\n} from './grouping'\n\nexport type { GroupedTextPreset, GroupedTextGroups, GroupedTextGroupsInput } from './grouping'\n\nimport style from './style.css?inline'\n\n/*\n * 两个形状各自写成一行:**模板里不许出现换行与缩进**。Lit 将标记之间的空白落成文本节点,\n * 而复制读的是文本节点的 data——组间多一个换行,剪贴板就多一个换行,组件唯一的硬需求当场失效。\n */\nfunction renderRun(run: GroupedRun) {\n if (run.gap === '') return html`<span class=\"group\">${run.body}</span>`\n return html`<span class=\"group\">${run.body}<span class=\"gap\">${run.gap}</span></span>`\n}\n\n/**\n * 组块显示:把长数字或字符串按组撑开肉眼可见的间隙,同时让「选中并复制」拿到的仍是**原始连续串**。\n *\n * 适用面是复制出去要能直接用的连续标识符——银行卡号、手机号、大额金额。间隙走字距而不走字符或\n * 外边距:分隔符一旦写进文本节点,复制、页内查找、双击选词三件事同时坏掉;`margin` 撑出的间隙落在\n * 两个文本片段之外,选区高亮会在每个组间断开一条与间隙等宽的洞,而 `::selection` 用不了布局属性,\n * 补不掉。实测边界与取证见 docs/research/web-ui-grouped-text-261102.md。\n *\n * 只读显示:没有事件、没有插槽、不参与表单。`value` 始终是那串原文,渲染只是把它切成首尾相接的组\n * 并给每组末位挂上字距,`renderRoot.textContent` 拼回去等于 `value`。\n */\n@customElement('web-ui-grouped-text')\nexport class WebUiGroupedText extends LitElement {\n static override styles = [unsafeCSS(style)]\n\n /** 要显示的原始连续串。组件只读它,不回写。 */\n @property({ type: String }) value = ''\n\n /**\n * 组形:`'card'`(每 4 位一组)、`'phone'`(3-4-4)、`'amount'`(整数部分从右往左每 3 位一组,\n * 小数点连同小数部分自成一组),或显式的逐组字素簇数数组(如 `[4, 4, 4, 4]`)。\n *\n * 字符串写法在 attribute 与 property 两条通道都认:`groups=\"card\"`、`groups=\"4,4,4,4\"`。Vue 这类\n * 框架发现 `'groups' in el` 成立就直接赋 property,`fromAttribute` 转换器不会跑,所以解析不能只挂在\n * attribute 那条路上。省略或读不出时**不分组**——猜一个组形会把身份证按 4 位切开,那种错切比\n * 不分组的可见成本高得多。\n */\n @property({ converter: { fromAttribute: parseGroupsAttribute } }) groups?: GroupedTextGroupsInput\n\n override render() {\n return html`${groupedRuns(this.value, resolveGroups(this.groups)).map(renderRun)}`\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-grouped-text': WebUiGroupedText\n }\n}\n"],"mappings":";;;;;;AAmBA,SAAS,UAAU,KAAiB;CAClC,IAAI,IAAI,QAAQ,IAAI,OAAO,IAAI,uBAAuB,IAAI,KAAK;CAC/D,OAAO,IAAI,uBAAuB,IAAI,KAAK,oBAAoB,IAAI,IAAI;AACzE;AAcO,IAAM,mBAAN,MAAM,yBAAyB,WAAW;;;EAIX,KAAA,QAAA;;;EAHX,KAAA,SAAA,CAAC,UAAUA,aAAK,CAAC;;CAgB1C,SAAkB;EAChB,OAAO,IAAI,GAAG,YAAY,KAAK,OAAO,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS;CACjF;AACF;AAhBG,WAAA,CAAA,SAAS,EAAE,MAAM,OAAO,CAAC,CAAA,GAAA,iBAAA,WAAA,SAAA,KAAA,CAAA;AAWzB,WAAA,CAAA,SAAS,EAAE,WAAW,EAAE,eAAe,qBAAqB,EAAE,CAAC,CAAA,GAAA,iBAAA,WAAA,UAAA,KAAA,CAAA;AAhBjE,mBAAA,WAAA,CAAA,cAAc,qBAAqB,CAAA,GAAA,gBAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/grouped-text/style.css?inline"],"sourcesContent":["/*\n * 数字宽度显式对齐:Chromium 默认就是等宽,WebKit 不是——同一串 `0123456789` 里 `1` 比 `6` 窄\n * 约 4px。不写这一条,同一份页面在两个引擎上的组间对齐就不一致。\n */\n:host {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/*\n * 组间间隙走字距,不走外边距。\n *\n * 字距属于末位字符自己的文本片段,选中这个片段时字距跟着一起被刷色,于是「看得见分组」与\n * 「高亮连续」两者兼得;`margin` / `padding` / 空盒撑出的间隙落在两个文本片段之外,洞宽恒等于\n * 间隙宽,而 `::selection` 只能用不影响布局的属性,补不掉。\n *\n * 只有末位簇带字距,组内其余字符不加宽;最后一组的末位不挂这个类,行尾因此不留多余字距。\n */\n.gap {\n letter-spacing: var(--wui-grouped-text-gap, 0.25em);\n}\n"],"mappings":";AAIA,IAAA,gBAAA"}
|
|
@@ -6,7 +6,7 @@ export declare class WebUiIcon extends LitElement {
|
|
|
6
6
|
spin: boolean;
|
|
7
7
|
size: number;
|
|
8
8
|
color?: string;
|
|
9
|
-
render(): import("lit").TemplateResult<1
|
|
9
|
+
render(): typeof nothing | import("lit").TemplateResult<1>;
|
|
10
10
|
}
|
|
11
11
|
declare global {
|
|
12
12
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/icon/index.ts"],"mappings":";;qBAgBa,kBAAkB;SACb,sBAAM;EAEU,OAAO;EACK;EACD;EACA;EAElC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/icon/index.ts"],"mappings":";;qBAgBa,kBAAkB;SACb,sBAAM;EAEU,OAAO;EACK;EACD;EACA;EAElC,iBAAM,wBAAA;;QAqBT;YACI;IACR,eAAe"}
|
|
@@ -10,7 +10,7 @@ import { WebUiCheckbox } from "./checkbox/index.js";
|
|
|
10
10
|
import { WebUiCheckboxGroup } from "./checkbox-group/index.js";
|
|
11
11
|
import { WebUiCollapse } from "./collapse/index.js";
|
|
12
12
|
import { WebUiContextMenu } from "./context-menu/index.js";
|
|
13
|
-
import { WebUiDialog } from "./dialog/index.js";
|
|
13
|
+
import { DialogFooterOrientation, WebUiDialog } from "./dialog/index.js";
|
|
14
14
|
import { DrawerPlacement, WebUiDrawer } from "./drawer/index.js";
|
|
15
15
|
import { WebUiDropdownDivider } from "./dropdown-divider/index.js";
|
|
16
16
|
import { WebUiDropdownHeader } from "./dropdown-header/index.js";
|
|
@@ -18,17 +18,21 @@ import { WebUiDropdownItem } from "./dropdown-item/index.js";
|
|
|
18
18
|
import { Placement, WebUiDropdown } from "./dropdown/index.js";
|
|
19
19
|
import { WebUiEditableText } from "./editable-text/index.js";
|
|
20
20
|
import { WebUiEmpty } from "./empty/index.js";
|
|
21
|
+
import { GroupedTextGroups, GroupedTextGroupsInput, GroupedTextPreset } from "./grouped-text/grouping.js";
|
|
22
|
+
import { WebUiGroupedText } from "./grouped-text/index.js";
|
|
21
23
|
import { ImagePreviewAriaLabels, ImagePreviewHandle, ImagePreviewItem, ImagePreviewOptions, imagePreview } from "./image-preview/index.js";
|
|
22
24
|
import { WebUiInputNumber } from "./input-number/index.js";
|
|
23
25
|
import { LayoutAriaLabels, WebUiLayout } from "./layout/index.js";
|
|
24
26
|
import { WebUiMiddleEllipsis } from "./middle-ellipsis/index.js";
|
|
25
27
|
import { WebUiOption } from "./option/index.js";
|
|
26
28
|
import { WebUiPopover } from "./popover/index.js";
|
|
29
|
+
import { WebUiProgress } from "./progress/index.js";
|
|
27
30
|
import { WebUiRadio } from "./radio/index.js";
|
|
28
31
|
import { WebUiRadioGroup } from "./radio-group/index.js";
|
|
29
32
|
import { WebUiSegmented } from "./segmented/index.js";
|
|
30
33
|
import { WebUiSegmentedTrigger } from "./segmented-trigger/index.js";
|
|
31
34
|
import { WebUiSelect } from "./select/index.js";
|
|
35
|
+
import { SHAKE_X_DISTANCE_FALLBACK, SHAKE_X_DISTANCE_VARIABLE, SHAKE_Y_DISTANCE_FALLBACK, SHAKE_Y_DISTANCE_VARIABLE, ShakeOptions, shake } from "./shake/index.js";
|
|
32
36
|
import { WebUiSlider } from "./slider/index.js";
|
|
33
37
|
import { WebUiSpinner } from "./spinner/index.js";
|
|
34
38
|
import { WebUiTextarea } from "./textarea/index.js";
|
|
@@ -42,5 +46,5 @@ import { WebUiSwitch } from "./switch/index.js";
|
|
|
42
46
|
import { WebUiTooltip } from "./tooltip/index.js";
|
|
43
47
|
import { WebUiEvent, WebUiEventName } from "../types/utils.js";
|
|
44
48
|
export type WebUiElementMap = { [K in keyof HTMLElementTagNameMap as K extends `web-ui-${string}` ? K : never]: HTMLElementTagNameMap[K]; };
|
|
45
|
-
export { DrawerPlacement, ImagePreviewAriaLabels, ImagePreviewHandle, ImagePreviewItem, ImagePreviewOptions, LayoutAriaLabels, type Placement, ReplayOptions, ResolvedThemeAppearance, ThemeAppearance, ThemeMotion, type ToastCloseReason, type ToastInstanceOptions, type ToastOptions, type ToastPosition, type ToastType, WebUiAutocomplete, WebUiAvatar, WebUiBackTop, WebUiBadge, WebUiButton, WebUiButtonGroup, WebUiCheckbox, WebUiCheckboxGroup, WebUiCollapse, WebUiContextMenu, WebUiDialog, WebUiDrawer, WebUiDropdown, WebUiDropdownDivider, WebUiDropdownHeader, WebUiDropdownItem, WebUiEditableText, WebUiEmpty, type WebUiEvent, type WebUiEventName, WebUiIcon, WebUiInput, WebUiInputNumber, WebUiLayout, WebUiMiddleEllipsis, WebUiOption, WebUiPopover, WebUiRadio, WebUiRadioGroup, WebUiSegmented, WebUiSegmentedTrigger, WebUiSelect, WebUiSlider, WebUiSpinner, WebUiSvgDrawLines, WebUiSwitch, WebUiTextarea, WebUiTheme, WebUiToast, WebUiTooltip, imagePreview, toast };
|
|
49
|
+
export { DialogFooterOrientation, DrawerPlacement, type GroupedTextGroups, type GroupedTextGroupsInput, type GroupedTextPreset, ImagePreviewAriaLabels, ImagePreviewHandle, ImagePreviewItem, ImagePreviewOptions, LayoutAriaLabels, type Placement, ReplayOptions, ResolvedThemeAppearance, SHAKE_X_DISTANCE_FALLBACK, SHAKE_X_DISTANCE_VARIABLE, SHAKE_Y_DISTANCE_FALLBACK, SHAKE_Y_DISTANCE_VARIABLE, ShakeOptions, ThemeAppearance, ThemeMotion, type ToastCloseReason, type ToastInstanceOptions, type ToastOptions, type ToastPosition, type ToastType, WebUiAutocomplete, WebUiAvatar, WebUiBackTop, WebUiBadge, WebUiButton, WebUiButtonGroup, WebUiCheckbox, WebUiCheckboxGroup, WebUiCollapse, WebUiContextMenu, WebUiDialog, WebUiDrawer, WebUiDropdown, WebUiDropdownDivider, WebUiDropdownHeader, WebUiDropdownItem, WebUiEditableText, WebUiEmpty, type WebUiEvent, type WebUiEventName, WebUiGroupedText, WebUiIcon, WebUiInput, WebUiInputNumber, WebUiLayout, WebUiMiddleEllipsis, WebUiOption, WebUiPopover, WebUiProgress, WebUiRadio, WebUiRadioGroup, WebUiSegmented, WebUiSegmentedTrigger, WebUiSelect, WebUiSlider, WebUiSpinner, WebUiSvgDrawLines, WebUiSwitch, WebUiTextarea, WebUiTheme, WebUiToast, WebUiTooltip, imagePreview, shake, toast };
|
|
46
50
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/components/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/components/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAgDY,qBACT,WAAW,yBAAyB,+BAA+B,YAAY,sBAAsB"}
|
package/dist/components/index.js
CHANGED
|
@@ -19,17 +19,20 @@ import { WebUiDropdownItem } from "./dropdown-item/index.js";
|
|
|
19
19
|
import { WebUiDropdown } from "./dropdown/index.js";
|
|
20
20
|
import { WebUiEditableText } from "./editable-text/index.js";
|
|
21
21
|
import { WebUiEmpty } from "./empty/index.js";
|
|
22
|
+
import { WebUiGroupedText } from "./grouped-text/index.js";
|
|
22
23
|
import { imagePreview } from "./image-preview/index.js";
|
|
23
24
|
import { WebUiInputNumber } from "./input-number/index.js";
|
|
24
25
|
import { WebUiLayout } from "./layout/index.js";
|
|
25
26
|
import { WebUiMiddleEllipsis } from "./middle-ellipsis/index.js";
|
|
26
27
|
import { WebUiOption } from "./option/index.js";
|
|
27
28
|
import { WebUiPopover } from "./popover/index.js";
|
|
29
|
+
import { WebUiProgress } from "./progress/index.js";
|
|
28
30
|
import { WebUiRadio } from "./radio/index.js";
|
|
29
31
|
import { WebUiRadioGroup } from "./radio-group/index.js";
|
|
30
32
|
import { WebUiSegmented } from "./segmented/index.js";
|
|
31
33
|
import { WebUiSegmentedTrigger } from "./segmented-trigger/index.js";
|
|
32
34
|
import { WebUiSelect } from "./select/index.js";
|
|
35
|
+
import { SHAKE_X_DISTANCE_FALLBACK, SHAKE_X_DISTANCE_VARIABLE, SHAKE_Y_DISTANCE_FALLBACK, SHAKE_Y_DISTANCE_VARIABLE, shake } from "./shake/index.js";
|
|
33
36
|
import { WebUiSlider } from "./slider/index.js";
|
|
34
37
|
import { WebUiSpinner } from "./spinner/index.js";
|
|
35
38
|
import { WebUiTextarea } from "./textarea/index.js";
|
|
@@ -39,4 +42,4 @@ import { WebUiToast } from "./toast/toast.js";
|
|
|
39
42
|
import "./toast/index.js";
|
|
40
43
|
import { WebUiSwitch } from "./switch/index.js";
|
|
41
44
|
import { WebUiTooltip } from "./tooltip/index.js";
|
|
42
|
-
export { WebUiAutocomplete, WebUiAvatar, WebUiBackTop, WebUiBadge, WebUiButton, WebUiButtonGroup, WebUiCheckbox, WebUiCheckboxGroup, WebUiCollapse, WebUiContextMenu, WebUiDialog, WebUiDrawer, WebUiDropdown, WebUiDropdownDivider, WebUiDropdownHeader, WebUiDropdownItem, WebUiEditableText, WebUiEmpty, WebUiIcon, WebUiInput, WebUiInputNumber, WebUiLayout, WebUiMiddleEllipsis, WebUiOption, WebUiPopover, WebUiRadio, WebUiRadioGroup, WebUiSegmented, WebUiSegmentedTrigger, WebUiSelect, WebUiSlider, WebUiSpinner, WebUiSvgDrawLines, WebUiSwitch, WebUiTextarea, WebUiTheme, WebUiToast, WebUiTooltip, imagePreview, toast };
|
|
45
|
+
export { SHAKE_X_DISTANCE_FALLBACK, SHAKE_X_DISTANCE_VARIABLE, SHAKE_Y_DISTANCE_FALLBACK, SHAKE_Y_DISTANCE_VARIABLE, WebUiAutocomplete, WebUiAvatar, WebUiBackTop, WebUiBadge, WebUiButton, WebUiButtonGroup, WebUiCheckbox, WebUiCheckboxGroup, WebUiCollapse, WebUiContextMenu, WebUiDialog, WebUiDrawer, WebUiDropdown, WebUiDropdownDivider, WebUiDropdownHeader, WebUiDropdownItem, WebUiEditableText, WebUiEmpty, WebUiGroupedText, WebUiIcon, WebUiInput, WebUiInputNumber, WebUiLayout, WebUiMiddleEllipsis, WebUiOption, WebUiPopover, WebUiProgress, WebUiRadio, WebUiRadioGroup, WebUiSegmented, WebUiSegmentedTrigger, WebUiSelect, WebUiSlider, WebUiSpinner, WebUiSvgDrawLines, WebUiSwitch, WebUiTextarea, WebUiTheme, WebUiToast, WebUiTooltip, imagePreview, shake, toast };
|
|
@@ -33,6 +33,14 @@ export declare class WebUiLayout extends LitElement {
|
|
|
33
33
|
* 侧边栏相关事件与属性在该模式下不产生可见效果。
|
|
34
34
|
*/
|
|
35
35
|
noSidebar: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* 启用桌面端 Sidebar 的 collapse 入口。
|
|
38
|
+
*
|
|
39
|
+
* 只有声明了本属性,layout 才渲染收起 / 展开 Toggle,也才会因为点击而派发
|
|
40
|
+
* `sidebar-collapsed-change`。桌面宽度仍由受控属性 `sidebar-collapsed` 决定,
|
|
41
|
+
* 未声明本属性不会阻止它收窄;移动端 Drawer 的 header Toggle 不受影响。
|
|
42
|
+
*/
|
|
43
|
+
sidebarCollapsible: boolean;
|
|
36
44
|
/**
|
|
37
45
|
* 桌面端 Sidebar 的持久布局偏好。
|
|
38
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/layout/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAyCiB;EACf;EACA;EACA;EACA;EACA;;qBAIW,oBAAoB;SACf,sBAAM;;;;;;;;EAUtB;;;;;;;;;;;;EAcA;EAGA;EAGA;;EAIA;;;;;EAOA;;EAIA;;;;;;;EASA;EAGA;;EAGgC,aAAa;UASrC;;;;;;;MAQJ;UAII;mBAES;UAST;EAUC,yBAAyB,cAAc,yBAAyB;mBAoBxD;mBAMA;UAET;UASA;UAUA;UAUA;UAKA;UACA;UAES;UAET;UAKA;UAMA;UASA;UAIA;UA+CA;UAUA;UAoDA;EAQC;EAQA;EAOA;UAID;UAIA;YAiBW,WAAW,SAAS;EAK9B,wBAAM;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/layout/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAyCiB;EACf;EACA;EACA;EACA;EACA;;qBAIW,oBAAoB;SACf,sBAAM;;;;;;;;EAUtB;;;;;;;;EAUA;;;;;;;;;;;;EAcA;EAGA;EAGA;;EAIA;;;;;EAOA;;EAIA;;;;;;;EASA;EAGA;;EAGgC,aAAa;UASrC;;;;;;;MAQJ;UAII;mBAES;UAST;EAUC,yBAAyB,cAAc,yBAAyB;mBAoBxD;mBAMA;UAET;UASA;UAUA;UAUA;UAKA;UACA;UAES;UAET;UAKA;UAMA;UASA;UAIA;UA+CA;UAUA;UAoDA;EAQC;EAQA;EAOA;UAID;UAIA;YAiBW,WAAW,SAAS;EAK9B,wBAAM;WAsIE;IACf,4BAA4B;MAAc;;IAC1C,uBAAuB;MAAc;;IACrC,wBAAwB;MAAc;;IACtC,iBAAiB;MAAc;;;;QAI3B;YACI;IACR,iBAAiB"}
|
|
@@ -2,6 +2,7 @@ import __decorate from "../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm
|
|
|
2
2
|
import "../icon/index.js";
|
|
3
3
|
import glass_default from "../../classes/glass.js";
|
|
4
4
|
import radix_icons_panel_left_minimized_default from "../../icons/generated/radix-icons-panel-left-minimized.js";
|
|
5
|
+
import ic_round_menu_open_default from "../../icons/generated/ic-round-menu-open.js";
|
|
5
6
|
import "../button/index.js";
|
|
6
7
|
import { resolveBuiltinLabelSet } from "../../shared/locale/builtin-labels.js";
|
|
7
8
|
import glow_default from "../../classes/glow.js";
|
|
@@ -27,9 +28,10 @@ let WebUiLayout = class WebUiLayout extends LitElement {
|
|
|
27
28
|
constructor(..._args) {
|
|
28
29
|
super(..._args);
|
|
29
30
|
this.noSidebar = false;
|
|
31
|
+
this.sidebarCollapsible = false;
|
|
30
32
|
this.sidebarCollapsed = false;
|
|
31
33
|
this.sidebarWidth = "240px";
|
|
32
|
-
this.collapsedWidth = "
|
|
34
|
+
this.collapsedWidth = "60px";
|
|
33
35
|
this.sidebarResizable = false;
|
|
34
36
|
this.sidebarMinWidth = "";
|
|
35
37
|
this.sidebarMaxWidth = "";
|
|
@@ -246,17 +248,20 @@ let WebUiLayout = class WebUiLayout extends LitElement {
|
|
|
246
248
|
const isSidebarOpen = !this.sidebarCollapsed;
|
|
247
249
|
const sidebarStyle = this._mobile ? {} : { width: this._resizeWidth ?? (this.sidebarCollapsed ? this.collapsedWidth : this.sidebarWidth) };
|
|
248
250
|
const toggleLabel = resolveBuiltinLabelSet(this.ariaLabels, isSidebarOpen ? "collapseSidebar" : "expandSidebar");
|
|
249
|
-
const desktopToggle = html`
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
251
|
+
const desktopToggle = this.sidebarCollapsible ? html`
|
|
252
|
+
<web-ui-button
|
|
253
|
+
class="sidebar-toggle"
|
|
254
|
+
icon
|
|
255
|
+
variant="ghost"
|
|
256
|
+
@click="${this._toggleSidebar}"
|
|
257
|
+
aria-label=${toggleLabel}
|
|
258
|
+
>
|
|
259
|
+
<web-ui-icon
|
|
260
|
+
.icon=${ic_round_menu_open_default}
|
|
261
|
+
style=${styleMap({ transform: isSidebarOpen ? "none" : "scaleX(-1)" })}
|
|
262
|
+
></web-ui-icon>
|
|
263
|
+
</web-ui-button>
|
|
264
|
+
` : nothing;
|
|
260
265
|
const sidebarViewport = html`
|
|
261
266
|
<div class="sidebar-viewport">
|
|
262
267
|
<slot name="sidebar"></slot>
|
|
@@ -294,7 +299,7 @@ let WebUiLayout = class WebUiLayout extends LitElement {
|
|
|
294
299
|
>
|
|
295
300
|
<div class="aside-panel wui-glass">
|
|
296
301
|
${sidebarViewport}
|
|
297
|
-
|
|
302
|
+
${this.sidebarCollapsible ? html`<div class="sidebar-toggle-area">${desktopToggle}</div>` : nothing}
|
|
298
303
|
</div>
|
|
299
304
|
${this.sidebarResizable && !this.sidebarCollapsed ? html`
|
|
300
305
|
<div
|
|
@@ -318,6 +323,7 @@ let WebUiLayout = class WebUiLayout extends LitElement {
|
|
|
318
323
|
?open="${this.sidebarOpen}"
|
|
319
324
|
controlled
|
|
320
325
|
draggable
|
|
326
|
+
page-shift
|
|
321
327
|
aria-dialog-label=${resolveBuiltinLabelSet(this.ariaLabels, "mainNavigation")}
|
|
322
328
|
@open-change="${this._onDrawerChange}"
|
|
323
329
|
style="${styleMap({
|
|
@@ -344,6 +350,11 @@ __decorate([property({
|
|
|
344
350
|
attribute: "no-sidebar",
|
|
345
351
|
reflect: true
|
|
346
352
|
})], WebUiLayout.prototype, "noSidebar", void 0);
|
|
353
|
+
__decorate([property({
|
|
354
|
+
type: Boolean,
|
|
355
|
+
attribute: "sidebar-collapsible",
|
|
356
|
+
reflect: true
|
|
357
|
+
})], WebUiLayout.prototype, "sidebarCollapsible", void 0);
|
|
347
358
|
__decorate([property({
|
|
348
359
|
type: Boolean,
|
|
349
360
|
attribute: "sidebar-collapsed",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["glass","glow","style","radixIconsPanelLeftMinimized"],"sources":["../../../src/components/layout/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/drawer'\nimport '@/components/icon'\n\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport glass from '@/classes/glass.css?inline'\nimport glow from '@/classes/glow.css?inline'\nimport { radixIconsPanelLeftMinimized } from '@/icons'\nimport { attachDragGesture, clamp, type DragGestureHandle } from '@/shared/gesture'\nimport { resolveBuiltinLabelSet } from '@/shared/locale/builtin-labels'\nimport { defineVisibleAreaTracker, VisibleAreaController } from '@/shared/visible-area'\n\nimport style from './style.css?inline'\n\n/**\n * 移动端断点的媒体查询。\n *\n * 必须与 `style.css` 里那份 `@media (width <= 640px)` 是同一条条件:CSS 用它在窄视口隐藏桌面\n * aside,TS 用同一条件决定渲染哪一套树,两者错开就会出现「树还在桌面、aside 已经被藏起来」\n * 的空档。两份字面量的相等由 `components/drawer/__tests__/breakpoint-parity.spec.ts` 守着。\n */\nconst MOBILE_VIEWPORT_QUERY = '(width <= 640px)'\n\n/**\n * Layout 组件 - 提供页面布局框架,支持 collapsible sidebar 与无 sidebar 模式\n *\n * @fires sidebar-collapsed-change - 桌面端侧边栏 collapse 状态变更请求时触发\n * @fires sidebar-open-change - 移动端侧边栏 Drawer toggle 请求时触发\n * @fires sidebar-width-change - 桌面端侧边栏拖拽调宽结束时触发,携带最终宽度(受控请求)\n * @fires mobile-change - 视口跨越移动端断点、`mobile` 翻转时触发(派生状态通知)\n * @slot banner - 顶部 Banner 区域(全宽,随页面滚动,可选)\n * @slot header - 内容区顶部 header(sticky)\n * @slot sidebar - 侧边栏内容;Consumer 决定其内部固定区域与滚动容器;`no-sidebar` 置位时不渲染\n * @slot tabbar - 底部 tabbar 区域\n * @slot default - 主内容区\n */\n/** `web-ui-layout` 内置无障碍名的成组覆盖;每个 key 对应一处图标控件的 accessible name。 */\nexport interface LayoutAriaLabels {\n collapseSidebar?: string\n expandSidebar?: string\n resizeSidebar?: string\n openNavigationMenu?: string\n mainNavigation?: string\n}\n\n@customElement('web-ui-layout')\nexport class WebUiLayout extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(glow), unsafeCSS(style)]\n\n /**\n * 关闭整块 sidebar:桌面端不渲染 aside 与 collapse Toggle,移动端不渲染 header Toggle 与 Drawer。\n *\n * 它是布局模式而不是 collapse 的别名:`sidebar-collapsed` 只收窄桌面 aside,本属性把侧边栏\n * 结构本身从树里去掉。`sidebar` slot 随之不渲染(消费方节点仍留在 light DOM),\n * 侧边栏相关事件与属性在该模式下不产生可见效果。\n */\n @property({ type: Boolean, attribute: 'no-sidebar', reflect: true })\n noSidebar = false\n\n /**\n * 桌面端 Sidebar 的持久布局偏好。\n *\n * `true` 只会将桌面 Sidebar 收窄到 `collapsedWidth`,不会影响移动端 Drawer。\n * 它不与 `sidebarOpen` 合并:前者是跨视口保留的密度选择,后者是移动端瞬时的 modal 可见性。\n *\n * 两处渲染的是同一份 `sidebar` slot 内容,所以本属性只负责桌面 aside 的几何:Consumer\n * 若让自己的 slot 内容跟着它变形态(隐藏 label、改排布),必须按 `mobile` 收窄那个条件\n * (例如 `collapsed && !layout.mobile`,或 CSS 的 `web-ui-layout[mobile]`),\n * 否则 collapsed state 会一路渲染进移动端 Drawer(issue #195)。\n */\n @property({ type: Boolean, attribute: 'sidebar-collapsed', reflect: true })\n sidebarCollapsed = false\n\n @property({ type: String, attribute: 'sidebar-width', reflect: true })\n sidebarWidth = '240px'\n\n @property({ type: String, attribute: 'collapsed-width', reflect: true })\n collapsedWidth = '68px'\n\n /** 启用桌面端 Sidebar 拖拽调宽(collapsed state 隐藏 handle)。 */\n @property({ type: Boolean, attribute: 'sidebar-resizable', reflect: true })\n sidebarResizable = false\n\n /**\n * 拖拽调宽的下限(px,其他单位不解析);未设置时回退为 `collapsedWidth`,\n * 防止拖到不可用宽度。上限 `sidebar-max-width` 受内置 50vw 硬上限钳制。\n */\n @property({ type: String, attribute: 'sidebar-min-width', reflect: true })\n sidebarMinWidth = ''\n\n /** 拖拽调宽的上限(px,其他单位不解析);始终受内置 50vw 硬上限钳制。 */\n @property({ type: String, attribute: 'sidebar-max-width', reflect: true })\n sidebarMaxWidth = ''\n\n /**\n * 移动端 web-ui-drawer 的瞬时可见性。\n *\n * Drawer Toggle、Escape 和 backdrop 关闭都通过 `sidebar-open-change` 请求更新它;它不会改变桌面端的 collapse preference。\n * 因而不与 `sidebarCollapsed` 合并,避免响应式切换时将一次移动端交互误写为桌面端布局选择。\n */\n @property({ type: Boolean, attribute: 'sidebar-open', reflect: true })\n sidebarOpen = false\n\n @property({ type: Boolean, attribute: 'header-glow', reflect: true })\n headerGlow = false\n\n /** 内置无障碍名的成组覆盖;未提供的 key 回退内置英文文案(见 ADR-0016)。 */\n @property({ attribute: false }) ariaLabels?: LayoutAriaLabels\n\n /*\n * `mobile` 是派生输出,不是第二个输入:像 `web-ui-theme` 的 `resolved-appearance` 一样由组件\n * 独占写入,读取取 `MOBILE_VIEWPORT_QUERY`。外部写 attribute 会在同一次 reaction 内被恢复,\n * 写 property 不生效(只读 accessor,严格模式下抛错)——两种写法都不构成本属性之外的\n * 第三个视口来源。\n */\n @property({ type: Boolean, attribute: 'mobile', reflect: true })\n private _mobile = false\n\n /**\n * 当前视口是否落在移动端分支(即 `MOBILE_VIEWPORT_QUERY` 命中)。\n *\n * 与组件渲染哪一套树、以及 `mobile-change` 的派发是同一个值:翻转瞬间三者一起变,\n * Consumer 不必自己写断点判断,也不会与 layout 内部的树切换错开半拍。派生状态只读。\n */\n get mobile(): boolean {\n return this._mobile\n }\n\n private _mobileQuery: MediaQueryList | null = null\n\n private readonly _onMobileQueryChange = (event: MediaQueryListEvent) => {\n this._syncMobile(event.matches)\n }\n\n /*\n * 本方法由媒体查询翻转驱动,也被连接时的初次求值调用;attribute 的恢复路径见\n * `attributeChangedCallback`,那里不改判、也不派发通知,所以 `mobile-change` 只在\n * 视口真正跨断点时出现一次(窄视口挂载时那次算第一次)。\n */\n private _syncMobile(next: boolean) {\n if (next === this._mobile) return\n this._mobile = next\n // 跨断点会卸载桌面 layout(resize handle 随之消失);必须先终结进行中的拖拽,\n // 否则手势悬挂,切回桌面后所有新拖拽都在入口被 `_isResizing()` 拦下。\n if (this._isResizing()) this._resetActiveResize()\n this.toggleAttribute('mobile', next)\n this.dispatchEvent(new CustomEvent('mobile-change', { detail: { mobile: next }, bubbles: true, composed: true }))\n }\n\n override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n super.attributeChangedCallback(name, oldValue, newValue)\n // super 已把外部值写进 backing field,所以这里要按媒体查询重算,不能拿字段当真相。\n if (name !== 'mobile') return\n // 无 matchMedia 的环境没有视口可判,`mobile` 恒为 false(见 connectedCallback)。\n const derived = this._mobileQuery?.matches ?? false\n if (this._mobile !== derived) this._mobile = derived\n /*\n * 布尔 attribute 只论存在性,但值本身也要归位:`setAttribute('mobile', 'false')` 在移动端\n * 会让 backing field 与 attribute 的**存在性**一致、值却留在宿主上(`getAttribute` 会回读\n * 到 consumer 写的那串)。组件写的形态是空串,这里按同一形态恢复,主题的\n * `resolved-appearance` 先例同理。\n */\n if (derived) {\n if (this.getAttribute('mobile') !== '') this.setAttribute('mobile', '')\n return\n }\n if (this.hasAttribute('mobile')) this.removeAttribute('mobile')\n }\n\n private readonly _visibleBanner = defineVisibleAreaTracker({\n onVisibleAreaChange: area => {\n this.style.setProperty('--wui-layout-visible-banner-height', `${area.height}px`)\n }\n }).make()\n\n private readonly _visibleBannerController = new VisibleAreaController(this, this._visibleBanner)\n\n private _toggleSidebar() {\n if (this._mobile) {\n this._emitSidebarOpenChange(!this.sidebarOpen)\n return\n }\n\n this._emitSidebarCollapsedChange(!this.sidebarCollapsed)\n }\n\n private _emitSidebarCollapsedChange(collapsed: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-collapsed-change', {\n detail: { collapsed },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _emitSidebarOpenChange(open: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-open-change', {\n detail: { open },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onDrawerChange(e: CustomEvent<{ open: boolean }>) {\n e.stopPropagation()\n this._emitSidebarOpenChange(e.detail.open)\n }\n\n private _resizeGesture: DragGestureHandle | null = null\n private _resizeStartWidth = 0\n // 拖拽中的临时宽度;由 render 的 styleMap 统一写入,避免与 Lit 样式管理竞争。\n @state() private _resizeWidth: string | null = null\n\n private _parsePx(value: string, fallback: number): number {\n const parsed = Number.parseFloat(value)\n return Number.isFinite(parsed) ? parsed : fallback\n }\n\n private _resolveSidebarMinWidth(): number {\n return this.sidebarMinWidth\n ? this._parsePx(this.sidebarMinWidth, this._parsePx(this.collapsedWidth, 68))\n : this._parsePx(this.collapsedWidth, 68)\n }\n\n private _resolveSidebarMaxWidth(): number {\n // 组件内置硬上限:Sidebar 最多占视口一半(无需调用方设置)。\n // 调用方 sidebar-max-width 在该上限内生效,防止拖拽占据整个视口。\n const halfViewport = Math.floor(window.innerWidth / 2)\n const configured = this.sidebarMaxWidth ? this._parsePx(this.sidebarMaxWidth, halfViewport) : halfViewport\n const upper = Math.min(configured, halfViewport)\n return Math.max(upper, this._resolveSidebarMinWidth())\n }\n\n private _isResizing(): boolean {\n return this._resizeGesture?.isDragging() ?? false\n }\n\n private _handleResizePointerDown(e: PointerEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n // jsdom 等环境的 PointerEvent 可能缺失 isPrimary,仅在明确为 false(多点触控副指针)时拒绝。\n if (e.isPrimary === false) return\n\n const aside = this.renderRoot.querySelector('aside')\n if (!aside) return\n\n // 拖拽起点以 computed width 为准,并丢弃键盘未提交的调整(Enter 才提交、\n // Escape 撤回;抓取隐式放弃),保证零位移松手不会派发陈旧宽度。\n this._resizeStartWidth = this._parsePx(window.getComputedStyle(aside).width, this._parsePx(this.sidebarWidth, 240))\n this._resizeWidth = null\n aside.classList.add('is-resizing')\n\n this._resizeGesture = attachDragGesture(e, {\n axis: 'x',\n onMove: info => {\n // 向右拖增宽、向左拖收窄(handle 在右缘);钳制在 [min, max] 与视口内。\n const next = this._resizeStartWidth + info.deltaX\n const clamped = clamp(next, this._resolveSidebarMinWidth(), this._resolveSidebarMaxWidth())\n this._resizeWidth = `${Math.round(clamped)}px`\n },\n onEnd: () => {\n // 零位移松手(点击而非拖拽)不视为调宽动作:按 cancel 语义静默收尾。\n if (this._resizeWidth === null) {\n this._resetActiveResize()\n return\n }\n const width = this._resizeWidth\n this._resetActiveResize()\n // 受控契约:松手派发请求并交还 prop 管辖,由 Consumer 回写 sidebar-width。\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', {\n detail: { width },\n bubbles: true,\n composed: true\n })\n )\n },\n onCancel: () => {\n this._resetActiveResize()\n }\n })\n }\n\n // 终结进行中的拖拽:清手势状态、移除跟手 class,宽度过渡回 prop 管辖,不派发事件。\n // pointer cancel、跨越移动端断点和切入 no-sidebar 时共用此收尾。\n private _resetActiveResize() {\n this._resizeGesture?.destroy()\n this._resizeGesture = null\n this.renderRoot.querySelector('aside')?.classList.remove('is-resizing')\n this._resizeWidth = null\n }\n\n // 键盘调宽(WAI-ARIA splitter 模式):方向键步进 16px(Shift 加速 64px)调整\n // 临时宽度,Enter 以受控请求派发 `sidebar-width-change`(与指针松手同语义),\n // Escape 撤回临时宽度交还 prop 管辖,Home/End 直接到 min/max。\n private _handleResizeKeydown(e: KeyboardEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n\n const min = this._resolveSidebarMinWidth()\n const max = this._resolveSidebarMaxWidth()\n const aside = this.renderRoot.querySelector('aside')\n const current = this._resizeWidth\n ? this._parsePx(this._resizeWidth, min)\n : this._parsePx(aside ? window.getComputedStyle(aside).width : this.sidebarWidth, min)\n const step = e.shiftKey ? 64 : 16\n let next: number | null = null\n\n switch (e.key) {\n case 'ArrowLeft':\n next = Math.max(min, current - step)\n break\n case 'ArrowRight':\n next = Math.min(max, current + step)\n break\n case 'Home':\n next = min\n break\n case 'End':\n next = max\n break\n case 'Enter':\n // 无未提交调整时不派发(焦点停留时的回车噪声)\n if (this._resizeWidth !== null) {\n const width = this._resizeWidth\n this._resizeWidth = null\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', { detail: { width }, bubbles: true, composed: true })\n )\n }\n e.preventDefault()\n return\n case 'Escape':\n this._resizeWidth = null\n e.preventDefault()\n return\n default:\n return\n }\n e.preventDefault()\n this._resizeWidth = `${Math.round(next)}px`\n }\n\n /*\n * 视口判定订阅媒体查询,而不是监听 window resize:跨断点每次只派发一次 change,没有\n * resize 事件流要挡。判定、CSS 的 `@media (width <= 640px)` 与对外暴露的 `mobile` 因此\n * 在同一时刻翻转,不会留下「树还在桌面、aside 已被 CSS 藏起来」的空档。\n */\n private _syncMobileViewportSubscription() {\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n // jsdom 等没有 matchMedia 的环境没有视口可判:`mobile` 停在 false,订阅与渲染照常。\n this._mobileQuery = typeof window.matchMedia === 'function' ? window.matchMedia(MOBILE_VIEWPORT_QUERY) : null\n this._mobileQuery?.addEventListener('change', this._onMobileQueryChange)\n this._syncMobile(this._mobileQuery?.matches ?? false)\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._syncMobileViewportSubscription()\n void this.updateComplete.then(() => {\n if (this.isConnected) this._syncBannerPresence()\n })\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._resetActiveResize()\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n this._mobileQuery = null\n }\n\n override firstUpdated() {\n this._syncBannerPresence()\n }\n\n private _onBannerSlotChange() {\n this._syncBannerPresence()\n }\n\n private _syncBannerPresence() {\n const bannerSlot = this.renderRoot.querySelector<HTMLSlotElement>('slot[name=\"banner\"]')\n const hasBanner =\n bannerSlot\n ?.assignedNodes()\n .some(\n node =>\n node.nodeType === Node.ELEMENT_NODE || (node.nodeType === Node.TEXT_NODE && !!node.textContent?.trim())\n ) ?? false\n const banner = this.shadowRoot?.querySelector<HTMLElement>('.layout-banner') ?? null\n this._visibleBanner.setTarget(hasBanner ? banner : null)\n }\n\n /*\n * 切到无 sidebar 模式会卸载 aside;先终结进行中的拖拽与未提交的临时宽度,\n * 否则手势悬挂,切回桌面后新拖拽会在 `_isResizing()` 入口被拦下。\n */\n protected override willUpdate(changed: PropertyValues) {\n super.willUpdate(changed)\n if (changed.has('noSidebar') && this.noSidebar) this._resetActiveResize()\n }\n\n override render() {\n const showSidebar = !this.noSidebar\n const isSidebarOpen = !this.sidebarCollapsed\n // 拖拽中的临时宽度优先于 prop 宽度;移动端宽度由 Drawer 内部变量管理。\n const sidebarStyle = this._mobile\n ? {}\n : { width: this._resizeWidth ?? (this.sidebarCollapsed ? this.collapsedWidth : this.sidebarWidth) }\n\n const toggleLabel = resolveBuiltinLabelSet(this.ariaLabels, isSidebarOpen ? 'collapseSidebar' : 'expandSidebar')\n\n const desktopToggle = html`\n <web-ui-button\n class=\"sidebar-toggle\"\n icon\n variant=\"secondary\"\n @click=\"${this._toggleSidebar}\"\n aria-label=${toggleLabel}\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n `\n\n const sidebarViewport = html`\n <div class=\"sidebar-viewport\">\n <slot name=\"sidebar\"></slot>\n </div>\n `\n\n const header = html`\n <header class=\"${classMap({ 'wui-glow': this.headerGlow })}\">\n ${\n this._mobile && showSidebar\n ? html`\n <web-ui-button\n class=\"mobile-toggle\"\n icon\n variant=\"glass\"\n @click=\"${this._toggleSidebar}\"\n aria-label=${resolveBuiltinLabelSet(this.ariaLabels, 'openNavigationMenu')}\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n <slot name=\"header\"></slot>\n </header>\n `\n\n const contentLayout = html`\n <div class=\"layout-content\">\n ${header}\n <main><slot></slot></main>\n <footer><slot name=\"tabbar\"></slot></footer>\n </div>\n `\n\n const desktopLayout = html`\n <div class=\"layout-body\">\n <div class=\"sidebar-wrapper\">\n <aside\n class=\"${classMap({\n collapsed: !isSidebarOpen\n })}\"\n style=\"${styleMap(sidebarStyle)}\"\n >\n <div class=\"aside-panel wui-glass\">\n ${sidebarViewport}\n <div class=\"sidebar-toggle-area\">${desktopToggle}</div>\n </div>\n ${\n this.sidebarResizable && !this.sidebarCollapsed\n ? html`\n <div\n class=\"sidebar-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n aria-label=${resolveBuiltinLabelSet(this.ariaLabels, 'resizeSidebar')}\n @pointerdown=${this._handleResizePointerDown}\n @keydown=${this._handleResizeKeydown}\n ></div>\n `\n : nothing\n }\n </aside>\n </div>\n ${contentLayout}\n </div>\n `\n\n const mobileDrawer = html`\n <web-ui-drawer\n placement=\"left\"\n ?open=\"${this.sidebarOpen}\"\n controlled\n draggable\n aria-dialog-label=${resolveBuiltinLabelSet(this.ariaLabels, 'mainNavigation')}\n @open-change=\"${this._onDrawerChange}\"\n style=\"${styleMap({\n '--wui-drawer-width': this.sidebarWidth,\n '--wui-drawer-radius': 'var(--wui-layout-sidebar-radius, 24px)'\n })}\"\n >\n ${sidebarViewport}\n </web-ui-drawer>\n `\n\n /*\n * 无 sidebar 模式只留内容列:桌面 aside 与移动端 Toggle / Drawer 都不进树,\n * `sidebar` slot 因而没有渲染出口(消费方节点仍留在 light DOM)。\n */\n const layoutBody = showSidebar\n ? this._mobile\n ? html`${contentLayout}${mobileDrawer}`\n : desktopLayout\n : contentLayout\n\n return html`\n <div class=\"layout-page\">\n <div class=\"layout-banner\">\n <slot name=\"banner\" @slotchange=\"${this._onBannerSlotChange}\"></slot>\n </div>\n ${layoutBody}\n </div>\n `\n }\n\n declare readonly $events: {\n 'sidebar-collapsed-change': CustomEvent<{ collapsed: boolean }>\n 'sidebar-open-change': CustomEvent<{ open: boolean }>\n 'sidebar-width-change': CustomEvent<{ width: string }>\n 'mobile-change': CustomEvent<{ mobile: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-layout': WebUiLayout\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["glass","glow","style","icRoundMenuOpen","radixIconsPanelLeftMinimized"],"sources":["../../../src/components/layout/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/drawer'\nimport '@/components/icon'\n\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport glass from '@/classes/glass.css?inline'\nimport glow from '@/classes/glow.css?inline'\nimport { icRoundMenuOpen, radixIconsPanelLeftMinimized } from '@/icons'\nimport { attachDragGesture, clamp, type DragGestureHandle } from '@/shared/gesture'\nimport { resolveBuiltinLabelSet } from '@/shared/locale/builtin-labels'\nimport { defineVisibleAreaTracker, VisibleAreaController } from '@/shared/visible-area'\n\nimport style from './style.css?inline'\n\n/**\n * 移动端断点的媒体查询。\n *\n * 必须与 `style.css` 里那份 `@media (width <= 640px)` 是同一条条件:CSS 用它在窄视口隐藏桌面\n * aside,TS 用同一条件决定渲染哪一套树,两者错开就会出现「树还在桌面、aside 已经被藏起来」\n * 的空档。两份字面量的相等由 `components/drawer/__tests__/breakpoint-parity.spec.ts` 守着。\n */\nconst MOBILE_VIEWPORT_QUERY = '(width <= 640px)'\n\n/**\n * Layout 组件 - 提供页面布局框架,支持 collapsible sidebar 与无 sidebar 模式\n *\n * @fires sidebar-collapsed-change - 桌面端侧边栏 collapse 状态变更请求时触发\n * @fires sidebar-open-change - 移动端侧边栏 Drawer toggle 请求时触发\n * @fires sidebar-width-change - 桌面端侧边栏拖拽调宽结束时触发,携带最终宽度(受控请求)\n * @fires mobile-change - 视口跨越移动端断点、`mobile` 翻转时触发(派生状态通知)\n * @slot banner - 顶部 Banner 区域(全宽,随页面滚动,可选)\n * @slot header - 内容区顶部 header(sticky)\n * @slot sidebar - 侧边栏内容;Consumer 决定其内部固定区域与滚动容器;`no-sidebar` 置位时不渲染\n * @slot tabbar - 底部 tabbar 区域\n * @slot default - 主内容区\n */\n/** `web-ui-layout` 内置无障碍名的成组覆盖;每个 key 对应一处图标控件的 accessible name。 */\nexport interface LayoutAriaLabels {\n collapseSidebar?: string\n expandSidebar?: string\n resizeSidebar?: string\n openNavigationMenu?: string\n mainNavigation?: string\n}\n\n@customElement('web-ui-layout')\nexport class WebUiLayout extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(glow), unsafeCSS(style)]\n\n /**\n * 关闭整块 sidebar:桌面端不渲染 aside 与 collapse Toggle,移动端不渲染 header Toggle 与 Drawer。\n *\n * 它是布局模式而不是 collapse 的别名:`sidebar-collapsed` 只收窄桌面 aside,本属性把侧边栏\n * 结构本身从树里去掉。`sidebar` slot 随之不渲染(消费方节点仍留在 light DOM),\n * 侧边栏相关事件与属性在该模式下不产生可见效果。\n */\n @property({ type: Boolean, attribute: 'no-sidebar', reflect: true })\n noSidebar = false\n\n /**\n * 启用桌面端 Sidebar 的 collapse 入口。\n *\n * 只有声明了本属性,layout 才渲染收起 / 展开 Toggle,也才会因为点击而派发\n * `sidebar-collapsed-change`。桌面宽度仍由受控属性 `sidebar-collapsed` 决定,\n * 未声明本属性不会阻止它收窄;移动端 Drawer 的 header Toggle 不受影响。\n */\n @property({ type: Boolean, attribute: 'sidebar-collapsible', reflect: true })\n sidebarCollapsible = false\n\n /**\n * 桌面端 Sidebar 的持久布局偏好。\n *\n * `true` 只会将桌面 Sidebar 收窄到 `collapsedWidth`,不会影响移动端 Drawer。\n * 它不与 `sidebarOpen` 合并:前者是跨视口保留的密度选择,后者是移动端瞬时的 modal 可见性。\n *\n * 两处渲染的是同一份 `sidebar` slot 内容,所以本属性只负责桌面 aside 的几何:Consumer\n * 若让自己的 slot 内容跟着它变形态(隐藏 label、改排布),必须按 `mobile` 收窄那个条件\n * (例如 `collapsed && !layout.mobile`,或 CSS 的 `web-ui-layout[mobile]`),\n * 否则 collapsed state 会一路渲染进移动端 Drawer(issue #195)。\n */\n @property({ type: Boolean, attribute: 'sidebar-collapsed', reflect: true })\n sidebarCollapsed = false\n\n @property({ type: String, attribute: 'sidebar-width', reflect: true })\n sidebarWidth = '240px'\n\n @property({ type: String, attribute: 'collapsed-width', reflect: true })\n collapsedWidth = '60px'\n\n /** 启用桌面端 Sidebar 拖拽调宽(collapsed state 隐藏 handle)。 */\n @property({ type: Boolean, attribute: 'sidebar-resizable', reflect: true })\n sidebarResizable = false\n\n /**\n * 拖拽调宽的下限(px,其他单位不解析);未设置时回退为 `collapsedWidth`,\n * 防止拖到不可用宽度。上限 `sidebar-max-width` 受内置 50vw 硬上限钳制。\n */\n @property({ type: String, attribute: 'sidebar-min-width', reflect: true })\n sidebarMinWidth = ''\n\n /** 拖拽调宽的上限(px,其他单位不解析);始终受内置 50vw 硬上限钳制。 */\n @property({ type: String, attribute: 'sidebar-max-width', reflect: true })\n sidebarMaxWidth = ''\n\n /**\n * 移动端 web-ui-drawer 的瞬时可见性。\n *\n * Drawer Toggle、Escape 和 backdrop 关闭都通过 `sidebar-open-change` 请求更新它;它不会改变桌面端的 collapse preference。\n * 因而不与 `sidebarCollapsed` 合并,避免响应式切换时将一次移动端交互误写为桌面端布局选择。\n */\n @property({ type: Boolean, attribute: 'sidebar-open', reflect: true })\n sidebarOpen = false\n\n @property({ type: Boolean, attribute: 'header-glow', reflect: true })\n headerGlow = false\n\n /** 内置无障碍名的成组覆盖;未提供的 key 回退内置英文文案(见 ADR-0016)。 */\n @property({ attribute: false }) ariaLabels?: LayoutAriaLabels\n\n /*\n * `mobile` 是派生输出,不是第二个输入:像 `web-ui-theme` 的 `resolved-appearance` 一样由组件\n * 独占写入,读取取 `MOBILE_VIEWPORT_QUERY`。外部写 attribute 会在同一次 reaction 内被恢复,\n * 写 property 不生效(只读 accessor,严格模式下抛错)——两种写法都不构成本属性之外的\n * 第三个视口来源。\n */\n @property({ type: Boolean, attribute: 'mobile', reflect: true })\n private _mobile = false\n\n /**\n * 当前视口是否落在移动端分支(即 `MOBILE_VIEWPORT_QUERY` 命中)。\n *\n * 与组件渲染哪一套树、以及 `mobile-change` 的派发是同一个值:翻转瞬间三者一起变,\n * Consumer 不必自己写断点判断,也不会与 layout 内部的树切换错开半拍。派生状态只读。\n */\n get mobile(): boolean {\n return this._mobile\n }\n\n private _mobileQuery: MediaQueryList | null = null\n\n private readonly _onMobileQueryChange = (event: MediaQueryListEvent) => {\n this._syncMobile(event.matches)\n }\n\n /*\n * 本方法由媒体查询翻转驱动,也被连接时的初次求值调用;attribute 的恢复路径见\n * `attributeChangedCallback`,那里不改判、也不派发通知,所以 `mobile-change` 只在\n * 视口真正跨断点时出现一次(窄视口挂载时那次算第一次)。\n */\n private _syncMobile(next: boolean) {\n if (next === this._mobile) return\n this._mobile = next\n // 跨断点会卸载桌面 layout(resize handle 随之消失);必须先终结进行中的拖拽,\n // 否则手势悬挂,切回桌面后所有新拖拽都在入口被 `_isResizing()` 拦下。\n if (this._isResizing()) this._resetActiveResize()\n this.toggleAttribute('mobile', next)\n this.dispatchEvent(new CustomEvent('mobile-change', { detail: { mobile: next }, bubbles: true, composed: true }))\n }\n\n override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n super.attributeChangedCallback(name, oldValue, newValue)\n // super 已把外部值写进 backing field,所以这里要按媒体查询重算,不能拿字段当真相。\n if (name !== 'mobile') return\n // 无 matchMedia 的环境没有视口可判,`mobile` 恒为 false(见 connectedCallback)。\n const derived = this._mobileQuery?.matches ?? false\n if (this._mobile !== derived) this._mobile = derived\n /*\n * 布尔 attribute 只论存在性,但值本身也要归位:`setAttribute('mobile', 'false')` 在移动端\n * 会让 backing field 与 attribute 的**存在性**一致、值却留在宿主上(`getAttribute` 会回读\n * 到 consumer 写的那串)。组件写的形态是空串,这里按同一形态恢复,主题的\n * `resolved-appearance` 先例同理。\n */\n if (derived) {\n if (this.getAttribute('mobile') !== '') this.setAttribute('mobile', '')\n return\n }\n if (this.hasAttribute('mobile')) this.removeAttribute('mobile')\n }\n\n private readonly _visibleBanner = defineVisibleAreaTracker({\n onVisibleAreaChange: area => {\n this.style.setProperty('--wui-layout-visible-banner-height', `${area.height}px`)\n }\n }).make()\n\n private readonly _visibleBannerController = new VisibleAreaController(this, this._visibleBanner)\n\n private _toggleSidebar() {\n if (this._mobile) {\n this._emitSidebarOpenChange(!this.sidebarOpen)\n return\n }\n\n this._emitSidebarCollapsedChange(!this.sidebarCollapsed)\n }\n\n private _emitSidebarCollapsedChange(collapsed: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-collapsed-change', {\n detail: { collapsed },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _emitSidebarOpenChange(open: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-open-change', {\n detail: { open },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onDrawerChange(e: CustomEvent<{ open: boolean }>) {\n e.stopPropagation()\n this._emitSidebarOpenChange(e.detail.open)\n }\n\n private _resizeGesture: DragGestureHandle | null = null\n private _resizeStartWidth = 0\n // 拖拽中的临时宽度;由 render 的 styleMap 统一写入,避免与 Lit 样式管理竞争。\n @state() private _resizeWidth: string | null = null\n\n private _parsePx(value: string, fallback: number): number {\n const parsed = Number.parseFloat(value)\n return Number.isFinite(parsed) ? parsed : fallback\n }\n\n private _resolveSidebarMinWidth(): number {\n return this.sidebarMinWidth\n ? this._parsePx(this.sidebarMinWidth, this._parsePx(this.collapsedWidth, 68))\n : this._parsePx(this.collapsedWidth, 68)\n }\n\n private _resolveSidebarMaxWidth(): number {\n // 组件内置硬上限:Sidebar 最多占视口一半(无需调用方设置)。\n // 调用方 sidebar-max-width 在该上限内生效,防止拖拽占据整个视口。\n const halfViewport = Math.floor(window.innerWidth / 2)\n const configured = this.sidebarMaxWidth ? this._parsePx(this.sidebarMaxWidth, halfViewport) : halfViewport\n const upper = Math.min(configured, halfViewport)\n return Math.max(upper, this._resolveSidebarMinWidth())\n }\n\n private _isResizing(): boolean {\n return this._resizeGesture?.isDragging() ?? false\n }\n\n private _handleResizePointerDown(e: PointerEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n // jsdom 等环境的 PointerEvent 可能缺失 isPrimary,仅在明确为 false(多点触控副指针)时拒绝。\n if (e.isPrimary === false) return\n\n const aside = this.renderRoot.querySelector('aside')\n if (!aside) return\n\n // 拖拽起点以 computed width 为准,并丢弃键盘未提交的调整(Enter 才提交、\n // Escape 撤回;抓取隐式放弃),保证零位移松手不会派发陈旧宽度。\n this._resizeStartWidth = this._parsePx(window.getComputedStyle(aside).width, this._parsePx(this.sidebarWidth, 240))\n this._resizeWidth = null\n aside.classList.add('is-resizing')\n\n this._resizeGesture = attachDragGesture(e, {\n axis: 'x',\n onMove: info => {\n // 向右拖增宽、向左拖收窄(handle 在右缘);钳制在 [min, max] 与视口内。\n const next = this._resizeStartWidth + info.deltaX\n const clamped = clamp(next, this._resolveSidebarMinWidth(), this._resolveSidebarMaxWidth())\n this._resizeWidth = `${Math.round(clamped)}px`\n },\n onEnd: () => {\n // 零位移松手(点击而非拖拽)不视为调宽动作:按 cancel 语义静默收尾。\n if (this._resizeWidth === null) {\n this._resetActiveResize()\n return\n }\n const width = this._resizeWidth\n this._resetActiveResize()\n // 受控契约:松手派发请求并交还 prop 管辖,由 Consumer 回写 sidebar-width。\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', {\n detail: { width },\n bubbles: true,\n composed: true\n })\n )\n },\n onCancel: () => {\n this._resetActiveResize()\n }\n })\n }\n\n // 终结进行中的拖拽:清手势状态、移除跟手 class,宽度过渡回 prop 管辖,不派发事件。\n // pointer cancel、跨越移动端断点和切入 no-sidebar 时共用此收尾。\n private _resetActiveResize() {\n this._resizeGesture?.destroy()\n this._resizeGesture = null\n this.renderRoot.querySelector('aside')?.classList.remove('is-resizing')\n this._resizeWidth = null\n }\n\n // 键盘调宽(WAI-ARIA splitter 模式):方向键步进 16px(Shift 加速 64px)调整\n // 临时宽度,Enter 以受控请求派发 `sidebar-width-change`(与指针松手同语义),\n // Escape 撤回临时宽度交还 prop 管辖,Home/End 直接到 min/max。\n private _handleResizeKeydown(e: KeyboardEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n\n const min = this._resolveSidebarMinWidth()\n const max = this._resolveSidebarMaxWidth()\n const aside = this.renderRoot.querySelector('aside')\n const current = this._resizeWidth\n ? this._parsePx(this._resizeWidth, min)\n : this._parsePx(aside ? window.getComputedStyle(aside).width : this.sidebarWidth, min)\n const step = e.shiftKey ? 64 : 16\n let next: number | null = null\n\n switch (e.key) {\n case 'ArrowLeft':\n next = Math.max(min, current - step)\n break\n case 'ArrowRight':\n next = Math.min(max, current + step)\n break\n case 'Home':\n next = min\n break\n case 'End':\n next = max\n break\n case 'Enter':\n // 无未提交调整时不派发(焦点停留时的回车噪声)\n if (this._resizeWidth !== null) {\n const width = this._resizeWidth\n this._resizeWidth = null\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', { detail: { width }, bubbles: true, composed: true })\n )\n }\n e.preventDefault()\n return\n case 'Escape':\n this._resizeWidth = null\n e.preventDefault()\n return\n default:\n return\n }\n e.preventDefault()\n this._resizeWidth = `${Math.round(next)}px`\n }\n\n /*\n * 视口判定订阅媒体查询,而不是监听 window resize:跨断点每次只派发一次 change,没有\n * resize 事件流要挡。判定、CSS 的 `@media (width <= 640px)` 与对外暴露的 `mobile` 因此\n * 在同一时刻翻转,不会留下「树还在桌面、aside 已被 CSS 藏起来」的空档。\n */\n private _syncMobileViewportSubscription() {\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n // jsdom 等没有 matchMedia 的环境没有视口可判:`mobile` 停在 false,订阅与渲染照常。\n this._mobileQuery = typeof window.matchMedia === 'function' ? window.matchMedia(MOBILE_VIEWPORT_QUERY) : null\n this._mobileQuery?.addEventListener('change', this._onMobileQueryChange)\n this._syncMobile(this._mobileQuery?.matches ?? false)\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._syncMobileViewportSubscription()\n void this.updateComplete.then(() => {\n if (this.isConnected) this._syncBannerPresence()\n })\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._resetActiveResize()\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n this._mobileQuery = null\n }\n\n override firstUpdated() {\n this._syncBannerPresence()\n }\n\n private _onBannerSlotChange() {\n this._syncBannerPresence()\n }\n\n private _syncBannerPresence() {\n const bannerSlot = this.renderRoot.querySelector<HTMLSlotElement>('slot[name=\"banner\"]')\n const hasBanner =\n bannerSlot\n ?.assignedNodes()\n .some(\n node =>\n node.nodeType === Node.ELEMENT_NODE || (node.nodeType === Node.TEXT_NODE && !!node.textContent?.trim())\n ) ?? false\n const banner = this.shadowRoot?.querySelector<HTMLElement>('.layout-banner') ?? null\n this._visibleBanner.setTarget(hasBanner ? banner : null)\n }\n\n /*\n * 切到无 sidebar 模式会卸载 aside;先终结进行中的拖拽与未提交的临时宽度,\n * 否则手势悬挂,切回桌面后新拖拽会在 `_isResizing()` 入口被拦下。\n */\n protected override willUpdate(changed: PropertyValues) {\n super.willUpdate(changed)\n if (changed.has('noSidebar') && this.noSidebar) this._resetActiveResize()\n }\n\n override render() {\n const showSidebar = !this.noSidebar\n const isSidebarOpen = !this.sidebarCollapsed\n // 拖拽中的临时宽度优先于 prop 宽度;移动端宽度由 Drawer 内部变量管理。\n const sidebarStyle = this._mobile\n ? {}\n : { width: this._resizeWidth ?? (this.sidebarCollapsed ? this.collapsedWidth : this.sidebarWidth) }\n\n const toggleLabel = resolveBuiltinLabelSet(this.ariaLabels, isSidebarOpen ? 'collapseSidebar' : 'expandSidebar')\n\n const desktopToggle = this.sidebarCollapsible\n ? html`\n <web-ui-button\n class=\"sidebar-toggle\"\n icon\n variant=\"ghost\"\n @click=\"${this._toggleSidebar}\"\n aria-label=${toggleLabel}\n >\n <web-ui-icon\n .icon=${icRoundMenuOpen}\n style=${styleMap({ transform: isSidebarOpen ? 'none' : 'scaleX(-1)' })}\n ></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n\n const sidebarViewport = html`\n <div class=\"sidebar-viewport\">\n <slot name=\"sidebar\"></slot>\n </div>\n `\n\n const header = html`\n <header class=\"${classMap({ 'wui-glow': this.headerGlow })}\">\n ${\n this._mobile && showSidebar\n ? html`\n <web-ui-button\n class=\"mobile-toggle\"\n icon\n variant=\"glass\"\n @click=\"${this._toggleSidebar}\"\n aria-label=${resolveBuiltinLabelSet(this.ariaLabels, 'openNavigationMenu')}\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n <slot name=\"header\"></slot>\n </header>\n `\n\n const contentLayout = html`\n <div class=\"layout-content\">\n ${header}\n <main><slot></slot></main>\n <footer><slot name=\"tabbar\"></slot></footer>\n </div>\n `\n\n const desktopLayout = html`\n <div class=\"layout-body\">\n <div class=\"sidebar-wrapper\">\n <aside\n class=\"${classMap({\n collapsed: !isSidebarOpen\n })}\"\n style=\"${styleMap(sidebarStyle)}\"\n >\n <div class=\"aside-panel wui-glass\">\n ${sidebarViewport}\n ${this.sidebarCollapsible ? html`<div class=\"sidebar-toggle-area\">${desktopToggle}</div>` : nothing}\n </div>\n ${\n this.sidebarResizable && !this.sidebarCollapsed\n ? html`\n <div\n class=\"sidebar-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n aria-label=${resolveBuiltinLabelSet(this.ariaLabels, 'resizeSidebar')}\n @pointerdown=${this._handleResizePointerDown}\n @keydown=${this._handleResizeKeydown}\n ></div>\n `\n : nothing\n }\n </aside>\n </div>\n ${contentLayout}\n </div>\n `\n\n const mobileDrawer = html`\n <web-ui-drawer\n placement=\"left\"\n ?open=\"${this.sidebarOpen}\"\n controlled\n draggable\n page-shift\n aria-dialog-label=${resolveBuiltinLabelSet(this.ariaLabels, 'mainNavigation')}\n @open-change=\"${this._onDrawerChange}\"\n style=\"${styleMap({\n '--wui-drawer-width': this.sidebarWidth,\n '--wui-drawer-radius': 'var(--wui-layout-sidebar-radius, 24px)'\n })}\"\n >\n ${sidebarViewport}\n </web-ui-drawer>\n `\n\n /*\n * 无 sidebar 模式只留内容列:桌面 aside 与移动端 Toggle / Drawer 都不进树,\n * `sidebar` slot 因而没有渲染出口(消费方节点仍留在 light DOM)。\n */\n const layoutBody = showSidebar\n ? this._mobile\n ? html`${contentLayout}${mobileDrawer}`\n : desktopLayout\n : contentLayout\n\n return html`\n <div class=\"layout-page\">\n <div class=\"layout-banner\">\n <slot name=\"banner\" @slotchange=\"${this._onBannerSlotChange}\"></slot>\n </div>\n ${layoutBody}\n </div>\n `\n }\n\n declare readonly $events: {\n 'sidebar-collapsed-change': CustomEvent<{ collapsed: boolean }>\n 'sidebar-open-change': CustomEvent<{ open: boolean }>\n 'sidebar-width-change': CustomEvent<{ width: string }>\n 'mobile-change': CustomEvent<{ mobile: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-layout': WebUiLayout\n }\n}\n"],"mappings":"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"}
|