diff --git a/packages/core/src/features/HandleLinkPressFeature.ts b/packages/core/src/features/HandleLinkPressFeature.ts new file mode 100644 index 00000000..e99510d8 --- /dev/null +++ b/packages/core/src/features/HandleLinkPressFeature.ts @@ -0,0 +1,97 @@ +import linkPressScript from './HandleLinkPressFeature.webjs'; +import { FeatureBuilder } from '../FeatureBuilder'; +import type { DOMRect, PropDefinition } from '../types'; +import type { FeatureConstructor } from '../Feature'; + +/** + * An object describing customization for the linkPress feature. + * + * @public + */ +export interface LinkPressOptions { + /** + * Prevent click events on anchors to propagate. + * + * @defaultValue true + */ + preventDefault?: boolean; + + /** + * Don't trigger an event when the target `href` is inside the page, e.g. + * `#top`. See also {@link HandleHashChangeFeature}. + * + * @defaultValue true + */ + ignoreHashChange?: boolean; +} + +/** + * The target of a link press event. + * + * @public + */ +export interface LinkPressTarget { + /** + * The full URI of the target. + */ + uri: string; + /** + * The URI scheme. + */ + scheme: string; + /** + * The exact content of the `href` attribute. + */ + hrefAttribute: string; + /** + * The bounding rectangle of the anchor which has been clicked. + * See {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect | Element.getBoundingClientRect()} + */ + clickedAnchorBoundingRect: DOMRect; + /** + * An object describing the page location from which the click originated. + */ + page: { + /** + * See {@link https://developer.mozilla.org/en-US/docs/Web/API/Location/origin}. + * + * @remarks + * Has the special value `null` when not bound to a URL (`{ html }` source). + */ + origin: string | null; + /** + * See {@link https://developer.mozilla.org/en-US/docs/Web/API/Location/href}. + * + * @remarks + * Has the special value `about:blank` when not bound to a URL (`{ html }` source). + */ + href: string; + }; +} + +const defaultOptions: LinkPressOptions = { + preventDefault: true, + ignoreHashChange: true +}; + +/** + * This feature allows to intercept clicks on anchors (``). By default, it + * will prevent the click from propagating. But you can disable this option. + * + * @public + */ +export const HandleLinkPressFeature: FeatureConstructor< + LinkPressOptions, + [ + PropDefinition<{ + onDOMLinkPress?: (t: LinkPressTarget) => void; + }> + ] +> = new FeatureBuilder({ + script: linkPressScript, + defaultOptions, + className: 'HandleLinkPressFeature', + featureIdentifier: 'org.formidable-webview/webshell.link-press' +}) + .withEventHandlerProp('onDOMLinkPress') + .build(); diff --git a/packages/core/src/features/HandleLinkPressFeature.webjs b/packages/core/src/features/HandleLinkPressFeature.webjs new file mode 100644 index 00000000..a8d3eeb2 --- /dev/null +++ b/packages/core/src/features/HandleLinkPressFeature.webjs @@ -0,0 +1,60 @@ +function HandleLinkPressFeature(context) { + var postMessage = context.postMessage; + var options = context.options || {}; + var preventDefault = options.preventDefault !== false; + var ignoreHashChange = + typeof options.ignoreHashChange === 'boolean' + ? options.ignoreHashChange + : true; + + function findParentByTagName(tagname, el) { + while (el) { + if ((el.nodeName || el.tagName).toLowerCase() === tagname.toLowerCase()) { + return el; + } + el = el.parentNode; + } + return null; + } + + function extractScheme(uri) { + var groups = uri.match(/(\w+):\/\//); + return (groups && groups.length > 1 && groups[1]) || ''; + } + + var interceptClickEvent = context.makeCallbackSafe(function (e) { + var target = e.target || e.srcElement; + var anchor = findParentByTagName('a', target); + if (anchor) { + var href = anchor.href; + if ( + ignoreHashChange && + anchor.origin === window.location.origin && + anchor.pathname === window.location.pathname + ) { + return; + } + var rect = anchor.getBoundingClientRect(); + var clickedAnchorBoundingRect = { + top: rect.top, + left: rect.left, + bottom: rect.bottom, + right: rect.right, + width: rect.width, + height: rect.height + }; + preventDefault && e.preventDefault(); + postMessage({ + uri: href, + scheme: extractScheme(href), + hrefAttribute: anchor.getAttribute('href'), + clickedAnchorBoundingRect: clickedAnchorBoundingRect, + page: { + href: window.location.href, + origin: window.location.origin + } + }); + } + }); + document.addEventListener('click', interceptClickEvent, false); +}