From e692fecae324d286f39444a6f70fda7a579e2e97 Mon Sep 17 00:00:00 2001 From: Damien TUPINIER Date: Thu, 7 Aug 2025 12:33:05 +0200 Subject: [PATCH 1/3] feat(core/ignoreFrom): support callback function for advanced filtering Added support for `ignoreFrom` as a callback function, enabling custom logic to dynamically filter elements during interactions. Includes tests and examples to demonstrate new functionality. --- examples/ignoreFrom-callback/index.html | 136 ++++++++++++++++++ examples/ignoreFrom-callback/test.js | 60 ++++++++ packages/@interactjs/auto-start/base.ts | 2 +- .../core/Interactable.ignoreFrom.spec.ts | 118 +++++++++++++++ packages/@interactjs/core/Interactable.ts | 4 +- packages/@interactjs/core/options.ts | 2 +- packages/@interactjs/core/types.ts | 2 +- 7 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 examples/ignoreFrom-callback/index.html create mode 100644 examples/ignoreFrom-callback/test.js create mode 100644 packages/@interactjs/core/Interactable.ignoreFrom.spec.ts diff --git a/examples/ignoreFrom-callback/index.html b/examples/ignoreFrom-callback/index.html new file mode 100644 index 000000000..d034e75b4 --- /dev/null +++ b/examples/ignoreFrom-callback/index.html @@ -0,0 +1,136 @@ + + + + + + InteractJS - ignoreFrom Callback Example + + + +

InteractJS - ignoreFrom avec fonction callback

+ +
+

Cet exemple montre comment utiliser une fonction callback avec l'option ignoreFrom.

+

Essayez de faire glisser les éléments ci-dessous. Certaines zones sont configurées pour être ignorées.

+
+ +
+
✋ Poignée de glissement
+

Zone draggable basique

+
⛔ Zone ignorée (no-drag)
+ +
+ +
+
✋ Drag Handle
+

Zone draggable avec logique avancée

