@ticatec/uniface-element 5.1.0 → 5.1.1
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/dist/overlay/dialog/CommonDialog.svelte +1 -1
- package/dist/overlay/dialog/CommonDialog.svelte.d.ts +1 -1
- package/dist/overlay/dialog/Dialog.svelte +1 -1
- package/dist/overlay/dialog/Dialog.svelte.d.ts +1 -1
- package/dist/overlay/dialog/index.d.ts +1 -1
- package/dist/overlay/tooltip/README.md +16 -1
- package/dist/overlay/tooltip/README_CN.md +16 -1
- package/dist/overlay/tooltip/Tooltip.svelte +94 -19
- package/docs/NAMING.md +2 -0
- package/docs/NAMING_CN.md +2 -0
- package/package.json +1 -1
- package/dist/overlay/dialog/DialogActions.d.ts +0 -16
- package/dist/overlay/dialog/DialogActions.js +0 -1
- /package/dist/overlay/dialog/{DialogCloseConfirm.d.ts → BeforeClose.d.ts} +0 -0
- /package/dist/overlay/dialog/{DialogCloseConfirm.js → BeforeClose.js} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type {Snippet} from 'svelte';
|
|
3
3
|
import Dialog from "./Dialog.svelte";
|
|
4
4
|
import type {ButtonAction, ButtonActions} from "../../types";
|
|
5
|
-
import type {BeforeClose} from "./
|
|
5
|
+
import type {BeforeClose} from "./BeforeClose";
|
|
6
6
|
import {ModalResult, type OnClose} from "./IDialog";
|
|
7
7
|
import i18nRes from "../../i18nRes";
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import {getContext, hasContext, onMount} from "svelte";
|
|
3
|
-
import type {BeforeClose} from "./
|
|
3
|
+
import type {BeforeClose} from "./BeforeClose";
|
|
4
4
|
import ActionBar from "../../layouts/action_bar";
|
|
5
5
|
import type {ButtonAction, ButtonActions} from "../../types";
|
|
6
6
|
import dialogManager from "./DialogManager.svelte";
|
|
@@ -2,7 +2,7 @@ import Dialog from "./Dialog.svelte";
|
|
|
2
2
|
import dialogManager from "./DialogManager.svelte";
|
|
3
3
|
import type IDialog from "./IDialog";
|
|
4
4
|
import { ModalResult, type OnClose } from "./IDialog";
|
|
5
|
-
import type { BeforeClose } from "./
|
|
5
|
+
import type { BeforeClose } from "./BeforeClose";
|
|
6
6
|
import CommonDialog from "./CommonDialog.svelte";
|
|
7
7
|
import DialogBoard from "./DialogBoard.svelte";
|
|
8
8
|
declare global {
|
|
@@ -9,7 +9,7 @@ A global tooltip system that automatically detects and shows contextual hints wh
|
|
|
9
9
|
- **Edge-Aware Alignment**: Adjusts horizontal alignment (left/center/right) to prevent screen overflow.
|
|
10
10
|
- **Markdown Support**: Renders rich text with bold, italic, code, lists, and more via snarkdown.
|
|
11
11
|
- **Delayed Display**: Configurable delay (default 200ms) to prevent flickering.
|
|
12
|
-
- **Auto Hide**:
|
|
12
|
+
- **Auto Hide**: Hides when the mouse leaves the element — and also when the element itself goes away (removed from the DOM, hidden by `display:none` / `visibility:hidden`, or its `data-tip` cleared), on scroll, and when the window loses focus.
|
|
13
13
|
- **Zero Boilerplate**: Just add `data-tip` attribute, no JavaScript needed.
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
@@ -147,6 +147,21 @@ The tooltip automatically adjusts its position to stay within the viewport:
|
|
|
147
147
|
<button data-tip="Right-aligned tooltip" style="margin-right: 0;">Right edge</button>
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
## Dismissal Behavior
|
|
151
|
+
|
|
152
|
+
A tooltip is dismissed when any of the following happens:
|
|
153
|
+
|
|
154
|
+
| Trigger | Why it matters |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| The pointer leaves the element (`mouseleave`) | The normal case. |
|
|
157
|
+
| The element is removed from the DOM | A destroyed component, a closed dialog, a re-rendered row: the browser does **not** reliably fire `mouseleave` for a node that disappears under the cursor, so the tooltip watches the element and hides itself. |
|
|
158
|
+
| The element (or an ancestor) becomes hidden | `display:none`, `visibility:hidden`, a collapsed panel, a switched tab. |
|
|
159
|
+
| `data-tip` / `data-tooltip` is cleared | E.g. a validation error tooltip whose error has just been fixed. Changing the text instead updates the tooltip in place. |
|
|
160
|
+
| The page or any scroll container scrolls | The tooltip is `position: fixed`; without this it would drift away from its target. |
|
|
161
|
+
| The window loses focus | Alt-tabbing away while hovering. |
|
|
162
|
+
|
|
163
|
+
This is handled internally (a `MutationObserver` is attached only while a tooltip is pending or visible, so there is no cost when idle) — nothing is required from the calling code.
|
|
164
|
+
|
|
150
165
|
## Styling & Theming
|
|
151
166
|
|
|
152
167
|
### CSS Variables
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **边缘对齐**:自动调整水平对齐(左/中/右)防止溢出屏幕
|
|
10
10
|
- **Markdown 支持**:支持富文本格式(粗体、斜体、代码、列表等),通过 snarkdown 渲染
|
|
11
11
|
- **延迟显示**:可配置延迟(默认 200 毫秒)防止误触发
|
|
12
|
-
-
|
|
12
|
+
- **自动隐藏**:鼠标离开元素时隐藏;元素本身消失(从 DOM 移除、被 `display:none` / `visibility:hidden` 隐藏、`data-tip` 被清空)、页面滚动、窗口失焦时同样会自动隐藏
|
|
13
13
|
- **零样板代码**:只需添加 `data-tip` 属性,无需编写 JavaScript
|
|
14
14
|
|
|
15
15
|
## 安装
|
|
@@ -147,6 +147,21 @@ import Tooltip from "@ticatec/uniface-element/Tooltip";
|
|
|
147
147
|
<button data-tip="右对齐提示" style="margin-right: 0;">右边缘</button>
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
## 隐藏时机
|
|
151
|
+
|
|
152
|
+
出现以下任一情况时,tooltip 都会收起:
|
|
153
|
+
|
|
154
|
+
| 触发条件 | 说明 |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| 鼠标离开元素(`mouseleave`) | 常规情况。 |
|
|
157
|
+
| 元素从 DOM 中被移除 | 组件被销毁、对话框关闭、列表行重新渲染等。光标下的节点消失时浏览器**不会**可靠地派发 `mouseleave`,因此 tooltip 会自己监视目标元素并收起——否则它会一直挂在屏幕上无法消除。 |
|
|
158
|
+
| 元素(或其祖先)被隐藏 | `display:none`、`visibility:hidden`、折叠面板、切换标签页等。 |
|
|
159
|
+
| `data-tip` / `data-tooltip` 被清空 | 例如校验错误已修正。若只是文本变化,则原地更新 tooltip 内容。 |
|
|
160
|
+
| 页面或任意滚动容器发生滚动 | tooltip 使用 `position: fixed`,不收起就会与目标元素脱节。 |
|
|
161
|
+
| 窗口失去焦点 | 悬停时切换到其他窗口。 |
|
|
162
|
+
|
|
163
|
+
这些都由组件内部处理(`MutationObserver` 仅在 tooltip 等待显示或已显示期间才连接,空闲时无任何开销),调用方无需做任何事情。
|
|
164
|
+
|
|
150
165
|
## 样式与主题定制
|
|
151
166
|
|
|
152
167
|
### CSS 变量
|
|
@@ -36,28 +36,104 @@
|
|
|
36
36
|
ALLOWED_ATTR: ['href', 'src', 'alt', 'class']
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
/** 当前触发 tooltip 的元素。元素被销毁或隐藏后浏览器不再派发 mouseleave,需要由 tooltip 自己收起。 */
|
|
40
|
+
let activeTarget: HTMLElement | null = null;
|
|
41
|
+
let observer: MutationObserver | null = null;
|
|
42
|
+
|
|
43
|
+
const getTipText = (el: Element | null): string =>
|
|
44
|
+
(el?.getAttribute('data-tip') || el?.getAttribute('data-tooltip') || '');
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 目标元素是否仍然渲染在页面上。
|
|
48
|
+
* 元素从 DOM 移除(isConnected === false),或自身/祖先被 display:none、visibility:hidden
|
|
49
|
+
* 隐藏时,浏览器不会再派发 mouseleave —— 这正是 tooltip 会一直挂在屏幕上的原因。
|
|
50
|
+
*/
|
|
51
|
+
const isRendered = (el: HTMLElement | null): boolean => {
|
|
52
|
+
if (el == null || !el.isConnected) return false;
|
|
53
|
+
const checkVisibility = (el as any).checkVisibility;
|
|
54
|
+
if (typeof checkVisibility === 'function') {
|
|
55
|
+
// 现代浏览器:一次性覆盖自身及祖先的 display / visibility / content-visibility
|
|
56
|
+
return checkVisibility.call(el, {checkVisibilityCSS: true, visibilityProperty: true});
|
|
57
|
+
}
|
|
58
|
+
// 兜底(老浏览器 / jsdom):只能判断元素自身的计算样式
|
|
59
|
+
const style = window.getComputedStyle(el);
|
|
60
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const renderTip = (text: string) => {
|
|
64
|
+
state.message = text;
|
|
65
|
+
// Parse markdown to HTML, converting \n to actual newlines first
|
|
66
|
+
const parsedText = text.replace(/\\n/g, '\n');
|
|
67
|
+
state.html = DOMPurify.sanitize(snarkdown(parsedText), DOMPURIFY_CONFIG);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** 收起 tooltip,并解除对当前目标元素的所有监听 */
|
|
71
|
+
const hide = () => {
|
|
72
|
+
if (timer) {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
timer = null;
|
|
75
|
+
}
|
|
76
|
+
observer?.disconnect();
|
|
77
|
+
activeTarget?.removeEventListener('mouseleave', hide);
|
|
78
|
+
activeTarget = null;
|
|
79
|
+
state.visible = false;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** 目标元素被移除 / 隐藏 / data-tip 被清空时收起;data-tip 变化时更新内容 */
|
|
83
|
+
const checkTarget = () => {
|
|
84
|
+
if (activeTarget == null) return;
|
|
85
|
+
const text = getTipText(activeTarget);
|
|
86
|
+
if (!text || !isRendered(activeTarget)) {
|
|
87
|
+
hide();
|
|
88
|
+
} else if (state.visible && text !== state.message) {
|
|
89
|
+
renderTip(text);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const watchTarget = (target: HTMLElement) => {
|
|
94
|
+
activeTarget = target;
|
|
95
|
+
target.addEventListener('mouseleave', hide);
|
|
96
|
+
if (typeof MutationObserver === 'undefined') return;
|
|
97
|
+
observer ??= new MutationObserver(checkTarget);
|
|
98
|
+
// 只在 tooltip 处于等待/显示状态时观察,空闲时不产生任何开销
|
|
99
|
+
observer.observe(document.documentElement ?? document, {
|
|
100
|
+
childList: true,
|
|
101
|
+
subtree: true,
|
|
102
|
+
attributes: true,
|
|
103
|
+
attributeFilter: ['style', 'class', 'hidden', 'data-tip', 'data-tooltip']
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
39
107
|
// 全局事件监听:利用事件冒泡实现自动绑定
|
|
40
108
|
onMount(() => {
|
|
41
109
|
const handleMouseOver = (e: MouseEvent) => {
|
|
42
110
|
const el = e.target as HTMLElement;
|
|
43
111
|
if (!el || typeof el.closest !== 'function') return;
|
|
44
|
-
const target = el.closest('[data-tip], [data-tooltip]');
|
|
112
|
+
const target = el.closest('[data-tip], [data-tooltip]') as HTMLElement | null;
|
|
45
113
|
if (!target) return;
|
|
46
114
|
|
|
47
|
-
const text =
|
|
115
|
+
const text = getTipText(target);
|
|
48
116
|
if (!text) return;
|
|
49
117
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// 清除之前的定时器
|
|
54
|
-
if (timer) clearTimeout(timer);
|
|
118
|
+
// 已经在为同一个元素显示,保持不动(避免在其子元素间移动时闪烁 / 重新定位)
|
|
119
|
+
if (target === activeTarget && state.visible) return;
|
|
55
120
|
|
|
56
|
-
//
|
|
57
|
-
|
|
121
|
+
// 清除上一个目标的定时器与监听
|
|
122
|
+
hide();
|
|
123
|
+
// 监听该元素的移除 / 隐藏 / 离开,以便收起
|
|
124
|
+
watchTarget(target);
|
|
58
125
|
|
|
59
126
|
// 延迟触发显示
|
|
60
127
|
timer = setTimeout(() => {
|
|
128
|
+
timer = null;
|
|
129
|
+
// 延迟期间目标可能已经被销毁或隐藏
|
|
130
|
+
if (!isRendered(target)) {
|
|
131
|
+
hide();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 获取元素相对于视口的位置,用来精准定位
|
|
136
|
+
const rect = target.getBoundingClientRect();
|
|
61
137
|
// 计算触发位置(这里以元素的中心/边缘为基准,比纯鼠标坐标更稳定)
|
|
62
138
|
const x = rect.left + rect.width / 2;
|
|
63
139
|
const elementBottom = rect.bottom;
|
|
@@ -65,10 +141,7 @@
|
|
|
65
141
|
|
|
66
142
|
const position = calculateOptimalPosition(x, elementBottom, rect);
|
|
67
143
|
|
|
68
|
-
|
|
69
|
-
// Parse markdown to HTML, converting \n to actual newlines first
|
|
70
|
-
const parsedText = text.replace(/\\n/g, '\n');
|
|
71
|
-
state.html = DOMPurify.sanitize(snarkdown(parsedText), DOMPURIFY_CONFIG);
|
|
144
|
+
renderTip(getTipText(target) || text);
|
|
72
145
|
state.position = position;
|
|
73
146
|
state.transform = getTransformStyle(position, x, elementBottom);
|
|
74
147
|
state.x = x;
|
|
@@ -78,17 +151,19 @@
|
|
|
78
151
|
}, delay);
|
|
79
152
|
};
|
|
80
153
|
|
|
81
|
-
const handleMouseLeave = () => {
|
|
82
|
-
if (timer) clearTimeout(timer);
|
|
83
|
-
state.visible = false;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
154
|
// 绑定到 document 上
|
|
87
155
|
document.addEventListener('mouseover', handleMouseOver);
|
|
156
|
+
// 指针移出整个文档、窗口失焦、页面滚动(tooltip 是 fixed 定位,滚动后会与目标脱节)
|
|
157
|
+
document.addEventListener('mouseleave', hide);
|
|
158
|
+
window.addEventListener('blur', hide);
|
|
159
|
+
window.addEventListener('scroll', hide, {capture: true, passive: true});
|
|
88
160
|
|
|
89
161
|
return () => {
|
|
90
162
|
document.removeEventListener('mouseover', handleMouseOver);
|
|
91
|
-
|
|
163
|
+
document.removeEventListener('mouseleave', hide);
|
|
164
|
+
window.removeEventListener('blur', hide);
|
|
165
|
+
window.removeEventListener('scroll', hide, {capture: true} as any);
|
|
166
|
+
hide();
|
|
92
167
|
};
|
|
93
168
|
});
|
|
94
169
|
|
package/docs/NAMING.md
CHANGED
|
@@ -284,6 +284,8 @@ interface Action<T = any> {
|
|
|
284
284
|
| `ContextMenuItem.ts` | `ActionCallback` → `ContextMenuAction`; fields `text`/`action` → `label`/`handler` |
|
|
285
285
|
| ImageFilesField | remove `text` from Props |
|
|
286
286
|
| AttachmentEditor, ImageEditor | type icons `icon_google_image` / `icon_google_picture` do not exist in the icon font (image / PDF types never had an icon) → `icon_google_insert_photo` / `icon_google_picture_as_pdf` |
|
|
287
|
+
| Tooltip (5.1.1) | Browsers do not fire `mouseleave` for an element removed/hidden under the cursor, so the tooltip stayed on screen forever → it now watches its target (`MutationObserver` + `isConnected` / `checkVisibility`) and also dismisses on a cleared `data-tip`, on scroll, and on window blur |
|
|
288
|
+
| overlay/dialog (5.1.1) | Removed the dead, unreferenced `DialogActions.ts` (still carrying the retired `closeConfirm` name); renamed `DialogCloseConfirm.ts` → `BeforeClose.ts` to match the type it exports |
|
|
287
289
|
| InputOptionsSelect README | align with the implementation (implement `hideOptions / disabledOptions` or drop the docs) |
|
|
288
290
|
|
|
289
291
|
### A3 Types and data objects
|
package/docs/NAMING_CN.md
CHANGED
|
@@ -284,6 +284,8 @@ interface Action<T = any> {
|
|
|
284
284
|
| `ContextMenuItem.ts` | `ActionCallback` → `ContextMenuAction`;字段 `text`/`action` → `label`/`handler` |
|
|
285
285
|
| ImageFilesField | 从 Props 删除 `text` |
|
|
286
286
|
| AttachmentEditor、ImageEditor | 类型图标 `icon_google_image`、`icon_google_picture` 在图标字体里不存在(图片 / PDF 类型一直没有图标) → `icon_google_insert_photo`、`icon_google_picture_as_pdf` |
|
|
287
|
+
| Tooltip(5.1.1) | 目标元素在 tooltip 显示期间被移除 / 隐藏时浏览器不再派发 `mouseleave`,tooltip 会永久停留 → 改为跟踪目标元素(`MutationObserver` + `isConnected` / `checkVisibility`),并在 `data-tip` 清空、滚动、窗口失焦时收起 |
|
|
288
|
+
| overlay/dialog(5.1.1) | 删除无人引用的死文件 `DialogActions.ts`(仍带退役名 `closeConfirm`);`DialogCloseConfirm.ts` 更名为 `BeforeClose.ts`,与其中的类型名一致 |
|
|
287
289
|
| InputOptionsSelect README | 与实现对齐(实现 `hideOptions / disabledOptions` 或删文档) |
|
|
288
290
|
|
|
289
291
|
### A3 类型与数据对象
|
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ButtonActions } from "../../types";
|
|
2
|
-
import type { ButtonAction } from "../../types";
|
|
3
|
-
export default interface DialogActions {
|
|
4
|
-
/**
|
|
5
|
-
* 操作类
|
|
6
|
-
*/
|
|
7
|
-
list: ButtonActions;
|
|
8
|
-
/**
|
|
9
|
-
* 关闭窗口的action
|
|
10
|
-
*/
|
|
11
|
-
close: ButtonAction;
|
|
12
|
-
/**
|
|
13
|
-
* 关闭问询
|
|
14
|
-
*/
|
|
15
|
-
closeConfirm: () => Promise<boolean>;
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { ModalResult } from "../message-box";
|
|
File without changes
|
|
File without changes
|