@globalbrain/sefirot 2.0.0-draft.8 → 2.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/README.md +6 -6
- package/lib/components/SAvatar.vue +17 -17
- package/lib/components/SButton.vue +512 -267
- package/lib/components/SButtonGroup.vue +149 -0
- package/lib/components/SDropdown.vue +26 -150
- package/lib/components/SDropdownSection.vue +48 -0
- package/lib/components/SDropdownSectionFilter.vue +189 -0
- package/lib/components/SDropdownSectionFilterItem.vue +21 -0
- package/lib/components/SDropdownSectionFilterItemAvatar.vue +31 -0
- package/lib/components/SDropdownSectionFilterItemText.vue +20 -0
- package/lib/components/SDropdownSectionMenu.vue +39 -0
- package/lib/components/SIcon.vue +13 -0
- package/lib/components/SInputBase.vue +31 -31
- package/lib/components/SInputCheckbox.vue +1 -1
- package/lib/components/SInputCheckboxes.vue +74 -0
- package/lib/components/SInputDate.vue +182 -0
- package/lib/components/SInputDropdown.vue +158 -157
- package/lib/components/SInputDropdownItem.vue +46 -48
- package/lib/components/{SInputDropdownItemUserTag.vue → SInputDropdownItemAvatar.vue} +43 -44
- package/lib/components/SInputDropdownItemText.vue +79 -16
- package/lib/components/SInputFile.vue +55 -60
- package/lib/components/SInputHMS.vue +120 -110
- package/lib/components/SInputNumber.vue +38 -9
- package/lib/components/SInputRadio.vue +39 -36
- package/lib/components/SInputRadios.vue +40 -53
- package/lib/components/SInputSelect.vue +3 -3
- package/lib/components/SInputSwitch.vue +193 -0
- package/lib/components/SInputSwitches.vue +88 -0
- package/lib/components/SInputText.vue +206 -62
- package/lib/components/SInputTextarea.vue +46 -32
- package/lib/components/SInputYMD.vue +123 -126
- package/lib/components/SMarkdown.vue +52 -0
- package/lib/components/SModal.vue +25 -63
- package/lib/components/SMount.vue +19 -0
- package/lib/components/SSheet.vue +49 -55
- package/lib/components/SSheetFooter.vue +1 -1
- package/lib/components/SSheetFooterAction.vue +24 -17
- package/lib/components/SSheetFooterActions.vue +1 -4
- package/lib/components/SSheetForm.vue +15 -0
- package/lib/components/SSheetMedium.vue +8 -10
- package/lib/components/SSheetTitle.vue +7 -14
- package/lib/components/SSnackbar.vue +55 -45
- package/lib/components/{SPortalSnackbars.vue → SSnackbars.vue} +17 -20
- package/lib/components/SStep.vue +106 -0
- package/lib/components/SSteps.vue +59 -0
- package/lib/components/STable.vue +241 -0
- package/lib/components/STableCell.vue +82 -0
- package/lib/components/STableCellAvatar.vue +69 -0
- package/lib/components/STableCellAvatars.vue +93 -0
- package/lib/components/STableCellDay.vue +40 -0
- package/lib/components/STableCellPill.vue +84 -0
- package/lib/components/STableCellText.vue +102 -0
- package/lib/components/STableColumn.vue +255 -0
- package/lib/components/STableFooter.vue +115 -0
- package/lib/components/STableHeader.vue +74 -0
- package/lib/components/STableItem.vue +38 -0
- package/lib/components/STooltip.vue +112 -0
- package/lib/composables/Dropdown.ts +40 -99
- package/lib/composables/Form.ts +21 -18
- package/lib/composables/Grid.ts +117 -0
- package/lib/composables/Markdown.ts +138 -0
- package/lib/composables/Step.ts +7 -0
- package/lib/composables/Table.ts +103 -0
- package/lib/composables/Tooltip.ts +91 -0
- package/lib/composables/Validation.ts +5 -9
- package/lib/composables/markdown/LinkPlugin.ts +45 -0
- package/lib/mixins/Sheet.ts +5 -3
- package/lib/stores/Snackbars.ts +48 -0
- package/lib/{assets/styles → styles}/base.css +0 -0
- package/lib/{assets/styles → styles}/bootstrap.css +1 -0
- package/lib/{assets/styles → styles}/variables.css +55 -48
- package/lib/support/Day.ts +8 -0
- package/lib/support/Num.ts +3 -0
- package/lib/support/Time.ts +5 -2
- package/lib/support/Utils.ts +4 -3
- package/lib/types/shims.d.ts +3 -0
- package/lib/validation/validators/requiredYmd.ts +1 -1
- package/lib/validation/validators/ymd.ts +4 -4
- package/package.json +57 -37
- package/CHANGELOG.md +0 -47
- package/lib/.DS_Store +0 -0
- package/lib/components/.DS_Store +0 -0
- package/lib/components/SDialog.vue +0 -140
- package/lib/components/SDropdownItem.vue +0 -78
- package/lib/components/SDropdownItemText.vue +0 -22
- package/lib/components/SDropdownItemUser.vue +0 -40
- package/lib/components/SInputDropdownItemTextTag.vue +0 -94
- package/lib/components/SInputDropdownItemUser.vue +0 -41
- package/lib/components/SPortalModals.vue +0 -74
- package/lib/components/icons/.DS_Store +0 -0
- package/lib/composables/Dialog.ts +0 -38
- package/lib/composables/Modal.ts +0 -34
- package/lib/composables/Snackbar.ts +0 -18
- package/lib/store/Sefirot.ts +0 -17
- package/lib/store/dialog/index.ts +0 -42
- package/lib/store/modal/index.ts +0 -61
- package/lib/store/snackbars/index.ts +0 -70
|
@@ -1,119 +1,60 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Ref, ComputedRef, ref, computed, isRef } from 'vue'
|
|
1
|
+
import { MaybeRef } from '@vueuse/core'
|
|
3
2
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
closeOnClick: boolean
|
|
8
|
-
selected?: ComputedRef<any>
|
|
9
|
-
items: ComputedRef<Item[]>
|
|
10
|
-
callback? (item: Item): void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface Search {
|
|
14
|
-
placeholder: string
|
|
15
|
-
missing: string
|
|
16
|
-
value: Ref<string>
|
|
17
|
-
onInput (text: string | null): void
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type Item = TextItem | UserItem
|
|
21
|
-
|
|
22
|
-
export interface ItemBase {
|
|
23
|
-
type: string
|
|
24
|
-
value: any
|
|
25
|
-
disabled?: boolean
|
|
26
|
-
callback?: Function
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type ItemType = 'text' | 'user'
|
|
3
|
+
export type DropdownSection =
|
|
4
|
+
| DropdownSectionMenu
|
|
5
|
+
| DropdownSectionFilter
|
|
30
6
|
|
|
31
|
-
export
|
|
32
|
-
type: 'text'
|
|
33
|
-
text: string
|
|
34
|
-
}
|
|
7
|
+
export type DropdownSectionType = 'menu' | 'filter'
|
|
35
8
|
|
|
36
|
-
export interface
|
|
37
|
-
type:
|
|
38
|
-
avatar: string
|
|
39
|
-
name: string
|
|
9
|
+
export interface DropdownSectionBase {
|
|
10
|
+
type: DropdownSectionType
|
|
40
11
|
}
|
|
41
12
|
|
|
42
|
-
export interface
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
closeOnClick?: boolean
|
|
46
|
-
selected?: ComputedRef<any>
|
|
47
|
-
items: Item[] | ComputedRef<Item[]>
|
|
48
|
-
callback? (item: Item): void
|
|
13
|
+
export interface DropdownSectionMenu extends DropdownSectionBase {
|
|
14
|
+
type: 'menu'
|
|
15
|
+
options: DropdownSectionMenuOption[]
|
|
49
16
|
}
|
|
50
17
|
|
|
51
|
-
export interface
|
|
52
|
-
|
|
53
|
-
|
|
18
|
+
export interface DropdownSectionMenuOption {
|
|
19
|
+
label: string
|
|
20
|
+
onClick(): void
|
|
54
21
|
}
|
|
55
22
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const callback = dropdown.callback
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
title,
|
|
66
|
-
search,
|
|
67
|
-
closeOnClick,
|
|
68
|
-
selected,
|
|
69
|
-
items,
|
|
70
|
-
callback
|
|
71
|
-
}
|
|
23
|
+
export interface DropdownSectionFilter extends DropdownSectionBase {
|
|
24
|
+
type: 'filter'
|
|
25
|
+
search?: boolean
|
|
26
|
+
selected: MaybeRef<DropdownSectionFilterSelectedValue>
|
|
27
|
+
options: DropdownSectionFilterOption[]
|
|
28
|
+
onClick?(value: string | number | boolean): void
|
|
72
29
|
}
|
|
73
30
|
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
value.value = text
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
placeholder,
|
|
85
|
-
missing,
|
|
86
|
-
value,
|
|
87
|
-
onInput
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function useItems(items: Item[] | ComputedRef<Item[]>, search?: Search): ComputedRef<Item[]> {
|
|
92
|
-
const fuse = computed(() => {
|
|
93
|
-
return createFuse(isRef(items) ? items.value : items, search)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
return computed(() => {
|
|
97
|
-
const value = search?.value.value
|
|
31
|
+
export type DropdownSectionFilterSelectedValue =
|
|
32
|
+
| string
|
|
33
|
+
| number
|
|
34
|
+
| boolean
|
|
35
|
+
| null
|
|
36
|
+
| (string | number | boolean)[]
|
|
98
37
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
38
|
+
export type DropdownSectionFilterOption =
|
|
39
|
+
| DropdownSectionFilterOptionText
|
|
40
|
+
| DropdownSectionFilterOptionAvatar
|
|
102
41
|
|
|
103
|
-
|
|
104
|
-
|
|
42
|
+
export interface DropdownSectionFilterOptionBase {
|
|
43
|
+
type?: 'text' | 'avatar'
|
|
44
|
+
label: string
|
|
45
|
+
value: string | number | boolean
|
|
46
|
+
onClick?(value: string | number | boolean): void
|
|
105
47
|
}
|
|
106
48
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
? new Fuse(items, { keys: ['text', 'name'], threshold: 0.3 })
|
|
110
|
-
: null
|
|
49
|
+
export interface DropdownSectionFilterOptionText extends DropdownSectionFilterOptionBase {
|
|
50
|
+
type?: 'text'
|
|
111
51
|
}
|
|
112
52
|
|
|
113
|
-
export
|
|
114
|
-
|
|
53
|
+
export interface DropdownSectionFilterOptionAvatar extends DropdownSectionFilterOptionBase {
|
|
54
|
+
type: 'avatar'
|
|
55
|
+
image?: string | null
|
|
115
56
|
}
|
|
116
57
|
|
|
117
|
-
export function
|
|
118
|
-
return
|
|
58
|
+
export function createDropdown(section: DropdownSection[]): DropdownSection[] {
|
|
59
|
+
return section
|
|
119
60
|
}
|
package/lib/composables/Form.ts
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Validation,
|
|
1
|
+
import cloneDeep from 'lodash-es/cloneDeep'
|
|
2
|
+
import { Ref, reactive } from 'vue'
|
|
3
|
+
import { useSnackbars } from '../stores/Snackbars'
|
|
4
|
+
import { Validation, useValidation } from './Validation'
|
|
5
5
|
|
|
6
|
-
export interface Form<
|
|
7
|
-
data:
|
|
8
|
-
validation: Ref<Validation
|
|
6
|
+
export interface Form<T extends Record<string, any>> {
|
|
7
|
+
data: T
|
|
8
|
+
validation: Ref<Validation<any, T>>
|
|
9
9
|
init(): void
|
|
10
10
|
reset(): void
|
|
11
11
|
validate(): Promise<boolean>
|
|
12
12
|
validateAndNotify(): Promise<boolean>
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export interface UseFormOptions<
|
|
16
|
-
data:
|
|
17
|
-
rules?:
|
|
15
|
+
export interface UseFormOptions<T extends Record<string, any>> {
|
|
16
|
+
data: T,
|
|
17
|
+
rules?: Record<string, any>
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function useForm<
|
|
21
|
-
|
|
20
|
+
export function useForm<
|
|
21
|
+
T extends Record<string, any>
|
|
22
|
+
>(options: UseFormOptions<T>): Form<T> {
|
|
23
|
+
const snackbars = useSnackbars()
|
|
22
24
|
|
|
23
25
|
const initialData = cloneDeep(options.data)
|
|
24
26
|
|
|
25
|
-
const data = reactive(options.data
|
|
27
|
+
const data = reactive(options.data)
|
|
26
28
|
|
|
27
29
|
const rules = options.rules ?? {}
|
|
28
30
|
|
|
29
|
-
const validation = useValidation(data, rules
|
|
31
|
+
const validation = useValidation(data, rules)
|
|
30
32
|
|
|
31
33
|
function init(): void {
|
|
32
34
|
Object.assign(data, initialData)
|
|
@@ -42,15 +44,16 @@ export function useForm<D>(options: UseFormOptions<D>): Form<D> {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
async function validateAndNotify(): Promise<boolean> {
|
|
45
|
-
const
|
|
47
|
+
const valid = await validate()
|
|
46
48
|
|
|
47
|
-
if (!
|
|
48
|
-
|
|
49
|
+
if (!valid) {
|
|
50
|
+
snackbars.push({
|
|
51
|
+
mode: 'danger',
|
|
49
52
|
text: 'Form contains errors. Please correct them and try again.'
|
|
50
53
|
})
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
return
|
|
56
|
+
return valid
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
return {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Ref, ref, watchEffect, onMounted, onUnmounted } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface Grid {
|
|
4
|
+
container: Ref<HTMLElement | null>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface UseGridOptions {
|
|
8
|
+
tag?: string
|
|
9
|
+
class?: string
|
|
10
|
+
type?: 'fill' | 'fit'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type CssStyles = Partial<Record<keyof CSSStyleDeclaration, string>>
|
|
14
|
+
|
|
15
|
+
export function useGrid(options: UseGridOptions): Grid {
|
|
16
|
+
const container: Ref<HTMLElement | null> = ref(null)
|
|
17
|
+
|
|
18
|
+
const spacerClass = options.class ? toClassName(options.class) : 'spacer'
|
|
19
|
+
const spacerTag = options.tag ?? 'div'
|
|
20
|
+
const type = options.type ?? 'fit'
|
|
21
|
+
|
|
22
|
+
const observer = new MutationObserver((_, observer) => {
|
|
23
|
+
observer.disconnect()
|
|
24
|
+
|
|
25
|
+
adjustSpacer()
|
|
26
|
+
|
|
27
|
+
observer.observe(container.value!, { childList: true })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
watchEffect(() => {
|
|
31
|
+
observer.disconnect()
|
|
32
|
+
|
|
33
|
+
if (container.value) {
|
|
34
|
+
adjustSpacer()
|
|
35
|
+
observer.observe(container.value, { childList: true })
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
window.addEventListener('resize', adjustSpacer)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
onUnmounted(() => {
|
|
44
|
+
window.removeEventListener('resize', adjustSpacer)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function adjustSpacer() {
|
|
48
|
+
container.value?.querySelectorAll(`${toClassSelector(spacerClass)}`)
|
|
49
|
+
.forEach(n => n.remove())
|
|
50
|
+
|
|
51
|
+
const track = container.value?.firstElementChild
|
|
52
|
+
|
|
53
|
+
const containerWidth = container.value?.clientWidth ?? 0
|
|
54
|
+
const trackWidth = track?.clientWidth ?? 0
|
|
55
|
+
const trackCount = container.value?.childElementCount ?? 0
|
|
56
|
+
|
|
57
|
+
const perRow = trackWidth !== 0 ? Math.floor(containerWidth / trackWidth) : 0
|
|
58
|
+
const mod = perRow !== 0 ? trackCount % perRow : 0
|
|
59
|
+
const lack = mod !== 0 ? perRow - mod : 0
|
|
60
|
+
|
|
61
|
+
const fragment = createSpacers(lack, spacerTag, spacerClass, type)
|
|
62
|
+
|
|
63
|
+
container.value?.appendChild(fragment!)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
container
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toClassSelector(name: string) {
|
|
72
|
+
return name.startsWith('.') ? name : '.' + name
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function toClassName(name: string) {
|
|
76
|
+
return name.startsWith('.') ? name.slice(1) : name
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createSpacers(size: number, tag: string, classes: string, type: 'fill' | 'fit') {
|
|
80
|
+
const fragment = document.createDocumentFragment()
|
|
81
|
+
|
|
82
|
+
if (size === 0) {
|
|
83
|
+
return fragment
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (type === 'fill') {
|
|
87
|
+
const spacer = createSpacer(tag, classes, { gridColumn: `span ${size}`})
|
|
88
|
+
|
|
89
|
+
fragment.appendChild(spacer)
|
|
90
|
+
|
|
91
|
+
return fragment
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (type === 'fit') {
|
|
95
|
+
for (let i = 0; i < size; i++) {
|
|
96
|
+
fragment.appendChild(createSpacer(tag, classes))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return fragment
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createSpacer(tag: string, classes?: string, styles?: CssStyles) {
|
|
104
|
+
const spacer = document.createElement(tag)
|
|
105
|
+
|
|
106
|
+
if (classes) {
|
|
107
|
+
spacer.className = classes
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (styles) {
|
|
111
|
+
for (const s in styles) {
|
|
112
|
+
spacer.style[s] = styles[s]!
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return spacer
|
|
117
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it'
|
|
2
|
+
import { onUnmounted, Ref } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
import { isCallbackUrl, isExternalUrl, LinkAttrs, linkPlugin } from './markdown/LinkPlugin'
|
|
5
|
+
|
|
6
|
+
export type UseMarkdown = (source: string, inline: boolean) => string
|
|
7
|
+
|
|
8
|
+
export interface UseMarkdownOptions extends MarkdownIt.Options {
|
|
9
|
+
linkAttrs?: LinkAttrs
|
|
10
|
+
config?: (md: MarkdownIt) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useMarkdown(options: UseMarkdownOptions = {}): UseMarkdown {
|
|
14
|
+
const md = new MarkdownIt({
|
|
15
|
+
linkify: true,
|
|
16
|
+
...options
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
md.use(linkPlugin, {
|
|
20
|
+
target: '_blank',
|
|
21
|
+
rel: 'noopener noreferrer',
|
|
22
|
+
...options.linkAttrs
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (options.config) {
|
|
26
|
+
options.config(md)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (source, inline) => {
|
|
30
|
+
return inline ? md.renderInline(source) : md.render(source)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UseLink {
|
|
35
|
+
addListeners(): void
|
|
36
|
+
removeListeners(): void
|
|
37
|
+
subscribe(cb: LinkSubscriber): () => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface UseLinkOptions {
|
|
41
|
+
container: Ref<Element | null>
|
|
42
|
+
callbacks?: LinkCallback[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LinkSubscriberPayload {
|
|
46
|
+
event: Event
|
|
47
|
+
target: HTMLAnchorElement
|
|
48
|
+
isExternal: boolean
|
|
49
|
+
isCallback: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type LinkSubscriber = (payload: LinkSubscriberPayload) => void
|
|
53
|
+
|
|
54
|
+
export type LinkCallback = () => void
|
|
55
|
+
|
|
56
|
+
export function useLink({ container, callbacks }: UseLinkOptions): UseLink {
|
|
57
|
+
const router = useRouter()
|
|
58
|
+
const subscribers: LinkSubscriber[] = []
|
|
59
|
+
|
|
60
|
+
onUnmounted(() => removeListeners())
|
|
61
|
+
|
|
62
|
+
function handler(event: Event): void {
|
|
63
|
+
const target = event.target as HTMLAnchorElement
|
|
64
|
+
const href = target.getAttribute('href')!
|
|
65
|
+
|
|
66
|
+
if (!href) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const isExternal = isExternalUrl(href)
|
|
71
|
+
const isCallback = isCallbackUrl(href)
|
|
72
|
+
|
|
73
|
+
subscribers.forEach(sub => sub({
|
|
74
|
+
event,
|
|
75
|
+
target,
|
|
76
|
+
isExternal,
|
|
77
|
+
isCallback
|
|
78
|
+
}))
|
|
79
|
+
|
|
80
|
+
if (isExternal) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!event.defaultPrevented) {
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isCallback) {
|
|
89
|
+
const idx = parseInt(target.dataset.callbackId || '')
|
|
90
|
+
const callback = (callbacks ?? [])[idx]
|
|
91
|
+
|
|
92
|
+
if (!callback) {
|
|
93
|
+
throw new Error(`Callback not found at index: ${idx}`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return callback()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
router.push(href)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function addListeners(): void {
|
|
103
|
+
removeListeners()
|
|
104
|
+
|
|
105
|
+
if (container.value) {
|
|
106
|
+
findLinks(container.value).forEach((element) => {
|
|
107
|
+
element.addEventListener('click', handler)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function removeListeners(): void {
|
|
113
|
+
if (container.value) {
|
|
114
|
+
findLinks(container.value).forEach((element) => {
|
|
115
|
+
element.removeEventListener('click', handler)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function subscribe(fn: LinkSubscriber): () => void {
|
|
121
|
+
subscribers.push(fn)
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
const idx = subscribers.indexOf(fn)
|
|
125
|
+
idx > -1 && subscribers.splice(idx, 1)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
addListeners,
|
|
131
|
+
removeListeners,
|
|
132
|
+
subscribe
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function findLinks(target: Element) {
|
|
137
|
+
return target.querySelectorAll('a.SMarkdown-link')
|
|
138
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { MaybeRef } from '@vueuse/core'
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
|
+
import { DropdownSection } from './Dropdown'
|
|
4
|
+
|
|
5
|
+
export interface Table {
|
|
6
|
+
orders: string[]
|
|
7
|
+
columns: TableColumns
|
|
8
|
+
records?: Record<string, any>[]
|
|
9
|
+
total?: number
|
|
10
|
+
page?: number
|
|
11
|
+
perPage?: number
|
|
12
|
+
reset?: boolean
|
|
13
|
+
borderless?: boolean
|
|
14
|
+
loading?: boolean
|
|
15
|
+
onPrev?(): void
|
|
16
|
+
onNext?(): void
|
|
17
|
+
onReset?(): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TableColumns {
|
|
21
|
+
[name: string]: TableColumn
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TableColumn {
|
|
25
|
+
label: string
|
|
26
|
+
className?: string
|
|
27
|
+
dropdown?: DropdownSection[]
|
|
28
|
+
cell?: TableCell
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type TableCell =
|
|
32
|
+
| TableCellText
|
|
33
|
+
| TableCellDay
|
|
34
|
+
| TableCellPill
|
|
35
|
+
| TableCellAvatar
|
|
36
|
+
| TableCellAvatars
|
|
37
|
+
|
|
38
|
+
export type TableCellType = 'text' | 'day' | 'pill' | 'avatar' | 'avatars'
|
|
39
|
+
|
|
40
|
+
export interface TableCellBase {
|
|
41
|
+
type: TableCellType
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TableCellText extends TableCellBase {
|
|
45
|
+
type: 'text'
|
|
46
|
+
icon?: any
|
|
47
|
+
value?: string | ((value: any) => string)
|
|
48
|
+
link?(value: any, record: any): string
|
|
49
|
+
color?: 'neutral' | 'soft' | 'mute'
|
|
50
|
+
iconColor?: 'neutral' | 'soft' | 'mute'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TableCellDay extends TableCellBase {
|
|
54
|
+
type: 'day'
|
|
55
|
+
format?: string
|
|
56
|
+
color?: 'neutral' | 'soft' | 'mute'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TableCellPill extends TableCellBase {
|
|
60
|
+
type: 'pill'
|
|
61
|
+
value?: string | ((value: any) => string)
|
|
62
|
+
color?: TableCellPillColor | ((value: any) => TableCellPillColor)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type TableCellPillColor = 'info' | 'success' | 'warning' | 'danger' | 'mute'
|
|
66
|
+
|
|
67
|
+
export interface TableCellAvatar extends TableCellBase {
|
|
68
|
+
type: 'avatar'
|
|
69
|
+
image?(value: any, record: any): string | undefined
|
|
70
|
+
name?(value: any, record: any): string
|
|
71
|
+
link?(value: any, record: any): string
|
|
72
|
+
color?: 'neutral' | 'soft' | 'mute'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface TableCellAvatars extends TableCellBase {
|
|
76
|
+
type: 'avatars'
|
|
77
|
+
avatars(value: any, record: any): TableCellAvatarsOption[]
|
|
78
|
+
color?: 'neutral' | 'soft' | 'mute'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TableCellAvatarsOption {
|
|
82
|
+
image?: string
|
|
83
|
+
name?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface UseTableOptions {
|
|
87
|
+
orders: string[]
|
|
88
|
+
columns: TableColumns
|
|
89
|
+
records?: MaybeRef<Record<string, any>[] | undefined>
|
|
90
|
+
total?: MaybeRef<number | undefined>
|
|
91
|
+
page?: MaybeRef<number | undefined>
|
|
92
|
+
perPage?: MaybeRef<number | undefined>
|
|
93
|
+
reset?: MaybeRef<boolean | undefined>
|
|
94
|
+
borderless?: boolean
|
|
95
|
+
loading?: MaybeRef<boolean | undefined>
|
|
96
|
+
onPrev?(): void
|
|
97
|
+
onNext?(): void
|
|
98
|
+
onReset?(): void
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function useTable(options: UseTableOptions): Table {
|
|
102
|
+
return reactive(options)
|
|
103
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Ref, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type Position = 'top' | 'right' | 'bottom' | 'left'
|
|
4
|
+
|
|
5
|
+
const SCREEN_PADDING = 16
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prevent tooltip going off-screen by adjusting the position depending on
|
|
9
|
+
* the current window size. This only applies to position `top` and
|
|
10
|
+
* `bottom` since we only care about left and right of the screen.
|
|
11
|
+
*/
|
|
12
|
+
export function useTooltip(
|
|
13
|
+
content: Ref<HTMLElement | null>,
|
|
14
|
+
tip: Ref<HTMLElement | null>,
|
|
15
|
+
position: Position
|
|
16
|
+
) {
|
|
17
|
+
const on = ref(false)
|
|
18
|
+
|
|
19
|
+
function show(): void {
|
|
20
|
+
setPosition()
|
|
21
|
+
setTimeout(() => { on.value = true })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hide(): void {
|
|
25
|
+
setTimeout(() => { on.value = false })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function setPosition(): void {
|
|
29
|
+
if (shouldPosition()) {
|
|
30
|
+
doSetPosition()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function doSetPosition(): void {
|
|
35
|
+
// Reset position first so that we can get the original position.
|
|
36
|
+
resetPosition()
|
|
37
|
+
|
|
38
|
+
// Temporally show tip to get its size.
|
|
39
|
+
tip.value!.style.display = 'block'
|
|
40
|
+
|
|
41
|
+
const contentRect = content.value!.getBoundingClientRect()
|
|
42
|
+
const tipRect = tip.value!.getBoundingClientRect()
|
|
43
|
+
|
|
44
|
+
const contentRightX = contentRect.x + contentRect.width
|
|
45
|
+
const tipRightX = tipRect.x + tipRect.width
|
|
46
|
+
|
|
47
|
+
if (tipRect.x < 0) {
|
|
48
|
+
adjustLeftPosition(contentRect.x)
|
|
49
|
+
} else if (tipRightX > window.outerWidth) {
|
|
50
|
+
adjustRightPosition(contentRightX)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
tip.value!.style.display = 'none'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function adjustLeftPosition(contentRectX: number): void {
|
|
57
|
+
tip.value!.style.left = '0'
|
|
58
|
+
tip.value!.style.right = 'auto'
|
|
59
|
+
setTransform(-contentRectX + SCREEN_PADDING)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function adjustRightPosition(contentRightX: number): void {
|
|
63
|
+
tip.value!.style.left = 'auto'
|
|
64
|
+
tip.value!.style.right = '0'
|
|
65
|
+
setTransform((window.outerWidth - contentRightX) - SCREEN_PADDING)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resetPosition(): void {
|
|
69
|
+
tip.value!.style.left = ''
|
|
70
|
+
tip.value!.style.right = ''
|
|
71
|
+
tip.value!.style.transform = ''
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setTransform(x: number): void {
|
|
75
|
+
tip.value!.style.transform = `translate(${x}px, ${position === 'top' ? -100 : 100}%)`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function shouldPosition(): boolean {
|
|
79
|
+
if (!tip.value || !content.value) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return position === 'top' || position === 'bottom'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
on,
|
|
88
|
+
show,
|
|
89
|
+
hide
|
|
90
|
+
}
|
|
91
|
+
}
|