+
+ 📝 Zone avec data-ignore (ignorée) +
+ + +
+ + + + + \ No newline at end of file diff --git a/examples/ignoreFrom-callback/test.js b/examples/ignoreFrom-callback/test.js new file mode 100644 index 000000000..1b309f2c4 --- /dev/null +++ b/examples/ignoreFrom-callback/test.js @@ -0,0 +1,60 @@ +// Test simple pour vérifier que la nouvelle fonctionnalité compile +// et fonctionne avec la syntaxe JavaScript moderne + +// Simuler un environnement interactjs +const interact = require('../../packages/interactjs/index.ts'); + +// Test 1: Usage avec string (fonctionnalité existante) +const configWithString = { + ignoreFrom: '.ignore-class', + listeners: { + move: (event) => console.log('move') + } +}; + +// Test 2: Usage avec fonction callback (nouvelle fonctionnalité) +const configWithCallback = { + ignoreFrom: function(targetNode, eventTarget) { + // Logique personnalisée + if (eventTarget instanceof Element) { + return eventTarget.hasAttribute('data-ignore'); + } + return false; + }, + listeners: { + move: (event) => console.log('callback move') + } +}; + +// Test 3: Usage avec fonction callback plus complexe +const configWithComplexCallback = { + ignoreFrom: (targetNode, eventTarget) => { + if (eventTarget instanceof Element) { + // Ignorer les inputs + if (eventTarget.tagName === 'INPUT' || eventTarget.tagName === 'TEXTAREA') { + return true; + } + + // Ignorer si parent a classe 'no-drag' + let parent = eventTarget.parentElement; + while (parent && parent !== targetNode) { + if (parent.classList && parent.classList.contains('no-drag')) { + return true; + } + parent = parent.parentElement; + } + } + return false; + } +}; + +console.log('Configurations créées avec succès:'); +console.log('- Config avec string:', typeof configWithString.ignoreFrom); +console.log('- Config avec callback:', typeof configWithCallback.ignoreFrom); +console.log('- Config avec callback complexe:', typeof configWithComplexCallback.ignoreFrom); + +module.exports = { + configWithString, + configWithCallback, + configWithComplexCallback +}; \ No newline at end of file diff --git a/packages/@interactjs/auto-start/base.ts b/packages/@interactjs/auto-start/base.ts index 442225457..3c6b7d271 100644 --- a/packages/@interactjs/auto-start/base.ts +++ b/packages/@interactjs/auto-start/base.ts @@ -60,7 +60,7 @@ declare module '@interactjs/core/options' { max?: number maxPerElement?: number allowFrom?: string | Element - ignoreFrom?: string | Element + ignoreFrom?: string | Element | ((targetNode: Node, eventTarget: Node) => boolean) cursorChecker?: CursorChecker // only allow left button by default diff --git a/packages/@interactjs/core/Interactable.ignoreFrom.spec.ts b/packages/@interactjs/core/Interactable.ignoreFrom.spec.ts new file mode 100644 index 000000000..a64ac4070 --- /dev/null +++ b/packages/@interactjs/core/Interactable.ignoreFrom.spec.ts @@ -0,0 +1,118 @@ +import type { Interactable } from './Interactable' +import { MockInteractable } from '@interactjs/core/tests/_helpers' + +describe('Interactable.testIgnore with callback function', () => { + let interactable: Interactable + let targetNode: HTMLElement + let eventTarget: HTMLElement + + beforeEach(() => { + // Create a mock interactable + interactable = new MockInteractable() as any + + // Create DOM elements for testing + targetNode = document.createElement('div') + targetNode.className = 'target' + + eventTarget = document.createElement('span') + eventTarget.className = 'event-target' + + targetNode.appendChild(eventTarget) + }) + + test('should work with string selector (existing functionality)', () => { + const ignoreFrom = '.ignore-class' + eventTarget.className = 'ignore-class' + + const result = interactable.testIgnore(ignoreFrom, targetNode, eventTarget) + expect(result).toBe(true) + }) + + test('should work with element (existing functionality)', () => { + const ignoreElement = document.createElement('div') + ignoreElement.appendChild(eventTarget) + + const result = interactable.testIgnore(ignoreElement, targetNode, eventTarget) + expect(result).toBe(true) + }) + + test('should work with callback function returning true', () => { + const ignoreFrom = (targetNode: Node, eventTarget: Node) => { + return eventTarget instanceof Element && eventTarget.hasAttribute('data-ignore') + } + + eventTarget.setAttribute('data-ignore', 'true') + + const result = interactable.testIgnore(ignoreFrom, targetNode, eventTarget) + expect(result).toBe(true) + }) + + test('should work with callback function returning false', () => { + const ignoreFrom = (targetNode: Node, eventTarget: Node) => { + return eventTarget instanceof Element && eventTarget.hasAttribute('data-ignore') + } + + // No data-ignore attribute, so should return false + + const result = interactable.testIgnore(ignoreFrom, targetNode, eventTarget) + expect(result).toBe(false) + }) + + test('should work with complex callback logic', () => { + const ignoreFrom = (targetNode: Node, eventTarget: Node) => { + if (eventTarget instanceof Element) { + // Ignore input elements + if (eventTarget.tagName === 'INPUT' || eventTarget.tagName === 'TEXTAREA') { + return true + } + + // Ignore elements with no-drag class in parent chain + let parent = eventTarget.parentElement + while (parent && parent !== targetNode) { + if (parent.classList.contains('no-drag')) { + return true + } + parent = parent.parentElement + } + } + return false + } + + // Test with input element + const inputElement = document.createElement('input') + targetNode.appendChild(inputElement) + + let result = interactable.testIgnore(ignoreFrom, targetNode, inputElement) + expect(result).toBe(true) + + // Test with element having no-drag parent + const noDragParent = document.createElement('div') + noDragParent.className = 'no-drag' + const childElement = document.createElement('span') + noDragParent.appendChild(childElement) + targetNode.appendChild(noDragParent) + + result = interactable.testIgnore(ignoreFrom, targetNode, childElement) + expect(result).toBe(true) + + // Test with normal element (should not ignore) + const normalElement = document.createElement('div') + targetNode.appendChild(normalElement) + + result = interactable.testIgnore(ignoreFrom, targetNode, normalElement) + expect(result).toBe(false) + }) + + test('should return false for undefined ignoreFrom', () => { + const result = interactable.testIgnore(undefined, targetNode, eventTarget) + expect(result).toBe(false) + }) + + test('should return false for non-element eventTarget', () => { + const ignoreFrom = () => true + const textNode = document.createTextNode('text') + + const result = interactable.testIgnore(ignoreFrom, targetNode, textNode) + expect(result).toBe(false) + }) +}) \ No newline at end of file diff --git a/packages/@interactjs/core/Interactable.ts b/packages/@interactjs/core/Interactable.ts index 9616423d9..513c07693 100644 --- a/packages/@interactjs/core/Interactable.ts +++ b/packages/@interactjs/core/Interactable.ts @@ -27,7 +27,7 @@ import type { import { Eventable } from './Eventable' import type { ActionDefaults, Defaults, OptionsArg, PerActionDefaults, Options } from './options' -type IgnoreValue = string | Element | boolean +type IgnoreValue = string | Element | boolean | ((targetNode: Node, eventTarget: Node) => boolean) type DeltaSource = 'page' | 'client' const enum OnOffMethod { @@ -330,6 +330,8 @@ export class Interactable implements Partial { return matchesUpTo(element, ignoreFrom, targetNode) } else if (is.element(ignoreFrom)) { return nodeContains(ignoreFrom, element) + } else if (is.function(ignoreFrom)) { + return ignoreFrom(targetNode, element) } return false diff --git a/packages/@interactjs/core/options.ts b/packages/@interactjs/core/options.ts index 553829939..1ef677992 100644 --- a/packages/@interactjs/core/options.ts +++ b/packages/@interactjs/core/options.ts @@ -21,7 +21,7 @@ export interface PerActionDefaults { origin?: Point | string | Element listeners?: Listeners allowFrom?: string | Element - ignoreFrom?: string | Element + ignoreFrom?: string | Element | ((targetNode: Node, eventTarget: Node) => boolean) } export type Options = Partial & diff --git a/packages/@interactjs/core/types.ts b/packages/@interactjs/core/types.ts index 3d65d508c..29ae4a00a 100644 --- a/packages/@interactjs/core/types.ts +++ b/packages/@interactjs/core/types.ts @@ -123,7 +123,7 @@ export type OriginFunction = (target: Element) => Rect export interface PointerEventsOptions { holdDuration?: number allowFrom?: string - ignoreFrom?: string + ignoreFrom?: string | Element | ((targetNode: Node, eventTarget: Node) => boolean) origin?: Rect | Point | string | Element | OriginFunction } From 28a2fe86262c20e277ac90d2a8d971a78f05534d Mon Sep 17 00:00:00 2001 From: Damien TUPINIER Date: Thu, 7 Aug 2025 14:16:50 +0200 Subject: [PATCH 2/3] feat(examples/allowFrom): add callback-based demos and tests Introduced examples demonstrating the use of `allowFrom` with callback functions for advanced interaction control. Includes new tests and detailed use-case scenarios for role-based and complex logic handling. --- examples/allowFrom-callback/index.html | 321 +++++++++++++ examples/combined-callbacks/index.html | 436 ++++++++++++++++++ examples/ignoreFrom-callback/index.html | 46 +- packages/@interactjs/auto-start/base.ts | 2 +- .../core/Interactable.allowFrom.spec.ts | 156 +++++++ packages/@interactjs/core/Interactable.ts | 27 +- packages/@interactjs/core/options.ts | 2 +- packages/@interactjs/core/types.ts | 2 +- 8 files changed, 954 insertions(+), 38 deletions(-) create mode 100644 examples/allowFrom-callback/index.html create mode 100644 examples/combined-callbacks/index.html create mode 100644 packages/@interactjs/core/Interactable.allowFrom.spec.ts diff --git a/examples/allowFrom-callback/index.html b/examples/allowFrom-callback/index.html new file mode 100644 index 000000000..8e6167616 --- /dev/null +++ b/examples/allowFrom-callback/index.html @@ -0,0 +1,321 @@ + + + + + + InteractJS - allowFrom Callback Example + + + +

