dsh-chef 0.3.39 → 0.3.40

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/dist/client.js CHANGED
@@ -49,8 +49,19 @@ window.__ModuleLoader__.load({
49
49
  3e3,
50
50
  8e3
51
51
  ];
52
- /** 复制那一枚按钮就地变「已复制」之后,多久变回「复制」(定稿 v3 第三条:1.5 秒,不弹提示)。 */
53
- const COPY_FEEDBACK_MS = 1500;
52
+ /** T1 交互 tokens(#1160,唯一定义地,真相源 #1159 参数表)。
53
+ *
54
+ * 按压 .97、焦点隔离 2px+主题色 5px、过渡 .15s——取值逐字照原型 V1
55
+ *(`.scratch/1155-real-panel-prototype.html`:`btn:active{transform:scale(.97)}`、
56
+ * `btn:focus-visible{box-shadow:0 0 0 2px #0b0b0d,0 0 0 5px var(--focus)}`、
57
+ * 主题橙 #f6ad55/蓝 #0a84ff/绿 #30d158、过渡 background/transform/box-shadow/border-color 各 .15s)。
58
+ *
59
+ * 颜色全走主题变量、永不写死蓝:隔离环取卡面底(深浅自适应),焦点环取
60
+ * `--ilife-focus`(缺席时退到宿主品牌色,宿主品牌色本身跟主题走)。
61
+ * 悬停/长按/折叠/复制变勾/提示/液态归后票,本件只定按压与焦点四格。 */
62
+ const PRESS_SCALE = .97;
63
+ const INTERACTION_TRANSITION = "background .15s ease,transform .15s ease,box-shadow .15s ease,border-color .15s ease";
64
+ const DISABLED_OPACITY = .4;
54
65
  /** 宿主给「这是哪一种目录选择能力」时用的那两格原语的名字(软依赖:按名字现取,缺席即没有入口)。 */
55
66
  const REMOTE_DIRECTORY_PICKER = "remote.directoryPicker";
56
67
  /** 宿主在「这条路不给」时回的那个码(`directory-browser-state` 的 `openRowBrowser` 只照比不认名)。 */
@@ -630,6 +641,11 @@ window.__ModuleLoader__.load({
630
641
  if (state.listing === null) return [];
631
642
  return filterEntries(visibleEntries(state.listing, state.showHidden), state.filter);
632
643
  }
644
+ /** 图上这一层能不能回上一级(根目录不能)。 */
645
+ function canGoUp(state) {
646
+ if (state.listing === null) return false;
647
+ return parentOf(state.listing.path) !== state.listing.path;
648
+ }
633
649
  /** 「打开」这颗按钮的目标:选中行优先,没选中就是当前层。 */
634
650
  function targetOf(state) {
635
651
  return state.selected ?? state.listing?.path ?? null;
@@ -890,6 +906,8 @@ window.__ModuleLoader__.load({
890
906
  fontWeight: isCurrent ? 700 : 400
891
907
  },
892
908
  title: crumb.path,
909
+ "data-ilife-press": "dialog-crumb",
910
+ "data-ilife-longpress": "dialog-crumb",
893
911
  onClick: () => onEnter(crumb.path)
894
912
  }, crumb.name));
895
913
  });
@@ -907,6 +925,8 @@ window.__ModuleLoader__.load({
907
925
  ...S$3.rootButton
908
926
  },
909
927
  title: ROOT_KIND_TEXT[root.kind] ?? ROOT_KIND_TEXT.other,
928
+ "data-ilife-press": "dialog-root",
929
+ "data-ilife-longpress": "dialog-root",
910
930
  onClick: () => onEnter(root.path)
911
931
  }, root.path));
912
932
  return react.createElement("div", { style: S$3.roots }, parts);
@@ -927,6 +947,8 @@ window.__ModuleLoader__.load({
927
947
  flex: 1,
928
948
  border: "none"
929
949
  },
950
+ "data-ilife-press": "dialog-entry",
951
+ "data-ilife-longpress": "dialog-entry",
930
952
  onClick: () => onEnter(entry.path)
931
953
  }, "📁 " + entry.name), react.createElement("button", {
932
954
  type: "button",
@@ -937,12 +959,15 @@ window.__ModuleLoader__.load({
937
959
  },
938
960
  "aria-pressed": selected,
939
961
  title: selected ? labels.selected : labels.select,
962
+ "data-ilife-press": "dialog-select",
963
+ "data-ilife-longpress": "dialog-select",
940
964
  onClick: () => onToggleSelect(entry.path)
941
965
  }, selected ? "✓" : labels.select));
942
966
  }
943
967
  /** 「新建文件夹」那一行(`creatingName === null` 时收起)。 */
