tolltop 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/package.json +6 -2
  3. package/tolltop.js +38 -7
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # tolltop
2
2
 
3
3
  Tiny, dependency-free tooltips with smart edge-aware positioning. One attribute to add a
4
- tooltip, one call to configure them all, no build step, and it works in every browser
5
- released since mid-2023 (Chrome/Edge 114+, Firefox 114+, Safari 16.5+).
4
+ tooltip, one call to configure them all, no build step, and it works in all modern
5
+ browsers.
6
6
 
7
7
  ## Install
8
8
 
@@ -42,7 +42,7 @@ tolltop({
42
42
  padding: '6px 9px',
43
43
  maxWidth: 240, // px
44
44
  placement: 'auto', // 'auto' | 'top' | 'bottom'
45
- gap: 8, // px between the trigger and the tooltip
45
+ gap: 10, // px between the trigger and the tooltip
46
46
  edge: 24, // px min gap from each viewport side
47
47
  });
48
48
  ```
@@ -56,7 +56,7 @@ tolltop({
56
56
  | `padding` | `'8px 12px'` | `6px 9px` |
57
57
  | `maxWidth` | `320` | `240` |
58
58
  | `placement` | `'top'` | `'auto'` |
59
- | `gap` | `10` | `8` |
59
+ | `gap` | `12` | `10` |
60
60
  | `edge` | `16` | `24` |
61
61
 
62
62
  Calling `tolltop()` with no arguments returns the current config. The only per-element
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "tolltop",
3
- "version": "3.0.0",
4
- "description": "Tiny dependency-free tooltips with smart edge-aware positioning. One attribute, no build, works in browsers back to mid-2023.",
3
+ "version": "3.0.1",
4
+ "description": "Tiny dependency-free tooltips with smart edge-aware positioning. One attribute, no build, works in all modern browsers.",
5
5
  "main": "tolltop.js",
6
6
  "style": "tolltop.css",
7
+ "scripts": {
8
+ "build": "node build.js",
9
+ "prepublishOnly": "node build.js"
10
+ },
7
11
  "files": [
8
12
  "tolltop.js",
9
13
  "tolltop.css"
package/tolltop.js CHANGED
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * tolltop v3.0.1
3
+ * Tiny edge-aware tooltips with smart positioning. One attribute, one config call.
4
+ * MIT License · https://github.com/panphora/tolltop
5
+ */
1
6
  (() => {
2
7
  'use strict';
3
8
  if (window.__tolltop) return;
@@ -7,8 +12,8 @@
7
12
  const TIP_ID = 'tolltop-tip';
8
13
 
9
14
  // Baseline styles, injected only if tolltop.css isn't already on the page.
10
- // Keep in sync with tolltop.css.
11
- const CSS = `.tolltop{--tt-bg:#18181b;--tt-color:#e4e4e7;--tt-radius:6px;--tt-font-size:12px;--tt-padding:6px 9px;--tt-arrow:6px;--tt-arrow-x:50%;position:fixed;top:0;left:0;z-index:2147483647;box-sizing:border-box;margin:0;width:max-content;max-width:240px;padding:var(--tt-padding);border-radius:var(--tt-radius);background:var(--tt-bg);color:var(--tt-color);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:var(--tt-font-size);font-weight:500;line-height:1.4;text-align:center;white-space:normal;overflow-wrap:break-word;pointer-events:none;box-shadow:0 2px 12px rgba(0,0,0,.3);opacity:0;visibility:hidden}.tolltop[data-show]{opacity:1;visibility:visible}.tolltop::after{content:"";position:absolute;left:var(--tt-arrow-x);transform:translateX(-50%);width:0;height:0;border:var(--tt-arrow) solid transparent}.tolltop[data-placement="top"]::after{top:100%;border-bottom-width:0;border-top-color:var(--tt-bg)}.tolltop[data-placement="bottom"]::after{bottom:100%;border-top-width:0;border-bottom-color:var(--tt-bg)}`;
15
+ // Generated from tolltop.css by build.js; do not edit by hand. Run `npm run build`.
16
+ const CSS = `.tolltop{--tt-bg:#18181b;--tt-color:#e4e4e7;--tt-radius:6px;--tt-font-size:12px;--tt-padding:6px 9px;--tt-arrow:6px;--tt-arrow-x:50%;position:fixed;top:0;left:0;z-index:2147483647;box-sizing:border-box;margin:0;width:max-content;max-width:240px;padding:var(--tt-padding);border-radius:var(--tt-radius);background:var(--tt-bg);color:var(--tt-color);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:var(--tt-font-size);font-weight:500;line-height:1.4;text-align:center;white-space:normal;overflow-wrap:break-word;pointer-events:none;box-shadow:0 2px 12px rgba(0,0,0,0.3);opacity:0;visibility:hidden;}.tolltop[data-show]{opacity:1;visibility:visible;}.tolltop::after{content:"";position:absolute;left:var(--tt-arrow-x);transform:translateX(-50%);width:0;height:0;border:var(--tt-arrow) solid transparent;}.tolltop[data-placement="top"]::after{top:100%;border-bottom-width:0;border-top-color:var(--tt-bg);}.tolltop[data-placement="bottom"]::after{bottom:100%;border-top-width:0;border-bottom-color:var(--tt-bg);}`;
12
17
 
13
18
  const cfg = {
14
19
  bg: null,
@@ -18,7 +23,7 @@
18
23
  padding: null,
19
24
  maxWidth: 240,
20
25
  placement: 'auto',
21
- gap: 8,
26
+ gap: 10,
22
27
  edge: 24,
23
28
  };
24
29
 
@@ -68,7 +73,7 @@
68
73
  // clientWidth/Height exclude the scrollbar; innerWidth would not.
69
74
  const vw = document.documentElement.clientWidth;
70
75
  const vh = document.documentElement.clientHeight;
71
- const gap = Math.max(0, numOr(cfg.gap, 8));
76
+ const gap = Math.max(0, numOr(cfg.gap, 10));
72
77
  const edge = Math.max(0, numOr(cfg.edge, 24));
73
78
 
74
79
  const maxW = Math.min(numOr(cfg.maxWidth, 240), vw - edge * 2);
@@ -133,10 +138,36 @@
133
138
  if (tip) tip.removeAttribute('data-show');
134
139
  }