InteractJS - allowFrom with Callback Function

+ +
+

Demonstration of the `allowFrom` option with callback:

+
    +
  • Only certain areas allow drag initiation
  • +
  • Logic is fully customizable via a callback function
  • +
  • Change your role below to see different behaviors
  • +
+
+ +
+ Your role: + + + +
+ +
+
+
✋ Handle Only
+
+

This container can only be moved by grabbing the blue handle above.

+ +

Try clicking here... it won't work!

+
+
+ +
+
🔐 Role-based Access
+
+ 👑 Admin Only Zone - Drag allowed if role = Admin +
+
+ ✏️ Editor+ Zone - Drag allowed if role = Admin or Editor +
+
+ ⛔ Forbidden Zone - No dragging possible +
+
+ +
+
🧠 Advanced Logic
+
+
📝 Conditional zone (role-based)
+ +
+ 🔧 Admin special feature +
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/examples/combined-callbacks/index.html b/examples/combined-callbacks/index.html new file mode 100644 index 000000000..6bcf9418e --- /dev/null +++ b/examples/combined-callbacks/index.html @@ -0,0 +1,436 @@ + + + + + + InteractJS - allowFrom & ignoreFrom Callbacks Combined + + + +

InteractJS - allowFrom & ignoreFrom with Callbacks

