A fork of https://github.com/ueokande/vim-vixen
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
3.7 KiB
132 lines
3.7 KiB
import Hint, { InputHint, LinkHint } from './Hint'; |
|
import * as doms from '../../shared/utils/dom'; |
|
|
|
const TARGET_SELECTOR = [ |
|
'a', 'button', 'input', 'textarea', 'area', |
|
'[contenteditable=true]', '[contenteditable=""]', '[tabindex]', |
|
'[role="button"]', 'summary' |
|
].join(','); |
|
|
|
interface Size { |
|
width: number; |
|
height: number; |
|
} |
|
|
|
interface Point { |
|
x: number; |
|
y: number; |
|
} |
|
|
|
const inViewport = ( |
|
win: Window, |
|
element: Element, |
|
viewSize: Size, |
|
framePosition: Point, |
|
): boolean => { |
|
let { |
|
top, left, bottom, right |
|
} = doms.viewportRect(element); |
|
let doc = win.document; |
|
let frameWidth = doc.documentElement.clientWidth; |
|
let frameHeight = doc.documentElement.clientHeight; |
|
|
|
if (right < 0 || bottom < 0 || top > frameHeight || left > frameWidth) { |
|
// out of frame |
|
return false; |
|
} |
|
if (right + framePosition.x < 0 || bottom + framePosition.y < 0 || |
|
left + framePosition.x > viewSize.width || |
|
top + framePosition.y > viewSize.height) { |
|
// out of viewport |
|
return false; |
|
} |
|
return true; |
|
}; |
|
|
|
const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => { |
|
if (!element || win.document.documentElement === element) { |
|
return false; |
|
} |
|
for (let attr of ['aria-hidden', 'aria-disabled']) { |
|
let value = element.getAttribute(attr); |
|
if (value !== null) { |
|
let hidden = value.toLowerCase(); |
|
if (hidden === '' || hidden === 'true') { |
|
return true; |
|
} |
|
} |
|
} |
|
return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element); |
|
}; |
|
|
|
export default interface FollowPresenter { |
|
getTargetCount(viewSize: Size, framePosition: Point): number; |
|
|
|
createHints(viewSize: Size, framePosition: Point, tags: string[]): void; |
|
|
|
filterHints(prefix: string): void; |
|
|
|
clearHints(): void; |
|
|
|
getHint(tag: string): Hint | undefined; |
|
} |
|
|
|
export class FollowPresenterImpl implements FollowPresenter { |
|
private hints: Hint[]; |
|
|
|
constructor() { |
|
this.hints = []; |
|
} |
|
|
|
getTargetCount(viewSize: Size, framePosition: Point): number { |
|
let targets = this.getTargets(viewSize, framePosition); |
|
return targets.length; |
|
} |
|
|
|
createHints(viewSize: Size, framePosition: Point, tags: string[]): void { |
|
let targets = this.getTargets(viewSize, framePosition); |
|
let min = Math.min(targets.length, tags.length); |
|
for (let i = 0; i < min; ++i) { |
|
let target = targets[i]; |
|
if (target instanceof HTMLAnchorElement || |
|
target instanceof HTMLAreaElement) { |
|
this.hints.push(new LinkHint(target, tags[i])); |
|
} else { |
|
this.hints.push(new InputHint(target, tags[i])); |
|
} |
|
} |
|
} |
|
|
|
filterHints(prefix: string): void { |
|
let shown = this.hints.filter(h => h.getTag().startsWith(prefix)); |
|
let hidden = this.hints.filter(h => !h.getTag().startsWith(prefix)); |
|
|
|
shown.forEach(h => h.show()); |
|
hidden.forEach(h => h.hide()); |
|
} |
|
|
|
clearHints(): void { |
|
this.hints.forEach(h => h.remove()); |
|
this.hints = []; |
|
} |
|
|
|
getHint(tag: string): Hint | undefined { |
|
return this.hints.find(h => h.getTag() === tag); |
|
} |
|
|
|
private getTargets(viewSize: Size, framePosition: Point): HTMLElement[] { |
|
let all = window.document.querySelectorAll(TARGET_SELECTOR); |
|
let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => { |
|
let style = window.getComputedStyle(element); |
|
|
|
// AREA's 'display' in Browser style is 'none' |
|
return (element.tagName === 'AREA' || style.display !== 'none') && |
|
style.visibility !== 'hidden' && |
|
(element as HTMLInputElement).type !== 'hidden' && |
|
element.offsetHeight > 0 && |
|
!isAriaHiddenOrAriaDisabled(window, element) && |
|
inViewport(window, element, viewSize, framePosition); |
|
}); |
|
return filtered; |
|
} |
|
}
|
|
|