weifuwu 0.65.1 → 0.66.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.
@@ -1626,6 +1626,46 @@ code {
1626
1626
  opacity: 1;
1627
1627
  }
1628
1628
 
1629
+ /* weifuwu/components — Anchor(锚点导航) */
1630
+
1631
+ .wf-anchor {
1632
+ display: flex;
1633
+ flex-direction: column;
1634
+ gap: 2px;
1635
+ font-family: var(--wf-font-sans);
1636
+ font-size: var(--wf-font-size-sm);
1637
+ }
1638
+
1639
+ .wf-anchor-link {
1640
+ display: block;
1641
+ padding: var(--wf-space-xs) var(--wf-space-sm);
1642
+ border-left: 2px solid transparent;
1643
+ border-radius: 0 var(--wf-radius-sm) var(--wf-radius-sm) 0;
1644
+ color: var(--wf-color-text-secondary);
1645
+ text-decoration: none;
1646
+ cursor: pointer;
1647
+ transition: color var(--wf-dur-fast) var(--wf-ease-out),
1648
+ background var(--wf-dur-fast) var(--wf-ease-out),
1649
+ border-color var(--wf-dur-fast) var(--wf-ease-out);
1650
+ }
1651
+
1652
+ .wf-anchor-link:hover {
1653
+ background: var(--wf-color-bg-secondary);
1654
+ color: var(--wf-color-text);
1655
+ }
1656
+
1657
+ .wf-anchor-link--active {
1658
+ color: var(--wf-color-primary-text);
1659
+ border-left-color: var(--wf-color-primary);
1660
+ font-weight: var(--wf-font-weight-semibold);
1661
+ background: var(--wf-color-primary-bg);
1662
+ }
1663
+
1664
+ .wf-anchor-link:focus-visible {
1665
+ outline: none;
1666
+ box-shadow: var(--wf-focus-ring);
1667
+ }
1668
+
1629
1669
  /* weifuwu/components — ApprovalCard */
1630
1670
 
