kitzo 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/README.md +168 -0
- package/dist/kitzo.esm.js +454 -0
- package/dist/kitzo.umd.js +465 -0
- package/dist/zero-kitzo.d.ts +80 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# zero-kitzo
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/kitzo)
|
|
4
|
+
|
|
5
|
+
### A lightweight tool
|
|
6
|
+
|
|
7
|
+
Current features
|
|
8
|
+
|
|
9
|
+
- Copy on click
|
|
10
|
+
- Tooltip on mouseover
|
|
11
|
+
- Ripple effect on mousedown
|
|
12
|
+
- Debounce function
|
|
13
|
+
|
|
14
|
+
#### Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i kitzo
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
> or
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
<script src="https://cdn.jsdelivr.net/npm/kitzo@1.0.0/dist/kitzo.umd.min.js"></script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
#### Quick usage overview
|
|
29
|
+
|
|
30
|
+
| [NPM](#npm-usage) | [CDN](#cdn-usage) |
|
|
31
|
+
| -------------------------------- | ------------------ |
|
|
32
|
+
| [`kitzoCopy()`](#copy-api-1) | `kitzo.copy()` |
|
|
33
|
+
| [`kitzoTooltip()`](#tooltip-api) | `kitzo.tooltip()` |
|
|
34
|
+
| [`kitzoRipple()`](#ripple-api) | `kitzo.ripple()` |
|
|
35
|
+
| [`kitzoDebounce()`](#debounce) | `kitzo.debounce()` |
|
|
36
|
+
|
|
37
|
+
#### NPM usage
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import { kitzoCopy, kitzoTooltip, kitzoRipple } from 'kitzo';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> Use a modern build tool. **vite** - recommended
|
|
44
|
+
|
|
45
|
+
##### Copy API:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
kitzoCopy(selector | element, {
|
|
49
|
+
doc: string,
|
|
50
|
+
event: 'click' | 'dblclick' | 'contextmenu' | 'mouseup' | 'touchend',
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> Instantly adds click-to-copy functionality to buttons, reliably and with fallback.
|
|
55
|
+
|
|
56
|
+
##### Tooltip API:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
kitzoTooltip(selectors | element | NodeList, {
|
|
60
|
+
tooltip: string,
|
|
61
|
+
direction: 'top' | 'right' | 'bottom' | 'left',
|
|
62
|
+
arrow: 'on' | 'off',
|
|
63
|
+
offset: number,
|
|
64
|
+
customClass: string,
|
|
65
|
+
style: {},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> Attach minimal tooltips to buttons for clean, helpful hover hints.
|
|
70
|
+
|
|
71
|
+
##### Ripple API:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
kitzoRipple(selectors | element | NodeList, {
|
|
75
|
+
opacity: number,
|
|
76
|
+
duration: number,
|
|
77
|
+
color: string,
|
|
78
|
+
size: number | null,
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> Adds a lightweight, clean ripple effect to your buttons on click.
|
|
83
|
+
|
|
84
|
+
##### Debounce API:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
kitzoDebounce(callback, delayInMilliseconds);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
// Log only after typing stops for 500ms
|
|
92
|
+
const logSearch = kitzoDebounce((text) => {
|
|
93
|
+
console.log("Searching for:", text);
|
|
94
|
+
}, 500);
|
|
95
|
+
|
|
96
|
+
// Attach to input
|
|
97
|
+
document.querySelector("#search").addEventListener("input", (e) => {
|
|
98
|
+
logSearch(e.target.value);
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> Debounce on every call of function.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
#### CDN usage
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<script src="https://cdn.jsdelivr.net/npm/kitzo@1.0.0/dist/kitzo.umd.min.js"></script>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> Attach this script tag in the html head tag and you are good to go.
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
kitzo.copy();
|
|
116
|
+
kitzo.tooltip();
|
|
117
|
+
kitzo.ripple();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
##### Copy API:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
kitzo.copy(selectors | element, {
|
|
124
|
+
doc: string,
|
|
125
|
+
event: 'click' | 'dblclick' | 'contextmenu' | 'mouseup' | 'touchend',
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
##### Tooltip API:
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
kitzo.tooltip(selectors | element | NodeList, {
|
|
133
|
+
tooltip: string,
|
|
134
|
+
direction: 'top' | 'right' | 'bottom' | 'left',
|
|
135
|
+
arrow: 'on' | 'off',
|
|
136
|
+
offset: number,
|
|
137
|
+
customClass: string,
|
|
138
|
+
style: {},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
##### Ripple API:
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
kitzo.ripple(selectors | element | NodeList, {
|
|
146
|
+
opacity: number,
|
|
147
|
+
duration: number,
|
|
148
|
+
color: string,
|
|
149
|
+
size: number | null,
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
##### Debounce API:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
kitzo.debounce(callback, delayInMilliseconds);
|
|
157
|
+
```
|
|
158
|
+
```javascript
|
|
159
|
+
// Log only after typing stops for 500ms
|
|
160
|
+
const logSearch = kitzo.debounce((text) => {
|
|
161
|
+
console.log("Searching for:", text);
|
|
162
|
+
}, 500);
|
|
163
|
+
|
|
164
|
+
// Attach to input
|
|
165
|
+
document.querySelector("#search").addEventListener("input", (e) => {
|
|
166
|
+
logSearch(e.target.value);
|
|
167
|
+
});
|
|
168
|
+
```
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
//! Helper functions
|
|
2
|
+
function getButtons(element) {
|
|
3
|
+
if (typeof element === 'string') {
|
|
4
|
+
return document.querySelectorAll(element);
|
|
5
|
+
}
|
|
6
|
+
if (element instanceof Element) {
|
|
7
|
+
return [element];
|
|
8
|
+
}
|
|
9
|
+
if (element instanceof NodeList || element instanceof HTMLCollection) {
|
|
10
|
+
return element;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Add style tags
|
|
15
|
+
let tooltipStyleAdded = false;
|
|
16
|
+
let rippleStyleAdded = false;
|
|
17
|
+
|
|
18
|
+
function addStyleTag(styles) {
|
|
19
|
+
const style = document.createElement('style');
|
|
20
|
+
style.innerHTML = styles;
|
|
21
|
+
document.head.appendChild(style);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function addStyleTagToHtmlHead(type, styles) {
|
|
25
|
+
if (type === 'tooltip' && !tooltipStyleAdded) {
|
|
26
|
+
addStyleTag(styles);
|
|
27
|
+
tooltipStyleAdded = true;
|
|
28
|
+
}
|
|
29
|
+
if (type === 'ripple' && !rippleStyleAdded) {
|
|
30
|
+
addStyleTag(styles);
|
|
31
|
+
rippleStyleAdded = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//! Copy function
|
|
36
|
+
function legecyCopy(docs) {
|
|
37
|
+
try {
|
|
38
|
+
const textarea = document.createElement('textarea');
|
|
39
|
+
textarea.value = docs;
|
|
40
|
+
document.body.appendChild(textarea);
|
|
41
|
+
textarea.select();
|
|
42
|
+
document.execCommand('copy');
|
|
43
|
+
document.body.removeChild(textarea);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
alert('Couldn’t copy automatically. Please copy manually.');
|
|
46
|
+
console.error(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function copyText(docs) {
|
|
51
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
52
|
+
try {
|
|
53
|
+
await navigator.clipboard.writeText(docs);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
legecyCopy(docs);
|
|
56
|
+
console.error(error);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
legecyCopy(docs);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const copyConfigMap = new WeakMap();
|
|
64
|
+
const allowedEvents = ['click', 'dblclick', 'contextmenu', 'mouseup', 'touchend'];
|
|
65
|
+
const attachedEvents = new Set();
|
|
66
|
+
|
|
67
|
+
function copy(element, config = {}) {
|
|
68
|
+
config = Object.assign(
|
|
69
|
+
{
|
|
70
|
+
doc: '',
|
|
71
|
+
event: 'click',
|
|
72
|
+
},
|
|
73
|
+
config
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const { doc, event } = config;
|
|
77
|
+
|
|
78
|
+
if (!element) {
|
|
79
|
+
console.error('A button element/selector is expected');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!doc) {
|
|
84
|
+
console.error('doc cannot be empty');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof doc !== 'string') {
|
|
89
|
+
console.error('Doc should be in string format');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof event !== 'string') {
|
|
94
|
+
console.error('Only strings are allowed as events');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!event.trim()) {
|
|
99
|
+
console.error('event cannot be empty');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const allButtons = getButtons(element);
|
|
104
|
+
if (!allButtons) {
|
|
105
|
+
console.error('No elements found for zeroCopy');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!allowedEvents.includes(event)) {
|
|
110
|
+
console.warn(`[zeroCopy] "${event}" is not allowed. Defaulting to "click".`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const safeEvent = allowedEvents.includes(event) ? event : 'click';
|
|
114
|
+
|
|
115
|
+
allButtons.forEach((btn) => {
|
|
116
|
+
btn.setAttribute('data-zero-copy', 'true');
|
|
117
|
+
|
|
118
|
+
copyConfigMap.set(btn, {
|
|
119
|
+
doc,
|
|
120
|
+
event: safeEvent,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!attachedEvents.has(safeEvent)) {
|
|
125
|
+
document.addEventListener(safeEvent, (e) => {
|
|
126
|
+
const btn = e.target.closest('[data-zero-copy]');
|
|
127
|
+
if (!btn) return;
|
|
128
|
+
|
|
129
|
+
const { doc, event } = copyConfigMap.get(btn);
|
|
130
|
+
if (event && event === safeEvent) {
|
|
131
|
+
copyText(doc);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
attachedEvents.add(safeEvent);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function debounce(fn, delay = 300) {
|
|
139
|
+
let timer;
|
|
140
|
+
|
|
141
|
+
return (...args) => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function rippleStyles() {
|
|
148
|
+
return `.zero-ripple {
|
|
149
|
+
position: relative;
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.zero-ripples {
|
|
154
|
+
display: block;
|
|
155
|
+
position: absolute;
|
|
156
|
+
top: 0;
|
|
157
|
+
left: 0;
|
|
158
|
+
transform: translate(-50%, -50%);
|
|
159
|
+
width: 0;
|
|
160
|
+
height: 0;
|
|
161
|
+
background-color: var(--ripples-color);
|
|
162
|
+
z-index: 5;
|
|
163
|
+
border-radius: 50%;
|
|
164
|
+
opacity: 1;
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.zero-ripples.expand {
|
|
169
|
+
animation: expand-ripple var(--ripples-duration) linear forwards;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@keyframes expand-ripple {
|
|
173
|
+
0% {
|
|
174
|
+
width: 0;
|
|
175
|
+
height: 0;
|
|
176
|
+
opacity: var(--ripples-opacity);
|
|
177
|
+
}
|
|
178
|
+
100% {
|
|
179
|
+
width: var(--ripples-size);
|
|
180
|
+
height: var(--ripples-size);
|
|
181
|
+
opacity: 0;
|
|
182
|
+
}
|
|
183
|
+
}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//! Ripple effect
|
|
187
|
+
let rippleListenerAdded = false;
|
|
188
|
+
|
|
189
|
+
function ripple(element, config = {}) {
|
|
190
|
+
if (!element) {
|
|
191
|
+
console.error('A button element/selector is expected');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
addStyleTagToHtmlHead('ripple', rippleStyles());
|
|
196
|
+
|
|
197
|
+
config = Object.assign(
|
|
198
|
+
{
|
|
199
|
+
opacity: 0.5,
|
|
200
|
+
duration: 1,
|
|
201
|
+
color: 'white',
|
|
202
|
+
size: null,
|
|
203
|
+
},
|
|
204
|
+
config
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const { opacity, color, duration, size } = config;
|
|
208
|
+
|
|
209
|
+
const allButtons = getButtons(element);
|
|
210
|
+
if (!allButtons) {
|
|
211
|
+
console.error('No elements found for zeroRipple');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
allButtons.forEach((btn) => {
|
|
215
|
+
btn.classList.add('zero-ripple');
|
|
216
|
+
btn.setAttribute('data-zero-ripple', 'true');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (!rippleListenerAdded) {
|
|
220
|
+
document.addEventListener('mousedown', (e) => {
|
|
221
|
+
const btn = e.target.closest('[data-zero-ripple]');
|
|
222
|
+
if (btn) {
|
|
223
|
+
const span = document.createElement('span');
|
|
224
|
+
span.className = 'zero-ripples';
|
|
225
|
+
btn.appendChild(span);
|
|
226
|
+
|
|
227
|
+
const { left, top, width } = btn.getBoundingClientRect();
|
|
228
|
+
span.style.left = e.clientX - left + 'px';
|
|
229
|
+
span.style.top = e.clientY - top + 'px';
|
|
230
|
+
|
|
231
|
+
btn.style.setProperty('--ripples-opacity', opacity);
|
|
232
|
+
btn.style.setProperty('--ripples-duration', duration + 's');
|
|
233
|
+
btn.style.setProperty('--ripples-size', `${size || width * 2}px`);
|
|
234
|
+
btn.style.setProperty('--ripples-color', color);
|
|
235
|
+
|
|
236
|
+
span.classList.add('expand');
|
|
237
|
+
|
|
238
|
+
span.addEventListener('animationend', () => span.remove());
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
rippleListenerAdded = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tooltipStyles() {
|
|
247
|
+
return `:root {
|
|
248
|
+
--tooltip-bg-clr: hsl(0, 0%, 10%);
|
|
249
|
+
--tooltip-text-clr: hsl(0, 0%, 90%);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@media (prefers-color-scheme: dark) {
|
|
253
|
+
:root {
|
|
254
|
+
--tooltip-bg-clr: hsl(0, 0%, 95%);
|
|
255
|
+
--tooltip-text-clr: hsl(0, 0%, 10%);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.zero-tooltip {
|
|
260
|
+
--tooltip-arrow-clr: var(--tooltip-bg-clr);
|
|
261
|
+
|
|
262
|
+
box-sizing: border-box;
|
|
263
|
+
font-family: inherit;
|
|
264
|
+
text-align: center;
|
|
265
|
+
|
|
266
|
+
position: fixed;
|
|
267
|
+
top: 0;
|
|
268
|
+
left: 0;
|
|
269
|
+
z-index: 999999;
|
|
270
|
+
|
|
271
|
+
background-color: var(--tooltip-bg-clr);
|
|
272
|
+
color: var(--tooltip-text-clr);
|
|
273
|
+
padding-block: 0.325rem;
|
|
274
|
+
padding-inline: 0.625rem;
|
|
275
|
+
border-radius: 4px;
|
|
276
|
+
box-shadow: 0 2px 6px hsla(235, 0%, 0%, 0.25);
|
|
277
|
+
|
|
278
|
+
opacity: 0 !important;
|
|
279
|
+
|
|
280
|
+
transition: opacity 200ms;
|
|
281
|
+
transition-delay: 100ms;
|
|
282
|
+
pointer-events: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.zero-tooltip.show {
|
|
286
|
+
opacity: 1 !important;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.zero-tooltip-top::before,
|
|
290
|
+
.zero-tooltip-right::before,
|
|
291
|
+
.zero-tooltip-bottom::before,
|
|
292
|
+
.zero-tooltip-left::before {
|
|
293
|
+
|
|
294
|
+
content: '';
|
|
295
|
+
position: absolute;
|
|
296
|
+
border: 6px solid;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.zero-tooltip-top::before {
|
|
300
|
+
top: calc(100% - 1px);
|
|
301
|
+
left: 50%;
|
|
302
|
+
translate: -50% 0;
|
|
303
|
+
border-color: var(--tooltip-arrow-clr) transparent transparent transparent;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.zero-tooltip-right::before {
|
|
307
|
+
top: 50%;
|
|
308
|
+
right: calc(100% - 1px);
|
|
309
|
+
translate: 0 -50%;
|
|
310
|
+
border-color: transparent var(--tooltip-arrow-clr) transparent transparent;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.zero-tooltip-bottom::before {
|
|
314
|
+
bottom: calc(100% - 1px);
|
|
315
|
+
left: 50%;
|
|
316
|
+
translate: -50% 0;
|
|
317
|
+
border-color: transparent transparent var(--tooltip-arrow-clr) transparent;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.zero-tooltip-left::before {
|
|
321
|
+
left: calc(100% - 1px);
|
|
322
|
+
top: 50%;
|
|
323
|
+
translate: 0 -50%;
|
|
324
|
+
border-color: transparent transparent transparent var(--tooltip-arrow-clr);
|
|
325
|
+
}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
//! Tooltip
|
|
329
|
+
let tooltipDiv;
|
|
330
|
+
let tooltipListenerAdded = false;
|
|
331
|
+
const tooltipConfigMap = new WeakMap();
|
|
332
|
+
|
|
333
|
+
function tooltip(element, config = {}) {
|
|
334
|
+
if (window.matchMedia('(pointer: coarse)').matches) return;
|
|
335
|
+
|
|
336
|
+
if (!element) {
|
|
337
|
+
console.error('A button element/selector is expected');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
addStyleTagToHtmlHead('tooltip', tooltipStyles());
|
|
342
|
+
|
|
343
|
+
config = Object.assign(
|
|
344
|
+
{
|
|
345
|
+
tooltip: 'Tool tip',
|
|
346
|
+
direction: 'bottom',
|
|
347
|
+
arrow: 'off',
|
|
348
|
+
offset: 10,
|
|
349
|
+
customClass: '',
|
|
350
|
+
style: {},
|
|
351
|
+
},
|
|
352
|
+
config
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const allButtons = getButtons(element);
|
|
356
|
+
if (!allButtons) {
|
|
357
|
+
console.error('No elements found for zeroTooltip');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const disAllowedStyles = ['top', 'left', 'right', 'bottom', 'position', 'zIndex', 'opacity', 'transform', 'translate', 'scale', 'rotate', 'perspective'];
|
|
362
|
+
for (const key of disAllowedStyles) {
|
|
363
|
+
if (key in config.style) {
|
|
364
|
+
console.warn(`[zeroTooltip] "${key}" style is managed internally and will be ignored.`);
|
|
365
|
+
delete config.style[key];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
allButtons.forEach((btn) => {
|
|
370
|
+
btn.setAttribute('data-zero-tooltip', true);
|
|
371
|
+
tooltipConfigMap.set(btn, config);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (!tooltipDiv) {
|
|
375
|
+
tooltipDiv = document.createElement('div');
|
|
376
|
+
tooltipDiv.style.opacity = '0';
|
|
377
|
+
document.body.appendChild(tooltipDiv);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function getPosition(btn, dir, offset) {
|
|
381
|
+
const { width, height, top, left } = btn.getBoundingClientRect();
|
|
382
|
+
let posX;
|
|
383
|
+
let posY;
|
|
384
|
+
|
|
385
|
+
if (dir === 'top') {
|
|
386
|
+
posX = left + width / 2 - tooltipDiv.offsetWidth / 2;
|
|
387
|
+
posY = top - tooltipDiv.offsetHeight - offset;
|
|
388
|
+
return { posX, posY };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (dir === 'right') {
|
|
392
|
+
posX = left + width + offset;
|
|
393
|
+
posY = top + height / 2 - tooltipDiv.offsetHeight / 2;
|
|
394
|
+
return { posX, posY };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (dir === 'bottom') {
|
|
398
|
+
posX = left + width / 2 - tooltipDiv.offsetWidth / 2;
|
|
399
|
+
posY = top + height + offset;
|
|
400
|
+
return { posX, posY };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (dir === 'left') {
|
|
404
|
+
posX = left - tooltipDiv.offsetWidth - offset;
|
|
405
|
+
posY = top + height / 2 - tooltipDiv.offsetHeight / 2;
|
|
406
|
+
return { posX, posY };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!tooltipListenerAdded) {
|
|
411
|
+
document.addEventListener('mouseover', (e) => {
|
|
412
|
+
const btn = e.target.closest('[data-zero-tooltip]');
|
|
413
|
+
if (btn) {
|
|
414
|
+
const { tooltip, direction, offset, customClass, style, arrow } = tooltipConfigMap.get(btn);
|
|
415
|
+
|
|
416
|
+
tooltipDiv.removeAttribute('style');
|
|
417
|
+
Object.assign(tooltipDiv.style, {
|
|
418
|
+
position: 'fixed',
|
|
419
|
+
top: '0px',
|
|
420
|
+
left: '0px',
|
|
421
|
+
zIndex: '999999',
|
|
422
|
+
...style,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const isArrowOn = arrow === 'on';
|
|
426
|
+
tooltipDiv.textContent = tooltip;
|
|
427
|
+
tooltipDiv.className = `zero-tooltip ${isArrowOn ? `zero-tooltip-${direction}` : ''} ${customClass.trim() ? customClass : ''}`;
|
|
428
|
+
|
|
429
|
+
if (isArrowOn) {
|
|
430
|
+
const color = getComputedStyle(tooltipDiv).backgroundColor;
|
|
431
|
+
tooltipDiv.style.setProperty('--tooltip-arrow-clr', color);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const { posX, posY } = getPosition(btn, direction, offset);
|
|
435
|
+
tooltipDiv.style.transform = `translate(${posX}px, ${posY}px)`;
|
|
436
|
+
|
|
437
|
+
requestAnimationFrame(() => {
|
|
438
|
+
tooltipDiv.classList.add('show');
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
document.addEventListener('mouseout', (e) => {
|
|
444
|
+
const btn = e.target.closest('[data-zero-tooltip]');
|
|
445
|
+
if (btn) {
|
|
446
|
+
tooltipDiv.classList.remove('show');
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
tooltipListenerAdded = true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export { copy, debounce, ripple, tooltip };
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.kitzo = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
//! Helper functions
|
|
8
|
+
function getButtons(element) {
|
|
9
|
+
if (typeof element === 'string') {
|
|
10
|
+
return document.querySelectorAll(element);
|
|
11
|
+
}
|
|
12
|
+
if (element instanceof Element) {
|
|
13
|
+
return [element];
|
|
14
|
+
}
|
|
15
|
+
if (element instanceof NodeList || element instanceof HTMLCollection) {
|
|
16
|
+
return element;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Add style tags
|
|
21
|
+
let tooltipStyleAdded = false;
|
|
22
|
+
let rippleStyleAdded = false;
|
|
23
|
+
|
|
24
|
+
function addStyleTag(styles) {
|
|
25
|
+
const style = document.createElement('style');
|
|
26
|
+
style.innerHTML = styles;
|
|
27
|
+
document.head.appendChild(style);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function addStyleTagToHtmlHead(type, styles) {
|
|
31
|
+
if (type === 'tooltip' && !tooltipStyleAdded) {
|
|
32
|
+
addStyleTag(styles);
|
|
33
|
+
tooltipStyleAdded = true;
|
|
34
|
+
}
|
|
35
|
+
if (type === 'ripple' && !rippleStyleAdded) {
|
|
36
|
+
addStyleTag(styles);
|
|
37
|
+
rippleStyleAdded = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//! Copy function
|
|
42
|
+
function legecyCopy(docs) {
|
|
43
|
+
try {
|
|
44
|
+
const textarea = document.createElement('textarea');
|
|
45
|
+
textarea.value = docs;
|
|
46
|
+
document.body.appendChild(textarea);
|
|
47
|
+
textarea.select();
|
|
48
|
+
document.execCommand('copy');
|
|
49
|
+
document.body.removeChild(textarea);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
alert('Couldn’t copy automatically. Please copy manually.');
|
|
52
|
+
console.error(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function copyText(docs) {
|
|
57
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
58
|
+
try {
|
|
59
|
+
await navigator.clipboard.writeText(docs);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
legecyCopy(docs);
|
|
62
|
+
console.error(error);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
legecyCopy(docs);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const copyConfigMap = new WeakMap();
|
|
70
|
+
const allowedEvents = ['click', 'dblclick', 'contextmenu', 'mouseup', 'touchend'];
|
|
71
|
+
const attachedEvents = new Set();
|
|
72
|
+
|
|
73
|
+
function copy(element, config = {}) {
|
|
74
|
+
config = Object.assign(
|
|
75
|
+
{
|
|
76
|
+
doc: '',
|
|
77
|
+
event: 'click',
|
|
78
|
+
},
|
|
79
|
+
config
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { doc, event } = config;
|
|
83
|
+
|
|
84
|
+
if (!element) {
|
|
85
|
+
console.error('A button element/selector is expected');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!doc) {
|
|
90
|
+
console.error('doc cannot be empty');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof doc !== 'string') {
|
|
95
|
+
console.error('Doc should be in string format');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof event !== 'string') {
|
|
100
|
+
console.error('Only strings are allowed as events');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!event.trim()) {
|
|
105
|
+
console.error('event cannot be empty');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const allButtons = getButtons(element);
|
|
110
|
+
if (!allButtons) {
|
|
111
|
+
console.error('No elements found for zeroCopy');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!allowedEvents.includes(event)) {
|
|
116
|
+
console.warn(`[zeroCopy] "${event}" is not allowed. Defaulting to "click".`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const safeEvent = allowedEvents.includes(event) ? event : 'click';
|
|
120
|
+
|
|
121
|
+
allButtons.forEach((btn) => {
|
|
122
|
+
btn.setAttribute('data-zero-copy', 'true');
|
|
123
|
+
|
|
124
|
+
copyConfigMap.set(btn, {
|
|
125
|
+
doc,
|
|
126
|
+
event: safeEvent,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!attachedEvents.has(safeEvent)) {
|
|
131
|
+
document.addEventListener(safeEvent, (e) => {
|
|
132
|
+
const btn = e.target.closest('[data-zero-copy]');
|
|
133
|
+
if (!btn) return;
|
|
134
|
+
|
|
135
|
+
const { doc, event } = copyConfigMap.get(btn);
|
|
136
|
+
if (event && event === safeEvent) {
|
|
137
|
+
copyText(doc);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
attachedEvents.add(safeEvent);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function debounce(fn, delay = 300) {
|
|
145
|
+
let timer;
|
|
146
|
+
|
|
147
|
+
return (...args) => {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function rippleStyles() {
|
|
154
|
+
return `.zero-ripple {
|
|
155
|
+
position: relative;
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.zero-ripples {
|
|
160
|
+
display: block;
|
|
161
|
+
position: absolute;
|
|
162
|
+
top: 0;
|
|
163
|
+
left: 0;
|
|
164
|
+
transform: translate(-50%, -50%);
|
|
165
|
+
width: 0;
|
|
166
|
+
height: 0;
|
|
167
|
+
background-color: var(--ripples-color);
|
|
168
|
+
z-index: 5;
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
opacity: 1;
|
|
171
|
+
pointer-events: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.zero-ripples.expand {
|
|
175
|
+
animation: expand-ripple var(--ripples-duration) linear forwards;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes expand-ripple {
|
|
179
|
+
0% {
|
|
180
|
+
width: 0;
|
|
181
|
+
height: 0;
|
|
182
|
+
opacity: var(--ripples-opacity);
|
|
183
|
+
}
|
|
184
|
+
100% {
|
|
185
|
+
width: var(--ripples-size);
|
|
186
|
+
height: var(--ripples-size);
|
|
187
|
+
opacity: 0;
|
|
188
|
+
}
|
|
189
|
+
}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//! Ripple effect
|
|
193
|
+
let rippleListenerAdded = false;
|
|
194
|
+
|
|
195
|
+
function ripple(element, config = {}) {
|
|
196
|
+
if (!element) {
|
|
197
|
+
console.error('A button element/selector is expected');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
addStyleTagToHtmlHead('ripple', rippleStyles());
|
|
202
|
+
|
|
203
|
+
config = Object.assign(
|
|
204
|
+
{
|
|
205
|
+
opacity: 0.5,
|
|
206
|
+
duration: 1,
|
|
207
|
+
color: 'white',
|
|
208
|
+
size: null,
|
|
209
|
+
},
|
|
210
|
+
config
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const { opacity, color, duration, size } = config;
|
|
214
|
+
|
|
215
|
+
const allButtons = getButtons(element);
|
|
216
|
+
if (!allButtons) {
|
|
217
|
+
console.error('No elements found for zeroRipple');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
allButtons.forEach((btn) => {
|
|
221
|
+
btn.classList.add('zero-ripple');
|
|
222
|
+
btn.setAttribute('data-zero-ripple', 'true');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (!rippleListenerAdded) {
|
|
226
|
+
document.addEventListener('mousedown', (e) => {
|
|
227
|
+
const btn = e.target.closest('[data-zero-ripple]');
|
|
228
|
+
if (btn) {
|
|
229
|
+
const span = document.createElement('span');
|
|
230
|
+
span.className = 'zero-ripples';
|
|
231
|
+
btn.appendChild(span);
|
|
232
|
+
|
|
233
|
+
const { left, top, width } = btn.getBoundingClientRect();
|
|
234
|
+
span.style.left = e.clientX - left + 'px';
|
|
235
|
+
span.style.top = e.clientY - top + 'px';
|
|
236
|
+
|
|
237
|
+
btn.style.setProperty('--ripples-opacity', opacity);
|
|
238
|
+
btn.style.setProperty('--ripples-duration', duration + 's');
|
|
239
|
+
btn.style.setProperty('--ripples-size', `${size || width * 2}px`);
|
|
240
|
+
btn.style.setProperty('--ripples-color', color);
|
|
241
|
+
|
|
242
|
+
span.classList.add('expand');
|
|
243
|
+
|
|
244
|
+
span.addEventListener('animationend', () => span.remove());
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
rippleListenerAdded = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function tooltipStyles() {
|
|
253
|
+
return `:root {
|
|
254
|
+
--tooltip-bg-clr: hsl(0, 0%, 10%);
|
|
255
|
+
--tooltip-text-clr: hsl(0, 0%, 90%);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@media (prefers-color-scheme: dark) {
|
|
259
|
+
:root {
|
|
260
|
+
--tooltip-bg-clr: hsl(0, 0%, 95%);
|
|
261
|
+
--tooltip-text-clr: hsl(0, 0%, 10%);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.zero-tooltip {
|
|
266
|
+
--tooltip-arrow-clr: var(--tooltip-bg-clr);
|
|
267
|
+
|
|
268
|
+
box-sizing: border-box;
|
|
269
|
+
font-family: inherit;
|
|
270
|
+
text-align: center;
|
|
271
|
+
|
|
272
|
+
position: fixed;
|
|
273
|
+
top: 0;
|
|
274
|
+
left: 0;
|
|
275
|
+
z-index: 999999;
|
|
276
|
+
|
|
277
|
+
background-color: var(--tooltip-bg-clr);
|
|
278
|
+
color: var(--tooltip-text-clr);
|
|
279
|
+
padding-block: 0.325rem;
|
|
280
|
+
padding-inline: 0.625rem;
|
|
281
|
+
border-radius: 4px;
|
|
282
|
+
box-shadow: 0 2px 6px hsla(235, 0%, 0%, 0.25);
|
|
283
|
+
|
|
284
|
+
opacity: 0 !important;
|
|
285
|
+
|
|
286
|
+
transition: opacity 200ms;
|
|
287
|
+
transition-delay: 100ms;
|
|
288
|
+
pointer-events: none;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.zero-tooltip.show {
|
|
292
|
+
opacity: 1 !important;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.zero-tooltip-top::before,
|
|
296
|
+
.zero-tooltip-right::before,
|
|
297
|
+
.zero-tooltip-bottom::before,
|
|
298
|
+
.zero-tooltip-left::before {
|
|
299
|
+
|
|
300
|
+
content: '';
|
|
301
|
+
position: absolute;
|
|
302
|
+
border: 6px solid;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.zero-tooltip-top::before {
|
|
306
|
+
top: calc(100% - 1px);
|
|
307
|
+
left: 50%;
|
|
308
|
+
translate: -50% 0;
|
|
309
|
+
border-color: var(--tooltip-arrow-clr) transparent transparent transparent;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.zero-tooltip-right::before {
|
|
313
|
+
top: 50%;
|
|
314
|
+
right: calc(100% - 1px);
|
|
315
|
+
translate: 0 -50%;
|
|
316
|
+
border-color: transparent var(--tooltip-arrow-clr) transparent transparent;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.zero-tooltip-bottom::before {
|
|
320
|
+
bottom: calc(100% - 1px);
|
|
321
|
+
left: 50%;
|
|
322
|
+
translate: -50% 0;
|
|
323
|
+
border-color: transparent transparent var(--tooltip-arrow-clr) transparent;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.zero-tooltip-left::before {
|
|
327
|
+
left: calc(100% - 1px);
|
|
328
|
+
top: 50%;
|
|
329
|
+
translate: 0 -50%;
|
|
330
|
+
border-color: transparent transparent transparent var(--tooltip-arrow-clr);
|
|
331
|
+
}`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
//! Tooltip
|
|
335
|
+
let tooltipDiv;
|
|
336
|
+
let tooltipListenerAdded = false;
|
|
337
|
+
const tooltipConfigMap = new WeakMap();
|
|
338
|
+
|
|
339
|
+
function tooltip(element, config = {}) {
|
|
340
|
+
if (window.matchMedia('(pointer: coarse)').matches) return;
|
|
341
|
+
|
|
342
|
+
if (!element) {
|
|
343
|
+
console.error('A button element/selector is expected');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
addStyleTagToHtmlHead('tooltip', tooltipStyles());
|
|
348
|
+
|
|
349
|
+
config = Object.assign(
|
|
350
|
+
{
|
|
351
|
+
tooltip: 'Tool tip',
|
|
352
|
+
direction: 'bottom',
|
|
353
|
+
arrow: 'off',
|
|
354
|
+
offset: 10,
|
|
355
|
+
customClass: '',
|
|
356
|
+
style: {},
|
|
357
|
+
},
|
|
358
|
+
config
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const allButtons = getButtons(element);
|
|
362
|
+
if (!allButtons) {
|
|
363
|
+
console.error('No elements found for zeroTooltip');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const disAllowedStyles = ['top', 'left', 'right', 'bottom', 'position', 'zIndex', 'opacity', 'transform', 'translate', 'scale', 'rotate', 'perspective'];
|
|
368
|
+
for (const key of disAllowedStyles) {
|
|
369
|
+
if (key in config.style) {
|
|
370
|
+
console.warn(`[zeroTooltip] "${key}" style is managed internally and will be ignored.`);
|
|
371
|
+
delete config.style[key];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
allButtons.forEach((btn) => {
|
|
376
|
+
btn.setAttribute('data-zero-tooltip', true);
|
|
377
|
+
tooltipConfigMap.set(btn, config);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
if (!tooltipDiv) {
|
|
381
|
+
tooltipDiv = document.createElement('div');
|
|
382
|
+
tooltipDiv.style.opacity = '0';
|
|
383
|
+
document.body.appendChild(tooltipDiv);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function getPosition(btn, dir, offset) {
|
|
387
|
+
const { width, height, top, left } = btn.getBoundingClientRect();
|
|
388
|
+
let posX;
|
|
389
|
+
let posY;
|
|
390
|
+
|
|
391
|
+
if (dir === 'top') {
|
|
392
|
+
posX = left + width / 2 - tooltipDiv.offsetWidth / 2;
|
|
393
|
+
posY = top - tooltipDiv.offsetHeight - offset;
|
|
394
|
+
return { posX, posY };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (dir === 'right') {
|
|
398
|
+
posX = left + width + offset;
|
|
399
|
+
posY = top + height / 2 - tooltipDiv.offsetHeight / 2;
|
|
400
|
+
return { posX, posY };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (dir === 'bottom') {
|
|
404
|
+
posX = left + width / 2 - tooltipDiv.offsetWidth / 2;
|
|
405
|
+
posY = top + height + offset;
|
|
406
|
+
return { posX, posY };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (dir === 'left') {
|
|
410
|
+
posX = left - tooltipDiv.offsetWidth - offset;
|
|
411
|
+
posY = top + height / 2 - tooltipDiv.offsetHeight / 2;
|
|
412
|
+
return { posX, posY };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!tooltipListenerAdded) {
|
|
417
|
+
document.addEventListener('mouseover', (e) => {
|
|
418
|
+
const btn = e.target.closest('[data-zero-tooltip]');
|
|
419
|
+
if (btn) {
|
|
420
|
+
const { tooltip, direction, offset, customClass, style, arrow } = tooltipConfigMap.get(btn);
|
|
421
|
+
|
|
422
|
+
tooltipDiv.removeAttribute('style');
|
|
423
|
+
Object.assign(tooltipDiv.style, {
|
|
424
|
+
position: 'fixed',
|
|
425
|
+
top: '0px',
|
|
426
|
+
left: '0px',
|
|
427
|
+
zIndex: '999999',
|
|
428
|
+
...style,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const isArrowOn = arrow === 'on';
|
|
432
|
+
tooltipDiv.textContent = tooltip;
|
|
433
|
+
tooltipDiv.className = `zero-tooltip ${isArrowOn ? `zero-tooltip-${direction}` : ''} ${customClass.trim() ? customClass : ''}`;
|
|
434
|
+
|
|
435
|
+
if (isArrowOn) {
|
|
436
|
+
const color = getComputedStyle(tooltipDiv).backgroundColor;
|
|
437
|
+
tooltipDiv.style.setProperty('--tooltip-arrow-clr', color);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const { posX, posY } = getPosition(btn, direction, offset);
|
|
441
|
+
tooltipDiv.style.transform = `translate(${posX}px, ${posY}px)`;
|
|
442
|
+
|
|
443
|
+
requestAnimationFrame(() => {
|
|
444
|
+
tooltipDiv.classList.add('show');
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
document.addEventListener('mouseout', (e) => {
|
|
450
|
+
const btn = e.target.closest('[data-zero-tooltip]');
|
|
451
|
+
if (btn) {
|
|
452
|
+
tooltipDiv.classList.remove('show');
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
tooltipListenerAdded = true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
exports.kitzoCopy = copy;
|
|
461
|
+
exports.kitzoDebounce = debounce;
|
|
462
|
+
exports.kitzoRipple = ripple;
|
|
463
|
+
exports.kitzoTooltip = tooltip;
|
|
464
|
+
|
|
465
|
+
}));
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function kitzoTooltip(
|
|
2
|
+
element: string | Element | NodeListOf<Element> | HTMLCollection,
|
|
3
|
+
config?: {
|
|
4
|
+
/**
|
|
5
|
+
* The tooltip text to display (default: "Tool tip")
|
|
6
|
+
*/
|
|
7
|
+
tooltip?: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Direction where tooltip appears: 'top', 'right', 'bottom', or 'left' (default: 'bottom')
|
|
11
|
+
*/
|
|
12
|
+
direction?: 'top' | 'right' | 'bottom' | 'left';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Show an arrow pointing to the target ('on' or 'off', default: 'off')
|
|
16
|
+
*/
|
|
17
|
+
arrow?: 'on' | 'off';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Distance in pixels between the tooltip and the target element (default: 10)
|
|
21
|
+
*/
|
|
22
|
+
offset?: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional custom class to add to the tooltip (for styling)
|
|
26
|
+
*/
|
|
27
|
+
customClass?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Inline styles to apply (excluding top, left, right, bottom, position, zIndex, opacity, transform, translate, scale, rotate, perspective)
|
|
31
|
+
*/
|
|
32
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
33
|
+
}
|
|
34
|
+
): void;
|
|
35
|
+
|
|
36
|
+
export function kitzoRipple(
|
|
37
|
+
element: string | Element | NodeListOf<Element> | HTMLCollection,
|
|
38
|
+
config?: {
|
|
39
|
+
/**
|
|
40
|
+
* Ripple opacity (0 to 1). Default: 0.5
|
|
41
|
+
*/
|
|
42
|
+
opacity?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Animation duration in seconds ('s'). Default: 1
|
|
45
|
+
*/
|
|
46
|
+
duration?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Ripple color (CSS color). Default: 'white'
|
|
49
|
+
*/
|
|
50
|
+
color?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Ripple size in pixels ('px'). If null, auto-scales based on button size. Default: null
|
|
53
|
+
*/
|
|
54
|
+
size?: number | null;
|
|
55
|
+
}
|
|
56
|
+
): void;
|
|
57
|
+
|
|
58
|
+
export function kitzoCopy(
|
|
59
|
+
element: string | Element | NodeListOf<Element>,
|
|
60
|
+
config: {
|
|
61
|
+
/**
|
|
62
|
+
* The text to be copied to the clipboard.
|
|
63
|
+
* Must be a non-empty string.
|
|
64
|
+
*/
|
|
65
|
+
doc?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The DOM event that triggers the copy action.
|
|
69
|
+
* Only the following events are allowed:
|
|
70
|
+
* - 'click' (default)
|
|
71
|
+
* - 'dblclick'
|
|
72
|
+
* - 'contextmenu'
|
|
73
|
+
* - 'mouseup'
|
|
74
|
+
* - 'touchend'
|
|
75
|
+
*/
|
|
76
|
+
event?: 'click' | 'dblclick' | 'contextmenu' | 'mouseup' | 'touchend';
|
|
77
|
+
}
|
|
78
|
+
): void;
|
|
79
|
+
|
|
80
|
+
export function debounce<Args extends any[]>(fn: (...args: Args) => any, delay?: number): (...args: Args) => void;
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kitzo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight JavaScript UI micro-library.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/kitzo.umd.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/kitzo.esm.js",
|
|
10
|
+
"require": "./dist/kitzo.umd.js",
|
|
11
|
+
"types": "./dist/kitzo.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite",
|
|
19
|
+
"build": "rollup -c",
|
|
20
|
+
"preview": "vite preview"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"tooltip",
|
|
24
|
+
"ripple",
|
|
25
|
+
"copy-button",
|
|
26
|
+
"micro-library",
|
|
27
|
+
"modular",
|
|
28
|
+
"ui",
|
|
29
|
+
"javascript",
|
|
30
|
+
"kitzo"
|
|
31
|
+
],
|
|
32
|
+
"author": "Riyad",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/riyad-96/kitzo"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/riyad-96/kitzo#readme",
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"rollup": "^4.46.2",
|
|
41
|
+
"vite": "^7.0.4"
|
|
42
|
+
}
|
|
43
|
+
}
|