+ +
+

Advanced demonstration combining allowFrom and ignoreFrom:

+

This example shows how to use both options together with callback functions to create sophisticated access control logic.

+

Logic: allowFrom determines allowed zones, then ignoreFrom can override and forbid specific zones.

+
+ +
+
+
+ Allowed zone (allowFrom) +
+
+
+ Ignored zone (ignoreFrom) +
+
+
+ Neutral zone +
+
+
+ Priority zone +
+
+ +
+
+
🎯 Complex Access Control
+ +
+ ✅ Basic allowed zone + +
+ +
+ ❌ Always ignored zone +
+ Even if nested in allowed, it's ignored +
+
+ +
+ ⚪ Neutral zone (not allowed by default) +
+ +
+ 🔥 Priority management zone +
+ ✨ High priority (allowed) +
+
+ 🔧 Under maintenance (ignored) +
+
+
+ +
+
👥 Role-based Permissions
+ +
+ 👤 User zone (all roles) +
+ +
+ 👑 Admin only zone +
+ 🔒 Sensitive data (always ignored) +
+
+ 🛠️ Admin tools +
+
+ + +
+
+ +
+

📊 Decision Log

+
+
+ + + + + \ No newline at end of file diff --git a/examples/ignoreFrom-callback/index.html b/examples/ignoreFrom-callback/index.html index d034e75b4..b6b8754fc 100644 --- a/examples/ignoreFrom-callback/index.html +++ b/examples/ignoreFrom-callback/index.html @@ -1,5 +1,5 @@ - + @@ -55,65 +55,65 @@ -

InteractJS - ignoreFrom avec fonction callback

+

InteractJS - ignoreFrom with Callback Function

-

Cet exemple montre comment utiliser une fonction callback avec l'option ignoreFrom.

-

Essayez de faire glisser les éléments ci-dessous. Certaines zones sont configurées pour être ignorées.

+

This example shows how to use a callback function with the ignoreFrom option.

+

Try dragging the elements below. Some areas are configured to be ignored.

-
✋ Poignée de glissement
-

Zone draggable basique

-
⛔ Zone ignorée (no-drag)
- +
✋ Drag Handle
+

Basic draggable area

+
⛔ Ignored zone (no-drag class)
+
✋ Drag Handle
-

Zone draggable avec logique avancée

+

Draggable area with advanced logic

- 📝 Zone avec data-ignore (ignorée) + 📝 Zone with data-ignore (ignored)
- - + +