944
968
  function createRow(labels, onName, onCancel, onCreate, creatingName) {
945
969
  if (creatingName === null) return null;
970
+ const createEmpty = creatingName.trim() === "";
946
971
  return react.createElement("div", { style: {
947
972
  ...S$3.pathRow,
948
973
  borderTop: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))"
@@ -951,6 +976,7 @@ window.__ModuleLoader__.load({
951
976
  value: creatingName,
952
977
  placeholder: labels.newFolder,
953
978
  "aria-label": labels.newFolder,
979
+ "data-ilife-focus": "dialog-input",
954
980
  onChange: (event) => onName(event.currentTarget.value),
955
981
  onKeyDown: (event) => {
956
982
  if (event.key === "Enter") onCreate(creatingName);
@@ -959,10 +985,15 @@ window.__ModuleLoader__.load({
959
985
  }), react.createElement("button", {
960
986
  type: "button",
961
987
  style: S$3.button,
988
+ disabled: createEmpty,
989
+ "data-ilife-press": "dialog-create",
990
+ "data-ilife-longpress": "dialog-create",
962
991
  onClick: () => onCreate(creatingName)
963
992
  }, labels.createConfirm), react.createElement("button", {
964
993
  type: "button",
965
994
  style: S$3.button,
995
+ "data-ilife-press": "dialog-create-cancel",
996
+ "data-ilife-longpress": "dialog-create-cancel",
966
997
  onClick: onCancel
967
998
  }, labels.createCancel));
968
999
  }
@@ -988,6 +1019,9 @@ window.__ModuleLoader__.load({
988
1019
  const rows = rowsOf(state);
989
1020
  const target = targetOf(state);
990
1021
  const hidden = hiddenCount(state.listing ?? EMPTY_LISTING);
1022
+ const upOff = !canGoUp(state);
1023
+ const goOff = state.draft.trim() === "";
1024
+ const pickOff = target === null;
991
1025
  const body = [];
992
1026
  body.push(crumbRow(state, props.onEnter));
993
1027
  body.push(rootsRow(state, props.onEnter));
@@ -996,6 +1030,7 @@ window.__ModuleLoader__.load({
996
1030
  value: state.draft,
997
1031
  placeholder: labels.pathPlaceholder,
998
1032
  "aria-label": labels.pathPlaceholder,
1033
+ "data-ilife-focus": "dialog-input",
999
1034
  onChange: (event) => props.onDraft(event.currentTarget.value),
1000
1035
  onKeyDown: (event) => {
1001
1036
  if (event.key === "Enter") props.onCommitDraft();
@@ -1003,10 +1038,16 @@ window.__ModuleLoader__.load({
1003
1038
  }), react.createElement("button", {
1004
1039
  type: "button",
1005
1040
  style: S$3.button,
1041
+ disabled: upOff,
1042
+ "data-ilife-press": "dialog-up",
1043
+ "data-ilife-longpress": "dialog-up",
1006
1044
  onClick: props.onUp
1007
1045
  }, labels.up), react.createElement("button", {
1008
1046
  type: "button",
1009
1047
  style: S$3.button,
1048
+ disabled: goOff,
1049
+ "data-ilife-press": "dialog-go",
1050
+ "data-ilife-longpress": "dialog-go",
1010
1051
  onClick: props.onCommitDraft
1011
1052
  }, labels.go)));
1012
1053
  body.push(react.createElement("div", {
@@ -1022,6 +1063,7 @@ window.__ModuleLoader__.load({
1022
1063
  } }, react.createElement("input", {
1023
1064
  type: "checkbox",
1024
1065
  checked: state.showHidden,
1066
+ "data-ilife-focus": "dialog-check",
1025
1067
  onChange: props.onToggleHidden
1026
1068
  }), " " + labels.showHidden(hidden)) : null, state.creating === null ? react.createElement("button", {
1027
1069
  type: "button",
@@ -1029,6 +1071,8 @@ window.__ModuleLoader__.load({
1029
1071
  ...S$3.chromeButton,
1030
1072
  textDecoration: "underline"
1031
1073
  },
1074
+ "data-ilife-press": "dialog-newfolder",
1075
+ "data-ilife-longpress": "dialog-newfolder",
1032
1076
  onClick: () => props.onCreatingChange("")
1033
1077
  }, labels.newFolder) : null, state.notice !== null ? react.createElement("div", { style: S$3.error }, state.notice) : null, state.phase === "failed" && state.failure !== null ? react.createElement("div", { style: S$3.error }, state.failure.message) : null));
1034
1078
  body.push(react.createElement("div", { style: S$3.foot }, react.createElement("span", {
@@ -1038,12 +1082,17 @@ window.__ModuleLoader__.load({
1038
1082
  type: "button",
1039
1083
  style: {
1040
1084
  ...S$3.button,
1041
- ...target === null ? S$3.buttonOff : {}
1085
+ ...pickOff ? S$3.buttonOff : {}
1042
1086
  },
1087
+ disabled: pickOff,
1088
+ "data-ilife-press": "dialog-pick",
1089
+ "data-ilife-longpress": "dialog-pick",
1043
1090
  onClick: props.onPick
1044
1091
  }, labels.open), react.createElement("button", {
1045
1092
  type: "button",
1046
1093
  style: S$3.button,
1094
+ "data-ilife-press": "dialog-cancel",
1095
+ "data-ilife-longpress": "dialog-cancel",
1047
1096
  onClick: props.onClose
1048
1097
  }, labels.cancel)));
1049
1098
  return react.createElement("div", {
@@ -1065,6 +1114,8 @@ window.__ModuleLoader__.load({
1065
1114
  },
1066
1115
  "aria-label": labels.close,
1067
1116
  title: labels.close,
1117
+ "data-ilife-press": "dialog-close",
1118
+ "data-ilife-longpress": "dialog-close",
1068
1119
  onClick: props.onClose
1069
1120
  }, "✕")), body));
1070
1121
  }
@@ -1183,10 +1234,15 @@ window.__ModuleLoader__.load({
1183
1234
  /** 糙边滤镜 id(文档作用域,故每页只许挂一次 `SealFilterDefs`)。 */
1184
1235
  const ROUGH_EDGE = "dshLifeSealRoughEdge";
1185
1236
  const ROUGH_FRAME = "dshLifeSealRoughFrame";
1237
+ /** 档位话:标题下那一行不再印档位字,直接说档位所处的阶段(渲染期用,顶层可放行)。 */
1238
+ const TIER_TEXT = {
1239
+ gold: "精雕细琢中",
1240
+ silver: "全打通中",
1241
+ copper: "基础建设中"
1242
+ };
1186
1243
  /** 纸面四色是印自带的(羊皮纸底+ availability 墨色),不跟宿主主题跑——
1187
1244
  * 深色下宿主变量会把纸染黑,故这里写死定稿值;字族仍继承(定稿零字族)。 */
1188
1245
  const PAPER_BG = "#fdfaf2";
1189
- const LABEL_MAIN = "#42506b";
1190
1246
  const LABEL_SUB = "#4b4335";
1191
1247
  /** 九档材质(3 角色 × 3 档)。键就是原型里的 `role`/`tier`。 */
1192
1248
  const MATERIALS = {
@@ -1323,14 +1379,11 @@ window.__ModuleLoader__.load({
1323
1379
  position: "relative",
1324
1380
  padding: "1.75em 2em 1.875em"
1325
1381
  },
1326
- head: { textAlign: "center" },
1327
- title: {
1328
- margin: 0,
1329
- fontSize: "1em",
1330
- fontWeight: 600,
1331
- letterSpacing: "0.16em",
1332
- color: LABEL_MAIN
1382
+ head: {
1383
+ textAlign: "center",
1384
+ marginBottom: "0.5em"
1333
1385
  },
1386
+ /** 档位语(标题下那一行):档位字退役,改说档位话(金=精雕细琢中/银=全打通中/铜=基础建设中)。 */
1334
1387
  tier: {
1335
1388
  marginTop: "0.4375em",
1336
1389
  fontSize: "0.66em",
@@ -1382,17 +1435,20 @@ window.__ModuleLoader__.load({
1382
1435
  position: "absolute",
1383
1436
  left: "0.5em",
1384
1437
  right: "0.5em",
1385
- top: "8em",
1438
+ top: "8.8em",
1386
1439
  zIndex: 10,
1387
1440
  display: "flex",
1388
1441
  justifyContent: "center",
1389
1442
  pointerEvents: "auto"
1390
1443
  },
1391
- /** 浮层内框:宽按 em 给(16px 下≈25em),窄卡上收满可用宽,永不捅破卡片。 */
1444
+ /** 浮层内框:宽按 em 给(16px 下≈25em),窄卡上收满可用宽,永不捅破卡片;
1445
+ * 挂卷轴展开动画(从上而下舒卷,240ms;纯声明式,无 hook,纯函数可直测)。 */
1392
1446
  popInner: {
1393
1447
  position: "relative",
1394
1448
  width: "25em",
1395
- maxWidth: "100%"
1449
+ maxWidth: "100%",
1450
+ transformOrigin: "50% 0",
1451
+ animation: "dshLifeSealUnroll 240ms ease-out"
1396
1452
  },
1397
1453
  close: {
1398
1454
  position: "absolute",
@@ -1513,15 +1569,19 @@ window.__ModuleLoader__.load({
1513
1569
  return react.createElement("div", { style: S$1.scroll }, react.createElement("div", { style: {
1514
1570
  ...S$1.frame,
1515
1571
  ...material.frame
1516
- } }, ...frameChildren), react.createElement("div", { style: S$1.paper }, react.createElement("div", { style: S$1.inner }, react.createElement("div", { style: S$1.head }, react.createElement("h4", { style: S$1.title }, props.title), react.createElement("div", { style: {
1572
+ } }, ...frameChildren), react.createElement("div", { style: S$1.paper }, react.createElement("div", { style: S$1.inner }, react.createElement("div", { style: S$1.head }, react.createElement(SealStamp, {
1573
+ role: props.role,
1574
+ tier: props.tier,
1575
+ label: props.title
1576
+ }), react.createElement("div", { style: {
1517
1577
  ...S$1.tier,
1518
1578
  color: material.accent
1519
- } }, props.tier === "copper" ? "铜" : props.tier === "silver" ? "银" : "金")), react.createElement("div", { style: S$1.orn }, line("l", "90"), react.createElement("i", { style: {
1579
+ } }, TIER_TEXT[props.tier])), react.createElement("div", { style: S$1.orn }, line("l", "90"), react.createElement("i", { style: {
1520
1580
  ...S$1.ornDot,
1521
1581
  background: material.accent
1522
1582
  } }), line("r", "270")), block("progress", "进展", props.progress, false), block("status", "状态", props.status, true), block("plan", "计划", props.plan, true, true))));
1523
1583
  }
1524
- /** 卷轴浮层:无遮罩 popover + 卷轴 + 关闭。`open` 为假时不渲染(纯函数,不收自己的状态)。 */
1584
+ /** 卷轴浮层:popover + 点击别处关闭 + 卷轴展开动画 + 关闭。`open` 为假时不渲染(纯函数,不收自己的状态)。 */
1525
1585
  function SealScrollDialog(props) {
1526
1586
  if (!props.open) return null;
1527
1587
  const scroll = {
@@ -1532,16 +1592,26 @@ window.__ModuleLoader__.load({
1532
1592
  status: props.status,
1533
1593
  plan: props.plan
1534
1594
  };
1535
- return react.createElement("div", {
1595
+ return react.createElement(react.Fragment, null, react.createElement("div", {
1596
+ style: {
1597
+ position: "fixed",
1598
+ inset: 0,
1599
+ zIndex: 9,
1600
+ background: "transparent"
1601
+ },
1602
+ "aria-hidden": true,
1603
+ onClick: props.onClose
1604
+ }), react.createElement("div", {
1536
1605
  style: S$1.dialog,
1537
1606
  role: "dialog",
1538
1607
  "aria-modal": false,
1539
1608
  "aria-label": props.title
1540
- }, react.createElement("div", { style: S$1.popInner }, react.createElement("button", {
1609
+ }, react.createElement("style", null, "@keyframes dshLifeSealUnroll{from{opacity:0;transform:translateY(-10px) scaleY(.7)}to{opacity:1;transform:none}}"), react.createElement("div", { style: S$1.popInner }, react.createElement("button", {
1541
1610
  type: "button",
1542
1611
  style: S$1.close,
1612
+ "data-ilife-press": "seal-close",
1543
1613
  onClick: props.onClose
1544
- }, "关闭"), react.createElement(SealScroll, scroll)));
1614
+ }, "关闭"), react.createElement(SealScroll, scroll))));
1545
1615
  }
1546
1616
  //#endregion
1547
1617
  //#region ../plugin-manager/src/seal-stamp.ts
@@ -1769,6 +1839,9 @@ window.__ModuleLoader__.load({
1769
1839
  const PANEL_FIELD_EDGE = inkMix(24);
1770
1840
  /** 骨架条:浅色 ≈`#c9ccd2`(1.6:1)⇔ 深色 ≈`#404143`。 */
1771
1841
  const PANEL_SKELETON_FILL = inkMix(21);
1842
+ function interactionCss() {
1843
+ return "[data-ilife-press]{transition:" + INTERACTION_TRANSITION + "}[data-ilife-focus]{transition:" + INTERACTION_TRANSITION + "}[data-ilife-press]:active{transform:scale(" + String(PRESS_SCALE) + ")}[data-ilife-press]:focus-visible,[data-ilife-focus]:focus-visible{outline:none;box-shadow:0 0 0 " + String(2) + "px var(--dsw-alias-bg-layer-1, #232324),0 0 0 " + String(5) + "px var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55))}[data-ilife-press]{position:relative;overflow:hidden}[data-ilife-press]:hover:not(:disabled){background:color-mix(in srgb, var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55)) " + String(13) + "%, transparent);border-color:var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55));box-shadow:0 0 0 1px color-mix(in srgb, var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55)) " + String(55) + "%, transparent),0 4px 16px color-mix(in srgb, var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55)) " + String(25) + "%, transparent);transform:translateY(-" + String(1) + "px)}[data-ilife-press]:active:hover:not(:disabled){transform:scale(" + String(PRESS_SCALE) + ")}[data-ilife-press]:focus-visible:hover:not(:disabled){box-shadow:0 0 0 " + String(2) + "px var(--dsw-alias-bg-layer-1, #232324),0 0 0 " + String(5) + "px var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55)),0 4px 16px color-mix(in srgb, var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55)) " + String(25) + "%, transparent)}[data-ilife-press]::before{content:\"\";position:absolute;inset:0;background:radial-gradient(" + String(120) + "px circle at var(--mx,50%) var(--my,50%),color-mix(in srgb, var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55)) " + String(22) + "%, transparent),transparent 65%);opacity:0;transition:opacity " + String(180 / 1e3) + "s ease;pointer-events:none}[data-ilife-press]:hover:not(:disabled)::before{opacity:1}[data-ilife-press]:disabled{cursor:not-allowed;opacity:" + String(DISABLED_OPACITY) + "}[data-ilife-press]:disabled:hover{animation:ilifeShake " + String(300 / 1e3) + "s ease}@keyframes ilifeShake{0%,100%{transform:translateX(0)}25%{transform:translateX(-2px)}75%{transform:translateX(2px)}}[data-ilife-longpress]{position:relative;overflow:hidden}[data-ilife-longpress]::after{content:\"\";position:absolute;left:0;bottom:0;height:" + String(3) + "px;width:0;background:var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55));pointer-events:none}[data-ilife-longpress]:active:not(:disabled)::after{animation:ilifeLongRing " + String(500 / 1e3) + "s linear forwards}@keyframes ilifeLongRing{from{width:0}to{width:100%}}[data-ilife-advanced=\"head\"]{transition:background .15s ease,transform .15s ease,box-shadow .15s ease,border-color .15s ease}[data-ilife-advanced=\"mark\"]{display:inline-block;transition:transform " + String(200 / 1e3) + "s ease}[data-ilife-advanced=\"group\"][open] [data-ilife-advanced=\"mark\"]{transform:rotate(90deg)}[data-ilife-advanced=\"head\"]:hover [data-ilife-advanced=\"mark\"]{color:var(--ilife-focus, var(--dsw-alias-brand-primary, #f6ad55));transform:translateX(" + String(2) + "px)}[data-ilife-advanced=\"group\"][open] [data-ilife-advanced=\"body\"]{animation:ilifeFoldIn " + String(220 / 1e3) + "s ease}@keyframes ilifeFoldIn{from{max-height:0;opacity:0}to{max-height:" + String(300) + "px;opacity:1}}[data-ilife-notice=\"line\"]{animation:ilifeToastIn " + String(180 / 1e3) + "s ease}@keyframes ilifeToastIn{from{opacity:0;transform:translateY(" + String(8) + "px)}to{opacity:1;transform:none}}@media (hover:none){[data-ilife-press]:hover:not(:disabled){transform:none}[data-ilife-press]::before{display:none}}@media (prefers-reduced-motion:reduce){[data-ilife-press],[data-ilife-focus]{transition:none}[data-ilife-press]:disabled:hover{animation:none}[data-ilife-longpress]:active:not(:disabled)::after{animation:none;width:100%}[data-ilife-advanced=\"mark\"]{transition:none}[data-ilife-advanced=\"group\"][open] [data-ilife-advanced=\"body\"]{animation:none}[data-ilife-notice=\"line\"]{animation:none}}@media (update: slow){[data-ilife-press],[data-ilife-focus]{transition:none}[data-ilife-press]::before{display:none}[data-ilife-press]:disabled:hover{animation:none}[data-ilife-longpress]:active:not(:disabled)::after{animation:none;width:100%}[data-ilife-advanced=\"mark\"]{transition:none}[data-ilife-advanced=\"group\"][open] [data-ilife-advanced=\"body\"]{animation:none}[data-ilife-notice=\"line\"]{animation:none}}";
1844
+ }
1772
1845
  const S = {
1773
1846
  /** `.ic-card`:`background:var(--ic-surface)`(=bg-layer-1)+`border:1px solid var(--ic-line-strong)`
1774
1847
  * +`border-radius:12px`+`padding:14px`+`box-shadow:inset 0 1px 0 rgba(255,255,255,.06)`。
@@ -2311,6 +2384,8 @@ window.__ModuleLoader__.load({
2311
2384
  style: S.btnPick,
2312
2385
  type: "button",
2313
2386
  disabled: props.onCopy === void 0,
2387
+ "data-ilife-press": "copy",
2388
+ "data-ilife-longpress": "copy",
2314
2389
  onClick: () => props.onCopy?.(item.key, props.value)
2315
2390
  }, copyLabelOf(props.copyState));
2316
2391
  const follow = props.follow === true ? react.createElement("span", { style: S.follow }, react.createElement("span", { style: S.followMark }, "↳"), "将跟随更新") : null;
@@ -2325,6 +2400,7 @@ window.__ModuleLoader__.load({
2325
2400
  style: S.checkbox,
2326
2401
  checked: props.value === "true",
2327
2402
  disabled,
2403
+ "data-ilife-focus": "input",
2328
2404
  onChange: (e) => props.onChange(item.key, e.target.checked ? "true" : "false")
2329
2405
  }) : react.createElement("input", {
2330
2406
  style: props.dirty === true ? S.inputDirty : S.input,
@@ -2332,6 +2408,7 @@ window.__ModuleLoader__.load({
2332
2408
  value: props.value,
2333
2409
  disabled,
2334
2410
  spellCheck: false,
2411
+ "data-ilife-focus": "input",
2335
2412
  onChange
2336
2413
  });
2337
2414
  /** 目录入口:只读行不画(定稿 v3 ①),供不了(`none`/缺席)也不画。 */
@@ -2340,6 +2417,8 @@ window.__ModuleLoader__.load({
2340
2417
  style: S.btnPick,
2341
2418
  type: "button",
2342
2419
  disabled,
2420
+ "data-ilife-press": "browse",
2421
+ "data-ilife-longpress": "browse",
2343
2422
  onClick: () => entry.onOpen(item.key)
2344
2423
  }, "浏览文件夹") : null;
2345
2424
  return react.createElement("div", { style: S.master }, react.createElement("div", { style: S.label }, item.title, react.createElement("span", { style: S.chip }, "可改")), react.createElement("div", { style: S.hint }, item.hint), control, react.createElement("div", { style: S.acts }, browse, copy), props.alert == null ? null : react.createElement("div", { style: S.invalid }, props.alert.message));
@@ -2369,85 +2448,33 @@ window.__ModuleLoader__.load({
2369
2448
  const surface = state.kind === "ready" ? state.surface : null;
2370
2449
  const product = title ?? "";
2371
2450
  const seals = props.seals ?? [];
2372
- /** 18 枚倾角各不同(1158):家下标×3+位序,步进错开。 */
2373
- const TILT18 = [
2374
- -4,
2375
- -2.5,
2376
- -1,
2377
- -3,
2378
- 1.5,
2379
- 3,
2380
- -1.5,
2381
- 2,
2382
- 4,
2383
- -3.5,
2384
- .5,
2385
- 2.5,
2386
- -4.5,
2451
+ const seed = props.sealSeed ?? 0;
2452
+ /** 三行定序(六家一致:HELP 上/技能中/插件下;HELP 永远 z 最上)。 */
2453
+ const order = [
2454
+ 0,
2387
2455
  1,
2388
- -2,
2389
- 3.5,
2390
- -.5,
2391
- 4.5
2456
+ 2
2392
2457
  ];
2393
- /** 三枚落位(卡片坐标:右上/左中/右下三处,互不遮挡)。右上与右下两枚按右钉死——
2394
- * HELP 是单行最长的一枚,左钉会在窄卡上整体飞出面板右侧,右钉则右边界天然收敛;
2395
- * 左中那枚按左钉(左钉的章向右长,左边界天然收敛)。 */
2396
- const SPOT = [
2397
- {
2398
- right: "0.2em",
2399
- top: "0.3em"
2400
- },
2401
- {
2402
- left: "2%",
2403
- top: "3.2em"
2404
- },
2405
- {
2406
- right: "6%",
2407
- top: "5.6em"
2408
- }
2458
+ /** 三行行高(上/中/下;纵向净空各留约 10px,糙边抖动也碰不到)。 */
2459
+ const TOPS = [
2460
+ "0.3em",
2461
+ "3.4em",
2462
+ "6.2em"
2409
2463
  ];
2410
- const seed = props.sealSeed ?? 0;
2411
- const order = [
2412
- [
2413
- 0,
2414
- 1,
2415
- 2
2416
- ],
2417
- [
2418
- 1,
2419
- 2,
2420
- 0
2421
- ],
2422
- [
2423
- 2,
2424
- 0,
2425
- 1
2426
- ],
2427
- [
2428
- 0,
2429
- 2,
2430
- 1
2431
- ],
2432
- [
2433
- 1,
2434
- 0,
2435
- 2
2436
- ],
2437
- [
2438
- 2,
2439
- 1,
2440
- 0
2441
- ]
2442
- ][seed % 6];
2443
- const tiltOfSlot = (slot) => String(TILT18[(seed * 3 + slot) % 18]);
2464
+ /** 轻微倾角(±0.5°~±2.5°,方向随机):家下标×槽位哈希,纯函数、每次同值,不过大。 */
2465
+ const tiltOfSlot = (slot) => {
2466
+ const q = (seed * 5 + slot * 11 + 3) % 10;
2467
+ return String((q % 2 === 0 ? -1 : 1) * (.5 + Math.floor(q / 2) * .5));
2468
+ };
2469
+ /** 右间距(三枚全部居右收敛,各家各行 0.2em~1.1em 错开,永不出面板右边界)。 */
2470
+ const padRightOfSlot = (slot) => String(.2 + (seed * 7 + slot * 3 + 1) % 6 * .18) + "em";
2444
2471
  const zOfRole = (role) => role === "help" ? 4 : role === "skill" ? 3 : 2;
2445
2472
  const overlay = seals.length === 0 ? null : react.createElement("div", { style: S.sealOverlay }, react.createElement(SealFilterDefs, {}), ...order.map((dataIndex, slot) => react.createElement("span", {
2446
2473
  key: seals[dataIndex].role + ":" + seals[dataIndex].tier,
2447
2474
  style: {
2448
2475
  position: "absolute",
2449
- top: SPOT[slot].top,
2450
- ...SPOT[slot].left === void 0 ? { right: SPOT[slot].right } : { left: SPOT[slot].left },
2476
+ top: TOPS[slot],
2477
+ right: padRightOfSlot(slot),
2451
2478
  zIndex: zOfRole(seals[dataIndex].role),
2452
2479
  pointerEvents: "auto",
2453
2480
  display: "inline-flex",
@@ -2521,12 +2548,16 @@ window.__ModuleLoader__.load({
2521
2548
  if (state.kind === "failed") return react.createElement("div", { style: S.card }, head, fileLines, react.createElement("div", { style: S.error }, state.message), extra, overlay, react.createElement("div", { style: S.bar }, react.createElement("button", {
2522
2549
  style: S.btn,
2523
2550
  type: "button",
2551
+ "data-ilife-press": "retry",
2552
+ "data-ilife-longpress": "retry",
2524
2553
  onClick: props.onRetry
2525
2554
  }, "重试"), react.createElement("button", {
2526
2555
  style: S.btn,
2527
2556
  type: "button",
2557
+ "data-ilife-press": "reset",
2558
+ "data-ilife-longpress": "reset",
2528
2559
  onClick: props.onReset
2529
- }, "重置为默认")));
2560
+ }, "重置为默认")), react.createElement("style", { "data-ilife-interaction": "t1" }, interactionCss()));
2530
2561
  const common = props.items.filter((item) => item.tier === "common");
2531
2562
  const advanced = props.items.filter((item) => item.tier === "advanced");
2532
2563
  /** 还没读到整面(`loading`)时**框架照画**:行表与控件形状是客户端常量,不必等任何回执。
@@ -2556,20 +2587,38 @@ window.__ModuleLoader__.load({
2556
2587
  const commonLast = lastIndexOf(common);
2557
2588
  const advancedLast = lastIndexOf(advanced);
2558
2589
  const dirty = ready && dirtyCount > 0;
2559
- return react.createElement("div", { style: S.card }, head, fileLines, react.createElement("div", { style: S.rows }, common.map((item, index) => renderRow(item, index === commonLast))), advanced.length === 0 ? null : react.createElement("details", { style: S.advanced }, react.createElement("summary", { style: S.summary }, react.createElement("span", { style: S.groupMark }, "›"), ADVANCED_GROUP_TITLE), react.createElement("div", { style: S.groupBody }, advanced.map((item, index) => renderRow(item, index === advancedLast)))), extra, react.createElement("div", { style: S.bar }, react.createElement("button", {
2590
+ return react.createElement("div", { style: S.card }, head, fileLines, react.createElement("div", { style: S.rows }, common.map((item, index) => renderRow(item, index === commonLast))), advanced.length === 0 ? null : react.createElement("details", {
2591
+ style: S.advanced,
2592
+ "data-ilife-advanced": "group"
2593
+ }, react.createElement("summary", {
2594
+ style: S.summary,
2595
+ "data-ilife-advanced": "head"
2596
+ }, react.createElement("span", {
2597
+ style: S.groupMark,
2598
+ "data-ilife-advanced": "mark"
2599
+ }, "›"), ADVANCED_GROUP_TITLE), react.createElement("div", {
2600
+ style: S.groupBody,
2601
+ "data-ilife-advanced": "body"
2602
+ }, advanced.map((item, index) => renderRow(item, index === advancedLast)))), extra, react.createElement("div", { style: S.bar }, react.createElement("button", {
2560
2603
  style: dirty ? S.btnPrimary : S.btn,
2561
2604
  type: "button",
2562
2605
  disabled: frozen || !dirty,
2606
+ "data-ilife-press": "save",
2607
+ "data-ilife-longpress": "save",
2563
2608
  onClick: props.onSave
2564
2609
  }, props.busy ? "处理中" : dirty ? `保存(${dirtyCount} 项未保存)` : "保存"), react.createElement("button", {
2565
2610
  style: S.btn,
2566
2611
  type: "button",
2567
2612
  disabled: frozen,
2613
+ "data-ilife-press": "reset",
2614
+ "data-ilife-longpress": "reset",
2568
2615
  onClick: props.onReset
2569
2616
  }, "重置为默认"), react.createElement("button", {
2570
2617
  style: S.btn,
2571
2618
  type: "button",
2572
2619
  disabled: frozen,
2620
+ "data-ilife-press": "retry",
2621
+ "data-ilife-longpress": "retry",
2573
2622
  onClick: props.onRetry
2574
2623
  }, "重新读取"), dirty ? react.createElement("div", { style: S.saveMsg }, `浏览改动后请点保存(${dirtyCount} 项未保存),保存后跟随项自动更新`) : ready ? react.createElement("div", { style: S.barNote }, "没有未保存的改动") : react.createElement("div", { style: S.barNote }, "配置读取中")), props.picking ? react.createElement("div", { style: S.muted }, "已唤起系统文件夹对话框:选中后自动填上,取消则不动。") : null, props.browseRow !== null ? react.createElement(DirectoryBrowserFromRow, {
2575
2624
  open: true,
@@ -2578,7 +2627,10 @@ window.__ModuleLoader__.load({
2578
2627
  }) : null, props.writeError !== null ? react.createElement("div", {
2579
2628
  style: S.error,
2580
2629
  role: "alert"
2581
- }, props.writeError) : null, props.error !== null ? react.createElement("div", { style: S.error }, props.error) : null, props.notice !== null ? react.createElement("div", { style: S.okText }, props.notice) : null, overlay);
2630
+ }, props.writeError) : null, props.error !== null ? react.createElement("div", { style: S.error }, props.error) : null, props.notice !== null ? react.createElement("div", {
2631
+ style: S.okText,
2632
+ "data-ilife-notice": "line"
2633
+ }, props.notice) : null, overlay, react.createElement("style", { "data-ilife-interaction": "t1" }, interactionCss()));
2582
2634
  }
2583
2635
  //#endregion
2584
2636
  //#region ../plugin-manager/src/directory-browser-roots.ts
@@ -2616,36 +2668,13 @@ window.__ModuleLoader__.load({
2616
2668
  }
2617
2669
  };
2618
2670
  }
2619
- /** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */
2620
2671
  const MANAGER_RPC = {
2621
- /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */
2622
2672
  base: "/api",
2623
- /** 单段通道名,须 match `/^\/[A-Za-z0-9._~-]+$/`(cookbook §6)。 */
2624
2673
  channel: "/ilife-manager",
2625
- /** 面板侧第二段参数(等于通道名去掉前导斜杠那一段)。 */
2626
2674
  endpoint: "ilife-manager",
2627
- /** 宿主侧注册的完整路径(= `base` + `channel`)。 */
2628
2675
  path: "/api/ilife-manager"
2629
2676
  };
2630
- /** 总管自有电话名:更新包的三个动作名是冻结集合(`config.ts:14`),这里另起名字不撞车。 */
2631
- const MANAGER_ACTIONS = {
2632
- /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */
2633
- install: "ilife-manager.install",
2634
- /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, panelRegistered, skill}]}`。
2635
- *
2636
- * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道
2637
- * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)
2638
- * 也不该把名字再写一份(写两份必然走散)。宿主把名字表连同版本行一起交出去,
2639
- * 面板一行字面量都不留。 */
2640
- targets: "ilife-manager.targets",
2641
- /** 本机「根」清单(票 #744):入参 `{}`,回包 `{roots: [{path, kind}]}`。
2642
- *
2643
- * 为什么这条能力住在总管:浏览器里没有卷清单接口,而宿主那条目录选择接缝只有
2644
- * `list`/`createDirectory` 两格——「这台机器上有哪些盘」只能由宿主去问操作系统。
2645
- * 六家的目录浏览器都取这一通电话,故名字与实现都只有这一处(清单本身见 `roots.ts`)。
2646
- * 回包**恒成功**:列不出来是空清单,不是错误码(界面据此只是不画那一行)。 */
2647
- roots: "ilife-manager.roots"
2648
- };
2677
+ const MANAGER_ACTIONS = { roots: "ilife-manager.roots" };
2649
2678
  //#endregion
2650
2679
  //#region ../plugin-manager/src/config-panel.ts
2651
2680
  /** 共用配置面板 · 组件本体:一台状态机 + 一圈副作用,渲染交给 `config-panel-view.ts`。
@@ -2874,7 +2903,7 @@ window.__ModuleLoader__.load({
2874
2903
  });
2875
2904
  if (opened === void 0 || await opened === "refused") await openNative(key);
2876
2905
  };
2877
- /** 复制一行(定稿 v3 第三条):写完就地变「已复制」(写不进去就地变「复制失败」),1.5 秒后复位,不弹提示。 */
2906
+ /** 复制一行(定稿 v3 第三条):写完就地变「已复制」(写不进去就地变「复制失败」),900ms 后复位,不弹提示(#1162 差值终结:终值 900ms,行为跟 COPY_FEEDBACK_MS 走)。 */
2878
2907
  const onCopy = (key, text) => {
2879
2908
  writeClipboard(text).then((ok) => {
2880
2909
  setCopy({
@@ -2885,7 +2914,7 @@ window.__ModuleLoader__.load({
2885
2914
  copyTimer.current = setTimeout(() => {
2886
2915
  copyTimer.current = null;
2887
2916
  setCopy(null);
2888
- }, COPY_FEEDBACK_MS);
2917
+ }, 900);
2889
2918
  });
2890
2919
  };
2891
2920
  const onSave = async () => {