leafer-x-tooltip-canvas 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/src/Tooltip.ts ADDED
@@ -0,0 +1,166 @@
1
+ import { IPenInputData, IPenData, IPen, ILeaf } from '@leafer-ui/interface'
2
+ import {
3
+ registerUI,
4
+ dataProcessor,
5
+ Pen,
6
+ PenData,
7
+ Text,
8
+ dataType,
9
+ } from 'leafer-ui'
10
+ import { IPos, IUserConfig } from './interface'
11
+ import { handleTextStyle } from './utils'
12
+
13
+ interface ITooltip extends IPen {
14
+ target?: ILeaf
15
+ isShow: boolean
16
+ showTimerId?: number | NodeJS.Timeout | null
17
+ hideTimerId?: number | NodeJS.Timeout | null
18
+ show(): void
19
+ hide(immediate?: boolean): void
20
+ update(pos: IPos): void
21
+ }
22
+
23
+ export interface ITooltipInputData extends IPenInputData {
24
+ target?: ILeaf
25
+ config?: IUserConfig
26
+ pointerPos?: IPos
27
+ }
28
+
29
+ export interface ITooltipData extends IPenData {
30
+ target?: ILeaf
31
+ pointerPos?: IPos
32
+ }
33
+
34
+ export class TooltipData extends PenData implements ITooltipData {
35
+ target?: ILeaf
36
+ timerId?: number | NodeJS.Timeout | null
37
+ pointerPos?: IPos
38
+ }
39
+
40
+ @registerUI()
41
+ export class Tooltip extends Pen implements ITooltip {
42
+ public get __tag() {
43
+ return 'Tooltip'
44
+ }
45
+ public className: 'leafer-x-tooltip'
46
+ @dataProcessor(TooltipData)
47
+ public declare __: ITooltipData
48
+
49
+ @dataType({ x: 0, y: 0 })
50
+ public declare pointerPos?: IPos
51
+
52
+ @dataType()
53
+ public declare showTimerId?: number | NodeJS.Timeout | null
54
+
55
+ @dataType()
56
+ public declare hideTimerId?: number | NodeJS.Timeout | null
57
+
58
+ @dataType(false)
59
+ public declare isShow: boolean
60
+ @dataType()
61
+ public declare config: IUserConfig
62
+
63
+ @dataType()
64
+ public declare target?: ILeaf
65
+ constructor(data: ITooltipInputData) {
66
+ super(data)
67
+ this.target = data.target
68
+ this.config = data.config
69
+ this.show()
70
+ }
71
+
72
+ /**
73
+ * @description tooltip
74
+ * @param pos 位置信息
75
+ */
76
+ private createShapes(pos = this.__.pointerPos): void {
77
+ this.clear() // 清除之前创建的路径
78
+ const { width, height, text } = handleTextStyle(this.target, this.config)
79
+ const {
80
+ backgroundColor,
81
+ stroke,
82
+ color,
83
+ padding,
84
+ borderRadius,
85
+ fontSize,
86
+ fontWeight,
87
+ fontFamily,
88
+ } = this.config.style
89
+ let offset = this.config.offset
90
+ this.setStyle({
91
+ fill: backgroundColor,
92
+ stroke,
93
+ })
94
+ this.add(
95
+ new Text({
96
+ className: 'leafer-x-tooltip',
97
+ fill: color,
98
+ fontSize,
99
+ fontWeight,
100
+ fontFamily,
101
+ x: pos.x + offset[0],
102
+ y: pos.y + offset[1],
103
+ text: text,
104
+ padding,
105
+ })
106
+ )
107
+ this.roundRect(
108
+ pos.x + offset[0],
109
+ pos.y + offset[1],
110
+ width,
111
+ height,
112
+ borderRadius
113
+ )
114
+ this.isShow = true
115
+ }
116
+
117
+ private clearShowHideTimers() {
118
+ if (this.showTimerId) {
119
+ clearTimeout(this.showTimerId)
120
+ this.showTimerId = null
121
+ }
122
+
123
+ if (this.hideTimerId) {
124
+ clearTimeout(this.hideTimerId)
125
+ this.hideTimerId = null
126
+ }
127
+ }
128
+
129
+ public show(pos = this.__.pointerPos) {
130
+ this.clearShowHideTimers()
131
+ this.showTimerId = setTimeout(() => {
132
+ this.createShapes(pos)
133
+ clearTimeout(this.showTimerId)
134
+ this.showTimerId = null
135
+ }, this.config.showDelay)
136
+ }
137
+
138
+ public hide(immediate = false) {
139
+ this.clearShowHideTimers()
140
+ if (immediate) {
141
+ this.destroy()
142
+ } else {
143
+ if (!this.hideTimerId) {
144
+ this.hideTimerId = setTimeout(() => {
145
+ this.destroy()
146
+ }, this.config.hideDelay)
147
+ }
148
+ }
149
+ }
150
+
151
+ public update(pos: IPos) {
152
+ this.clearShowHideTimers()
153
+ if (this.isShow) {
154
+ this.createShapes(pos)
155
+ } else {
156
+ this.show(pos)
157
+ }
158
+ }
159
+
160
+ public destroyTooltip() {
161
+ this.clearShowHideTimers()
162
+ this.destroy()
163
+ this.isShow = false
164
+ this.target = undefined
165
+ }
166
+ }
@@ -0,0 +1,187 @@
1
+ import { App, PointerEvent } from '@leafer-ui/core'
2
+ import type {
3
+ IEventListenerId,
4
+ ILeaf,
5
+ ILeafer,
6
+ } from '@leafer-ui/interface'
7
+ import { IUserConfig } from './interface'
8
+ import { Tooltip } from './Tooltip'
9
+ import { getTooltipId } from './utils'
10
+ import { defaultConfig } from './defaultConfig'
11
+
12
+ export class TooltipPlugin {
13
+ /**
14
+ * @param instance 实例
15
+ * @private
16
+ */
17
+ private instance: ILeafer | App
18
+ private aimLeafer: ILeafer
19
+ /**
20
+ * @param config 用户配置
21
+ * @private
22
+ */
23
+ private readonly config: IUserConfig
24
+
25
+ /**
26
+ * @param bindEventIds - 绑定的事件 id
27
+ * @private
28
+ */
29
+ private readonly pointEventId: IEventListenerId
30
+
31
+ constructor(instance: ILeafer | App, config?: IUserConfig) {
32
+ this.instance = instance
33
+ this.config = Object.assign({}, defaultConfig, config)
34
+ this.handleConfig()
35
+ this.initState()
36
+ this.pointEventId = this.initEvent()
37
+ }
38
+
39
+ /**
40
+ * @description 初始化状态
41
+ */
42
+ private initState() {
43
+ if (this.instance.isApp) {
44
+ const app = this.instance as App
45
+ if (app.sky === undefined) {
46
+ app.sky = app.addLeafer({
47
+ type: 'draw',
48
+ usePartRender: false,
49
+ })
50
+ }
51
+ this.aimLeafer = app.sky
52
+ } else if (this.instance.isLeafer) {
53
+ this.aimLeafer = this.instance
54
+ }
55
+ }
56
+
57
+ private handleConfig(){
58
+ if(this.config.theme==='dark'){
59
+ this.config.style.backgroundColor = "black"
60
+ this.config.style.color = "white"
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @description 初始化事件处理
66
+ * @private
67
+ */
68
+ private initEvent() {
69
+ return this.instance.on_(PointerEvent.MOVE, this.handlePointMove, this)
70
+ }
71
+
72
+ /**
73
+ * @description 处理鼠标移动事件
74
+ * @param event
75
+ * @private
76
+ */
77
+ private handlePointMove(event: PointerEvent) {
78
+ const result = this.instance.pick(
79
+ { x: event.x, y: event.y },
80
+ {
81
+ ignoreHittable: true,
82
+ through: true,
83
+ }
84
+ )
85
+
86
+ const target = this.filterTarget(result.throughPath.list)
87
+ if (!target) {
88
+ this.hideTooltip()
89
+ return
90
+ }
91
+
92
+ if (!this.handleAllowed(target)) {
93
+ this.hideTooltip()
94
+ return
95
+ }
96
+ this.handleTooltip(event, target)
97
+ }
98
+
99
+ private filterTarget(list: ILeaf[]): ILeaf | null {
100
+ const ignoreTag = ['Leafer', 'App']
101
+ const pureResult = list.filter((item) => {
102
+ if (
103
+ ignoreTag.includes(item?.tag) ||
104
+ item?.parent?.tag === 'Tooltip' ||
105
+ item?.className === 'leafer-x-tooltip'
106
+ ) {
107
+ return false
108
+ }
109
+ return true
110
+ })
111
+
112
+ return pureResult[pureResult.length - 1] || null
113
+ }
114
+
115
+ /**
116
+ * @description 处理显示许可
117
+ * @param target 目标节点
118
+ * @returns
119
+ */
120
+ private handleAllowed(target: ILeaf) {
121
+ const infoArr = ['#' + target.id, '.' + target.className, target.tag]
122
+ const { includesType, excludesType } = this.config
123
+
124
+ if (includesType.length === 0 && excludesType.length === 0) return true
125
+
126
+ const isInclude = infoArr.some((string) => includesType.includes(string))
127
+ const isExclude = infoArr.some((string) => excludesType.includes(string))
128
+
129
+ if (!isExclude && includesType.length === 0) return true
130
+ if (!isInclude && excludesType.length === 0) return false
131
+ return isInclude || !isExclude
132
+ }
133
+
134
+ /**
135
+ * @description 隐藏 tooltip
136
+ */
137
+ private hideTooltip() {
138
+ const tooltipList = this.aimLeafer.find('Tooltip') as Tooltip[]
139
+ tooltipList.forEach((tooltip: Tooltip) => {
140
+ tooltip.hide()
141
+ })
142
+ }
143
+
144
+ /**
145
+ * @description 创建或更新 tooltip
146
+ */
147
+ private handleTooltip(event: PointerEvent, target: ILeaf) {
148
+ const id = getTooltipId(target)
149
+ const tooltipList = this.aimLeafer.find('Tooltip') as Tooltip[]
150
+ let processed = false
151
+ for (const tooltip of tooltipList) {
152
+ if (tooltip.id === id) {
153
+ tooltip.update({ x: event.x, y: event.y })
154
+ processed = true
155
+ } else {
156
+ tooltip.hide()
157
+ }
158
+ }
159
+
160
+ if (!processed) {
161
+ this.aimLeafer.add(
162
+ new Tooltip({
163
+ id,
164
+ pointerPos: { x: event.x, y: event.y },
165
+ target,
166
+ config: this.config,
167
+ })
168
+ )
169
+ }
170
+ }
171
+
172
+ /**
173
+ * @description 销毁
174
+ */
175
+ public destroy() {
176
+ const tooltipList = this.aimLeafer.find('Tooltip') as Tooltip[]
177
+ if (tooltipList) {
178
+ tooltipList.forEach((tooltip) => {
179
+ tooltip.destroyTooltip()
180
+ tooltip.parent.remove(tooltip)
181
+ })
182
+ }
183
+ this.instance.off_(this.pointEventId)
184
+ this.instance = null
185
+ this.aimLeafer = null
186
+ }
187
+ }
@@ -0,0 +1,26 @@
1
+ import { IUserConfig } from './interface'
2
+ export const defaultConfig: IUserConfig = {
3
+ reference: 'pointer',
4
+ info: ['tag'],
5
+ showType: 'value',
6
+ formatter: () => undefined,
7
+ showDelay: 500,
8
+ hideDelay: 0,
9
+ arrow: false,
10
+ placement: 'right-end',
11
+ offset: [5, 5],
12
+ preventOverflow: false,
13
+ includesType: [],
14
+ excludesType: [],
15
+ theme: 'light',
16
+ style: {
17
+ backgroundColor: 'white',
18
+ stroke: 'black',
19
+ color: 'black',
20
+ borderRadius: 8,
21
+ padding: 8,
22
+ fontSize: 14,
23
+ fontWeight: 400,
24
+ fontFamily: `"Punctuation SC","Inter",ui-sans-serif,system-ui,"PingFang SC","Noto Sans CJK SC", "Noto Sans SC", "Heiti SC", "Microsoft YaHei", "DengXian", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`,
25
+ },
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { TooltipPlugin } from './TooltipPlugin'
@@ -0,0 +1,40 @@
1
+ import { IFontWeight } from '@leafer-ui/interface'
2
+ interface IUserConfig {
3
+ reference?: 'pointer' | 'element'
4
+ showDelay?: number
5
+ arrow?: boolean
6
+ hideDelay?: number
7
+ placement?:
8
+ | 'top'
9
+ | 'top-start'
10
+ | 'top-end'
11
+ | 'bottom'
12
+ | 'bottom-start'
13
+ | 'bottom-end'
14
+ | 'left'
15
+ | 'left-start'
16
+ | 'left-end'
17
+ | 'right'
18
+ | 'right-start'
19
+ | 'right-end'
20
+ offset?: [number, number]
21
+ info?: Array<string>
22
+ formatter?: (item?: any) => string | undefined
23
+ showType?: 'value' | 'key-value'
24
+ preventOverflow?: boolean
25
+ includesType?: Array<string>
26
+ excludesType?: Array<string>
27
+ theme?: 'light' | 'dark'
28
+ style?: IStyleConfig
29
+ }
30
+ interface IStyleConfig {
31
+ backgroundColor?: string
32
+ stroke?: string
33
+ color?: string
34
+ borderRadius?: number
35
+ padding?: number
36
+ fontSize?: number
37
+ fontWeight?: IFontWeight
38
+ fontFamily?: string
39
+ }
40
+ export type { IUserConfig }
@@ -0,0 +1,5 @@
1
+ interface IPos {
2
+ x: number
3
+ y: number
4
+ }
5
+ export type { IPos }
@@ -0,0 +1,2 @@
1
+ export * from './config'
2
+ export * from './display'
package/src/utils.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { Box } from 'leafer-ui'
2
+ import { ILeaf } from '@leafer-ui/interface'
3
+ import { IUserConfig } from './interface'
4
+ /**
5
+ * @description 获取uuid 考虑兼容性问题采用此方法
6
+ * @param length id长度
7
+ * @returns
8
+ */
9
+ export const getTooltipId = function (target: ILeaf) {
10
+ return target.tag + target.innerId
11
+ }
12
+ export const handleTextStyle = function (target: ILeaf, config: IUserConfig) {
13
+ const str = handleContent(target, config)
14
+ const { fontSize, fontFamily, fontWeight, padding } = config.style
15
+ const box = new Box({
16
+ children: [
17
+ {
18
+ tag: 'Text',
19
+ text: str,
20
+ fontSize,
21
+ fontFamily,
22
+ fontWeight,
23
+ padding,
24
+ },
25
+ ],
26
+ })
27
+
28
+ const { width, height } = box.getBounds()
29
+ return { width, height, text: str }
30
+ }
31
+
32
+ function handleContent(target: ILeaf, config: IUserConfig) {
33
+ let str = ''
34
+ const data = target as { [key: string]: any }
35
+
36
+ // 如果formatter函数存在,则使用formatter函数进行格式化
37
+ if (config.formatter(data) !== undefined) {
38
+ str = config.formatter(data)
39
+ } else {
40
+ // 如果formatter函数不存在,则根据showType进行默认格式化
41
+ if (config.showType == 'value') {
42
+ str += config.info
43
+ .map((dataName: string) => `${data[dataName]}`)
44
+ .join('\n')
45
+ } else if (config.showType == 'key-value') {
46
+ str += config.info
47
+ .map((dataName: string) => `${dataName} : ${data[dataName]}`)
48
+ .join('\n')
49
+ }
50
+ }
51
+ return str
52
+ }
@@ -0,0 +1,48 @@
1
+ import { App } from '@leafer-ui/core';
2
+ import { IFontWeight, ILeafer } from '@leafer-ui/interface';
3
+
4
+ interface IUserConfig {
5
+ reference?: 'pointer' | 'element';
6
+ showDelay?: number;
7
+ arrow?: boolean;
8
+ hideDelay?: number;
9
+ placement?: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end';
10
+ offset?: [number, number];
11
+ info?: Array<string>;
12
+ formatter?: (item?: any) => string | undefined;
13
+ showType?: 'value' | 'key-value';
14
+ preventOverflow?: boolean;
15
+ includesType?: Array<string>;
16
+ excludesType?: Array<string>;
17
+ theme?: 'light' | 'dark';
18
+ style?: IStyleConfig;
19
+ }
20
+ interface IStyleConfig {
21
+ backgroundColor?: string;
22
+ stroke?: string;
23
+ color?: string;
24
+ borderRadius?: number;
25
+ padding?: number;
26
+ fontSize?: number;
27
+ fontWeight?: IFontWeight;
28
+ fontFamily?: string;
29
+ }
30
+
31
+ declare class TooltipPlugin {
32
+ private instance;
33
+ private aimLeafer;
34
+ private readonly config;
35
+ private readonly pointEventId;
36
+ constructor(instance: ILeafer | App, config?: IUserConfig);
37
+ private initState;
38
+ private handleConfig;
39
+ private initEvent;
40
+ private handlePointMove;
41
+ private filterTarget;
42
+ private handleAllowed;
43
+ private hideTooltip;
44
+ private handleTooltip;
45
+ destroy(): void;
46
+ }
47
+
48
+ export { TooltipPlugin };