interestfor 1.0.6 → 1.0.8

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 CHANGED
@@ -1,12 +1,11 @@
1
1
  # `interestfor` Polyfill
2
2
 
3
3
  A polyfill for the `interestfor` attribute. This is a new API for "interest
4
- interactions" that is in the process of being implemented in browsers. It will
5
- [likely ship soon in
6
- Chrome](https://groups.google.com/a/chromium.org/g/blink-dev/c/bX1G_yDt6W4?e=48417069).
7
- See the explainer for all of the details for the "real" feature:
4
+ interactions" that is in the process of being implemented in browsers. It [shipped](https://chromestatus.com/feature/4530756656562176)
5
+ in Chromium browsers (e.g. Chrome, Edge) in version 142 (October 2025).
6
+ See the MDN documentation for more details:
8
7
 
9
- https://open-ui.org/components/interest-invokers.explainer/
8
+ https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using_interest_invokers
10
9
 
11
10
  ## Usage
12
11
  To use this polyfill, simply load it:
@@ -119,6 +118,11 @@ polyfill (optionally minified) or the native feature. You can run tests directly
119
118
  from this repo,
120
119
  [here](https://mfreed7.github.io/interestfor/test/test.html).
121
120
 
121
+ ## Browser Compatibility
122
+
123
+ This polyfill uses all standard APIs, and is therefore compatible with all
124
+ modern browsers, including Safari and Firefox.
125
+
122
126
  ## Improvements / Bugs
123
127
 
124
128
  If you find issues with the polyfill, feel free to file them [here](https://github.com/mfreed7/interestfor/issues).
package/build.sh CHANGED
@@ -11,7 +11,7 @@ MINIFIED_FILE="${BUILD_DIR}/interestfor.min.js"
11
11
  OUTPUT_FILE="${SRC_DIR}/interestfor.min.js"
12
12
 
13
13
  # Minify the combined file
14
- terser "${SOURCE_FILE}" --compress --mangle --output "${MINIFIED_FILE}"
14
+ npx terser "${SOURCE_FILE}" --compress --mangle --output "${MINIFIED_FILE}"
15
15
 
16
16
  cat COPYRIGHT "${MINIFIED_FILE}" > "${OUTPUT_FILE}"
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "interestfor",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "A polyfill for the `interestfor` API",
5
5
  "keywords": "interestfor, interesttarget, polyfill, hover-triggering",
6
6
  "homepage": "https://github.com/mfreed7/interestfor#readme",
@@ -11,7 +11,5 @@
11
11
  },
12
12
  "author": "Mason Freed <mfreed@mfreed.com>",
13
13
  "license": "BSD-3-Clause",
14
- "main": "src/interestfor.min.js",
15
- "dependencies": {}
14
+ "main": "src/interestfor.min.js"
16
15
  }
17
-
@@ -44,6 +44,35 @@
44
44
  capture: true,
45
45
  });
46
46
  }
47
+
48
+ if(!window.InterestEvent) {
49
+ window.InterestEvent = class InterestEvent extends Event {
50
+ #source;
51
+ constructor(type, eventInit = {}) {
52
+ super(type, eventInit);
53
+ const { source } = eventInit;
54
+ if (source != null && !(source instanceof Element)) {
55
+ throw new TypeError(`source must be an element`);
56
+ }
57
+ this.#source = source;
58
+ }
59
+
60
+ get [Symbol.toStringTag]() {
61
+ return "InterestEvent";
62
+ }
63
+
64
+ get source() {
65
+ const source = this.#source;
66
+ if (!source) return null;
67
+ // Retarget `source` to the current event's root:
68
+ const sourceRoot = getRootNode(source);
69
+ if (sourceRoot !== getRootNode(this.target || document)) {
70
+ return sourceRoot.host;
71
+ }
72
+ return source;
73
+ }
74
+ }
75
+ }
47
76
 
48
77
  // Enum-like state and source
49
78
  const InterestState = {
@@ -58,6 +87,14 @@
58
87
  Touch: "touch",
59
88
  };
60
89
 
90
+ function getRootNode(node) {
91
+ if (node && typeof node.getRootNode === "function") {
92
+ return node.getRootNode();
93
+ }
94
+ if (node && node.parentNode) return getRootNode(node.parentNode);
95
+ return node;
96
+ }
97
+
61
98
  // Gain or lose interest
