niuma-ui 1.0.2 → 1.1.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/package.json +1 -1
  3. package/src/__tests__/RsDatePicker.spec.ts +21 -0
  4. package/src/__tests__/RsDialog.spec.ts +61 -0
  5. package/src/__tests__/RsForm.spec.ts +5 -5
  6. package/src/__tests__/RsFormItem.spec.ts +478 -0
  7. package/src/__tests__/RsFormList.spec.ts +109 -0
  8. package/src/__tests__/RsInput.spec.ts +93 -0
  9. package/src/__tests__/RsInputNumber.spec.ts +33 -0
  10. package/src/__tests__/RsSelect.spec.ts +423 -1
  11. package/src/__tests__/RsSwitch.spec.ts +30 -0
  12. package/src/__tests__/RsTimePicker.spec.ts +9 -0
  13. package/src/__tests__/RsTooltip.spec.ts +43 -0
  14. package/src/__tests__/css-token.spec.ts +51 -0
  15. package/src/__tests__/date-picker-utils.spec.ts +49 -0
  16. package/src/__tests__/dialog-utils.spec.ts +43 -0
  17. package/src/__tests__/form-path.spec.ts +46 -0
  18. package/src/__tests__/form-rules.spec.ts +63 -0
  19. package/src/__tests__/theme.spec.ts +2 -0
  20. package/src/components/RsAlert.vue +1 -1
  21. package/src/components/RsAvatar.vue +1 -1
  22. package/src/components/RsBadge.vue +1 -1
  23. package/src/components/RsBreadcrumb.vue +1 -1
  24. package/src/components/RsButton.vue +4 -4
  25. package/src/components/RsCalendarGrid.vue +3 -3
  26. package/src/components/RsCard.vue +4 -4
  27. package/src/components/RsCodeBlock.vue +4 -4
  28. package/src/components/RsCodeEditor.vue +8 -8
  29. package/src/components/RsConfirmDialog.vue +2 -2
  30. package/src/components/RsContextMenu.vue +6 -4
  31. package/src/components/RsDatePicker.vue +75 -31
  32. package/src/components/RsDateTimePicker.vue +9 -1
  33. package/src/components/RsDescriptions.vue +2 -2
  34. package/src/components/RsDialog.vue +26 -16
  35. package/src/components/RsDrawer.vue +1 -1
  36. package/src/components/RsDropdown.vue +1 -1
  37. package/src/components/RsDynamicTags.vue +18 -4
  38. package/src/components/RsEmpty.vue +1 -1
  39. package/src/components/RsForm.vue +68 -1
  40. package/src/components/RsFormItem.vue +423 -0
  41. package/src/components/RsFormItemControl.vue +21 -0
  42. package/src/components/RsFormList.vue +117 -0
  43. package/src/components/RsInput.vue +411 -18
  44. package/src/components/RsInputNumber.vue +13 -4
  45. package/src/components/RsLink.vue +1 -1
  46. package/src/components/RsMarkdown.vue +5 -5
  47. package/src/components/RsMenu.vue +2 -2
  48. package/src/components/RsMonacoEditor.vue +9 -3
  49. package/src/components/RsPagination.vue +1 -1
  50. package/src/components/RsProseEditor.vue +3 -3
  51. package/src/components/RsSelect.vue +332 -611
  52. package/src/components/RsSidebarGroup.vue +1 -1
  53. package/src/components/RsSplitPane.vue +22 -1
  54. package/src/components/RsStatCard.vue +2 -2
  55. package/src/components/RsSteps.vue +2 -2
  56. package/src/components/RsSwitch.vue +20 -7
  57. package/src/components/RsTabs.vue +9 -9
  58. package/src/components/RsTag.vue +1 -1
  59. package/src/components/RsTerminal.vue +58 -19
  60. package/src/components/RsTimePicker.vue +28 -4
  61. package/src/components/RsTimePickerColumns.vue +4 -4
  62. package/src/components/RsToaster.vue +7 -7
  63. package/src/components/RsTooltip.vue +10 -1
  64. package/src/components/RsTooltipProvider.vue +11 -1
  65. package/src/components/RsTree.vue +6 -6
  66. package/src/components/RsUpload.vue +2 -2
  67. package/src/components/date-picker-utils.ts +113 -2
  68. package/src/components/dialog-utils.ts +37 -2
  69. package/src/components/form-item-control.ts +55 -0
  70. package/src/components/form-path.ts +92 -0
  71. package/src/components/form-rules.ts +64 -10
  72. package/src/components/form-utils.ts +94 -2
  73. package/src/components/select-utils.ts +272 -12
  74. package/src/components/table/RsTableHeaderFilter.vue +1 -1
  75. package/src/components/table/rs-table.css +1 -1
  76. package/src/components/use-rs-select.ts +436 -0
  77. package/src/composables/useRsConfig.ts +10 -3
  78. package/src/composables/useRsI18n.ts +16 -5
  79. package/src/index.ts +69 -1
  80. package/src/locale/interpolate.ts +32 -0
  81. package/src/locale/messages.ts +10 -0
  82. package/src/styles.css +68 -12
  83. package/src/theme/brand.example.css +4 -1
  84. package/src/theme/css-token.ts +107 -0
  85. package/src/theme/types.ts +1 -0
