interestfor 1.0.9 → 1.0.11

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
@@ -202,15 +202,29 @@ native feature:
202
202
  - The polyfill installs a `MutationObserver` on the document to keep interest
203
203
  buttons up to date, and prepends a `<style>` element to `<head>` containing
204
204
  the default styles for interest invokers and interest buttons.
205
+ - Accessibility mappings (`aria-labelledby`, `aria-describedby`, `aria-details`,
206
+ `aria-expanded`, and `role="tooltip"`) are set up on a best-effort basis by
207
+ inspecting the DOM. Because JavaScript does not have access to the browser's
208
+ accessibility tree or computed roles/names, the polyfill cannot match the
209
+ native feature in all cases (for example, when accessible names depend on CSS,
210
+ shadow DOM boundaries, or targets without an `id`, or when plain vs. rich
211
+ hint classification depends on computed roles). Native browser support handles
212
+ these accessibility mappings directly in the accessibility tree without
213
+ mutating DOM attributes.
214
+ - On iOS (detected via user-agent sniffing for `iPad`, `iPhone`, or `iPod`),
215
+ long-pressing a `<button interestfor>` shows interest when the finger is
216
+ released (`touchend`) after holding for at least 500ms, rather than while the
217
+ finger is still held down. Unlike Android (which fires `pointercancel` and
218
+ `touchcancel` after a ~500ms hold), iOS WebKit does not cancel the touch
219
+ stream and fires `pointerup` on finger release, which would otherwise trigger
220
+ native popover light-dismiss on a popover opened after `pointerdown`.
205
221
 
206
222
 
207
223
  ## Tests
208
224
 
209
- The `test/test.html` file exercises the various parts of this feature and tests
210
- for correctness. This is a manual test, since hovering, keyboard-focusing, etc.
211
- cannot be done programmatically. The test is capable of testing either the
212
- polyfill (optionally minified) or the native feature. You can run tests directly
213
- from this repo,
225
+ The `test/test.html` file contains an automated test suite that runs on page
226
+ load and tests the polyfill (either `src/interestfor.js` or
227
+ `src/interestfor.min.js`). You can run the tests directly from this repo,
214
228
  [here](https://mfreed7.github.io/interestfor/test/test.html).
215
229
 
216
230
  ## Browser Compatibility
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "interestfor",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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",
@@ -59,8 +59,7 @@
59
59
  capture: true,
60
60
  });
61
61
  }