135
140
 
141
+ // True when a scrollable/overflow ancestor fully clips the element out of its box.
142
+ function clippedOut(el) {
143
+ const pos = getComputedStyle(el).position;
144
+ if (pos === 'fixed') return false; // viewport-positioned, not clipped by scroll ancestors
145
+ const r = el.getBoundingClientRect();
146
+ // An absolute element isn't clipped by overflow ancestors below its containing block.
147
+ const cb = pos === 'absolute' ? el.offsetParent : null;
148
+ let node = el.parentElement;
149
+ let clips = !cb;
150
+ while (node && node !== document.documentElement) {
151
+ if (!clips && node === cb) clips = true;
152
+ // Skip non-rendered/zero-box ancestors (e.g. display:contents) that don't actually clip.
153
+ if (clips && (node.clientWidth || node.clientHeight)) {
154
+ const o = getComputedStyle(node);
155
+ if (/auto|scroll|hidden|clip/.test(o.overflow + o.overflowX + o.overflowY)) {
156
+ const c = node.getBoundingClientRect();
157
+ const left = c.left + node.clientLeft; // padding box: where overflow actually clips
158
+ const top = c.top + node.clientTop;
159
+ if (r.bottom <= top || r.top >= top + node.clientHeight || r.right <= left || r.left >= left + node.clientWidth) return true;
160
+ }
161
+ }
162
+ node = node.parentElement;
163
+ }
164
+ return false;
165
+ }
166
+
136
167
  function reposition() {
137
168
  if (!active) return;
138
- // Hide if the trigger has been removed or scrolled out of view (a fixed tip
139
- // isn't clipped by an ancestor's overflow, so it would otherwise float free).
169
+ // A fixed tip isn't clipped by an ancestor's overflow, so hide it ourselves when the
170
+ // trigger is removed, scrolled out of the viewport, or clipped out of a scroll container.
140
171
  if (!active.isConnected) {
141
172
  hide();
142
173
  return;
@@ -144,7 +175,7 @@
144
175
  const r = active.getBoundingClientRect();
145
176
  const vw = document.documentElement.clientWidth;
146
177
  const vh = document.documentElement.clientHeight;
147
- if (r.bottom < 0 || r.top > vh || r.right < 0 || r.left > vw) {
178
+ if (r.bottom < 0 || r.top > vh || r.right < 0 || r.left > vw || clippedOut(active)) {
148
179
  hide();
149
180
  return;
150
181
  }