@studious-creative/yumekit 0.1.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/dist/components/y-appbar.js +1378 -0
- package/dist/components/y-avatar.js +104 -0
- package/dist/components/y-badge.js +149 -0
- package/dist/components/y-button.js +550 -0
- package/dist/components/y-card.js +178 -0
- package/dist/components/y-checkbox.js +246 -0
- package/dist/components/y-dialog.js +213 -0
- package/dist/components/y-drawer.js +454 -0
- package/dist/components/y-input.js +225 -0
- package/dist/components/y-menu.js +299 -0
- package/dist/components/y-panel.js +350 -0
- package/dist/components/y-panelbar.js +27 -0
- package/dist/components/y-progress.js +317 -0
- package/dist/components/y-radio.js +202 -0
- package/dist/components/y-select.js +484 -0
- package/dist/components/y-slider.js +387 -0
- package/dist/components/y-switch.js +353 -0
- package/dist/components/y-table.js +323 -0
- package/dist/components/y-tabs.js +283 -0
- package/dist/components/y-tag.js +186 -0
- package/dist/components/y-theme.js +84 -0
- package/dist/components/y-toast.js +335 -0
- package/dist/components/y-tooltip.js +320 -0
- package/dist/index.js +6442 -0
- package/dist/modules/helpers.js +98 -0
- package/dist/styles/blue-dark.css +123 -0
- package/dist/styles/blue-light.css +123 -0
- package/dist/styles/orange-dark.css +123 -0
- package/dist/styles/orange-light.css +123 -0
- package/dist/styles/style.css +20 -0
- package/dist/styles/variables.css +397 -0
- package/dist/yumekit.min.js +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// helpers/slot-utils.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a CSS custom-property value to a concrete color string.
|
|
5
|
+
* Reads from the given element's computed style.
|
|
6
|
+
* @param {string} varExpr — e.g. "var(--primary-content--)"
|
|
7
|
+
* @param {HTMLElement} el — element to resolve against
|
|
8
|
+
* @returns {string} — resolved color or fallback
|
|
9
|
+
*/
|
|
10
|
+
function resolveCSSColor(varExpr, el) {
|
|
11
|
+
const match = varExpr.match(/var\(\s*(--[^,)]+)/);
|
|
12
|
+
if (!match) return varExpr;
|
|
13
|
+
const val = getComputedStyle(el).getPropertyValue(match[1]).trim();
|
|
14
|
+
return val || varExpr;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse a CSS color string (#hex, rgb(), etc.) to {r, g, b}.
|
|
19
|
+
* Returns null if it can't parse.
|
|
20
|
+
*/
|
|
21
|
+
function parseColor(colorStr) {
|
|
22
|
+
// #RGB, #RRGGBB, #RRGGBBAA
|
|
23
|
+
const hexMatch = colorStr.match(
|
|
24
|
+
/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,
|
|
25
|
+
);
|
|
26
|
+
if (hexMatch) {
|
|
27
|
+
let hex = hexMatch[1];
|
|
28
|
+
if (hex.length <= 4) {
|
|
29
|
+
hex = hex
|
|
30
|
+
.split("")
|
|
31
|
+
.map((c) => c + c)
|
|
32
|
+
.join("");
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
36
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
37
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// rgb(r, g, b) or rgba(r, g, b, a)
|
|
41
|
+
const rgbMatch = colorStr.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
42
|
+
if (rgbMatch) {
|
|
43
|
+
return {
|
|
44
|
+
r: parseInt(rgbMatch[1], 10),
|
|
45
|
+
g: parseInt(rgbMatch[2], 10),
|
|
46
|
+
b: parseInt(rgbMatch[3], 10),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compute relative luminance of an {r,g,b} color (0-255 range).
|
|
54
|
+
* Returns a value between 0 (black) and 1 (white).
|
|
55
|
+
*/
|
|
56
|
+
function luminance({ r, g, b }) {
|
|
57
|
+
const [rs, gs, bs] = [r, g, b].map((c) => {
|
|
58
|
+
c /= 255;
|
|
59
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
60
|
+
});
|
|
61
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Given a background color string, return a CSS value for best contrast text.
|
|
66
|
+
* Uses WCAG relative luminance to pick dark or light, referencing theme tokens
|
|
67
|
+
* (--neutral-black / --neutral-white) with hardcoded fallbacks.
|
|
68
|
+
* @param {string} bgColor — any CSS color string (#hex, rgb(), etc.)
|
|
69
|
+
* @returns {string} CSS var() expression for contrasting text color
|
|
70
|
+
*/
|
|
71
|
+
function contrastTextColor(bgColor) {
|
|
72
|
+
const parsed = parseColor(bgColor);
|
|
73
|
+
if (!parsed) return "var(--neutral-white, #ffffff)";
|
|
74
|
+
return luminance(parsed) > 0.179
|
|
75
|
+
? "var(--neutral-black, #000000)"
|
|
76
|
+
: "var(--neutral-white, #ffffff)";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class YumeToast extends HTMLElement {
|
|
80
|
+
static get observedAttributes() {
|
|
81
|
+
return ["position", "duration", "max"];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
constructor() {
|
|
85
|
+
super();
|
|
86
|
+
this.attachShadow({ mode: "open" });
|
|
87
|
+
this._queue = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
connectedCallback() {
|
|
91
|
+
this.render();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
95
|
+
if (oldVal === newVal) return;
|
|
96
|
+
this.render();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get position() {
|
|
100
|
+
return this.getAttribute("position") || "bottom-right";
|
|
101
|
+
}
|
|
102
|
+
set position(val) {
|
|
103
|
+
this.setAttribute("position", val);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get duration() {
|
|
107
|
+
return parseInt(this.getAttribute("duration") ?? "4000", 10);
|
|
108
|
+
}
|
|
109
|
+
set duration(val) {
|
|
110
|
+
this.setAttribute("duration", String(val));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get max() {
|
|
114
|
+
return parseInt(this.getAttribute("max") ?? "5", 10);
|
|
115
|
+
}
|
|
116
|
+
set max(val) {
|
|
117
|
+
this.setAttribute("max", String(val));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Show a toast notification.
|
|
122
|
+
* @param {Object} opts
|
|
123
|
+
* @param {string} opts.message — Required text content.
|
|
124
|
+
* @param {string} [opts.color] — base|primary|secondary|success|warning|error|help (default base).
|
|
125
|
+
* @param {number} [opts.duration] — Override container-level duration for this toast.
|
|
126
|
+
* @param {boolean} [opts.dismissible] — Show a close button (default true).
|
|
127
|
+
* @param {string} [opts.icon] — Optional Font Awesome class e.g. "fas fa-check".
|
|
128
|
+
* @returns {HTMLElement} The toast element (for manual removal).
|
|
129
|
+
*/
|
|
130
|
+
show(opts = {}) {
|
|
131
|
+
const {
|
|
132
|
+
message = "",
|
|
133
|
+
color = "base",
|
|
134
|
+
duration = this.duration,
|
|
135
|
+
dismissible = true,
|
|
136
|
+
icon = null,
|
|
137
|
+
} = opts;
|
|
138
|
+
|
|
139
|
+
const container = this.shadowRoot.querySelector(".toast-container");
|
|
140
|
+
if (!container) return null;
|
|
141
|
+
|
|
142
|
+
const existing = container.querySelectorAll(".toast");
|
|
143
|
+
if (existing.length >= this.max) {
|
|
144
|
+
this._removeToast(existing[0]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const toast = document.createElement("div");
|
|
148
|
+
toast.className = `toast color-${color}`;
|
|
149
|
+
toast.setAttribute("role", "alert");
|
|
150
|
+
toast.setAttribute("aria-live", "assertive");
|
|
151
|
+
|
|
152
|
+
const bgVar = this._getColorBg(color);
|
|
153
|
+
const resolvedBg = resolveCSSColor(bgVar, this);
|
|
154
|
+
const textColor = contrastTextColor(resolvedBg);
|
|
155
|
+
toast.style.backgroundColor = bgVar;
|
|
156
|
+
toast.style.color = textColor;
|
|
157
|
+
|
|
158
|
+
if (icon) {
|
|
159
|
+
const iconEl = document.createElement("i");
|
|
160
|
+
iconEl.className = `toast-icon ${icon}`;
|
|
161
|
+
toast.appendChild(iconEl);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const msg = document.createElement("span");
|
|
165
|
+
msg.className = "toast-message";
|
|
166
|
+
msg.textContent = message;
|
|
167
|
+
toast.appendChild(msg);
|
|
168
|
+
|
|
169
|
+
if (dismissible) {
|
|
170
|
+
const btn = document.createElement("button");
|
|
171
|
+
btn.className = "toast-close";
|
|
172
|
+
btn.setAttribute("aria-label", "Dismiss");
|
|
173
|
+
btn.innerHTML = "×";
|
|
174
|
+
btn.addEventListener("click", () => this._removeToast(toast));
|
|
175
|
+
toast.appendChild(btn);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
container.appendChild(toast);
|
|
179
|
+
|
|
180
|
+
// Trigger enter animation (next frame)
|
|
181
|
+
requestAnimationFrame(() => {
|
|
182
|
+
toast.classList.add("visible");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (duration > 0) {
|
|
186
|
+
toast._timeout = setTimeout(
|
|
187
|
+
() => this._removeToast(toast),
|
|
188
|
+
duration,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.dispatchEvent(
|
|
193
|
+
new CustomEvent("y-toast-show", {
|
|
194
|
+
detail: { message, color },
|
|
195
|
+
bubbles: true,
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return toast;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
clear() {
|
|
203
|
+
const container = this.shadowRoot.querySelector(".toast-container");
|
|
204
|
+
if (!container) return;
|
|
205
|
+
container
|
|
206
|
+
.querySelectorAll(".toast")
|
|
207
|
+
.forEach((t) => this._removeToast(t));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_removeToast(toast) {
|
|
211
|
+
if (!toast || toast._removing) return;
|
|
212
|
+
toast._removing = true;
|
|
213
|
+
clearTimeout(toast._timeout);
|
|
214
|
+
toast.classList.remove("visible");
|
|
215
|
+
toast.classList.add("exit");
|
|
216
|
+
toast.addEventListener(
|
|
217
|
+
"transitionend",
|
|
218
|
+
() => {
|
|
219
|
+
toast.remove();
|
|
220
|
+
this.dispatchEvent(
|
|
221
|
+
new CustomEvent("y-toast-dismiss", { bubbles: true }),
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
{ once: true },
|
|
225
|
+
);
|
|
226
|
+
// Fallback if transition doesn't fire
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
if (toast.parentNode) {
|
|
229
|
+
toast.remove();
|
|
230
|
+
}
|
|
231
|
+
}, 350);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_getPositionStyles() {
|
|
235
|
+
const pos = this.position;
|
|
236
|
+
const base = `position: fixed; z-index: 10000; display: flex; flex-direction: column; gap: var(--spacing-small, 6px); pointer-events: none; max-width: 420px; min-width: 280px;`;
|
|
237
|
+
const pad = `var(--component-toast-offset, var(--spacing-x-large, 16px))`;
|
|
238
|
+
|
|
239
|
+
const map = {
|
|
240
|
+
"top-right": `${base} top: ${pad}; right: ${pad}; align-items: flex-end;`,
|
|
241
|
+
"top-left": `${base} top: ${pad}; left: ${pad}; align-items: flex-start;`,
|
|
242
|
+
"top-center": `${base} top: ${pad}; left: 50%; transform: translateX(-50%); align-items: center;`,
|
|
243
|
+
"bottom-right": `${base} bottom: ${pad}; right: ${pad}; align-items: flex-end;`,
|
|
244
|
+
"bottom-left": `${base} bottom: ${pad}; left: ${pad}; align-items: flex-start;`,
|
|
245
|
+
"bottom-center": `${base} bottom: ${pad}; left: 50%; transform: translateX(-50%); align-items: center;`,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return map[pos] || map["bottom-right"];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// "base" inverts the page: light content on dark, dark on light.
|
|
252
|
+
_getColorBg(color) {
|
|
253
|
+
const map = {
|
|
254
|
+
base: "var(--base-content--, #fff)",
|
|
255
|
+
primary: "var(--primary-content--, #0070f3)",
|
|
256
|
+
secondary: "var(--secondary-content--, #6c757d)",
|
|
257
|
+
success: "var(--success-content--, #28a745)",
|
|
258
|
+
warning: "var(--warning-content--, #ffc107)",
|
|
259
|
+
error: "var(--error-content--, #dc3545)",
|
|
260
|
+
help: "var(--help-content--, #6f42c1)",
|
|
261
|
+
};
|
|
262
|
+
return map[color] || map.base;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
render() {
|
|
266
|
+
this.shadowRoot.innerHTML = `
|
|
267
|
+
<style>
|
|
268
|
+
:host {
|
|
269
|
+
font-family: var(--font-family-body, sans-serif);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.toast-container {
|
|
273
|
+
${this._getPositionStyles()}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.toast {
|
|
277
|
+
pointer-events: auto;
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
gap: var(--spacing-medium, 8px);
|
|
281
|
+
padding: var(--component-toast-padding, var(--spacing-medium, 8px));
|
|
282
|
+
border-radius: var(--component-toast-border-radius, var(--radii-small, 4px));
|
|
283
|
+
font-size: var(--font-size-paragraph, 1em);
|
|
284
|
+
line-height: 1.4;
|
|
285
|
+
opacity: 0;
|
|
286
|
+
transform: translateY(8px);
|
|
287
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
288
|
+
box-shadow: var(--base-shadow, 0 2px 6px rgba(0,0,0,0.15));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.toast.visible {
|
|
292
|
+
opacity: 1;
|
|
293
|
+
transform: translateY(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.toast.exit {
|
|
297
|
+
opacity: 0;
|
|
298
|
+
transform: translateY(-8px);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.toast-icon {
|
|
302
|
+
flex-shrink: 0;
|
|
303
|
+
font-size: 1.1em;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.toast-message {
|
|
307
|
+
flex: 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.toast-close {
|
|
311
|
+
flex-shrink: 0;
|
|
312
|
+
align-self: flex-start;
|
|
313
|
+
background: none;
|
|
314
|
+
border: none;
|
|
315
|
+
color: inherit;
|
|
316
|
+
font-size: 1.2em;
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
padding: 0 0 0 var(--spacing-small, 6px);
|
|
319
|
+
opacity: 0.7;
|
|
320
|
+
line-height: 1;
|
|
321
|
+
}
|
|
322
|
+
.toast-close:hover {
|
|
323
|
+
opacity: 1;
|
|
324
|
+
}
|
|
325
|
+
</style>
|
|
326
|
+
<div class="toast-container"></div>
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!customElements.get("y-toast")) {
|
|
332
|
+
customElements.define("y-toast", YumeToast);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export { YumeToast };
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// helpers/slot-utils.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a CSS custom-property value to a concrete color string.
|
|
5
|
+
* Reads from the given element's computed style.
|
|
6
|
+
* @param {string} varExpr — e.g. "var(--primary-content--)"
|
|
7
|
+
* @param {HTMLElement} el — element to resolve against
|
|
8
|
+
* @returns {string} — resolved color or fallback
|
|
9
|
+
*/
|
|
10
|
+
function resolveCSSColor(varExpr, el) {
|
|
11
|
+
const match = varExpr.match(/var\(\s*(--[^,)]+)/);
|
|
12
|
+
if (!match) return varExpr;
|
|
13
|
+
const val = getComputedStyle(el).getPropertyValue(match[1]).trim();
|
|
14
|
+
return val || varExpr;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse a CSS color string (#hex, rgb(), etc.) to {r, g, b}.
|
|
19
|
+
* Returns null if it can't parse.
|
|
20
|
+
*/
|
|
21
|
+
function parseColor(colorStr) {
|
|
22
|
+
// #RGB, #RRGGBB, #RRGGBBAA
|
|
23
|
+
const hexMatch = colorStr.match(
|
|
24
|
+
/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,
|
|
25
|
+
);
|
|
26
|
+
if (hexMatch) {
|
|
27
|
+
let hex = hexMatch[1];
|
|
28
|
+
if (hex.length <= 4) {
|
|
29
|
+
hex = hex
|
|
30
|
+
.split("")
|
|
31
|
+
.map((c) => c + c)
|
|
32
|
+
.join("");
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
36
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
37
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// rgb(r, g, b) or rgba(r, g, b, a)
|
|
41
|
+
const rgbMatch = colorStr.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
42
|
+
if (rgbMatch) {
|
|
43
|
+
return {
|
|
44
|
+
r: parseInt(rgbMatch[1], 10),
|
|
45
|
+
g: parseInt(rgbMatch[2], 10),
|
|
46
|
+
b: parseInt(rgbMatch[3], 10),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compute relative luminance of an {r,g,b} color (0-255 range).
|
|
54
|
+
* Returns a value between 0 (black) and 1 (white).
|
|
55
|
+
*/
|
|
56
|
+
function luminance({ r, g, b }) {
|
|
57
|
+
const [rs, gs, bs] = [r, g, b].map((c) => {
|
|
58
|
+
c /= 255;
|
|
59
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
60
|
+
});
|
|
61
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Given a background color string, return a CSS value for best contrast text.
|
|
66
|
+
* Uses WCAG relative luminance to pick dark or light, referencing theme tokens
|
|
67
|
+
* (--neutral-black / --neutral-white) with hardcoded fallbacks.
|
|
68
|
+
* @param {string} bgColor — any CSS color string (#hex, rgb(), etc.)
|
|
69
|
+
* @returns {string} CSS var() expression for contrasting text color
|
|
70
|
+
*/
|
|
71
|
+
function contrastTextColor(bgColor) {
|
|
72
|
+
const parsed = parseColor(bgColor);
|
|
73
|
+
if (!parsed) return "var(--neutral-white, #ffffff)";
|
|
74
|
+
return luminance(parsed) > 0.179
|
|
75
|
+
? "var(--neutral-black, #000000)"
|
|
76
|
+
: "var(--neutral-white, #ffffff)";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class YumeTooltip extends HTMLElement {
|
|
80
|
+
static get observedAttributes() {
|
|
81
|
+
return ["text", "position", "delay", "color"];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
constructor() {
|
|
85
|
+
super();
|
|
86
|
+
this.attachShadow({ mode: "open" });
|
|
87
|
+
this._showTimeout = null;
|
|
88
|
+
this._hideTimeout = null;
|
|
89
|
+
this._visible = false;
|
|
90
|
+
this._onMouseEnter = this._onMouseEnter.bind(this);
|
|
91
|
+
this._onMouseLeave = this._onMouseLeave.bind(this);
|
|
92
|
+
this._onFocusIn = this._onFocusIn.bind(this);
|
|
93
|
+
this._onFocusOut = this._onFocusOut.bind(this);
|
|
94
|
+
this._onKeyDown = this._onKeyDown.bind(this);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
connectedCallback() {
|
|
98
|
+
this.render();
|
|
99
|
+
this.addEventListener("mouseenter", this._onMouseEnter);
|
|
100
|
+
this.addEventListener("mouseleave", this._onMouseLeave);
|
|
101
|
+
this.addEventListener("focusin", this._onFocusIn);
|
|
102
|
+
this.addEventListener("focusout", this._onFocusOut);
|
|
103
|
+
document.addEventListener("keydown", this._onKeyDown);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
disconnectedCallback() {
|
|
107
|
+
this.removeEventListener("mouseenter", this._onMouseEnter);
|
|
108
|
+
this.removeEventListener("mouseleave", this._onMouseLeave);
|
|
109
|
+
this.removeEventListener("focusin", this._onFocusIn);
|
|
110
|
+
this.removeEventListener("focusout", this._onFocusOut);
|
|
111
|
+
document.removeEventListener("keydown", this._onKeyDown);
|
|
112
|
+
clearTimeout(this._showTimeout);
|
|
113
|
+
clearTimeout(this._hideTimeout);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
117
|
+
if (oldVal === newVal) return;
|
|
118
|
+
this.render();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get text() {
|
|
122
|
+
return this.getAttribute("text") || "";
|
|
123
|
+
}
|
|
124
|
+
set text(val) {
|
|
125
|
+
this.setAttribute("text", val);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
get position() {
|
|
129
|
+
return this.getAttribute("position") || "top";
|
|
130
|
+
}
|
|
131
|
+
set position(val) {
|
|
132
|
+
this.setAttribute("position", val);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get delay() {
|
|
136
|
+
return parseInt(this.getAttribute("delay") ?? "200", 10);
|
|
137
|
+
}
|
|
138
|
+
set delay(val) {
|
|
139
|
+
this.setAttribute("delay", String(val));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get color() {
|
|
143
|
+
return this.getAttribute("color") || "base";
|
|
144
|
+
}
|
|
145
|
+
set color(val) {
|
|
146
|
+
this.setAttribute("color", val);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
show() {
|
|
150
|
+
clearTimeout(this._hideTimeout);
|
|
151
|
+
this._showTimeout = setTimeout(() => {
|
|
152
|
+
this._visible = true;
|
|
153
|
+
const tip = this.shadowRoot.querySelector(".tooltip");
|
|
154
|
+
if (tip) {
|
|
155
|
+
const bg = this._getBg();
|
|
156
|
+
const resolvedBg = resolveCSSColor(bg, this);
|
|
157
|
+
tip.style.color = contrastTextColor(resolvedBg);
|
|
158
|
+
tip.classList.add("visible");
|
|
159
|
+
}
|
|
160
|
+
}, this.delay);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
hide() {
|
|
164
|
+
clearTimeout(this._showTimeout);
|
|
165
|
+
this._visible = false;
|
|
166
|
+
const tip = this.shadowRoot.querySelector(".tooltip");
|
|
167
|
+
if (tip) tip.classList.remove("visible");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_onMouseEnter() {
|
|
171
|
+
this.show();
|
|
172
|
+
}
|
|
173
|
+
_onMouseLeave() {
|
|
174
|
+
this.hide();
|
|
175
|
+
}
|
|
176
|
+
_onFocusIn() {
|
|
177
|
+
this.show();
|
|
178
|
+
}
|
|
179
|
+
_onFocusOut() {
|
|
180
|
+
this.hide();
|
|
181
|
+
}
|
|
182
|
+
_onKeyDown(e) {
|
|
183
|
+
if (e.key === "Escape" && this._visible) {
|
|
184
|
+
this.hide();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_getBg() {
|
|
189
|
+
const map = {
|
|
190
|
+
base: "var(--base-content--, #555)",
|
|
191
|
+
primary: "var(--primary-content--, #0070f3)",
|
|
192
|
+
secondary: "var(--secondary-content--, #6c757d)",
|
|
193
|
+
success: "var(--success-content--, #28a745)",
|
|
194
|
+
warning: "var(--warning-content--, #ffc107)",
|
|
195
|
+
error: "var(--error-content--, #dc3545)",
|
|
196
|
+
help: "var(--help-content--, #6f42c1)",
|
|
197
|
+
};
|
|
198
|
+
return map[this.color] || map.base;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
render() {
|
|
202
|
+
const pos = this.position;
|
|
203
|
+
const bg = this._getBg();
|
|
204
|
+
const resolvedBg = resolveCSSColor(bg, this);
|
|
205
|
+
const fg = contrastTextColor(resolvedBg);
|
|
206
|
+
|
|
207
|
+
this.shadowRoot.innerHTML = `
|
|
208
|
+
<style>
|
|
209
|
+
:host {
|
|
210
|
+
display: inline-block;
|
|
211
|
+
position: relative;
|
|
212
|
+
font-family: var(--font-family-body, sans-serif);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.trigger {
|
|
216
|
+
display: inline-block;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.tooltip {
|
|
220
|
+
position: absolute;
|
|
221
|
+
z-index: 9999;
|
|
222
|
+
white-space: nowrap;
|
|
223
|
+
pointer-events: none;
|
|
224
|
+
opacity: 0;
|
|
225
|
+
transform: scale(0.95);
|
|
226
|
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
227
|
+
padding: var(--component-tooltip-padding, var(--spacing-x-small, 4px)) var(--component-tooltip-padding-h, var(--spacing-medium, 8px));
|
|
228
|
+
border-radius: var(--component-tooltip-border-radius, var(--radii-small, 4px));
|
|
229
|
+
font-size: var(--font-size-small, 0.8em);
|
|
230
|
+
background: ${bg};
|
|
231
|
+
color: ${fg};
|
|
232
|
+
line-height: 1.4;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.tooltip.visible {
|
|
236
|
+
opacity: 1;
|
|
237
|
+
transform: scale(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.tooltip::after {
|
|
241
|
+
content: "";
|
|
242
|
+
position: absolute;
|
|
243
|
+
border: 5px solid transparent;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.tooltip.top {
|
|
247
|
+
bottom: 100%;
|
|
248
|
+
left: 50%;
|
|
249
|
+
transform: translateX(-50%) scale(0.95);
|
|
250
|
+
margin-bottom: 6px;
|
|
251
|
+
}
|
|
252
|
+
.tooltip.top.visible {
|
|
253
|
+
transform: translateX(-50%) scale(1);
|
|
254
|
+
}
|
|
255
|
+
.tooltip.top::after {
|
|
256
|
+
top: 100%;
|
|
257
|
+
left: 50%;
|
|
258
|
+
transform: translateX(-50%);
|
|
259
|
+
border-top-color: ${bg};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.tooltip.bottom {
|
|
263
|
+
top: 100%;
|
|
264
|
+
left: 50%;
|
|
265
|
+
transform: translateX(-50%) scale(0.95);
|
|
266
|
+
margin-top: 6px;
|
|
267
|
+
}
|
|
268
|
+
.tooltip.bottom.visible {
|
|
269
|
+
transform: translateX(-50%) scale(1);
|
|
270
|
+
}
|
|
271
|
+
.tooltip.bottom::after {
|
|
272
|
+
bottom: 100%;
|
|
273
|
+
left: 50%;
|
|
274
|
+
transform: translateX(-50%);
|
|
275
|
+
border-bottom-color: ${bg};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.tooltip.left {
|
|
279
|
+
right: 100%;
|
|
280
|
+
top: 50%;
|
|
281
|
+
transform: translateY(-50%) scale(0.95);
|
|
282
|
+
margin-right: 6px;
|
|
283
|
+
}
|
|
284
|
+
.tooltip.left.visible {
|
|
285
|
+
transform: translateY(-50%) scale(1);
|
|
286
|
+
}
|
|
287
|
+
.tooltip.left::after {
|
|
288
|
+
left: 100%;
|
|
289
|
+
top: 50%;
|
|
290
|
+
transform: translateY(-50%);
|
|
291
|
+
border-left-color: ${bg};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.tooltip.right {
|
|
295
|
+
left: 100%;
|
|
296
|
+
top: 50%;
|
|
297
|
+
transform: translateY(-50%) scale(0.95);
|
|
298
|
+
margin-left: 6px;
|
|
299
|
+
}
|
|
300
|
+
.tooltip.right.visible {
|
|
301
|
+
transform: translateY(-50%) scale(1);
|
|
302
|
+
}
|
|
303
|
+
.tooltip.right::after {
|
|
304
|
+
right: 100%;
|
|
305
|
+
top: 50%;
|
|
306
|
+
transform: translateY(-50%);
|
|
307
|
+
border-right-color: ${bg};
|
|
308
|
+
}
|
|
309
|
+
</style>
|
|
310
|
+
<slot class="trigger"></slot>
|
|
311
|
+
<div class="tooltip ${pos}" role="tooltip">${this.text}</div>
|
|
312
|
+
`;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!customElements.get("y-tooltip")) {
|
|
317
|
+
customElements.define("y-tooltip", YumeTooltip);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export { YumeTooltip };
|