Skip to content

Commit

Permalink
refactor(core): simplify target to interactable mapping
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/@interactjs/arrange/componentUtils.ts
#	packages/@interactjs/arrange/types.ts
#	packages/@interactjs/multi-target/multiTarget.spec.ts
#	packages/@interactjs/multi-target/plugin.ts
#	packages/@interactjs/vue/package.json
  • Loading branch information
taye committed Nov 24, 2023
1 parent d5d3e37 commit 539223f
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 149 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"@babel/preset-typescript": "^7.17.12",
"@babel/register": "^7.17.7",
"@babel/runtime": "^7.18.3",
"@testing-library/dom": "^9.3.3",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "27",
"@types/node": "^17.0.42",
"@types/react": "^18.0.12",
Expand Down
15 changes: 3 additions & 12 deletions packages/@interactjs/actions/drop/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ function install (scope: Scope) {
defaults.actions.drop = drop.defaults
}

function collectDrops ({ interactables }: Scope, draggableElement: Element) {
function collectDropzones ({ interactables }: Scope, draggableElement: Element) {
const drops: ActiveDrop[] = []

// collect all dropzones and their elements which qualify for a drop
Expand All @@ -281,16 +281,7 @@ function collectDrops ({ interactables }: Scope, draggableElement: Element) {
continue
}

// query for new elements if necessary
const dropElements = (
is.string(dropzone.target)
? dropzone._context.querySelectorAll(dropzone.target)
: is.array(dropzone.target)
? dropzone.target
: [dropzone.target]
) as Element[]

for (const dropzoneElement of dropElements) {
for (const dropzoneElement of dropzone.getAllElements()) {
if (dropzoneElement !== draggableElement) {
drops.push({
dropzone,
Expand Down Expand Up @@ -321,7 +312,7 @@ function fireActivationEvents (activeDrops: ActiveDrop[], event: DropEvent) {
// dynamicDrop is true
function getActiveDrops (scope: Scope, dragElement: Element) {
// get dropzones and their elements that could receive the draggable
const activeDrops = collectDrops(scope, dragElement)
const activeDrops = collectDropzones(scope, dragElement)

for (const activeDrop of activeDrops) {
activeDrop.rect = activeDrop.dropzone.getRect(activeDrop.element)
Expand Down
2 changes: 1 addition & 1 deletion packages/@interactjs/core/InteractStatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function createInteractStatic (scope: Scope): _InteractStatic {
* @return {Interactable}
*/
const interact = ((target: Target, options: Options) => {
let interactable = scope.interactables.get(target, options)
let interactable = scope.interactables.getExisting(target, options)

if (!interactable) {
interactable = scope.interactables.new(target, options)
Expand Down
15 changes: 5 additions & 10 deletions packages/@interactjs/core/Interactable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,18 @@ describe('core/Interactable', () => {
})

test('Interactable unset correctly', () => {
const scope = helpers.mockScope() as any
const scope = helpers.mockScope()

const div = scope.document.createElement('div')
const interactable = scope.interactables.new(div)

const mappingInfo = div[scope.id][0]

scope.fire('interactable:unset', { interactable })
expect(div[scope.id]).toHaveLength(1)

// unset mappingInfo context
expect(mappingInfo.context).toBeNull()
interactable.unset()

// unset mappingInfo interactable
expect(mappingInfo.interactable).toBeNull()

// unset target are removed
// clears target mapping
expect(div[scope.id]).toHaveLength(0)

div.remove()
})

Expand Down
15 changes: 15 additions & 0 deletions packages/@interactjs/core/Interactable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,21 @@ export class Interactable implements Partial<Eventable> {
return this.options.deltaSource
}

/** @internal */
getAllElements (): Element[] {
const { target } = this

if (is.string(target)) {
return Array.from(this._context.querySelectorAll(target))
}

if (is.func(target) && (target as any).getAllElements) {
return (target as any).getAllElements()
}

return is.element(target) ? [target] : []
}

/**
* Gets the selector context Node of the Interactable. The default is
* `window.document`.
Expand Down
44 changes: 15 additions & 29 deletions packages/@interactjs/core/InteractableSet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Interactable } from '@interactjs/core/Interactable'
import type { OptionsArg, Options } from '@interactjs/core/options'
import type { Scope } from '@interactjs/core/scope'
import type { Target, Context } from '@interactjs/core/types'
import type { Target } from '@interactjs/core/types'
import * as arr from '@interactjs/utils/arr'
import * as domUtils from '@interactjs/utils/domUtils'
import extend from '@interactjs/utils/extend'
Expand All @@ -18,17 +18,12 @@ declare module '@interactjs/core/scope' {
}
}

interface InteractableScopeProp {
context: Context
interactable: Interactable
}

export class InteractableSet {
// all set interactables
list: Interactable[] = []

selectorMap: {
[selector: string]: InteractableScopeProp[]
[selector: string]: Interactable[]
} = {}

scope: Scope
Expand All @@ -37,18 +32,13 @@ export class InteractableSet {
this.scope = scope
scope.addListeners({
'interactable:unset': ({ interactable }) => {
const { target, _context: context } = interactable
const targetMappings: InteractableScopeProp[] = is.string(target)
const { target } = interactable
const interactablesOnTarget: Interactable[] = is.string(target)
? this.selectorMap[target]
: (target as any)[this.scope.id]

const targetIndex = arr.findIndex(targetMappings, (m) => m.context === context)
if (targetMappings[targetIndex]) {
// Destroying mappingInfo's context and interactable
targetMappings[targetIndex].context = null as never
targetMappings[targetIndex].interactable = null as never
}
targetMappings.splice(targetIndex, 1)
const targetIndex = arr.findIndex(interactablesOnTarget, (i) => i === interactable)
interactablesOnTarget.splice(targetIndex, 1)
},
})
}
Expand All @@ -58,7 +48,6 @@ export class InteractableSet {
actions: this.scope.actions,
})
const interactable = new this.scope.Interactable(target, options, this.scope.document, this.scope.events)
const mappingInfo = { context: interactable._context, interactable }

this.scope.addDocument(interactable._doc)
this.list.push(interactable)
Expand All @@ -67,7 +56,7 @@ export class InteractableSet {
if (!this.selectorMap[target]) {
this.selectorMap[target] = []
}
this.selectorMap[target].push(mappingInfo)
this.selectorMap[target].push(interactable)
} else {
if (!(interactable.target as any)[this.scope.id]) {
Object.defineProperty(target, this.scope.id, {
Expand All @@ -76,7 +65,7 @@ export class InteractableSet {
})
}

;(target as any)[this.scope.id].push(mappingInfo)
;(target as any)[this.scope.id].push(interactable)
}

this.scope.fire('interactable:new', {
Expand All @@ -89,23 +78,20 @@ export class InteractableSet {
return interactable
}

get (target: Target, options?: Options) {
getExisting (target: Target, options?: Options) {
const context = (options && options.context) || this.scope.document
const isSelector = is.string(target)
const targetMappings: InteractableScopeProp[] = isSelector
const interactablesOnTarget: Interactable[] = isSelector
? this.selectorMap[target as string]
: (target as any)[this.scope.id]

if (!targetMappings) {
return null
}
if (!interactablesOnTarget) return undefined

const found = arr.find(
targetMappings,
(m) => m.context === context && (isSelector || m.interactable.inContext(target as any)),
return arr.find(
interactablesOnTarget,
(interactable) =>
interactable._context === context && (isSelector || interactable.inContext(target as any)),
)

return found && found.interactable
}

forEachMatch<T> (node: Node, callback: (interactable: Interactable) => T): T | void {
Expand Down
1 change: 1 addition & 0 deletions packages/@interactjs/core/NativeTypes.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const NativePointerEvent = null as unknown as InstanceType<typeof PointerEvent>
export type NativeEventTarget = EventTarget
export type NativeElement = Element
2 changes: 1 addition & 1 deletion packages/@interactjs/interact/interact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test('interact export', () => {

for (const { interactable, target, context } of results) {
// interactions.get returns correct result with identical targets and different contexts
expect(scope.interactables.get(target, { context })).toBe(interactable)
expect(scope.interactables.getExisting(target, { context })).toBe(interactable)
}

const doc3 = makeIframeDoc()
Expand Down
7 changes: 1 addition & 6 deletions packages/@interactjs/reflow/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { DoAnyPhaseArg, Interaction } from '@interactjs/core/Interaction'
import type { Scope, Plugin } from '@interactjs/core/scope'
import type { ActionName, ActionProps, Element } from '@interactjs/core/types'
import * as arr from '@interactjs/utils/arr'
import is from '@interactjs/utils/is'
import { copyAction } from '@interactjs/utils/misc'
import * as pointerUtils from '@interactjs/utils/pointerUtils'
import { tlbrToXywh } from '@interactjs/utils/rect'
Expand Down Expand Up @@ -69,11 +68,7 @@ function doReflow<T extends ActionName> (
action: ActionProps<T>,
scope: Scope,
): Promise<Interactable> {
const elements = (
is.string(interactable.target)
? arr.from(interactable._context.querySelectorAll(interactable.target))
: [interactable.target]
) as Element[]
const elements = interactable.getAllElements()

// tslint:disable-next-line variable-name
const Promise = (scope.window as any).Promise
Expand Down
4 changes: 2 additions & 2 deletions packages/@interactjs/reflow/reflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ describe('reflow', () => {
interactable.fire = ((iEvent: any) => {
fired.push(iEvent)
}) as any
;(interactable.target as any) = {}
;(interactable.options as any).TEST = { enabled: true }
interactable.rectChecker(() => ({ ...rect }))

Expand All @@ -47,6 +46,7 @@ describe('reflow', () => {
interactable.reflow(testAction)

const phases = ['reflow', 'start', 'move', 'end']
expect(phases.map((_phase, index) => fired[index]?.type)).toEqual(phases.map((phase) => `TEST${phase}`))

for (const index in phases) {
const phase = phases[index]
Expand Down Expand Up @@ -84,7 +84,7 @@ describe('reflow', () => {
let reflowEvent: any
let promise: Promise<Interactable>

const interactable = scope.interactables.new(scope.window)
const interactable = scope.interactables.new(scope.document.documentElement)
const rect = Object.freeze({ top: 100, left: 200, bottom: 300, right: 400 })
interactable.rectChecker(() => ({ ...rect }))
interactable.fire = ((iEvent: any) => {
Expand Down
Loading

0 comments on commit 539223f

Please sign in to comment.