niuma-ui 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/NOTICE +2 -2
  3. package/package.json +13 -5
  4. package/src/__tests__/RsButton.spec.ts +47 -0
  5. package/src/__tests__/RsCard.spec.ts +160 -1
  6. package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
  7. package/src/__tests__/RsContextMenu.spec.ts +12 -0
  8. package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
  9. package/src/__tests__/RsDescriptions.spec.ts +50 -0
  10. package/src/__tests__/RsDialog.spec.ts +117 -0
  11. package/src/__tests__/RsDrawer.spec.ts +96 -3
  12. package/src/__tests__/RsDynamicTags.spec.ts +52 -0
  13. package/src/__tests__/RsForm.spec.ts +245 -1
  14. package/src/__tests__/RsInput.spec.ts +15 -1
  15. package/src/__tests__/RsMenu.spec.ts +406 -14
  16. package/src/__tests__/RsPagination.spec.ts +67 -2
  17. package/src/__tests__/RsSelect.spec.ts +42 -1
  18. package/src/__tests__/RsSwitch.spec.ts +44 -0
  19. package/src/__tests__/RsTable.a11y.spec.ts +83 -0
  20. package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
  21. package/src/__tests__/RsTable.perf.spec.ts +23 -4
  22. package/src/__tests__/RsTable.spec.ts +63 -8
  23. package/src/__tests__/RsTable.ssr.spec.ts +71 -0
  24. package/src/__tests__/RsTable.summary.spec.ts +45 -0
  25. package/src/__tests__/RsTabs.spec.ts +102 -4
  26. package/src/__tests__/RsTerminal.spec.ts +1 -1
  27. package/src/__tests__/RsTimePicker.spec.ts +11 -0
  28. package/src/__tests__/RsUpload.spec.ts +62 -6
  29. package/src/__tests__/createRsDialog.spec.ts +145 -0
  30. package/src/__tests__/form-rules.spec.ts +76 -0
  31. package/src/__tests__/perf-budgets.ts +32 -0
  32. package/src/__tests__/rs-table-api.spec.ts +206 -0
  33. package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
  34. package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
  35. package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
  36. package/src/__tests__/rs-table-isolation.spec.ts +158 -0
  37. package/src/__tests__/split-pane-utils.spec.ts +21 -0
  38. package/src/__tests__/table-chart-utils.spec.ts +88 -0
  39. package/src/__tests__/table-summary-utils.spec.ts +62 -0
  40. package/src/__tests__/table-utils.spec.ts +124 -0
  41. package/src/__tests__/theme.spec.ts +25 -0
  42. package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
  43. package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
  44. package/src/components/RsAlert.vue +212 -0
  45. package/src/components/RsButton.vue +145 -21
  46. package/src/components/RsCard.vue +200 -16
  47. package/src/components/RsConfirmDialog.vue +215 -28
  48. package/src/components/RsContextMenu.vue +51 -13
  49. package/src/components/RsDatePicker.vue +267 -34
  50. package/src/components/RsDateTimePicker.vue +19 -2
  51. package/src/components/RsDescriptions.vue +186 -0
  52. package/src/components/RsDescriptionsItem.vue +32 -0
  53. package/src/components/RsDialog.vue +363 -51
  54. package/src/components/RsDivider.vue +89 -0
  55. package/src/components/RsDrawer.vue +558 -33
  56. package/src/components/RsDropdown.vue +33 -2
  57. package/src/components/RsDynamicTags.vue +517 -0
  58. package/src/components/RsForm.vue +97 -8
  59. package/src/components/RsInput.vue +80 -82
  60. package/src/components/RsInputNumber.vue +64 -23
  61. package/src/components/RsLabel.vue +20 -5
  62. package/src/components/RsLoadingBar.vue +151 -0
  63. package/src/components/RsMenu.vue +73 -15
  64. package/src/components/RsMenuItems.vue +147 -9
  65. package/src/components/RsPagination.vue +93 -14
  66. package/src/components/RsRadio.vue +81 -0
  67. package/src/components/RsRadioItem.vue +156 -0
  68. package/src/components/RsSelect.vue +67 -58
  69. package/src/components/RsSplitPane.vue +69 -1
  70. package/src/components/RsSwitch.vue +179 -0
  71. package/src/components/RsTable.vue +653 -2555
  72. package/src/components/RsTabs.vue +415 -83
  73. package/src/components/RsTag.vue +191 -0
  74. package/src/components/RsTerminal.vue +3 -3
  75. package/src/components/RsTimePicker.vue +44 -5
  76. package/src/components/RsTimePickerColumns.vue +136 -77
  77. package/src/components/RsToaster.vue +58 -18
  78. package/src/components/RsTree.vue +39 -21
  79. package/src/components/RsUpload.vue +145 -12
  80. package/src/components/RsVNodeHost.vue +20 -0
  81. package/src/components/button-utils.ts +39 -0
  82. package/src/components/context-menu-utils.ts +6 -0
  83. package/src/components/date-picker-utils.ts +84 -4
  84. package/src/components/descriptions-utils.ts +24 -0
  85. package/src/components/dialog-utils.ts +147 -0
  86. package/src/components/dialog-window.ts +74 -9
  87. package/src/components/drawer-utils.ts +73 -0
  88. package/src/components/form-rules.ts +257 -0
  89. package/src/components/form-utils.ts +52 -2
  90. package/src/components/menu-utils.ts +11 -0
  91. package/src/components/overlay-utils.ts +3 -0
  92. package/src/components/radio-utils.ts +12 -0
  93. package/src/components/reka.ts +5 -0
  94. package/src/components/select-utils.ts +21 -2
  95. package/src/components/split-pane-utils.ts +65 -12
  96. package/src/components/table/RsTableBody.vue +217 -0
  97. package/src/components/table/RsTableBodyRow.vue +90 -1
  98. package/src/components/table/RsTableColGroup.vue +41 -0
  99. package/src/components/table/RsTableHeader.vue +158 -0
  100. package/src/components/table/RsTableSummaryRow.vue +52 -0
  101. package/src/components/table/rs-table-api.ts +99 -0
  102. package/src/components/table/rs-table-compat-matrix.ts +71 -0
  103. package/src/components/table/rs-table-echarts-adapter.ts +81 -0
  104. package/src/components/table/rs-table-feature-host.ts +178 -0
  105. package/src/components/table/rs-table-grid-nav.ts +122 -0
  106. package/src/components/table/rs-table-header-types.ts +47 -0
  107. package/src/components/table/rs-table-module-registry.ts +72 -0
  108. package/src/components/table/rs-table-props.ts +274 -0
  109. package/src/components/table/rs-table-view-context.ts +258 -0
  110. package/src/components/table/rs-table.css +84 -6
  111. package/src/components/table/table-chart-utils.ts +165 -0
  112. package/src/components/table/table-features.ts +284 -0
  113. package/src/components/table/table-summary-utils.ts +194 -0
  114. package/src/components/table-utils.ts +452 -2
  115. package/src/components/tabs-utils.ts +142 -2
  116. package/src/components/terminal-utils.ts +1 -1
  117. package/src/components/terminal-wheel.ts +1 -1
  118. package/src/components/time-picker-utils.ts +76 -3
  119. package/src/components/upload-utils.ts +122 -0
  120. package/src/components/virtual-list-utils.ts +15 -2
  121. package/src/composables/assembleRsTableApi.ts +79 -0
  122. package/src/composables/bindRsTableViewContext.ts +269 -0
  123. package/src/composables/createRsDialog.ts +357 -0
  124. package/src/composables/createRsTableEditEmitBridge.ts +38 -0
  125. package/src/composables/useRsLoadingBar.ts +36 -0
  126. package/src/composables/useRsTable.ts +134 -0
  127. package/src/composables/useRsTableA11y.ts +52 -0
  128. package/src/composables/useRsTableChartBridge.ts +79 -0
  129. package/src/composables/useRsTableColumnLayout.ts +320 -0
  130. package/src/composables/useRsTableColumnResize.ts +244 -0
  131. package/src/composables/useRsTableColumnVirtual.ts +180 -0
  132. package/src/composables/useRsTableColumns.ts +200 -0
  133. package/src/composables/useRsTableContextMenu.ts +257 -0
  134. package/src/composables/useRsTableCore.ts +256 -0
  135. package/src/composables/useRsTableEditActions.ts +606 -0
  136. package/src/composables/useRsTableEditLayer.ts +251 -0
  137. package/src/composables/useRsTableEngine.ts +357 -0
  138. package/src/composables/useRsTableGridKeyboard.ts +123 -0
  139. package/src/composables/useRsTableHeadless.ts +137 -0
  140. package/src/composables/useRsTableInteraction.ts +433 -0
  141. package/src/composables/useRsTableScrollHost.ts +75 -0
  142. package/src/composables/useRsTableScrollLayout.ts +255 -0
  143. package/src/composables/useRsTableSelectionSource.ts +89 -0
  144. package/src/composables/useRsTableShell.ts +416 -0
  145. package/src/composables/useRsTableShellChrome.ts +170 -0
  146. package/src/composables/useRsTableSummary.ts +113 -0
  147. package/src/composables/useRsTableViewProvide.ts +263 -0
  148. package/src/composables/useRsTableVirtual.ts +154 -0
  149. package/src/composables/useRsTabsNav.ts +4 -0
  150. package/src/composables/useTableEdit.ts +1 -1
  151. package/src/icons/lucide.ts +2 -2
  152. package/src/index.ts +309 -9
  153. package/src/locale/messages.ts +30 -0
  154. package/src/styles.css +305 -52
  155. package/src/theme/brand.example.css +21 -2
  156. package/src/theme/presets.ts +4 -0
  157. package/src/theme/types.ts +67 -0
  158. package/src/components/RsTableColgroup.vue +0 -17
  159. package/src/components/RsTableHeaderRow.vue +0 -30
