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.
- package/CHANGELOG.md +50 -1
- package/NOTICE +2 -2
- package/package.json +13 -5
- package/src/__tests__/RsButton.spec.ts +47 -0
- package/src/__tests__/RsCard.spec.ts +160 -1
- package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
- package/src/__tests__/RsContextMenu.spec.ts +12 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
- package/src/__tests__/RsDescriptions.spec.ts +50 -0
- package/src/__tests__/RsDialog.spec.ts +117 -0
- package/src/__tests__/RsDrawer.spec.ts +96 -3
- package/src/__tests__/RsDynamicTags.spec.ts +52 -0
- package/src/__tests__/RsForm.spec.ts +245 -1
- package/src/__tests__/RsInput.spec.ts +15 -1
- package/src/__tests__/RsMenu.spec.ts +406 -14
- package/src/__tests__/RsPagination.spec.ts +67 -2
- package/src/__tests__/RsSelect.spec.ts +42 -1
- package/src/__tests__/RsSwitch.spec.ts +44 -0
- package/src/__tests__/RsTable.a11y.spec.ts +83 -0
- package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
- package/src/__tests__/RsTable.perf.spec.ts +23 -4
- package/src/__tests__/RsTable.spec.ts +63 -8
- package/src/__tests__/RsTable.ssr.spec.ts +71 -0
- package/src/__tests__/RsTable.summary.spec.ts +45 -0
- package/src/__tests__/RsTabs.spec.ts +102 -4
- package/src/__tests__/RsTerminal.spec.ts +1 -1
- package/src/__tests__/RsTimePicker.spec.ts +11 -0
- package/src/__tests__/RsUpload.spec.ts +62 -6
- package/src/__tests__/createRsDialog.spec.ts +145 -0
- package/src/__tests__/form-rules.spec.ts +76 -0
- package/src/__tests__/perf-budgets.ts +32 -0
- package/src/__tests__/rs-table-api.spec.ts +206 -0
- package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
- package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
- package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
- package/src/__tests__/rs-table-isolation.spec.ts +158 -0
- package/src/__tests__/split-pane-utils.spec.ts +21 -0
- package/src/__tests__/table-chart-utils.spec.ts +88 -0
- package/src/__tests__/table-summary-utils.spec.ts +62 -0
- package/src/__tests__/table-utils.spec.ts +124 -0
- package/src/__tests__/theme.spec.ts +25 -0
- package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
- package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
- package/src/components/RsAlert.vue +212 -0
- package/src/components/RsButton.vue +145 -21
- package/src/components/RsCard.vue +200 -16
- package/src/components/RsConfirmDialog.vue +215 -28
- package/src/components/RsContextMenu.vue +51 -13
- package/src/components/RsDatePicker.vue +267 -34
- package/src/components/RsDateTimePicker.vue +19 -2
- package/src/components/RsDescriptions.vue +186 -0
- package/src/components/RsDescriptionsItem.vue +32 -0
- package/src/components/RsDialog.vue +363 -51
- package/src/components/RsDivider.vue +89 -0
- package/src/components/RsDrawer.vue +558 -33
- package/src/components/RsDropdown.vue +33 -2
- package/src/components/RsDynamicTags.vue +517 -0
- package/src/components/RsForm.vue +97 -8
- package/src/components/RsInput.vue +80 -82
- package/src/components/RsInputNumber.vue +64 -23
- package/src/components/RsLabel.vue +20 -5
- package/src/components/RsLoadingBar.vue +151 -0
- package/src/components/RsMenu.vue +73 -15
- package/src/components/RsMenuItems.vue +147 -9
- package/src/components/RsPagination.vue +93 -14
- package/src/components/RsRadio.vue +81 -0
- package/src/components/RsRadioItem.vue +156 -0
- package/src/components/RsSelect.vue +67 -58
- package/src/components/RsSplitPane.vue +69 -1
- package/src/components/RsSwitch.vue +179 -0
- package/src/components/RsTable.vue +653 -2555
- package/src/components/RsTabs.vue +415 -83
- package/src/components/RsTag.vue +191 -0
- package/src/components/RsTerminal.vue +3 -3
- package/src/components/RsTimePicker.vue +44 -5
- package/src/components/RsTimePickerColumns.vue +136 -77
- package/src/components/RsToaster.vue +58 -18
- package/src/components/RsTree.vue +39 -21
- package/src/components/RsUpload.vue +145 -12
- package/src/components/RsVNodeHost.vue +20 -0
- package/src/components/button-utils.ts +39 -0
- package/src/components/context-menu-utils.ts +6 -0
- package/src/components/date-picker-utils.ts +84 -4
- package/src/components/descriptions-utils.ts +24 -0
- package/src/components/dialog-utils.ts +147 -0
- package/src/components/dialog-window.ts +74 -9
- package/src/components/drawer-utils.ts +73 -0
- package/src/components/form-rules.ts +257 -0
- package/src/components/form-utils.ts +52 -2
- package/src/components/menu-utils.ts +11 -0
- package/src/components/overlay-utils.ts +3 -0
- package/src/components/radio-utils.ts +12 -0
- package/src/components/reka.ts +5 -0
- package/src/components/select-utils.ts +21 -2
- package/src/components/split-pane-utils.ts +65 -12
- package/src/components/table/RsTableBody.vue +217 -0
- package/src/components/table/RsTableBodyRow.vue +90 -1
- package/src/components/table/RsTableColGroup.vue +41 -0
- package/src/components/table/RsTableHeader.vue +158 -0
- package/src/components/table/RsTableSummaryRow.vue +52 -0
- package/src/components/table/rs-table-api.ts +99 -0
- package/src/components/table/rs-table-compat-matrix.ts +71 -0
- package/src/components/table/rs-table-echarts-adapter.ts +81 -0
- package/src/components/table/rs-table-feature-host.ts +178 -0
- package/src/components/table/rs-table-grid-nav.ts +122 -0
- package/src/components/table/rs-table-header-types.ts +47 -0
- package/src/components/table/rs-table-module-registry.ts +72 -0
- package/src/components/table/rs-table-props.ts +274 -0
- package/src/components/table/rs-table-view-context.ts +258 -0
- package/src/components/table/rs-table.css +84 -6
- package/src/components/table/table-chart-utils.ts +165 -0
- package/src/components/table/table-features.ts +284 -0
- package/src/components/table/table-summary-utils.ts +194 -0
- package/src/components/table-utils.ts +452 -2
- package/src/components/tabs-utils.ts +142 -2
- package/src/components/terminal-utils.ts +1 -1
- package/src/components/terminal-wheel.ts +1 -1
- package/src/components/time-picker-utils.ts +76 -3
- package/src/components/upload-utils.ts +122 -0
- package/src/components/virtual-list-utils.ts +15 -2
- package/src/composables/assembleRsTableApi.ts +79 -0
- package/src/composables/bindRsTableViewContext.ts +269 -0
- package/src/composables/createRsDialog.ts +357 -0
- package/src/composables/createRsTableEditEmitBridge.ts +38 -0
- package/src/composables/useRsLoadingBar.ts +36 -0
- package/src/composables/useRsTable.ts +134 -0
- package/src/composables/useRsTableA11y.ts +52 -0
- package/src/composables/useRsTableChartBridge.ts +79 -0
- package/src/composables/useRsTableColumnLayout.ts +320 -0
- package/src/composables/useRsTableColumnResize.ts +244 -0
- package/src/composables/useRsTableColumnVirtual.ts +180 -0
- package/src/composables/useRsTableColumns.ts +200 -0
- package/src/composables/useRsTableContextMenu.ts +257 -0
- package/src/composables/useRsTableCore.ts +256 -0
- package/src/composables/useRsTableEditActions.ts +606 -0
- package/src/composables/useRsTableEditLayer.ts +251 -0
- package/src/composables/useRsTableEngine.ts +357 -0
- package/src/composables/useRsTableGridKeyboard.ts +123 -0
- package/src/composables/useRsTableHeadless.ts +137 -0
- package/src/composables/useRsTableInteraction.ts +433 -0
- package/src/composables/useRsTableScrollHost.ts +75 -0
- package/src/composables/useRsTableScrollLayout.ts +255 -0
- package/src/composables/useRsTableSelectionSource.ts +89 -0
- package/src/composables/useRsTableShell.ts +416 -0
- package/src/composables/useRsTableShellChrome.ts +170 -0
- package/src/composables/useRsTableSummary.ts +113 -0
- package/src/composables/useRsTableViewProvide.ts +263 -0
- package/src/composables/useRsTableVirtual.ts +154 -0
- package/src/composables/useRsTabsNav.ts +4 -0
- package/src/composables/useTableEdit.ts +1 -1
- package/src/icons/lucide.ts +2 -2
- package/src/index.ts +309 -9
- package/src/locale/messages.ts +30 -0
- package/src/styles.css +305 -52
- package/src/theme/brand.example.css +21 -2
- package/src/theme/presets.ts +4 -0
- package/src/theme/types.ts +67 -0
- package/src/components/RsTableColgroup.vue +0 -17
- 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
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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: () => (
|
|
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:
|
|
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-
|
|
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="{
|
|
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>
|