iter(g239): 焦点描边令牌化,outline 收敛为三档复合令牌并纳入审计第十四类
This commit is contained in:
@@ -125,6 +125,9 @@
|
||||
--ua-focus-ring-danger: 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 18%, transparent); /* 错误强调环:校验失败时叠在被标红控件外侧,比 danger 描边更外扩一圈 */
|
||||
--ua-focus-ring-pulse: 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 4%, transparent); /* 呼吸光晕档:与焦点环交替出现做脉冲动画(时间线条目) */
|
||||
--ua-focus-ring-selected: 0 0 0 1.5px var(--ua-color-accent-soft); /* 选中环(非焦点):细一档 + 柔和色,2px 实色在色板/网格里像加粗边框 */
|
||||
--ua-focus-outline: 2px solid var(--ua-color-accent); /* 外焦点描边(outline 路线):键盘可见性标准档 */
|
||||
--ua-focus-outline-inset: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点描边:密集列表细一档 + 柔和色,2px 实线像粗边框 */
|
||||
--ua-focus-outline-soft: 2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent); /* 柔和外描边:大面积控件用淡化主题色 */
|
||||
|
||||
/* ---- 控件高度:三档,略放大以提升可点面积 ---- */
|
||||
--ua-control-h-sm: 28px; /* 小控件高度:紧凑工具栏、标签内嵌按钮 */
|
||||
@@ -182,4 +185,7 @@
|
||||
--ua-focus-ring-danger: 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 30%, transparent); /* 暗色错误强调环 */
|
||||
--ua-focus-ring-pulse: 0 0 0 5px color-mix(in srgb, var(--ua-color-accent) 8%, transparent); /* 暗色呼吸光晕:暗底上低透明度看不见,加倍 */
|
||||
--ua-focus-ring-selected: 0 0 0 1.5px var(--ua-color-accent-soft); /* 暗色选中环:随 accent 翻转并与亮色同口径 */
|
||||
--ua-focus-outline: 2px solid var(--ua-color-accent); /* 暗色外焦点描边:随 accent 翻转,与亮色同口径 */
|
||||
--ua-focus-outline-inset: 1.5px solid var(--ua-color-accent-soft); /* 暗色内嵌焦点描边:accent-soft 随底色重混 */
|
||||
--ua-focus-outline-soft: 2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent); /* 暗色柔和外描边:浓度档与亮色一致 */
|
||||
}
|
||||
|
||||
@@ -246,6 +246,18 @@
|
||||
"ringSelected": {
|
||||
"light": "0 0 0 2px var(--ua-color-accent)",
|
||||
"dark": "0 0 0 2px var(--ua-color-accent)"
|
||||
},
|
||||
"outline": {
|
||||
"light": "2px solid var(--ua-color-accent)",
|
||||
"dark": "2px solid var(--ua-color-accent)"
|
||||
},
|
||||
"outlineInset": {
|
||||
"light": "1.5px solid var(--ua-color-accent-soft)",
|
||||
"dark": "1.5px solid var(--ua-color-accent-soft)"
|
||||
},
|
||||
"outlineSoft": {
|
||||
"light": "2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent)",
|
||||
"dark": "2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent)"
|
||||
}
|
||||
},
|
||||
"control": {
|
||||
|
||||
+20
-20
@@ -3,7 +3,7 @@
|
||||
/* —— 键盘焦点可见性:组件普遍用 outline:none 自绘,这里统一兜底,避免键盘用户丢失焦点指示 —— */
|
||||
[class^='ua-']:focus-visible,
|
||||
[class*=' ua-']:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
/* —— 控件高度三档:所有可交互控件统一走 --ua-control-h-*,size 档只改高度 —— */
|
||||
@@ -485,7 +485,7 @@ body.ua-col-resizing {
|
||||
但两者的密集列表换成内嵌描边后更整齐;与 cascader 项保持同一口径 */
|
||||
.ua-option:focus-visible,
|
||||
.ua-menu-item:focus-visible {
|
||||
outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline: var(--ua-focus-outline-inset); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline-offset: -1.5px;
|
||||
}
|
||||
/* 选中态:低视觉比重——淡色底 + 弱化文字,不抢焦点 */
|
||||
@@ -884,7 +884,7 @@ body.ua-col-resizing {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
.ua-tag.is-checkable:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent);
|
||||
outline: var(--ua-focus-outline-soft);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.ua-tag.is-checked {
|
||||
@@ -1138,7 +1138,7 @@ button.ua-step-dot:hover {
|
||||
gap: var(--ua-space-05);
|
||||
}
|
||||
.ua-rate:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent);
|
||||
outline: var(--ua-focus-outline-soft);
|
||||
outline-offset: 3px;
|
||||
border-radius: var(--ua-radius-sm);
|
||||
}
|
||||
@@ -1676,7 +1676,7 @@ button.ua-step-dot:hover {
|
||||
.ua-tab:focus-visible {
|
||||
/* 页签条是 overflow-x:auto 的滚动容器,外偏移焦点环会被裁掉,
|
||||
这里改用与 seg-btn 一致的内嵌描边,键盘用户才真看得见焦点 */
|
||||
outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline: var(--ua-focus-outline-inset); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline-offset: -1.5px;
|
||||
color: var(--ua-color-accent);
|
||||
}
|
||||
@@ -1717,7 +1717,7 @@ button.ua-step-dot:hover {
|
||||
}
|
||||
.ua-tab-close:focus-visible {
|
||||
opacity: 1;
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.ua-tab-close:hover {
|
||||
@@ -2441,7 +2441,7 @@ button.ua-step-dot:hover {
|
||||
color: var(--ua-color-text);
|
||||
}
|
||||
.ua-seg-btn:focus-visible {
|
||||
outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline: var(--ua-focus-outline-inset); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline-offset: -1.5px;
|
||||
}
|
||||
.ua-seg-btn.is-active {
|
||||
@@ -3250,7 +3250,7 @@ button.ua-step-dot:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.ua-cascader-opt:focus-visible {
|
||||
outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline: var(--ua-focus-outline-inset); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline-offset: -1.5px;
|
||||
}
|
||||
.ua-cascader-arrow {
|
||||
@@ -3305,7 +3305,7 @@ button.ua-step-dot:hover {
|
||||
background: var(--ua-color-accent);
|
||||
}
|
||||
.ua-splitter-bar:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -3526,7 +3526,7 @@ button.ua-step-dot:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.ua-datepicker-day:focus-visible {
|
||||
outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline: var(--ua-focus-outline-inset); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline-offset: -1.5px;
|
||||
}
|
||||
.ua-datepicker-foot {
|
||||
@@ -3603,7 +3603,7 @@ button.ua-step-dot:hover {
|
||||
transform: translateY(calc(-1 * var(--ua-translate-2)));
|
||||
}
|
||||
.ua-backtop:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.ua-backtop-ring {
|
||||
@@ -4091,7 +4091,7 @@ button.ua-step-dot:hover {
|
||||
pointer-events: none;
|
||||
}
|
||||
.ua-anchor-link:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -4160,7 +4160,7 @@ button.ua-step-dot:hover {
|
||||
background: var(--ua-color-tint-accent);
|
||||
}
|
||||
.ua-select-clear:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.ua-select-clear[hidden] {
|
||||
@@ -4183,7 +4183,7 @@ button.ua-step-dot:hover {
|
||||
background: var(--ua-color-tint-accent-weak);
|
||||
}
|
||||
.ua-crumb-fold:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
@@ -4205,7 +4205,7 @@ button.ua-step-dot:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ua-toast-action:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@@ -4245,7 +4245,7 @@ button.ua-step-dot:hover {
|
||||
background: var(--ua-color-tint-accent-weak);
|
||||
}
|
||||
.ua-calendar-nav:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.ua-calendar-week,
|
||||
@@ -4292,7 +4292,7 @@ button.ua-step-dot:hover {
|
||||
transform: none;
|
||||
}
|
||||
.ua-calendar-day:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.ua-calendar-mark {
|
||||
@@ -4475,7 +4475,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
|
||||
transform: translateY(0) scale(0.97);
|
||||
}
|
||||
.ua-fab:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.ua-fab-icon {
|
||||
@@ -4677,7 +4677,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ua-link:focus-visible {
|
||||
outline: 2px solid var(--ua-color-accent);
|
||||
outline: var(--ua-focus-outline);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--ua-radius-sm);
|
||||
}
|
||||
@@ -4730,7 +4730,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
|
||||
background: var(--ua-color-tint-accent-weak);
|
||||
}
|
||||
.ua-row-clickable:focus-visible {
|
||||
outline: 1.5px solid var(--ua-color-accent-soft); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline: var(--ua-focus-outline-inset); /* 内嵌焦点环:细一档 + 柔和色,2px 实色在密集列表里像粗边框 */
|
||||
outline-offset: -1.5px; /* 环画在行内,避免被表格滚动容器裁掉 */
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,11 @@ export const KIND_META = [
|
||||
label: '位移字面值',
|
||||
hint: 'transform 的 translate 写死 px 位移,应改用 --ua-translate-* 档位(负号用 calc(-1 * var(...)),百分比居中豁免)',
|
||||
},
|
||||
{
|
||||
key: 'outline-literal',
|
||||
label: '焦点描边字面值',
|
||||
hint: 'outline 写死宽度与色值,应改用 --ua-focus-outline(_inset/_soft) 三档(outline: none 豁免)',
|
||||
},
|
||||
];
|
||||
|
||||
/*** 作用:需要扫描的 CSS 属性白名单(其余属性本轮不动;用白名单顺带避开伪类选择器误判) ***/
|
||||
@@ -485,6 +490,20 @@ function migrateLeading(core) {
|
||||
return { kind: 'leading-literal', value: token };
|
||||
}
|
||||
|
||||
/*** 作用:outline 焦点描边字面值 → 三档复合令牌(none 是「移除描边」语义不入表;outline-offset 是逐控件几何微调,不走此表) ***/
|
||||
const OUTLINE_TOKENS = new Map([
|
||||
['2px solid var(--ua-color-accent)', 'var(--ua-focus-outline)'],
|
||||
['1.5px solid var(--ua-color-accent-soft)', 'var(--ua-focus-outline-inset)'],
|
||||
['2px solid color-mix(in srgb, var(--ua-color-accent) var(--ua-alpha-mid), transparent)', 'var(--ua-focus-outline-soft)'],
|
||||
]);
|
||||
|
||||
/*** 作用:迁移单条 outline:整值命中三档即换令牌,其余写法留给门禁拦截 ***/
|
||||
function migrateOutline(core) {
|
||||
const token = OUTLINE_TOKENS.get(squash(core));
|
||||
if (!token) return null;
|
||||
return { kind: 'outline-literal', value: token };
|
||||
}
|
||||
|
||||
/*** 作用:迁移单条 transform:px 位移逐个换 --ua-translate-* 档位,负号转 calc(-1 * var(...));百分比居中与 0 天然不入表 ***/
|
||||
function migrateTranslate(core) {
|
||||
let hits = 0;
|
||||
@@ -540,6 +559,8 @@ export function migrateDeclaration(prop, rawValue) {
|
||||
result = migrateZ(core);
|
||||
} else if (prop === 'transform') {
|
||||
result = migrateTranslate(core); // 位移量走独立档位,不与内距梯子混用
|
||||
} else if (prop === 'outline') {
|
||||
result = migrateOutline(core); // 焦点描边整值收敛三档
|
||||
} else {
|
||||
result = migrateMix(core) ?? migrateBorder(prop, core); // 其余属性:先收敛 color-mix 浓度,再收敛纯黑 rgba 边
|
||||
}
|
||||
@@ -623,6 +644,11 @@ function detect(prop, rawValue, key) {
|
||||
const stripped = value.replace(/var\(--ua-translate-[\w-]+\)/g, ' '); // 已令牌化的位移先剔除
|
||||
return (stripped.match(/-?\d+(\.\d+)?px\b/gi) ?? []).length; // 百分比(居中/轨道)与 0 不算漂移
|
||||
}
|
||||
if (key === 'outline-literal') {
|
||||
if (prop !== 'outline') return 0;
|
||||
if (value === 'none') return 0; // 移除描边是语义写法,不算漂移
|
||||
return /^var\(/.test(value) ? 0 : /\d+(\.\d+)?px\b/.test(value) ? 1 : 0; // 整值已走令牌不算漂移
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user