interestfor 1.0.13 → 1.0.14
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/package.json +1 -1
- package/src/interestfor.js +94 -17
- package/src/interestfor.min.js +1 -1
- package/test/test.html +190 -1
package/package.json
CHANGED
package/src/interestfor.js
CHANGED
|
@@ -414,9 +414,7 @@
|
|
|
414
414
|
invokersWithInterest.add(invoker);
|
|
415
415
|
invoker.classList.add("interest-source");
|
|
416
416
|
target.classList.add("interest-target");
|
|
417
|
-
|
|
418
|
-
invoker.setAttribute("aria-expanded", "true");
|
|
419
|
-
}
|
|
417
|
+
syncAriaExpanded(invoker);
|
|
420
418
|
if (getComputedStyle(invoker).anchorName === 'none' &&
|
|
421
419
|
getComputedStyle(target).positionAnchor === 'auto') {
|
|
422
420
|
const anchorName = `--interest-anchor-${Math.random()
|
|
@@ -459,6 +457,7 @@
|
|
|
459
457
|
resetInterestState(invoker);
|
|
460
458
|
if (shouldHide && isPopoverOpen(target)) {
|
|
461
459
|
hideTargetPopover(invoker, target);
|
|
460
|
+
syncAriaExpanded(invoker);
|
|
462
461
|
}
|
|
463
462
|
return true;
|
|
464
463
|
}
|
|
@@ -486,9 +485,6 @@
|
|
|
486
485
|
target[targetDataField] = null;
|
|
487
486
|
target.classList.remove("interest-target");
|
|
488
487
|
}
|
|
489
|
-
if (!isPlainHint(target)) {
|
|
490
|
-
invoker.setAttribute("aria-expanded", "false");
|
|
491
|
-
}
|
|
492
488
|
}
|
|
493
489
|
if (data.anchorName) {
|
|
494
490
|
invoker.style.anchorName = "";
|
|
@@ -499,6 +495,7 @@
|
|
|
499
495
|
}
|
|
500
496
|
data.state = InterestState.NoInterest;
|
|
501
497
|
data.target = null;
|
|
498
|
+
syncAriaExpanded(invoker);
|
|
502
499
|
}
|
|
503
500
|
|
|
504
501
|
function hideTargetPopover(invoker, target) {
|
|
@@ -534,10 +531,9 @@
|
|
|
534
531
|
].join(",");
|
|
535
532
|
|
|
536
533
|
// Accessibility utilities
|
|
534
|
+
// Plain vs. rich hints are distinguished by the contents of the target,
|
|
535
|
+
// regardless of the popover type.
|
|
537
536
|
function isPlainHint(target) {
|
|
538
|
-
if (target.getAttribute("popover")?.toLowerCase() !== "hint") {
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
537
|
// Anything focusable is not plain.
|
|
542
538
|
if (target.querySelector(focusableSelector)) {
|
|
543
539
|
return false;
|
|
@@ -553,7 +549,16 @@
|
|
|
553
549
|
for (const el of elementsWithRoles) {
|
|
554
550
|
const role = el.getAttribute("role").toLowerCase();
|
|
555
551
|
// Allowed roles are those that don't add semantics for a screen reader to explore.
|
|
556
|
-
if (
|
|
552
|
+
if (
|
|
553
|
+
![
|
|
554
|
+
"presentation",
|
|
555
|
+
"none",
|
|
556
|
+
"generic",
|
|
557
|
+
"image",
|
|
558
|
+
"tooltip",
|
|
559
|
+
"paragraph",
|
|
560
|
+
].includes(role)
|
|
561
|
+
) {
|
|
557
562
|
return false;
|
|
558
563
|
}
|
|
559
564
|
}
|
|
@@ -638,13 +643,56 @@
|
|
|
638
643
|
return normalizeText(el.getAttribute("title"));
|
|
639
644
|
}
|
|
640
645
|
|
|
646
|
+
function idrefListIncludes(el, attr, id) {
|
|
647
|
+
const value = el.getAttribute(attr) || "";
|
|
648
|
+
return value.trim().split(/\s+/).includes(id);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Returns true if `invoker` references `target` via `popovertarget` or
|
|
652
|
+
// `commandfor`, i.e. activating the invoker (also) toggles the target.
|
|
653
|
+
function activationTogglesTarget(invoker, target) {
|
|
654
|
+
const refersTo = (attr, prop) => {
|
|
655
|
+
if (prop in invoker) {
|
|
656
|
+
return invoker[prop] === target;
|
|
657
|
+
}
|
|
658
|
+
return !!target.id && invoker.getAttribute(attr) === target.id;
|
|
659
|
+
};
|
|
660
|
+
return (
|
|
661
|
+
refersTo("popovertarget", "popoverTargetElement") ||
|
|
662
|
+
refersTo("commandfor", "commandForElement")
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function isLink(el) {
|
|
667
|
+
return (
|
|
668
|
+
el instanceof HTMLAnchorElement ||
|
|
669
|
+
el instanceof HTMLAreaElement ||
|
|
670
|
+
(!!window.SVGAElement && el instanceof SVGAElement)
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Sets `aria-expanded` on the invoker, if it is managed by the polyfill. For
|
|
675
|
+
// popover targets, this reflects whether the popover is open (which might
|
|
676
|
+
// be because of something other than interest, e.g. `popovertarget`). For
|
|
677
|
+
// other targets, it reflects whether the invoker has interest.
|
|
678
|
+
function syncAriaExpanded(invoker) {
|
|
679
|
+
const data = invoker[dataField];
|
|
680
|
+
if (!data || !data.managesAriaExpanded || !data.a11yTarget) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const target = data.a11yTarget;
|
|
684
|
+
const expanded = target.hasAttribute("popover")
|
|
685
|
+
? isPopoverOpen(target)
|
|
686
|
+
: data.state !== InterestState.NoInterest && data.target === target;
|
|
687
|
+
invoker.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
688
|
+
}
|
|
689
|
+
|
|
641
690
|
function setupAccessibility(invoker, target) {
|
|
642
691
|
if (!target.id) {
|
|
643
692
|
return;
|
|
644
693
|
}
|
|
645
694
|
if (isPlainHint(target)) {
|
|
646
|
-
|
|
647
|
-
if (labelledBy.trim().split(/\s+/).includes(target.id)) {
|
|
695
|
+
if (idrefListIncludes(invoker, "aria-labelledby", target.id)) {
|
|
648
696
|
return;
|
|
649
697
|
}
|
|
650
698
|
const name = getAccessibleName(invoker);
|
|
@@ -656,16 +704,36 @@
|
|
|
656
704
|
} else if (
|
|
657
705
|
hintText &&
|
|
658
706
|
hintText !== name &&
|
|
659
|
-
!invoker.hasAttribute("aria-describedby")
|
|
707
|
+
!invoker.hasAttribute("aria-describedby") &&
|
|
708
|
+
// For toggletips, where activating the invoker reveals the hint,
|
|
709
|
+
// describing the invoker with the hint would read the hint twice.
|
|
710
|
+
!invoker.hasAttribute("aria-expanded") &&
|
|
711
|
+
!activationTogglesTarget(invoker, target)
|
|
660
712
|
) {
|
|
661
713
|
invoker.setAttribute("aria-describedby", target.id);
|
|
662
714
|
}
|
|
663
715
|
} else {
|
|
664
716
|
// Rich hint
|
|
665
|
-
|
|
717
|
+
const data = invoker[dataField];
|
|
718
|
+
// If the author already related the target to the invoker, don't add
|
|
719
|
+
// more relationships.
|
|
720
|
+
const authorRelated =
|
|
721
|
+
idrefListIncludes(invoker, "aria-describedby", target.id) ||
|
|
722
|
+
idrefListIncludes(invoker, "aria-labelledby", target.id);
|
|
723
|
+
if (!authorRelated && !invoker.hasAttribute("aria-details")) {
|
|
666
724
|
invoker.setAttribute("aria-details", target.id);
|
|
667
725
|
}
|
|
668
|
-
|
|
726
|
+
// Don't overwrite an author-provided `aria-expanded`. Also, activating
|
|
727
|
+
// a link navigates rather than expanding the hint, so only links whose
|
|
728
|
+
// hint contains focusable content get `aria-expanded`.
|
|
729
|
+
data.managesAriaExpanded =
|
|
730
|
+
!authorRelated &&
|
|
731
|
+
!invoker.hasAttribute("aria-expanded") &&
|
|
732
|
+
(!isLink(invoker) || !!target.querySelector(focusableSelector));
|
|
733
|
+
if (data.managesAriaExpanded) {
|
|
734
|
+
target.addEventListener("toggle", data.onTargetToggle);
|
|
735
|
+
syncAriaExpanded(invoker);
|
|
736
|
+
}
|
|
669
737
|
if (!target.hasAttribute("role")) {
|
|
670
738
|
target.setAttribute("role", "tooltip");
|
|
671
739
|
}
|
|
@@ -678,6 +746,8 @@
|
|
|
678
746
|
state: InterestState.NoInterest,
|
|
679
747
|
target: null,
|
|
680
748
|
a11yTarget: null,
|
|
749
|
+
managesAriaExpanded: false,
|
|
750
|
+
onTargetToggle: () => syncAriaExpanded(el),
|
|
681
751
|
gainedTimer: null,
|
|
682
752
|
lostTimer: null,
|
|
683
753
|
longPressTimer: null,
|
|
@@ -690,9 +760,16 @@
|
|
|
690
760
|
},
|
|
691
761
|
};
|
|
692
762
|
}
|
|
763
|
+
const data = el[dataField];
|
|
693
764
|
const target = GetInterestForTarget(el);
|
|
694
|
-
if (target &&
|
|
695
|
-
|
|
765
|
+
if (target && data.a11yTarget !== target) {
|
|
766
|
+
if (data.managesAriaExpanded) {
|
|
767
|
+
// Stop managing `aria-expanded` for the previous target.
|
|
768
|
+
data.a11yTarget?.removeEventListener("toggle", data.onTargetToggle);
|
|
769
|
+
el.removeAttribute("aria-expanded");
|
|
770
|
+
data.managesAriaExpanded = false;
|
|
771
|
+
}
|
|
772
|
+
data.a11yTarget = target;
|
|
696
773
|
setupAccessibility(el, target);
|
|
697
774
|
}
|
|
698
775
|
}
|
package/src/interestfor.min.js
CHANGED
|
@@ -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",h="http://www.w3.org/1999/xlink",p=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.addEventListener(t,e,{capture:!0}),document.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=x(e);return t!==x(this.target||document)?t.host:e}});const w="none",v="full",T="explicit",A="hover",L="dehover",N="focus",S="blur";function x(e){return e&&"function"==typeof e.getRootNode?e.getRootNode():e&&e.parentNode?x(e.parentNode):e}function P(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(!P(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",M),n[u].shownByInterest=!1,!$(n))){try{n.showPopover({source:e})}catch{}n[u].shownByInterest=$(n)}if(p.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 k(e){const t=1e3*O(e,o);if(!_(t))return;const n=e[c];n.clearLostTask();const r=n.target;n.lostTimer=setTimeout(()=>{P(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(h,"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=x(e);let r=x(t);for(;r;){if(r===n)return!0;r=r instanceof ShadowRoot?x(r.host):null}return!1}(r,t)?r:null;if(""===n)return null;const o=x(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)}P(n,t,w,{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 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;p.delete(e),e.classList.remove("interest-source"),n&&(n[u]?.invoker===e&&(n.removeEventListener("beforetoggle",M),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=x(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 X(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 R(e){const t=new Set;if(0===p.size)return t;const n=e=>{const r=F(e);r&&!t.has(r)&&(t.add(r),n(r));const o=X(e);o&&!t.has(o)&&n(o)};return n(e),t}function Y(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 R(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);_(o)&&(e[c].clearGainedTask(),e[c].gainedTimer=setTimeout(()=>{P(e,n,t)},o))}(e,v)}else if(o&&(o.clearGainedTask(),o.state===v&&k(e)),t===S||!e.matches(":hover"))for(const t of R(e))k(t)}function U(e){return e instanceof HTMLElement&&"area"!==e.localName&&!!e.parentNode&&!!I(e)}function J(e){const t=getComputedStyle(e).getPropertyValue(i).trim();return t&&"none"!==t&&"normal"!==t?t:null}function K(){const e=this[f];e&&P(e,I(e),T)}function Q(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",K),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 W(t){const n=[];for(const r of t){let t=null;const o=I(r);o?(g.delete(r),U(r)&&(t=J(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),Q(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[...p]){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)||(U(e)?e.nextSibling!==e[d]&&e.after(e[d]):Q(e,null));W(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=X(e))Y(e,t)},t=e=>{"touch"!==e.pointerType&&(y=!1)};document.addEventListener("pointerover",t),document.addEventListener("pointerdown",t),document.addEventListener("mouseover",t=>e(t,A)),document.addEventListener("mouseout",t=>e(t,L)),document.addEventListener("focusin",t=>e(t,N)),document.addEventListener("focusout",t=>e(t,S)),document.addEventListener("keydown",e=>{y=!1,"Escape"!==e.key||e.defaultPrevented||[...p].reverse().forEach(e=>{P(e,e[c].target,w,{force:!0})})});const n=()=>/iPad|iPhone|iPod/.test(navigator.userAgent);document.addEventListener("touchstart",e=>{y=!0;const t=e.target.closest("button[interestfor]");if(t){z(t);const r=n();r&&1===e.touches?.length&&(t[c].touchStartX=e.touches[0].clientX,t[c].touchStartY=e.touches[0].clientY),t[c].longPressActive=!1,t[c].longPressTimer=setTimeout(()=>{t[c].state===w&&(r?t[c].longPressActive=!0:P(t,I(t),T)),t[c].longPressTimer=null},500)}});const r=e=>{const t=e.target.closest("button[interestfor]");if(t&&t[c]){if(n()&&"touchmove"===e.type&&1===e.touches?.length){const n=e.touches[0].clientX-t[c].touchStartX,r=e.touches[0].clientY-t[c].touchStartY;if(Math.hypot(n,r)<=10)return}clearTimeout(t[c].longPressTimer),t[c].longPressTimer=null,t[c].longPressActive=!1}};document.addEventListener("touchmove",r),document.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&&P(t,I(t),T))},{passive:!1})}(),new MutationObserver(Z).observe(document,{childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,attributeFilter:[e,"id","href","disabled"]}),W(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)}();
|
|
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,g=new Set,b=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.addEventListener(t,e,{capture:!0}),document.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=S(e);return t!==S(this.target||document)?t.host:e}});const v="none",w="full",T="explicit",A="hover",L="dehover",x="focus",N="blur";function S(e){return e&&"function"==typeof e.getRootNode?e.getRootNode():e&&e.parentNode?S(e.parentNode):e}function P(e,r,o,s={}){if(!e||!r)return!1;if(!e.isConnected||I(e)!==r||o===v&&F(r)!==e)return!1;if(o!==v){X(e),e[c].state!==v&&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(!P(n,r,v))return!1;if(!e.isConnected||I(e)!==r)return!1}return function(e,n,r){const o=e[c];switch(r){case w:case T:if(o.state!==v)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"),j(e),"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===v)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),j(e));return!0}(e,s)}function k(e){const t=1e3*V(e,o);if(!_(t))return;const n=e[c];n.clearLostTask();const r=n.target;n.lostTimer=setTimeout(()=>{P(e,r,v)},t)}function F(e){const t=e[u]?.invoker||null;if(!t)return null;const n=t[c];return n&&n.state!==v&&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)}P(n,t,v,{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===v)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")),t.anchorName&&(e.style.anchorName="",n&&(n.style.positionAnchor=""),t.anchorName=null),t.state=v,t.target=null,j(e)}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 D(e){return e?e.replace(/\s+/g," ").trim():""}function G(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+=G(t);return r}function q(e,t,n){return(e.getAttribute(t)||"").trim().split(/\s+/).includes(n)}function j(e){const t=e[c];if(!t||!t.managesAriaExpanded||!t.a11yTarget)return;const n=t.a11yTarget,r=n.hasAttribute("popover")?$(n):t.state!==v&&t.target===n;e.setAttribute("aria-expanded",r?"true":"false")}function z(e,t){var n;if(t.id)if(function(e){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","tooltip","paragraph"].includes(t))return!1}return!0}(t)){if(q(e,"aria-labelledby",t.id))return;const n=function(e){const t=e.getAttribute("aria-labelledby");if(t){const n=S(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(D(G(t,!0)))}const r=D(e.join(" "));if(r)return r}}const n=D(e.getAttribute("aria-label"));if(n)return n;if(e.labels?.length){const t=D([...e.labels].map(e=>G(e,!0)).join(" "));if(t)return t}return D(G(e,!0))||D(e.getAttribute("title"))}(e),r=D(G(t,!0));n?!r||r===n||e.hasAttribute("aria-describedby")||e.hasAttribute("aria-expanded")||function(e,t){const n=(n,r)=>r in e?e[r]===t:!!t.id&&e.getAttribute(n)===t.id;return n("popovertarget","popoverTargetElement")||n("commandfor","commandForElement")}(e,t)||e.setAttribute("aria-describedby",t.id):r&&e.setAttribute("aria-labelledby",t.id)}else{const r=e[c],o=q(e,"aria-describedby",t.id)||q(e,"aria-labelledby",t.id);o||e.hasAttribute("aria-details")||e.setAttribute("aria-details",t.id),r.managesAriaExpanded=!(o||e.hasAttribute("aria-expanded")||(n=e,(n instanceof HTMLAnchorElement||n instanceof HTMLAreaElement||window.SVGAElement&&n instanceof SVGAElement)&&!t.querySelector(O))),r.managesAriaExpanded&&(t.addEventListener("toggle",r.onTargetToggle),j(e)),t.hasAttribute("role")||t.setAttribute("role","tooltip")}}function X(e){e[c]||(e[c]={state:v,target:null,a11yTarget:null,managesAriaExpanded:!1,onTargetToggle:()=>j(e),gainedTimer:null,lostTimer:null,longPressTimer:null,suppressNextFocusInterest:!1,clearGainedTask(){clearTimeout(this.gainedTimer)},clearLostTask(){clearTimeout(this.lostTimer)}});const t=e[c],n=I(e);n&&t.a11yTarget!==n&&(t.managesAriaExpanded&&(t.a11yTarget?.removeEventListener("toggle",t.onTargetToggle),e.removeAttribute("aria-expanded"),t.managesAriaExpanded=!1),t.a11yTarget=n,z(e,n))}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 Y(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 U(e,t){if(y)return;if(!e.isConnected)return;const n=I(e);n&&X(e);const o=e[c];if(t===A||t===x){let s=!1;o&&(o.clearLostTask(),s=t===x&&o.suppressNextFocusInterest,o.suppressNextFocusInterest=!1);for(const t of Y(e))t[c].clearLostTask();n&&!s&&o.state===v&&function(e,t){const n=I(e);if(!n)return;const o=1e3*V(e,r);_(o)&&(e[c].clearGainedTask(),e[c].gainedTimer=setTimeout(()=>{P(e,n,t)},o))}(e,w)}else if(o&&(o.clearGainedTask(),o.state===w&&k(e)),t===N||!e.matches(":hover"))for(const t of Y(e))k(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&&P(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,g.add(e)),n.style.getPropertyValue(a)!==t&&n.style.setProperty(a,t),e.nextSibling!==n&&e.after(n)):n&&(n.remove(),e[d]=null,g.delete(e))}function Z(t){const n=[];for(const r of t){let t=null;const o=I(r);o?(b.delete(r),J(r)&&(t=K(r))):r.isConnected&&r.hasAttribute(e)?b.add(r):b.delete(r),n.push([r,o,t])}for(const[e,t,r]of n)t&&X(e),W(e,r)}function ee(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 b)n.add(e);for(const e of[...g])n.has(e)||(J(e)?e.nextSibling!==e[d]&&e.after(e[d]):W(e,null));Z(n)}function te(){!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))U(e,t)},t=e=>{"touch"!==e.pointerType&&(y=!1)};document.addEventListener("pointerover",t),document.addEventListener("pointerdown",t),document.addEventListener("mouseover",t=>e(t,A)),document.addEventListener("mouseout",t=>e(t,L)),document.addEventListener("focusin",t=>e(t,x)),document.addEventListener("focusout",t=>e(t,N)),document.addEventListener("keydown",e=>{y=!1,"Escape"!==e.key||e.defaultPrevented||[...h].reverse().forEach(e=>{P(e,e[c].target,v,{force:!0})})});const n=()=>/iPad|iPhone|iPod/.test(navigator.userAgent);document.addEventListener("touchstart",e=>{y=!0;const t=e.target.closest("button[interestfor]");if(t){X(t);const r=n();r&&1===e.touches?.length&&(t[c].touchStartX=e.touches[0].clientX,t[c].touchStartY=e.touches[0].clientY),t[c].longPressActive=!1,t[c].longPressTimer=setTimeout(()=>{t[c].state===v&&(r?t[c].longPressActive=!0:P(t,I(t),T)),t[c].longPressTimer=null},500)}});const r=e=>{const t=e.target.closest("button[interestfor]");if(t&&t[c]){if(n()&&"touchmove"===e.type&&1===e.touches?.length){const n=e.touches[0].clientX-t[c].touchStartX,r=e.touches[0].clientY-t[c].touchStartY;if(Math.hypot(n,r)<=10)return}clearTimeout(t[c].longPressTimer),t[c].longPressTimer=null,t[c].longPressActive=!1}};document.addEventListener("touchmove",r),document.addEventListener("touchend",e=>{const t=e.target.closest("button[interestfor]"),n=t?.[c]?.longPressActive;r(e),n&&(e.cancelable&&e.preventDefault(),t[c].state===v&&P(t,I(t),T))},{passive:!1})}(),new MutationObserver(ee).observe(document,{childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,attributeFilter:[e,"id","href","disabled"]}),Z(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?te():window.addEventListener("load",te)}();
|
package/test/test.html
CHANGED
|
@@ -192,6 +192,191 @@ async function runTests() {
|
|
|
192
192
|
assert_equals(invoker.getAttribute("aria-labelledby"), "delayed_target");
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
+
await test("Plain vs. rich classification ignores popover type, and allows role=tooltip/paragraph", async () => {
|
|
196
|
+
const { invoker: autoInv, target: autoTip } = createPair({
|
|
197
|
+
invokerHTML: "Help",
|
|
198
|
+
popoverType: "auto",
|
|
199
|
+
targetHTML: "Plain text in an auto popover",
|
|
200
|
+
});
|
|
201
|
+
const { invoker: manualInv, target: manualTip } = createPair({
|
|
202
|
+
invokerHTML: "Help",
|
|
203
|
+
popoverType: "manual",
|
|
204
|
+
targetHTML: "Plain text in a manual popover",
|
|
205
|
+
});
|
|
206
|
+
const { invoker: roleInv, target: roleTip } = createPair({
|
|
207
|
+
invokerHTML: "Help",
|
|
208
|
+
popoverType: "hint",
|
|
209
|
+
targetHTML: '<div role="tooltip"><span role="paragraph">Some text</span></div>',
|
|
210
|
+
});
|
|
211
|
+
const { invoker: richAutoInv, target: richAutoTip } = createPair({
|
|
212
|
+
invokerHTML: "Help",
|
|
213
|
+
popoverType: "hint",
|
|
214
|
+
targetHTML: "<ul><li>Rich</li></ul>",
|
|
215
|
+
});
|
|
216
|
+
await nextTick();
|
|
217
|
+
for (const [inv, tip, label] of [
|
|
218
|
+
[autoInv, autoTip, "popover=auto"],
|
|
219
|
+
[manualInv, manualTip, "popover=manual"],
|
|
220
|
+
[roleInv, roleTip, "role=tooltip/paragraph"],
|
|
221
|
+
]) {
|
|
222
|
+
assert_equals(inv.getAttribute("aria-describedby"), tip.id, `${label} -> aria-describedby`);
|
|
223
|
+
assert_equals(inv.getAttribute("aria-details"), null, `${label} -> no aria-details`);
|
|
224
|
+
assert_equals(inv.getAttribute("aria-expanded"), null, `${label} -> no aria-expanded`);
|
|
225
|
+
}
|
|
226
|
+
assert_equals(richAutoInv.getAttribute("aria-details"), richAutoTip.id, "rich hint popover -> aria-details");
|
|
227
|
+
assert_equals(richAutoInv.getAttribute("aria-expanded"), "false", "rich hint popover -> aria-expanded");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await test("Plain-hint toggletips (popovertarget, commandfor, author aria-expanded) don't get aria-describedby", async () => {
|
|
231
|
+
const targetId1 = "toggletip_1";
|
|
232
|
+
const { invoker: ptInv } = createPair({
|
|
233
|
+
targetId: targetId1,
|
|
234
|
+
invokerAttrs: { "aria-label": "More info", popovertarget: targetId1, popovertargetaction: "show" },
|
|
235
|
+
invokerHTML: "?",
|
|
236
|
+
targetHTML: "Some helpful text",
|
|
237
|
+
});
|
|
238
|
+
const targetId2 = "toggletip_2";
|
|
239
|
+
const { invoker: cfInv } = createPair({
|
|
240
|
+
targetId: targetId2,
|
|
241
|
+
invokerAttrs: { "aria-label": "More info", commandfor: targetId2, command: "show-popover" },
|
|
242
|
+
invokerHTML: "?",
|
|
243
|
+
targetHTML: "Some helpful text",
|
|
244
|
+
});
|
|
245
|
+
const { invoker: expInv } = createPair({
|
|
246
|
+
invokerAttrs: { "aria-label": "More info", "aria-expanded": "false" },
|
|
247
|
+
invokerHTML: "?",
|
|
248
|
+
targetHTML: "Some helpful text",
|
|
249
|
+
});
|
|
250
|
+
// Control: popovertarget pointing elsewhere still gets aria-describedby.
|
|
251
|
+
const other = document.createElement("div");
|
|
252
|
+
other.id = "toggletip_other";
|
|
253
|
+
other.setAttribute("popover", "auto");
|
|
254
|
+
sandbox.appendChild(other);
|
|
255
|
+
const { invoker: ctlInv, target: ctlTip } = createPair({
|
|
256
|
+
invokerAttrs: { "aria-label": "More info", popovertarget: other.id },
|
|
257
|
+
invokerHTML: "?",
|
|
258
|
+
targetHTML: "Some helpful text",
|
|
259
|
+
});
|
|
260
|
+
await nextTick();
|
|
261
|
+
assert_equals(ptInv.getAttribute("aria-describedby"), null, "popovertarget");
|
|
262
|
+
assert_equals(cfInv.getAttribute("aria-describedby"), null, "commandfor");
|
|
263
|
+
assert_equals(expInv.getAttribute("aria-describedby"), null, "author aria-expanded");
|
|
264
|
+
assert_equals(expInv.getAttribute("aria-expanded"), "false", "author aria-expanded unchanged");
|
|
265
|
+
assert_equals(ctlInv.getAttribute("aria-describedby"), ctlTip.id, "popovertarget to another element");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await test("Rich hint: author aria-expanded is preserved through interest changes", async () => {
|
|
269
|
+
const { invoker, target } = createPair({
|
|
270
|
+
invokerAttrs: { "aria-expanded": "true" },
|
|
271
|
+
targetHTML: '<a href="#">Rich</a>',
|
|
272
|
+
});
|
|
273
|
+
await nextTick();
|
|
274
|
+
assert_equals(invoker.getAttribute("aria-details"), target.id, "aria-details still added");
|
|
275
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "true", "after init");
|
|
276
|
+
hover(invoker);
|
|
277
|
+
await wait(30);
|
|
278
|
+
assert_true(target.matches(":popover-open"), "popover opened");
|
|
279
|
+
dehover(invoker);
|
|
280
|
+
await wait(30);
|
|
281
|
+
assert_false(target.matches(":popover-open"), "popover closed");
|
|
282
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "true", "after losing interest");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await test("Rich hint: author aria-describedby/aria-labelledby referencing target suppresses aria-details and aria-expanded", async () => {
|
|
286
|
+
const id1 = "rich_described";
|
|
287
|
+
const { invoker: descInv, target: descTip } = createPair({
|
|
288
|
+
targetId: id1,
|
|
289
|
+
invokerAttrs: { "aria-describedby": `other ${id1}` },
|
|
290
|
+
targetHTML: '<a href="#">Rich</a>',
|
|
291
|
+
});
|
|
292
|
+
const id2 = "rich_labelled";
|
|
293
|
+
const { invoker: lblInv } = createPair({
|
|
294
|
+
targetId: id2,
|
|
295
|
+
invokerAttrs: { "aria-labelledby": id2 },
|
|
296
|
+
targetHTML: '<a href="#">Rich</a>',
|
|
297
|
+
});
|
|
298
|
+
await nextTick();
|
|
299
|
+
for (const [inv, label] of [[descInv, "describedby"], [lblInv, "labelledby"]]) {
|
|
300
|
+
assert_equals(inv.getAttribute("aria-details"), null, `${label} -> no aria-details`);
|
|
301
|
+
assert_equals(inv.getAttribute("aria-expanded"), null, `${label} -> no aria-expanded`);
|
|
302
|
+
}
|
|
303
|
+
hover(descInv);
|
|
304
|
+
await wait(30);
|
|
305
|
+
assert_true(descTip.matches(":popover-open"), "popover opened");
|
|
306
|
+
assert_equals(descInv.getAttribute("aria-expanded"), null, "not set on interest");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await test("Rich hint on <a href>: aria-expanded only if the hint contains focusable content", async () => {
|
|
310
|
+
const { invoker: plainLink, target: plainLinkTip } = createPair({
|
|
311
|
+
invokerTag: "a",
|
|
312
|
+
invokerHTML: "@user",
|
|
313
|
+
targetHTML: "<h3>User</h3><p>Bio</p>",
|
|
314
|
+
});
|
|
315
|
+
const { invoker: focusLink } = createPair({
|
|
316
|
+
invokerTag: "a",
|
|
317
|
+
invokerHTML: "@user",
|
|
318
|
+
targetHTML: '<h3>User</h3><button>Follow</button>',
|
|
319
|
+
});
|
|
320
|
+
await nextTick();
|
|
321
|
+
assert_equals(plainLink.getAttribute("aria-details"), plainLinkTip.id, "aria-details still set");
|
|
322
|
+
assert_equals(plainLink.getAttribute("aria-expanded"), null, "non-focusable rich hint -> no aria-expanded");
|
|
323
|
+
hover(plainLink);
|
|
324
|
+
await wait(30);
|
|
325
|
+
assert_true(plainLinkTip.matches(":popover-open"), "popover opened");
|
|
326
|
+
assert_equals(plainLink.getAttribute("aria-expanded"), null, "not set on interest");
|
|
327
|
+
assert_equals(focusLink.getAttribute("aria-expanded"), "false", "focusable rich hint -> aria-expanded");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await test("Rich hint: aria-expanded tracks the popover state, even when opened via popovertarget", async () => {
|
|
331
|
+
const targetId = "rich_pt";
|
|
332
|
+
const { invoker, target } = createPair({
|
|
333
|
+
targetId,
|
|
334
|
+
invokerAttrs: { popovertarget: targetId },
|
|
335
|
+
targetHTML: '<a href="#">Rich</a>',
|
|
336
|
+
});
|
|
337
|
+
await nextTick();
|
|
338
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "false", "initial");
|
|
339
|
+
invoker.click();
|
|
340
|
+
assert_true(target.matches(":popover-open"), "opened by click");
|
|
341
|
+
await wait(10);
|
|
342
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "true", "after click");
|
|
343
|
+
hover(invoker);
|
|
344
|
+
await wait(30);
|
|
345
|
+
dehover(invoker);
|
|
346
|
+
await wait(30);
|
|
347
|
+
assert_true(target.matches(":popover-open"), "popover kept open after losing interest");
|
|
348
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "true", "still expanded after losing interest");
|
|
349
|
+
target.hidePopover();
|
|
350
|
+
await wait(10);
|
|
351
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "false", "after hidePopover");
|
|
352
|
+
|
|
353
|
+
// Interest-shown popover that is hidden by light dismiss / script.
|
|
354
|
+
hover(invoker);
|
|
355
|
+
await wait(30);
|
|
356
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "true", "after gaining interest");
|
|
357
|
+
target.hidePopover();
|
|
358
|
+
await wait(10);
|
|
359
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "false", "after hiding while interest shown");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await test("Changing interestfor from a rich to a plain hint removes polyfill-managed aria-expanded", async () => {
|
|
363
|
+
const { invoker } = createPair({
|
|
364
|
+
invokerAttrs: { "aria-label": "Info" },
|
|
365
|
+
targetHTML: '<a href="#">Rich</a>',
|
|
366
|
+
});
|
|
367
|
+
const plain = document.createElement("div");
|
|
368
|
+
plain.id = "switch_plain";
|
|
369
|
+
plain.setAttribute("popover", "hint");
|
|
370
|
+
plain.textContent = "Plain description";
|
|
371
|
+
sandbox.appendChild(plain);
|
|
372
|
+
await nextTick();
|
|
373
|
+
assert_equals(invoker.getAttribute("aria-expanded"), "false", "rich -> managed");
|
|
374
|
+
invoker.setAttribute("interestfor", plain.id);
|
|
375
|
+
await nextTick();
|
|
376
|
+
assert_equals(invoker.getAttribute("aria-expanded"), null, "plain -> removed");
|
|
377
|
+
assert_equals(invoker.getAttribute("aria-describedby"), plain.id, "plain -> aria-describedby");
|
|
378
|
+
});
|
|
379
|
+
|
|
195
380
|
// =========================================================================
|
|
196
381
|
section("2. Element Validity & interestForElement IDL");
|
|
197
382
|
// =========================================================================
|
|
@@ -295,7 +480,11 @@ async function runTests() {
|
|
|
295
480
|
// =========================================================================
|
|
296
481
|
|
|
297
482
|
await test("Hover gains interest after delay, opens popover, sets classes, and dehover loses interest", async () => {
|
|
298
|
-
const { invoker, target } = createPair({
|
|
483
|
+
const { invoker, target } = createPair({
|
|
484
|
+
delayStart: "20ms",
|
|
485
|
+
delayEnd: "20ms",
|
|
486
|
+
targetHTML: '<a href="#">Rich content</a>',
|
|
487
|
+
});
|
|
299
488
|
const events = [];
|
|
300
489
|
target.addEventListener("interest", (e) => events.push(`interest:${e.source.tagName}`));
|
|
301
490
|
target.addEventListener("loseinterest", (e) => events.push(`loseinterest:${e.source.tagName}`));
|