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.
- package/CHANGELOG.md +29 -0
- package/LICENSE +202 -0
- package/NOTICE +25 -0
- package/README.en.md +99 -0
- package/README.md +161 -0
- package/package.json +106 -0
- package/src/__tests__/RsBadge.spec.ts +40 -0
- package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
- package/src/__tests__/RsButton.spec.ts +108 -0
- package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
- package/src/__tests__/RsCard.spec.ts +97 -0
- package/src/__tests__/RsCheckbox.spec.ts +36 -0
- package/src/__tests__/RsCodeEditor.spec.ts +111 -0
- package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
- package/src/__tests__/RsContainer.spec.ts +89 -0
- package/src/__tests__/RsContextMenu.spec.ts +118 -0
- package/src/__tests__/RsDatePicker.spec.ts +88 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
- package/src/__tests__/RsDialog.spec.ts +404 -0
- package/src/__tests__/RsDrawer.spec.ts +145 -0
- package/src/__tests__/RsDropdown.spec.ts +135 -0
- package/src/__tests__/RsEmpty.spec.ts +99 -0
- package/src/__tests__/RsForm.spec.ts +173 -0
- package/src/__tests__/RsIcon.spec.ts +244 -0
- package/src/__tests__/RsInput.spec.ts +269 -0
- package/src/__tests__/RsInputNumber.spec.ts +118 -0
- package/src/__tests__/RsLabel.spec.ts +106 -0
- package/src/__tests__/RsLink.spec.ts +49 -0
- package/src/__tests__/RsLoading.spec.ts +84 -0
- package/src/__tests__/RsMarkdown.spec.ts +143 -0
- package/src/__tests__/RsMenu.spec.ts +119 -0
- package/src/__tests__/RsNewComponents.spec.ts +17 -0
- package/src/__tests__/RsPagination.spec.ts +158 -0
- package/src/__tests__/RsPopover.spec.ts +126 -0
- package/src/__tests__/RsScrollbar.spec.ts +53 -0
- package/src/__tests__/RsSelect.spec.ts +291 -0
- package/src/__tests__/RsSidebar.spec.ts +119 -0
- package/src/__tests__/RsSplitPane.spec.ts +177 -0
- package/src/__tests__/RsStatCard.spec.ts +79 -0
- package/src/__tests__/RsSteps.spec.ts +125 -0
- package/src/__tests__/RsTable.perf.spec.ts +103 -0
- package/src/__tests__/RsTable.spec.ts +1135 -0
- package/src/__tests__/RsTabs.spec.ts +328 -0
- package/src/__tests__/RsTerminal.spec.ts +218 -0
- package/src/__tests__/RsTimePicker.spec.ts +77 -0
- package/src/__tests__/RsToaster.spec.ts +95 -0
- package/src/__tests__/RsToolbar.spec.ts +75 -0
- package/src/__tests__/RsTooltip.spec.ts +111 -0
- package/src/__tests__/RsTree.spec.ts +572 -0
- package/src/__tests__/RsUpload.spec.ts +160 -0
- package/src/__tests__/RsVirtualList.spec.ts +77 -0
- package/src/__tests__/date-picker-utils.spec.ts +70 -0
- package/src/__tests__/dialog-viewport.spec.ts +38 -0
- package/src/__tests__/iso-local-datetime.spec.ts +58 -0
- package/src/__tests__/rs-clipboard.spec.ts +142 -0
- package/src/__tests__/setup.ts +12 -0
- package/src/__tests__/split-pane-utils.spec.ts +136 -0
- package/src/__tests__/table-drag.spec.ts +35 -0
- package/src/__tests__/table-edit-utils.spec.ts +194 -0
- package/src/__tests__/table-utils.spec.ts +286 -0
- package/src/__tests__/theme.spec.ts +22 -0
- package/src/__tests__/tree-utils.spec.ts +234 -0
- package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
- package/src/components/RsAvatar.vue +115 -0
- package/src/components/RsBadge.vue +59 -0
- package/src/components/RsBreadcrumb.vue +65 -0
- package/src/components/RsBreadcrumbItems.vue +36 -0
- package/src/components/RsButton.vue +393 -0
- package/src/components/RsCalendarGrid.vue +284 -0
- package/src/components/RsCard.vue +237 -0
- package/src/components/RsCheckbox.vue +189 -0
- package/src/components/RsCodeBlock.vue +278 -0
- package/src/components/RsCodeEditor.vue +597 -0
- package/src/components/RsConfigProvider.vue +92 -0
- package/src/components/RsConfirmDialog.vue +178 -0
- package/src/components/RsContainer.vue +235 -0
- package/src/components/RsContextMenu.vue +238 -0
- package/src/components/RsContextMenuItems.vue +77 -0
- package/src/components/RsDatePicker.vue +756 -0
- package/src/components/RsDateTimePicker.vue +42 -0
- package/src/components/RsDialog.vue +486 -0
- package/src/components/RsDrawer.vue +175 -0
- package/src/components/RsDropdown.vue +252 -0
- package/src/components/RsDropdownItems.vue +90 -0
- package/src/components/RsEmpty.vue +107 -0
- package/src/components/RsForm.vue +131 -0
- package/src/components/RsIcon.vue +104 -0
- package/src/components/RsInput.vue +1101 -0
- package/src/components/RsInputNumber.vue +532 -0
- package/src/components/RsLabel.vue +54 -0
- package/src/components/RsLink.vue +106 -0
- package/src/components/RsLoading.vue +171 -0
- package/src/components/RsMarkdown.vue +329 -0
- package/src/components/RsMenu.vue +256 -0
- package/src/components/RsMenuItems.vue +125 -0
- package/src/components/RsMonacoEditor.vue +541 -0
- package/src/components/RsPagination.vue +184 -0
- package/src/components/RsPopover.vue +77 -0
- package/src/components/RsProseEditor.vue +160 -0
- package/src/components/RsScrollbar.vue +171 -0
- package/src/components/RsSelect.vue +1045 -0
- package/src/components/RsSidebar.vue +121 -0
- package/src/components/RsSidebarGroup.vue +32 -0
- package/src/components/RsSidebarItem.vue +72 -0
- package/src/components/RsSplitPane.vue +619 -0
- package/src/components/RsStatCard.vue +85 -0
- package/src/components/RsSteps.vue +138 -0
- package/src/components/RsTable.vue +2664 -0
- package/src/components/RsTableColgroup.vue +17 -0
- package/src/components/RsTableHeaderRow.vue +30 -0
- package/src/components/RsTabs.vue +951 -0
- package/src/components/RsTerminal.vue +697 -0
- package/src/components/RsTimePicker.vue +554 -0
- package/src/components/RsTimePickerColumns.vue +226 -0
- package/src/components/RsToaster.vue +396 -0
- package/src/components/RsToolbar.vue +135 -0
- package/src/components/RsTooltip.vue +142 -0
- package/src/components/RsTooltipProvider.vue +20 -0
- package/src/components/RsTree.vue +1248 -0
- package/src/components/RsUpload.vue +279 -0
- package/src/components/RsVirtualList.vue +217 -0
- package/src/components/avatar-utils.ts +19 -0
- package/src/components/breadcrumb-utils.ts +19 -0
- package/src/components/class-utils.ts +29 -0
- package/src/components/code-editor-utils.ts +106 -0
- package/src/components/code-mirror-completion.ts +40 -0
- package/src/components/code-mirror-diagnostics.ts +52 -0
- package/src/components/code-mirror-ghost.ts +158 -0
- package/src/components/code-mirror-inline-edit.ts +55 -0
- package/src/components/code-mirror-intel.ts +81 -0
- package/src/components/code-mirror-lang.ts +128 -0
- package/src/components/context-menu-utils.ts +20 -0
- package/src/components/date-picker-utils.ts +284 -0
- package/src/components/date-range-validation.ts +29 -0
- package/src/components/date-validation.ts +38 -0
- package/src/components/dialog-viewport.ts +48 -0
- package/src/components/dialog-window.ts +253 -0
- package/src/components/dropdown-utils.ts +31 -0
- package/src/components/form-utils.ts +77 -0
- package/src/components/input-number-utils.ts +128 -0
- package/src/components/input-rules.ts +84 -0
- package/src/components/loading-utils.ts +8 -0
- package/src/components/markdown-utils.ts +126 -0
- package/src/components/menu-utils.ts +48 -0
- package/src/components/overlay-utils.ts +47 -0
- package/src/components/pagination-utils.ts +38 -0
- package/src/components/popover-utils.ts +8 -0
- package/src/components/reka.ts +106 -0
- package/src/components/resolve-radius.ts +22 -0
- package/src/components/resolve-size.ts +29 -0
- package/src/components/scrollbar-utils.ts +10 -0
- package/src/components/select-utils.ts +72 -0
- package/src/components/split-pane-utils.ts +187 -0
- package/src/components/steps-utils.ts +22 -0
- package/src/components/table/RsTableBodyRow.vue +287 -0
- package/src/components/table/RsTableCell.vue +239 -0
- package/src/components/table/RsTableCellContent.ts +18 -0
- package/src/components/table/RsTableCellEditor.vue +351 -0
- package/src/components/table/RsTableEditGutter.vue +54 -0
- package/src/components/table/RsTableHeaderFilter.vue +107 -0
- package/src/components/table/RsTableRowStatus.vue +69 -0
- package/src/components/table/rs-table.css +1141 -0
- package/src/components/table/table-context-menu.ts +53 -0
- package/src/components/table/table-edit-utils.ts +496 -0
- package/src/components/table/table-keys.ts +12 -0
- package/src/components/table-drag.ts +215 -0
- package/src/components/table-utils.ts +950 -0
- package/src/components/tabs-utils.ts +107 -0
- package/src/components/terminal-utils.ts +301 -0
- package/src/components/terminal-wheel.ts +49 -0
- package/src/components/time-picker-utils.ts +167 -0
- package/src/components/time-range-validation.ts +23 -0
- package/src/components/time-validation.ts +20 -0
- package/src/components/tree-utils.ts +518 -0
- package/src/components/upload-utils.ts +55 -0
- package/src/components/virtual-list-utils.ts +17 -0
- package/src/composables/useClickOutside.ts +22 -0
- package/src/composables/useRsConfig.ts +91 -0
- package/src/composables/useRsI18n.ts +22 -0
- package/src/composables/useRsTabsNav.ts +187 -0
- package/src/composables/useRsToast.ts +48 -0
- package/src/composables/useTableCellTooltip.ts +239 -0
- package/src/composables/useTableEdit.ts +271 -0
- package/src/composables/useTableRowHighlight.ts +50 -0
- package/src/dev/vite-codemirror-deps.ts +20 -0
- package/src/icons/custom/api.ts +54 -0
- package/src/icons/custom/clickhouse.ts +52 -0
- package/src/icons/custom/dameng.ts +83 -0
- package/src/icons/custom/ftp.ts +61 -0
- package/src/icons/custom/kingbase.ts +52 -0
- package/src/icons/custom/mongodb.ts +50 -0
- package/src/icons/custom/mysql.ts +51 -0
- package/src/icons/custom/oracle.ts +52 -0
- package/src/icons/custom/redis.ts +51 -0
- package/src/icons/custom/sqlite.ts +50 -0
- package/src/icons/custom/sqlserver.ts +52 -0
- package/src/icons/custom/vastbase.ts +57 -0
- package/src/icons/lucide.ts +78 -0
- package/src/icons/registry.ts +35 -0
- package/src/index.ts +425 -0
- package/src/lib/iso-local-datetime.ts +152 -0
- package/src/lib/rs-dayjs.ts +56 -0
- package/src/locale/messages.ts +362 -0
- package/src/locale/types.ts +5 -0
- package/src/monaco/debug-decorations.css +20 -0
- package/src/monaco/debug-decorations.ts +70 -0
- package/src/monaco/index.ts +22 -0
- package/src/monaco/languages/index.ts +21 -0
- package/src/monaco/languages/mongodb-shell.ts +125 -0
- package/src/monaco/languages/types.ts +55 -0
- package/src/monaco/setup.ts +101 -0
- package/src/styles.css +1040 -0
- package/src/theme/apply.ts +12 -0
- package/src/theme/brand.example.css +37 -0
- package/src/theme/presets.ts +69 -0
- package/src/theme/types.ts +81 -0
- package/src/utils/rs-clipboard.ts +272 -0
- package/src/vite-env.d.ts +7 -0
- package/vite-plugins/monaco-zh-nls.ts +101 -0
- package/vite-plugins/silence-antlr-parse-console.ts +29 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { computed, inject } from 'vue'
|
|
2
|
+
import { localeMap } from '../locale/messages'
|
|
3
|
+
import { defaultLocale, type RsLocale } from '../locale/types'
|
|
4
|
+
import { rsConfigKey } from './useRsConfig'
|
|
5
|
+
|
|
6
|
+
export type RsTranslateFn = (key: string, fallback?: string) => string
|
|
7
|
+
|
|
8
|
+
/** 组件内安全取文案:Provider 内跟随 locale,否则回退 defaultLocale */
|
|
9
|
+
export function useRsI18n() {
|
|
10
|
+
const ctx = inject(rsConfigKey, null)
|
|
11
|
+
const locale = computed<RsLocale>(() => ctx?.locale.value ?? defaultLocale)
|
|
12
|
+
|
|
13
|
+
function t(key: string, fallback?: string): string {
|
|
14
|
+
return localeMap[locale.value][key] ?? fallback ?? key
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { t, locale }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createTranslator(locale: RsLocale): RsTranslateFn {
|
|
21
|
+
return (key, fallback) => localeMap[locale][key] ?? fallback ?? key
|
|
22
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
nextTick,
|
|
4
|
+
onMounted,
|
|
5
|
+
onUnmounted,
|
|
6
|
+
ref,
|
|
7
|
+
watch,
|
|
8
|
+
type ComputedRef,
|
|
9
|
+
type Ref,
|
|
10
|
+
} from 'vue'
|
|
11
|
+
import { resolveVisibleTabValues, type RsTabItem, type RsTabsOverflow } from '../components/tabs-utils'
|
|
12
|
+
|
|
13
|
+
interface UseRsTabsNavOptions {
|
|
14
|
+
items: ComputedRef<RsTabItem[]>
|
|
15
|
+
activeValue: Ref<string>
|
|
16
|
+
overflow: ComputedRef<RsTabsOverflow | false>
|
|
17
|
+
canAdd: ComputedRef<boolean>
|
|
18
|
+
navRef: Ref<HTMLElement | null>
|
|
19
|
+
measureRef: Ref<HTMLElement | null>
|
|
20
|
+
overflowRef: Ref<HTMLElement | null>
|
|
21
|
+
addButtonWidth: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useRsTabsNav(options: UseRsTabsNavOptions) {
|
|
25
|
+
const navViewportRef = ref<HTMLElement | null>(null)
|
|
26
|
+
const triggerWidths = ref<Map<string, number>>(new Map())
|
|
27
|
+
const visibleValues = ref<Set<string>>(new Set())
|
|
28
|
+
const canScrollPrev = ref(false)
|
|
29
|
+
const canScrollNext = ref(false)
|
|
30
|
+
|
|
31
|
+
let resizeObserver: ResizeObserver | null = null
|
|
32
|
+
|
|
33
|
+
const useDropdownOverflow = computed(() => options.overflow.value === 'dropdown')
|
|
34
|
+
const useScrollOverflow = computed(() => options.overflow.value === 'scroll')
|
|
35
|
+
|
|
36
|
+
const visibleTabItems = computed(() =>
|
|
37
|
+
options.items.value.filter((item) => visibleValues.value.has(item.value)),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const hiddenItems = computed(() =>
|
|
41
|
+
options.items.value.filter((item) => !visibleValues.value.has(item.value)),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const overflowMenuItems = computed(() =>
|
|
45
|
+
hiddenItems.value.map((item) => ({ value: item.value, label: item.label })),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const activeInHidden = computed(() =>
|
|
49
|
+
hiddenItems.value.some((item) => item.value === options.activeValue.value),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const showOverflowMenu = computed(
|
|
53
|
+
() => useDropdownOverflow.value && hiddenItems.value.length > 0,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
function measureTriggerWidths(): Map<string, number> {
|
|
57
|
+
const measureRoot = options.measureRef.value
|
|
58
|
+
if (measureRoot) {
|
|
59
|
+
const next = new Map<string, number>()
|
|
60
|
+
for (const node of measureRoot.querySelectorAll<HTMLElement>('[data-tab-value]')) {
|
|
61
|
+
const value = node.dataset.tabValue
|
|
62
|
+
if (value) next.set(value, node.offsetWidth)
|
|
63
|
+
}
|
|
64
|
+
if (next.size > 0) return next
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const viewport = navViewportRef.value
|
|
68
|
+
const next = new Map<string, number>()
|
|
69
|
+
if (!viewport) return next
|
|
70
|
+
for (const trigger of viewport.querySelectorAll<HTMLElement>('.rs-tabs__trigger[data-tab-value]')) {
|
|
71
|
+
const value = trigger.dataset.tabValue
|
|
72
|
+
if (value) next.set(value, trigger.offsetWidth)
|
|
73
|
+
}
|
|
74
|
+
return next
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function reservedWidth(): number {
|
|
78
|
+
let width = 0
|
|
79
|
+
if (useDropdownOverflow.value && options.items.value.length > 1) {
|
|
80
|
+
width += options.overflowRef.value?.offsetWidth ?? 76
|
|
81
|
+
}
|
|
82
|
+
if (options.canAdd.value) width += options.addButtonWidth
|
|
83
|
+
return width
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function recomputeVisibleTabs() {
|
|
87
|
+
if (!useDropdownOverflow.value) {
|
|
88
|
+
visibleValues.value = new Set(options.items.value.map((item) => item.value))
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const nav = options.navRef.value
|
|
93
|
+
if (!nav) return
|
|
94
|
+
|
|
95
|
+
triggerWidths.value = measureTriggerWidths()
|
|
96
|
+
const availableWidth = nav.clientWidth
|
|
97
|
+
visibleValues.value = resolveVisibleTabValues(
|
|
98
|
+
options.items.value,
|
|
99
|
+
triggerWidths.value,
|
|
100
|
+
availableWidth,
|
|
101
|
+
options.activeValue.value,
|
|
102
|
+
reservedWidth(),
|
|
103
|
+
{ keepActiveVisible: false },
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function updateScrollButtons() {
|
|
108
|
+
const viewport = navViewportRef.value
|
|
109
|
+
if (!viewport || !useScrollOverflow.value) {
|
|
110
|
+
canScrollPrev.value = false
|
|
111
|
+
canScrollNext.value = false
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
canScrollPrev.value = viewport.scrollLeft > 1
|
|
115
|
+
canScrollNext.value = viewport.scrollLeft + viewport.clientWidth < viewport.scrollWidth - 1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function scrollNav(direction: -1 | 1) {
|
|
119
|
+
navViewportRef.value?.scrollBy({ left: direction * 180, behavior: 'smooth' })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function onNavScroll() {
|
|
123
|
+
updateScrollButtons()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function scrollActiveTabIntoView() {
|
|
127
|
+
if (!useScrollOverflow.value) return
|
|
128
|
+
await nextTick()
|
|
129
|
+
const viewport = navViewportRef.value
|
|
130
|
+
if (!viewport) return
|
|
131
|
+
const active = viewport.querySelector<HTMLElement>(`.rs-tabs__trigger[data-state='active']`)
|
|
132
|
+
active?.scrollIntoView({ inline: 'nearest', block: 'nearest', behavior: 'smooth' })
|
|
133
|
+
updateScrollButtons()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function scheduleLayout() {
|
|
137
|
+
nextTick(() => {
|
|
138
|
+
recomputeVisibleTabs()
|
|
139
|
+
updateScrollButtons()
|
|
140
|
+
if (!useDropdownOverflow.value) return
|
|
141
|
+
nextTick(() => {
|
|
142
|
+
recomputeVisibleTabs()
|
|
143
|
+
updateScrollButtons()
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onMounted(() => {
|
|
149
|
+
scheduleLayout()
|
|
150
|
+
const nav = options.navRef.value
|
|
151
|
+
if (!nav || typeof ResizeObserver === 'undefined') return
|
|
152
|
+
resizeObserver = new ResizeObserver(() => scheduleLayout())
|
|
153
|
+
resizeObserver.observe(nav)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
onUnmounted(() => {
|
|
157
|
+
resizeObserver?.disconnect()
|
|
158
|
+
resizeObserver = null
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
watch(
|
|
162
|
+
() => [options.items.value, options.overflow.value, options.canAdd.value] as const,
|
|
163
|
+
() => scheduleLayout(),
|
|
164
|
+
{ deep: true },
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
watch(options.activeValue, () => {
|
|
168
|
+
scheduleLayout()
|
|
169
|
+
void scrollActiveTabIntoView()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
navViewportRef,
|
|
174
|
+
useDropdownOverflow,
|
|
175
|
+
useScrollOverflow,
|
|
176
|
+
visibleTabItems,
|
|
177
|
+
hiddenItems,
|
|
178
|
+
overflowMenuItems,
|
|
179
|
+
activeInHidden,
|
|
180
|
+
showOverflowMenu,
|
|
181
|
+
canScrollPrev,
|
|
182
|
+
canScrollNext,
|
|
183
|
+
scrollNav,
|
|
184
|
+
onNavScroll,
|
|
185
|
+
scheduleLayout,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { toast } from 'vue-sonner'
|
|
2
|
+
import type { RsToastPosition, RsToastType } from '../components/overlay-utils'
|
|
3
|
+
|
|
4
|
+
export type { RsToastPosition, RsToastType }
|
|
5
|
+
|
|
6
|
+
export type RsToastInput =
|
|
7
|
+
| string
|
|
8
|
+
| {
|
|
9
|
+
title: string
|
|
10
|
+
description?: string
|
|
11
|
+
position?: RsToastPosition
|
|
12
|
+
duration?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveToast(input: RsToastInput): {
|
|
16
|
+
title: string
|
|
17
|
+
description?: string
|
|
18
|
+
position?: RsToastPosition
|
|
19
|
+
duration?: number
|
|
20
|
+
} {
|
|
21
|
+
if (typeof input === 'string') return { title: input }
|
|
22
|
+
return input
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useRsToast() {
|
|
26
|
+
function show(type: RsToastType, input: RsToastInput): void {
|
|
27
|
+
const payload = resolveToast(input)
|
|
28
|
+
const options = {
|
|
29
|
+
description: payload.description,
|
|
30
|
+
...(payload.position ? { position: payload.position } : {}),
|
|
31
|
+
...(payload.duration ? { duration: payload.duration } : {}),
|
|
32
|
+
class: `rs-toast rs-toast--${type}`,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (type === 'success') toast.success(payload.title, options)
|
|
36
|
+
else if (type === 'error') toast.error(payload.title, options)
|
|
37
|
+
else if (type === 'warning') toast.warning(payload.title, options)
|
|
38
|
+
else toast.info(payload.title, options)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
success: (input: RsToastInput) => show('success', input),
|
|
43
|
+
error: (input: RsToastInput) => show('error', input),
|
|
44
|
+
info: (input: RsToastInput) => show('info', input),
|
|
45
|
+
warning: (input: RsToastInput) => show('warning', input),
|
|
46
|
+
dismiss: toast.dismiss,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { nextTick, onUnmounted, ref, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export const RS_TABLE_TIP_ATTR = 'data-rs-table-tip'
|
|
4
|
+
export const RS_TABLE_TIP_MODE_ATTR = 'data-rs-table-tip-mode'
|
|
5
|
+
export const RS_TABLE_TIP_TEXT_ATTR = 'data-rs-table-tip-text'
|
|
6
|
+
/** 列头 headerTip:整表共享浮层,悬停 th-label 显示 */
|
|
7
|
+
export const RS_TABLE_HEADER_TIP_ATTR = 'data-rs-table-header-tip'
|
|
8
|
+
|
|
9
|
+
const TIP_SIDE_OFFSET = 6
|
|
10
|
+
const TIP_VIEWPORT_PAD = 8
|
|
11
|
+
const TIP_HIDE_GRACE_MS = 80
|
|
12
|
+
|
|
13
|
+
export interface TableCellTooltipState {
|
|
14
|
+
visible: boolean
|
|
15
|
+
ready: boolean
|
|
16
|
+
text: string
|
|
17
|
+
kind: 'cell' | 'header'
|
|
18
|
+
style: { left: string; top: string }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isHeaderTipHost(host: HTMLElement): boolean {
|
|
22
|
+
return host.hasAttribute(RS_TABLE_HEADER_TIP_ATTR)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findTipHost(target: EventTarget | null): HTMLElement | null {
|
|
26
|
+
if (!(target instanceof Element)) return null
|
|
27
|
+
const header = target.closest(`[${RS_TABLE_HEADER_TIP_ATTR}]`)
|
|
28
|
+
if (header instanceof HTMLElement) return header
|
|
29
|
+
const cell = target.closest(`[${RS_TABLE_TIP_ATTR}], .rs-table__cell-tip`)
|
|
30
|
+
return cell instanceof HTMLElement ? cell : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveMeasureEl(host: HTMLElement): HTMLElement {
|
|
34
|
+
if (host.classList.contains('rs-table__ellipsis-text')) return host
|
|
35
|
+
const inner = host.querySelector('.rs-table__ellipsis-text')
|
|
36
|
+
return inner instanceof HTMLElement ? inner : host
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveTipText(host: HTMLElement): string | null {
|
|
40
|
+
if (isHeaderTipHost(host)) {
|
|
41
|
+
const text = host.getAttribute(RS_TABLE_HEADER_TIP_ATTR)?.trim()
|
|
42
|
+
return text || null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (host.hasAttribute(RS_TABLE_TIP_ATTR)) {
|
|
46
|
+
const text = host.getAttribute(RS_TABLE_TIP_ATTR)?.trim()
|
|
47
|
+
return text || null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const mode = host.getAttribute(RS_TABLE_TIP_MODE_ATTR)
|
|
51
|
+
if (!mode) return null
|
|
52
|
+
|
|
53
|
+
const text = host.getAttribute(RS_TABLE_TIP_TEXT_ATTR)?.trim() ?? ''
|
|
54
|
+
if (mode === 'always') return text || null
|
|
55
|
+
|
|
56
|
+
if (mode === 'overflow') {
|
|
57
|
+
const measure = resolveMeasureEl(host)
|
|
58
|
+
if (measure.scrollWidth <= measure.clientWidth) return null
|
|
59
|
+
return text || measure.textContent?.trim() || null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function clamp(value: number, min: number, max: number): number {
|
|
66
|
+
return Math.min(Math.max(value, min), max)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function useTableCellTooltip(options: {
|
|
70
|
+
enabled: () => boolean
|
|
71
|
+
/** 列头 headerTip 共享浮层;默认 true */
|
|
72
|
+
headerEnabled?: () => boolean
|
|
73
|
+
delay: () => number
|
|
74
|
+
tipRef: Ref<HTMLElement | null>
|
|
75
|
+
}): {
|
|
76
|
+
state: Ref<TableCellTooltipState>
|
|
77
|
+
onPointerOver: (event: PointerEvent) => void
|
|
78
|
+
onPointerOut: (event: PointerEvent) => void
|
|
79
|
+
hide: () => void
|
|
80
|
+
} {
|
|
81
|
+
const state = ref<TableCellTooltipState>({
|
|
82
|
+
visible: false,
|
|
83
|
+
ready: false,
|
|
84
|
+
text: '',
|
|
85
|
+
kind: 'cell',
|
|
86
|
+
style: { left: '0', top: '0' },
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
let tipHost: HTMLElement | null = null
|
|
90
|
+
let showTimer = 0
|
|
91
|
+
let hideTimer = 0
|
|
92
|
+
let positionRaf = 0
|
|
93
|
+
|
|
94
|
+
function clearTimers(): void {
|
|
95
|
+
if (showTimer) {
|
|
96
|
+
clearTimeout(showTimer)
|
|
97
|
+
showTimer = 0
|
|
98
|
+
}
|
|
99
|
+
if (hideTimer) {
|
|
100
|
+
clearTimeout(hideTimer)
|
|
101
|
+
hideTimer = 0
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function cancelPositionRaf(): void {
|
|
106
|
+
if (positionRaf) {
|
|
107
|
+
cancelAnimationFrame(positionRaf)
|
|
108
|
+
positionRaf = 0
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function positionTip(host: HTMLElement): void {
|
|
113
|
+
const tipEl = options.tipRef.value
|
|
114
|
+
if (!tipEl) return
|
|
115
|
+
|
|
116
|
+
const hostRect = host.getBoundingClientRect()
|
|
117
|
+
const tipRect = tipEl.getBoundingClientRect()
|
|
118
|
+
const vw = window.innerWidth
|
|
119
|
+
const vh = window.innerHeight
|
|
120
|
+
|
|
121
|
+
let left: number
|
|
122
|
+
let top: number
|
|
123
|
+
|
|
124
|
+
if (isHeaderTipHost(host)) {
|
|
125
|
+
left = hostRect.left
|
|
126
|
+
top = hostRect.bottom + TIP_SIDE_OFFSET
|
|
127
|
+
if (top + tipRect.height > vh - TIP_VIEWPORT_PAD) {
|
|
128
|
+
top = hostRect.top - tipRect.height - TIP_SIDE_OFFSET
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
left = hostRect.left + hostRect.width / 2 - tipRect.width / 2
|
|
132
|
+
top = hostRect.top - tipRect.height - TIP_SIDE_OFFSET
|
|
133
|
+
if (top < TIP_VIEWPORT_PAD) {
|
|
134
|
+
top = hostRect.bottom + TIP_SIDE_OFFSET
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
left = clamp(left, TIP_VIEWPORT_PAD, Math.max(TIP_VIEWPORT_PAD, vw - tipRect.width - TIP_VIEWPORT_PAD))
|
|
139
|
+
top = clamp(top, TIP_VIEWPORT_PAD, Math.max(TIP_VIEWPORT_PAD, vh - tipRect.height - TIP_VIEWPORT_PAD))
|
|
140
|
+
|
|
141
|
+
state.value.style = {
|
|
142
|
+
left: `${left}px`,
|
|
143
|
+
top: `${top}px`,
|
|
144
|
+
}
|
|
145
|
+
state.value.ready = true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hide(): void {
|
|
149
|
+
clearTimers()
|
|
150
|
+
cancelPositionRaf()
|
|
151
|
+
tipHost = null
|
|
152
|
+
state.value.visible = false
|
|
153
|
+
state.value.ready = false
|
|
154
|
+
state.value.text = ''
|
|
155
|
+
state.value.kind = 'cell'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function show(host: HTMLElement, text: string): void {
|
|
159
|
+
tipHost = host
|
|
160
|
+
state.value.text = text
|
|
161
|
+
state.value.kind = isHeaderTipHost(host) ? 'header' : 'cell'
|
|
162
|
+
state.value.ready = false
|
|
163
|
+
state.value.visible = true
|
|
164
|
+
|
|
165
|
+
cancelPositionRaf()
|
|
166
|
+
nextTick(() => {
|
|
167
|
+
positionRaf = requestAnimationFrame(() => {
|
|
168
|
+
positionRaf = 0
|
|
169
|
+
if (tipHost === host) positionTip(host)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function scheduleShow(host: HTMLElement): void {
|
|
175
|
+
const header = isHeaderTipHost(host)
|
|
176
|
+
if (header) {
|
|
177
|
+
if (options.headerEnabled && !options.headerEnabled()) return
|
|
178
|
+
} else if (!options.enabled()) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const text = resolveTipText(host)
|
|
183
|
+
if (!text) {
|
|
184
|
+
hide()
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
clearTimers()
|
|
189
|
+
if (tipHost !== host) tipHost = host
|
|
190
|
+
|
|
191
|
+
const delay = options.delay()
|
|
192
|
+
if (delay <= 0) {
|
|
193
|
+
show(host, text)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
showTimer = window.setTimeout(() => {
|
|
198
|
+
showTimer = 0
|
|
199
|
+
if (tipHost === host) show(host, text)
|
|
200
|
+
}, delay)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function scheduleHide(): void {
|
|
204
|
+
clearTimers()
|
|
205
|
+
hideTimer = window.setTimeout(hide, TIP_HIDE_GRACE_MS)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function onPointerOver(event: PointerEvent): void {
|
|
209
|
+
if (event.pointerType === 'touch') return
|
|
210
|
+
|
|
211
|
+
const host = findTipHost(event.target)
|
|
212
|
+
if (!host) {
|
|
213
|
+
if (tipHost) scheduleHide()
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (host === tipHost && state.value.visible) return
|
|
218
|
+
if (tipHost && host !== tipHost) hide()
|
|
219
|
+
scheduleShow(host)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function onPointerOut(event: PointerEvent): void {
|
|
223
|
+
const related = event.relatedTarget
|
|
224
|
+
if (related instanceof Element && findTipHost(related)) return
|
|
225
|
+
scheduleHide()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
onUnmounted(() => {
|
|
229
|
+
clearTimers()
|
|
230
|
+
cancelPositionRaf()
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
state,
|
|
235
|
+
onPointerOver,
|
|
236
|
+
onPointerOut,
|
|
237
|
+
hide,
|
|
238
|
+
}
|
|
239
|
+
}
|