@jsswift/ui 1.0.44 → 1.0.46
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/css/base.css +2 -137
- package/dist/css/fonts-material.css +7 -0
- package/dist/css/fonts-nunito.css +134 -0
- package/dist/css/min-ui.css +1 -1
- package/dist/css/ui.css +2 -137
- package/dist/css/ui.min.css +1 -1
- package/dist/min-ui.js +4 -4
- package/dist/tabler.js +7 -0
- package/dist/ui.js +10561 -14120
- package/dist/ui.min.js +4 -4
- package/package.json +6 -3
- package/src/01-foundation-helpers.js +0 -402
- package/src/10-primitives-layout.js +14 -1013
- package/src/20-display-content.js +484 -718
- package/src/21-list-content.js +246 -341
- package/src/22-banner-content.js +3 -116
- package/src/23-tooltip.js +276 -326
- package/src/31-form-advanced.js +2638 -3003
- package/src/40-navigation.js +1262 -1454
- package/src/41-tab-panel.js +896 -1090
- package/src/50-feedback.js +999 -1209
- package/src/60-shell-app.js +5 -265
- package/src/61-form-service.js +304 -386
- package/src/70-dialog.js +209 -277
- package/src/71-menu-overlays.js +1175 -1400
- package/src/80-data.js +433 -503
- package/src/css/base.css +2 -137
- package/src/css/fonts-material.css +7 -0
- package/src/css/fonts-nunito.css +134 -0
- package/src/modules.json +59 -23
- package/src/tabler.js +7 -0
package/src/23-tooltip.js
CHANGED
|
@@ -1,326 +1,276 @@
|
|
|
1
|
-
UI.Tooltip = (...args) => {
|
|
2
|
-
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
-
const slots = props.slots || {};
|
|
4
|
-
let entry = null;
|
|
5
|
-
let openTimer = null;
|
|
6
|
-
let closeTimer = null;
|
|
7
|
-
let boundEl = null;
|
|
8
|
-
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(props, key);
|
|
9
|
-
const hasExternalTarget = hasOwn("target") || hasOwn("anchorEl") || hasOwn("triggerEl");
|
|
10
|
-
const targetNode = props.target || props.anchorEl || props.triggerEl || (children && children.length ? children[0] : null);
|
|
11
|
-
|
|
12
|
-
const getNumber = (value, fallback) => {
|
|
13
|
-
const raw = uiUnwrap(value);
|
|
14
|
-
if (raw == null || raw === "") return fallback;
|
|
15
|
-
const n = Number(raw);
|
|
16
|
-
return Number.isFinite(n) ? n : fallback;
|
|
17
|
-
};
|
|
18
|
-
const clearTimers = () => {
|
|
19
|
-
clearTimeout(openTimer);
|
|
20
|
-
clearTimeout(closeTimer);
|
|
21
|
-
openTimer = null;
|
|
22
|
-
closeTimer = null;
|
|
23
|
-
};
|
|
24
|
-
const getDelay = () => getNumber(props.delay, 350);
|
|
25
|
-
const getHideDelay = () => getNumber(props.hideDelay, uiUnwrap(props.interactive) ? 120 : 0);
|
|
26
|
-
const closeNow = () => {
|
|
27
|
-
clearTimers();
|
|
28
|
-
if (!entry) return;
|
|
29
|
-
const toClose = entry;
|
|
30
|
-
entry = null;
|
|
31
|
-
overlayLeave(toClose, () => JSswift.overlay.close(toClose.id));
|
|
32
|
-
};
|
|
33
|
-
const parseTriggers = (value) => {
|
|
34
|
-
if (value == null || value === true) return new Set(["hover", "focus"]);
|
|
35
|
-
if (value === false) return new Set(["manual"]);
|
|
36
|
-
const raw = Array.isArray(value) ? value : String(value).split(/[\s,|/]+/);
|
|
37
|
-
const out = new Set();
|
|
38
|
-
raw.forEach((item) => {
|
|
39
|
-
const key = String(item || "").trim().toLowerCase();
|
|
40
|
-
if (!key) return;
|
|
41
|
-
if (key === "mouseenter" || key === "mouseover") out.add("hover");
|
|
42
|
-
else if (key === "blur" || key === "focusin") out.add("focus");
|
|
43
|
-
else if (key === "tap") out.add("click");
|
|
44
|
-
else out.add(key);
|
|
45
|
-
});
|
|
46
|
-
return out.size ? out : new Set(["manual"]);
|
|
47
|
-
};
|
|
48
|
-
const triggers = parseTriggers(props.trigger ?? props.triggers ?? (hasOwn("open") ? "manual" : null));
|
|
49
|
-
const allowHover = triggers.has("hover");
|
|
50
|
-
const allowFocus = triggers.has("focus");
|
|
51
|
-
const allowClick = triggers.has("click");
|
|
52
|
-
const isManual = triggers.has("manual") || (!allowHover && !allowFocus && !allowClick);
|
|
53
|
-
|
|
54
|
-
const buildContent = () => {
|
|
55
|
-
const ctx = {
|
|
56
|
-
anchorEl: boundEl || targetNode || null,
|
|
57
|
-
close: () => closeNow(),
|
|
58
|
-
hide: () => closeNow(),
|
|
59
|
-
isOpen: () => !!entry
|
|
60
|
-
};
|
|
61
|
-
const iconFallback = props.icon
|
|
62
|
-
? (typeof props.icon === "string"
|
|
63
|
-
? UI.Icon({ name: props.icon, size: props.iconSize || props.size || "sm" })
|
|
64
|
-
: JSswift.ui.slot(props.icon, { as: "icon" }))
|
|
65
|
-
: null;
|
|
66
|
-
const titleNodes = renderSlotToArray(slots, "title", ctx, props.title ?? props.heading);
|
|
67
|
-
const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
|
|
68
|
-
const footerNodes = renderSlotToArray(slots, "footer", ctx, props.footer ?? props.actions);
|
|
69
|
-
|
|
70
|
-
let bodyRaw = props.content ?? props.text ?? props.body ?? props.description ?? props.label;
|
|
71
|
-
if (bodyRaw == null) {
|
|
72
|
-
if (hasExternalTarget) bodyRaw = children;
|
|
73
|
-
else if (targetNode && children && children.length > 1) bodyRaw = children.slice(1);
|
|
74
|
-
else if (!targetNode) bodyRaw = children;
|
|
75
|
-
}
|
|
76
|
-
let bodyNodes = renderSlotToArray(slots, "content", ctx, bodyRaw);
|
|
77
|
-
if (!bodyNodes.length) bodyNodes = renderSlotToArray(slots, "default", ctx, bodyRaw);
|
|
78
|
-
|
|
79
|
-
const bodyEl = bodyNodes.length ? _.div({ class: "cms-tooltip-body" }, ...bodyNodes) : null;
|
|
80
|
-
const headEl = (iconNodes.length || titleNodes.length)
|
|
81
|
-
? _.div(
|
|
82
|
-
{ class: "cms-tooltip-head" },
|
|
83
|
-
iconNodes.length ? _.div({ class: "cms-tooltip-icon" }, ...iconNodes) : null,
|
|
84
|
-
_.div(
|
|
85
|
-
{ class: "cms-tooltip-head-main" },
|
|
86
|
-
titleNodes.length ? _.div({ class: "cms-tooltip-title" }, ...titleNodes) : null,
|
|
87
|
-
bodyEl
|
|
88
|
-
)
|
|
89
|
-
)
|
|
90
|
-
: bodyEl;
|
|
91
|
-
const footerEl = footerNodes.length ? _.div({ class: "cms-tooltip-footer" }, ...footerNodes) : null;
|
|
92
|
-
|
|
93
|
-
return _.div({ class: "cms-tooltip-content" }, ...[headEl, footerEl].filter(Boolean));
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const open = (anchorEl) => {
|
|
97
|
-
const anchor = anchorEl || boundEl;
|
|
98
|
-
if (!anchor || uiUnwrap(props.disabled)) return null;
|
|
99
|
-
if (entry && entry._anchorEl === anchor) return entry;
|
|
100
|
-
if (entry) closeNow();
|
|
101
|
-
const shouldCloseOnOutside = props.closeOnOutside ?? (allowClick || uiUnwrap(props.interactive));
|
|
102
|
-
const shouldCloseOnEsc = props.closeOnEsc ?? (allowClick || uiUnwrap(props.interactive));
|
|
103
|
-
let currentRef = null;
|
|
104
|
-
entry = JSswift.overlay.open(() => buildContent(), {
|
|
105
|
-
type: "tooltip",
|
|
106
|
-
anchorEl: anchor,
|
|
107
|
-
placement: props.placement || "top",
|
|
108
|
-
offsetX: props.offsetX ?? 0,
|
|
109
|
-
offsetY: props.offsetY ?? props.offset ?? 8,
|
|
110
|
-
backdrop: false,
|
|
111
|
-
lockScroll: false,
|
|
112
|
-
trapFocus: false,
|
|
113
|
-
closeOnOutside: shouldCloseOnOutside,
|
|
114
|
-
closeOnBackdrop: false,
|
|
115
|
-
closeOnEsc: shouldCloseOnEsc,
|
|
116
|
-
autoFocus: false,
|
|
117
|
-
className: uiClassStatic([
|
|
118
|
-
"cms-tooltip",
|
|
119
|
-
uiWhen(props.interactive, "interactive"),
|
|
120
|
-
props.class,
|
|
121
|
-
props.panelClass
|
|
122
|
-
]),
|
|
123
|
-
onClose: () => {
|
|
124
|
-
currentRef?._tooltipCleanup?.();
|
|
125
|
-
if (entry === currentRef) entry = null;
|
|
126
|
-
props.onClose?.();
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
if (!entry?.panel) return entry;
|
|
130
|
-
const current = entry;
|
|
131
|
-
currentRef = current;
|
|
132
|
-
current._anchorEl = anchor;
|
|
133
|
-
current.panel.dataset.placement = String(uiUnwrap(props.placement || "top"));
|
|
134
|
-
if (props.maxWidth != null) current.panel.style.maxWidth = toCssSize(uiUnwrap(props.maxWidth));
|
|
135
|
-
if (props.minWidth != null) current.panel.style.minWidth = toCssSize(uiUnwrap(props.minWidth));
|
|
136
|
-
if (props.width != null) current.panel.style.width = toCssSize(uiUnwrap(props.width));
|
|
137
|
-
if (props.style) Object.assign(current.panel.style, props.style);
|
|
138
|
-
setPropertyProps(current.panel, props);
|
|
139
|
-
if (uiUnwrap(props.interactive)) {
|
|
140
|
-
const cancelHide = () => {
|
|
141
|
-
clearTimeout(closeTimer);
|
|
142
|
-
closeTimer = null;
|
|
143
|
-
};
|
|
144
|
-
const scheduleHide = () => hide();
|
|
145
|
-
const onPanelClick = (e) => {
|
|
146
|
-
const target = e.target;
|
|
147
|
-
if (target && target.closest && target.closest("[data-tooltip-close]")) {
|
|
148
|
-
closeNow();
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
current.panel.addEventListener("mouseenter", cancelHide);
|
|
152
|
-
current.panel.addEventListener("mouseleave", scheduleHide);
|
|
153
|
-
current.panel.addEventListener("focusin", cancelHide);
|
|
154
|
-
current.panel.addEventListener("focusout", scheduleHide);
|
|
155
|
-
current.panel.addEventListener("click", onPanelClick);
|
|
156
|
-
current._tooltipCleanup = () => {
|
|
157
|
-
current.panel?.removeEventListener("mouseenter", cancelHide);
|
|
158
|
-
current.panel?.removeEventListener("mouseleave", scheduleHide);
|
|
159
|
-
current.panel?.removeEventListener("focusin", cancelHide);
|
|
160
|
-
current.panel?.removeEventListener("focusout", scheduleHide);
|
|
161
|
-
current.panel?.removeEventListener("click", onPanelClick);
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
overlayEnter(current);
|
|
165
|
-
props.onOpen?.(current);
|
|
166
|
-
return current;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const show = (anchorEl) => {
|
|
170
|
-
clearTimeout(closeTimer);
|
|
171
|
-
closeTimer = null;
|
|
172
|
-
clearTimeout(openTimer);
|
|
173
|
-
openTimer = setTimeout(() => open(anchorEl), getDelay());
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const hide = (immediate = false) => {
|
|
177
|
-
clearTimeout(openTimer);
|
|
178
|
-
openTimer = null;
|
|
179
|
-
if (!entry) return;
|
|
180
|
-
clearTimeout(closeTimer);
|
|
181
|
-
if (immediate) {
|
|
182
|
-
closeNow();
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
closeTimer = setTimeout(() => closeNow(), getHideDelay());
|
|
186
|
-
};
|
|
187
|
-
const toggle = (anchorEl) => {
|
|
188
|
-
if (entry) hide(true);
|
|
189
|
-
else open(anchorEl);
|
|
190
|
-
};
|
|
191
|
-
const isOpen = () => !!entry;
|
|
192
|
-
|
|
193
|
-
const bind = (el) => {
|
|
194
|
-
if (!el) return () => { };
|
|
195
|
-
boundEl = el;
|
|
196
|
-
const cleanups = [];
|
|
197
|
-
const cleanup = () => {
|
|
198
|
-
clearTimers();
|
|
199
|
-
cleanups.forEach((fn) => fn());
|
|
200
|
-
};
|
|
201
|
-
if (hasOwn("open")) {
|
|
202
|
-
if (uiIsReactive(props.open)) {
|
|
203
|
-
JSswift.reactive.effect(() => {
|
|
204
|
-
if (!boundEl) return;
|
|
205
|
-
if (uiUnwrap(props.open)) open(boundEl);
|
|
206
|
-
else hide(true);
|
|
207
|
-
}, "UI.Tooltip:open");
|
|
208
|
-
} else if (props.open === true) {
|
|
209
|
-
open(el);
|
|
210
|
-
} else if (props.open === false) {
|
|
211
|
-
hide(true);
|
|
212
|
-
}
|
|
213
|
-
return cleanup;
|
|
214
|
-
}
|
|
215
|
-
if (isManual || uiUnwrap(props.disabled)) return cleanup;
|
|
216
|
-
if (allowHover) {
|
|
217
|
-
const onEnter = () => show(el);
|
|
218
|
-
const onLeave = () => hide();
|
|
219
|
-
el.addEventListener("mouseenter", onEnter);
|
|
220
|
-
el.addEventListener("mouseleave", onLeave);
|
|
221
|
-
cleanups.push(() => {
|
|
222
|
-
el.removeEventListener("mouseenter", onEnter);
|
|
223
|
-
el.removeEventListener("mouseleave", onLeave);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
if (allowFocus) {
|
|
227
|
-
const onFocus = () => show(el);
|
|
228
|
-
const onBlur = () => hide();
|
|
229
|
-
el.addEventListener("focus", onFocus);
|
|
230
|
-
el.addEventListener("blur", onBlur);
|
|
231
|
-
cleanups.push(() => {
|
|
232
|
-
el.removeEventListener("focus", onFocus);
|
|
233
|
-
el.removeEventListener("blur", onBlur);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
if (allowClick) {
|
|
237
|
-
const onClick = (e) => {
|
|
238
|
-
props.onTriggerClick?.(e);
|
|
239
|
-
if (e.defaultPrevented) return;
|
|
240
|
-
toggle(el);
|
|
241
|
-
};
|
|
242
|
-
el.addEventListener("click", onClick);
|
|
243
|
-
cleanups.push(() => el.removeEventListener("click", onClick));
|
|
244
|
-
}
|
|
245
|
-
return cleanup;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
if (targetNode) {
|
|
249
|
-
const cls = uiClass(["cms-tooltip-wrap", props.wrapClass, props.targetClass]);
|
|
250
|
-
const p = JSswift.omit(props, [
|
|
251
|
-
"actions", "anchorEl", "body", "closeOnEsc", "closeOnOutside", "content", "delay", "description",
|
|
252
|
-
"disabled", "footer", "heading", "hideDelay", "icon", "iconSize", "interactive", "label",
|
|
253
|
-
"maxWidth", "minWidth", "offset", "offsetX", "offsetY", "onClose", "onOpen", "onTriggerClick",
|
|
254
|
-
"open", "panelClass", "placement", "slots", "style", "target", "targetClass", "targetStyle",
|
|
255
|
-
"text", "title", "trigger", "triggerEl", "triggers", "width", "wrapClass", "wrapStyle"
|
|
256
|
-
]);
|
|
257
|
-
p.class = cls;
|
|
258
|
-
p.style = { display: "inline-flex", alignItems: "center", ...(props.wrapStyle || props.targetStyle || {}) };
|
|
259
|
-
const target = JSswift.ui.renderSlot(slots, "target", {
|
|
260
|
-
open: () => open(boundEl),
|
|
261
|
-
show: () => show(boundEl),
|
|
262
|
-
hide: () => hide(true),
|
|
263
|
-
toggle: () => toggle(boundEl),
|
|
264
|
-
isOpen
|
|
265
|
-
}, targetNode);
|
|
266
|
-
const wrap = _.span(p, ...renderSlotToArray(null, "default", {}, target));
|
|
267
|
-
bind(wrap);
|
|
268
|
-
wrap._tooltip = { bind, open, show, hide, close: closeNow, toggle, isOpen };
|
|
269
|
-
return wrap;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return { bind, open, show, hide, close: closeNow, toggle, isOpen };
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
signature: "UI.Tooltip(props, target?) | UI.Tooltip(target, content)",
|
|
278
|
-
props: {
|
|
279
|
-
title: "String|Node|Function|Array",
|
|
280
|
-
content: "String|Node|Function|Array",
|
|
281
|
-
text: "String|Node|Function|Array",
|
|
282
|
-
body: "String|Node|Function|Array",
|
|
283
|
-
footer: "String|Node|Function|Array",
|
|
284
|
-
icon: "String|Node|Function|Array",
|
|
285
|
-
target: "Node|Function|Array",
|
|
286
|
-
trigger: "\"hover focus\" | \"click\" | \"manual\" | Array",
|
|
287
|
-
interactive: "boolean",
|
|
288
|
-
disabled: "boolean",
|
|
289
|
-
open: "boolean | reactive",
|
|
290
|
-
placement: "string",
|
|
291
|
-
delay: "number",
|
|
292
|
-
hideDelay: "number",
|
|
293
|
-
offset: "number (legacy)",
|
|
294
|
-
offsetX: "number",
|
|
295
|
-
offsetY: "number",
|
|
296
|
-
closeOnOutside: "boolean",
|
|
297
|
-
closeOnEsc: "boolean",
|
|
298
|
-
maxWidth: "string|number",
|
|
299
|
-
minWidth: "string|number",
|
|
300
|
-
width: "string|number",
|
|
301
|
-
slots: "{ target?, icon?, title?, content?, footer?, default? }",
|
|
302
|
-
class: "string",
|
|
303
|
-
panelClass: "string",
|
|
304
|
-
wrapClass: "string",
|
|
305
|
-
style: "object",
|
|
306
|
-
targetStyle: "object"
|
|
307
|
-
},
|
|
308
|
-
slots: {
|
|
309
|
-
target: "Tooltip trigger content ({ open, show, hide, toggle, isOpen })",
|
|
310
|
-
icon: "Tooltip icon content",
|
|
311
|
-
title: "Tooltip title content",
|
|
312
|
-
content: "Tooltip body content ({ close, hide, isOpen, anchorEl })",
|
|
313
|
-
footer: "Tooltip footer content ({ close, hide, isOpen, anchorEl })",
|
|
314
|
-
default: "Fallback tooltip body content"
|
|
315
|
-
},
|
|
316
|
-
events: {
|
|
317
|
-
onOpen: "(entry) => void",
|
|
318
|
-
onClose: "() => void",
|
|
319
|
-
onTriggerClick: "(event) => void"
|
|
320
|
-
},
|
|
321
|
-
returns: "Object { bind(), open(), show(), hide(), close(), toggle(), isOpen() } | HTMLSpanElement",
|
|
322
|
-
description: "Anchored tooltip with hover/focus/click triggers, rich content, and imperative API."
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
// Esempio: JSswift.ui.Tooltip({ title: "Info", text: "Dettaglio rapido" }, JSswift.ui.Icon({ name: "info" }))
|
|
326
|
-
|
|
1
|
+
UI.Tooltip = (...args) => {
|
|
2
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
+
const slots = props.slots || {};
|
|
4
|
+
let entry = null;
|
|
5
|
+
let openTimer = null;
|
|
6
|
+
let closeTimer = null;
|
|
7
|
+
let boundEl = null;
|
|
8
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(props, key);
|
|
9
|
+
const hasExternalTarget = hasOwn("target") || hasOwn("anchorEl") || hasOwn("triggerEl");
|
|
10
|
+
const targetNode = props.target || props.anchorEl || props.triggerEl || (children && children.length ? children[0] : null);
|
|
11
|
+
|
|
12
|
+
const getNumber = (value, fallback) => {
|
|
13
|
+
const raw = uiUnwrap(value);
|
|
14
|
+
if (raw == null || raw === "") return fallback;
|
|
15
|
+
const n = Number(raw);
|
|
16
|
+
return Number.isFinite(n) ? n : fallback;
|
|
17
|
+
};
|
|
18
|
+
const clearTimers = () => {
|
|
19
|
+
clearTimeout(openTimer);
|
|
20
|
+
clearTimeout(closeTimer);
|
|
21
|
+
openTimer = null;
|
|
22
|
+
closeTimer = null;
|
|
23
|
+
};
|
|
24
|
+
const getDelay = () => getNumber(props.delay, 350);
|
|
25
|
+
const getHideDelay = () => getNumber(props.hideDelay, uiUnwrap(props.interactive) ? 120 : 0);
|
|
26
|
+
const closeNow = () => {
|
|
27
|
+
clearTimers();
|
|
28
|
+
if (!entry) return;
|
|
29
|
+
const toClose = entry;
|
|
30
|
+
entry = null;
|
|
31
|
+
overlayLeave(toClose, () => JSswift.overlay.close(toClose.id));
|
|
32
|
+
};
|
|
33
|
+
const parseTriggers = (value) => {
|
|
34
|
+
if (value == null || value === true) return new Set(["hover", "focus"]);
|
|
35
|
+
if (value === false) return new Set(["manual"]);
|
|
36
|
+
const raw = Array.isArray(value) ? value : String(value).split(/[\s,|/]+/);
|
|
37
|
+
const out = new Set();
|
|
38
|
+
raw.forEach((item) => {
|
|
39
|
+
const key = String(item || "").trim().toLowerCase();
|
|
40
|
+
if (!key) return;
|
|
41
|
+
if (key === "mouseenter" || key === "mouseover") out.add("hover");
|
|
42
|
+
else if (key === "blur" || key === "focusin") out.add("focus");
|
|
43
|
+
else if (key === "tap") out.add("click");
|
|
44
|
+
else out.add(key);
|
|
45
|
+
});
|
|
46
|
+
return out.size ? out : new Set(["manual"]);
|
|
47
|
+
};
|
|
48
|
+
const triggers = parseTriggers(props.trigger ?? props.triggers ?? (hasOwn("open") ? "manual" : null));
|
|
49
|
+
const allowHover = triggers.has("hover");
|
|
50
|
+
const allowFocus = triggers.has("focus");
|
|
51
|
+
const allowClick = triggers.has("click");
|
|
52
|
+
const isManual = triggers.has("manual") || (!allowHover && !allowFocus && !allowClick);
|
|
53
|
+
|
|
54
|
+
const buildContent = () => {
|
|
55
|
+
const ctx = {
|
|
56
|
+
anchorEl: boundEl || targetNode || null,
|
|
57
|
+
close: () => closeNow(),
|
|
58
|
+
hide: () => closeNow(),
|
|
59
|
+
isOpen: () => !!entry
|
|
60
|
+
};
|
|
61
|
+
const iconFallback = props.icon
|
|
62
|
+
? (typeof props.icon === "string"
|
|
63
|
+
? UI.Icon({ name: props.icon, size: props.iconSize || props.size || "sm" })
|
|
64
|
+
: JSswift.ui.slot(props.icon, { as: "icon" }))
|
|
65
|
+
: null;
|
|
66
|
+
const titleNodes = renderSlotToArray(slots, "title", ctx, props.title ?? props.heading);
|
|
67
|
+
const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
|
|
68
|
+
const footerNodes = renderSlotToArray(slots, "footer", ctx, props.footer ?? props.actions);
|
|
69
|
+
|
|
70
|
+
let bodyRaw = props.content ?? props.text ?? props.body ?? props.description ?? props.label;
|
|
71
|
+
if (bodyRaw == null) {
|
|
72
|
+
if (hasExternalTarget) bodyRaw = children;
|
|
73
|
+
else if (targetNode && children && children.length > 1) bodyRaw = children.slice(1);
|
|
74
|
+
else if (!targetNode) bodyRaw = children;
|
|
75
|
+
}
|
|
76
|
+
let bodyNodes = renderSlotToArray(slots, "content", ctx, bodyRaw);
|
|
77
|
+
if (!bodyNodes.length) bodyNodes = renderSlotToArray(slots, "default", ctx, bodyRaw);
|
|
78
|
+
|
|
79
|
+
const bodyEl = bodyNodes.length ? _.div({ class: "cms-tooltip-body" }, ...bodyNodes) : null;
|
|
80
|
+
const headEl = (iconNodes.length || titleNodes.length)
|
|
81
|
+
? _.div(
|
|
82
|
+
{ class: "cms-tooltip-head" },
|
|
83
|
+
iconNodes.length ? _.div({ class: "cms-tooltip-icon" }, ...iconNodes) : null,
|
|
84
|
+
_.div(
|
|
85
|
+
{ class: "cms-tooltip-head-main" },
|
|
86
|
+
titleNodes.length ? _.div({ class: "cms-tooltip-title" }, ...titleNodes) : null,
|
|
87
|
+
bodyEl
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
: bodyEl;
|
|
91
|
+
const footerEl = footerNodes.length ? _.div({ class: "cms-tooltip-footer" }, ...footerNodes) : null;
|
|
92
|
+
|
|
93
|
+
return _.div({ class: "cms-tooltip-content" }, ...[headEl, footerEl].filter(Boolean));
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const open = (anchorEl) => {
|
|
97
|
+
const anchor = anchorEl || boundEl;
|
|
98
|
+
if (!anchor || uiUnwrap(props.disabled)) return null;
|
|
99
|
+
if (entry && entry._anchorEl === anchor) return entry;
|
|
100
|
+
if (entry) closeNow();
|
|
101
|
+
const shouldCloseOnOutside = props.closeOnOutside ?? (allowClick || uiUnwrap(props.interactive));
|
|
102
|
+
const shouldCloseOnEsc = props.closeOnEsc ?? (allowClick || uiUnwrap(props.interactive));
|
|
103
|
+
let currentRef = null;
|
|
104
|
+
entry = JSswift.overlay.open(() => buildContent(), {
|
|
105
|
+
type: "tooltip",
|
|
106
|
+
anchorEl: anchor,
|
|
107
|
+
placement: props.placement || "top",
|
|
108
|
+
offsetX: props.offsetX ?? 0,
|
|
109
|
+
offsetY: props.offsetY ?? props.offset ?? 8,
|
|
110
|
+
backdrop: false,
|
|
111
|
+
lockScroll: false,
|
|
112
|
+
trapFocus: false,
|
|
113
|
+
closeOnOutside: shouldCloseOnOutside,
|
|
114
|
+
closeOnBackdrop: false,
|
|
115
|
+
closeOnEsc: shouldCloseOnEsc,
|
|
116
|
+
autoFocus: false,
|
|
117
|
+
className: uiClassStatic([
|
|
118
|
+
"cms-tooltip",
|
|
119
|
+
uiWhen(props.interactive, "interactive"),
|
|
120
|
+
props.class,
|
|
121
|
+
props.panelClass
|
|
122
|
+
]),
|
|
123
|
+
onClose: () => {
|
|
124
|
+
currentRef?._tooltipCleanup?.();
|
|
125
|
+
if (entry === currentRef) entry = null;
|
|
126
|
+
props.onClose?.();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
if (!entry?.panel) return entry;
|
|
130
|
+
const current = entry;
|
|
131
|
+
currentRef = current;
|
|
132
|
+
current._anchorEl = anchor;
|
|
133
|
+
current.panel.dataset.placement = String(uiUnwrap(props.placement || "top"));
|
|
134
|
+
if (props.maxWidth != null) current.panel.style.maxWidth = toCssSize(uiUnwrap(props.maxWidth));
|
|
135
|
+
if (props.minWidth != null) current.panel.style.minWidth = toCssSize(uiUnwrap(props.minWidth));
|
|
136
|
+
if (props.width != null) current.panel.style.width = toCssSize(uiUnwrap(props.width));
|
|
137
|
+
if (props.style) Object.assign(current.panel.style, props.style);
|
|
138
|
+
setPropertyProps(current.panel, props);
|
|
139
|
+
if (uiUnwrap(props.interactive)) {
|
|
140
|
+
const cancelHide = () => {
|
|
141
|
+
clearTimeout(closeTimer);
|
|
142
|
+
closeTimer = null;
|
|
143
|
+
};
|
|
144
|
+
const scheduleHide = () => hide();
|
|
145
|
+
const onPanelClick = (e) => {
|
|
146
|
+
const target = e.target;
|
|
147
|
+
if (target && target.closest && target.closest("[data-tooltip-close]")) {
|
|
148
|
+
closeNow();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
current.panel.addEventListener("mouseenter", cancelHide);
|
|
152
|
+
current.panel.addEventListener("mouseleave", scheduleHide);
|
|
153
|
+
current.panel.addEventListener("focusin", cancelHide);
|
|
154
|
+
current.panel.addEventListener("focusout", scheduleHide);
|
|
155
|
+
current.panel.addEventListener("click", onPanelClick);
|
|
156
|
+
current._tooltipCleanup = () => {
|
|
157
|
+
current.panel?.removeEventListener("mouseenter", cancelHide);
|
|
158
|
+
current.panel?.removeEventListener("mouseleave", scheduleHide);
|
|
159
|
+
current.panel?.removeEventListener("focusin", cancelHide);
|
|
160
|
+
current.panel?.removeEventListener("focusout", scheduleHide);
|
|
161
|
+
current.panel?.removeEventListener("click", onPanelClick);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
overlayEnter(current);
|
|
165
|
+
props.onOpen?.(current);
|
|
166
|
+
return current;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const show = (anchorEl) => {
|
|
170
|
+
clearTimeout(closeTimer);
|
|
171
|
+
closeTimer = null;
|
|
172
|
+
clearTimeout(openTimer);
|
|
173
|
+
openTimer = setTimeout(() => open(anchorEl), getDelay());
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const hide = (immediate = false) => {
|
|
177
|
+
clearTimeout(openTimer);
|
|
178
|
+
openTimer = null;
|
|
179
|
+
if (!entry) return;
|
|
180
|
+
clearTimeout(closeTimer);
|
|
181
|
+
if (immediate) {
|
|
182
|
+
closeNow();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
closeTimer = setTimeout(() => closeNow(), getHideDelay());
|
|
186
|
+
};
|
|
187
|
+
const toggle = (anchorEl) => {
|
|
188
|
+
if (entry) hide(true);
|
|
189
|
+
else open(anchorEl);
|
|
190
|
+
};
|
|
191
|
+
const isOpen = () => !!entry;
|
|
192
|
+
|
|
193
|
+
const bind = (el) => {
|
|
194
|
+
if (!el) return () => { };
|
|
195
|
+
boundEl = el;
|
|
196
|
+
const cleanups = [];
|
|
197
|
+
const cleanup = () => {
|
|
198
|
+
clearTimers();
|
|
199
|
+
cleanups.forEach((fn) => fn());
|
|
200
|
+
};
|
|
201
|
+
if (hasOwn("open")) {
|
|
202
|
+
if (uiIsReactive(props.open)) {
|
|
203
|
+
JSswift.reactive.effect(() => {
|
|
204
|
+
if (!boundEl) return;
|
|
205
|
+
if (uiUnwrap(props.open)) open(boundEl);
|
|
206
|
+
else hide(true);
|
|
207
|
+
}, "UI.Tooltip:open");
|
|
208
|
+
} else if (props.open === true) {
|
|
209
|
+
open(el);
|
|
210
|
+
} else if (props.open === false) {
|
|
211
|
+
hide(true);
|
|
212
|
+
}
|
|
213
|
+
return cleanup;
|
|
214
|
+
}
|
|
215
|
+
if (isManual || uiUnwrap(props.disabled)) return cleanup;
|
|
216
|
+
if (allowHover) {
|
|
217
|
+
const onEnter = () => show(el);
|
|
218
|
+
const onLeave = () => hide();
|
|
219
|
+
el.addEventListener("mouseenter", onEnter);
|
|
220
|
+
el.addEventListener("mouseleave", onLeave);
|
|
221
|
+
cleanups.push(() => {
|
|
222
|
+
el.removeEventListener("mouseenter", onEnter);
|
|
223
|
+
el.removeEventListener("mouseleave", onLeave);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (allowFocus) {
|
|
227
|
+
const onFocus = () => show(el);
|
|
228
|
+
const onBlur = () => hide();
|
|
229
|
+
el.addEventListener("focus", onFocus);
|
|
230
|
+
el.addEventListener("blur", onBlur);
|
|
231
|
+
cleanups.push(() => {
|
|
232
|
+
el.removeEventListener("focus", onFocus);
|
|
233
|
+
el.removeEventListener("blur", onBlur);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (allowClick) {
|
|
237
|
+
const onClick = (e) => {
|
|
238
|
+
props.onTriggerClick?.(e);
|
|
239
|
+
if (e.defaultPrevented) return;
|
|
240
|
+
toggle(el);
|
|
241
|
+
};
|
|
242
|
+
el.addEventListener("click", onClick);
|
|
243
|
+
cleanups.push(() => el.removeEventListener("click", onClick));
|
|
244
|
+
}
|
|
245
|
+
return cleanup;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (targetNode) {
|
|
249
|
+
const cls = uiClass(["cms-tooltip-wrap", props.wrapClass, props.targetClass]);
|
|
250
|
+
const p = JSswift.omit(props, [
|
|
251
|
+
"actions", "anchorEl", "body", "closeOnEsc", "closeOnOutside", "content", "delay", "description",
|
|
252
|
+
"disabled", "footer", "heading", "hideDelay", "icon", "iconSize", "interactive", "label",
|
|
253
|
+
"maxWidth", "minWidth", "offset", "offsetX", "offsetY", "onClose", "onOpen", "onTriggerClick",
|
|
254
|
+
"open", "panelClass", "placement", "slots", "style", "target", "targetClass", "targetStyle",
|
|
255
|
+
"text", "title", "trigger", "triggerEl", "triggers", "width", "wrapClass", "wrapStyle"
|
|
256
|
+
]);
|
|
257
|
+
p.class = cls;
|
|
258
|
+
p.style = { display: "inline-flex", alignItems: "center", ...(props.wrapStyle || props.targetStyle || {}) };
|
|
259
|
+
const target = JSswift.ui.renderSlot(slots, "target", {
|
|
260
|
+
open: () => open(boundEl),
|
|
261
|
+
show: () => show(boundEl),
|
|
262
|
+
hide: () => hide(true),
|
|
263
|
+
toggle: () => toggle(boundEl),
|
|
264
|
+
isOpen
|
|
265
|
+
}, targetNode);
|
|
266
|
+
const wrap = _.span(p, ...renderSlotToArray(null, "default", {}, target));
|
|
267
|
+
bind(wrap);
|
|
268
|
+
wrap._tooltip = { bind, open, show, hide, close: closeNow, toggle, isOpen };
|
|
269
|
+
return wrap;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { bind, open, show, hide, close: closeNow, toggle, isOpen };
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Esempio: JSswift.ui.Tooltip({ title: "Info", text: "Dettaglio rapido" }, JSswift.ui.Icon({ name: "info" }))
|
|
276
|
+
|