niuma-ui 1.0.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 (220) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +25 -0
  4. package/README.en.md +99 -0
  5. package/README.md +161 -0
  6. package/package.json +106 -0
  7. package/src/__tests__/RsBadge.spec.ts +40 -0
  8. package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
  9. package/src/__tests__/RsButton.spec.ts +108 -0
  10. package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
  11. package/src/__tests__/RsCard.spec.ts +97 -0
  12. package/src/__tests__/RsCheckbox.spec.ts +36 -0
  13. package/src/__tests__/RsCodeEditor.spec.ts +111 -0
  14. package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
  15. package/src/__tests__/RsContainer.spec.ts +89 -0
  16. package/src/__tests__/RsContextMenu.spec.ts +118 -0
  17. package/src/__tests__/RsDatePicker.spec.ts +88 -0
  18. package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
  19. package/src/__tests__/RsDialog.spec.ts +404 -0
  20. package/src/__tests__/RsDrawer.spec.ts +145 -0
  21. package/src/__tests__/RsDropdown.spec.ts +135 -0
  22. package/src/__tests__/RsEmpty.spec.ts +99 -0
  23. package/src/__tests__/RsForm.spec.ts +173 -0
  24. package/src/__tests__/RsIcon.spec.ts +244 -0
  25. package/src/__tests__/RsInput.spec.ts +269 -0
  26. package/src/__tests__/RsInputNumber.spec.ts +118 -0
  27. package/src/__tests__/RsLabel.spec.ts +106 -0
  28. package/src/__tests__/RsLink.spec.ts +49 -0
  29. package/src/__tests__/RsLoading.spec.ts +84 -0
  30. package/src/__tests__/RsMarkdown.spec.ts +143 -0
  31. package/src/__tests__/RsMenu.spec.ts +119 -0
  32. package/src/__tests__/RsNewComponents.spec.ts +17 -0
  33. package/src/__tests__/RsPagination.spec.ts +158 -0
  34. package/src/__tests__/RsPopover.spec.ts +126 -0
  35. package/src/__tests__/RsScrollbar.spec.ts +53 -0
  36. package/src/__tests__/RsSelect.spec.ts +291 -0
  37. package/src/__tests__/RsSidebar.spec.ts +119 -0
  38. package/src/__tests__/RsSplitPane.spec.ts +177 -0
  39. package/src/__tests__/RsStatCard.spec.ts +79 -0
  40. package/src/__tests__/RsSteps.spec.ts +125 -0
  41. package/src/__tests__/RsTable.perf.spec.ts +103 -0
  42. package/src/__tests__/RsTable.spec.ts +1135 -0
  43. package/src/__tests__/RsTabs.spec.ts +328 -0
  44. package/src/__tests__/RsTerminal.spec.ts +218 -0
  45. package/src/__tests__/RsTimePicker.spec.ts +77 -0
  46. package/src/__tests__/RsToaster.spec.ts +95 -0
  47. package/src/__tests__/RsToolbar.spec.ts +75 -0
  48. package/src/__tests__/RsTooltip.spec.ts +111 -0
  49. package/src/__tests__/RsTree.spec.ts +572 -0
  50. package/src/__tests__/RsUpload.spec.ts +160 -0
  51. package/src/__tests__/RsVirtualList.spec.ts +77 -0
  52. package/src/__tests__/date-picker-utils.spec.ts +70 -0
  53. package/src/__tests__/dialog-viewport.spec.ts +38 -0
  54. package/src/__tests__/iso-local-datetime.spec.ts +58 -0
  55. package/src/__tests__/rs-clipboard.spec.ts +142 -0
  56. package/src/__tests__/setup.ts +12 -0
  57. package/src/__tests__/split-pane-utils.spec.ts +136 -0
  58. package/src/__tests__/table-drag.spec.ts +35 -0
  59. package/src/__tests__/table-edit-utils.spec.ts +194 -0
  60. package/src/__tests__/table-utils.spec.ts +286 -0
  61. package/src/__tests__/theme.spec.ts +22 -0
  62. package/src/__tests__/tree-utils.spec.ts +234 -0
  63. package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
  64. package/src/components/RsAvatar.vue +115 -0
  65. package/src/components/RsBadge.vue +59 -0
  66. package/src/components/RsBreadcrumb.vue +65 -0
  67. package/src/components/RsBreadcrumbItems.vue +36 -0
  68. package/src/components/RsButton.vue +393 -0
  69. package/src/components/RsCalendarGrid.vue +284 -0
  70. package/src/components/RsCard.vue +237 -0
  71. package/src/components/RsCheckbox.vue +189 -0
  72. package/src/components/RsCodeBlock.vue +278 -0
  73. package/src/components/RsCodeEditor.vue +597 -0
  74. package/src/components/RsConfigProvider.vue +92 -0
  75. package/src/components/RsConfirmDialog.vue +178 -0
  76. package/src/components/RsContainer.vue +235 -0
  77. package/src/components/RsContextMenu.vue +238 -0
  78. package/src/components/RsContextMenuItems.vue +77 -0
  79. package/src/components/RsDatePicker.vue +756 -0
  80. package/src/components/RsDateTimePicker.vue +42 -0
  81. package/src/components/RsDialog.vue +486 -0
  82. package/src/components/RsDrawer.vue +175 -0
  83. package/src/components/RsDropdown.vue +252 -0
  84. package/src/components/RsDropdownItems.vue +90 -0
  85. package/src/components/RsEmpty.vue +107 -0
  86. package/src/components/RsForm.vue +131 -0
  87. package/src/components/RsIcon.vue +104 -0
  88. package/src/components/RsInput.vue +1101 -0
  89. package/src/components/RsInputNumber.vue +532 -0
  90. package/src/components/RsLabel.vue +54 -0
  91. package/src/components/RsLink.vue +106 -0
  92. package/src/components/RsLoading.vue +171 -0
  93. package/src/components/RsMarkdown.vue +329 -0
  94. package/src/components/RsMenu.vue +256 -0
  95. package/src/components/RsMenuItems.vue +125 -0
  96. package/src/components/RsMonacoEditor.vue +541 -0
  97. package/src/components/RsPagination.vue +184 -0
  98. package/src/components/RsPopover.vue +77 -0
  99. package/src/components/RsProseEditor.vue +160 -0
  100. package/src/components/RsScrollbar.vue +171 -0
  101. package/src/components/RsSelect.vue +1045 -0
  102. package/src/components/RsSidebar.vue +121 -0
  103. package/src/components/RsSidebarGroup.vue +32 -0
  104. package/src/components/RsSidebarItem.vue +72 -0
  105. package/src/components/RsSplitPane.vue +619 -0
  106. package/src/components/RsStatCard.vue +85 -0
  107. package/src/components/RsSteps.vue +138 -0
  108. package/src/components/RsTable.vue +2664 -0
  109. package/src/components/RsTableColgroup.vue +17 -0
  110. package/src/components/RsTableHeaderRow.vue +30 -0
  111. package/src/components/RsTabs.vue +951 -0
  112. package/src/components/RsTerminal.vue +697 -0
  113. package/src/components/RsTimePicker.vue +554 -0
  114. package/src/components/RsTimePickerColumns.vue +226 -0
  115. package/src/components/RsToaster.vue +396 -0
  116. package/src/components/RsToolbar.vue +135 -0
  117. package/src/components/RsTooltip.vue +142 -0
  118. package/src/components/RsTooltipProvider.vue +20 -0
  119. package/src/components/RsTree.vue +1248 -0
  120. package/src/components/RsUpload.vue +279 -0
  121. package/src/components/RsVirtualList.vue +217 -0
  122. package/src/components/avatar-utils.ts +19 -0
  123. package/src/components/breadcrumb-utils.ts +19 -0
  124. package/src/components/class-utils.ts +29 -0
  125. package/src/components/code-editor-utils.ts +106 -0
  126. package/src/components/code-mirror-completion.ts +40 -0
  127. package/src/components/code-mirror-diagnostics.ts +52 -0
  128. package/src/components/code-mirror-ghost.ts +158 -0
  129. package/src/components/code-mirror-inline-edit.ts +55 -0
  130. package/src/components/code-mirror-intel.ts +81 -0
  131. package/src/components/code-mirror-lang.ts +128 -0
  132. package/src/components/context-menu-utils.ts +20 -0
  133. package/src/components/date-picker-utils.ts +284 -0
  134. package/src/components/date-range-validation.ts +29 -0
  135. package/src/components/date-validation.ts +38 -0
  136. package/src/components/dialog-viewport.ts +48 -0
  137. package/src/components/dialog-window.ts +253 -0
  138. package/src/components/dropdown-utils.ts +31 -0
  139. package/src/components/form-utils.ts +77 -0
  140. package/src/components/input-number-utils.ts +128 -0
  141. package/src/components/input-rules.ts +84 -0
  142. package/src/components/loading-utils.ts +8 -0
  143. package/src/components/markdown-utils.ts +126 -0
  144. package/src/components/menu-utils.ts +48 -0
  145. package/src/components/overlay-utils.ts +47 -0
  146. package/src/components/pagination-utils.ts +38 -0
  147. package/src/components/popover-utils.ts +8 -0
  148. package/src/components/reka.ts +106 -0
  149. package/src/components/resolve-radius.ts +22 -0
  150. package/src/components/resolve-size.ts +29 -0
  151. package/src/components/scrollbar-utils.ts +10 -0
  152. package/src/components/select-utils.ts +72 -0
  153. package/src/components/split-pane-utils.ts +187 -0
  154. package/src/components/steps-utils.ts +22 -0
  155. package/src/components/table/RsTableBodyRow.vue +287 -0
  156. package/src/components/table/RsTableCell.vue +239 -0
  157. package/src/components/table/RsTableCellContent.ts +18 -0
  158. package/src/components/table/RsTableCellEditor.vue +351 -0
  159. package/src/components/table/RsTableEditGutter.vue +54 -0
  160. package/src/components/table/RsTableHeaderFilter.vue +107 -0
  161. package/src/components/table/RsTableRowStatus.vue +69 -0
  162. package/src/components/table/rs-table.css +1141 -0
  163. package/src/components/table/table-context-menu.ts +53 -0
  164. package/src/components/table/table-edit-utils.ts +496 -0
  165. package/src/components/table/table-keys.ts +12 -0
  166. package/src/components/table-drag.ts +215 -0
  167. package/src/components/table-utils.ts +950 -0
  168. package/src/components/tabs-utils.ts +107 -0
  169. package/src/components/terminal-utils.ts +301 -0
  170. package/src/components/terminal-wheel.ts +49 -0
  171. package/src/components/time-picker-utils.ts +167 -0
  172. package/src/components/time-range-validation.ts +23 -0
  173. package/src/components/time-validation.ts +20 -0
  174. package/src/components/tree-utils.ts +518 -0
  175. package/src/components/upload-utils.ts +55 -0
  176. package/src/components/virtual-list-utils.ts +17 -0
  177. package/src/composables/useClickOutside.ts +22 -0
  178. package/src/composables/useRsConfig.ts +91 -0
  179. package/src/composables/useRsI18n.ts +22 -0
  180. package/src/composables/useRsTabsNav.ts +187 -0
  181. package/src/composables/useRsToast.ts +48 -0
  182. package/src/composables/useTableCellTooltip.ts +239 -0
  183. package/src/composables/useTableEdit.ts +271 -0
  184. package/src/composables/useTableRowHighlight.ts +50 -0
  185. package/src/dev/vite-codemirror-deps.ts +20 -0
  186. package/src/icons/custom/api.ts +54 -0
  187. package/src/icons/custom/clickhouse.ts +52 -0
  188. package/src/icons/custom/dameng.ts +83 -0
  189. package/src/icons/custom/ftp.ts +61 -0
  190. package/src/icons/custom/kingbase.ts +52 -0
  191. package/src/icons/custom/mongodb.ts +50 -0
  192. package/src/icons/custom/mysql.ts +51 -0
  193. package/src/icons/custom/oracle.ts +52 -0
  194. package/src/icons/custom/redis.ts +51 -0
  195. package/src/icons/custom/sqlite.ts +50 -0
  196. package/src/icons/custom/sqlserver.ts +52 -0
  197. package/src/icons/custom/vastbase.ts +57 -0
  198. package/src/icons/lucide.ts +78 -0
  199. package/src/icons/registry.ts +35 -0
  200. package/src/index.ts +425 -0
  201. package/src/lib/iso-local-datetime.ts +152 -0
  202. package/src/lib/rs-dayjs.ts +56 -0
  203. package/src/locale/messages.ts +362 -0
  204. package/src/locale/types.ts +5 -0
  205. package/src/monaco/debug-decorations.css +20 -0
  206. package/src/monaco/debug-decorations.ts +70 -0
  207. package/src/monaco/index.ts +22 -0
  208. package/src/monaco/languages/index.ts +21 -0
  209. package/src/monaco/languages/mongodb-shell.ts +125 -0
  210. package/src/monaco/languages/types.ts +55 -0
  211. package/src/monaco/setup.ts +101 -0
  212. package/src/styles.css +1040 -0
  213. package/src/theme/apply.ts +12 -0
  214. package/src/theme/brand.example.css +37 -0
  215. package/src/theme/presets.ts +69 -0
  216. package/src/theme/types.ts +81 -0
  217. package/src/utils/rs-clipboard.ts +272 -0
  218. package/src/vite-env.d.ts +7 -0
  219. package/vite-plugins/monaco-zh-nls.ts +101 -0
  220. package/vite-plugins/silence-antlr-parse-console.ts +29 -0