62
99
  function GainOrLoseInterest(invoker, target, newState) {
63
100
  if (!invoker || !target) {
@@ -208,7 +245,8 @@
208
245
  if (data.state !== InterestState.NoInterest) {
209
246
  throw new Error("Invalid state");
210
247
  }
211
- target.dispatchEvent(new Event(interestEventName));
248
+ const shouldContinue = target.dispatchEvent(new InterestEvent(interestEventName, { source: invoker }));
249
+ if (!shouldContinue) return;
212
250
  try {
213
251
  target.showPopover({ source: invoker });
214
252
  } catch {}
@@ -227,12 +265,15 @@
227
265
  if (!isPlainHint(target)) {
228
266
  invoker.setAttribute("aria-expanded", "true");
229
267
  }
230
- const anchorName = `--interest-anchor-${Math.random()
231
- .toString(36)
232
- .substring(2)}`;
233
- invoker.style.anchorName = anchorName;
234
- target.style.positionAnchor = anchorName;
235
- data.anchorName = anchorName;
268
+ if (getComputedStyle(invoker).anchorName === 'none' &&
269
+ getComputedStyle(target).positionAnchor === 'auto') {
270
+ const anchorName = `--interest-anchor-${Math.random()
271
+ .toString(36)
272
+ .substring(2)}`;
273
+ invoker.style.anchorName = anchorName;
274
+ target.style.positionAnchor = anchorName;
275
+ data.anchorName = anchorName;
276
+ }
236
277
  break;
237
278
  default:
238
279
  throw new Error("Invalid state");
@@ -240,13 +281,14 @@
240
281
  return true;
241
282
  }
242
283
 
243
- function clearState(invoker) {
284
+ function clearState(invoker, force = false) {
244
285
  const data = invoker[dataField];
245
286
  clearTimeout(data.gainedTimer);
246
287
  clearTimeout(data.lostTimer);
247
288
  if (data.state !== InterestState.NoInterest) {
248
289
  const target = GetInterestForTarget(invoker);
249
- target.dispatchEvent(new Event(loseInterestEventName));
290
+ const shouldContinue = target.dispatchEvent(new InterestEvent(loseInterestEventName, { source: invoker }));
291
+ if (!force && !shouldContinue) return;
250
292
  try {
251
293
  target.hidePopover();
252
294
  } catch {}
@@ -408,7 +450,7 @@
408
450
  document.body.addEventListener("keydown", (e) => {
409
451
  if (e.key === "Escape") {
410
452
  invokersWithInterest.forEach((invoker) => {
411
- clearState(invoker);
453
+ clearState(invoker, true);
412
454
  });
413
455
  }
414
456
  });
@@ -5,4 +5,4 @@
5
5
  // LICENSE file in the root directory of this source tree.
6
6
 
7
7
 
8
- !function(){const e="interest",t="loseinterest",n="--interest-delay-start",r="--interest-delay-end",o="--interest-delay",i="__interestForData",s="__interestForTargetData",a=new Set;let l=!1;if(window.interestForPolyfillInstalled)return;window.interestForPolyfillInstalled=!0;const c=HTMLButtonElement.prototype.hasOwnProperty("interestForElement");if(c&&!window.interestForUsePolyfillAlways)return;if(c){const n=e=>{e.isTrusted&&(e.preventDefault(),e.stopImmediatePropagation())};document.body.addEventListener(e,n,{capture:!0}),document.body.addEventListener(t,n,{capture:!0})}const d="none",u="full",m="hover",f="dehover",h="focus",g="blur";function y(t,n,r){if(!t||!n)return!1;if(!t.isConnected||b(t)!==n||r===d&&v(n)!==t)return!1;if(r!==d){const o=v(n);if(o){if(o===t)return o[i].clearLostTask(),!1;if(!y(o,n,d))return!1;if(!t.isConnected||b(t)!==n)return!1}return function(t,n){const r=t[i],o=b(t);if(n!==u)throw new Error("Invalid state");{if(r.state!==d)throw new Error("Invalid state");o.dispatchEvent(new Event(e));try{o.showPopover({source:t})}catch{}r.state=u,o[s]||(o[s]={}),o[s].invoker=t,o.hasAttribute("popover")&&(o[s].toggleListener=T,o.addEventListener("toggle",T)),a.add(t),t.classList.add("interest-source"),o.classList.add("interest-target"),P(o)||t.setAttribute("aria-expanded","true");const n=`--interest-anchor-${Math.random().toString(36).substring(2)}`;t.style.anchorName=n,o.style.positionAnchor=n,r.anchorName=n}return!0}(t,r)}return w(t),!0}function p(e){const t=1e3*E(e,r);!isFinite(t)||t<0||(e[i].clearLostTask(),e[i].lostTimer=setTimeout(()=>{y(e,b(e),d)},t))}function v(e){const t=e[s]?.invoker||null;return t&&t[i]?.state!==d?t:null}function b(e){const t=e.getAttribute("interestfor");return document.getElementById(t)}function T(e){const t=e.target;if("closed"===e.newState){const e=t[s]?.invoker;e&&y(e,t,d)}}function L(e){const t=String(e).trim(),n=t.match(/^([\d.]+)s$/);if(n)return parseFloat(n[1]);const r=t.match(/^([\d.]+)ms$/);return r?parseFloat(r[1])/1e3:parseFloat(t)||0}function E(e,t){const r=getComputedStyle(e),i=r.getPropertyValue(t).trim();if("normal"!==i.toLowerCase())return L(i);const s=r.getPropertyValue(o).trim();if(s&&"normal"!==s.toLowerCase()){const e=s.split(/\s+/).filter(e=>e.length>0);if(e.length>0){const r=e[0],o=e.length>1?e[1]:r;let i;if(i=t===n?r:o,"normal"!==i.toLowerCase())return L(i)}}return t===n?.5:.25}function w(e){const n=e[i];if(clearTimeout(n.gainedTimer),clearTimeout(n.lostTimer),n.state!==d){const r=b(e);r.dispatchEvent(new Event(t));try{r.hidePopover()}catch{}r[s]?.toggleListener&&r.removeEventListener("toggle",r[s].toggleListener),r[s]=null,a.delete(e),e.classList.remove("interest-source"),r.classList.remove("interest-target"),P(r)||e.setAttribute("aria-expanded","false"),n.anchorName&&(e.style.anchorName="",r.style.positionAnchor="",n.anchorName=null),n.state=d}}const k=["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])","iframe","object","embed","[contenteditable]",'[tabindex]:not([tabindex="-1"])'].join(",");function P(e){if("hint"!==e.getAttribute("popover")?.toLowerCase())return!1;if(e.querySelector(k))return!1;if(e.querySelector("h1,h2,h3,h4,h5,h6,ul,ol,li,table,nav,header,footer,main,aside,article,section,form,blockquote,details,summary,dialog"))return!1;const t=e.querySelectorAll("[role]");for(const e of t){const t=e.getAttribute("role").toLowerCase();if(!["presentation","none","generic","image"].includes(t))return!1}return!0}function A(e){if(e[i])return;e[i]={state:d,gainedTimer:null,lostTimer:null,longPressTimer:null,clearGainedTask(){clearTimeout(this.gainedTimer)},clearLostTask(){clearTimeout(this.lostTimer)}};const t=b(e);t&&function(e,t){P(t)?e.setAttribute("aria-describedby",t.id):(e.setAttribute("aria-details",t.id),e.setAttribute("aria-expanded","false"),t.hasAttribute("role")||t.setAttribute("role","tooltip"))}(e,t)}function C(e,t){if(l)return;if(!e.isConnected)return;if(!b(e)){const n=e.closest(".interest-target");if(n){const r=v(n);r&&(t===m||t===h?r[i].clearLostTask():t!==g&&e.matches(":hover")||p(r))}return}A(e);const r=e[i],o=v(e);t===m||t===h?(r.clearLostTask&&r.clearLostTask(),o&&o[i].clearLostTask(),function(e,t){const r=1e3*E(e,n);!isFinite(r)||r<0||(e[i].clearGainedTask(),e[i].gainedTimer=setTimeout(()=>{y(e,b(e),t)},r))}(e,u)):(r.clearGainedTask&&r.clearGainedTask(),r.state!==d&&p(e),o&&(o[i].clearGainedTask(),t!==g&&e.matches(":hover")||p(o)))}function F(){!function(){const e=document.createElement("style");e.textContent=`@property ${n} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${r} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${o} {syntax: "[ normal | <time> ]{1,2}"; inherits: false; initial-value: normal;}`,document.head.appendChild(e),document[i]={globalPropsStyle:e}}(),function(){const e=(e,t)=>{for(let n=e.target;n;n=n.parentElement)C(n,t)};document.body.addEventListener("mouseover",t=>e(t,m)),document.body.addEventListener("mouseout",t=>e(t,f)),document.body.addEventListener("focusin",t=>e(t,h)),document.body.addEventListener("focusout",t=>e(t,g)),document.body.addEventListener("keydown",e=>{"Escape"===e.key&&a.forEach(e=>{w(e)})}),document.body.addEventListener("touchstart",e=>{l=!0;const t=e.target.closest("button[interestfor]");t&&(A(t),t[i].longPressTimer=setTimeout(()=>{y(t,b(t),u),t[i].longPressTimer=null},500))});const t=e=>{const t=e.target.closest("button[interestfor]");t&&t[i]?.longPressTimer&&(clearTimeout(t[i].longPressTimer),t[i].longPressTimer=null)};document.body.addEventListener("touchend",e=>{t(e),l=!1}),document.body.addEventListener("touchmove",t)}(),console.log(`interestfor polyfill installed (native: ${c}).`)}"complete"===document.readyState?F():window.addEventListener("load",F)}();
8
+ !function(){const e="interest",t="loseinterest",n="--interest-delay-start",r="--interest-delay-end",o="--interest-delay",s="__interestForData",i="__interestForTargetData",a=new Set;let l=!1;if(window.interestForPolyfillInstalled)return;window.interestForPolyfillInstalled=!0;const c=HTMLButtonElement.prototype.hasOwnProperty("interestForElement");if(c&&!window.interestForUsePolyfillAlways)return;if(c){const n=e=>{e.isTrusted&&(e.preventDefault(),e.stopImmediatePropagation())};document.body.addEventListener(e,n,{capture:!0}),document.body.addEventListener(t,n,{capture:!0})}window.InterestEvent||(window.InterestEvent=class extends Event{#e;constructor(e,t={}){super(e,t);const{source:n}=t;if(null!=n&&!(n instanceof Element))throw new TypeError("source must be an element");this.#e=n}get[Symbol.toStringTag](){return"InterestEvent"}get source(){const e=this.#e;if(!e)return null;const t=p(e);return t!==p(this.target||document)?t.host:e}});const u="none",d="full",m="hover",f="dehover",h="focus",g="blur";function p(e){return e&&"function"==typeof e.getRootNode?e.getRootNode():e&&e.parentNode?p(e.parentNode):e}function y(t,n,r){if(!t||!n)return!1;if(!t.isConnected||T(t)!==n||r===u&&b(n)!==t)return!1;if(r!==u){const o=b(n);if(o){if(o===t)return o[s].clearLostTask(),!1;if(!y(o,n,u))return!1;if(!t.isConnected||T(t)!==n)return!1}return function(t,n){const r=t[s],o=T(t);if(n!==d)throw new Error("Invalid state");if(r.state!==u)throw new Error("Invalid state");if(!o.dispatchEvent(new InterestEvent(e,{source:t})))return;try{o.showPopover({source:t})}catch{}if(r.state=d,o[i]||(o[i]={}),o[i].invoker=t,o.hasAttribute("popover")&&(o[i].toggleListener=L,o.addEventListener("toggle",L)),a.add(t),t.classList.add("interest-source"),o.classList.add("interest-target"),P(o)||t.setAttribute("aria-expanded","true"),"none"===getComputedStyle(t).anchorName&&"auto"===getComputedStyle(o).positionAnchor){const e=`--interest-anchor-${Math.random().toString(36).substring(2)}`;t.style.anchorName=e,o.style.positionAnchor=e,r.anchorName=e}return!0}(t,r)}return k(t),!0}function v(e){const t=1e3*w(e,r);!isFinite(t)||t<0||(e[s].clearLostTask(),e[s].lostTimer=setTimeout(()=>{y(e,T(e),u)},t))}function b(e){const t=e[i]?.invoker||null;return t&&t[s]?.state!==u?t:null}function T(e){const t=e.getAttribute("interestfor");return document.getElementById(t)}function L(e){const t=e.target;if("closed"===e.newState){const e=t[i]?.invoker;e&&y(e,t,u)}}function E(e){const t=String(e).trim(),n=t.match(/^([\d.]+)s$/);if(n)return parseFloat(n[1]);const r=t.match(/^([\d.]+)ms$/);return r?parseFloat(r[1])/1e3:parseFloat(t)||0}function w(e,t){const r=getComputedStyle(e),s=r.getPropertyValue(t).trim();if("normal"!==s.toLowerCase())return E(s);const i=r.getPropertyValue(o).trim();if(i&&"normal"!==i.toLowerCase()){const e=i.split(/\s+/).filter(e=>e.length>0);if(e.length>0){const r=e[0],o=e.length>1?e[1]:r;let s;if(s=t===n?r:o,"normal"!==s.toLowerCase())return E(s)}}return t===n?.5:.25}function k(e,n=!1){const r=e[s];if(clearTimeout(r.gainedTimer),clearTimeout(r.lostTimer),r.state!==u){const o=T(e),s=o.dispatchEvent(new InterestEvent(t,{source:e}));if(!n&&!s)return;try{o.hidePopover()}catch{}o[i]?.toggleListener&&o.removeEventListener("toggle",o[i].toggleListener),o[i]=null,a.delete(e),e.classList.remove("interest-source"),o.classList.remove("interest-target"),P(o)||e.setAttribute("aria-expanded","false"),r.anchorName&&(e.style.anchorName="",o.style.positionAnchor="",r.anchorName=null),r.state=u}}const A=["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])","iframe","object","embed","[contenteditable]",'[tabindex]:not([tabindex="-1"])'].join(",");function P(e){if("hint"!==e.getAttribute("popover")?.toLowerCase())return!1;if(e.querySelector(A))return!1;if(e.querySelector("h1,h2,h3,h4,h5,h6,ul,ol,li,table,nav,header,footer,main,aside,article,section,form,blockquote,details,summary,dialog"))return!1;const t=e.querySelectorAll("[role]");for(const e of t){const t=e.getAttribute("role").toLowerCase();if(!["presentation","none","generic","image"].includes(t))return!1}return!0}function S(e){if(e[s])return;e[s]={state:u,gainedTimer:null,lostTimer:null,longPressTimer:null,clearGainedTask(){clearTimeout(this.gainedTimer)},clearLostTask(){clearTimeout(this.lostTimer)}};const t=T(e);t&&function(e,t){P(t)?e.setAttribute("aria-describedby",t.id):(e.setAttribute("aria-details",t.id),e.setAttribute("aria-expanded","false"),t.hasAttribute("role")||t.setAttribute("role","tooltip"))}(e,t)}function C(e,t){if(l)return;if(!e.isConnected)return;if(!T(e)){const n=e.closest(".interest-target");if(n){const r=b(n);r&&(t===m||t===h?r[s].clearLostTask():t!==g&&e.matches(":hover")||v(r))}return}S(e);const r=e[s],o=b(e);t===m||t===h?(r.clearLostTask&&r.clearLostTask(),o&&o[s].clearLostTask(),function(e,t){const r=1e3*w(e,n);!isFinite(r)||r<0||(e[s].clearGainedTask(),e[s].gainedTimer=setTimeout(()=>{y(e,T(e),t)},r))}(e,d)):(r.clearGainedTask&&r.clearGainedTask(),r.state!==u&&v(e),o&&(o[s].clearGainedTask(),t!==g&&e.matches(":hover")||v(o)))}function x(){!function(){const e=document.createElement("style");e.textContent=`@property ${n} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${r} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${o} {syntax: "[ normal | <time> ]{1,2}"; inherits: false; initial-value: normal;}`,document.head.appendChild(e),document[s]={globalPropsStyle:e}}(),function(){const e=(e,t)=>{for(let n=e.target;n;n=n.parentElement)C(n,t)};document.body.addEventListener("mouseover",t=>e(t,m)),document.body.addEventListener("mouseout",t=>e(t,f)),document.body.addEventListener("focusin",t=>e(t,h)),document.body.addEventListener("focusout",t=>e(t,g)),document.body.addEventListener("keydown",e=>{"Escape"===e.key&&a.forEach(e=>{k(e,!0)})}),document.body.addEventListener("touchstart",e=>{l=!0;const t=e.target.closest("button[interestfor]");t&&(S(t),t[s].longPressTimer=setTimeout(()=>{y(t,T(t),d),t[s].longPressTimer=null},500))});const t=e=>{const t=e.target.closest("button[interestfor]");t&&t[s]?.longPressTimer&&(clearTimeout(t[s].longPressTimer),t[s].longPressTimer=null)};document.body.addEventListener("touchend",e=>{t(e),l=!1}),document.body.addEventListener("touchmove",t)}(),console.log(`interestfor polyfill installed (native: ${c}).`)}"complete"===document.readyState?x():window.addEventListener("load",x)}();
@@ -20,4 +20,7 @@
20
20
  [popover].interest-target {
21
21
  background-color: lightblue;
22
22
  }
23
+ [popover] {
24
+ position-area: right center;
25
+ }
23
26
  </style>