@@ -8,22 +8,23 @@ import { RS_COMPONENT_SIZE_ICON_PX } from '../theme/types'
8
8
 
9
9
  import RsIcon from './RsIcon.vue'
10
10
  import { useRsFormContext, useRsFormField } from './form-utils'
11
+ import {
12
+ buildLocalInputRules,
13
+ runFormFieldRules,
14
+ type RsFormRuleTrigger,
15
+ } from './form-rules'
11
16
  import { useResolvedRsComponentSize } from './resolve-size'
12
17
  import { rsRadiusCss, useResolvedRsRadius } from './resolve-radius'
13
18
 
14
19
  import {
15
-
16
20
  buildOptionDisabledMap,
17
-
18
21
  buildOptionLabelMap,
19
-
20
22
  filterSelectOptions,
21
-
22
23
  flattenSelectOptions,
23
-
24
24
  flattenSelectValues,
25
-
25
+ fromComboboxValue,
26
26
  isSelectOptionGroup,
27
+ toComboboxValue,
27
28
  type RsSelectOptions,
28
29
  } from './select-utils'
29
30
 
@@ -92,6 +93,8 @@ const props = withDefaults(
92
93
 
93
94
  multiple?: boolean
94
95
  required?: boolean
96
+ /** 字段名:匹配 RsForm.rules[name] */
97
+ name?: string
95
98
 
96
99
  /** 显示清空按钮 */
97
100
 
@@ -220,17 +223,12 @@ const flatOptions = computed(() => flattenSelectOptions(props.options))
220
223
 
221
224
  const flatCount = computed(() => flatOptions.value.length)
222
225
 
223
-
224
-
225
226
  const useVirtual = computed(
226
-
227
227
  () => props.virtual || flatCount.value > props.virtualThreshold,
228
-
229
228
  )
230
229
 
231
230
  const useManualFilter = computed(() => props.remote || useVirtual.value)
232
231
 
233
-
234
232
  const displayOptions = computed(() => {
235
233
  if (props.remote) return props.options
236
234
  if (!useManualFilter.value) return props.options
@@ -259,28 +257,18 @@ const virtualValues = computed(() => {
259
257
  return [createValue.value, ...values.filter((v) => String(v) !== createValue.value)]
260
258
  })
261
259
 
262
-
263
-
264
260
  const selectedValues = computed<string[]>(() => {
265
-
266
261
  if (props.multiple) {
267
-
268
262
  const value = model.value
269
-
270
263
  if (Array.isArray(value)) return value.map(String)
271
-
272
264
  return value ? [String(value)] : []
273
-
274
265
  }
275
266
 
276
267
  const value = model.value
277
-
268
+ // '' 表示未选中
278
269
  return value !== '' && value !== undefined && value !== null ? [String(value)] : []
279
-
280
270
  })
281
271
 
282
-
283
-
284
272
  const hasValue = computed(() => selectedValues.value.length > 0)
285
273
  const resolvedDisabled = computed(() => props.disabled || formContext?.disabled.value || false)
286
274
  const resolvedSize = useResolvedRsComponentSize(() => props.size)
@@ -290,42 +278,30 @@ const rootStyle = computed(() => ({
290
278
  '--rs-select-radius': rsRadiusCss(resolvedRadius.value),
291
279
  }))
292
280
 
293
-
294
-
295
281
  const singleDisplayLabel = computed(() => {
296
-
297
282
  if (props.multiple || !hasValue.value) return ''
298
-
299
283
  return labelMap.value.get(selectedValues.value[0]!) ?? selectedValues.value[0]!
300
-
301
284
  })
302
285
 
303
-
304
-
305
286
  const comboboxModel = computed<string | string[] | undefined>({
306
-
307
287
  get() {
308
-
309
- if (props.multiple) return selectedValues.value
310
-
311
- return hasValue.value ? selectedValues.value[0] : undefined
312
-
288
+ if (props.multiple) return selectedValues.value.map(toComboboxValue)
289
+ return hasValue.value ? toComboboxValue(selectedValues.value[0]!) : undefined
313
290
  },
314
-
315
291
  set(value) {
316
-
317
292
  if (props.multiple) {
318
-
319
- model.value = Array.isArray(value) ? value.map(String) : []
320
-
321
- } else {
322
-
323
- model.value = value == null || value === '' ? '' : String(value)
324
-
293
+ model.value = Array.isArray(value)
294
+ ? value.map((item) => fromComboboxValue(String(item)))
295
+ : []
296
+ return
325
297
  }
326
-
298
+ // Reka 清空 / 空串哨兵 → 对外 ''
299
+ if (value == null || value === '') {
300
+ model.value = ''
301
+ return
302
+ }
303
+ model.value = fromComboboxValue(String(value))
327
304
  },
328
-
329
305
  })
330
306
 
331
307
 
@@ -367,13 +343,9 @@ function onSearchKeydown(event: KeyboardEvent): void {
367
343
 
368
344
 
369
345
  function onClear(event: MouseEvent) {
370
-
371
346
  event.preventDefault()
372
-
373
347
  event.stopPropagation()
374
-
375
348
  model.value = props.multiple ? [] : ''
376
-
377
349
  }
378
350
 
379
351
 
@@ -404,20 +376,43 @@ function setValue(value: unknown): void {
404
376
  model.value = value == null ? '' : String(value)
405
377
  }
406
378
 
379
+ const autoMessage = ref('')
380
+
407
381
  function clearValidation(): void {
408
382
  searchQuery.value = ''
383
+ autoMessage.value = ''
384
+ }
385
+
386
+ async function runValidate(trigger: RsFormRuleTrigger = 'submit') {
387
+ const formRules = formContext?.getFieldRules(props.name) ?? []
388
+ const localRules = buildLocalInputRules({ required: props.required })
389
+ const rules = [...formRules, ...localRules]
390
+ if (!rules.length) {
391
+ autoMessage.value = ''
392
+ return { valid: true as const, name: props.name }
393
+ }
394
+ const result = await runFormFieldRules(model.value, rules, { trigger })
395
+ autoMessage.value = result.message ?? ''
396
+ return { valid: result.valid, message: result.message, name: props.name }
409
397
  }
410
398
 
411
399
  useRsFormField(() => ({
400
+ get name() {
401
+ return props.name
402
+ },
412
403
  getValue: () => model.value,
413
404
  setValue,
414
- validate: () => ({ valid: props.multiple ? selectedValues.value.length > 0 || !props.required : Boolean(model.value) || !props.required }),
405
+ validate: (trigger) => runValidate(trigger ?? 'submit'),
415
406
  clearValidation,
407
+ setError: (message: string) => {
408
+ autoMessage.value = message
409
+ },
416
410
  }))
417
411
 
418
412
  defineExpose({
419
413
  setValue,
420
414
  clearValidation,
415
+ validate: runValidate,
421
416
  })
422
417
 
423
418
  </script>
@@ -613,7 +608,7 @@ defineExpose({
613
608
  {{
614
609
  canCreate && String(option) === createValue
615
610
  ? createOptionLabel
616
- : (labelMap.get(String(option)) ?? option)
611
+ : (labelMap.get(String(option)) ?? fromComboboxValue(String(option)))
617
612
  }}
618
613
  </span>
619
614
  <ComboboxItemIndicator class="rs-select__item-check">
@@ -651,8 +646,8 @@ defineExpose({
651
646
 
652
647
  <ComboboxItem
653
648
  v-for="opt in entry.options"
654
- :key="opt.value"
655
- :value="opt.value"
649
+ :key="toComboboxValue(opt.value)"
650
+ :value="toComboboxValue(opt.value)"
656
651
  :disabled="opt.disabled"
657
652
  :text-value="opt.label"
658
653
  class="rs-select__item"
@@ -669,8 +664,8 @@ defineExpose({
669
664
 
670
665
  <ComboboxItem
671
666
  v-else
672
- :key="entry.value"
673
- :value="entry.value"
667
+ :key="toComboboxValue(entry.value)"
668
+ :value="toComboboxValue(entry.value)"
674
669
  :disabled="entry.disabled"
675
670
  :text-value="entry.label"
676
671
  class="rs-select__item"
@@ -705,7 +700,11 @@ defineExpose({
705
700
 
706
701
  width: 100%;
707
702
 
708
- max-width: 20rem;
703
+ max-width: 100%;
704
+
705
+ vertical-align: middle;
706
+
707
+ box-sizing: border-box;
709
708
 
710
709
  }
711
710
 
@@ -719,8 +718,12 @@ defineExpose({
719
718
 
720
719
  gap: var(--rs-space-sm);
721
720
 
721
+ box-sizing: border-box;
722
+
722
723
  width: 100%;
723
724
 
725
+ height: var(--rs-control-height-md);
726
+
724
727
  min-height: var(--rs-control-height-md);
725
728
 
726
729
  padding: 0 var(--rs-space-md);
@@ -735,7 +738,7 @@ defineExpose({
735
738
 
736
739
  font-size: var(--rs-font-size-sm);
737
740
 
738
- line-height: var(--rs-line-height-normal);
741
+ line-height: var(--rs-line-height-tight);
739
742
 
740
743
  text-align: left;
741
744
 
@@ -769,7 +772,10 @@ defineExpose({
769
772
  display: flex;
770
773
  flex-direction: column;
771
774
  width: 100%;
775
+ max-width: none;
772
776
  min-width: 0;
777
+ align-self: stretch;
778
+ box-sizing: border-box;
773
779
  }
774
780
 
775
781
  .rs-select--block .rs-select__anchor,
@@ -778,6 +784,7 @@ defineExpose({
778
784
  }
779
785
 
780
786
  .rs-select--ssm .rs-select__trigger {
787
+ height: var(--rs-control-height-ssm);
781
788
  min-height: var(--rs-control-height-ssm);
782
789
  padding: 0 var(--rs-space-xs);
783
790
  font-size: var(--rs-font-size-xs);
@@ -788,6 +795,7 @@ defineExpose({
788
795
  }
789
796
 
790
797
  .rs-select--sm .rs-select__trigger {
798
+ height: var(--rs-control-height-sm);
791
799
  min-height: var(--rs-control-height-sm);
792
800
  padding: 0 var(--rs-space-sm);
793
801
  font-size: var(--rs-font-size-xs);
@@ -798,6 +806,7 @@ defineExpose({
798
806
  }
799
807
 
800
808
  .rs-select--lg .rs-select__trigger {
809
+ height: var(--rs-control-height-lg);
801
810
  min-height: var(--rs-control-height-lg);
802
811
  padding: 0 var(--rs-space-lg);
803
812
  font-size: var(--rs-font-size-base);
@@ -8,6 +8,7 @@ import {
8
8
  expandSplitPane,
9
9
  isSplitPaneCollapsed,
10
10
  normalizeSplitSizes,
11
+ resolveSplitAutoFlags,
11
12
  resolveSplitConstraints,
12
13
  roundSize,
13
14
  splitSizesEqual,
@@ -56,6 +57,9 @@ const sizes = ref<number[]>([])
56
57
  const constraints = computed(() => resolveSplitConstraints(props.panes))
57
58
  const resizerCount = computed(() => Math.max(0, props.panes.length - 1))
58
59
 
60
+ /** 内容自适应面板标记;首次拖拽/键盘调整时会物化为百分比并清空 */
61
+ const autoFlags = ref<boolean[]>(resolveSplitAutoFlags(props.panes))
62
+
59
63
  /** 归一化初始值,作为双击复位的基准 */
60
64
  let initialSizes: number[] = normalizeSplitSizes(props.panes, modelSeed())
61
65
  sizes.value = initialSizes.slice()
@@ -72,15 +76,31 @@ watch(
72
76
  () => paneKeySignature(),
73
77
  () => {
74
78
  const provided = sizes.value.length === props.panes.length ? sizes.value : undefined
79
+ autoFlags.value = resolveSplitAutoFlags(props.panes)
75
80
  initialSizes = normalizeSplitSizes(props.panes, provided)
76
81
  sizes.value = initialSizes.slice()
77
82
  syncModel(sizes.value)
78
83
  },
79
84
  )
80
85
 
86
+ watch(
87
+ () => props.panes.map((pane) => pane.size).join('|'),
88
+ () => {
89
+ // panes 引用不变但 size 声明变化时同步 auto 标记与基准尺寸
90
+ autoFlags.value = resolveSplitAutoFlags(props.panes)
91
+ initialSizes = normalizeSplitSizes(props.panes, modelSeed())
92
+ if (!autoFlags.value.some(Boolean)) {
93
+ sizes.value = initialSizes.slice()
94
+ syncModel(sizes.value)
95
+ }
96
+ },
97
+ )
98
+
81
99
  watch(model, (value) => {
82
100
  if (!value || value.length !== props.panes.length) return
83
101
  if (splitSizesEqual(value, sizes.value)) return
102
+ // 外部受控 sizes 一律视为百分比物化结果
103
+ autoFlags.value = props.panes.map(() => false)
84
104
  sizes.value = normalizeSplitSizes(props.panes, value)
85
105
  })
86
106
 
@@ -213,6 +233,7 @@ function measureFlexPx(): number {
213
233
  function onResizerPointerDown(index: number, event: PointerEvent): void {
214
234
  if (props.disabled) return
215
235
  if (event.pointerType === 'mouse' && event.button !== 0) return
236
+ materializeAutoPanes()
216
237
  const target = event.currentTarget as HTMLElement
217
238
  target.setPointerCapture?.(event.pointerId)
218
239
  dragIndex = index
@@ -241,6 +262,7 @@ function endDrag(index: number, event: PointerEvent): void {
241
262
  // —— 键盘无障碍 ——
242
263
  function onResizerKeydown(index: number, event: KeyboardEvent): void {
243
264
  if (props.disabled) return
265
+ materializeAutoPanes()
244
266
  const horizontal = props.orientation === 'horizontal'
245
267
  const step = props.keyboardStep
246
268
  let delta = 0
@@ -281,6 +303,7 @@ function onResizerKeydown(index: number, event: KeyboardEvent): void {
281
303
 
282
304
  function onResizerDblClick(index: number): void {
283
305
  if (props.disabled) return
306
+ materializeAutoPanes()
284
307
  const a = sizes.value[index]
285
308
  const b = sizes.value[index + 1]
286
309
  const initA = initialSizes[index]
@@ -324,12 +347,48 @@ function toggleCollapseAt(index: number): void {
324
347
  emit('resize-end', sizes.value.slice())
325
348
  }
326
349
 
350
+ /** 将 auto 面板按当前像素占比物化为百分比,之后走统一拖拽逻辑 */
351
+ function materializeAutoPanes(): void {
352
+ if (!autoFlags.value.some(Boolean)) return
353
+ const total = measureFlexPx()
354
+ if (total <= 0) return
355
+ const panes = paneElements()
356
+ const measured = panes.map((el) => (axisSize(el) / total) * 100)
357
+ const next = normalizeSplitSizes(
358
+ props.panes.map((pane) => ({
359
+ ...pane,
360
+ size: typeof pane.size === 'number' ? pane.size : undefined,
361
+ })),
362
+ measured,
363
+ )
364
+ autoFlags.value = props.panes.map(() => false)
365
+ sizes.value = next
366
+ syncModel(next)
367
+ }
368
+
327
369
  function paneStyle(index: number): Record<string, string> {
370
+ if (autoFlags.value[index]) {
371
+ const pane = props.panes[index]
372
+ const style: Record<string, string> = {
373
+ flexGrow: '0',
374
+ flexShrink: '0',
375
+ flexBasis: 'auto',
376
+ }
377
+ const vertical = props.orientation === 'vertical'
378
+ if (typeof pane?.min === 'number' && pane.min > 0) {
379
+ style[vertical ? 'minHeight' : 'minWidth'] = `${pane.min}%`
380
+ }
381
+ if (typeof pane?.max === 'number' && pane.max < 100) {
382
+ style[vertical ? 'maxHeight' : 'maxWidth'] = `${pane.max}%`
383
+ }
384
+ return style
385
+ }
328
386
  const size = sizes.value[index] ?? 0
329
387
  return { flexGrow: String(size), flexShrink: '1', flexBasis: '0%' }
330
388
  }
331
389
 
332
390
  function paneCollapsed(index: number): boolean {
391
+ if (autoFlags.value[index]) return false
333
392
  return isSplitPaneCollapsed(sizes.value[index] ?? 0, constraints.value[index])
334
393
  }
335
394
 
@@ -368,6 +427,7 @@ function expand(key: string, toSize?: number): void {
368
427
  }
369
428
 
370
429
  function reset(): void {
430
+ autoFlags.value = resolveSplitAutoFlags(props.panes)
371
431
  initialSizes = normalizeSplitSizes(props.panes)
372
432
  commit(initialSizes.slice())
373
433
  emit('resize-end', sizes.value.slice())
@@ -388,7 +448,10 @@ defineExpose({ collapse, expand, reset, getSizes: () => sizes.value.slice() })
388
448
  <template v-for="(pane, index) in panes" :key="pane.key">
389
449
  <div
390
450
  class="rs-split__pane"
391
- :class="{ 'rs-split__pane--collapsed': paneCollapsed(index) }"
451
+ :class="{
452
+ 'rs-split__pane--collapsed': paneCollapsed(index),
453
+ 'rs-split__pane--auto': autoFlags[index],
454
+ }"
392
455
  :style="paneStyle(index)"
393
456
  :data-pane-key="pane.key"
394
457
  >
@@ -457,6 +520,11 @@ defineExpose({ collapse, expand, reset, getSizes: () => sizes.value.slice() })
457
520
  overflow: hidden;
458
521
  }
459
522
 
523
+ .rs-split__pane--auto {
524
+ /* 内容撑开;超出 max 时在面板内滚动 */
525
+ overflow: auto;
526
+ }
527
+
460
528
  .rs-split__pane--collapsed {
461
529
  overflow: hidden;
462
530
  }
@@ -0,0 +1,179 @@
1
+ <script setup lang="ts">
2
+ import { computed, useId } from 'vue'
3
+ import type { RsComponentSize } from '../theme/types'
4
+ import { SwitchRoot, SwitchThumb } from './reka'
5
+ import { useResolvedRsComponentSize } from './resolve-size'
6
+
7
+ const model = defineModel<boolean>({ default: false })
8
+
9
+ const props = withDefaults(
10
+ defineProps<{
11
+ disabled?: boolean
12
+ size?: RsComponentSize
13
+ /** 无障碍名称;有默认插槽文案时可省略 */
14
+ ariaLabel?: string
15
+ id?: string
16
+ }>(),
17
+ {
18
+ disabled: false,
19
+ },
20
+ )
21
+
22
+ const emit = defineEmits<{
23
+ change: [value: boolean]
24
+ }>()
25
+
26
+ const autoId = useId()
27
+ const inputId = computed(() => props.id || autoId)
28
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
29
+
30
+ const rootClass = computed(() => [
31
+ 'rs-switch',
32
+ `rs-switch--${resolvedSize.value}`,
33
+ {
34
+ 'rs-switch--checked': model.value,
35
+ 'rs-switch--disabled': props.disabled,
36
+ },
37
+ ])
38
+
39
+ function onUpdate(value: boolean): void {
40
+ model.value = value
41
+ emit('change', value)
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <div :class="rootClass">
47
+ <SwitchRoot
48
+ :id="inputId"
49
+ class="rs-switch__root"
50
+ :model-value="model"
51
+ :disabled="disabled"
52
+ :aria-label="ariaLabel"
53
+ @update:model-value="onUpdate"
54
+ >
55
+ <SwitchThumb class="rs-switch__thumb" />
56
+ </SwitchRoot>
57
+ <label v-if="$slots.default" class="rs-switch__label" :for="inputId">
58
+ <slot />
59
+ </label>
60
+ </div>
61
+ </template>
62
+
63
+ <style>
64
+ /**
65
+ * 轨道高度 = control-height * 2/3(对齐 Ant:Input SM 24 / Switch SM 16)。
66
+ * 宽高、内边距、滑块均由 token / calc 推导,不写死档位 px。
67
+ * 外层 min-height = control-height,保证与同排输入垂直居中对齐。
68
+ */
69
+ .rs-switch {
70
+ --rs-switch-pad: calc(var(--rs-switch-track-h) * 0.12);
71
+ --rs-switch-track-w: calc(var(--rs-switch-track-h) * 1.75);
72
+ --rs-switch-thumb: calc(var(--rs-switch-track-h) - var(--rs-switch-pad) * 2 - 2px);
73
+ display: inline-flex;
74
+ align-items: center;
75
+ gap: var(--rs-space-xs);
76
+ vertical-align: middle;
77
+ line-height: 1;
78
+ cursor: pointer;
79
+ user-select: none;
80
+ color: var(--rs-text);
81
+ }
82
+
83
+ .rs-switch--disabled {
84
+ cursor: not-allowed;
85
+ opacity: 0.55;
86
+ }
87
+
88
+ .rs-switch__root {
89
+ position: relative;
90
+ display: inline-flex;
91
+ flex-shrink: 0;
92
+ align-items: center;
93
+ box-sizing: border-box;
94
+ width: var(--rs-switch-track-w);
95
+ height: var(--rs-switch-track-h);
96
+ padding: var(--rs-switch-pad);
97
+ border: 1px solid var(--rs-border);
98
+ border-radius: var(--rs-radius-full);
99
+ background: var(--rs-surface-hover);
100
+ outline: none;
101
+ cursor: pointer;
102
+ transition:
103
+ background-color var(--rs-transition-fast),
104
+ border-color var(--rs-transition-fast);
105
+ }
106
+
107
+ .rs-switch--disabled .rs-switch__root {
108
+ cursor: not-allowed;
109
+ }
110
+
111
+ .rs-switch:hover:not(.rs-switch--disabled):not(.rs-switch--checked) .rs-switch__root {
112
+ border-color: var(--rs-primary);
113
+ }
114
+
115
+ .rs-switch__root:focus-visible {
116
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
117
+ }
118
+
119
+ .rs-switch--checked .rs-switch__root {
120
+ background: var(--rs-primary);
121
+ border-color: var(--rs-primary);
122
+ }
123
+
124
+ .rs-switch--ssm {
125
+ --rs-switch-track-h: calc(var(--rs-control-height-ssm) * 2 / 3);
126
+ min-height: var(--rs-control-height-ssm);
127
+ }
128
+
129
+ .rs-switch--sm {
130
+ --rs-switch-track-h: calc(var(--rs-control-height-sm) * 2 / 3);
131
+ min-height: var(--rs-control-height-sm);
132
+ }
133
+
134
+ .rs-switch--md {
135
+ --rs-switch-track-h: calc(var(--rs-control-height-md) * 2 / 3);
136
+ min-height: var(--rs-control-height-md);
137
+ }
138
+
139
+ .rs-switch--lg {
140
+ --rs-switch-track-h: calc(var(--rs-control-height-lg) * 2 / 3);
141
+ min-height: var(--rs-control-height-lg);
142
+ }
143
+
144
+ .rs-switch__thumb {
145
+ position: absolute;
146
+ top: 50%;
147
+ left: var(--rs-switch-pad);
148
+ display: block;
149
+ width: var(--rs-switch-thumb);
150
+ height: var(--rs-switch-thumb);
151
+ border-radius: var(--rs-radius-full);
152
+ background: var(--rs-surface);
153
+ box-shadow: var(--rs-shadow-sm);
154
+ transform: translateY(-50%);
155
+ transition:
156
+ left var(--rs-transition-fast),
157
+ right var(--rs-transition-fast);
158
+ }
159
+
160
+ .rs-switch--checked .rs-switch__thumb {
161
+ left: auto;
162
+ right: var(--rs-switch-pad);
163
+ }
164
+
165
+ .rs-switch__label {
166
+ font-size: var(--rs-font-size-sm);
167
+ line-height: var(--rs-line-height-normal);
168
+ cursor: pointer;
169
+ }
170
+
171
+ .rs-switch--disabled .rs-switch__label {
172
+ cursor: not-allowed;
173
+ }
174
+
175
+ .rs-switch--ssm .rs-switch__label,
176
+ .rs-switch--sm .rs-switch__label {
177
+ font-size: var(--rs-font-size-xs);
178
+ }
179
+ </style>