@@ -22,7 +22,7 @@ defineProps<{
22
22
  padding: var(--rs-space-xs) var(--rs-space-sm);
23
23
  color: var(--rs-muted);
24
24
  font-size: var(--rs-font-size-xs);
25
- font-weight: 600;
25
+ font-weight: var(--rs-font-weight-semibold);
26
26
  letter-spacing: 0.04em;
27
27
  }
28
28
  .rs-sidebar-group__body {
@@ -210,6 +210,10 @@ function finishDrag(index: number): void {
210
210
  setDraggingUi(false, index)
211
211
  const final = pendingDragSizes ?? sizes.value
212
212
  pendingDragSizes = null
213
+ // 清掉拖拽期手写的 flex-grow,交回 Vue :style,避免残留内联样式干扰后续布局
214
+ paneElements().forEach((el) => {
215
+ el.style.removeProperty('flex-grow')
216
+ })
213
217
  commit(final)
214
218
  emitBoundaryTransitions(dragStartSizes, final)
215
219
  emit('resize-end', final.slice())
@@ -369,12 +373,15 @@ function materializeAutoPanes(): void {
369
373
  function paneStyle(index: number): Record<string, string> {
370
374
  if (autoFlags.value[index]) {
371
375
  const pane = props.panes[index]
376
+ const vertical = props.orientation === 'vertical'
377
+ // auto 面板必须按内容撑开;不能继承基类 min-height/min-width:0,否则在
378
+ // 「另一侧 flex-grow 吃满」的分栏里会被算成 0 高/宽(KeepAlive 切页后常见)。
372
379
  const style: Record<string, string> = {
373
380
  flexGrow: '0',
374
381
  flexShrink: '0',
375
382
  flexBasis: 'auto',
383
+ [vertical ? 'minHeight' : 'minWidth']: 'min-content',
376
384
  }
377
- const vertical = props.orientation === 'vertical'
378
385
  if (typeof pane?.min === 'number' && pane.min > 0) {
379
386
  style[vertical ? 'minHeight' : 'minWidth'] = `${pane.min}%`
380
387
  }
@@ -523,6 +530,20 @@ defineExpose({ collapse, expand, reset, getSizes: () => sizes.value.slice() })
523
530
  .rs-split__pane--auto {
524
531
  /* 内容撑开;超出 max 时在面板内滚动 */
525
532
  overflow: auto;
533
+ flex: 0 0 auto;
534
+ }
535
+
536
+ /* 覆盖基类 min-*:0,避免 auto 面板被弹性侧压成 0 */
537
+ .rs-split--vertical > .rs-split__pane--auto {
538
+ min-height: min-content;
539
+ height: auto;
540
+ max-height: none;
541
+ }
542
+
543
+ .rs-split--horizontal > .rs-split__pane--auto {
544
+ min-width: min-content;
545
+ width: auto;
546
+ max-width: none;
526
547
  }
527
548
 
528
549
  .rs-split__pane--collapsed {
@@ -66,8 +66,8 @@ withDefaults(
66
66
  .rs-stat-card__value {
67
67
  margin-top: var(--rs-space-sm);
68
68
  color: var(--rs-text);
69
- font-size: 1.75rem;
70
- font-weight: 700;
69
+ font-size: var(--rs-font-size-3xl);
70
+ font-weight: var(--rs-font-weight-bold);
71
71
  line-height: 1.1;
72
72
  }
73
73
  .rs-stat-card__description {
@@ -97,7 +97,7 @@ function selectStep(item: RsStepItem): void {
97
97
  background: var(--rs-surface-hover);
98
98
  color: var(--rs-muted);
99
99
  font-size: var(--rs-font-size-xs);
100
- font-weight: 700;
100
+ font-weight: var(--rs-font-weight-bold);
101
101
  }
102
102
  .rs-steps--sm .rs-steps__indicator {
103
103
  width: 1.5rem;
@@ -119,7 +119,7 @@ function selectStep(item: RsStepItem): void {
119
119
  .rs-steps__title {
120
120
  color: var(--rs-text);
121
121
  font-size: var(--rs-font-size-sm);
122
- font-weight: 600;
122
+ font-weight: var(--rs-font-weight-semibold);
123
123
  }
124
124
  .rs-steps__description {
125
125
  color: var(--rs-muted);
@@ -4,7 +4,10 @@ import type { RsComponentSize } from '../theme/types'
4
4
  import { SwitchRoot, SwitchThumb } from './reka'
5
5
  import { useResolvedRsComponentSize } from './resolve-size'
6
6
 
7
- const model = defineModel<boolean>({ default: false })
7
+ /** Switch v-model 取值:默认 boolean,也可为业务码如 'Y'/'N'、1/0 */
8
+ export type RsSwitchValue = boolean | string | number
9
+
10
+ const model = defineModel<RsSwitchValue>({ default: false })
8
11
 
9
12
  const props = withDefaults(
10
13
  defineProps<{
@@ -13,32 +16,42 @@ const props = withDefaults(
13
16
  /** 无障碍名称;有默认插槽文案时可省略 */
14
17
  ariaLabel?: string
15
18
  id?: string
19
+ /** 打开时写入 v-model 的值,默认 true */
20
+ checkedValue?: RsSwitchValue
21
+ /** 关闭时写入 v-model 的值,默认 false */
22
+ uncheckedValue?: RsSwitchValue
16
23
  }>(),
17
24
  {
18
25
  disabled: false,
26
+ checkedValue: true,
27
+ uncheckedValue: false,
19
28
  },
20
29
  )
21
30
 
22
31
  const emit = defineEmits<{
23
- change: [value: boolean]
32
+ change: [value: RsSwitchValue]
24
33
  }>()
25
34
 
26
35
  const autoId = useId()
27
36
  const inputId = computed(() => props.id || autoId)
28
37
  const resolvedSize = useResolvedRsComponentSize(() => props.size)
38
+ /** Reka Switch 只吃 boolean;对外 v-model 按 checkedValue / uncheckedValue 映射 */
39
+ const isChecked = computed(() => Object.is(model.value, props.checkedValue))
29
40
 
30
41
  const rootClass = computed(() => [
31
42
  'rs-switch',
32
43
  `rs-switch--${resolvedSize.value}`,
33
44
  {
34
- 'rs-switch--checked': model.value,
45
+ 'rs-switch--checked': isChecked.value,
35
46
  'rs-switch--disabled': props.disabled,
36
47
  },
37
48
  ])
38
49
 
39
- function onUpdate(value: boolean): void {
40
- model.value = value
41
- emit('change', value)
50
+ function onUpdate(checked: boolean): void {
51
+ const next = checked ? props.checkedValue : props.uncheckedValue
52
+ if (Object.is(model.value, next)) return
53
+ model.value = next
54
+ emit('change', next)
42
55
  }
43
56
  </script>
44
57
 
@@ -47,7 +60,7 @@ function onUpdate(value: boolean): void {
47
60
  <SwitchRoot
48
61
  :id="inputId"
49
62
  class="rs-switch__root"
50
- :model-value="model"
63
+ :model-value="isChecked"
51
64
  :disabled="disabled"
52
65
  :aria-label="ariaLabel"
53
66
  @update:model-value="onUpdate"
@@ -409,8 +409,8 @@ function onDragEnd(): void {
409
409
  <div
410
410
  ref="navViewportRef"
411
411
  class="rs-tabs__nav-viewport"
412
- @scroll="onNavScroll"
413
- @wheel="onNavWheel"
412
+ @scroll.passive="onNavScroll"
413
+ v-bind="useScrollOverflow ? { onWheel: onNavWheel } : {}"
414
414
  >
415
415
  <TabsList class="rs-tabs__list">
416
416
  <RsContextMenu
@@ -679,7 +679,7 @@ function onDragEnd(): void {
679
679
  }
680
680
 
681
681
  .rs-tabs__trigger--fixed {
682
- font-weight: 500;
682
+ font-weight: var(--rs-font-weight-medium);
683
683
  }
684
684
 
685
685
  .rs-tabs--draggable .rs-tabs__trigger--movable {
@@ -733,7 +733,7 @@ function onDragEnd(): void {
733
733
  background: var(--rs-surface-hover);
734
734
  color: var(--rs-muted);
735
735
  font-size: var(--rs-font-size-xs);
736
- font-weight: 500;
736
+ font-weight: var(--rs-font-weight-medium);
737
737
  font-variant-numeric: tabular-nums;
738
738
  line-height: 1.4;
739
739
  }
@@ -910,7 +910,7 @@ function onDragEnd(): void {
910
910
 
911
911
  .rs-tabs__more--active {
912
912
  color: var(--rs-primary);
913
- font-weight: 500;
913
+ font-weight: var(--rs-font-weight-medium);
914
914
  }
915
915
 
916
916
  .rs-tabs__more-menu {
@@ -944,7 +944,7 @@ function onDragEnd(): void {
944
944
 
945
945
  .rs-tabs__more-item--active {
946
946
  color: var(--rs-primary);
947
- font-weight: 500;
947
+ font-weight: var(--rs-font-weight-medium);
948
948
  background: color-mix(in srgb, var(--rs-primary) 12%, transparent);
949
949
  }
950
950
 
@@ -1034,7 +1034,7 @@ function onDragEnd(): void {
1034
1034
 
1035
1035
  .rs-tabs--line .rs-tabs__trigger[data-state='active'] {
1036
1036
  color: var(--rs-primary);
1037
- font-weight: 500;
1037
+ font-weight: var(--rs-font-weight-medium);
1038
1038
  }
1039
1039
 
1040
1040
  .rs-tabs--line .rs-tabs__trigger[data-state='active']::after {
@@ -1113,7 +1113,7 @@ function onDragEnd(): void {
1113
1113
  .rs-tabs--segmented .rs-tabs__trigger[data-state='active'] {
1114
1114
  color: var(--rs-primary);
1115
1115
  background: var(--rs-surface);
1116
- font-weight: 500;
1116
+ font-weight: var(--rs-font-weight-medium);
1117
1117
  box-shadow: 0 1px 2px color-mix(in srgb, var(--rs-text) 8%, transparent);
1118
1118
  }
1119
1119
 
@@ -1169,7 +1169,7 @@ function onDragEnd(): void {
1169
1169
 
1170
1170
  .rs-tabs--card .rs-tabs__trigger[data-state='active'] {
1171
1171
  color: var(--rs-primary);
1172
- font-weight: 500;
1172
+ font-weight: var(--rs-font-weight-medium);
1173
1173
  background: var(--rs-surface);
1174
1174
  border-color: var(--rs-border);
1175
1175
  margin-bottom: -1px;
@@ -84,7 +84,7 @@ function onClose(event: MouseEvent): void {
84
84
  gap: var(--rs-space-xs);
85
85
  max-width: 100%;
86
86
  border-radius: var(--rs-tag-radius, var(--rs-radius-sm));
87
- font-weight: var(--rs-tag-font-weight, 500);
87
+ font-weight: var(--rs-tag-font-weight, 400);
88
88
  line-height: var(--rs-line-height-tight);
89
89
  box-sizing: border-box;
90
90
  vertical-align: middle;
@@ -5,6 +5,12 @@ import { Terminal } from '@xterm/xterm'
5
5
  import '@xterm/xterm/css/xterm.css'
6
6
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
7
7
  import { useRsI18n } from '../composables/useRsI18n'
8
+ import {
9
+ readTerminalFontFamily,
10
+ readTerminalFontSizePx,
11
+ readTerminalFontWeight,
12
+ readTerminalFontWeightBold,
13
+ } from '../theme/css-token'
8
14
  import {
9
15
  beginClipboardPrefetch,
10
16
  copyTextToClipboard,
@@ -29,16 +35,33 @@ import {
29
35
 
30
36
  const TERMINAL_LINE_HEIGHT = 1.2
31
37
 
38
+ type RsTerminalFontWeight =
39
+ | 'normal'
40
+ | 'bold'
41
+ | '100'
42
+ | '200'
43
+ | '300'
44
+ | '400'
45
+ | '500'
46
+ | '600'
47
+ | '700'
48
+ | '800'
49
+ | '900'
50
+
32
51
  const props = withDefaults(
33
52
  defineProps<{
34
53
  loading?: boolean
35
54
  overlay?: string
36
55
  inputEnabled?: boolean
37
56
  cursorBlink?: boolean
57
+ /** 缺省读取 `--rs-terminal-font-family` / `--rs-font-mono` */
38
58
  fontFamily?: string
59
+ /** 缺省读取 `--rs-terminal-font-size`(对齐 `--rs-font-size-sm`) */
39
60
  fontSize?: number
40
- fontWeight?: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'
41
- fontWeightBold?: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'
61
+ /** 缺省读取 `--rs-terminal-font-weight` */
62
+ fontWeight?: RsTerminalFontWeight
63
+ /** 缺省读取 `--rs-terminal-font-weight-bold` */
64
+ fontWeightBold?: RsTerminalFontWeight
42
65
  allowTransparency?: boolean
43
66
  /** auto 跟随 data-rs-theme;也可强制 light / dark */
44
67
  themeMode?: RsTerminalThemeMode
@@ -68,11 +91,6 @@ const props = withDefaults(
68
91
  overlay: '',
69
92
  inputEnabled: true,
70
93
  cursorBlink: true,
71
- fontFamily:
72
- '"SF Mono", "Cascadia Code", "Cascadia Mono", Consolas, "Liberation Mono", Menlo, monospace',
73
- fontSize: 13,
74
- fontWeight: '300',
75
- fontWeightBold: '400',
76
94
  allowTransparency: false,
77
95
  themeMode: 'auto',
78
96
  theme: () => ({}),
@@ -109,13 +127,34 @@ const zebraRowStepPx = ref<number | null>(null)
109
127
 
110
128
  const showLoading = computed(() => !terminalReady.value || props.loading)
111
129
 
130
+ function asTerminalFontWeight(value: string): RsTerminalFontWeight {
131
+ return value as RsTerminalFontWeight
132
+ }
133
+
134
+ function resolveTerminalFontFamily(): string {
135
+ return props.fontFamily || readTerminalFontFamily(hostEl.value)
136
+ }
137
+
138
+ function resolveTerminalFontSize(): number {
139
+ return props.fontSize ?? readTerminalFontSizePx(hostEl.value)
140
+ }
141
+
142
+ function resolveTerminalFontWeight(): RsTerminalFontWeight {
143
+ return props.fontWeight ?? asTerminalFontWeight(readTerminalFontWeight(hostEl.value))
144
+ }
145
+
146
+ function resolveTerminalFontWeightBold(): RsTerminalFontWeight {
147
+ return props.fontWeightBold ?? asTerminalFontWeight(readTerminalFontWeightBold(hostEl.value))
148
+ }
149
+
112
150
  const zebraStyle = computed((): Record<string, string> | undefined => {
113
151
  if (!props.zebraStripes) {
114
152
  return undefined
115
153
  }
116
- const step = zebraRowStepPx.value ?? props.fontSize * TERMINAL_LINE_HEIGHT
154
+ const fontSize = resolveTerminalFontSize()
155
+ const step = zebraRowStepPx.value ?? fontSize * TERMINAL_LINE_HEIGHT
117
156
  return {
118
- '--rs-terminal-font-size': `${props.fontSize}px`,
157
+ '--rs-terminal-font-size': `${fontSize}px`,
119
158
  '--rs-terminal-line-height': String(TERMINAL_LINE_HEIGHT),
120
159
  '--rs-terminal-zebra-step': `${step}px`,
121
160
  }
@@ -468,10 +507,10 @@ watch(
468
507
  }
469
508
  refreshResolvedTheme()
470
509
  terminal.options.cursorBlink = props.cursorBlink
471
- terminal.options.fontFamily = props.fontFamily
472
- terminal.options.fontSize = props.fontSize
473
- terminal.options.fontWeight = props.fontWeight
474
- terminal.options.fontWeightBold = props.fontWeightBold
510
+ terminal.options.fontFamily = resolveTerminalFontFamily()
511
+ terminal.options.fontSize = resolveTerminalFontSize()
512
+ terminal.options.fontWeight = resolveTerminalFontWeight()
513
+ terminal.options.fontWeightBold = resolveTerminalFontWeightBold()
475
514
  terminal.options.allowTransparency = resolveAllowTransparency()
476
515
  terminal.options.convertEol = props.convertEol
477
516
  attachWheelGuard()
@@ -487,10 +526,10 @@ onMounted(async () => {
487
526
  refreshResolvedTheme()
488
527
  terminal = new Terminal({
489
528
  cursorBlink: props.cursorBlink,
490
- fontFamily: props.fontFamily,
491
- fontSize: props.fontSize,
492
- fontWeight: props.fontWeight,
493
- fontWeightBold: props.fontWeightBold,
529
+ fontFamily: resolveTerminalFontFamily(),
530
+ fontSize: resolveTerminalFontSize(),
531
+ fontWeight: resolveTerminalFontWeight(),
532
+ fontWeightBold: resolveTerminalFontWeightBold(),
494
533
  lineHeight: TERMINAL_LINE_HEIGHT,
495
534
  allowTransparency: resolveAllowTransparency(),
496
535
  drawBoldTextInBrightColors: true,
@@ -636,7 +675,7 @@ defineExpose({
636
675
  pointer-events: none;
637
676
  font-family: var(--rs-font-mono);
638
677
  font-size: var(--rs-font-size-xs);
639
- font-weight: 400;
678
+ font-weight: var(--rs-font-weight-regular);
640
679
  line-height: var(--rs-line-height-normal);
641
680
  }
642
681
 
@@ -658,7 +697,7 @@ defineExpose({
658
697
  }
659
698
 
660
699
  .rs-terminal--zebra :deep(.xterm) {
661
- font-size: var(--rs-terminal-font-size, 13px);
700
+ font-size: var(--rs-terminal-font-size);
662
701
  line-height: var(--rs-terminal-line-height, 1.2);
663
702
  }
664
703
 
@@ -1,11 +1,12 @@
1
1
  <script setup lang="ts">
2
- import { computed, nextTick, ref, useId, useTemplateRef, watch } from 'vue'
2
+ import { computed, nextTick, ref, useAttrs, useId, useTemplateRef, watch } from 'vue'
3
3
  import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from './reka'
4
4
  import RsIcon from './RsIcon.vue'
5
5
  import RsTimePickerColumns from './RsTimePickerColumns.vue'
6
6
  import { useRsI18n } from '../composables/useRsI18n'
7
7
  import { RS_COMPONENT_SIZE_ICON_PX, type RsComponentSize } from '../theme/types'
8
8
  import { useResolvedRsComponentSize } from './resolve-size'
9
+ import { isRsFormItemBoundControl, useRsFormItemContext } from './form-utils'
9
10
  import {
10
11
  EMPTY_TIME_RANGE,
11
12
  formatTimeFromParts,
@@ -21,7 +22,7 @@ import {
21
22
  type RsTimeUnit,
22
23
  } from './time-picker-utils'
23
24
 
24
- defineOptions({ name: 'RsTimePicker' })
25
+ defineOptions({ name: 'RsTimePicker', inheritAttrs: false })
25
26
 
26
27
  export type { RsTimeRangeValue } from './time-picker-utils'
27
28
  export type RsTimePickerLabelPosition = 'top' | 'left'
@@ -47,6 +48,8 @@ const props = withDefaults(
47
48
  embedded?: boolean
48
49
  labelPosition?: RsTimePickerLabelPosition
49
50
  size?: RsComponentSize
51
+ id?: string
52
+ invalid?: boolean
50
53
  }>(),
51
54
  {
52
55
  disabled: false,
@@ -59,7 +62,14 @@ const props = withDefaults(
59
62
  )
60
63
 
61
64
  const fieldId = useId()
65
+ const attrs = useAttrs()
62
66
  const { t } = useRsI18n()
67
+ const formItem = useRsFormItemContext()
68
+ const boundToItem = computed(() => isRsFormItemBoundControl(formItem, { id: props.id }))
69
+ const isInvalid = computed(() =>
70
+ Boolean(props.invalid || (boundToItem.value && formItem?.invalid.value)),
71
+ )
72
+ const triggerId = computed(() => props.id || fieldId)
63
73
  const resolvedSize = useResolvedRsComponentSize(() => props.size)
64
74
  const triggerIconSize = computed(() => RS_COMPONENT_SIZE_ICON_PX[resolvedSize.value])
65
75
  const columnsRef = useTemplateRef<InstanceType<typeof RsTimePickerColumns>>('columnsRef')
@@ -294,14 +304,17 @@ watch(open, async (isOpen) => {
294
304
  <div class="rs-time-picker">
295
305
  <PopoverRoot v-model:open="open" :modal="!embedded">
296
306
  <PopoverTrigger
297
- :id="embedded ? undefined : fieldId"
307
+ v-bind="embedded ? undefined : attrs"
308
+ :id="embedded ? undefined : triggerId"
298
309
  type="button"
299
310
  class="rs-time-picker__trigger"
300
311
  :class="{
301
312
  'rs-time-picker__trigger--placeholder': isEmpty,
302
313
  'rs-time-picker__trigger--embedded': embedded,
314
+ 'rs-time-picker__trigger--invalid': !embedded && isInvalid,
303
315
  }"
304
316
  :disabled="disabled"
317
+ :aria-invalid="!embedded && isInvalid ? true : undefined"
305
318
  >
306
319
  <span class="rs-time-picker__leading">
307
320
  <RsIcon
@@ -421,6 +434,7 @@ watch(open, async (isOpen) => {
421
434
  line-height: var(--rs-line-height-tight);
422
435
  text-align: left;
423
436
  cursor: pointer;
437
+ outline: none;
424
438
  box-shadow: var(--rs-input-shadow, none);
425
439
  transition:
426
440
  border-color var(--rs-transition-fast),
@@ -472,6 +486,16 @@ watch(open, async (isOpen) => {
472
486
  .rs-time-picker__trigger--placeholder .rs-time-picker__value {
473
487
  color: var(--rs-placeholder);
474
488
  }
489
+ .rs-time-picker__trigger--invalid {
490
+ border-color: var(--rs-danger, var(--rs-color-danger, #dc2626));
491
+ }
492
+ .rs-time-picker__trigger--invalid:focus-visible {
493
+ border-color: var(--rs-danger, var(--rs-color-danger, #dc2626));
494
+ box-shadow:
495
+ var(--rs-input-shadow, none),
496
+ 0 0 0 var(--rs-focus-ring-width, 2px)
497
+ color-mix(in srgb, var(--rs-danger, #dc2626) 14%, transparent);
498
+ }
475
499
  .rs-time-picker__leading {
476
500
  display: inline-flex;
477
501
  min-width: 0;
@@ -536,7 +560,7 @@ watch(open, async (isOpen) => {
536
560
  }
537
561
  .rs-time-picker__pane-title {
538
562
  font-size: var(--rs-font-size-xs);
539
- font-weight: 600;
563
+ font-weight: var(--rs-font-weight-semibold);
540
564
  color: var(--rs-muted);
541
565
  }
542
566
  .rs-time-picker__footer {
@@ -186,7 +186,7 @@ defineExpose({
186
186
  --rs-time-columns-list-max-height: 10rem;
187
187
  --rs-time-columns-item-padding-y: 0.375rem;
188
188
  --rs-time-columns-item-font-size: var(--rs-font-size-sm);
189
- --rs-time-columns-label-font-size: 0.6875rem;
189
+ --rs-time-columns-label-font-size: var(--rs-font-size-xs);
190
190
  }
191
191
  .rs-time-columns--seconds {
192
192
  grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -195,14 +195,14 @@ defineExpose({
195
195
  --rs-time-columns-list-max-height: 7.5rem;
196
196
  --rs-time-columns-item-padding-y: 0.2rem;
197
197
  --rs-time-columns-item-font-size: var(--rs-font-size-xs);
198
- --rs-time-columns-label-font-size: 0.625rem;
198
+ --rs-time-columns-label-font-size: var(--rs-font-size-xs);
199
199
  gap: var(--rs-space-xs);
200
200
  }
201
201
  .rs-time-columns--sm {
202
202
  --rs-time-columns-list-max-height: 8.5rem;
203
203
  --rs-time-columns-item-padding-y: 0.28rem;
204
204
  --rs-time-columns-item-font-size: var(--rs-font-size-xs);
205
- --rs-time-columns-label-font-size: 0.625rem;
205
+ --rs-time-columns-label-font-size: var(--rs-font-size-xs);
206
206
  }
207
207
  .rs-time-columns--lg {
208
208
  --rs-time-columns-list-max-height: 12rem;
@@ -219,7 +219,7 @@ defineExpose({
219
219
  .rs-time-columns__label {
220
220
  text-align: center;
221
221
  font-size: var(--rs-time-columns-label-font-size);
222
- font-weight: 500;
222
+ font-weight: var(--rs-font-weight-medium);
223
223
  letter-spacing: 0.02em;
224
224
  color: var(--rs-muted);
225
225
  }
@@ -220,17 +220,17 @@ const sonnerTheme = computed<'light' | 'dark'>(() => {
220
220
  .rs-toast__title {
221
221
  margin: 0;
222
222
  color: var(--rs-text);
223
- font-size: 0.875rem;
224
- font-weight: 600;
225
- line-height: 1.25;
223
+ font-size: var(--rs-font-size-sm);
224
+ font-weight: var(--rs-font-weight-semibold);
225
+ line-height: var(--rs-line-height-tight);
226
226
  letter-spacing: -0.012em;
227
227
  }
228
228
 
229
229
  .rs-toast__description {
230
230
  margin: 0;
231
231
  color: color-mix(in srgb, var(--rs-muted) 94%, var(--rs-text) 6%);
232
- font-size: 0.8125rem;
233
- line-height: 1.4;
232
+ font-size: var(--rs-font-size-xs);
233
+ line-height: var(--rs-line-height-normal);
234
234
  }
235
235
 
236
236
  .rs-toast [data-close-button].rs-toast__close {
@@ -301,8 +301,8 @@ const sonnerTheme = computed<'light' | 'dark'>(() => {
301
301
  border-radius: 999px;
302
302
  background: color-mix(in srgb, var(--rs-primary) 92%, white 8%);
303
303
  color: var(--rs-primary-foreground);
304
- font-size: 0.75rem;
305
- font-weight: 600;
304
+ font-size: var(--rs-font-size-xs);
305
+ font-weight: var(--rs-font-weight-semibold);
306
306
  line-height: 1;
307
307
  letter-spacing: 0.01em;
308
308
  box-shadow:
@@ -10,6 +10,11 @@ const props = withDefaults(
10
10
  align?: 'start' | 'center' | 'end'
11
11
  sideOffset?: number
12
12
  disabled?: boolean
13
+ /**
14
+ * 仅键盘 `:focus-visible` 时因焦点打开 tip。
15
+ * 默认 true:避免 Dialog/Popover 关闭回焦误开(对齐 Reka 推荐与 RsButton tooltip)。
16
+ */
17
+ ignoreNonKeyboardFocus?: boolean
13
18
  /** 单行展示并按内容撑开宽度(适合集合名等标识符) */
14
19
  nowrap?: boolean
15
20
  /**
@@ -25,6 +30,7 @@ const props = withDefaults(
25
30
  align: 'center',
26
31
  sideOffset: 6,
27
32
  disabled: false,
33
+ ignoreNonKeyboardFocus: true,
28
34
  nowrap: false,
29
35
  icon: false,
30
36
  iconName: 'info',
@@ -37,7 +43,10 @@ const iconAriaLabel = computed(() => props.content?.trim() || 'Help')
37
43
  </script>
38
44
 
39
45
  <template>
40
- <TooltipRoot :disabled="disabled">
46
+ <TooltipRoot
47
+ :disabled="disabled"
48
+ :ignore-non-keyboard-focus="ignoreNonKeyboardFocus"
49
+ >
41
50
  <template v-if="icon">
42
51
  <span class="rs-tooltip__with-icon">
43
52
  <span v-if="hasLabelSlot" class="rs-tooltip__label">
@@ -5,16 +5,26 @@ withDefaults(
5
5
  defineProps<{
6
6
  delayDuration?: number
7
7
  skipDelayDuration?: number
8
+ /**
9
+ * 仅键盘 focus-visible 时因焦点打开 tip。
10
+ * 默认 true:避免 Dialog/Popover 关闭回焦、切页签等非键盘焦点误开(对齐 Reka 推荐与 RsButton tooltip)。
11
+ */
12
+ ignoreNonKeyboardFocus?: boolean
8
13
  }>(),
9
14
  {
10
15
  delayDuration: 300,
11
16
  skipDelayDuration: 0,
17
+ ignoreNonKeyboardFocus: true,
12
18
  },
13
19
  )
14
20
  </script>
15
21
 
16
22
  <template>
17
- <TooltipProvider :delay-duration="delayDuration" :skip-delay-duration="skipDelayDuration">
23
+ <TooltipProvider
24
+ :delay-duration="delayDuration"
25
+ :skip-delay-duration="skipDelayDuration"
26
+ :ignore-non-keyboard-focus="ignoreNonKeyboardFocus"
27
+ >
18
28
  <slot />
19
29
  </TooltipProvider>
20
30
  </template>
@@ -1096,8 +1096,8 @@ defineExpose({
1096
1096
 
1097
1097
  .rs-tree__toggle-caret {
1098
1098
  display: block;
1099
- font-size: 1.125rem;
1100
- font-weight: 500;
1099
+ font-size: var(--rs-font-size-lg);
1100
+ font-weight: var(--rs-font-weight-medium);
1101
1101
  line-height: 1;
1102
1102
  transform: translateY(-0.5px);
1103
1103
  transition: transform var(--rs-transition-fast);
@@ -1212,7 +1212,7 @@ defineExpose({
1212
1212
  border-radius: 2px;
1213
1213
  background: color-mix(in srgb, var(--rs-warning) 45%, transparent);
1214
1214
  color: inherit;
1215
- font-weight: 600;
1215
+ font-weight: var(--rs-font-weight-semibold);
1216
1216
  }
1217
1217
 
1218
1218
  .rs-tree__pad {
@@ -1232,7 +1232,7 @@ defineExpose({
1232
1232
  }
1233
1233
 
1234
1234
  .rs-tree--ssm .rs-tree__toggle-caret {
1235
- font-size: 0.875rem;
1235
+ font-size: var(--rs-font-size-sm);
1236
1236
  }
1237
1237
 
1238
1238
  .rs-tree--sm .rs-tree__label,
@@ -1247,7 +1247,7 @@ defineExpose({
1247
1247
  }
1248
1248
 
1249
1249
  .rs-tree--sm .rs-tree__toggle-caret {
1250
- font-size: 1rem;
1250
+ font-size: var(--rs-font-size-base);
1251
1251
  }
1252
1252
 
1253
1253
  .rs-tree--lg .rs-tree__label {
@@ -1261,6 +1261,6 @@ defineExpose({
1261
1261
  }
1262
1262
 
1263
1263
  .rs-tree--lg .rs-tree__toggle-caret {
1264
- font-size: 1.25rem;
1264
+ font-size: var(--rs-font-size-xl);
1265
1265
  }
1266
1266
  </style>
@@ -273,7 +273,7 @@ function fileIcon(file: File): string {
273
273
  .rs-upload__label {
274
274
  color: var(--rs-text);
275
275
  font-size: var(--rs-font-size-sm);
276
- font-weight: 600;
276
+ font-weight: var(--rs-font-weight-semibold);
277
277
  line-height: var(--rs-line-height-tight);
278
278
  }
279
279
 
@@ -367,7 +367,7 @@ function fileIcon(file: File): string {
367
367
  overflow: hidden;
368
368
  color: var(--rs-text);
369
369
  font-size: var(--rs-font-size-sm);
370
- font-weight: 500;
370
+ font-weight: var(--rs-font-weight-medium);
371
371
  line-height: var(--rs-line-height-tight);
372
372
  text-overflow: ellipsis;
373
373
  white-space: nowrap;