62
-
63
- if(!window.InterestEvent) {
62
+ if (!window.InterestEvent || window.interestForUsePolyfillAlways) {
64
63
  window.InterestEvent = class InterestEvent extends Event {
65
64
  #source;
66
65
  constructor(type, eventInit = {}) {
@@ -561,12 +560,111 @@
561
560
  return true;
562
561
  }
563
562
 
563
+ // Best-effort approximation of the Accessible Name Computation algorithm.
564
+ // Because JavaScript cannot query the browser's actual accessibility tree,
565
+ // this only inspects common DOM attributes (`aria-labelledby`, `aria-label`,
566
+ // `alt`, `title`), `<label>` elements, and subtree text nodes. It will miss
567
+ // names or visibility derived from CSS (e.g. `display: none` or `::before`
568
+ // `content`), `ElementInternals`, `ariaLabelledByElements`, or other
569
+ // browser-internal accessibility heuristics.
570
+ function normalizeText(str) {
571
+ return str ? str.replace(/\s+/g, " ").trim() : "";
572
+ }
573
+
574
+ function getSubtreeText(node, isRoot = false) {
575
+ if (node.nodeType === Node.TEXT_NODE) {
576
+ return node.data;
577
+ }
578
+ if (node.nodeType !== Node.ELEMENT_NODE) {
579
+ return "";
580
+ }
581
+ if (
582
+ !isRoot &&
583
+ (node.hasAttribute("hidden") ||
584
+ node.getAttribute("aria-hidden") === "true" ||
585
+ node.localName === "script" ||
586
+ node.localName === "style")
587
+ ) {
588
+ return "";
589
+ }
590
+ const ariaLabel = node.getAttribute("aria-label");
591
+ if (ariaLabel) {
592
+ return ariaLabel;
593
+ }
594
+ if (node.localName === "img" || node.localName === "area") {
595
+ return node.getAttribute("alt") || "";
596
+ }
597
+ let text = "";
598
+ for (const child of node.childNodes) {
599
+ text += getSubtreeText(child);
600
+ }
601
+ return text;
602
+ }
603
+
604
+ function getAccessibleName(el) {
605
+ const labelledBy = el.getAttribute("aria-labelledby");
606
+ if (labelledBy) {
607
+ const root = getRootNode(el);
608
+ if (typeof root.getElementById === "function") {
609
+ const parts = [];
610
+ for (const id of labelledBy.trim().split(/\s+/)) {
611
+ const ref = id ? root.getElementById(id) : null;
612
+ if (ref) {
613
+ parts.push(normalizeText(getSubtreeText(ref, true)));
614
+ }
615
+ }
616
+ const text = normalizeText(parts.join(" "));
617
+ if (text) {
618
+ return text;
619
+ }
620
+ }
621
+ }
622
+ const ariaLabel = normalizeText(el.getAttribute("aria-label"));
623
+ if (ariaLabel) {
624
+ return ariaLabel;
625
+ }
626
+ if (el.labels?.length) {
627
+ const labelText = normalizeText(
628
+ [...el.labels].map((l) => getSubtreeText(l, true)).join(" ")
629
+ );
630
+ if (labelText) {
631
+ return labelText;
632
+ }
633
+ }
634
+ const subtreeText = normalizeText(getSubtreeText(el, true));
635
+ if (subtreeText) {
636
+ return subtreeText;
637
+ }
638
+ return normalizeText(el.getAttribute("title"));
639
+ }
640
+
564
641
  function setupAccessibility(invoker, target) {
642
+ if (!target.id) {
643
+ return;
644
+ }
565
645
  if (isPlainHint(target)) {
566
- invoker.setAttribute("aria-describedby", target.id);
646
+ const labelledBy = invoker.getAttribute("aria-labelledby") || "";
647
+ if (labelledBy.trim().split(/\s+/).includes(target.id)) {
648
+ return;
649
+ }
650
+ const name = getAccessibleName(invoker);
651
+ const hintText = normalizeText(getSubtreeText(target, true));
652
+ if (!name) {
653
+ if (hintText) {
654
+ invoker.setAttribute("aria-labelledby", target.id);
655
+ }
656
+ } else if (
657
+ hintText &&
658
+ hintText !== name &&
659
+ !invoker.hasAttribute("aria-describedby")
660
+ ) {
661
+ invoker.setAttribute("aria-describedby", target.id);
662
+ }
567
663
  } else {
568
664
  // Rich hint
569
- invoker.setAttribute("aria-details", target.id);
665
+ if (!invoker.hasAttribute("aria-details")) {
666
+ invoker.setAttribute("aria-details", target.id);
667
+ }
570
668
  invoker.setAttribute("aria-expanded", "false");
571
669
  if (!target.hasAttribute("role")) {
572
670
  target.setAttribute("role", "tooltip");
@@ -575,23 +673,26 @@
575
673
  }
576
674
 
577
675
  function initializeDataField(el) {
578
- if (el[dataField]) return;
579
- el[dataField] = {
580
- state: InterestState.NoInterest,
581
- target: null,
582
- gainedTimer: null,
583
- lostTimer: null,
584
- longPressTimer: null,
585
- suppressNextFocusInterest: false,
586
- clearGainedTask() {
587
- clearTimeout(this.gainedTimer);
588
- },
589
- clearLostTask() {
590
- clearTimeout(this.lostTimer);
591
- },
592
- };
676
+ if (!el[dataField]) {
677
+ el[dataField] = {
678
+ state: InterestState.NoInterest,
679
+ target: null,
680
+ a11yTarget: null,
681
+ gainedTimer: null,
682
+ lostTimer: null,
683
+ longPressTimer: null,
684
+ suppressNextFocusInterest: false,
685
+ clearGainedTask() {
686
+ clearTimeout(this.gainedTimer);
687
+ },
688
+ clearLostTask() {
689
+ clearTimeout(this.lostTimer);
690
+ },
691
+ };
692
+ }
593
693
  const target = GetInterestForTarget(el);
594
- if (target) {
694
+ if (target && el[dataField].a11yTarget !== target) {
695
+ el[dataField].a11yTarget = target;
595
696
  setupAccessibility(el, target);
596
697
  }
597
698
  }
@@ -764,20 +865,24 @@
764
865
  const updates = [];
765
866
  for (const invoker of invokers) {
766
867
  let content = null;
767
- if (invoker.isConnected && invoker.hasAttribute(attributeName)) {
868
+ const target = GetInterestForTarget(invoker);
869
+ if (target) {
870
+ invokersMissingTarget.delete(invoker);
768
871
  if (wantsInterestButton(invoker)) {
769
- invokersMissingTarget.delete(invoker);
770
872
  content = getInterestButtonContent(invoker);
771
- } else {
772
- // The target might be added (or become valid) later.
773
- invokersMissingTarget.add(invoker);
774
873
  }
874
+ } else if (invoker.isConnected && invoker.hasAttribute(attributeName)) {
875
+ // The target might be added (or become valid) later.
876
+ invokersMissingTarget.add(invoker);
775
877
  } else {
776
878
  invokersMissingTarget.delete(invoker);
777
879
  }
778
- updates.push([invoker, content]);
880
+ updates.push([invoker, target, content]);
779
881
  }
780
- for (const [invoker, content] of updates) {
882
+ for (const [invoker, target, content] of updates) {
883
+ if (target) {
884
+ initializeDataField(invoker);
885
+ }
781
886
  setInterestButton(invoker, content);
782
887
  }
783
888
  }
@@ -879,11 +984,19 @@
879
984
  HandleInterestHoverOrFocus(el, source);
880
985
  }
881
986
  };
987
+ const clearTouch = (e) => {
988
+ if (e.pointerType !== "touch") {
989
+ touchInProgress = false;
990
+ }
991
+ };
992
+ document.body.addEventListener("pointerover", clearTouch);
993
+ document.body.addEventListener("pointerdown", clearTouch);
882
994
  document.body.addEventListener("mouseover", (e) => handler(e, Source.Hover));
883
995
  document.body.addEventListener("mouseout", (e) => handler(e, Source.DeHover));
884
996
  document.body.addEventListener("focusin", (e) => handler(e, Source.Focus));
885
997
  document.body.addEventListener("focusout", (e) => handler(e, Source.Blur));
886
998
  document.body.addEventListener("keydown", (e) => {
999
+ touchInProgress = false;
887
1000
  if (e.key === "Escape" && !e.defaultPrevented) {
888
1001
  // Lose interest in all invokers, most recent first. These
889
1002
  // `loseinterest` events are not cancelable.
@@ -899,20 +1012,37 @@
899
1012
  });
900
1013
  // Touch support
901
1014
  const longPressTime = 500;
1015
+ // WebKit (iOS) long-press workaround:
1016
+ // On Android, holding for ~500ms fires `pointercancel` and `touchcancel`,
1017
+ // so the popover can open immediately when `longPressTimer` fires while the
1018
+ // finger is still down. On iOS WebKit, holding a button with
1019
+ // `user-select: none` does not cancel the touch stream; when the finger is
1020
+ // later lifted, iOS fires `pointerup` (which triggers WebKit's native
1021
+ // popover light-dismiss because the popover was not open at `pointerdown`)
1022
+ // followed by `touchend` and synthesized mouse/`click` events.
1023
+ // On iOS only, record that the 500ms hold succeeded (`longPressActive`),
1024
+ // and on `touchend` (which fires after `pointerup`), call `preventDefault()`
1025
+ // to suppress the synthesized `click` and open the popover then.
1026
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
902
1027
  document.body.addEventListener("touchstart", (e) => {
903
1028
  touchInProgress = true;
904
1029
  const invoker = e.target.closest("button[interestfor]");
905
1030
  if (invoker) {
906
1031
  initializeDataField(invoker);
1032
+ invoker[dataField].longPressActive = false;
907
1033
  invoker[dataField].longPressTimer = setTimeout(() => {
908
1034
  // Long-press shows interest immediately, and that interest is not
909
1035
  // lost by de-hovering or blurring the invoker.
910
1036
  if (invoker[dataField].state === InterestState.NoInterest) {
911
- GainOrLoseInterest(
912
- invoker,
913
- GetInterestForTarget(invoker),
914
- InterestState.ExplicitInterest
915
- );
1037
+ if (isIOS) {
1038
+ invoker[dataField].longPressActive = true;
1039
+ } else {
1040
+ GainOrLoseInterest(
1041
+ invoker,
1042
+ GetInterestForTarget(invoker),
1043
+ InterestState.ExplicitInterest
1044
+ );
1045
+ }
916
1046
  }
917
1047
  invoker[dataField].longPressTimer = null;
918
1048
  }, longPressTime);
@@ -920,24 +1050,45 @@
920
1050
  });
921
1051
  const cancelLongPress = (e) => {
922
1052
  const invoker = e.target.closest("button[interestfor]");
923
- if (invoker && invoker[dataField]?.longPressTimer) {
1053
+ if (invoker && invoker[dataField]) {
924
1054
  clearTimeout(invoker[dataField].longPressTimer);
925
1055
  invoker[dataField].longPressTimer = null;
1056
+ invoker[dataField].longPressActive = false;
926
1057
  }
927
1058
  };
928
- document.body.addEventListener("touchend", (e) => {
929
- cancelLongPress(e);
930
- touchInProgress = false;
931
- });
932
1059
  document.body.addEventListener("touchmove", cancelLongPress);
1060
+ document.body.addEventListener(
1061
+ "touchend",
1062
+ (e) => {
1063
+ const invoker = e.target.closest("button[interestfor]");
1064
+ const longPressActive = invoker?.[dataField]?.longPressActive;
1065
+ cancelLongPress(e);
1066
+ if (longPressActive) {
1067
+ if (e.cancelable) {
1068
+ e.preventDefault();
1069
+ }
1070
+ if (invoker[dataField].state === InterestState.NoInterest) {
1071
+ GainOrLoseInterest(
1072
+ invoker,
1073
+ GetInterestForTarget(invoker),
1074
+ InterestState.ExplicitInterest
1075
+ );
1076
+ }
1077
+ }
1078
+ },
1079
+ { passive: false }
1080
+ );
933
1081
  }
934
1082
 
935
1083
  // CSS registration
936
1084
  function registerCustomProperties() {
937
1085
  const style = document.createElement("style");
1086
+ // `@property` syntax can't express "one or two of `normal | <time>`", so
1087
+ // the shorthand uses the universal syntax. It is still registered, so that
1088
+ // it doesn't inherit.
938
1089
  style.textContent = `@property ${interestDelayStartProp} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}
939
1090
  @property ${interestDelayEndProp} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}
940
- @property ${interestDelayProp} {syntax: "[ normal | <time> ]{1,2}"; inherits: false; initial-value: normal;}
1091
+ @property ${interestDelayProp} {syntax: "*"; inherits: false;}
941
1092
  @property ${interestButtonContentProp} {syntax: "*"; inherits: false;}`;
942
1093
  document.head.appendChild(style);
943
1094
  // Equivalents of the UA stylesheet rules for interest invokers. These are
@@ -1007,7 +1158,8 @@
1007
1158
  ].forEach((iface) => {
1008
1159
  if (
1009
1160
  iface &&
1010
- !Object.prototype.hasOwnProperty.call(iface.prototype, "interestForElement")
1161
+ (!Object.prototype.hasOwnProperty.call(iface.prototype, "interestForElement") ||
1162
+ window.interestForUsePolyfillAlways)
1011
1163
  ) {
1012
1164
  Object.defineProperty(iface.prototype, "interestForElement", descriptor);
1013
1165
  }
@@ -5,4 +5,4 @@
5
5
  // LICENSE file in the root directory of this source tree.
6
6
 
7
7
 
8
- !function(){const e="interestfor",t="interest",n="loseinterest",r="--interest-delay-start",o="--interest-delay-end",s="--interest-delay",i="--interest-button-content",a="--interest-button-polyfill-content",l="interest-button",c="__interestForData",u="__interestForTargetData",d="__interestForButton",f="__interestForButtonInvoker",m="__interestForExplicitTarget",p="http://www.w3.org/1999/xlink",h=new Set,b=new Set,y=new Set;let g=!1;if(window.interestForPolyfillInstalled)return;window.interestForPolyfillInstalled=!0;const w=HTMLButtonElement.prototype.hasOwnProperty("interestForElement");if(w&&!window.interestForUsePolyfillAlways)return;if(w){const e=e=>{e.isTrusted&&(e.preventDefault(),e.stopImmediatePropagation())};document.body.addEventListener(t,e,{capture:!0}),document.body.addEventListener(n,e,{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=S(e);return t!==S(this.target||document)?t.host:e}});const E="none",v="full",T="explicit",L="hover",A="dehover",x="focus",k="blur";function S(e){return e&&"function"==typeof e.getRootNode?e.getRootNode():e&&e.parentNode?S(e.parentNode):e}function N(e,r,o,s={}){if(!e||!r)return!1;if(!e.isConnected||I(e)!==r||o===E&&F(r)!==e)return!1;if(o!==E){q(e),e[c].state!==E&&e[c].target!==r&&H(e);const n=F(r);if(n){if(n===e)return o===T&&(n[c].state=T),n[c].clearLostTask(),!1;if(!N(n,r,E))return!1;if(!e.isConnected||I(e)!==r)return!1}return function(e,n,r){const o=e[c];switch(r){case v:case T:if(o.state!==E)throw new Error("Invalid state");if(!n.dispatchEvent(new InterestEvent(t,{source:e,cancelable:!0})))return!1;if(o.state=r,o.target=n,o.clearGainedTask(),n[u]||(n[u]={}),n[u].invoker=e,n.hasAttribute("popover")&&(n.addEventListener("beforetoggle",M),n[u].shownByInterest=!1,!$(n))){try{n.showPopover({source:e})}catch{}n[u].shownByInterest=$(n)}if(h.add(e),e.classList.add("interest-source"),n.classList.add("interest-target"),G(n)||e.setAttribute("aria-expanded","true"),"none"===getComputedStyle(e).anchorName&&"auto"===getComputedStyle(n).positionAnchor){const t=`--interest-anchor-${Math.random().toString(36).substring(2)}`;e.style.anchorName=t,n.style.positionAnchor=t,o.anchorName=t}break;default:throw new Error("Invalid state")}return!0}(e,r,o)}return function(e,{force:t=!1,closePopovers:r=!0}={}){const o=e[c];if(clearTimeout(o.gainedTimer),clearTimeout(o.lostTimer),o.state===E)return!0;const s=o.target,i=s.dispatchEvent(new InterestEvent(n,{source:e,cancelable:!t}));if(!i)return!1;const a=s[u],l=r&&a?.invoker===e&&a.shownByInterest;H(e),l&&$(s)&&function(e,t){const n=e[c],r=t.contains(document.activeElement);r&&(n.suppressNextFocusInterest=!0);try{t.hidePopover()}catch{}r&&(n.suppressNextFocusInterest=!1)}(e,s);return!0}(e,s)}function P(e){const t=1e3*V(e,o);if(!_(t))return;const n=e[c];n.clearLostTask();const r=n.target;n.lostTimer=setTimeout(()=>{N(e,r,E)},t)}function F(e){const t=e[u]?.invoker||null;if(!t)return null;const n=t[c];return n&&n.state!==E&&n.target===e&&I(t)===e?t:null}function I(t){if(!t.hasAttribute(e)||!t.isConnected||!function(e){if(e instanceof HTMLButtonElement)return!e.matches(":disabled");if(e instanceof HTMLAnchorElement||e instanceof HTMLAreaElement)return e.hasAttribute("href");if(window.SVGAElement&&e instanceof SVGAElement)return e.hasAttribute("href")||e.hasAttributeNS(p,"href");return!1}(t))return null;const n=C(t);return n&&n.isConnected?n:null}function C(t){const n=t.getAttribute(e);if(null===n)return null;const r=t[m];if(r&&""===n)return function(e,t){const n=S(e);let r=S(t);for(;r;){if(r===n)return!0;r=r instanceof ShadowRoot?S(r.host):null}return!1}(r,t)?r:null;if(""===n)return null;const o=S(t);return"function"==typeof o.getElementById?o.getElementById(n):null}function $(e){try{return e.matches(":popover-open")}catch{return!1}}function M(e){if("closed"!==e.newState)return;const t=e.currentTarget,n=F(t);if(n){if(t.contains(document.activeElement)){const e=n[c];e.suppressNextFocusInterest=!0,setTimeout(()=>{e.suppressNextFocusInterest=!1},0)}N(n,t,E,{closePopovers:!1})}}function _(e){return e>=0&&e<=2147483647}function B(e){const t=String(e).trim(),n=t.match(/^([\d.]+(?:e[+-]?\d+)?)s$/i);if(n)return parseFloat(n[1]);const r=t.match(/^([\d.]+(?:e[+-]?\d+)?)ms$/i);return r?parseFloat(r[1])/1e3:/\b(infinity|nan)\b/i.test(t)?1/0:parseFloat(t)||0}function V(e,t){const n=getComputedStyle(e),o=n.getPropertyValue(t).trim();if("normal"!==o.toLowerCase())return B(o);const i=n.getPropertyValue(s).trim();if(i&&"normal"!==i.toLowerCase()){const e=i.split(/\s+/).filter(e=>e.length>0);if(e.length>0){const n=e[0],o=e.length>1?e[1]:n;let s;if(s=t===r?n:o,"normal"!==s.toLowerCase())return B(s)}}return t===r?.25:.15}function H(e){const t=e[c];if(!t)return;if(clearTimeout(t.gainedTimer),clearTimeout(t.lostTimer),t.state===E)return;const n=t.target;h.delete(e),e.classList.remove("interest-source"),n&&(n[u]?.invoker===e&&(n.removeEventListener("beforetoggle",M),n[u]=null,n.classList.remove("interest-target")),G(n)||e.setAttribute("aria-expanded","false")),t.anchorName&&(e.style.anchorName="",n&&(n.style.positionAnchor=""),t.anchorName=null),t.state=E,t.target=null}const O=["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 G(e){if("hint"!==e.getAttribute("popover")?.toLowerCase())return!1;if(e.querySelector(O))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 q(e){if(e[c])return;e[c]={state:E,target:null,gainedTimer:null,lostTimer:null,longPressTimer:null,suppressNextFocusInterest:!1,clearGainedTask(){clearTimeout(this.gainedTimer)},clearLostTask(){clearTimeout(this.lostTimer)}};const t=I(e);t&&function(e,t){G(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 D(e){if(e[f])return e[f];if(e.assignedSlot)return e.assignedSlot;if(e.parentElement)return e.parentElement;const t=e.parentNode;return t instanceof ShadowRoot?t.host:null}function z(e){const t=new Set;if(0===h.size)return t;const n=e=>{const r=F(e);r&&!t.has(r)&&(t.add(r),n(r));const o=D(e);o&&!t.has(o)&&n(o)};return n(e),t}function j(e,t){if(g)return;if(!e.isConnected)return;const n=I(e);n&&q(e);const o=e[c];if(t===L||t===x){let s=!1;o&&(o.clearLostTask(),s=t===x&&o.suppressNextFocusInterest,o.suppressNextFocusInterest=!1);for(const t of z(e))t[c].clearLostTask();n&&!s&&o.state===E&&function(e,t){const n=I(e);if(!n)return;const o=1e3*V(e,r);_(o)&&(e[c].clearGainedTask(),e[c].gainedTimer=setTimeout(()=>{N(e,n,t)},o))}(e,v)}else if(o&&(o.clearGainedTask(),o.state===v&&P(e)),t===k||!e.matches(":hover"))for(const t of z(e))P(t)}function R(e){return e instanceof HTMLElement&&"area"!==e.localName&&!!e.parentNode&&!!I(e)}function U(e){const t=getComputedStyle(e).getPropertyValue(i).trim();return t&&"none"!==t&&"normal"!==t?t:null}function J(){const e=this[f];e&&N(e,I(e),T)}function K(e,t){let n=e[d];t?(n||(n=e.ownerDocument.createElement("span"),n.className=l,n.setAttribute("aria-hidden","true"),n[f]=e,n.addEventListener("click",J),e[d]=n,b.add(e)),n.style.getPropertyValue(a)!==t&&n.style.setProperty(a,t),e.nextSibling!==n&&e.after(n)):n&&(n.remove(),e[d]=null,b.delete(e))}function Q(t){const n=[];for(const r of t){let t=null;r.isConnected&&r.hasAttribute(e)?R(r)?(y.delete(r),t=U(r)):y.add(r):y.delete(r),n.push([r,t])}for(const[e,t]of n)K(e,t)}function W(t){const n=new Set;let r=!1;const o=t=>{if(t.nodeType===Node.ELEMENT_NODE&&!t[f]){t.hasAttribute(e)&&n.add(t);for(const r of t.querySelectorAll(`[${e}]`))n.add(r)}};for(const s of t){if("childList"===s.type){for(const e of s.addedNodes)e.nodeType!==Node.ELEMENT_NODE||e[f]||(r=!0,o(e));continue}const t=s.target;switch(s.attributeName){case e:null!==s.oldValue&&H(t),""!==t.getAttribute(e)&&(t[m]=null),n.add(t);break;case"id":r=!0;break;default:o(t)}}for(const e of[...h]){const t=e[c].target;e.isConnected&&t&&t.isConnected||H(e)}if(r)for(const e of y)n.add(e);for(const e of[...b])n.has(e)||(R(e)?e.nextSibling!==e[d]&&e.after(e[d]):K(e,null));Q(n)}function X(){!function(){const t=document.createElement("style");t.textContent=`@property ${r} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${o} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${s} {syntax: "[ normal | <time> ]{1,2}"; inherits: false; initial-value: normal;}\n @property ${i} {syntax: "*"; inherits: false;}`,document.head.appendChild(t);const n=`span.${l}[aria-hidden]`,u=document.createElement("style");u.textContent=`:where(button[${e}]) {\n -webkit-user-select: none;\n user-select: none;\n }\n :where(${n}) {\n display: inline-block;\n vertical-align: middle;\n color: inherit;\n background-color: transparent;\n box-sizing: border-box;\n min-inline-size: 24px;\n min-block-size: 24px;\n min-block-size: max(24px, 1lh);\n align-content: center;\n text-align: center;\n -webkit-user-select: none;\n user-select: none;\n cursor: help;\n }\n :where(${n}:hover) {\n background-color: color-mix(in oklab, currentColor 10%, transparent);\n }\n :where(${n}:active) {\n background-color: color-mix(in oklab, currentColor 20%, transparent);\n }\n :where(${n})::before {\n content: var(${a});\n }`,document.head.prepend(u),document[c]={globalPropsStyle:t,uaStyle:u}}(),function(){const e=(e,t)=>{const n=e.composedPath(),r=n.length?n[0]:e.target;if(r instanceof Element)for(let e=r;e;e=D(e))j(e,t)};document.body.addEventListener("mouseover",t=>e(t,L)),document.body.addEventListener("mouseout",t=>e(t,A)),document.body.addEventListener("focusin",t=>e(t,x)),document.body.addEventListener("focusout",t=>e(t,k)),document.body.addEventListener("keydown",e=>{"Escape"!==e.key||e.defaultPrevented||[...h].reverse().forEach(e=>{N(e,e[c].target,E,{force:!0})})}),document.body.addEventListener("touchstart",e=>{g=!0;const t=e.target.closest("button[interestfor]");t&&(q(t),t[c].longPressTimer=setTimeout(()=>{t[c].state===E&&N(t,I(t),T),t[c].longPressTimer=null},500))});const t=e=>{const t=e.target.closest("button[interestfor]");t&&t[c]?.longPressTimer&&(clearTimeout(t[c].longPressTimer),t[c].longPressTimer=null)};document.body.addEventListener("touchend",e=>{t(e),g=!1}),document.body.addEventListener("touchmove",t)}(),new MutationObserver(W).observe(document,{childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,attributeFilter:[e,"id","href","disabled"]}),Q(document.querySelectorAll(`[${e}]`)),console.log(`interestfor polyfill installed (native: ${w}).`)}!function(){const t={configurable:!0,enumerable:!0,get(){return C(this)},set(t){if(null==t)this[m]=null,this.removeAttribute(e);else{if(!(t instanceof Element))throw new TypeError("Failed to set the 'interestForElement' property: The provided value is not of type 'Element'.");this[m]=t,this.setAttribute(e,"")}}};[window.HTMLButtonElement,window.HTMLAnchorElement,window.HTMLAreaElement,window.SVGAElement].forEach(e=>{e&&!Object.prototype.hasOwnProperty.call(e.prototype,"interestForElement")&&Object.defineProperty(e.prototype,"interestForElement",t)})}(),"complete"===document.readyState?X():window.addEventListener("load",X)}();
8
+ !function(){const e="interestfor",t="interest",n="loseinterest",r="--interest-delay-start",o="--interest-delay-end",s="--interest-delay",i="--interest-button-content",a="--interest-button-polyfill-content",l="interest-button",c="__interestForData",u="__interestForTargetData",d="__interestForButton",f="__interestForButtonInvoker",m="__interestForExplicitTarget",p="http://www.w3.org/1999/xlink",h=new Set,b=new Set,g=new Set;let y=!1;if(window.interestForPolyfillInstalled)return;window.interestForPolyfillInstalled=!0;const E=HTMLButtonElement.prototype.hasOwnProperty("interestForElement");if(E&&!window.interestForUsePolyfillAlways)return;if(E){const e=e=>{e.isTrusted&&(e.preventDefault(),e.stopImmediatePropagation())};document.body.addEventListener(t,e,{capture:!0}),document.body.addEventListener(n,e,{capture:!0})}window.InterestEvent&&!window.interestForUsePolyfillAlways||(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 w="none",v="full",T="explicit",A="hover",L="dehover",N="focus",x="blur";function P(e){return e&&"function"==typeof e.getRootNode?e.getRootNode():e&&e.parentNode?P(e.parentNode):e}function k(e,r,o,s={}){if(!e||!r)return!1;if(!e.isConnected||I(e)!==r||o===w&&F(r)!==e)return!1;if(o!==w){z(e),e[c].state!==w&&e[c].target!==r&&D(e);const n=F(r);if(n){if(n===e)return o===T&&(n[c].state=T),n[c].clearLostTask(),!1;if(!k(n,r,w))return!1;if(!e.isConnected||I(e)!==r)return!1}return function(e,n,r){const o=e[c];switch(r){case v:case T:if(o.state!==w)throw new Error("Invalid state");if(!n.dispatchEvent(new InterestEvent(t,{source:e,cancelable:!0})))return!1;if(o.state=r,o.target=n,o.clearGainedTask(),n[u]||(n[u]={}),n[u].invoker=e,n.hasAttribute("popover")&&(n.addEventListener("beforetoggle",_),n[u].shownByInterest=!1,!$(n))){try{n.showPopover({source:e})}catch{}n[u].shownByInterest=$(n)}if(h.add(e),e.classList.add("interest-source"),n.classList.add("interest-target"),H(n)||e.setAttribute("aria-expanded","true"),"none"===getComputedStyle(e).anchorName&&"auto"===getComputedStyle(n).positionAnchor){const t=`--interest-anchor-${Math.random().toString(36).substring(2)}`;e.style.anchorName=t,n.style.positionAnchor=t,o.anchorName=t}break;default:throw new Error("Invalid state")}return!0}(e,r,o)}return function(e,{force:t=!1,closePopovers:r=!0}={}){const o=e[c];if(clearTimeout(o.gainedTimer),clearTimeout(o.lostTimer),o.state===w)return!0;const s=o.target,i=s.dispatchEvent(new InterestEvent(n,{source:e,cancelable:!t}));if(!i)return!1;const a=s[u],l=r&&a?.invoker===e&&a.shownByInterest;D(e),l&&$(s)&&function(e,t){const n=e[c],r=t.contains(document.activeElement);r&&(n.suppressNextFocusInterest=!0);try{t.hidePopover()}catch{}r&&(n.suppressNextFocusInterest=!1)}(e,s);return!0}(e,s)}function S(e){const t=1e3*O(e,o);if(!M(t))return;const n=e[c];n.clearLostTask();const r=n.target;n.lostTimer=setTimeout(()=>{k(e,r,w)},t)}function F(e){const t=e[u]?.invoker||null;if(!t)return null;const n=t[c];return n&&n.state!==w&&n.target===e&&I(t)===e?t:null}function I(t){if(!t.hasAttribute(e)||!t.isConnected||!function(e){if(e instanceof HTMLButtonElement)return!e.matches(":disabled");if(e instanceof HTMLAnchorElement||e instanceof HTMLAreaElement)return e.hasAttribute("href");if(window.SVGAElement&&e instanceof SVGAElement)return e.hasAttribute("href")||e.hasAttributeNS(p,"href");return!1}(t))return null;const n=C(t);return n&&n.isConnected?n:null}function C(t){const n=t.getAttribute(e);if(null===n)return null;const r=t[m];if(r&&""===n)return function(e,t){const n=P(e);let r=P(t);for(;r;){if(r===n)return!0;r=r instanceof ShadowRoot?P(r.host):null}return!1}(r,t)?r:null;if(""===n)return null;const o=P(t);return"function"==typeof o.getElementById?o.getElementById(n):null}function $(e){try{return e.matches(":popover-open")}catch{return!1}}function _(e){if("closed"!==e.newState)return;const t=e.currentTarget,n=F(t);if(n){if(t.contains(document.activeElement)){const e=n[c];e.suppressNextFocusInterest=!0,setTimeout(()=>{e.suppressNextFocusInterest=!1},0)}k(n,t,w,{closePopovers:!1})}}function M(e){return e>=0&&e<=2147483647}function B(e){const t=String(e).trim(),n=t.match(/^([\d.]+(?:e[+-]?\d+)?)s$/i);if(n)return parseFloat(n[1]);const r=t.match(/^([\d.]+(?:e[+-]?\d+)?)ms$/i);return r?parseFloat(r[1])/1e3:/\b(infinity|nan)\b/i.test(t)?1/0:parseFloat(t)||0}function O(e,t){const n=getComputedStyle(e),o=n.getPropertyValue(t).trim();if("normal"!==o.toLowerCase())return B(o);const i=n.getPropertyValue(s).trim();if(i&&"normal"!==i.toLowerCase()){const e=i.split(/\s+/).filter(e=>e.length>0);if(e.length>0){const n=e[0],o=e.length>1?e[1]:n;let s;if(s=t===r?n:o,"normal"!==s.toLowerCase())return B(s)}}return t===r?.25:.15}function D(e){const t=e[c];if(!t)return;if(clearTimeout(t.gainedTimer),clearTimeout(t.lostTimer),t.state===w)return;const n=t.target;h.delete(e),e.classList.remove("interest-source"),n&&(n[u]?.invoker===e&&(n.removeEventListener("beforetoggle",_),n[u]=null,n.classList.remove("interest-target")),H(n)||e.setAttribute("aria-expanded","false")),t.anchorName&&(e.style.anchorName="",n&&(n.style.positionAnchor=""),t.anchorName=null),t.state=w,t.target=null}const V=["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 H(e){if("hint"!==e.getAttribute("popover")?.toLowerCase())return!1;if(e.querySelector(V))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 G(e){return e?e.replace(/\s+/g," ").trim():""}function j(e,t=!1){if(e.nodeType===Node.TEXT_NODE)return e.data;if(e.nodeType!==Node.ELEMENT_NODE)return"";if(!t&&(e.hasAttribute("hidden")||"true"===e.getAttribute("aria-hidden")||"script"===e.localName||"style"===e.localName))return"";const n=e.getAttribute("aria-label");if(n)return n;if("img"===e.localName||"area"===e.localName)return e.getAttribute("alt")||"";let r="";for(const t of e.childNodes)r+=j(t);return r}function q(e,t){if(t.id)if(H(t)){if((e.getAttribute("aria-labelledby")||"").trim().split(/\s+/).includes(t.id))return;const n=function(e){const t=e.getAttribute("aria-labelledby");if(t){const n=P(e);if("function"==typeof n.getElementById){const e=[];for(const r of t.trim().split(/\s+/)){const t=r?n.getElementById(r):null;t&&e.push(G(j(t,!0)))}const r=G(e.join(" "));if(r)return r}}const n=G(e.getAttribute("aria-label"));if(n)return n;if(e.labels?.length){const t=G([...e.labels].map(e=>j(e,!0)).join(" "));if(t)return t}return G(j(e,!0))||G(e.getAttribute("title"))}(e),r=G(j(t,!0));n?r&&r!==n&&!e.hasAttribute("aria-describedby")&&e.setAttribute("aria-describedby",t.id):r&&e.setAttribute("aria-labelledby",t.id)}else e.hasAttribute("aria-details")||e.setAttribute("aria-details",t.id),e.setAttribute("aria-expanded","false"),t.hasAttribute("role")||t.setAttribute("role","tooltip")}function z(e){e[c]||(e[c]={state:w,target:null,a11yTarget:null,gainedTimer:null,lostTimer:null,longPressTimer:null,suppressNextFocusInterest:!1,clearGainedTask(){clearTimeout(this.gainedTimer)},clearLostTask(){clearTimeout(this.lostTimer)}});const t=I(e);t&&e[c].a11yTarget!==t&&(e[c].a11yTarget=t,q(e,t))}function R(e){if(e[f])return e[f];if(e.assignedSlot)return e.assignedSlot;if(e.parentElement)return e.parentElement;const t=e.parentNode;return t instanceof ShadowRoot?t.host:null}function U(e){const t=new Set;if(0===h.size)return t;const n=e=>{const r=F(e);r&&!t.has(r)&&(t.add(r),n(r));const o=R(e);o&&!t.has(o)&&n(o)};return n(e),t}function X(e,t){if(y)return;if(!e.isConnected)return;const n=I(e);n&&z(e);const o=e[c];if(t===A||t===N){let s=!1;o&&(o.clearLostTask(),s=t===N&&o.suppressNextFocusInterest,o.suppressNextFocusInterest=!1);for(const t of U(e))t[c].clearLostTask();n&&!s&&o.state===w&&function(e,t){const n=I(e);if(!n)return;const o=1e3*O(e,r);M(o)&&(e[c].clearGainedTask(),e[c].gainedTimer=setTimeout(()=>{k(e,n,t)},o))}(e,v)}else if(o&&(o.clearGainedTask(),o.state===v&&S(e)),t===x||!e.matches(":hover"))for(const t of U(e))S(t)}function J(e){return e instanceof HTMLElement&&"area"!==e.localName&&!!e.parentNode&&!!I(e)}function K(e){const t=getComputedStyle(e).getPropertyValue(i).trim();return t&&"none"!==t&&"normal"!==t?t:null}function Q(){const e=this[f];e&&k(e,I(e),T)}function W(e,t){let n=e[d];t?(n||(n=e.ownerDocument.createElement("span"),n.className=l,n.setAttribute("aria-hidden","true"),n[f]=e,n.addEventListener("click",Q),e[d]=n,b.add(e)),n.style.getPropertyValue(a)!==t&&n.style.setProperty(a,t),e.nextSibling!==n&&e.after(n)):n&&(n.remove(),e[d]=null,b.delete(e))}function Y(t){const n=[];for(const r of t){let t=null;const o=I(r);o?(g.delete(r),J(r)&&(t=K(r))):r.isConnected&&r.hasAttribute(e)?g.add(r):g.delete(r),n.push([r,o,t])}for(const[e,t,r]of n)t&&z(e),W(e,r)}function Z(t){const n=new Set;let r=!1;const o=t=>{if(t.nodeType===Node.ELEMENT_NODE&&!t[f]){t.hasAttribute(e)&&n.add(t);for(const r of t.querySelectorAll(`[${e}]`))n.add(r)}};for(const s of t){if("childList"===s.type){for(const e of s.addedNodes)e.nodeType!==Node.ELEMENT_NODE||e[f]||(r=!0,o(e));continue}const t=s.target;switch(s.attributeName){case e:null!==s.oldValue&&D(t),""!==t.getAttribute(e)&&(t[m]=null),n.add(t);break;case"id":r=!0;break;default:o(t)}}for(const e of[...h]){const t=e[c].target;e.isConnected&&t&&t.isConnected||D(e)}if(r)for(const e of g)n.add(e);for(const e of[...b])n.has(e)||(J(e)?e.nextSibling!==e[d]&&e.after(e[d]):W(e,null));Y(n)}function ee(){!function(){const t=document.createElement("style");t.textContent=`@property ${r} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${o} {syntax: "normal | <time>"; inherits: false; initial-value: normal;}\n @property ${s} {syntax: "*"; inherits: false;}\n @property ${i} {syntax: "*"; inherits: false;}`,document.head.appendChild(t);const n=`span.${l}[aria-hidden]`,u=document.createElement("style");u.textContent=`:where(button[${e}]) {\n -webkit-user-select: none;\n user-select: none;\n }\n :where(${n}) {\n display: inline-block;\n vertical-align: middle;\n color: inherit;\n background-color: transparent;\n box-sizing: border-box;\n min-inline-size: 24px;\n min-block-size: 24px;\n min-block-size: max(24px, 1lh);\n align-content: center;\n text-align: center;\n -webkit-user-select: none;\n user-select: none;\n cursor: help;\n }\n :where(${n}:hover) {\n background-color: color-mix(in oklab, currentColor 10%, transparent);\n }\n :where(${n}:active) {\n background-color: color-mix(in oklab, currentColor 20%, transparent);\n }\n :where(${n})::before {\n content: var(${a});\n }`,document.head.prepend(u),document[c]={globalPropsStyle:t,uaStyle:u}}(),function(){const e=(e,t)=>{const n=e.composedPath(),r=n.length?n[0]:e.target;if(r instanceof Element)for(let e=r;e;e=R(e))X(e,t)},t=e=>{"touch"!==e.pointerType&&(y=!1)};document.body.addEventListener("pointerover",t),document.body.addEventListener("pointerdown",t),document.body.addEventListener("mouseover",t=>e(t,A)),document.body.addEventListener("mouseout",t=>e(t,L)),document.body.addEventListener("focusin",t=>e(t,N)),document.body.addEventListener("focusout",t=>e(t,x)),document.body.addEventListener("keydown",e=>{y=!1,"Escape"!==e.key||e.defaultPrevented||[...h].reverse().forEach(e=>{k(e,e[c].target,w,{force:!0})})});const n=/iPad|iPhone|iPod/.test(navigator.userAgent);document.body.addEventListener("touchstart",e=>{y=!0;const t=e.target.closest("button[interestfor]");t&&(z(t),t[c].longPressActive=!1,t[c].longPressTimer=setTimeout(()=>{t[c].state===w&&(n?t[c].longPressActive=!0:k(t,I(t),T)),t[c].longPressTimer=null},500))});const r=e=>{const t=e.target.closest("button[interestfor]");t&&t[c]&&(clearTimeout(t[c].longPressTimer),t[c].longPressTimer=null,t[c].longPressActive=!1)};document.body.addEventListener("touchmove",r),document.body.addEventListener("touchend",e=>{const t=e.target.closest("button[interestfor]"),n=t?.[c]?.longPressActive;r(e),n&&(e.cancelable&&e.preventDefault(),t[c].state===w&&k(t,I(t),T))},{passive:!1})}(),new MutationObserver(Z).observe(document,{childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,attributeFilter:[e,"id","href","disabled"]}),Y(document.querySelectorAll(`[${e}]`)),console.log(`interestfor polyfill installed (native: ${E}).`)}!function(){const t={configurable:!0,enumerable:!0,get(){return C(this)},set(t){if(null==t)this[m]=null,this.removeAttribute(e);else{if(!(t instanceof Element))throw new TypeError("Failed to set the 'interestForElement' property: The provided value is not of type 'Element'.");this[m]=t,this.setAttribute(e,"")}}};[window.HTMLButtonElement,window.HTMLAnchorElement,window.HTMLAreaElement,window.SVGAElement].forEach(e=>{!e||Object.prototype.hasOwnProperty.call(e.prototype,"interestForElement")&&!window.interestForUsePolyfillAlways||Object.defineProperty(e.prototype,"interestForElement",t)})}(),"complete"===document.readyState?ee():window.addEventListener("load",ee)}();
@@ -1,20 +1,23 @@
1
1
  <!DOCTYPE html>
2
+ <meta charset="utf-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.4">
2
4
 
3
- <script src="../src/interestfor.js" async></script>
4
-
5
- <a interestfor=foo href=bar>Link - hover me, don't click me</a>
6
- <button interestfor=foo>
7
- <span style="margin:10px;">Button - hover me, don't click me</span></button>
8
- <div popover id=foo>Popover</div>
9
- <p>
10
- <a interestfor=bar href=bar class=with-button>Link with an interest button - click the "?"</a>
11
- </p>
12
- <div popover id=bar>Popover shown by the interest button</div>
5
+ <div>
6
+ <a interestfor=foo href=bar>Link - hover me</a>
7
+ </div>
8
+ <div>
9
+ <button interestfor=foo commandfor=bar command=toggle-popover>Button - hover me</button>
10
+ </div>
11
+ <div popover id=foo>Hovercard</div>
12
+ <div popover id=bar>Button action</div>
13
13
 
14
14
  <style>
15
+ body {
16
+ padding: 20px 0 0 20px;
17
+ }
15
18
  [interestfor] {
16
- interest-delay: 600ms 300ms;
17
- --interest-delay: 600ms 300ms;
19
+ --interest-delay: 600ms 500ms;
20
+ interest-delay: var(--interest-delay);
18
21
  }
19
22
  :is([interestfor]:interest-source),
20
23
  [interestfor].interest-source {
@@ -26,13 +29,53 @@
26
29
  }
27
30
  [popover] {
28
31
  position-area: right center;
32
+ margin-left:30px;
29
33
  }
30
- /* Native ::interest-button */
31
- .with-button::interest-button {
32
- content: "?";
34
+ #bar {
35
+ position-area: bottom center;
36
+ margin: 10px 0 0 0;
33
37
  }
38
+ div { margin: 10px; padding: 7.5px 0; }
34
39
  /* Polyfilled ::interest-button */
35
- .with-button {
36
- --interest-button-content: "?";
40
+ [interestfor] {
41
+ --interest-button-content: "\24D8";
42
+ }
43
+ /* Native ::interest-button */
44
+ [interestfor]::interest-button {
45
+ content: var(--interest-button-content);
37
46
  }
38
47
  </style>
48
+
49
+ <div>
50
+ <label>Mode:
51
+ <select id="mode">
52
+ <option value="polyfill">Use polyfill</option>
53
+ <option value="native">Native</option>
54
+ </select>
55
+ </label>
56
+ </div>
57
+ <div id="nav_msg"></div>
58
+ <script>
59
+ let navTimer;
60
+ document.querySelector("a[interestfor]").addEventListener("click", (e) => {
61
+ e.preventDefault();
62
+ const msg = document.getElementById("nav_msg");
63
+ msg.textContent = "would have navigated";
64
+ clearTimeout(navTimer);
65
+ navTimer = setTimeout(() => {
66
+ msg.textContent = "";
67
+ }, 1000);
68
+ });
69
+ const mode = new URLSearchParams(location.search).get("mode") || "polyfill";
70
+ const select = document.getElementById("mode");
71
+ select.value = mode;
72
+ select.addEventListener("change", () => {
73
+ location.search = `?mode=${select.value}`;
74
+ });
75
+ if (mode === "polyfill") {
76
+ window.interestForUsePolyfillAlways = true;
77
+ const s = document.createElement("script");
78
+ s.src = `../src/interestfor.js?v=${Date.now()}`;
79
+ document.head.appendChild(s);
80
+ }
81
+ </script>