jizy-tooltip 2.1.3 → 2.2.7

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.
@@ -1,2 +1,2 @@
1
- /*! jTooltip v2.1.3 | MIT | 2026-04-23T09:35Z | [default] */
2
- @keyframes tooltip-anim{0%{opacity:0;transform:matrix(.5,0,0,.8,0,0)}20%{transform:matrix(1,0,0,1,0,0)}40%{opacity:1}70%{transform:matrix(1,0,0,1,0,0)}to{transform:matrix(1,0,0,1,0,0)}}.tooltip{animation:tooltip-anim .7s;background-color:#333;border-radius:4px;box-shadow:0 0 4px hsla(0,0%,100%,.7);color:#fff;font-family:inherit;font-size:14px;line-height:1;max-width:170px;opacity:0;position:absolute;text-align:center;transform:translateZ(0);transition:opacity .8s;z-index:234567891}.tooltip:empty{display:none!important;opacity:0!important}.tooltip .tip-header{border-bottom:1px solid #fff;padding:2px 4px}.tooltip .tip-content{padding:8px 10px}.tooltip.arrow:after{border:0 solid transparent;box-shadow:0 0 4px hsla(0,0%,100%,.7);content:"";display:block;position:absolute}.tooltip.arrow.left:after,.tooltip.arrow.right:after{top:50%;transform:translateY(-50%)}.tooltip.arrow.bottom:after,.tooltip.arrow.top:after{left:50%;transform:translateX(-50%)}.tooltip.arrow.left:after{border-left-color:#333;border-width:6px 0 6px 8px;right:-8px}.tooltip.arrow.right:after{border-right-color:#333;border-width:6px 8px 6px 0;left:-8px}.tooltip.arrow.bottom:after{border-top-color:#333;border-width:0 6px 8px;top:-8px}.tooltip.arrow.top:after{border-bottom-color:#333;border-width:8px 6px 0;bottom:-8px}
1
+ /*! jTooltip v2.2.7 | MIT | 2026-06-01T11:24Z | [default] */
2
+ :root{--jizy-tooltip-font-family:inherit;--jizy-tooltip-font-size:14px;--jizy-tooltip-width:170px;--jizy-tooltip-bg-color:#333;--jizy-tooltip-fg-color:#fff}.jtip{background-color:var(--jizy-tooltip-bg-color);border-radius:4px;box-shadow:0 0 4px rgba(0,0,0,.25);color:var(--jizy-tooltip-fg-color);font-family:var(--jizy-tooltip-font-family);font-size:var(--jizy-tooltip-font-size);line-height:1;max-width:var(--jizy-tooltip-width);opacity:0;pointer-events:none;position:absolute;text-align:center;transform:translateZ(0);transition:opacity .25s ease;z-index:234567891}.jtip.fade-in{opacity:1}.jtip.fade-out{opacity:0}.jtip.hidden{display:none}.jtip:empty{display:none!important;opacity:0!important}.jtip .tip-header{border-bottom:1px solid var(--jizy-tooltip-fg-color);padding:2px 4px}.jtip .tip-content{padding:8px 10px}.jtip:after{border:0 solid transparent;content:"";display:block;position:absolute}.jtip.left:after,.jtip.right:after{top:var(--jtip-arrow-y,50%);transform:translateY(-50%)}.jtip.bottom:after,.jtip.top:after{left:var(--jtip-arrow-x,50%);transform:translateX(-50%)}.jtip.left:after{border-left-color:var(--jizy-tooltip-bg-color);border-width:6px 0 6px 8px;right:-8px}.jtip.right:after{border-right-color:var(--jizy-tooltip-bg-color);border-width:6px 8px 6px 0;left:-8px}.jtip.top:after{border-top-color:var(--jizy-tooltip-bg-color);border-width:8px 6px 0;bottom:-8px}.jtip.bottom:after{border-bottom-color:var(--jizy-tooltip-bg-color);border-width:0 6px 8px;top:-8px}
@@ -1,2 +1,2 @@
1
- /*! jTooltip v2.1.3 | MIT | 2026-04-23T09:35Z | [default] */
2
- !function(t){"use strict";if("object"!=typeof t||!t||!t.document)throw new Error("jTooltip requires a window and a document");if(void 0!==t.jTooltip)throw new Error("jTooltip is already defined");!function(){class t{constructor(t){this.el=t,this.uuid=t.dataset.tipId||autoUuid(),this.header=t.dataset.tipHeader||"",this.content=t.dataset.tip||"",this.theme=t.dataset.tipTheme||"",this.position=t.dataset.tipPosition||"top",this.coords=null}setCoords(){const t=this.el.getBoundingClientRect();this.coords={top:t.top+window.scrollY,left:t.left+window.scrollX,right:t.right+window.scrollX,bottom:t.bottom+window.scrollY,width:t.width,height:t.height}}autoUuid(){return crypto.randomUUID?crypto.randomUUID():"tip-"+Date.now()+Math.random().toString(16).slice(2)}}}();t.jTooltip=Tooltip}("undefined"!=typeof window?window:this);
1
+ /*! jTooltip v2.2.7 | MIT | 2026-06-01T11:24Z | [default] */
2
+ !function(t){"use strict";if("object"!=typeof t||!t||!t.document)throw new Error("jTooltip requires a window and a document");if(void 0!==t.jTooltip)throw new Error("jTooltip is already defined");var e=function(t){class e{constructor(t){this.el=t,this.uuid=t.dataset.tipId||this.autoUuid(),this.header=t.dataset.tipHeader||"",this.content=t.dataset.tip||"",this.theme=t.dataset.tipTheme||"",this.position=t.dataset.tipPosition||"top",this.coords=null}setCoords(){const t=this.el.getBoundingClientRect();this.coords={top:t.top+window.scrollY,left:t.left+window.scrollX,right:t.right+window.scrollX,bottom:t.bottom+window.scrollY,width:t.width,height:t.height}}autoUuid(){return crypto.randomUUID?crypto.randomUUID():"tip-"+Date.now()+Math.random().toString(16).slice(2)}}return t.Tooltip=class{constructor(t="jTip",e=10){this.id=t,this.distance=e,this.shown=null,this.customRenderer=null,this.element=null,this.uuid=1,this.hideTimer=null}setRenderer(t){return"function"==typeof t&&(this.customRenderer=t),this}ready(){return this.element=this.getElement(),document.body.addEventListener("keyup",t=>{"Escape"!==t.key&&"Esc"!==t.key||this.hide()}),document.body.addEventListener("click",t=>{this.element.contains(t.target)||this.hide()}),document.body.addEventListener("touchstart",t=>{this.element.contains(t.target)||this.hide()}),this}getElement(){let t=document.querySelector("#"+this.id);return t&&0!==t.length||(t=document.createElement("div"),t.id=this.id,document.body.appendChild(t)),t.setAttribute("role","tooltip"),t.setAttribute("aria-hidden","true"),t.classList.add("jtip","hidden"),t}position(t,e){let i,s;const o=this.element.offsetWidth,n=this.element.offsetHeight,h=document.documentElement.clientWidth;"center"===e?(s=t.top+t.height/2-n/2,i=t.left+t.width/2-o/2):"left"===e||"right"===e?(s=(t.top+t.bottom)/2-n/2,"left"===e?(i=t.left-this.distance-o,i<0&&(i=this.distance)):(i=t.right+this.distance,i+o>h&&(i=h-o-this.distance))):(i=t.left+(t.width-o)/2,s="bottom"===e?t.bottom+this.distance:t.top-n-this.distance),i<0&&(i=t.left),s<0&&(s=t.bottom+this.distance);const d=t.left+t.width/2,l=t.top+t.height/2,r=Math.max(12,Math.min(o-12,d-i)),a=Math.max(12,Math.min(n-12,l-s));this.element.style.setProperty("--jtip-arrow-x",r+"px"),this.element.style.setProperty("--jtip-arrow-y",a+"px"),this.element.style.left=i+"px",this.element.style.top=s+"px"}cleanClasses(){this.element.className.split(" ").forEach(t=>{t.match(/theme-|arrow|top|right|bottom|left/)&&this.element.classList.remove(t)})}show(t){this.shown!==t.uuid&&(this.hideTimer&&(clearTimeout(this.hideTimer),this.hideTimer=null),t.setCoords(),this.shown=t.uuid,this.cleanClasses(),this.element.innerHTML=this.customRenderer?this.customRenderer(t):this.getTemplate(t),this.element.classList.add(t.position),t.theme&&t.theme.split(" ").forEach(t=>{this.element.classList.add(t)}),this.element.classList.remove("fade-out"),this.element.classList.remove("hidden"),this.position(t.coords,t.position),this.element.setAttribute("aria-hidden","false"),this.element.classList.add("fade-in"))}hide(t){!this.shown||t&&t.uuid!==this.shown||(this.shown=null,this.element.classList.remove("fade-in"),this.element.classList.add("fade-out"),this.hideTimer=setTimeout(()=>{this.hideTimer=null,this.element.innerHTML="",this.cleanClasses(),this.element.classList.add("hidden"),this.element.classList.remove("fade-out"),this.element.setAttribute("aria-hidden","true")},200))}getTemplate(t){let e="<div>";return t.header&&(e+='<div class="tip-header">'+t.header+"</div>"),e+='<div class="tip-content">'+t.content+"</div>",e+="</div>",e}fromElement(t){if(!t)return;t.dataset.tipId||(t.dataset.tipId="d-"+this.uuid++);const i=new e(t);this.show(i)}init(){this.ready()}},t}({});t.jTooltip=e.Tooltip}("undefined"!=typeof window?window:this);
package/lib/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import Tooltip from './js/Tooltip.js';
2
- import Tip from './js/Tip.js';
3
-
4
- export default { Tooltip, Tip };
1
+ import Tooltip from './js/Tooltip.js';
2
+
3
+ export { Tooltip };
package/lib/js/Tip.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export default class Tip {
2
2
  constructor(el) {
3
3
  this.el = el;
4
- this.uuid = el.dataset.tipId || autoUuid();
4
+ this.uuid = el.dataset.tipId || this.autoUuid();
5
5
  this.header = el.dataset.tipHeader || '';
6
6
  this.content = el.dataset.tip || '';
7
7
  this.theme = el.dataset.tipTheme || '';
package/lib/js/Tooltip.js CHANGED
@@ -8,6 +8,7 @@ export default class Tooltip {
8
8
  this.customRenderer = null;
9
9
  this.element = null;
10
10
  this.uuid = 1;
11
+ this.hideTimer = null;
11
12
  }
12
13
 
13
14
  setRenderer(callback) {
@@ -82,8 +83,20 @@ export default class Tooltip {
82
83
  if (left < 0) left = trigger.left;
83
84
  if (top < 0) top = trigger.bottom + this.distance;
84
85
 
86
+ // Point the arrow at the trigger's centre rather than the tip's centre:
87
+ // when the tip is shifted (e.g. clamped to the viewport edge) the arrow
88
+ // must track the trigger, not sit in the middle of the box.
89
+ const triggerCx = trigger.left + trigger.width / 2;
90
+ const triggerCy = trigger.top + trigger.height / 2;
91
+ const arrowX = Math.max(12, Math.min(tipW - 12, triggerCx - left));
92
+ const arrowY = Math.max(12, Math.min(tipH - 12, triggerCy - top));
93
+ this.element.style.setProperty('--jtip-arrow-x', arrowX + 'px');
94
+ this.element.style.setProperty('--jtip-arrow-y', arrowY + 'px');
95
+
96
+ // trigger coords already include the scroll offset (setCoords adds
97
+ // scrollX/scrollY), so don't add it again here.
85
98
  this.element.style.left = left + 'px';
86
- this.element.style.top = top + window.pageYOffset + 'px';
99
+ this.element.style.top = top + 'px';
87
100
  }
88
101
 
89
102
  cleanClasses() {
@@ -97,11 +110,18 @@ export default class Tooltip {
97
110
  show(tip) {
98
111
  if (this.shown === tip.uuid) return;
99
112
 
113
+ // cancel any pending hide so moving between triggers doesn't blank the
114
+ // freshly-shown tip mid-transition.
115
+ if (this.hideTimer) {
116
+ clearTimeout(this.hideTimer);
117
+ this.hideTimer = null;
118
+ }
119
+
100
120
  tip.setCoords();
101
121
  this.shown = tip.uuid;
102
122
  this.cleanClasses();
103
123
 
104
- this.element.innerHTML = this.renderer ? this.renderer(tip) : this.getTemplate(tip);
124
+ this.element.innerHTML = this.customRenderer ? this.customRenderer(tip) : this.getTemplate(tip);
105
125
  this.element.classList.add(tip.position);
106
126
 
107
127
  if (tip.theme) {
@@ -110,11 +130,19 @@ export default class Tooltip {
110
130
  });
111
131
  }
112
132
 
133
+ // Drop any leftover fade-out from a still-pending hide of a previous
134
+ // trigger — otherwise it wins over fade-in (later in the cascade) and
135
+ // the freshly-shown tip stays invisible when switching fast.
136
+ this.element.classList.remove('fade-out');
137
+
138
+ // Lay the element out (still transparent via the base .jtip opacity:0)
139
+ // BEFORE measuring/positioning — otherwise offsetWidth/Height read 0
140
+ // while it is display:none and the placement math collapses.
141
+ this.element.classList.remove('hidden');
113
142
  this.position(tip.coords, tip.position);
114
143
 
115
144
  this.element.setAttribute('aria-hidden', 'false');
116
145
  this.element.classList.add('fade-in');
117
- this.element.classList.remove('hidden');
118
146
  }
119
147
 
120
148
  hide(tip) {
@@ -123,7 +151,8 @@ export default class Tooltip {
123
151
  this.element.classList.remove('fade-in');
124
152
  this.element.classList.add('fade-out');
125
153
 
126
- setTimeout(() => {
154
+ this.hideTimer = setTimeout(() => {
155
+ this.hideTimer = null;
127
156
  this.element.innerHTML = '';
128
157
  this.cleanClasses();
129
158
  this.element.classList.add('hidden');
@@ -1,90 +1,104 @@
1
- .tooltip {
2
- // display: block;
3
- max-width: @tooltip-width;
4
- position: absolute;
5
- opacity: 0;
6
- border-radius: 4px;
7
- background-color: @tooltip-bg-color;
8
- font-family: @tooltip-font-family;
9
- font-size: @tooltip-font-size;
10
- line-height: 1;
11
- color: @tooltip-fg-color;
12
- text-align: center;
13
- transition: opacity .8s;
14
- transform: translateZ(0); // GPU
15
- box-shadow: 0 0 4px rgba(255, 255, 255, .7);
16
- animation: tooltip-anim .7s;
17
- z-index: 234567891;
18
-
19
- &:empty {
20
- display: none !important;
21
- opacity: 0 !important;
22
- }
23
-
24
- .tip-header {
25
- padding: 2px 4px;
26
- border-bottom: 1px solid @tooltip-fg-color;
27
- }
28
-
29
- .tip-content {
30
- padding: 8px 10px;
31
- }
32
-
33
- &.arrow {
34
- &::after {
35
- position: absolute;
36
- display: block;
37
- content: "";
38
- border: 0 solid transparent;
39
- box-shadow: 0 0 4px rgba(255, 255, 255, .7);
40
- }
41
-
42
- // position arrow in the center Y
43
- &.left::after,
44
- &.right::after {
45
- top: 50%;
46
- transform: translateY(-50%);
47
- }
48
-
49
- // position arrow in the center X
50
- &.top::after,
51
- &.bottom::after {
52
- left: 50%;
53
- transform: translateX(-50%);
54
- }
55
-
56
- // position arrow on the right
57
- &.left::after {
58
- right: -8px;
59
- border-width: 6px 0 6px 8px;
60
- border-left-color: @tooltip-bg-color;
61
- }
62
-
63
- // position arrow on the left
64
- &.right::after {
65
- left: -8px;
66
- border-width: 6px 8px 6px 0;
67
- border-right-color: @tooltip-bg-color;
68
- }
69
-
70
- // position arrow in the top
71
- &.bottom::after {
72
- top: -8px;
73
- border-width: 0 6px 8px 6px;
74
- border-top-color: @tooltip-bg-color;
75
- }
76
-
77
- // position arrow in the bottom
78
- &.top::after {
79
- bottom: -8px;
80
- border-width: 8px 6px 0 6px;
81
- border-bottom-color: @tooltip-bg-color;
82
- }
83
- }
84
- }
85
-
86
- // [data-tooltip] {
87
- // cursor: pointer;
88
- // color: #7cb342;
89
- // display: inline-block;
90
- // }
1
+ // The singleton tip element (id #jTip) gets the base class `jtip` plus state
2
+ // classes toggled by Tooltip.js at runtime:
3
+ // .hidden — not currently shown (default)
4
+ // .fade-in — visible (opacity transition in)
5
+ // .fade-out — transitioning out
6
+ // .top/.bottom/.left/.right — placement relative to the trigger (drives arrow)
7
+ .jtip {
8
+ position: absolute;
9
+ max-width: var(--jizy-tooltip-width);
10
+ border-radius: 4px;
11
+ background-color: var(--jizy-tooltip-bg-color);
12
+ font-family: var(--jizy-tooltip-font-family);
13
+ font-size: var(--jizy-tooltip-font-size);
14
+ line-height: 1;
15
+ color: var(--jizy-tooltip-fg-color);
16
+ text-align: center;
17
+ box-shadow: 0 0 4px rgba(0, 0, 0, .25);
18
+ transform: translateZ(0); // GPU
19
+ z-index: 234567891;
20
+
21
+ // Visibility is driven by the state classes, not by a one-shot animation —
22
+ // the element is a reused singleton, so a mount animation would only ever
23
+ // play once.
24
+ opacity: 0;
25
+ pointer-events: none;
26
+ transition: opacity .25s ease;
27
+
28
+ &.fade-in {
29
+ opacity: 1;
30
+ }
31
+
32
+ &.fade-out {
33
+ opacity: 0;
34
+ }
35
+
36
+ &.hidden {
37
+ display: none;
38
+ }
39
+
40
+ &:empty {
41
+ display: none !important;
42
+ opacity: 0 !important;
43
+ }
44
+
45
+ .tip-header {
46
+ padding: 2px 4px;
47
+ border-bottom: 1px solid var(--jizy-tooltip-fg-color);
48
+ }
49
+
50
+ .tip-content {
51
+ padding: 8px 10px;
52
+ }
53
+
54
+ // Arrow — rendered for every placed tip, pointing back at the trigger.
55
+ &::after {
56
+ position: absolute;
57
+ display: block;
58
+ content: "";
59
+ border: 0 solid transparent;
60
+ }
61
+
62
+ // Arrow offset along the relevant edge is set by Tooltip.js (--jtip-arrow-x
63
+ // / --jtip-arrow-y) so the arrow points at the trigger even when the tip is
64
+ // shifted; falls back to centred if the script hasn't set it.
65
+ &.left::after,
66
+ &.right::after {
67
+ top: var(--jtip-arrow-y, 50%);
68
+ transform: translateY(-50%);
69
+ }
70
+
71
+ &.top::after,
72
+ &.bottom::after {
73
+ left: var(--jtip-arrow-x, 50%);
74
+ transform: translateX(-50%);
75
+ }
76
+
77
+ // tip sits to the LEFT of the trigger → arrow on the tip's right edge
78
+ &.left::after {
79
+ right: -8px;
80
+ border-width: 6px 0 6px 8px;
81
+ border-left-color: var(--jizy-tooltip-bg-color);
82
+ }
83
+
84
+ // tip sits to the RIGHT of the trigger → arrow on the tip's left edge
85
+ &.right::after {
86
+ left: -8px;
87
+ border-width: 6px 8px 6px 0;
88
+ border-right-color: var(--jizy-tooltip-bg-color);
89
+ }
90
+
91
+ // tip sits ABOVE the trigger → arrow on the tip's bottom edge
92
+ &.top::after {
93
+ bottom: -8px;
94
+ border-width: 8px 6px 0 6px;
95
+ border-top-color: var(--jizy-tooltip-bg-color);
96
+ }
97
+
98
+ // tip sits BELOW the trigger → arrow on the tip's top edge
99
+ &.bottom::after {
100
+ top: -8px;
101
+ border-width: 0 6px 8px 6px;
102
+ border-bottom-color: var(--jizy-tooltip-bg-color);
103
+ }
104
+ }
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Default theming exposed as CSS custom properties.
3
+ *
4
+ * Override at `:root` from a consumer stylesheet loaded AFTER this CSS:
5
+ *
6
+ * :root {
7
+ * --jizy-tooltip-bg-color: #1a1a1a;
8
+ * --jizy-tooltip-fg-color: #eee;
9
+ * --jizy-tooltip-width: 220px;
10
+ * }
11
+ */
12
+ :root {
13
+ --jizy-tooltip-font-family: inherit;
14
+ --jizy-tooltip-font-size: 14px;
15
+ --jizy-tooltip-width: 170px;
16
+ --jizy-tooltip-bg-color: #333;
17
+ --jizy-tooltip-fg-color: #fff;
18
+ }
package/lib/tooltip.less CHANGED
@@ -1,4 +1,2 @@
1
- @import (optional) url("less/_variables.less");
2
- @import url("less/variables.less");
3
- @import url("less/animations.less");
4
- @import url("less/structure.less");
1
+ @import url("less/theme.less");
2
+ @import url("less/structure.less");
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "jizy-tooltip",
3
- "version": "2.1.3",
3
+ "version": "2.2.7",
4
4
  "browser": "dist/js/jizy-tooltip.min.js",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
7
7
  "jizy": "dist/",
8
- "keywords": [
9
- "JiZy",
10
- "JS",
11
- "Tooltip"
12
- ],
13
8
  "author": "Joffrey Demetz <joffrey.demetz@gmail.com> (https://joffreydemetz.com/)",
14
9
  "license": "MIT",
15
10
  "description": "A lightweight JS tooltip module",
16
11
  "homepage": "https://jizy.joffreydemetz.com/tooltip",
12
+ "keywords": [
13
+ "JiZy",
14
+ "Tooltip"
15
+ ],
17
16
  "files": [
18
17
  "dist/*",
19
18
  "lib/*"
20
19
  ],
21
20
  "scripts": {
22
- "jpack:dist": "node ./cli/jpack.js"
21
+ "jpack:dist": "node ./cli/jpack.js",
22
+ "test": "node --test \"tests/*.test.js\""
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "devDependencies": {
29
29
  "jizy-packer": "^2.1.44",
30
+ "jsdom": "^25.0.1",
30
31
  "less": "^4.4.1"
31
32
  }
32
33
  }
@@ -1,22 +0,0 @@
1
- @keyframes tooltip-anim {
2
- 0% {
3
- opacity: 0;
4
- transform: matrix(0.5, 0, 0, 0.8, 0, 0);
5
- }
6
-
7
- 20% {
8
- transform: matrix(1, 0, 0, 1, 0, 0);
9
- }
10
-
11
- 40% {
12
- opacity: 1;
13
- }
14
-
15
- 70% {
16
- transform: matrix(1, 0, 0, 1, 0, 0);
17
- }
18
-
19
- 100% {
20
- transform: matrix(1, 0, 0, 1, 0, 0);
21
- }
22
- }
@@ -1,7 +0,0 @@
1
- @desktop-breakpoint: 768px;
2
- @mobile-breakpoint: @desktop-breakpoint - 1px;
3
- @tooltip-font-family: inherit;
4
- @tooltip-font-size: 14px;
5
- @tooltip-width: 170px;
6
- @tooltip-bg-color: #333;
7
- @tooltip-fg-color: #FFF;