@@ -0,0 +1,756 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, useId, watch } from 'vue'
3
+ import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from './reka'
4
+ import RsCalendarGrid from './RsCalendarGrid.vue'
5
+ import RsIcon from './RsIcon.vue'
6
+ import RsTimePicker from './RsTimePicker.vue'
7
+ import { useRsI18n } from '../composables/useRsI18n'
8
+ import {
9
+ compareDates,
10
+ EMPTY_DATE_RANGE,
11
+ extractTimeFromDateTime,
12
+ formatDateParts,
13
+ formatDateRangeLabel,
14
+ formatDateDisplay,
15
+ formatDateTimeDisplay,
16
+ formatDateTimeParts,
17
+ formatDateTimeRangeLabel,
18
+ getNextMonth,
19
+ getNowDateTime,
20
+ getTodayDate,
21
+ isDateRangeEmpty,
22
+ isDateRangeOrdered,
23
+ isDateWithinBounds,
24
+ parseDateTimeValue,
25
+ parseDateValue,
26
+ type RsDateRangeValue,
27
+ type RsParsedDate,
28
+ } from './date-picker-utils'
29
+ import { formatTimeParts } from './time-picker-utils'
30
+
31
+ export type { RsDateRangeValue } from './date-picker-utils'
32
+ export type RsDatePickerLabelPosition = 'top' | 'left'
33
+
34
+ function isRangeModel(value: string | RsDateRangeValue): value is RsDateRangeValue {
35
+ return typeof value === 'object' && value !== null && ('start' in value || 'end' in value)
36
+ }
37
+
38
+ const model = defineModel<string | RsDateRangeValue>({ default: '' })
39
+ const open = defineModel<boolean>('open', { default: false })
40
+
41
+ const props = withDefaults(
42
+ defineProps<{
43
+ label?: string
44
+ hint?: string
45
+ placeholder?: string
46
+ disabled?: boolean
47
+ required?: boolean
48
+ range?: boolean
49
+ minDate?: string
50
+ maxDate?: string
51
+ withTime?: boolean
52
+ withSeconds?: boolean
53
+ labelPosition?: RsDatePickerLabelPosition
54
+ }>(),
55
+ {
56
+ disabled: false,
57
+ required: false,
58
+ range: false,
59
+ withTime: false,
60
+ withSeconds: false,
61
+ labelPosition: 'top',
62
+ },
63
+ )
64
+
65
+ const fieldId = useId()
66
+ const { t } = useRsI18n()
67
+
68
+ const viewYear = ref(getTodayDate().year)
69
+ const viewMonth = ref(getTodayDate().month)
70
+ const startViewYear = ref(getTodayDate().year)
71
+ const startViewMonth = ref(getTodayDate().month)
72
+ const endViewYear = ref(getTodayDate().year)
73
+ const endViewMonth = ref(getTodayDate().month)
74
+
75
+ const draftDate = ref<RsParsedDate | null>(null)
76
+ const draftStart = ref<RsParsedDate | null>(null)
77
+ const draftEnd = ref<RsParsedDate | null>(null)
78
+ const draftTime = ref('')
79
+ const draftStartTime = ref('')
80
+ const draftEndTime = ref('')
81
+
82
+ const rangeModel = computed<RsDateRangeValue>({
83
+ get() {
84
+ if (!props.range) return { ...EMPTY_DATE_RANGE }
85
+ return isRangeModel(model.value) ? model.value : { ...EMPTY_DATE_RANGE }
86
+ },
87
+ set(value) {
88
+ model.value = value
89
+ },
90
+ })
91
+
92
+ const singleModel = computed({
93
+ get() {
94
+ if (props.range) return ''
95
+ return typeof model.value === 'string' ? model.value : ''
96
+ },
97
+ set(value: string) {
98
+ model.value = value
99
+ },
100
+ })
101
+
102
+ const startMaxDate = computed(() => {
103
+ if (!draftEnd.value) return props.maxDate
104
+ return formatDateParts(draftEnd.value)
105
+ })
106
+
107
+ const endMinDate = computed(() => {
108
+ if (!draftStart.value) return props.minDate
109
+ return formatDateParts(draftStart.value)
110
+ })
111
+
112
+ const isEmpty = computed(() =>
113
+ props.range ? isDateRangeEmpty(rangeModel.value) : !singleModel.value,
114
+ )
115
+
116
+ const displayValue = computed(() => {
117
+ if (props.range) {
118
+ const formatted = props.withTime
119
+ ? formatDateTimeRangeLabel(rangeModel.value, t('datePicker.separator'))
120
+ : formatDateRangeLabel(rangeModel.value, t('datePicker.separator'))
121
+ if (formatted) return formatted
122
+ if (props.placeholder) return props.placeholder
123
+ return props.withTime ? t('dateTimePicker.rangePlaceholder') : t('datePicker.rangePlaceholder')
124
+ }
125
+
126
+ if (!singleModel.value) {
127
+ return props.placeholder ?? (props.withTime ? t('dateTimePicker.placeholder') : t('datePicker.placeholder'))
128
+ }
129
+
130
+ return props.withTime
131
+ ? formatDateTimeDisplay(singleModel.value)
132
+ : formatDateDisplay(singleModel.value)
133
+ })
134
+
135
+ const triggerIcon = computed(() => {
136
+ if (props.range || props.withTime) return 'calendar-clock'
137
+ return 'calendar-days'
138
+ })
139
+
140
+ function currentTimeValue(): string {
141
+ const now = getNowDateTime()
142
+ return formatTimeParts(now.hour, now.minute, props.withSeconds ? now.second : undefined)
143
+ }
144
+
145
+ function resetRangeTimeDrafts(): void {
146
+ const time = currentTimeValue()
147
+ draftStartTime.value = time
148
+ draftEndTime.value = time
149
+ }
150
+
151
+ function combineDraftDateTime(date: RsParsedDate, time: string): string | null {
152
+ return formatDateTimeParts(date, time, props.withSeconds)
153
+ }
154
+
155
+ function setEndViewFromStart(start: RsParsedDate): void {
156
+ const next = getNextMonth(start.year, start.month)
157
+ endViewYear.value = next.year
158
+ endViewMonth.value = next.month
159
+ }
160
+
161
+ function syncSingleDraft(): void {
162
+ if (props.withTime) {
163
+ const parsed = parseDateTimeValue(singleModel.value)
164
+ if (parsed) {
165
+ draftDate.value = { year: parsed.year, month: parsed.month, day: parsed.day }
166
+ draftTime.value = extractTimeFromDateTime(singleModel.value, props.withSeconds)
167
+ viewYear.value = parsed.year
168
+ viewMonth.value = parsed.month
169
+ return
170
+ }
171
+
172
+ const today = getTodayDate()
173
+ draftDate.value = null
174
+ draftTime.value = currentTimeValue()
175
+ viewYear.value = today.year
176
+ viewMonth.value = today.month
177
+ return
178
+ }
179
+
180
+ const parsed = parseDateValue(singleModel.value)
181
+ if (parsed) {
182
+ draftDate.value = parsed
183
+ viewYear.value = parsed.year
184
+ viewMonth.value = parsed.month
185
+ return
186
+ }
187
+
188
+ const today = getTodayDate()
189
+ draftDate.value = null
190
+ viewYear.value = today.year
191
+ viewMonth.value = today.month
192
+ }
193
+
194
+ function syncRangeDraft(): void {
195
+ const today = getTodayDate()
196
+ const value = rangeModel.value
197
+
198
+ if (props.withTime) {
199
+ const start = parseDateTimeValue(value.start)
200
+ const end = parseDateTimeValue(value.end)
201
+
202
+ draftStart.value = start
203
+ ? { year: start.year, month: start.month, day: start.day }
204
+ : null
205
+ draftEnd.value = end ? { year: end.year, month: end.month, day: end.day } : null
206
+
207
+ if (start) {
208
+ draftStartTime.value = extractTimeFromDateTime(value.start, props.withSeconds)
209
+ startViewYear.value = start.year
210
+ startViewMonth.value = start.month
211
+ } else {
212
+ resetRangeTimeDrafts()
213
+ startViewYear.value = today.year
214
+ startViewMonth.value = today.month
215
+ }
216
+
217
+ if (end) {
218
+ draftEndTime.value = extractTimeFromDateTime(value.end, props.withSeconds)
219
+ endViewYear.value = end.year
220
+ endViewMonth.value = end.month
221
+ } else if (start) {
222
+ draftEndTime.value = draftStartTime.value
223
+ setEndViewFromStart(start)
224
+ } else {
225
+ const next = getNextMonth(today.year, today.month)
226
+ endViewYear.value = next.year
227
+ endViewMonth.value = next.month
228
+ }
229
+ return
230
+ }
231
+
232
+ const start = parseDateValue(value.start)
233
+ const end = parseDateValue(value.end)
234
+
235
+ draftStart.value = start
236
+ draftEnd.value = end
237
+
238
+ if (start) {
239
+ startViewYear.value = start.year
240
+ startViewMonth.value = start.month
241
+ } else {
242
+ startViewYear.value = today.year
243
+ startViewMonth.value = today.month
244
+ }
245
+
246
+ if (end) {
247
+ endViewYear.value = end.year
248
+ endViewMonth.value = end.month
249
+ } else if (start) {
250
+ setEndViewFromStart(start)
251
+ } else {
252
+ const next = getNextMonth(today.year, today.month)
253
+ endViewYear.value = next.year
254
+ endViewMonth.value = next.month
255
+ }
256
+ }
257
+
258
+ function syncDraftFromModel(): void {
259
+ if (props.range) {
260
+ syncRangeDraft()
261
+ return
262
+ }
263
+ syncSingleDraft()
264
+ }
265
+
266
+ function confirmSingleSelection(): void {
267
+ if (!draftDate.value) return
268
+
269
+ if (props.withTime) {
270
+ const combined = combineDraftDateTime(draftDate.value, draftTime.value)
271
+ if (!combined) return
272
+ singleModel.value = combined
273
+ open.value = false
274
+ return
275
+ }
276
+
277
+ if (
278
+ !isDateWithinBounds(draftDate.value.year, draftDate.value.month, draftDate.value.day, {
279
+ minDate: props.minDate,
280
+ maxDate: props.maxDate,
281
+ })
282
+ ) {
283
+ return
284
+ }
285
+
286
+ singleModel.value = formatDateParts(draftDate.value)
287
+ open.value = false
288
+ }
289
+
290
+ function confirmRangeSelection(): void {
291
+ if (!draftStart.value || !draftEnd.value) return
292
+
293
+ if (props.withTime) {
294
+ const start = combineDraftDateTime(draftStart.value, draftStartTime.value)
295
+ const end = combineDraftDateTime(draftEnd.value, draftEndTime.value)
296
+ if (!start || !end) return
297
+ if (!isDateRangeOrdered({ start, end })) return
298
+ rangeModel.value = { start, end }
299
+ open.value = false
300
+ return
301
+ }
302
+
303
+ const start = formatDateParts(draftStart.value)
304
+ const end = formatDateParts(draftEnd.value)
305
+ if (!isDateRangeOrdered({ start, end })) return
306
+
307
+ if (
308
+ !isDateWithinBounds(draftStart.value.year, draftStart.value.month, draftStart.value.day, {
309
+ minDate: props.minDate,
310
+ maxDate: props.maxDate,
311
+ })
312
+ || !isDateWithinBounds(draftEnd.value.year, draftEnd.value.month, draftEnd.value.day, {
313
+ minDate: props.minDate,
314
+ maxDate: props.maxDate,
315
+ })
316
+ ) {
317
+ return
318
+ }
319
+
320
+ rangeModel.value = { start, end }
321
+ open.value = false
322
+ }
323
+
324
+ function confirmSelection(): void {
325
+ if (props.range) {
326
+ confirmRangeSelection()
327
+ return
328
+ }
329
+ confirmSingleSelection()
330
+ }
331
+
332
+ function clearSelection(): void {
333
+ if (props.range) {
334
+ rangeModel.value = { ...EMPTY_DATE_RANGE }
335
+ draftStart.value = null
336
+ draftEnd.value = null
337
+ if (props.withTime) resetRangeTimeDrafts()
338
+ } else {
339
+ singleModel.value = ''
340
+ draftDate.value = null
341
+ if (props.withTime) draftTime.value = currentTimeValue()
342
+ }
343
+ open.value = false
344
+ }
345
+
346
+ function selectToday(): void {
347
+ const today = getTodayDate()
348
+ const now = getNowDateTime()
349
+
350
+ if (props.range) {
351
+ draftStart.value = today
352
+ draftEnd.value = today
353
+ startViewYear.value = today.year
354
+ startViewMonth.value = today.month
355
+ endViewYear.value = today.year
356
+ endViewMonth.value = today.month
357
+ if (props.withTime) {
358
+ const time = formatTimeParts(now.hour, now.minute, props.withSeconds ? now.second : undefined)
359
+ draftStartTime.value = time
360
+ draftEndTime.value = time
361
+ }
362
+ confirmRangeSelection()
363
+ return
364
+ }
365
+
366
+ draftDate.value = today
367
+ viewYear.value = today.year
368
+ viewMonth.value = today.month
369
+ if (props.withTime) {
370
+ draftTime.value = formatTimeParts(now.hour, now.minute, props.withSeconds ? now.second : undefined)
371
+ }
372
+ confirmSingleSelection()
373
+ }
374
+
375
+ function handleDateSelect(date: RsParsedDate): void {
376
+ draftDate.value = date
377
+ viewYear.value = date.year
378
+ viewMonth.value = date.month
379
+ }
380
+
381
+ function handleStartSelect(date: RsParsedDate): void {
382
+ if (
383
+ !isDateWithinBounds(date.year, date.month, date.day, {
384
+ minDate: props.minDate,
385
+ maxDate: startMaxDate.value,
386
+ })
387
+ ) {
388
+ return
389
+ }
390
+
391
+ draftStart.value = date
392
+ startViewYear.value = date.year
393
+ startViewMonth.value = date.month
394
+
395
+ if (draftEnd.value && compareDates(date, draftEnd.value) > 0) {
396
+ draftEnd.value = null
397
+ setEndViewFromStart(date)
398
+ }
399
+ }
400
+
401
+ function handleEndSelect(date: RsParsedDate): void {
402
+ if (
403
+ !isDateWithinBounds(date.year, date.month, date.day, {
404
+ minDate: endMinDate.value,
405
+ maxDate: props.maxDate,
406
+ })
407
+ ) {
408
+ return
409
+ }
410
+
411
+ if (draftStart.value && compareDates(date, draftStart.value) < 0) {
412
+ return
413
+ }
414
+
415
+ draftEnd.value = date
416
+ endViewYear.value = date.year
417
+ endViewMonth.value = date.month
418
+ }
419
+
420
+ watch(open, (isOpen) => {
421
+ if (isOpen) syncDraftFromModel()
422
+ })
423
+ </script>
424
+
425
+ <template>
426
+ <div class="rs-field" :class="`rs-field--label-${labelPosition}`">
427
+ <span v-if="label" class="rs-field__label">
428
+ <label v-if="labelPosition === 'left'" :for="fieldId">{{ label }}</label>
429
+ <template v-else>{{ label }}</template>
430
+ <span v-if="required" class="rs-field__required" aria-hidden="true">*</span>
431
+ </span>
432
+
433
+ <div class="rs-date-picker">
434
+ <PopoverRoot v-model:open="open">
435
+ <PopoverTrigger
436
+ :id="fieldId"
437
+ type="button"
438
+ class="rs-date-picker__trigger"
439
+ :class="{ 'rs-date-picker__trigger--placeholder': isEmpty }"
440
+ :disabled="disabled"
441
+ >
442
+ <span class="rs-date-picker__leading">
443
+ <RsIcon :name="triggerIcon" :size="16" class="rs-date-picker__icon" />
444
+ <span class="rs-date-picker__value">{{ displayValue }}</span>
445
+ </span>
446
+ <RsIcon name="chevron-down" :size="16" class="rs-date-picker__chevron" />
447
+ </PopoverTrigger>
448
+
449
+ <PopoverPortal>
450
+ <PopoverContent
451
+ class="rs-date-picker__content"
452
+ :class="{ 'rs-date-picker__content--range': range }"
453
+ :side-offset="6"
454
+ align="start"
455
+ >
456
+ <div v-if="open" class="rs-date-picker__panel">
457
+ <template v-if="range">
458
+ <div class="rs-date-picker__range-grid">
459
+ <section class="rs-date-picker__range-pane">
460
+ <span class="rs-date-picker__pane-title">
461
+ {{ withTime ? t('dateTimePicker.rangeStart') : t('datePicker.rangeStart') }}
462
+ </span>
463
+ <RsCalendarGrid
464
+ v-model:view-year="startViewYear"
465
+ v-model:view-month="startViewMonth"
466
+ :selected="draftStart"
467
+ :range-start="draftStart"
468
+ :range-end="draftEnd"
469
+ :min-date="minDate"
470
+ :max-date="startMaxDate"
471
+ @select="handleStartSelect"
472
+ />
473
+ <div v-if="withTime" class="rs-date-picker__time-row">
474
+ <span class="rs-date-picker__time-label">{{ t('dateTimePicker.time') }}</span>
475
+ <RsTimePicker
476
+ v-model="draftStartTime"
477
+ class="rs-date-picker__time-picker"
478
+ embedded
479
+ :with-seconds="withSeconds"
480
+ :disabled="!draftStart || disabled"
481
+ />
482
+ </div>
483
+ </section>
484
+
485
+ <section class="rs-date-picker__range-pane">
486
+ <span class="rs-date-picker__pane-title">
487
+ {{ withTime ? t('dateTimePicker.rangeEnd') : t('datePicker.rangeEnd') }}
488
+ </span>
489
+ <RsCalendarGrid
490
+ v-model:view-year="endViewYear"
491
+ v-model:view-month="endViewMonth"
492
+ :selected="draftEnd"
493
+ :range-start="draftStart"
494
+ :range-end="draftEnd"
495
+ :min-date="endMinDate"
496
+ :max-date="maxDate"
497
+ @select="handleEndSelect"
498
+ />
499
+ <div v-if="withTime" class="rs-date-picker__time-row">
500
+ <span class="rs-date-picker__time-label">{{ t('dateTimePicker.time') }}</span>
501
+ <RsTimePicker
502
+ v-model="draftEndTime"
503
+ class="rs-date-picker__time-picker"
504
+ embedded
505
+ :with-seconds="withSeconds"
506
+ :disabled="!draftEnd || disabled"
507
+ />
508
+ </div>
509
+ </section>
510
+ </div>
511
+ </template>
512
+
513
+ <template v-else>
514
+ <RsCalendarGrid
515
+ v-model:view-year="viewYear"
516
+ v-model:view-month="viewMonth"
517
+ :selected="draftDate"
518
+ :min-date="minDate"
519
+ :max-date="maxDate"
520
+ @select="handleDateSelect"
521
+ />
522
+ <div v-if="withTime" class="rs-date-picker__time-row">
523
+ <span class="rs-date-picker__time-label">{{ t('dateTimePicker.time') }}</span>
524
+ <RsTimePicker
525
+ v-model="draftTime"
526
+ class="rs-date-picker__time-picker"
527
+ embedded
528
+ :with-seconds="withSeconds"
529
+ :disabled="!draftDate || disabled"
530
+ />
531
+ </div>
532
+ </template>
533
+
534
+ <footer class="rs-date-picker__footer">
535
+ <button type="button" class="rs-date-picker__link" @click="selectToday">
536
+ {{
537
+ range
538
+ ? withTime
539
+ ? t('dateTimePicker.now')
540
+ : t('datePicker.rangeToday')
541
+ : withTime
542
+ ? t('dateTimePicker.now')
543
+ : t('datePicker.today')
544
+ }}
545
+ </button>
546
+ <div class="rs-date-picker__actions">
547
+ <button type="button" class="rs-date-picker__ghost" @click="clearSelection">
548
+ {{ withTime ? t('dateTimePicker.clear') : t('datePicker.clear') }}
549
+ </button>
550
+ <button
551
+ type="button"
552
+ class="rs-date-picker__confirm"
553
+ :disabled="range ? !draftStart || !draftEnd : !draftDate || (withTime && !draftTime)"
554
+ @click="confirmSelection"
555
+ >
556
+ {{ t('datePicker.confirm') }}
557
+ </button>
558
+ </div>
559
+ </footer>
560
+ </div>
561
+ </PopoverContent>
562
+ </PopoverPortal>
563
+ </PopoverRoot>
564
+ </div>
565
+
566
+ <span v-if="hint" class="rs-field__hint">{{ hint }}</span>
567
+ </div>
568
+ </template>
569
+
570
+ <style scoped>
571
+ .rs-date-picker {
572
+ width: 100%;
573
+ }
574
+ .rs-date-picker__trigger {
575
+ display: inline-flex;
576
+ align-items: center;
577
+ justify-content: space-between;
578
+ gap: var(--rs-space-sm);
579
+ width: 100%;
580
+ min-height: var(--rs-control-height-md);
581
+ padding: 0 var(--rs-space-md);
582
+ border: 1px solid var(--rs-input-border, var(--rs-border));
583
+ border-radius: var(--rs-radius-sm);
584
+ background: var(--rs-input-bg);
585
+ color: var(--rs-text);
586
+ font: inherit;
587
+ font-size: var(--rs-font-size-sm);
588
+ text-align: left;
589
+ cursor: pointer;
590
+ box-shadow: var(--rs-input-shadow, none);
591
+ transition:
592
+ border-color var(--rs-transition-fast),
593
+ box-shadow var(--rs-transition-fast),
594
+ background var(--rs-transition-fast);
595
+ }
596
+ .rs-date-picker__trigger:hover:not(:disabled) {
597
+ border-color: var(--rs-input-border-hover, var(--rs-border));
598
+ }
599
+ .rs-date-picker__trigger:focus-visible {
600
+ outline: none;
601
+ border-color: var(--rs-focus-border, var(--rs-primary));
602
+ background: var(--rs-input-bg);
603
+ box-shadow:
604
+ var(--rs-input-shadow, none),
605
+ 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
606
+ }
607
+ .rs-date-picker__trigger:disabled {
608
+ opacity: 0.38;
609
+ cursor: not-allowed;
610
+ }
611
+ .rs-date-picker__trigger--placeholder .rs-date-picker__value {
612
+ color: var(--rs-placeholder);
613
+ }
614
+ .rs-date-picker__leading {
615
+ display: inline-flex;
616
+ min-width: 0;
617
+ flex: 1;
618
+ align-items: center;
619
+ gap: var(--rs-space-sm);
620
+ }
621
+ .rs-date-picker__icon,
622
+ .rs-date-picker__chevron {
623
+ flex-shrink: 0;
624
+ color: var(--rs-muted);
625
+ }
626
+ .rs-date-picker__value {
627
+ min-width: 0;
628
+ overflow: hidden;
629
+ text-overflow: ellipsis;
630
+ white-space: nowrap;
631
+ }
632
+ </style>
633
+
634
+ <style>
635
+ .rs-date-picker__content {
636
+ /* 高于 RsDialog,避免在对话框内被遮挡 */
637
+ z-index: calc(var(--rs-z-modal) + 2);
638
+ padding: 0.75rem;
639
+ border: 1px solid var(--rs-border);
640
+ border-radius: var(--rs-radius);
641
+ background: var(--rs-surface-elevated);
642
+ box-shadow: var(--rs-shadow-lg);
643
+ outline: none;
644
+ /* 允许页脚/时间行完整展示,避免被裁切 */
645
+ overflow: visible;
646
+ }
647
+ .rs-date-picker__content--range {
648
+ width: min(42rem, calc(100vw - 2rem));
649
+ }
650
+ .rs-date-picker__content:not(.rs-date-picker__content--range) {
651
+ width: min(20rem, calc(100vw - 2rem));
652
+ }
653
+ .rs-date-picker__panel {
654
+ display: flex;
655
+ flex-direction: column;
656
+ gap: var(--rs-space-sm);
657
+ overflow: visible;
658
+ }
659
+ .rs-date-picker__range-grid {
660
+ display: grid;
661
+ gap: 0.75rem;
662
+ }
663
+ @media (min-width: 40rem) {
664
+ .rs-date-picker__range-grid {
665
+ grid-template-columns: repeat(2, minmax(0, 1fr));
666
+ }
667
+ }
668
+ .rs-date-picker__range-pane {
669
+ display: flex;
670
+ flex-direction: column;
671
+ gap: var(--rs-space-sm);
672
+ padding: 0.5rem;
673
+ border: 1px solid var(--rs-border-subtle);
674
+ border-radius: var(--rs-radius-sm);
675
+ background: color-mix(in srgb, var(--rs-surface) 72%, transparent);
676
+ }
677
+ .rs-date-picker__pane-title {
678
+ font-size: var(--rs-font-size-xs);
679
+ font-weight: 600;
680
+ color: var(--rs-muted);
681
+ }
682
+ .rs-date-picker__footer {
683
+ position: relative;
684
+ z-index: 1;
685
+ display: flex;
686
+ align-items: center;
687
+ justify-content: space-between;
688
+ gap: var(--rs-space-sm);
689
+ margin-top: var(--rs-space-xs);
690
+ padding-top: 0.75rem;
691
+ border-top: 1px solid var(--rs-border-subtle);
692
+ background: var(--rs-surface-elevated);
693
+ }
694
+ .rs-date-picker__link {
695
+ border: 0;
696
+ background: transparent;
697
+ color: var(--rs-primary);
698
+ font-size: var(--rs-font-size-xs);
699
+ cursor: pointer;
700
+ }
701
+ .rs-date-picker__link:hover {
702
+ text-decoration: underline;
703
+ }
704
+ .rs-date-picker__actions {
705
+ display: inline-flex;
706
+ align-items: center;
707
+ gap: var(--rs-space-xs);
708
+ }
709
+ .rs-date-picker__ghost,
710
+ .rs-date-picker__confirm {
711
+ border: 0;
712
+ border-radius: var(--rs-radius-sm);
713
+ padding: 0.375rem 0.625rem;
714
+ font-size: var(--rs-font-size-xs);
715
+ cursor: pointer;
716
+ }
717
+ .rs-date-picker__ghost {
718
+ background: transparent;
719
+ color: var(--rs-muted);
720
+ }
721
+ .rs-date-picker__ghost:hover {
722
+ background: var(--rs-surface-hover);
723
+ color: var(--rs-text);
724
+ }
725
+ .rs-date-picker__confirm {
726
+ background: var(--rs-primary);
727
+ color: var(--rs-primary-foreground);
728
+ }
729
+ .rs-date-picker__confirm:hover:not(:disabled) {
730
+ opacity: 0.92;
731
+ }
732
+ .rs-date-picker__confirm:disabled {
733
+ opacity: 0.38;
734
+ cursor: not-allowed;
735
+ }
736
+ .rs-date-picker__time-row {
737
+ position: relative;
738
+ z-index: 1;
739
+ display: flex;
740
+ align-items: center;
741
+ gap: var(--rs-space-sm);
742
+ padding-top: var(--rs-space-sm);
743
+ border-top: 1px solid var(--rs-border-subtle);
744
+ background: var(--rs-surface-elevated);
745
+ }
746
+ .rs-date-picker__time-label {
747
+ flex-shrink: 0;
748
+ font-size: var(--rs-font-size-xs);
749
+ font-weight: 500;
750
+ color: var(--rs-muted);
751
+ }
752
+ .rs-date-picker__time-picker {
753
+ flex: 1;
754
+ min-width: 0;
755
+ }
756
+ </style>