diff --git a/.changeset/silver-pianos-attack.md b/.changeset/silver-pianos-attack.md new file mode 100644 index 0000000000..594e32c84b --- /dev/null +++ b/.changeset/silver-pianos-attack.md @@ -0,0 +1,7 @@ +--- +"rrweb-snapshot": patch +"rrweb": patch +"@rrweb/types": patch +--- + +Adds `blockElementFN` to record options to allow consumer finer-grained element blocking. diff --git a/guide.md b/guide.md index 764e359fb4..acd3557d23 100644 --- a/guide.md +++ b/guide.md @@ -137,7 +137,8 @@ The parameter of `rrweb.record` accepts the following options. | checkoutEveryNth | - | take a full snapshot after every N events
refer to the [checkout](#checkout) chapter | | checkoutEveryNms | - | take a full snapshot after every N ms
refer to the [checkout](#checkout) chapter | | blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter | -| blockSelector | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter | +| blockElementFN | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter | +| blockSelector | null | Manually determine what element should be blocked, refer to the [privacy](#privacy) chapter | | ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter | | ignoreSelector | null | Use a string to configure which selector should be ignored, refer to the [privacy](#privacy) chapter | | ignoreCSSAttributes | null | array of CSS attributes that should be ignored | @@ -171,6 +172,7 @@ You may find some contents on the webpage which are not willing to be recorded, - All text of elements with the class name `.rr-mask` and their children will be masked. - `input[type="password"]` will be masked by default. - Mask options to mask the content in input elements. +- `blockElementFN` takes precedence over `blockSelector` and `blockClass`, this callback function is passed a single argument: the elment to block. #### Checkout diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts index 0e8ec68be9..42a26daa0b 100644 --- a/packages/rrweb-snapshot/src/snapshot.ts +++ b/packages/rrweb-snapshot/src/snapshot.ts @@ -16,6 +16,7 @@ import type { attributes, mediaAttributes, DataURLOptions, + BlockElementFn, } from '@rrweb/types'; import { Mirror, @@ -219,8 +220,12 @@ export function _isBlockedElement( element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null, + blockElementFn: BlockElementFn | null, ): boolean { try { + if (blockElementFn) { + return blockElementFn(element); + } if (typeof blockClass === 'string') { if (element.classList.contains(blockClass)) { return true; @@ -392,6 +397,7 @@ function serializeNode( doc: Document; mirror: Mirror; blockClass: string | RegExp; + blockElementFn: BlockElementFn | null; blockSelector: string | null; needsMask: boolean; inlineStylesheet: boolean; @@ -413,6 +419,7 @@ function serializeNode( doc, mirror, blockClass, + blockElementFn, blockSelector, needsMask, inlineStylesheet, @@ -454,6 +461,7 @@ function serializeNode( return serializeElementNode(n as HTMLElement, { doc, blockClass, + blockElementFn, blockSelector, inlineStylesheet, maskInputOptions, @@ -544,6 +552,7 @@ function serializeElementNode( options: { doc: Document; blockClass: string | RegExp; + blockElementFn: BlockElementFn | null; blockSelector: string | null; inlineStylesheet: boolean; maskInputOptions: MaskInputOptions; @@ -562,6 +571,7 @@ function serializeElementNode( const { doc, blockClass, + blockElementFn, blockSelector, inlineStylesheet, maskInputOptions = {}, @@ -573,7 +583,12 @@ function serializeElementNode( newlyAddedElement = false, rootId, } = options; - const needBlock = _isBlockedElement(n, blockClass, blockSelector); + const needBlock = _isBlockedElement( + n, + blockClass, + blockSelector, + blockElementFn, + ); const tagName = getValidTagName(n); let attributes: attributes = {}; const len = n.attributes.length; @@ -903,6 +918,7 @@ export function serializeNodeWithId( doc: Document; mirror: Mirror; blockClass: string | RegExp; + blockElementFn: BlockElementFn | null; blockSelector: string | null; maskTextClass: string | RegExp; maskTextSelector: string | null; @@ -937,6 +953,7 @@ export function serializeNodeWithId( doc, mirror, blockClass, + blockElementFn, blockSelector, maskTextClass, maskTextSelector, @@ -976,6 +993,7 @@ export function serializeNodeWithId( doc, mirror, blockClass, + blockElementFn, blockSelector, needsMask, inlineStylesheet, @@ -1047,6 +1065,7 @@ export function serializeNodeWithId( doc, mirror, blockClass, + blockElementFn, blockSelector, needsMask, maskTextClass, @@ -1123,6 +1142,7 @@ export function serializeNodeWithId( doc: iframeDoc, mirror, blockClass, + blockElementFn, blockSelector, needsMask, maskTextClass, @@ -1175,6 +1195,7 @@ export function serializeNodeWithId( doc, mirror, blockClass, + blockElementFn, blockSelector, needsMask, maskTextClass, @@ -1217,6 +1238,7 @@ function snapshot( options?: { mirror?: Mirror; blockClass?: string | RegExp; + blockElementFn?: BlockElementFn | null; blockSelector?: string | null; maskTextClass?: string | RegExp; maskTextSelector?: string | null; @@ -1246,6 +1268,7 @@ function snapshot( const { mirror = new Mirror(), blockClass = 'rr-block', + blockElementFn = null, blockSelector = null, maskTextClass = 'rr-mask', maskTextSelector = null, @@ -1312,6 +1335,7 @@ function snapshot( doc: n, mirror, blockClass, + blockElementFn, blockSelector, maskTextClass, maskTextSelector, diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts index 5778eb0aff..7a2ce70606 100644 --- a/packages/rrweb-snapshot/test/snapshot.test.ts +++ b/packages/rrweb-snapshot/test/snapshot.test.ts @@ -16,6 +16,7 @@ const serializeNode = (node: Node): serializedNodeWithId | null => { doc: document, mirror: new Mirror(), blockClass: 'blockblock', + blockElementFn: null, blockSelector: null, maskTextClass: 'maskmask', maskTextSelector: null, @@ -129,7 +130,12 @@ describe('absolute url to stylesheet', () => { describe('isBlockedElement()', () => { const subject = (html: string, opt: any = {}) => - _isBlockedElement(render(html), 'rr-block', opt.blockSelector); + _isBlockedElement( + render(html), + 'rr-block', + opt.blockSelector, + opt.blockElementFn, + ); const render = (html: string): HTMLElement => JSDOM.fragment(html).querySelector('div')!; @@ -151,6 +157,14 @@ describe('isBlockedElement()', () => { subject('
', { blockSelector: '[data-rr-block]' }), ).toEqual(true); }); + + it('blocks with blockElementFn', () => { + expect( + subject('
', { + blockElementFn: (e: HTMLElement) => e.matches('div.special'), + }), + ).toEqual(true); + }); }); describe('style elements', () => { diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 1308c378a6..bf5560438a 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -70,6 +70,7 @@ function record( checkoutEveryNms, checkoutEveryNth, blockClass = 'rr-block', + blockElementFn = null, blockSelector = null, ignoreClass = 'rr-ignore', ignoreSelector = null, @@ -321,6 +322,7 @@ function record( mutationCb: wrappedCanvasMutationEmit, win: window, blockClass, + blockElementFn, blockSelector, mirror, sampling: sampling.canvas, @@ -332,6 +334,7 @@ function record( scrollCb: wrappedScrollEmit, bypassOptions: { blockClass, + blockElementFn, blockSelector, maskTextClass, maskTextSelector, @@ -378,6 +381,7 @@ function record( const node = snapshot(document, { mirror, blockClass, + blockElementFn, blockSelector, maskTextClass, maskTextSelector, @@ -543,6 +547,7 @@ function record( maskInputFn, maskTextFn, keepIframeSrcFn, + blockElementFn, blockSelector, slimDOMOptions, dataURLOptions, diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 08e927a98f..75ccaa061c 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -173,6 +173,7 @@ export default class MutationBuffer { private mutationCb: observerParam['mutationCb']; private blockClass: observerParam['blockClass']; + private blockElementFn: observerParam['blockElementFn']; private blockSelector: observerParam['blockSelector']; private maskTextClass: observerParam['maskTextClass']; private maskTextSelector: observerParam['maskTextSelector']; @@ -199,6 +200,7 @@ export default class MutationBuffer { [ 'mutationCb', 'blockClass', + 'blockElementFn', 'blockSelector', 'maskTextClass', 'maskTextSelector', @@ -316,6 +318,7 @@ export default class MutationBuffer { doc: this.doc, mirror: this.mirror, blockClass: this.blockClass, + blockElementFn: this.blockElementFn, blockSelector: this.blockSelector, maskTextClass: this.maskTextClass, maskTextSelector: this.maskTextSelector, @@ -556,7 +559,13 @@ export default class MutationBuffer { const value = dom.textContent(m.target); if ( - !isBlocked(m.target, this.blockClass, this.blockSelector, false) && + !isBlocked( + m.target, + this.blockClass, + this.blockSelector, + this.blockElementFn, + false, + ) && value !== m.oldValue ) { this.texts.push({ @@ -594,7 +603,13 @@ export default class MutationBuffer { }); } if ( - isBlocked(m.target, this.blockClass, this.blockSelector, false) || + isBlocked( + m.target, + this.blockClass, + this.blockSelector, + this.blockElementFn, + false, + ) || value === m.oldValue ) { return; @@ -695,7 +710,15 @@ export default class MutationBuffer { /** * Parent is blocked, ignore all child mutations */ - if (isBlocked(m.target, this.blockClass, this.blockSelector, true)) + if ( + isBlocked( + m.target, + this.blockClass, + this.blockSelector, + this.blockElementFn, + true, + ) + ) return; if ((m.target as Element).tagName === 'TEXTAREA') { @@ -711,7 +734,13 @@ export default class MutationBuffer { ? this.mirror.getId(dom.host(m.target)) : this.mirror.getId(m.target); if ( - isBlocked(m.target, this.blockClass, this.blockSelector, false) || + isBlocked( + m.target, + this.blockClass, + this.blockSelector, + this.blockElementFn, + false, + ) || isIgnored(n, this.mirror, this.slimDOMOptions) || !isSerialized(n, this.mirror) ) { @@ -790,7 +819,15 @@ export default class MutationBuffer { // if this node is blocked `serializeNode` will turn it into a placeholder element // but we have to remove it's children otherwise they will be added as placeholders too - if (!isBlocked(n, this.blockClass, this.blockSelector, false)) { + if ( + !isBlocked( + n, + this.blockClass, + this.blockSelector, + this.blockElementFn, + false, + ) + ) { dom.childNodes(n).forEach((childN) => this.genAdds(childN)); if (hasShadowRoot(n)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index 8326d79651..deceeb7c47 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -194,6 +194,7 @@ function initMouseInteractionObserver({ mirror, blockClass, blockSelector, + blockElementFn, sampling, }: observerParam): listenerHandler { if (sampling.mouseInteraction === false) { @@ -212,7 +213,7 @@ function initMouseInteractionObserver({ const getHandler = (eventKey: keyof typeof MouseInteractions) => { return (event: MouseEvent | TouchEvent | PointerEvent) => { const target = getEventTarget(event) as Node; - if (isBlocked(target, blockClass, blockSelector, true)) { + if (isBlocked(target, blockClass, blockSelector, blockElementFn, true)) { return; } let pointerType: PointerTypes | null = null; @@ -313,10 +314,17 @@ export function initScrollObserver({ mirror, blockClass, blockSelector, + blockElementFn, sampling, }: Pick< observerParam, - 'scrollCb' | 'doc' | 'mirror' | 'blockClass' | 'blockSelector' | 'sampling' + | 'scrollCb' + | 'doc' + | 'mirror' + | 'blockClass' + | 'blockSelector' + | 'blockElementFn' + | 'sampling' >): listenerHandler { const updatePosition = callbackWrapper( throttle( @@ -324,7 +332,13 @@ export function initScrollObserver({ const target = getEventTarget(evt); if ( !target || - isBlocked(target as Node, blockClass, blockSelector, true) + isBlocked( + target as Node, + blockClass, + blockSelector, + blockElementFn, + true, + ) ) { return; } @@ -384,6 +398,7 @@ function initInputObserver({ mirror, blockClass, blockSelector, + blockElementFn, ignoreClass, ignoreSelector, maskInputOptions, @@ -407,7 +422,7 @@ function initInputObserver({ !target || !tagName || INPUT_TAGS.indexOf(tagName) < 0 || - isBlocked(target as Node, blockClass, blockSelector, true) + isBlocked(target as Node, blockClass, blockSelector, blockElementFn, true) ) { return; } @@ -1017,6 +1032,7 @@ function initMediaInteractionObserver({ mediaInteractionCb, blockClass, blockSelector, + blockElementFn, mirror, sampling, doc, @@ -1027,7 +1043,13 @@ function initMediaInteractionObserver({ const target = getEventTarget(event); if ( !target || - isBlocked(target as Node, blockClass, blockSelector, true) + isBlocked( + target as Node, + blockClass, + blockSelector, + blockElementFn, + true, + ) ) { return; } @@ -1120,7 +1142,14 @@ function initFontObserver({ fontCb, doc }: observerParam): listenerHandler { } function initSelectionObserver(param: observerParam): listenerHandler { - const { doc, mirror, blockClass, blockSelector, selectionCb } = param; + const { + doc, + mirror, + blockClass, + blockSelector, + blockElementFn, + selectionCb, + } = param; let collapsed = true; const updateSelection = callbackWrapper(() => { @@ -1139,8 +1168,20 @@ function initSelectionObserver(param: observerParam): listenerHandler { const { startContainer, startOffset, endContainer, endOffset } = range; const blocked = - isBlocked(startContainer, blockClass, blockSelector, true) || - isBlocked(endContainer, blockClass, blockSelector, true); + isBlocked( + startContainer, + blockClass, + blockSelector, + blockElementFn, + true, + ) || + isBlocked( + endContainer, + blockClass, + blockSelector, + blockElementFn, + true, + ); if (blocked) continue; diff --git a/packages/rrweb/src/record/observers/canvas/2d.ts b/packages/rrweb/src/record/observers/canvas/2d.ts index ce1332a890..c44d487b7f 100644 --- a/packages/rrweb/src/record/observers/canvas/2d.ts +++ b/packages/rrweb/src/record/observers/canvas/2d.ts @@ -1,5 +1,6 @@ import { type blockClass, + type BlockElementFn, CanvasContext, type canvasManagerMutationCallback, type IWindow, @@ -14,6 +15,7 @@ export default function initCanvas2DMutationObserver( win: IWindow, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, ): listenerHandler { const handlers: listenerHandler[] = []; const props2D = Object.getOwnPropertyNames( @@ -41,7 +43,15 @@ export default function initCanvas2DMutationObserver( this: CanvasRenderingContext2D, ...args: Array ) { - if (!isBlocked(this.canvas, blockClass, blockSelector, true)) { + if ( + !isBlocked( + this.canvas, + blockClass, + blockSelector, + blockElementFn, + true, + ) + ) { // Using setTimeout as toDataURL can be heavy // and we'd rather not block the main thread setTimeout(() => { diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts index 82c756ba24..062118b547 100644 --- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts +++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts @@ -1,6 +1,7 @@ import type { ICanvas, Mirror } from 'rrweb-snapshot'; import type { blockClass, + BlockElementFn, canvasManagerMutationCallback, canvasMutationCallback, canvasMutationCommand, @@ -61,6 +62,7 @@ export class CanvasManager { mutationCb: canvasMutationCallback; win: IWindow; blockClass: blockClass; + blockElementFn: BlockElementFn | null; blockSelector: string | null; mirror: Mirror; sampling?: 'all' | number; @@ -70,6 +72,7 @@ export class CanvasManager { sampling = 'all', win, blockClass, + blockElementFn, blockSelector, recordCanvas, dataURLOptions, @@ -78,11 +81,23 @@ export class CanvasManager { this.mirror = options.mirror; if (recordCanvas && sampling === 'all') - this.initCanvasMutationObserver(win, blockClass, blockSelector); + this.initCanvasMutationObserver( + win, + blockClass, + blockSelector, + blockElementFn, + ); if (recordCanvas && typeof sampling === 'number') - this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, { - dataURLOptions, - }); + this.initCanvasFPSObserver( + sampling, + win, + blockClass, + blockSelector, + blockElementFn, + { + dataURLOptions, + }, + ); } private processMutation: canvasManagerMutationCallback = ( @@ -107,6 +122,7 @@ export class CanvasManager { win: IWindow, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, options: { dataURLOptions: DataURLOptions; }, @@ -115,6 +131,7 @@ export class CanvasManager { win, blockClass, blockSelector, + blockElementFn, true, ); const snapshotInProgressMap: Map = new Map(); @@ -163,7 +180,9 @@ export class CanvasManager { const getCanvas = (): HTMLCanvasElement[] => { const matchedCanvas: HTMLCanvasElement[] = []; win.document.querySelectorAll('canvas').forEach((canvas) => { - if (!isBlocked(canvas, blockClass, blockSelector, true)) { + if ( + !isBlocked(canvas, blockClass, blockSelector, blockElementFn, true) + ) { matchedCanvas.push(canvas); } }); @@ -241,6 +260,7 @@ export class CanvasManager { win: IWindow, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, ): void { this.startRAFTimestamping(); this.startPendingCanvasMutationFlusher(); @@ -249,6 +269,7 @@ export class CanvasManager { win, blockClass, blockSelector, + blockElementFn, false, ); const canvas2DReset = initCanvas2DMutationObserver( @@ -256,6 +277,7 @@ export class CanvasManager { win, blockClass, blockSelector, + blockElementFn, ); const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver( @@ -263,6 +285,7 @@ export class CanvasManager { win, blockClass, blockSelector, + blockElementFn, ); this.resetObservers = () => { diff --git a/packages/rrweb/src/record/observers/canvas/canvas.ts b/packages/rrweb/src/record/observers/canvas/canvas.ts index aa9be94d51..a6e718941f 100644 --- a/packages/rrweb/src/record/observers/canvas/canvas.ts +++ b/packages/rrweb/src/record/observers/canvas/canvas.ts @@ -1,5 +1,10 @@ import type { ICanvas } from 'rrweb-snapshot'; -import type { blockClass, IWindow, listenerHandler } from '@rrweb/types'; +import type { + blockClass, + BlockElementFn, + IWindow, + listenerHandler, +} from '@rrweb/types'; import { isBlocked } from '../../../utils'; import { patch } from '@rrweb/utils'; @@ -11,6 +16,7 @@ export default function initCanvasContextObserver( win: IWindow, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, setPreserveDrawingBufferToTrue: boolean, ): listenerHandler { const handlers: listenerHandler[] = []; @@ -30,7 +36,9 @@ export default function initCanvasContextObserver( contextType: string, ...args: Array ) { - if (!isBlocked(this, blockClass, blockSelector, true)) { + if ( + !isBlocked(this, blockClass, blockSelector, blockElementFn, true) + ) { const ctxName = getNormalizedContextName(contextType); if (!('__context' in this)) (this as ICanvas).__context = ctxName; diff --git a/packages/rrweb/src/record/observers/canvas/webgl.ts b/packages/rrweb/src/record/observers/canvas/webgl.ts index 3c51323b2d..465084cfdd 100644 --- a/packages/rrweb/src/record/observers/canvas/webgl.ts +++ b/packages/rrweb/src/record/observers/canvas/webgl.ts @@ -1,5 +1,6 @@ import { type blockClass, + type BlockElementFn, CanvasContext, type canvasManagerMutationCallback, type canvasMutationWithType, @@ -16,6 +17,7 @@ function patchGLPrototype( cb: canvasManagerMutationCallback, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, win: IWindow, ): listenerHandler[] { const handlers: listenerHandler[] = []; @@ -50,7 +52,13 @@ function patchGLPrototype( saveWebGLVar(result, win, this); if ( 'tagName' in this.canvas && - !isBlocked(this.canvas, blockClass, blockSelector, true) + !isBlocked( + this.canvas, + blockClass, + blockSelector, + blockElementFn, + true, + ) ) { const recordArgs = serializeArgs(args, win, this); const mutation: canvasMutationWithType = { @@ -92,6 +100,7 @@ export default function initCanvasWebGLMutationObserver( win: IWindow, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, ): listenerHandler { const handlers: listenerHandler[] = []; @@ -102,6 +111,7 @@ export default function initCanvasWebGLMutationObserver( cb, blockClass, blockSelector, + blockElementFn, win, ), ); @@ -114,6 +124,7 @@ export default function initCanvasWebGLMutationObserver( cb, blockClass, blockSelector, + blockElementFn, win, ), ); diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index a03e326b6f..fa5136aadf 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -15,6 +15,7 @@ import type { DataURLOptions, addedNodeMutation, blockClass, + BlockElementFn, canvasMutationCallback, customElementCallback, eventWithTime, @@ -46,6 +47,7 @@ export type recordOptions = { checkoutEveryNth?: number; checkoutEveryNms?: number; blockClass?: blockClass; + blockElementFn?: BlockElementFn; blockSelector?: string; ignoreClass?: string; ignoreSelector?: string; @@ -86,6 +88,7 @@ export type observerParam = { mediaInteractionCb: mediaInteractionCallback; selectionCb: selectionCallback; blockClass: blockClass; + blockElementFn: BlockElementFn | null; blockSelector: string | null; ignoreClass: string; ignoreSelector: string | null; @@ -132,6 +135,7 @@ export type MutationBufferParam = Pick< observerParam, | 'mutationCb' | 'blockClass' + | 'blockElementFn' | 'blockSelector' | 'maskTextClass' | 'maskTextSelector' diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts index 13b15ec6c1..7cf7bb8dec 100644 --- a/packages/rrweb/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -3,6 +3,7 @@ import type { listenerHandler, hookResetter, blockClass, + BlockElementFn, addedNodeMutation, DocumentDimension, IWindow, @@ -196,6 +197,7 @@ export function closestElementOfNode(node: Node | null): HTMLElement | null { * @param node - node to check * @param blockClass - class name to check * @param blockSelector - css selectors to check + * @param blockElementFn - callback function to manually check node * @param checkAncestors - whether to search through parent nodes for the block class * @returns true/false if the node was blocked or not */ @@ -203,6 +205,7 @@ export function isBlocked( node: Node | null, blockClass: blockClass, blockSelector: string | null, + blockElementFn: BlockElementFn | null, checkAncestors: boolean, ): boolean { if (!node) { @@ -214,6 +217,10 @@ export function isBlocked( return false; } + if (blockElementFn) { + return blockElementFn(el); + } + try { if (typeof blockClass === 'string') { if (el.classList.contains(blockClass)) return true; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index bba276e483..77b6f43396 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -182,6 +182,7 @@ export type canvasEventWithTime = eventWithTime & { }; export type blockClass = string | RegExp; +export type BlockElementFn = (element: HTMLElement) => boolean; export type maskTextClass = string | RegExp;