1631
1671
  .wf-approval {
@@ -4116,27 +4156,67 @@ code {
4116
4156
  flex-shrink: 0;
4117
4157
  }
4118
4158
 
4159
+ .wf-upload-err {
4160
+ font-family: var(--wf-font-sans);
4161
+ font-size: var(--wf-font-size-xs);
4162
+ color: var(--wf-color-error-text);
4163
+ }
4164
+
4165
+ /* ── 第六批增强:缩略图 / 进度条 ────────────────────────── */
4166
+
4167
+ .wf-upload-item {
4168
+ display: flex;
4169
+ align-items: center;
4170
+ gap: var(--wf-gap-sm);
4171
+ padding: var(--wf-space-sm);
4172
+ border-radius: var(--wf-radius-sm);
4173
+ background: var(--wf-color-bg-secondary);
4174
+ }
4175
+
4176
+ .wf-upload-thumb {
4177
+ width: 36px;
4178
+ height: 36px;
4179
+ object-fit: cover;
4180
+ border-radius: var(--wf-radius-sm);
4181
+ border: var(--wf-border-width) solid var(--wf-color-border);
4182
+ flex-shrink: 0;
4183
+ background: var(--wf-color-bg);
4184
+ }
4185
+
4119
4186
  .wf-upload-item-remove {
4187
+ margin-left: auto;
4188
+ width: 24px; height: 24px;
4189
+ min-width: 24px; max-width: 24px;
4190
+ min-height: 24px; max-height: 24px;
4191
+ display: inline-flex;
4192
+ align-items: center;
4193
+ justify-content: center;
4120
4194
  border: none;
4121
- background: transparent;
4195
+ background: none;
4196
+ border-radius: var(--wf-radius-sm);
4122
4197
  color: var(--wf-color-text-tertiary);
4123
4198
  cursor: pointer;
4124
- padding: 2px 4px;
4125
- font-size: 12px;
4126
- line-height: 1;
4127
- border-radius: var(--wf-radius-sm);
4128
- flex-shrink: 0;
4199
+ line-height: 0; padding: 0; flex-shrink: 0;
4129
4200
  }
4130
4201
 
4131
4202
  .wf-upload-item-remove:hover {
4132
- color: var(--wf-color-error-text);
4133
4203
  background: var(--wf-color-error-bg);
4204
+ color: var(--wf-color-error-text);
4134
4205
  }
4135
4206
 
4136
- .wf-upload-err {
4137
- font-family: var(--wf-font-sans);
4138
- font-size: var(--wf-font-size-xs);
4139
- color: var(--wf-color-error-text);
4207
+ .wf-upload-progress {
4208
+ height: 4px;
4209
+ border-radius: 2px;
4210
+ background: var(--wf-color-bg-tertiary);
4211
+ overflow: hidden;
4212
+ margin-top: var(--wf-space-sm);
4213
+ }
4214
+
4215
+ .wf-upload-progress-fill {
4216
+ height: 100%;
4217
+ background: var(--wf-color-primary);
4218
+ border-radius: 2px;
4219
+ transition: width var(--wf-dur-fast) var(--wf-ease-out);
4140
4220
  }
4141
4221
 
4142
4222
  /* weifuwu/components — Form */
@@ -4516,6 +4596,152 @@ code {
4516
4596
  .wf-inputnumber-err { font-size: var(--wf-font-size-xs); color: var(--wf-color-error-text); }
4517
4597
  .wf-inputnumber-hint { font-size: var(--wf-font-size-xs); color: var(--wf-color-text-tertiary); }
4518
4598
 
4599
+ /* weifuwu/components — JSONViewer(结构化 JSON 浏览) */
4600
+
4601
+ .wf-json-viewer {
4602
+ font-family: var(--wf-font-mono);
4603
+ font-size: var(--wf-font-size-xs);
4604
+ color: var(--wf-color-text);
4605
+ background: var(--wf-color-bg-secondary);
4606
+ border-radius: var(--wf-radius-sm);
4607
+ padding: var(--wf-space-sm);
4608
+ overflow-x: auto;
4609
+ line-height: 1.6;
4610
+ }
4611
+
4612
+ /* 对象/数组节点不额外缩进——同层键(标量/对象/数组)对齐;
4613
+ 子层缩进由 .wf-json-children 容器承担(padding + border-left) */
4614
+ .wf-json-node {
4615
+ padding-left: 0;
4616
+ }
4617
+
4618
+ .wf-json-row {
4619
+ display: flex;
4620
+ align-items: center;
4621
+ gap: var(--wf-gap-xs);
4622
+ white-space: nowrap;
4623
+ border-radius: var(--wf-radius-sm);
4624
+ }
4625
+
4626
+ .wf-json-row:hover {
4627
+ background: var(--wf-color-bg-tertiary);
4628
+ }
4629
+
4630
+ .wf-json-children {
4631
+ padding-left: var(--wf-space-sm);
4632
+ border-left: 1px solid var(--wf-color-border);
4633
+ margin-left: var(--wf-space-xs);
4634
+ }
4635
+
4636
+ .wf-json-key {
4637
+ color: var(--wf-color-text-secondary);
4638
+ }
4639
+
4640
+ /* 标量行 toggle 占位(与对象/数组行 chevron 同宽 16px)——同层 key 文本对齐 */
4641
+ .wf-json-toggle-placeholder {
4642
+ width: 16px;
4643
+ flex-shrink: 0;
4644
+ display: inline-flex;
4645
+ }
4646
+
4647
+ .wf-json-toggle {
4648
+ width: 16px; height: 16px;
4649
+ min-width: 16px; max-width: 16px;
4650
+ min-height: 16px; max-height: 16px;
4651
+ display: inline-flex;
4652
+ align-items: center;
4653
+ justify-content: center;
4654
+ border: none;
4655
+ background: none;
4656
+ color: var(--wf-color-text-tertiary);
4657
+ cursor: pointer;
4658
+ line-height: 0; padding: 0; flex-shrink: 0;
4659
+ }
4660
+
4661
+ .wf-json-toggle:hover {
4662
+ color: var(--wf-color-text);
4663
+ }
4664
+
4665
+ .wf-json-toggle:focus-visible {
4666
+ outline: none;
4667
+ box-shadow: var(--wf-focus-ring);
4668
+ }
4669
+
4670
+ .wf-json-summary,
4671
+ .wf-json-node-summary {
4672
+ color: var(--wf-color-text-tertiary);
4673
+ }
4674
+
4675
+ .wf-json-more {
4676
+ padding-left: var(--wf-space-lg);
4677
+ color: var(--wf-color-warning-text);
4678
+ font-size: var(--wf-font-size-xs);
4679
+ }
4680
+
4681
+ .wf-json-copy {
4682
+ width: 16px; height: 16px;
4683
+ min-width: 16px; max-width: 16px;
4684
+ min-height: 16px; max-height: 16px;
4685
+ display: inline-flex;
4686
+ align-items: center;
4687
+ justify-content: center;
4688
+ border: none;
4689
+ background: none;
4690
+ color: var(--wf-color-text-tertiary);
4691
+ cursor: pointer;
4692
+ line-height: 0; padding: 0; flex-shrink: 0;
4693
+ opacity: 0;
4694
+ }
4695
+
4696
+ .wf-json-row:hover .wf-json-copy {
4697
+ opacity: 1;
4698
+ }
4699
+
4700
+ .wf-json-copy:hover {
4701
+ color: var(--wf-color-primary-text);
4702
+ }
4703
+
4704
+ .wf-json-copy:focus-visible {
4705
+ outline: none;
4706
+ box-shadow: var(--wf-focus-ring);
4707
+ opacity: 1;
4708
+ }
4709
+
4710
+ /* ── 类型色(语义 Token) ── */
4711
+ .wf-json-string { color: var(--wf-color-success-text); }
4712
+ .wf-json-number { color: var(--wf-color-primary-text); }
4713
+ .wf-json-boolean { color: var(--wf-color-warning-text); }
4714
+ .wf-json-null { color: var(--wf-color-text-tertiary); font-style: italic; }
4715
+
4716
+ /* ── 整行折叠交互(大命中区)+ 触屏适配 ── */
4717
+
4718
+ .wf-json-collapse,
4719
+ .wf-json-row--header {
4720
+ cursor: pointer;
4721
+ }
4722
+
4723
+ .wf-json-collapse:hover,
4724
+ .wf-json-row--header:hover {
4725
+ background: var(--wf-color-bg-tertiary);
4726
+ }
4727
+
4728
+ .wf-json-collapse:focus-visible,
4729
+ .wf-json-row--header:focus-visible {
4730
+ outline: none;
4731
+ box-shadow: var(--wf-focus-ring);
4732
+ }
4733
+
4734
+ /* 触屏(coarse):toggle/复制按钮 44px 命中区(padding 扩展) */
4735
+ @media (pointer: coarse) {
4736
+ .wf-json-toggle,
4737
+ .wf-json-copy {
4738
+ width: 24px; height: 24px;
4739
+ min-width: 24px; max-width: 24px;
4740
+ min-height: 24px; max-height: 24px;
4741
+ padding: 4px;
4742
+ }
4743
+ }
4744
+
4519
4745
  /* weifuwu/components — Label(与 Input/Field 的 label 样式同源) */
4520
4746
 
4521
4747
  .wf-label {
@@ -4609,6 +4835,106 @@ code {
4609
4835
  font-size: var(--wf-font-size-sm);
4610
4836
  }
4611
4837
 
4838
+ /* weifuwu/components — LogViewer(日志流:ANSI 着色 + 虚拟滚动) */
4839
+
4840
+ .wf-log-viewer {
4841
+ font-family: var(--wf-font-mono);
4842
+ font-size: var(--wf-font-size-xs);
4843
+ color: var(--wf-color-text);
4844
+ position: relative;
4845
+ border: var(--wf-border-width) solid var(--wf-color-border);
4846
+ border-radius: var(--wf-radius);
4847
+ background: var(--wf-color-bg-secondary);
4848
+ overflow: hidden;
4849
+ }
4850
+
4851
+ .wf-log-copy {
4852
+ position: absolute;
4853
+ top: var(--wf-space-sm);
4854
+ right: var(--wf-space-sm);
4855
+ z-index: 1;
4856
+ width: 24px; height: 24px;
4857
+ min-width: 24px; max-width: 24px;
4858
+ min-height: 24px; max-height: 24px;
4859
+ display: inline-flex;
4860
+ align-items: center;
4861
+ justify-content: center;
4862
+ border: none;
4863
+ background: var(--wf-color-bg-tertiary);
4864
+ border-radius: var(--wf-radius-sm);
4865
+ color: var(--wf-color-text-secondary);
4866
+ cursor: pointer;
4867
+ line-height: 0; padding: 0; flex-shrink: 0;
4868
+ }
4869
+
4870
+ .wf-log-copy:hover {
4871
+ background: var(--wf-color-bg);
4872
+ color: var(--wf-color-text);
4873
+ }
4874
+
4875
+ .wf-log-body {
4876
+ position: relative;
4877
+ overflow-y: auto;
4878
+ }
4879
+
4880
+ .wf-log-spacer {
4881
+ pointer-events: none;
4882
+ }
4883
+
4884
+ .wf-log-row {
4885
+ display: flex;
4886
+ align-items: center;
4887
+ box-sizing: border-box;
4888
+ padding-right: var(--wf-space-md);
4889
+ white-space: pre;
4890
+ }
4891
+
4892
+ .wf-log-row:hover {
4893
+ background: var(--wf-color-bg-tertiary);
4894
+ }
4895
+
4896
+ .wf-log-line-no {
4897
+ display: inline-flex;
4898
+ align-items: center;
4899
+ justify-content: flex-end;
4900
+ min-width: 48px;
4901
+ padding: 0 var(--wf-space-sm);
4902
+ margin-right: var(--wf-space-sm);
4903
+ color: var(--wf-color-text-tertiary);
4904
+ user-select: none;
4905
+ flex-shrink: 0;
4906
+ }
4907
+
4908
+ .wf-log-line-content {
4909
+ overflow: hidden;
4910
+ }
4911
+
4912
+ /* ── ANSI 前景色(16 色映射到语义 Token) ── */
4913
+ .wf-log-ansi--bold { font-weight: var(--wf-font-weight-bold); }
4914
+ .wf-log-ansi--30 { color: var(--wf-color-text-secondary); }
4915
+ .wf-log-ansi--31 { color: var(--wf-color-error-text); }
4916
+ .wf-log-ansi--32 { color: var(--wf-color-success-text); }
4917
+ .wf-log-ansi--33 { color: var(--wf-color-warning-text); }
4918
+ .wf-log-ansi--34 { color: var(--wf-color-primary-text); }
4919
+ .wf-log-ansi--35 { color: var(--wf-color-primary-text); }
4920
+ .wf-log-ansi--36 { color: var(--wf-color-info-text); }
4921
+ .wf-log-ansi--37 { color: var(--wf-color-text); }
4922
+
4923
+ /* ── ANSI 背景色(8 色 → 50 级语义底) ── */
4924
+ .wf-log-ansi--bg41 { background: var(--wf-color-error-bg); }
4925
+ .wf-log-ansi--bg42 { background: var(--wf-color-success-bg); }
4926
+ .wf-log-ansi--bg43 { background: var(--wf-color-warning-bg); }
4927
+ .wf-log-ansi--bg44 { background: var(--wf-color-primary-bg); }
4928
+ .wf-log-ansi--bg47 { background: var(--wf-color-bg-tertiary); }
4929
+
4930
+ .wf-log-empty {
4931
+ display: flex;
4932
+ align-items: center;
4933
+ justify-content: center;
4934
+ padding: var(--wf-space-lg);
4935
+ color: var(--wf-color-text-tertiary);
4936
+ }
4937
+
4612
4938
  /* weifuwu/components — Markdown 排版 */
4613
4939
 
4614
4940
  .wf-md {
@@ -4798,6 +5124,102 @@ code {
4798
5124
  flex-shrink: 0;
4799
5125
  }
4800
5126
 
5127
+ /* ── 子菜单(第六批) ────────────────────────────── */
5128
+
5129
+ .wf-menu-submenu {
5130
+ display: flex;
5131
+ flex-direction: column;
5132
+ gap: 2px;
5133
+ }
5134
+
5135
+ .wf-menu-submenu-title {
5136
+ display: flex;
5137
+ align-items: center;
5138
+ gap: var(--wf-gap-sm);
5139
+ padding: var(--wf-space-sm) var(--wf-space-md);
5140
+ border-radius: var(--wf-radius-sm);
5141
+ font-size: var(--wf-font-size-sm);
5142
+ color: var(--wf-color-text-secondary);
5143
+ cursor: pointer;
5144
+ user-select: none;
5145
+ transition: background var(--wf-dur-fast) var(--wf-ease-out), color var(--wf-dur-fast) var(--wf-ease-out);
5146
+ }
5147
+
5148
+ .wf-menu-submenu-title:hover {
5149
+ background: var(--wf-color-bg-secondary);
5150
+ color: var(--wf-color-text);
5151
+ }
5152
+
5153
+ .wf-menu-submenu-title:focus-visible {
5154
+ outline: 2px solid var(--wf-color-primary);
5155
+ outline-offset: -2px;
5156
+ }
5157
+
5158
+ .wf-menu-arrow {
5159
+ margin-left: auto;
5160
+ display: inline-flex;
5161
+ color: var(--wf-color-text-tertiary);
5162
+ transition: transform var(--wf-dur-fast) var(--wf-ease-out);
5163
+ }
5164
+
5165
+ .wf-menu-submenu--open .wf-menu-arrow {
5166
+ transform: rotate(90deg);
5167
+ }
5168
+
5169
+ .wf-menu-submenu-content {
5170
+ display: none;
5171
+ flex-direction: column;
5172
+ gap: 2px;
5173
+ }
5174
+
5175
+ .wf-menu-submenu--open .wf-menu-submenu-content {
5176
+ display: flex;
5177
+ }
5178
+
5179
+ .wf-menu-item--child {
5180
+ padding-left: var(--wf-space-xl);
5181
+ font-size: var(--wf-font-size-sm);
5182
+ }
5183
+
5184
+ /* ── 折叠(第六批) ────────────────────────────── */
5185
+
5186
+ .wf-menu--collapsed {
5187
+ width: 56px;
5188
+ }
5189
+
5190
+ .wf-menu--collapsed .wf-menu-group {
5191
+ display: none;
5192
+ }
5193
+
5194
+ .wf-menu--collapsed .wf-menu-item,
5195
+ .wf-menu--collapsed .wf-menu-submenu-title {
5196
+ justify-content: center;
5197
+ padding: var(--wf-space-sm);
5198
+ gap: 0;
5199
+ }
5200
+
5201
+ .wf-menu-collapse-btn {
5202
+ display: flex;
5203
+ align-items: center;
5204
+ justify-content: center;
5205
+ margin-top: auto;
5206
+ padding: var(--wf-space-sm);
5207
+ border-radius: var(--wf-radius-sm);
5208
+ color: var(--wf-color-text-tertiary);
5209
+ cursor: pointer;
5210
+ min-height: 36px;
5211
+ }
5212
+
5213
+ .wf-menu-collapse-btn:hover {
5214
+ background: var(--wf-color-bg-secondary);
5215
+ color: var(--wf-color-text);
5216
+ }
5217
+
5218
+ .wf-menu-collapse-btn:focus-visible {
5219
+ outline: 2px solid var(--wf-color-primary);
5220
+ outline-offset: -2px;
5221
+ }
5222
+
4801
5223
  /* weifuwu/components — Menubar */
4802
5224
 
4803
5225
  .wf-menubar {
@@ -7473,6 +7895,104 @@ code {
7473
7895
  box-sizing: border-box;
7474
7896
  }
7475
7897
 
7898
+ /* weifuwu/components — VirtualTable(固定表头 + 虚拟滚动表体) */
7899
+
7900
+ .wf-virtual-table {
7901
+ font-family: var(--wf-font-sans);
7902
+ font-size: var(--wf-font-size-sm);
7903
+ color: var(--wf-color-text);
7904
+ border: var(--wf-border-width) solid var(--wf-color-border);
7905
+ border-radius: var(--wf-radius);
7906
+ overflow: hidden;
7907
+ }
7908
+
7909
+ .wf-virtual-table-thead {
7910
+ display: flex;
7911
+ border-bottom: var(--wf-border-width) solid var(--wf-color-border);
7912
+ background: var(--wf-color-bg-secondary);
7913
+ }
7914
+
7915
+ .wf-virtual-table-th {
7916
+ display: flex;
7917
+ align-items: center;
7918
+ gap: var(--wf-gap-xs);
7919
+ padding: var(--wf-space-sm) var(--wf-space-md);
7920
+ font-size: var(--wf-font-size-xs);
7921
+ font-weight: var(--wf-font-weight-semibold);
7922
+ color: var(--wf-color-text-secondary);
7923
+ flex-shrink: 0;
7924
+ box-sizing: border-box;
7925
+ }
7926
+
7927
+ .wf-virtual-table-th--sortable {
7928
+ cursor: pointer;
7929
+ user-select: none;
7930
+ }
7931
+
7932
+ .wf-virtual-table-th--sortable:hover {
7933
+ color: var(--wf-color-text);
7934
+ }
7935
+
7936
+ .wf-virtual-table-th--sortable:focus-visible {
7937
+ outline: none;
7938
+ box-shadow: var(--wf-focus-ring);
7939
+ }
7940
+
7941
+ .wf-virtual-table-th--sorted {
7942
+ color: var(--wf-color-primary-text);
7943
+ }
7944
+
7945
+ .wf-virtual-table-sort-icon {
7946
+ display: inline-flex;
7947
+ color: var(--wf-color-text-tertiary);
7948
+ }
7949
+
7950
+ .wf-virtual-table-sort-icon--active {
7951
+ color: var(--wf-color-primary-text);
7952
+ }
7953
+
7954
+ .wf-virtual-table-body {
7955
+ position: relative;
7956
+ overflow-y: auto;
7957
+ }
7958
+
7959
+ .wf-virtual-table-spacer {
7960
+ pointer-events: none;
7961
+ }
7962
+
7963
+ .wf-virtual-table-row {
7964
+ display: flex;
7965
+ align-items: center;
7966
+ box-sizing: border-box;
7967
+ border-bottom: var(--wf-border-width) solid var(--wf-color-border);
7968
+ cursor: default;
7969
+ }
7970
+
7971
+ .wf-virtual-table-row:hover {
7972
+ background: var(--wf-color-bg-tertiary);
7973
+ }
7974
+
7975
+ .wf-virtual-table-td {
7976
+ display: flex;
7977
+ align-items: center;
7978
+ padding: 0 var(--wf-space-md);
7979
+ flex-shrink: 0;
7980
+ box-sizing: border-box;
7981
+ overflow: hidden;
7982
+ white-space: nowrap;
7983
+ text-overflow: ellipsis;
7984
+ color: var(--wf-color-text);
7985
+ }
7986
+
7987
+ .wf-virtual-table-empty {
7988
+ display: flex;
7989
+ align-items: center;
7990
+ justify-content: center;
7991
+ padding: var(--wf-space-lg);
7992
+ color: var(--wf-color-text-tertiary);
7993
+ font-size: var(--wf-font-size-sm);
7994
+ }
7995
+
7476
7996
  /* weifuwu/components — Watermark */
7477
7997
 
7478
7998
  .wf-watermark {
package/dist/index.js CHANGED
@@ -4426,8 +4426,33 @@ function createSsrContext(serverCtx, dataStore) {
4426
4426
  }
4427
4427
  })
4428
4428
  };
4429
+ const browser = {
4430
+ activeElement: () => null,
4431
+ byId: () => null,
4432
+ query: () => null,
4433
+ createElement: () => null,
4434
+ bodyAppend: () => {
4435
+ },
4436
+ bodyRemove: () => {
4437
+ },
4438
+ copyText: async () => false,
4439
+ execCommand: () => false,
4440
+ selectionText: () => null,
4441
+ getSelection: () => null,
4442
+ viewportHeight: () => 0,
4443
+ scrollTop: () => 0,
4444
+ hash: () => "",
4445
+ setHash: () => {
4446
+ },
4447
+ timeout: () => 0,
4448
+ rootElement: () => null,
4449
+ storageGet: () => null,
4450
+ storageSet: () => {
4451
+ }
4452
+ };
4429
4453
  const ctx = Object.create(serverCtx ?? {});
4430
4454
  ctx.ui = ui2;
4455
+ ctx.browser = browser;
4431
4456
  ctx.data = {
4432
4457
  async get(key, fetcher) {
4433
4458
  if (dataStore.has(key)) return dataStore.get(key);
@@ -141,13 +141,17 @@
141
141
  <Badge variant="secondary">x</Badge> // → <Tag variant="secondary">x</Tag>
142
142
  ```
143
143
 
144
- ## weifuwu 尚未实现(三库有)→ 待补清单
144
+ ## weifuwu 已补齐(第六批,2026-08)→ 待补清单清零 ✅
145
145
 
146
- 仅 4 项:
146
+ 第六批 6 组件全部落地(TDD 红→绿 + style-audit + demo + agent-browser 实测):
147
147
 
148
- - **Anchor**(锚点导航,三库均有)
149
- - **Menu 子菜单/折叠**(分组 group 已支持,子级/折叠待补)
150
- - **FileUpload 增强**(拖拽已支持;文件列表/预览/进度待补)
151
- - **VirtualTable**(VirtualList 已有,表格虚拟化待补)
148
+ | 组件 | 类型 | 要点 |
149
+ |------|------|------|
150
+ | **Menu 子菜单/折叠** | 增强 | `children` 子菜单 + `collapsible` 折叠侧栏 + 多级键盘流 |
151
+ | **FileUpload 增强** | 增强 | 图片缩略图(objectURL revoke)+ 进度条 + 受控纪律 |
152
+ | **VirtualTable** | 新增 | 固定表头 + 虚拟滚动(10k 行 ~17 DOM)+ 排序 |
153
+ | **Anchor** | 新增 | 滚动高亮跟随 + 点击平滑滚动 + useHash |
154
+ | **LogViewer** | 新增 | ANSI 着色 + 虚拟滚动 + follow 自动跟随 + maxLines |
155
+ | **JSONViewer** | 新增 | 递归折叠 + 类型色 + 路径复制 + 懒展开(ToolCallCard 已接入) |
152
156
 
153
- > 难度阶梯(L1-L6)与验证的 client 能力点:见 `design/components-cdd.md`;三库 → weifuwu 覆盖矩阵:见 `design/components-migration.md`。
157
+ > 三库共识覆盖度 ~100%(剩余均为已声明裁剪项,见 `design/components-roadmap.md` 第六批裁剪清单)。
@@ -207,6 +207,32 @@ const CollapseItem: Component<{ active?: boolean; onChange?: (v: boolean) => voi
207
207
 
208
208
  **纪律**:组件内动画事件监听**唯一入口是 `useAnimationEnd`**——禁直接 `addEventListener('animationend')`(DatePicker 已收敛);退场优先 `usePresence`(声明式状态机)或 `animateOut`(命令式)。
209
209
 
210
+ ## 8.6 浏览器环境纪律(ctx.browser)
211
+
212
+ > **自定义组件禁止直接引用 window/document**——统一经 `ctx.browser`:
213
+ > SSR 安全(shim 安全默认)+ 测试 mock 单点 + 环境差异隔离。
214
+
215
+ ```tsx
216
+ const MyComp: Component = (_init, ctx) => {
217
+ // mount 层取 browser(ctx.browser 优先,测试/无注入环境 fallback jsdom)
218
+ const browser = ctx.browser ?? createClientBrowser()
219
+ return (props) =>
220
+ h('button', {
221
+ onClick: () => {
222
+ // 复制/查询/存储/滚动——全部经 browser
223
+ void browser.copyText('hello')
224
+ const el = browser.byId('target')
225
+ }
226
+ })
227
+ }
228
+ ```
229
+
230
+ **规则**:
231
+ - 复制统一 `browser.copyText`(clipboard + 降级——勿自建 textarea+execCommand)
232
+ - 键盘导航用 `browser.activeElement()`(勿 document.activeElement)
233
+ - 存储用 `browser.storageGet/Set`(勿 localStorage 裸调)
234
+ - SSR 场景(render/mount 期)绝对不碰环境 API(shim 返回 null——需防御)
235
+
210
236
  ## 9. 样式纪律(style-audit 强制)
211
237
 
212
238
  - 动效用 Token:`--wf-dur-*` / `--wf-ease-*` / `--wf-motion-*`(禁硬编码)
package/docs/frontend.md CHANGED
@@ -143,6 +143,34 @@ ctx.ui.render(['name'])
143
143
 
144
144
  ---
145
145
 
146
+ ## 浏览器环境抽象(ctx.browser)
147
+
148
+ > 组件**不直接引用 window/document**——统一经 `ctx.browser`(环境 API):
149
+ > SSR 安全(shim 返回安全默认)+ 测试可 mock + 环境差异单点隔离。
150
+
151
+ | 方法 | 说明 |
152
+ |------|------|
153
+ | `activeElement()` | 当前焦点元素(键盘导航) |
154
+ | `byId(id)` / `query(sel)` | 元素查询(getElementById / querySelector) |
155
+ | `createElement(tag)` / `bodyAppend(el)` / `bodyRemove(el)` | 动态创建/挂载容器 |
156
+ | `copyText(text)` | **复制统一入口**(clipboard API + execCommand 降级) |
157
+ | `execCommand(cmd, value?)` | 富文本编辑器命令 |
158
+ | `selectionText()` / `getSelection()` | 编辑器选区(文本 / 完整 Selection 对象) |
159
+ | `viewportHeight()` / `scrollTop()` | 视口高度 / 滚动量(scrollingElement 优先) |
160
+ | `hash()` / `setHash(h)` | 锚点 hash |
161
+ | `timeout(fn, ms)` | 定时器(SSR no-op) |
162
+ | `rootElement()` | document.documentElement(主题应用) |
163
+ | `storageGet(key)` / `storageSet(key, val)` | localStorage(SSR/隐私模式安全) |
164
+
165
+ **三态实现**:客户端 `createClientBrowser`(惰性 typeof 防御)· SSR shim(null/0/no-op)· 测试 mock 或 jsdom fallback。
166
+
167
+ ```tsx
168
+ // 组件内(mount 层)
169
+ const browser = ctx.browser ?? createClientBrowser()
170
+ // 事件回调
171
+ await browser.copyText(text)
172
+ ```
173
+
146
174
  ## 状态管理
147
175
 
148
176
  ### ctx.ui 方法速查