mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	This PR is extracted from #23346 to address some unclear (I don't
understand) code-belonging concerns.
This PR needs to be backported, otherwise the `aria.js` is too buggy in
some cases. Since there would be two minor conflicts, I will do the
backport manually.
Before: the `aria.js` is still buggy in some cases.
After: tested with AppleVoice, Android TalkBack
* Fix incorrect dropdown init code
* Fix incorrect role element (the menu role should be on the `$menu`
element, but not on the `$focusable`)
* Fix the focus-show-click-hide problem on mobile. Now the language menu
works as expected
* Fix incorrect dropdown template function setting
* Clarify the logic in aria.js
* Hide item's tippy after menu gets hidden
* Fix incorrect tippy `setProps` after `destroy`
* Fix UI lag problem when page gets redirected during menu hiding
animation with screen reader
* Improve comments
* Implement the layout proposed by #19861
<details>
d74a7efb60/web_src/js/features/aria.md (L38-L47)
</details>
		
	
		
			
				
	
	
		
			64 lines
		
	
	
		
			1.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			64 lines
		
	
	
		
			1.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import tippy from 'tippy.js';
 | 
						|
 | 
						|
export function createTippy(target, opts = {}) {
 | 
						|
  const instance = tippy(target, {
 | 
						|
    appendTo: document.body,
 | 
						|
    placement: target.getAttribute('data-placement') || 'top-start',
 | 
						|
    animation: false,
 | 
						|
    allowHTML: false,
 | 
						|
    hideOnClick: false,
 | 
						|
    interactiveBorder: 30,
 | 
						|
    ignoreAttributes: true,
 | 
						|
    maxWidth: 500, // increase over default 350px
 | 
						|
    arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`,
 | 
						|
    ...(opts?.role && {theme: opts.role}),
 | 
						|
    ...opts,
 | 
						|
  });
 | 
						|
 | 
						|
  // for popups where content refers to a DOM element, we use the 'tippy-target' class
 | 
						|
  // to initially hide the content, now we can remove it as the content has been removed
 | 
						|
  // from the DOM by tippy
 | 
						|
  if (opts.content instanceof Element) {
 | 
						|
    opts.content.classList.remove('tippy-target');
 | 
						|
  }
 | 
						|
 | 
						|
  return instance;
 | 
						|
}
 | 
						|
 | 
						|
export function initTooltip(el, props = {}) {
 | 
						|
  const content = el.getAttribute('data-content') || props.content;
 | 
						|
  if (!content) return null;
 | 
						|
  if (!el.hasAttribute('aria-label')) el.setAttribute('aria-label', content);
 | 
						|
  return createTippy(el, {
 | 
						|
    content,
 | 
						|
    delay: 100,
 | 
						|
    role: 'tooltip',
 | 
						|
    ...(el.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true} : {}),
 | 
						|
    ...props,
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
export function showTemporaryTooltip(target, content) {
 | 
						|
  let tippy, oldContent;
 | 
						|
  if (target._tippy) {
 | 
						|
    tippy = target._tippy;
 | 
						|
    oldContent = tippy.props.content;
 | 
						|
  } else {
 | 
						|
    tippy = initTooltip(target, {content});
 | 
						|
  }
 | 
						|
 | 
						|
  tippy.setContent(content);
 | 
						|
  if (!tippy.state.isShown) tippy.show();
 | 
						|
  tippy.setProps({
 | 
						|
    onHidden: (tippy) => {
 | 
						|
      if (oldContent) {
 | 
						|
        tippy.setContent(oldContent);
 | 
						|
        tippy.setProps({onHidden: undefined});
 | 
						|
      } else {
 | 
						|
        tippy.destroy();
 | 
						|
        // after destroy, the `_tippy` is detached, it can't do "setProps (etc...)" anymore
 | 
						|
      }
 | 
						|
    },
 | 
						|
  });
 | 
						|
}
 |