refactor(tree): ts

This commit is contained in:
07akioni 2021-01-15 19:07:25 +08:00
parent 73c9d3f9c1
commit d963e2f2d6
24 changed files with 992 additions and 895 deletions

View File

@ -65,7 +65,7 @@ export default defineComponent({
},
inheritAttrs: false,
props: {
...useTheme.props,
...useTheme.createProps<AlertThemeVars>(),
title: {
type: String,
default: undefined
@ -107,13 +107,7 @@ export default defineComponent({
}
},
setup (props) {
const themeRef = useTheme<AlertThemeVars>(
'Alert',
'Alert',
style,
alertLight,
props
)
const themeRef = useTheme('Alert', 'Alert', style, alertLight, props)
const cssVars = computed(() => {
const {
common: { cubicBezierEaseInOut },

View File

@ -7,7 +7,7 @@ import style from './styles/index.cssr'
export default defineComponent({
name: 'Code',
props: {
...useTheme.props,
...useTheme.createProps<CodeThemeVars>(),
language: {
type: String,
default: undefined
@ -56,13 +56,7 @@ export default defineComponent({
watch(toRef(props, 'language'), setCode)
watch(toRef(props, 'code'), setCode)
watch(hljsRef, setCode)
const themeRef = useTheme<CodeThemeVars>(
'Code',
'Code',
style,
codeLight,
props
)
const themeRef = useTheme('Code', 'Code', style, codeLight, props)
return {
codeRef,
cssVars: computed(() => {

View File

@ -114,7 +114,9 @@ export default defineComponent({
{
class: 'n-menu-item-content__arrow'
},
[h(ChevronDownFilledIcon)]
{
default: () => h(ChevronDownFilledIcon)
}
)
: null
]

View File

@ -15,6 +15,7 @@ import { useMergedState, useCompitable, useIsMounted, useMemo } from 'vooks'
import { call, keep, warn } from '../../_utils'
import { useTheme } from '../../_mixins'
import NPopoverBody, { popoverBodyProps } from './PopoverBody'
import type { PopoverThemeVars } from '../styles'
const bodyPropKeys = Object.keys(
popoverBodyProps
@ -73,7 +74,7 @@ export interface PopoverInjection {
}
export const popoverProps = {
...useTheme.props,
...useTheme.createProps<PopoverThemeVars>(),
show: {
type: Boolean,
default: undefined

View File

@ -24,7 +24,7 @@ import { PopoverThemeVars } from '../styles/light'
import { PopoverInjection } from './Popover'
export const popoverBodyProps = {
...useTheme.props,
...useTheme.createProps<PopoverThemeVars>(),
show: {
type: Boolean,
default: undefined
@ -106,13 +106,7 @@ export default defineComponent({
inheritAttrs: false,
props: popoverBodyProps,
setup (props) {
const themeRef = useTheme<PopoverThemeVars>(
'Popover',
'Popover',
style,
popoverLight,
props
)
const themeRef = useTheme('Popover', 'Popover', style, popoverLight, props)
const followerRef = ref<FollowerRef | null>(null)
const NPopover = inject<PopoverInjection>('NPopover') as PopoverInjection
const followerEnabledRef = ref(props.show)
@ -125,7 +119,9 @@ export default defineComponent({
if (trigger === 'hover') {
directives.push([mousemoveoutside, handleMouseMoveOutside])
}
if (props.displayDirective === 'show') { directives.push([vShow, props.show]) }
if (props.displayDirective === 'show') {
directives.push([vShow, props.show])
}
return directives as DirectiveArguments
})
const styleRef = computed(() => {

View File

@ -1,2 +1,3 @@
export { default as popoverDark } from './dark'
export { default as popoverLight } from './light'
export type { PopoverThemeVars } from './light'

View File

@ -1,6 +1,5 @@
import { commonDark } from '../_styles/new-common'
import { alertDark } from '../alert/styles'
import { affixDark } from '../affix/styles'
import { anchorDark } from '../anchor/styles'
import { autoCompleteDark } from '../auto-complete/styles'
import { avatarDark } from '../avatar/styles'
@ -68,7 +67,6 @@ import { selectDark } from '../select/styles'
export const darkTheme = {
common: commonDark,
Alert: alertDark,
Affix: affixDark,
Anchor: anchorDark,
AutoComplete: autoCompleteDark,
Avatar: avatarDark,

View File

@ -1,6 +1,5 @@
import { commonLight } from '../_styles/new-common'
import { alertLight } from '../alert/styles'
import { affixLight } from '../affix/styles'
import { anchorLight } from '../anchor/styles'
import { autoCompleteLight } from '../auto-complete/styles'
import { avatarLight } from '../avatar/styles'
@ -68,7 +67,6 @@ import { selectLight } from '../select/styles'
export const lightTheme = {
common: commonLight,
Alert: alertLight,
Affix: affixLight,
Anchor: anchorLight,
AutoComplete: autoCompleteLight,
Avatar: avatarLight,

View File

@ -1,465 +0,0 @@
import { h, ref, toRef, computed, defineComponent } from 'vue'
import { createTreeMate } from 'treemate'
import { useMergedState } from 'vooks'
import { useTheme } from '../../_mixins'
import { call, warn } from '../../_utils'
import { treeLight } from '../styles'
import NTreeNode from './TreeNode'
import { keysWithFilter } from './utils'
import style from './styles/index.cssr.js'
export default defineComponent({
name: 'Tree',
provide () {
return { NTree: this }
},
props: {
data: {
type: Array,
required: true
},
defaultExpandAll: {
type: Boolean,
default: false
},
expandOnDragenter: {
type: Boolean,
default: true
},
cancelable: {
type: Boolean,
default: true
},
checkable: {
type: Boolean,
default: false
},
draggable: {
type: Boolean,
default: false
},
blockNode: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
checkedKeys: {
type: Array,
default: undefined
},
defaultCheckedKeys: {
type: Array,
default: () => []
},
expandedKeys: {
type: Array,
default: undefined
},
defaultExpandedKeys: {
type: Array,
default: () => []
},
selectedKeys: {
type: Array,
default: undefined
},
defaultSelectedKeys: {
type: Array,
default: () => []
},
remote: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
pattern: {
type: String,
default: ''
},
filter: {
type: Function,
default: (pattern, node) => {
if (!pattern) return true
return ~node.label.toLowerCase().indexOf(pattern.toLowerCase())
}
},
onLoad: {
type: Function,
default: undefined
},
cascade: {
type: Boolean,
default: false
},
selectable: {
type: Boolean,
default: true
},
onDragEnter: {
type: [Function, Array],
default: undefined
},
onDragLeave: {
type: [Function, Array],
default: undefined
},
onDragEnd: {
type: [Function, Array],
default: undefined
},
onDragStart: {
type: [Function, Array],
default: undefined
},
onDrop: {
type: [Function, Array],
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:expandedKeys': {
type: [Function, Array],
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:checkedKeys': {
type: [Function, Array],
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:selectedKeys': {
type: [Function, Array],
default: undefined
},
// deprecated
onExpandedKeysChange: {
validator () {
warn(
'tree',
'`on-expanded-keys-change` is deprecated, please use `on-update:expanded-keys` instead.'
)
return true
},
default: undefined
},
onCheckedKeysChange: {
validator () {
warn(
'tree',
'`on-checked-keys-change` is deprecated, please use `on-update:expanded-keys` instead.'
)
return true
},
default: undefined
},
onSelectedKeysChange: {
validator () {
warn(
'tree',
'`on-selected-keys-change` is deprecated, please use `on-update:selected-keys` instead.'
)
return true
},
default: undefined
}
},
setup (props) {
const themeRef = useTheme('Tree', 'Tree', style, treeLight, props)
const treeMateRef = computed(() => createTreeMate(props.data))
const uncontrolledCheckedKeysRef = ref(
props.defaultCheckedKeys || props.checkedKeys
)
const controlledCheckedKeysRef = toRef(props, 'checkedKeys')
const mergedCheckedKeysRef = useMergedState(
controlledCheckedKeysRef,
uncontrolledCheckedKeysRef
)
const checkedStatusRef = computed(() => {
return treeMateRef.value.getCheckedKeys(mergedCheckedKeysRef.value, {
cascade: props.cascade
})
})
const displayedCheckedKeysRef = computed(() => {
return checkedStatusRef.value.checkedKeys
})
const displayedIndeterminateKeysRef = computed(() => {
return checkedStatusRef.value.indeterminateKeys
})
const uncontrolledSelectedKeysRef = ref(
props.defaultSelectedKeys || props.selectedKeys
)
const controlledSelectedKeysRef = toRef(props, 'selectedKeys')
const mergedSelectedKeysRef = useMergedState(
controlledSelectedKeysRef,
uncontrolledSelectedKeysRef
)
const uncontrolledExpandedKeysRef = ref(
props.defaultExpandAll
? treeMateRef.value.getNonLeafKeys()
: props.defaultExpandedKeys || props.expandedKeys
)
const controlledExpandedKeysRef = toRef(props, 'selectedKeys')
const mergedExpandedKeysRef = useMergedState(
controlledExpandedKeysRef,
uncontrolledExpandedKeysRef
)
return {
treeMate: treeMateRef,
tmNodes: computed(() => treeMateRef.value.treeNodes),
uncontrolledCheckedKeys: uncontrolledCheckedKeysRef,
displayedCheckedKeys: displayedCheckedKeysRef,
displayedIndeterminateKeys: displayedIndeterminateKeysRef,
uncontrolledSelectedKeys: uncontrolledSelectedKeysRef,
mergedSelectedKeys: mergedSelectedKeysRef,
uncontrolledExpandedKeys: uncontrolledExpandedKeysRef,
mergedExpandedKeys: mergedExpandedKeysRef,
highlightKeys: ref([]),
loadingKeys: ref([]),
mergedTheme: themeRef,
cssVars: computed(() => {
const {
common: { cubicBezierEaseInOut },
self: {
fontSize,
nodeBorderRadius,
nodeColorHover,
nodeColorPressed,
nodeColorActive,
arrowColor,
loadingColor,
nodeTextColor,
nodeTextColorDisabled
}
} = themeRef.value
return {
'--arrow-color': arrowColor,
'--loading-color': loadingColor,
'--bezier': cubicBezierEaseInOut,
'--font-size': fontSize,
'--node-border-radius': nodeBorderRadius,
'--node-color-active': nodeColorActive,
'--node-color-hover': nodeColorHover,
'--node-color-pressed': nodeColorPressed,
'--node-text-color': nodeTextColor,
'--node-text-color-disabled': nodeTextColorDisabled
}
})
}
},
data () {
return {
draggingNodeKey: null,
draggingNode: null,
droppingNodeKey: null,
expandTimerId: null
}
},
watch: {
data () {
this.loadingKeys = []
this.expandTimerId = null
},
pattern (value) {
if (value) {
const [expandedKeysAfterChange, highlightKeys] = keysWithFilter(
this.data,
this.pattern,
this.filter
)
this.highlightKeys = highlightKeys
this.doExpandedKeysChange(expandedKeysAfterChange)
} else {
this.highlightKeys = []
}
}
},
methods: {
doExpandedKeysChange (value) {
const {
'onUpdate:expandedKeys': onUpdateExpandedKeys,
onExpandedKeysChange
} = this
this.uncontrolledExpandedKeys = value
if (onUpdateExpandedKeys) call(onUpdateExpandedKeys, value)
if (onExpandedKeysChange) call(onExpandedKeysChange, value)
},
doCheckedKeysChange (value) {
const {
'onUpdate:checkedKeys': onUpdateCheckedKeys,
onCheckedKeysChange
} = this
this.uncontrolledCheckedKeys = value
if (onUpdateCheckedKeys) call(onUpdateCheckedKeys, value)
if (onCheckedKeysChange) call(onCheckedKeysChange, value)
},
doSelectedKeysChange (value) {
const {
'onUpdate:selectedKeys': onUpdateSelectedKeys,
onSelectedKeysChange
} = this
this.uncontrolledSelectedKeys = value
if (onUpdateSelectedKeys) call(onUpdateSelectedKeys, value)
if (onSelectedKeysChange) call(onSelectedKeysChange, value)
},
doDragEnter (...args) {
const { onDragEnter } = this
if (onDragEnter) call(onDragEnter, ...args)
},
doDragLeave (...args) {
const { onDragLeave } = this
if (onDragLeave) call(onDragLeave, ...args)
},
doDragEnd (...args) {
const { onDragEnd } = this
if (onDragEnd) call(onDragEnd, ...args)
},
doDragStart (...args) {
const { onDragStart } = this
if (onDragStart) call(onDragStart, ...args)
},
doDrop (...args) {
const { onDrop } = this
if (onDrop) call(onDrop, ...args)
},
resetDragStatus () {
this.draggingNodeKey = null
this.draggingNode = null
this.droppingNodeKey = null
},
handleCheck (node, checked) {
if (this.disabled || node.disabled) return
const { checkedKeys } = this.treeMate[checked ? 'check' : 'uncheck'](
node.key,
this.displayedCheckedKeys,
{
cascade: this.cascade
}
)
this.doCheckedKeysChange(checkedKeys)
},
toggleExpand (node) {
if (this.disabled) return
const { mergedExpandedKeys } = this
const index = mergedExpandedKeys.findIndex(
(expandNodeId) => expandNodeId === node.key
)
if (~index) {
const expandedKeysAfterChange = Array.from(mergedExpandedKeys)
expandedKeysAfterChange.splice(index, 1)
this.doExpandedKeysChange(expandedKeysAfterChange)
} else {
this.doExpandedKeysChange(mergedExpandedKeys.concat(node.key))
}
},
handleSwitcherClick (node) {
if (this.disabled || node.disabled) return
this.toggleExpand(node)
},
handleSelect (node) {
if (this.disabled || node.disabled || !this.selectable) return
if (this.multiple) {
const selectedKeys = this.mergedSelectedKeys
const index = selectedKeys.findIndex((key) => key === node.key)
if (~index) {
if (this.cancelable) {
selectedKeys.splice(index, 1)
}
} else if (!~index) {
selectedKeys.push(node.key)
}
this.doSelectedKeysChange(selectedKeys)
} else {
const selectedKeys = this.mergedSelectedKeys
if (selectedKeys.includes(node.key)) {
if (this.cancelable) {
this.doSelectedKeysChange([])
}
} else {
this.doSelectedKeysChange([node.key])
}
}
},
handleDragEnter ({ event, node }) {
// node should be a tmNode
if (!this.draggable || this.disabled || node.disabled) return
this.doDragEnter({ event, node })
if (!this.expandOnDragenter) return
this.droppingNodeKey = node.key
if (node.key === this.draggingNodeKey) return
if (!this.mergedExpandedKeys.includes(node.key) && !node.isLeaf) {
window.clearTimeout(this.expandTimerId)
const expand = () => {
if (
this.droppingNodeKey === node.key &&
!this.mergedExpandedKeys.includes(node.key)
) {
this.doExpandedKeysChange(this.mergedExpandedKeys.concat(node.key))
}
}
if (!node.isShallowLoaded) {
if (!this.loadingKeys.includes(node.key)) {
this.loadingKeys.push(node.key)
}
this.onLoad(node).then(() => {
this.loadingKeys.splice(
this.loadingKeys.find((key) => key === node.key),
1
)
expand()
})
return
}
this.expandTimerId = window.setTimeout(() => {
expand()
this.expandTimerId = null
}, 800)
}
},
handleDragLeave ({ event, node }) {
if (!this.draggable || this.disabled || node.disabled) return
this.droppingNodeKey = null
this.doDragLeave({ event, node })
},
handleDragEnd ({ event, node }) {
if (!this.draggable || this.disabled || node.disabled) return
this.doDragEnd({ event, node })
this.resetDragStatus()
},
handleDragStart ({ event, node }) {
if (!this.draggable || this.disabled || node.disabled) return
this.draggingNodeKey = node.key
this.draggingNode = node
this.doDragStart({ event, node })
},
handleDrop ({ event, node, dropPosition }) {
if (!this.draggable || this.disabled || node.disabled) return
this.doDrop('drop', {
event,
node,
dragNode: this.draggingNode,
dropPosition
})
this.resetDragStatus()
}
},
render () {
return h(
'div',
{
class: 'n-tree',
style: this.cssVars
},
this.tmNodes.map((tmNode) =>
h(NTreeNode, {
tmNode,
key: tmNode.key
})
)
)
}
})

547
src/tree/src/Tree.ts Normal file
View File

@ -0,0 +1,547 @@
import {
h,
ref,
toRef,
computed,
defineComponent,
provide,
PropType,
watch,
reactive
} from 'vue'
import { createTreeMate, Key, KeyedRawNode, TreeNode } from 'treemate'
import { useMergedState } from 'vooks'
import { useTheme } from '../../_mixins'
import type { MergedTheme } from '../../_mixins/use-theme'
import { call, warn } from '../../_utils'
import { treeLight } from '../styles'
import type { TreeThemeVars } from '../styles'
import NTreeNode from './TreeNode'
import { keysWithFilter } from './utils'
import style from './styles/index.cssr'
export interface DragInfo {
event: DragEvent
node: KeyedRawNode
}
export interface DropInfo {
event: DragEvent
node: KeyedRawNode
dropPosition: 'top' | 'center' | 'bottom'
}
export interface TreeInjection {
loadingKeys: Key[]
highlightKeys: Key[]
displayedCheckedKeys: Key[]
displayedIndeterminateKeys: Key[]
mergedSelectedKeys: Key[]
mergedExpandedKeys: Key[]
remote: boolean
draggable: boolean
checkable: boolean
blockNode: boolean
onLoad: (node: KeyedRawNode) => Promise<void>
handleSwitcherClick: (node: TreeNode) => void
handleSelect: (node: TreeNode) => void
handleCheck: (node: TreeNode, checked: boolean) => void
handleDragStart: (info: DragInfo) => void
handleDragEnter: (info: DragInfo) => void
handleDragLeave: (info: DragInfo) => void
handleDragEnd: (info: DragInfo) => void
handleDrop: (info: DropInfo) => void
mergedTheme: MergedTheme<TreeThemeVars>
}
export default defineComponent({
name: 'Tree',
props: {
...useTheme.createProps<TreeThemeVars>(),
data: {
type: Array as PropType<KeyedRawNode[]>,
required: true
},
defaultExpandAll: {
type: Boolean,
default: false
},
expandOnDragenter: {
type: Boolean,
default: true
},
cancelable: {
type: Boolean,
default: true
},
checkable: {
type: Boolean,
default: false
},
draggable: {
type: Boolean,
default: false
},
blockNode: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
checkedKeys: {
type: Array as PropType<Key[]>,
default: undefined
},
defaultCheckedKeys: {
type: Array as PropType<Key[]>,
default: () => []
},
expandedKeys: {
type: Array as PropType<Key[]>,
default: undefined
},
defaultExpandedKeys: {
type: Array as PropType<Key[]>,
default: () => []
},
selectedKeys: {
type: Array as PropType<Key[]>,
default: undefined
},
defaultSelectedKeys: {
type: Array as PropType<Key[]>,
default: () => []
},
remote: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
pattern: {
type: String,
default: ''
},
filter: {
type: Function as PropType<
(pattern: string, node: KeyedRawNode) => boolean
>,
default: (pattern: string, node: KeyedRawNode) => {
if (!pattern) return true
return ~node.label.toLowerCase().indexOf(pattern.toLowerCase())
}
},
onLoad: {
type: Function as PropType<(node: KeyedRawNode) => Promise<void>>,
default: undefined
},
cascade: {
type: Boolean,
default: false
},
selectable: {
type: Boolean,
default: true
},
onDragEnter: {
type: [Function, Array] as PropType<
(e: DragEvent) => void | ((e: DragEvent) => void)[]
>,
default: undefined
},
onDragLeave: {
type: [Function, Array] as PropType<
(e: DragEvent) => void | ((e: DragEvent) => void)[]
>,
default: undefined
},
onDragEnd: {
type: [Function, Array] as PropType<
(e: DragEvent) => void | ((e: DragEvent) => void)[]
>,
default: undefined
},
onDragStart: {
type: [Function, Array] as PropType<
(e: DragEvent) => void | ((e: DragEvent) => void)[]
>,
default: undefined
},
onDrop: {
type: [Function, Array] as PropType<
(e: DragEvent) => void | ((e: DragEvent) => void)[]
>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:expandedKeys': {
type: [Function, Array] as PropType<
(value: Key[]) => void | ((value: Key[]) => void)[]
>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:checkedKeys': {
type: [Function, Array] as PropType<
(value: Key[]) => void | ((value: Key[]) => void)[]
>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:selectedKeys': {
type: [Function, Array] as PropType<
(value: Key[]) => void | ((value: Key[]) => void)[]
>,
default: undefined
},
// deprecated
onExpandedKeysChange: {
type: [Function, Array] as PropType<
(value: Key[]) => void | ((value: Key[]) => void)[]
>,
validator: () => {
warn(
'tree',
'`on-expanded-keys-change` is deprecated, please use `on-update:expanded-keys` instead.'
)
return true
},
default: undefined
},
onCheckedKeysChange: {
type: [Function, Array] as PropType<
(value: Key[]) => void | ((value: Key[]) => void)[]
>,
validator: () => {
warn(
'tree',
'`on-checked-keys-change` is deprecated, please use `on-update:expanded-keys` instead.'
)
return true
},
default: undefined
},
onSelectedKeysChange: {
type: [Function, Array] as PropType<
(value: Key[]) => void | ((value: Key[]) => void)[]
>,
validator: () => {
warn(
'tree',
'`on-selected-keys-change` is deprecated, please use `on-update:selected-keys` instead.'
)
return true
},
default: undefined
}
},
setup (props) {
const themeRef = useTheme('Tree', 'Tree', style, treeLight, props)
const treeMateRef = computed(() => createTreeMate(props.data))
const uncontrolledCheckedKeysRef = ref(
props.defaultCheckedKeys || props.checkedKeys
)
const controlledCheckedKeysRef = toRef(props, 'checkedKeys')
const mergedCheckedKeysRef = useMergedState(
controlledCheckedKeysRef,
uncontrolledCheckedKeysRef
)
const checkedStatusRef = computed(() => {
return treeMateRef.value.getCheckedKeys(mergedCheckedKeysRef.value, {
cascade: props.cascade
})
})
const displayedCheckedKeysRef = computed(() => {
return checkedStatusRef.value.checkedKeys
})
const displayedIndeterminateKeysRef = computed(() => {
return checkedStatusRef.value.indeterminateKeys
})
const uncontrolledSelectedKeysRef = ref(
props.defaultSelectedKeys || props.selectedKeys
)
const controlledSelectedKeysRef = toRef(props, 'selectedKeys')
const mergedSelectedKeysRef = useMergedState(
controlledSelectedKeysRef,
uncontrolledSelectedKeysRef
)
const uncontrolledExpandedKeysRef = ref(
props.defaultExpandAll
? treeMateRef.value.getNonLeafKeys()
: props.defaultExpandedKeys || props.expandedKeys
)
const controlledExpandedKeysRef = toRef(props, 'selectedKeys')
const mergedExpandedKeysRef = useMergedState(
controlledExpandedKeysRef,
uncontrolledExpandedKeysRef
)
const draggingNodeKeyRef = ref<Key | null>(null)
const draggingNodeRef = ref<KeyedRawNode | null>(null)
const droppingNodeKeyRef = ref<Key | null>(null)
const expandTimerIdRef = ref<number | undefined>(undefined)
const highlightKeysRef = ref<Key[]>([])
const loadingKeysRef = ref<Key[]>([])
watch(toRef(props, 'data'), () => {
loadingKeysRef.value = []
expandTimerIdRef.value = undefined
})
watch(toRef(props, 'pattern'), (value) => {
if (value) {
const [expandedKeysAfterChange, highlightKeys] = keysWithFilter(
props.data,
props.pattern,
props.filter
)
highlightKeysRef.value = highlightKeys
doExpandedKeysChange(expandedKeysAfterChange)
} else {
highlightKeysRef.value = []
}
})
function doExpandedKeysChange (value: Key[]) {
const {
'onUpdate:expandedKeys': onUpdateExpandedKeys,
onExpandedKeysChange
} = props
uncontrolledExpandedKeysRef.value = value
if (onUpdateExpandedKeys) call(onUpdateExpandedKeys, value)
if (onExpandedKeysChange) call(onExpandedKeysChange, value)
}
function doCheckedKeysChange (value: Key[]) {
const {
'onUpdate:checkedKeys': onUpdateCheckedKeys,
onCheckedKeysChange
} = props
uncontrolledCheckedKeysRef.value = value
if (onUpdateCheckedKeys) call(onUpdateCheckedKeys, value)
if (onCheckedKeysChange) call(onCheckedKeysChange, value)
}
function doSelectedKeysChange (value: Key[]) {
const {
'onUpdate:selectedKeys': onUpdateSelectedKeys,
onSelectedKeysChange
} = props
uncontrolledSelectedKeysRef.value = value
if (onUpdateSelectedKeys) call(onUpdateSelectedKeys, value)
if (onSelectedKeysChange) call(onSelectedKeysChange, value)
}
// Drag & Drop
function doDragEnter (info: DragInfo) {
const { onDragEnter } = props
if (onDragEnter) call(onDragEnter, info)
}
function doDragLeave (info: DragInfo) {
const { onDragLeave } = props
if (onDragLeave) call(onDragLeave, info)
}
function doDragEnd (info: DragInfo) {
const { onDragEnd } = props
if (onDragEnd) call(onDragEnd, info)
}
function doDragStart (info: DragInfo) {
const { onDragStart } = props
if (onDragStart) call(onDragStart, info)
}
function doDrop (info: DropInfo & { dragNode: TreeNode }) {
const { onDrop } = props
if (onDrop) call(onDrop, info)
}
function resetDragStatus () {
draggingNodeKeyRef.value = null
draggingNodeRef.value = null
droppingNodeKeyRef.value = null
}
function handleCheck (node: TreeNode, checked: boolean) {
if (props.disabled || node.disabled) return
const { checkedKeys } = treeMateRef.value[checked ? 'check' : 'uncheck'](
node.key,
displayedCheckedKeysRef.value,
{
cascade: props.cascade
}
)
doCheckedKeysChange(checkedKeys)
}
function toggleExpand (node: TreeNode) {
if (props.disabled) return
const { value: mergedExpandedKeys } = mergedExpandedKeysRef
const index = mergedExpandedKeys.findIndex(
(expandNodeId) => expandNodeId === node.key
)
if (~index) {
const expandedKeysAfterChange = Array.from(mergedExpandedKeys)
expandedKeysAfterChange.splice(index, 1)
doExpandedKeysChange(expandedKeysAfterChange)
} else {
doExpandedKeysChange(mergedExpandedKeys.concat(node.key))
}
}
function handleSwitcherClick (node: TreeNode) {
if (props.disabled || node.disabled) return
toggleExpand(node)
}
function handleSelect (node: TreeNode) {
if (props.disabled || node.disabled || !props.selectable) return
if (props.multiple) {
const selectedKeys = mergedSelectedKeysRef.value
const index = selectedKeys.findIndex((key) => key === node.key)
if (~index) {
if (props.cancelable) {
selectedKeys.splice(index, 1)
}
} else if (!~index) {
selectedKeys.push(node.key)
}
doSelectedKeysChange(selectedKeys)
} else {
const selectedKeys = mergedSelectedKeysRef.value
if (selectedKeys.includes(node.key)) {
if (props.cancelable) {
doSelectedKeysChange([])
}
} else {
doSelectedKeysChange([node.key])
}
}
}
function handleDragEnter ({ event, node }: DragInfo) {
// node should be a tmNode
if (!props.draggable || props.disabled || node.disabled) return
doDragEnter({ event, node })
if (!props.expandOnDragenter) return
droppingNodeKeyRef.value = node.key
if (node.key === draggingNodeKeyRef.value) return
if (!mergedExpandedKeysRef.value.includes(node.key) && !node.isLeaf) {
window.clearTimeout(expandTimerIdRef.value)
const expand = () => {
if (
droppingNodeKeyRef.value === node.key &&
!mergedExpandedKeysRef.value.includes(node.key)
) {
doExpandedKeysChange(mergedExpandedKeysRef.value.concat(node.key))
}
}
if (!node.isShallowLoaded) {
if (!loadingKeysRef.value.includes(node.key)) {
loadingKeysRef.value.push(node.key)
}
props.onLoad(node).then(() => {
loadingKeysRef.value.splice(
loadingKeysRef.value.findIndex((key) => key === node.key),
1
)
expand()
})
return
}
expandTimerIdRef.value = window.setTimeout(() => {
expand()
expandTimerIdRef.value = undefined
}, 800)
}
}
function handleDragLeave ({ event, node }: DragInfo) {
if (!props.draggable || props.disabled || node.disabled) return
droppingNodeKeyRef.value = null
doDragLeave({ event, node })
}
function handleDragEnd ({ event, node }: DragInfo) {
if (!props.draggable || props.disabled || node.disabled) return
doDragEnd({ event, node })
resetDragStatus()
}
function handleDragStart ({ event, node }: DragInfo) {
if (!props.draggable || props.disabled || node.disabled) return
draggingNodeKeyRef.value = node.key
draggingNodeRef.value = node
doDragStart({ event, node })
}
function handleDrop ({ event, node, dropPosition }: DropInfo) {
if (!props.draggable || props.disabled || node.disabled) return
doDrop({
event,
node,
dragNode: draggingNodeRef.value as TreeNode,
dropPosition
})
resetDragStatus()
}
provide<TreeInjection>(
'NTree',
reactive({
loadingKeys: loadingKeysRef,
highlightKeys: highlightKeysRef,
displayedCheckedKeys: displayedCheckedKeysRef,
displayedIndeterminateKeys: displayedIndeterminateKeysRef,
mergedSelectedKeys: mergedSelectedKeysRef,
mergedExpandedKeys: mergedExpandedKeysRef,
remote: toRef(props, 'remote'),
onLoad: toRef(props, 'onLoad'),
draggable: toRef(props, 'draggable'),
checkable: toRef(props, 'checkable'),
blockNode: toRef(props, 'blockNode'),
handleSwitcherClick,
handleDragEnd,
handleDragEnter,
handleDragLeave,
handleDragStart,
handleDrop,
handleSelect,
handleCheck,
mergedTheme: themeRef
})
)
return {
tmNodes: computed(() => treeMateRef.value.treeNodes),
cssVars: computed(() => {
const {
common: { cubicBezierEaseInOut },
self: {
fontSize,
nodeBorderRadius,
nodeColorHover,
nodeColorPressed,
nodeColorActive,
arrowColor,
loadingColor,
nodeTextColor,
nodeTextColorDisabled
}
} = themeRef.value
return {
'--arrow-color': arrowColor,
'--loading-color': loadingColor,
'--bezier': cubicBezierEaseInOut,
'--font-size': fontSize,
'--node-border-radius': nodeBorderRadius,
'--node-color-active': nodeColorActive,
'--node-color-hover': nodeColorHover,
'--node-color-pressed': nodeColorPressed,
'--node-text-color': nodeTextColor,
'--node-text-color-disabled': nodeTextColorDisabled
}
})
}
},
render () {
return h(
'div',
{
class: 'n-tree',
style: this.cssVars
},
this.tmNodes.map((tmNode) =>
h(NTreeNode, {
tmNode,
key: tmNode.key
})
)
)
}
})

View File

@ -1,26 +1,82 @@
import { h, inject, computed, defineComponent } from 'vue'
import { h, inject, computed, defineComponent, PropType } from 'vue'
import { useMemo } from 'vooks'
import { TreeNode, KeyedRawNode } from 'treemate'
import NTreeNodeSwitcher from './TreeNodeSwitcher.vue'
import NTreeNodeCheckbox from './TreeNodeCheckbox.vue'
import NTreeNodeContent from './TreeNodeContent.vue'
import { NFadeInExpandTransition } from '../../_base'
import type { TreeInjection } from './Tree'
const TreeNode = defineComponent({
name: 'NTreeNode',
inject: {
NTree: {
default: null
}
},
props: {
tmNode: {
type: Object,
type: Object as PropType<TreeNode>,
required: true
}
},
setup (props) {
const NTree = inject('NTree')
const NTree = inject<TreeInjection>('NTree') as TreeInjection
function handleSwitcherClick () {
const { tmNode } = props
if (NTree.remote && !tmNode.isLeaf && !tmNode.isShallowLoaded) {
if (!NTree.loadingKeys.includes(tmNode.key)) {
NTree.loadingKeys.push(tmNode.key)
}
NTree.onLoad &&
NTree.onLoad(tmNode.rawNode as KeyedRawNode).then(() => {
NTree.loadingKeys.splice(
NTree.loadingKeys.findIndex((key) => key === tmNode.key),
1
)
NTree.handleSwitcherClick(tmNode)
})
} else {
NTree.handleSwitcherClick(tmNode)
}
}
function handleContentClick () {
NTree.handleSelect(props.tmNode)
}
function handleDragEnter (e: DragEvent) {
NTree.handleDragEnter({
event: e,
node: props.tmNode.rawNode as KeyedRawNode
})
}
function handleDragStart (e: DragEvent) {
NTree.handleDragStart({
event: e,
node: props.tmNode.rawNode as KeyedRawNode
})
}
function handleDragLeave (e: DragEvent) {
NTree.handleDragLeave({
event: e,
node: props.tmNode.rawNode as KeyedRawNode
})
}
function handleDragEnd (e: DragEvent) {
NTree.handleDragEnd({
event: e,
node: props.tmNode.rawNode as KeyedRawNode
})
}
function handleDrop (
e: DragEvent,
dropPosition: 'top' | 'center' | 'bottom'
) {
NTree.handleDrop({
event: e,
node: props.tmNode.rawNode as KeyedRawNode,
dropPosition
})
}
function handleCheck (checked: boolean) {
NTree.handleCheck(props.tmNode, checked)
}
return {
NTree,
loading: useMemo(() => NTree.loadingKeys.includes(props.tmNode.key)),
highlight: useMemo(() => NTree.highlightKeys.includes(props.tmNode.key)),
checked: useMemo(() =>
@ -35,109 +91,15 @@ const TreeNode = defineComponent({
expanded: useMemo(() =>
NTree.mergedExpandedKeys.includes(props.tmNode.key)
),
icon: computed(() => props.tmNode.rawNode.icon)
}
},
methods: {
doDragStart (...args) {
const {
NTree: { handleDragStart }
} = this
if (handleDragStart) handleDragStart(...args)
},
doDragEnter (...args) {
const {
NTree: { handleDragEnter }
} = this
if (handleDragEnter) handleDragEnter(...args)
},
doDragEnd (...args) {
const {
NTree: { handleDragEnd }
} = this
if (handleDragEnd) handleDragEnd(...args)
},
doDragLeave (...args) {
const {
NTree: { handleDragLeave }
} = this
if (handleDragLeave) handleDragLeave(...args)
},
doDragOver (...args) {
const {
NTree: { handleDragOver }
} = this
if (handleDragOver) handleDragOver(...args)
},
doDrop (...args) {
const {
NTree: { handleDrop }
} = this
if (handleDrop) handleDrop(...args)
},
doSwitcherClick (...args) {
const {
NTree: { handleSwitcherClick }
} = this
if (handleSwitcherClick) handleSwitcherClick(...args)
},
doCheck (...args) {
const {
NTree: { handleCheck }
} = this
if (handleCheck) handleCheck(...args)
},
doSelect (...args) {
const {
NTree: { handleSelect }
} = this
if (handleSelect) handleSelect(...args)
},
handleSwitcherClick () {
const { NTree, tmNode } = this
if (NTree.remote && !tmNode.isLeaf && !tmNode.isShallowLoaded) {
if (!NTree.loadingKeys.includes(tmNode.key)) {
NTree.loadingKeys.push(tmNode.key)
}
NTree.onLoad &&
NTree.onLoad(tmNode.rawNode).then(() => {
NTree.loadingKeys.splice(
NTree.loadingKeys.find((key) => key === tmNode.key),
1
)
this.doSwitcherClick(tmNode.rawNode)
})
} else {
this.doSwitcherClick(tmNode.rawNode)
}
},
handleContentClick () {
this.doSelect(this.tmNode.rawNode)
},
handleDragOver (e) {
this.doDragOver({ event: e, node: this.tmNode.rawNode })
},
handleDragEnter (e) {
this.doDragEnter({ event: e, node: this.tmNode.rawNode })
},
handleDragStart (e) {
this.doDragStart({ event: e, node: this.tmNode.rawNode })
},
handleDragLeave (e) {
this.doDragLeave({ event: e, node: this.tmNode.rawNode })
},
handleDragEnd (e) {
this.doDragEnd({ event: e, node: this.tmNode.rawNode })
},
handleDrop (e, dropPosition) {
this.doDrop({
event: e,
node: this.tmNode.rawNode,
dropPosition
})
},
handleCheck (checked) {
this.doCheck(this.tmNode.rawNode, checked)
icon: computed(() => props.tmNode.rawNode.icon),
handleCheck,
handleDrop,
handleDragStart,
handleDragEnter,
handleDragEnd,
handleDragLeave,
handleContentClick,
handleSwitcherClick
}
},
render () {
@ -170,10 +132,10 @@ const TreeNode = defineComponent({
highlight: this.highlight,
draggable: this.NTree.draggable,
onClick: this.handleContentClick,
onDragover: this.handleDragOver,
onDragenter: this.handleDragEnter,
onDragstart: this.handleDragStart,
onDragleave: this.handleDragLeave,
onDragEnd: this.handleDragEnd,
onDrop: this.handleDrop
},
{

View File

@ -10,20 +10,16 @@
</span>
</template>
<script>
import { defineComponent } from 'vue'
<script lang="ts">
import { defineComponent, inject } from 'vue'
import { NCheckbox } from '../../checkbox'
import type { TreeInjection } from './Tree'
export default defineComponent({
name: 'NTreeNodeCheckbox',
components: {
NCheckbox
},
inject: {
NTree: {
default: null
}
},
props: {
checked: {
type: Boolean,
@ -38,18 +34,23 @@ export default defineComponent({
default: undefined
}
},
methods: {
doCheck (value) {
const { onCheck } = this
setup (props) {
const NTree = inject<TreeInjection>('NTree')
function doCheck (value: boolean) {
const { onCheck } = props
if (onCheck) return onCheck(value)
},
handleUpdateValue (value) {
if (this.indeterminate) {
this.doCheck(false)
}
function handleUpdateValue (value: boolean) {
if (props.indeterminate) {
doCheck(false)
} else {
this.doCheck(value)
doCheck(value)
}
}
return {
handleUpdateValue,
NTree
}
}
})
</script>

View File

@ -1,5 +1,6 @@
<template>
<span
ref="selfRef"
class="n-tree-node-content"
:class="{
'n-tree-node-content--pending': pending,
@ -26,8 +27,8 @@
</span>
</template>
<script>
import { defineComponent } from 'vue'
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'NTreeNodeContent',
@ -81,96 +82,110 @@ export default defineComponent({
default: undefined
}
},
data () {
return {
pending: false,
pendingPosition: null
}
},
methods: {
doClick () {
const { onClick } = this
setup (props) {
const pendingRef = ref(false)
const pendingPositionRef = ref<'top' | 'center' | 'bottom' | null>(null)
const selfRef = ref<HTMLElement | null>(null)
function doClick () {
const { onClick } = props
if (onClick) onClick()
},
doDragStart (e) {
const { onDragStart } = this
}
function doDragStart (e: DragEvent) {
const { onDragStart } = props
if (onDragStart) onDragStart(e)
},
doDragEnter (e) {
const { onDragEnter } = this
}
function doDragEnter (e: DragEvent) {
const { onDragEnter } = props
if (onDragEnter) onDragEnter(e)
},
doDragEnd (e) {
const { onDragEnd } = this
}
function doDragEnd (e: DragEvent) {
const { onDragEnd } = props
if (onDragEnd) onDragEnd(e)
},
doDragLeave (e) {
const { onDragLeave } = this
}
function doDragLeave (e: DragEvent) {
const { onDragLeave } = props
if (onDragLeave) onDragLeave(e)
},
doDragOver (e) {
const { onDragOver } = this
if (onDragOver) onDragOver(e)
},
doDrop (e, dropPosition) {
const { onDrop } = this
}
// function doDragOver (e: DragEvent) {
// const { onDragOver } = props
// if (onDragOver) onDragOver(e)
// }
function doDrop (e: DragEvent, dropPosition: 'top' | 'bottom' | 'center') {
const { onDrop } = props
if (onDrop) onDrop(e, dropPosition)
},
handleClick () {
this.doClick()
},
handleContentDragStart (e) {
this.doDragStart(e)
},
handleContentDragEnter (e) {
}
function handleClick () {
doClick()
}
function handleContentDragStart (e: DragEvent) {
doDragStart(e)
}
function handleContentDragEnter (e: DragEvent) {
if (
e.currentTarget &&
e.relatedTarget &&
e.currentTarget.contains(e.relatedTarget)
(e.currentTarget as HTMLElement).contains(
e.relatedTarget as HTMLElement
)
) {
return
}
this.doDragEnter(e)
},
handleDragOverContent (e) {
doDragEnter(e)
}
function handleDragOverContent (e: DragEvent) {
e.preventDefault()
const el = this.$el
this.pending = true
const el = selfRef.value as HTMLElement
pendingRef.value = true
const elOffsetHeight = el.offsetHeight
const elClientTop = el.getBoundingClientRect().top
const eventOffsetY = e.clientY - elClientTop
if (eventOffsetY <= 8) {
this.pendingPosition = 'top'
pendingPositionRef.value = 'top'
} else if (eventOffsetY >= elOffsetHeight - 8) {
this.pendingPosition = 'bottom'
pendingPositionRef.value = 'bottom'
} else {
this.pendingPosition = 'body'
pendingPositionRef.value = 'center'
}
this.doDragOver(e)
},
handleContentDragEnd (e) {
this.doDragEnd(e)
},
handleContentDragLeave (e) {
}
function handleContentDragEnd (e: DragEvent) {
doDragEnd(e)
}
function handleContentDragLeave (e: DragEvent) {
if (
e.currentTarget &&
e.relatedTarget &&
e.currentTarget.contains(e.relatedTarget)
(e.currentTarget as HTMLElement).contains(
e.relatedTarget as HTMLElement
)
) {
return
}
this.pending = false
this.doDragLeave(e)
},
handleContentDrop (e) {
pendingRef.value = false
doDragLeave(e)
}
function handleContentDrop (e: DragEvent) {
e.preventDefault()
this.pending = false
const dropPosition = {
top: 'top',
bottom: 'bottom',
body: 'center'
}[this.pendingPosition]
this.doDrop(e, dropPosition)
pendingRef.value = false
if (pendingPositionRef.value !== null) {
const dropPosition = {
top: 'top',
bottom: 'bottom',
center: 'center'
}[pendingPositionRef.value]
doDrop(e, dropPosition as 'top' | 'bottom' | 'center')
}
}
return {
selfRef,
pending: pendingRef,
pendingPosition: pendingPositionRef,
handleContentDragLeave,
handleContentDragStart,
handleDragOverContent,
handleContentDragEnd,
handleContentDragEnter,
handleContentDrop,
handleClick
}
}
})

View File

@ -18,7 +18,7 @@
</span>
</template>
<script>
<script lang="ts">
import { defineComponent } from 'vue'
import { SwitcherIcon } from '../../_base/icons'
import { NIconSwitchTransition, NBaseLoading, NBaseIcon } from '../../_base'
@ -54,13 +54,15 @@ export default defineComponent({
default: undefined
}
},
methods: {
doClick () {
const { onClick } = this
setup (props) {
function doClick () {
const { onClick } = props
if (onClick) onClick()
},
handleClick () {
this.doClick()
}
return {
handleClick () {
doClick()
}
}
}
})

View File

@ -1,161 +0,0 @@
import { c, cB, cE, cM } from '../../../_utils/cssr'
import fadeInHeightExpandTransition from '../../../_styles/transitions/fade-in-height-expand'
import iconSwitchTransition from '../../../_styles/transitions/icon-switch'
// vars:
// --arrow-color
// --bezier
// --font-size
// --node-border-radius
// --node-color-active
// --node-color-hover
// --node-color-pressed
// --node-text-color
// --node-text-color-disabled
export default cB('tree', {
fontSize: 'var(--font-size)'
}, [
c('ul, li', `
margin: 0;
padding: 0;
list-style: none;
`),
c('>', [
cB('tree-node', [
c('&:first-child', {
paddingTop: 0
})
])
]),
cB('tree-children-wrapper', {
marginLeft: '16px'
}, [
fadeInHeightExpandTransition({ duration: '0.15s' })
]),
cB('tree-node', {
padding: '6px 0 0 0'
}),
cB('tree-node-switcher', `
cursor: pointer;
display: inline-flex;
height: 24px;
width: 24px;
align-items: center;
justify-content: center;
transition: transform .15s var(--bezier);
vertical-align: bottom;
`, [
cE('icon', `
position: relative;
height: 14px;
width: 14px;
display: flex;
color: var(--arrow-color);
transition: color .3s var(--bezier);
font-size: 14px;
`, [
cB('icon', [
iconSwitchTransition()
]),
cB('base-loading', `
color: var(--loading-color);
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
`, [
iconSwitchTransition()
])
]),
cM('hide', {
visibility: 'hidden'
}),
cM('expanded', {
transform: 'rotate(90deg)'
})
]),
cB('tree-node-checkbox', `
display: inline-flex;
height: 24px;
width: 16px;
vertical-align: bottom;
align-items: center;
justify-content: center;
margin-right: 4px;
`),
cB('tree-node-content', `
position: relative;
display: inline-flex;
height: 24px;
box-sizing: border-box;
border-bottom: 3px solid transparent;
border-top: 3px solid transparent;
line-height: 24px;
align-items: center;
vertical-align: bottom;
padding: 0 6px;
cursor: pointer;
border-radius: var(--node-border-radius);
text-decoration-color: transparent;
text-decoration-line: underline;
color: var(--node-text-color);
transition:
color .3s var(--bezier),
text-decoration-color .3s var(--bezier),
background-color .3s var(--bezier),
border-color .3s var(--bezier);
`, [
c('&:last-child', {
marginBottom: 0
}),
cE('padding-box', `
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: transparent;
`),
cE('text', `
line-height: 1.25;
border-bottom: 1px solid transparent;
transition: border-color .3s var(--bezier);
`),
cM('block', {
width: 'calc(100% - 24px)'
}, [
cM('checkable', {
width: 'calc(100% - 48px)'
})
]),
c('&:hover', {
backgroundColor: 'var(--node-color-hover)'
}),
c('&:active', {
backgroundColor: 'var(--node-color-pressed)'
}),
cM('hightlight', [
cE('text', {
borderBottomColor: 'var(--node-text-color-disabled)'
})
]),
cM('pending', [
c('&:hover', {
backgroundColor: 'transparent'
}),
cM('pending-bottom', {
borderBottom: '3px solid var(--node-color-hover)'
}),
cM('pending-top', {
borderTop: '3px solid var(--node-color-hover)'
}),
cM('pending-body', {
backgroundColor: 'var(--node-color-hover)'
})
]),
cM('selected', {
backgroundColor: 'var(--node-color-active)'
})
])
])

View File

@ -0,0 +1,195 @@
import { c, cB, cE, cM } from '../../../_utils/cssr'
import fadeInHeightExpandTransition from '../../../_styles/transitions/fade-in-height-expand'
import iconSwitchTransition from '../../../_styles/transitions/icon-switch'
// vars:
// --arrow-color
// --bezier
// --font-size
// --node-border-radius
// --node-color-active
// --node-color-hover
// --node-color-pressed
// --node-text-color
// --node-text-color-disabled
export default cB(
'tree',
{
fontSize: 'var(--font-size)'
},
[
c(
'ul, li',
`
margin: 0;
padding: 0;
list-style: none;
`
),
c('>', [
cB('tree-node', [
c('&:first-child', {
paddingTop: 0
})
])
]),
cB(
'tree-children-wrapper',
{
marginLeft: '16px'
},
[fadeInHeightExpandTransition({ duration: '0.15s' })]
),
cB('tree-node', {
padding: '6px 0 0 0'
}),
cB(
'tree-node-switcher',
`
cursor: pointer;
display: inline-flex;
height: 24px;
width: 24px;
align-items: center;
justify-content: center;
transition: transform .15s var(--bezier);
vertical-align: bottom;
`,
[
cE(
'icon',
`
position: relative;
height: 14px;
width: 14px;
display: flex;
color: var(--arrow-color);
transition: color .3s var(--bezier);
font-size: 14px;
`,
[
cB('icon', [iconSwitchTransition()]),
cB(
'base-loading',
`
color: var(--loading-color);
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
`,
[iconSwitchTransition()]
)
]
),
cM('hide', {
visibility: 'hidden'
}),
cM('expanded', {
transform: 'rotate(90deg)'
})
]
),
cB(
'tree-node-checkbox',
`
display: inline-flex;
height: 24px;
width: 16px;
vertical-align: bottom;
align-items: center;
justify-content: center;
margin-right: 4px;
`
),
cB(
'tree-node-content',
`
position: relative;
display: inline-flex;
height: 24px;
box-sizing: border-box;
border-bottom: 3px solid transparent;
border-top: 3px solid transparent;
line-height: 24px;
align-items: center;
vertical-align: bottom;
padding: 0 6px;
cursor: pointer;
border-radius: var(--node-border-radius);
text-decoration-color: transparent;
text-decoration-line: underline;
color: var(--node-text-color);
transition:
color .3s var(--bezier),
text-decoration-color .3s var(--bezier),
background-color .3s var(--bezier),
border-color .3s var(--bezier);
`,
[
c('&:last-child', {
marginBottom: 0
}),
cE(
'padding-box',
`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: transparent;
`
),
cE(
'text',
`
line-height: 1.25;
border-bottom: 1px solid transparent;
transition: border-color .3s var(--bezier);
`
),
cM(
'block',
{
width: 'calc(100% - 24px)'
},
[
cM('checkable', {
width: 'calc(100% - 48px)'
})
]
),
c('&:hover', {
backgroundColor: 'var(--node-color-hover)'
}),
c('&:active', {
backgroundColor: 'var(--node-color-pressed)'
}),
cM('hightlight', [
cE('text', {
borderBottomColor: 'var(--node-text-color-disabled)'
})
]),
cM('pending', [
c('&:hover', {
backgroundColor: 'transparent'
}),
cM('pending-bottom', {
borderBottom: '3px solid var(--node-color-hover)'
}),
cM('pending-top', {
borderTop: '3px solid var(--node-color-hover)'
}),
cM('pending-body', {
backgroundColor: 'var(--node-color-hover)'
})
]),
cM('selected', {
backgroundColor: 'var(--node-color-active)'
})
]
)
]
)

View File

@ -1,34 +0,0 @@
function traverse (nodes, callback, callbackAfter) {
nodes &&
nodes.forEach((node) => {
callback(node)
traverse(node.children, callback, callbackAfter)
callbackAfter && callbackAfter(node)
})
}
export function keysWithFilter (nodes, pattern, filter) {
const keys = new Set()
const highlightKeys = new Set()
const path = []
traverse(
nodes,
(node) => {
path.push(node)
if (filter(pattern, node)) {
highlightKeys.add(node.key)
for (let i = path.length - 2; i >= 0; --i) {
if (!keys.has(path[i].key)) {
keys.add(path[i].key)
} else {
return
}
}
}
},
() => {
path.pop()
}
)
return [Array.from(keys), Array.from(highlightKeys)]
}

44
src/tree/src/utils.ts Normal file
View File

@ -0,0 +1,44 @@
import { Key, KeyedRawNode } from 'treemate'
function traverse (
nodes: KeyedRawNode[] | undefined,
callback: (node: KeyedRawNode) => void,
callbackAfter: (node: KeyedRawNode) => void
) {
nodes &&
nodes.forEach((node) => {
callback(node)
traverse(node.children, callback, callbackAfter)
callbackAfter && callbackAfter(node)
})
}
export function keysWithFilter (
nodes: KeyedRawNode[],
pattern: string,
filter: (pattern: string, node: KeyedRawNode) => boolean
): [Key[], Key[]] {
const keys = new Set<Key>()
const highlightKeys = new Set<Key>()
const path: KeyedRawNode[] = []
traverse(
nodes,
(node) => {
path.push(node)
if (filter(pattern, node)) {
highlightKeys.add(node.key as Key)
for (let i = path.length - 2; i >= 0; --i) {
if (!keys.has(path[i].key as Key)) {
keys.add(path[i].key as Key)
} else {
return
}
}
}
},
() => {
path.pop()
}
)
return [Array.from(keys), Array.from(highlightKeys)]
}

View File

@ -1,6 +1,8 @@
import { changeColor } from 'seemly'
import { checkboxDark } from '../../checkbox/styles'
import { commonDark } from '../../_styles/new-common'
import type { ThemeCommonVars } from '../../_styles/new-common'
import type { TreeThemeVars } from './light'
export default {
name: 'Tree',
@ -8,7 +10,7 @@ export default {
peers: {
Checkbox: checkboxDark
},
self (vars) {
self (vars: ThemeCommonVars): TreeThemeVars {
const {
borderRadiusSmall,
hoverColorOverlay,

View File

@ -1,2 +0,0 @@
export { default as treeDark } from './dark.js'
export { default as treeLight } from './light.js'

3
src/tree/styles/index.ts Normal file
View File

@ -0,0 +1,3 @@
export { default as treeDark } from './dark'
export { default as treeLight } from './light'
export type { TreeThemeVars } from './light'

View File

@ -1,14 +1,15 @@
import { changeColor } from 'seemly'
import { checkboxLight } from '../../checkbox/styles'
import { commonLight } from '../../_styles/new-common'
import type { ThemeCommonVars } from '../../_styles/new-common'
export default {
const treeLight = {
name: 'Tree',
common: commonLight,
peers: {
Checkbox: checkboxLight
},
self (vars) {
self (vars: ThemeCommonVars) {
const {
borderRadiusSmall,
hoverColorOverlay,
@ -32,3 +33,6 @@ export default {
}
}
}
export default treeLight
export type TreeThemeVars = ReturnType<typeof treeLight.self>