Add yet-another-react-lightbox package and update .gitignore to exclude node_modules

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-12 18:50:30 +00:00
parent bd2a5570a9
commit c92f4a5edd
9304 changed files with 29 additions and 2008667 deletions

View File

@@ -1,123 +0,0 @@
import { invariant } from '@react-dnd/invariant'
import type {
Action,
BeginDragOptions,
BeginDragPayload,
DragDropManager,
DragDropMonitor,
HandlerRegistry,
Identifier,
XYCoord,
} from '../../interfaces.js'
import { isObject } from '../../utils/js_utils.js'
import { setClientOffset } from './local/setClientOffset.js'
import { BEGIN_DRAG, INIT_COORDS } from './types.js'
const ResetCoordinatesAction = {
type: INIT_COORDS,
payload: {
clientOffset: null,
sourceClientOffset: null,
},
}
export function createBeginDrag(manager: DragDropManager) {
return function beginDrag(
sourceIds: Identifier[] = [],
options: BeginDragOptions = {
publishSource: true,
},
): Action<BeginDragPayload> | undefined {
const {
publishSource = true,
clientOffset,
getSourceClientOffset,
}: BeginDragOptions = options
const monitor = manager.getMonitor()
const registry = manager.getRegistry()
// Initialize the coordinates using the client offset
manager.dispatch(setClientOffset(clientOffset))
verifyInvariants(sourceIds, monitor, registry)
// Get the draggable source
const sourceId = getDraggableSource(sourceIds, monitor)
if (sourceId == null) {
manager.dispatch(ResetCoordinatesAction)
return
}
// Get the source client offset
let sourceClientOffset: XYCoord | null = null
if (clientOffset) {
if (!getSourceClientOffset) {
throw new Error('getSourceClientOffset must be defined')
}
verifyGetSourceClientOffsetIsFunction(getSourceClientOffset)
sourceClientOffset = getSourceClientOffset(sourceId)
}
// Initialize the full coordinates
manager.dispatch(setClientOffset(clientOffset, sourceClientOffset))
const source = registry.getSource(sourceId)
const item = source.beginDrag(monitor, sourceId)
// If source.beginDrag returns null, this is an indicator to cancel the drag
if (item == null) {
return undefined
}
verifyItemIsObject(item)
registry.pinSource(sourceId)
const itemType = registry.getSourceType(sourceId)
return {
type: BEGIN_DRAG,
payload: {
itemType,
item,
sourceId,
clientOffset: clientOffset || null,
sourceClientOffset: sourceClientOffset || null,
isSourcePublic: !!publishSource,
},
}
}
}
function verifyInvariants(
sourceIds: Identifier[],
monitor: DragDropMonitor,
registry: HandlerRegistry,
) {
invariant(!monitor.isDragging(), 'Cannot call beginDrag while dragging.')
sourceIds.forEach(function (sourceId) {
invariant(
registry.getSource(sourceId),
'Expected sourceIds to be registered.',
)
})
}
function verifyGetSourceClientOffsetIsFunction(getSourceClientOffset: any) {
invariant(
typeof getSourceClientOffset === 'function',
'When clientOffset is provided, getSourceClientOffset must be a function.',
)
}
function verifyItemIsObject(item: any) {
invariant(isObject(item), 'Item must be an object.')
}
function getDraggableSource(sourceIds: Identifier[], monitor: DragDropMonitor) {
let sourceId = null
for (let i = sourceIds.length - 1; i >= 0; i--) {
if (monitor.canDragSource(sourceIds[i])) {
sourceId = sourceIds[i]
break
}
}
return sourceId
}

View File

@@ -1,74 +0,0 @@
import { invariant } from '@react-dnd/invariant'
import type {
Action,
DragDropManager,
DragDropMonitor,
DropPayload,
HandlerRegistry,
Identifier,
} from '../../interfaces.js'
import { isObject } from '../../utils/js_utils.js'
import { DROP } from './types.js'
export function createDrop(manager: DragDropManager) {
return function drop(options = {}): void {
const monitor = manager.getMonitor()
const registry = manager.getRegistry()
verifyInvariants(monitor)
const targetIds = getDroppableTargets(monitor)
// Multiple actions are dispatched here, which is why this doesn't return an action
targetIds.forEach((targetId, index) => {
const dropResult = determineDropResult(targetId, index, registry, monitor)
const action: Action<DropPayload> = {
type: DROP,
payload: {
dropResult: {
...options,
...dropResult,
},
},
}
manager.dispatch(action)
})
}
}
function verifyInvariants(monitor: DragDropMonitor) {
invariant(monitor.isDragging(), 'Cannot call drop while not dragging.')
invariant(
!monitor.didDrop(),
'Cannot call drop twice during one drag operation.',
)
}
function determineDropResult(
targetId: Identifier,
index: number,
registry: HandlerRegistry,
monitor: DragDropMonitor,
) {
const target = registry.getTarget(targetId)
let dropResult = target ? target.drop(monitor, targetId) : undefined
verifyDropResultType(dropResult)
if (typeof dropResult === 'undefined') {
dropResult = index === 0 ? {} : monitor.getDropResult()
}
return dropResult
}
function verifyDropResultType(dropResult: any) {
invariant(
typeof dropResult === 'undefined' || isObject(dropResult),
'Drop result must either be an object or undefined.',
)
}
function getDroppableTargets(monitor: DragDropMonitor) {
const targetIds = monitor
.getTargetIds()
.filter(monitor.canDropOnTarget, monitor)
targetIds.reverse()
return targetIds
}

View File

@@ -1,28 +0,0 @@
import { invariant } from '@react-dnd/invariant'
import type {
DragDropManager,
DragDropMonitor,
SentinelAction,
} from '../../interfaces.js'
import { END_DRAG } from './types.js'
export function createEndDrag(manager: DragDropManager) {
return function endDrag(): SentinelAction {
const monitor = manager.getMonitor()
const registry = manager.getRegistry()
verifyIsDragging(monitor)
const sourceId = monitor.getSourceId()
if (sourceId != null) {
const source = registry.getSource(sourceId, true)
source.endDrag(monitor, sourceId)
registry.unpinSource()
}
return { type: END_DRAG }
}
}
function verifyIsDragging(monitor: DragDropMonitor) {
invariant(monitor.isDragging(), 'Cannot call endDrag while not dragging.')
}

View File

@@ -1,89 +0,0 @@
import { invariant } from '@react-dnd/invariant'
import type {
Action,
DragDropManager,
DragDropMonitor,
HandlerRegistry,
HoverOptions,
HoverPayload,
Identifier,
} from '../../interfaces.js'
import { matchesType } from '../../utils/matchesType.js'
import { HOVER } from './types.js'
export function createHover(manager: DragDropManager) {
return function hover(
targetIdsArg: string[],
{ clientOffset }: HoverOptions = {},
): Action<HoverPayload> {
verifyTargetIdsIsArray(targetIdsArg)
const targetIds = targetIdsArg.slice(0)
const monitor = manager.getMonitor()
const registry = manager.getRegistry()
const draggedItemType = monitor.getItemType()
removeNonMatchingTargetIds(targetIds, registry, draggedItemType)
checkInvariants(targetIds, monitor, registry)
hoverAllTargets(targetIds, monitor, registry)
return {
type: HOVER,
payload: {
targetIds,
clientOffset: clientOffset || null,
},
}
}
}
function verifyTargetIdsIsArray(targetIdsArg: string[]) {
invariant(Array.isArray(targetIdsArg), 'Expected targetIds to be an array.')
}
function checkInvariants(
targetIds: string[],
monitor: DragDropMonitor,
registry: HandlerRegistry,
) {
invariant(monitor.isDragging(), 'Cannot call hover while not dragging.')
invariant(!monitor.didDrop(), 'Cannot call hover after drop.')
for (let i = 0; i < targetIds.length; i++) {
const targetId = targetIds[i] as string
invariant(
targetIds.lastIndexOf(targetId) === i,
'Expected targetIds to be unique in the passed array.',
)
const target = registry.getTarget(targetId)
invariant(target, 'Expected targetIds to be registered.')
}
}
function removeNonMatchingTargetIds(
targetIds: string[],
registry: HandlerRegistry,
draggedItemType: Identifier | null,
) {
// Remove those targetIds that don't match the targetType. This
// fixes shallow isOver which would only be non-shallow because of
// non-matching targets.
for (let i = targetIds.length - 1; i >= 0; i--) {
const targetId = targetIds[i] as string
const targetType = registry.getTargetType(targetId)
if (!matchesType(targetType, draggedItemType)) {
targetIds.splice(i, 1)
}
}
}
function hoverAllTargets(
targetIds: string[],
monitor: DragDropMonitor,
registry: HandlerRegistry,
) {
// Finally call hover on all matching targets.
targetIds.forEach(function (targetId) {
const target = registry.getTarget(targetId)
target.hover(monitor, targetId)
})
}

View File

@@ -1,20 +0,0 @@
import type { DragDropActions, DragDropManager } from '../../interfaces.js'
import { createBeginDrag } from './beginDrag.js'
import { createDrop } from './drop.js'
import { createEndDrag } from './endDrag.js'
import { createHover } from './hover.js'
import { createPublishDragSource } from './publishDragSource.js'
export * from './types.js'
export function createDragDropActions(
manager: DragDropManager,
): DragDropActions {
return {
beginDrag: createBeginDrag(manager),
publishDragSource: createPublishDragSource(manager),
hover: createHover(manager),
drop: createDrop(manager),
endDrag: createEndDrag(manager),
}
}

View File

@@ -1,17 +0,0 @@
import type { AnyAction } from 'redux'
import type { XYCoord } from '../../../interfaces.js'
import { INIT_COORDS } from '../types.js'
export function setClientOffset(
clientOffset: XYCoord | null | undefined,
sourceClientOffset?: XYCoord | null | undefined,
): AnyAction {
return {
type: INIT_COORDS,
payload: {
sourceClientOffset: sourceClientOffset || null,
clientOffset: clientOffset || null,
},
}
}

View File

@@ -1,12 +0,0 @@
import type { DragDropManager, SentinelAction } from '../../interfaces.js'
import { PUBLISH_DRAG_SOURCE } from './types.js'
export function createPublishDragSource(manager: DragDropManager) {
return function publishDragSource(): SentinelAction | undefined {
const monitor = manager.getMonitor()
if (monitor.isDragging()) {
return { type: PUBLISH_DRAG_SOURCE }
}
return
}
}

View File

@@ -1,6 +0,0 @@
export const INIT_COORDS = 'dnd-core/INIT_COORDS'
export const BEGIN_DRAG = 'dnd-core/BEGIN_DRAG'
export const PUBLISH_DRAG_SOURCE = 'dnd-core/PUBLISH_DRAG_SOURCE'
export const HOVER = 'dnd-core/HOVER'
export const DROP = 'dnd-core/DROP'
export const END_DRAG = 'dnd-core/END_DRAG'

View File

@@ -1,42 +0,0 @@
import type { Action, SourceIdPayload, TargetIdPayload } from '../interfaces.js'
export const ADD_SOURCE = 'dnd-core/ADD_SOURCE'
export const ADD_TARGET = 'dnd-core/ADD_TARGET'
export const REMOVE_SOURCE = 'dnd-core/REMOVE_SOURCE'
export const REMOVE_TARGET = 'dnd-core/REMOVE_TARGET'
export function addSource(sourceId: string): Action<SourceIdPayload> {
return {
type: ADD_SOURCE,
payload: {
sourceId,
},
}
}
export function addTarget(targetId: string): Action<TargetIdPayload> {
return {
type: ADD_TARGET,
payload: {
targetId,
},
}
}
export function removeSource(sourceId: string): Action<SourceIdPayload> {
return {
type: REMOVE_SOURCE,
payload: {
sourceId,
},
}
}
export function removeTarget(targetId: string): Action<TargetIdPayload> {
return {
type: REMOVE_TARGET,
payload: {
targetId,
},
}
}

View File

@@ -1,87 +0,0 @@
import type { Action, Store } from 'redux'
import { createDragDropActions } from '../actions/dragDrop/index.js'
import type {
ActionCreator,
Backend,
DragDropActions,
DragDropManager,
DragDropMonitor,
HandlerRegistry,
} from '../interfaces.js'
import type { State } from '../reducers/index.js'
import type { DragDropMonitorImpl } from './DragDropMonitorImpl.js'
export class DragDropManagerImpl implements DragDropManager {
private store: Store<State>
private monitor: DragDropMonitor
private backend: Backend | undefined
private isSetUp = false
public constructor(store: Store<State>, monitor: DragDropMonitor) {
this.store = store
this.monitor = monitor
store.subscribe(this.handleRefCountChange)
}
public receiveBackend(backend: Backend): void {
this.backend = backend
}
public getMonitor(): DragDropMonitor {
return this.monitor
}
public getBackend(): Backend {
return this.backend as Backend
}
public getRegistry(): HandlerRegistry {
return (this.monitor as DragDropMonitorImpl).registry
}
public getActions(): DragDropActions {
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
const manager = this
const { dispatch } = this.store
function bindActionCreator(actionCreator: ActionCreator<any>) {
return (...args: any[]) => {
const action = actionCreator.apply(manager, args as any)
if (typeof action !== 'undefined') {
dispatch(action)
}
}
}
const actions = createDragDropActions(this)
return Object.keys(actions).reduce(
(boundActions: DragDropActions, key: string) => {
const action: ActionCreator<any> = (actions as any)[
key
] as ActionCreator<any>
;(boundActions as any)[key] = bindActionCreator(action)
return boundActions
},
{} as DragDropActions,
)
}
public dispatch(action: Action<any>): void {
this.store.dispatch(action)
}
private handleRefCountChange = (): void => {
const shouldSetUp = this.store.getState().refCount > 0
if (this.backend) {
if (shouldSetUp && !this.isSetUp) {
this.backend.setup()
this.isSetUp = true
} else if (!shouldSetUp && this.isSetUp) {
this.backend.teardown()
this.isSetUp = false
}
}
}
}

View File

@@ -1,216 +0,0 @@
import { invariant } from '@react-dnd/invariant'
import type { Store } from 'redux'
import type {
DragDropMonitor,
HandlerRegistry,
Identifier,
Listener,
Unsubscribe,
XYCoord,
} from '../interfaces.js'
import type { State } from '../reducers/index.js'
import {
getDifferenceFromInitialOffset,
getSourceClientOffset,
} from '../utils/coords.js'
import { areDirty } from '../utils/dirtiness.js'
import { matchesType } from '../utils/matchesType.js'
export class DragDropMonitorImpl implements DragDropMonitor {
private store: Store<State>
public readonly registry: HandlerRegistry
public constructor(store: Store<State>, registry: HandlerRegistry) {
this.store = store
this.registry = registry
}
public subscribeToStateChange(
listener: Listener,
options: { handlerIds?: string[] } = {},
): Unsubscribe {
const { handlerIds } = options
invariant(typeof listener === 'function', 'listener must be a function.')
invariant(
typeof handlerIds === 'undefined' || Array.isArray(handlerIds),
'handlerIds, when specified, must be an array of strings.',
)
let prevStateId = this.store.getState().stateId
const handleChange = () => {
const state = this.store.getState()
const currentStateId = state.stateId
try {
const canSkipListener =
currentStateId === prevStateId ||
(currentStateId === prevStateId + 1 &&
!areDirty(state.dirtyHandlerIds, handlerIds))
if (!canSkipListener) {
listener()
}
} finally {
prevStateId = currentStateId
}
}
return this.store.subscribe(handleChange)
}
public subscribeToOffsetChange(listener: Listener): Unsubscribe {
invariant(typeof listener === 'function', 'listener must be a function.')
let previousState = this.store.getState().dragOffset
const handleChange = () => {
const nextState = this.store.getState().dragOffset
if (nextState === previousState) {
return
}
previousState = nextState
listener()
}
return this.store.subscribe(handleChange)
}
public canDragSource(sourceId: string | undefined): boolean {
if (!sourceId) {
return false
}
const source = this.registry.getSource(sourceId)
invariant(source, `Expected to find a valid source. sourceId=${sourceId}`)
if (this.isDragging()) {
return false
}
return source.canDrag(this, sourceId)
}
public canDropOnTarget(targetId: string | undefined): boolean {
// undefined on initial render
if (!targetId) {
return false
}
const target = this.registry.getTarget(targetId)
invariant(target, `Expected to find a valid target. targetId=${targetId}`)
if (!this.isDragging() || this.didDrop()) {
return false
}
const targetType = this.registry.getTargetType(targetId)
const draggedItemType = this.getItemType()
return (
matchesType(targetType, draggedItemType) && target.canDrop(this, targetId)
)
}
public isDragging(): boolean {
return Boolean(this.getItemType())
}
public isDraggingSource(sourceId: string | undefined): boolean {
// undefined on initial render
if (!sourceId) {
return false
}
const source = this.registry.getSource(sourceId, true)
invariant(source, `Expected to find a valid source. sourceId=${sourceId}`)
if (!this.isDragging() || !this.isSourcePublic()) {
return false
}
const sourceType = this.registry.getSourceType(sourceId)
const draggedItemType = this.getItemType()
if (sourceType !== draggedItemType) {
return false
}
return source.isDragging(this, sourceId)
}
public isOverTarget(
targetId: string | undefined,
options = { shallow: false },
): boolean {
// undefined on initial render
if (!targetId) {
return false
}
const { shallow } = options
if (!this.isDragging()) {
return false
}
const targetType = this.registry.getTargetType(targetId)
const draggedItemType = this.getItemType()
if (draggedItemType && !matchesType(targetType, draggedItemType)) {
return false
}
const targetIds = this.getTargetIds()
if (!targetIds.length) {
return false
}
const index = targetIds.indexOf(targetId)
if (shallow) {
return index === targetIds.length - 1
} else {
return index > -1
}
}
public getItemType(): Identifier {
return this.store.getState().dragOperation.itemType as Identifier
}
public getItem(): any {
return this.store.getState().dragOperation.item
}
public getSourceId(): string | null {
return this.store.getState().dragOperation.sourceId
}
public getTargetIds(): string[] {
return this.store.getState().dragOperation.targetIds
}
public getDropResult(): any {
return this.store.getState().dragOperation.dropResult
}
public didDrop(): boolean {
return this.store.getState().dragOperation.didDrop
}
public isSourcePublic(): boolean {
return Boolean(this.store.getState().dragOperation.isSourcePublic)
}
public getInitialClientOffset(): XYCoord | null {
return this.store.getState().dragOffset.initialClientOffset
}
public getInitialSourceClientOffset(): XYCoord | null {
return this.store.getState().dragOffset.initialSourceClientOffset
}
public getClientOffset(): XYCoord | null {
return this.store.getState().dragOffset.clientOffset
}
public getSourceClientOffset(): XYCoord | null {
return getSourceClientOffset(this.store.getState().dragOffset)
}
public getDifferenceFromInitialOffset(): XYCoord | null {
return getDifferenceFromInitialOffset(this.store.getState().dragOffset)
}
}

View File

@@ -1,181 +0,0 @@
import { asap } from '@react-dnd/asap'
import { invariant } from '@react-dnd/invariant'
import type { Store } from 'redux'
import {
addSource,
addTarget,
removeSource,
removeTarget,
} from '../actions/registry.js'
import {
validateSourceContract,
validateTargetContract,
validateType,
} from '../contracts.js'
import type {
DragSource,
DropTarget,
HandlerRegistry,
Identifier,
SourceType,
TargetType,
} from '../interfaces.js'
import { HandlerRole } from '../interfaces.js'
import type { State } from '../reducers/index.js'
import { getNextUniqueId } from '../utils/getNextUniqueId.js'
function getNextHandlerId(role: HandlerRole): string {
const id = getNextUniqueId().toString()
switch (role) {
case HandlerRole.SOURCE:
return `S${id}`
case HandlerRole.TARGET:
return `T${id}`
default:
throw new Error(`Unknown Handler Role: ${role}`)
}
}
function parseRoleFromHandlerId(handlerId: string) {
switch (handlerId[0]) {
case 'S':
return HandlerRole.SOURCE
case 'T':
return HandlerRole.TARGET
default:
throw new Error(`Cannot parse handler ID: ${handlerId}`)
}
}
function mapContainsValue<T>(map: Map<string, T>, searchValue: T) {
const entries = map.entries()
let isDone = false
do {
const {
done,
value: [, value],
} = entries.next()
if (value === searchValue) {
return true
}
isDone = !!done
} while (!isDone)
return false
}
export class HandlerRegistryImpl implements HandlerRegistry {
private types: Map<string, SourceType | TargetType> = new Map()
private dragSources: Map<string, DragSource> = new Map()
private dropTargets: Map<string, DropTarget> = new Map()
private pinnedSourceId: string | null = null
private pinnedSource: any = null
private store: Store<State>
public constructor(store: Store<State>) {
this.store = store
}
public addSource(type: SourceType, source: DragSource): string {
validateType(type)
validateSourceContract(source)
const sourceId = this.addHandler(HandlerRole.SOURCE, type, source)
this.store.dispatch(addSource(sourceId))
return sourceId
}
public addTarget(type: TargetType, target: DropTarget): string {
validateType(type, true)
validateTargetContract(target)
const targetId = this.addHandler(HandlerRole.TARGET, type, target)
this.store.dispatch(addTarget(targetId))
return targetId
}
public containsHandler(handler: DragSource | DropTarget): boolean {
return (
mapContainsValue(this.dragSources, handler) ||
mapContainsValue(this.dropTargets, handler)
)
}
public getSource(sourceId: string, includePinned = false): DragSource {
invariant(this.isSourceId(sourceId), 'Expected a valid source ID.')
const isPinned = includePinned && sourceId === this.pinnedSourceId
const source = isPinned ? this.pinnedSource : this.dragSources.get(sourceId)
return source
}
public getTarget(targetId: string): DropTarget {
invariant(this.isTargetId(targetId), 'Expected a valid target ID.')
return this.dropTargets.get(targetId) as DropTarget
}
public getSourceType(sourceId: string): Identifier {
invariant(this.isSourceId(sourceId), 'Expected a valid source ID.')
return this.types.get(sourceId) as Identifier
}
public getTargetType(targetId: string): Identifier | Identifier[] {
invariant(this.isTargetId(targetId), 'Expected a valid target ID.')
return this.types.get(targetId) as Identifier | Identifier[]
}
public isSourceId(handlerId: string): boolean {
const role = parseRoleFromHandlerId(handlerId)
return role === HandlerRole.SOURCE
}
public isTargetId(handlerId: string): boolean {
const role = parseRoleFromHandlerId(handlerId)
return role === HandlerRole.TARGET
}
public removeSource(sourceId: string): void {
invariant(this.getSource(sourceId), 'Expected an existing source.')
this.store.dispatch(removeSource(sourceId))
asap(() => {
this.dragSources.delete(sourceId)
this.types.delete(sourceId)
})
}
public removeTarget(targetId: string): void {
invariant(this.getTarget(targetId), 'Expected an existing target.')
this.store.dispatch(removeTarget(targetId))
this.dropTargets.delete(targetId)
this.types.delete(targetId)
}
public pinSource(sourceId: string): void {
const source = this.getSource(sourceId)
invariant(source, 'Expected an existing source.')
this.pinnedSourceId = sourceId
this.pinnedSource = source
}
public unpinSource(): void {
invariant(this.pinnedSource, 'No source is pinned at the time.')
this.pinnedSourceId = null
this.pinnedSource = null
}
private addHandler(
role: HandlerRole,
type: SourceType | TargetType,
handler: DragSource | DropTarget,
): string {
const id = getNextHandlerId(role)
this.types.set(id, type)
if (role === HandlerRole.SOURCE) {
this.dragSources.set(id, handler as DragSource)
} else if (role === HandlerRole.TARGET) {
this.dropTargets.set(id, handler as DropTarget)
}
return id
}
}

View File

@@ -1,50 +0,0 @@
import { invariant } from '@react-dnd/invariant'
import type { DragSource, DropTarget, Identifier } from './interfaces.js'
export function validateSourceContract(source: DragSource): void {
invariant(
typeof source.canDrag === 'function',
'Expected canDrag to be a function.',
)
invariant(
typeof source.beginDrag === 'function',
'Expected beginDrag to be a function.',
)
invariant(
typeof source.endDrag === 'function',
'Expected endDrag to be a function.',
)
}
export function validateTargetContract(target: DropTarget): void {
invariant(
typeof target.canDrop === 'function',
'Expected canDrop to be a function.',
)
invariant(
typeof target.hover === 'function',
'Expected hover to be a function.',
)
invariant(
typeof target.drop === 'function',
'Expected beginDrag to be a function.',
)
}
export function validateType(
type: Identifier | Identifier[],
allowArray?: boolean,
): void {
if (allowArray && Array.isArray(type)) {
type.forEach((t) => validateType(t, false))
return
}
invariant(
typeof type === 'string' || typeof type === 'symbol',
allowArray
? 'Type can only be a string, a symbol, or an array of either.'
: 'Type can only be a string or a symbol.',
)
}

View File

@@ -1,40 +0,0 @@
import type { Store } from 'redux'
import { createStore } from 'redux'
import { DragDropManagerImpl } from './classes/DragDropManagerImpl.js'
import { DragDropMonitorImpl } from './classes/DragDropMonitorImpl.js'
import { HandlerRegistryImpl } from './classes/HandlerRegistryImpl.js'
import type { BackendFactory, DragDropManager } from './interfaces.js'
import type { State } from './reducers/index.js'
import { reduce } from './reducers/index.js'
export function createDragDropManager(
backendFactory: BackendFactory,
globalContext: unknown = undefined,
backendOptions: unknown = {},
debugMode = false,
): DragDropManager {
const store = makeStoreInstance(debugMode)
const monitor = new DragDropMonitorImpl(store, new HandlerRegistryImpl(store))
const manager = new DragDropManagerImpl(store, monitor)
const backend = backendFactory(manager, globalContext, backendOptions)
manager.receiveBackend(backend)
return manager
}
function makeStoreInstance(debugMode: boolean): Store<State> {
// TODO: if we ever make a react-native version of this,
// we'll need to consider how to pull off dev-tooling
const reduxDevTools =
typeof window !== 'undefined' &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__
return createStore(
reduce,
debugMode &&
reduxDevTools &&
reduxDevTools({
name: 'dnd-core',
instanceId: 'dnd-core',
}),
)
}

View File

@@ -1,2 +0,0 @@
export * from './createDragDropManager.js'
export * from './interfaces.js'

View File

@@ -1,209 +0,0 @@
export type Identifier = string | symbol
export type SourceType = Identifier
export type TargetType = Identifier | Identifier[]
export type Unsubscribe = () => void
export type Listener = () => void
export interface XYCoord {
x: number
y: number
}
export enum HandlerRole {
SOURCE = 'SOURCE',
TARGET = 'TARGET',
}
export interface Backend {
setup(): void
teardown(): void
connectDragSource(sourceId: any, node?: any, options?: any): Unsubscribe
connectDragPreview(sourceId: any, node?: any, options?: any): Unsubscribe
connectDropTarget(targetId: any, node?: any, options?: any): Unsubscribe
profile(): Record<string, number>
}
export interface DragDropMonitor {
subscribeToStateChange(
listener: Listener,
options?: {
handlerIds?: Identifier[]
},
): Unsubscribe
subscribeToOffsetChange(listener: Listener): Unsubscribe
canDragSource(sourceId: Identifier | undefined): boolean
canDropOnTarget(targetId: Identifier | undefined): boolean
/**
* Returns true if a drag operation is in progress, and either the owner initiated the drag, or its isDragging()
* is defined and returns true.
*/
isDragging(): boolean
isDraggingSource(sourceId: Identifier | undefined): boolean
isOverTarget(
targetId: Identifier | undefined,
options?: {
shallow?: boolean
},
): boolean
/**
* Returns a string or a symbol identifying the type of the current dragged item. Returns null if no item is being dragged.
*/
getItemType(): Identifier | null
/**
* Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object
* from its beginDrag() method. Returns null if no item is being dragged.
*/
getItem(): any
getSourceId(): Identifier | null
getTargetIds(): Identifier[]
/**
* Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an
* object from their drop() methods. When a chain of drop() is dispatched for the nested targets, bottom up, any parent that
* explicitly returns its own result from drop() overrides the child drop result previously set by the child. Returns null if
* called outside endDrag().
*/
getDropResult(): any
/**
* Returns true if some drop target has handled the drop event, false otherwise. Even if a target did not return a drop result,
* didDrop() returns true. Use it inside endDrag() to test whether any drop target has handled the drop. Returns false if called
* outside endDrag().
*/
didDrop(): boolean
isSourcePublic(): boolean | null
/**
* Returns the { x, y } client offset of the pointer at the time when the current drag operation has started.
* Returns null if no item is being dragged.
*/
getInitialClientOffset(): XYCoord | null
/**
* Returns the { x, y } client offset of the drag source component's root DOM node at the time when the current drag
* operation has started. Returns null if no item is being dragged.
*/
getInitialSourceClientOffset(): XYCoord | null
/**
* Returns the last recorded { x, y } client offset of the pointer while a drag operation is in progress.
* Returns null if no item is being dragged.
*/
getClientOffset(): XYCoord | null
/**
* Returns the projected { x, y } client offset of the drag source component's root DOM node, based on its position at the time
* when the current drag operation has started, and the movement difference. Returns null if no item is being dragged.
*/
getSourceClientOffset(): XYCoord | null
/**
* Returns the { x, y } difference between the last recorded client offset of the pointer and the client offset when the current
* drag operation has started. Returns null if no item is being dragged.
*/
getDifferenceFromInitialOffset(): XYCoord | null
}
export interface HandlerRegistry {
addSource(type: SourceType, source: DragSource): Identifier
addTarget(type: TargetType, target: DropTarget): Identifier
containsHandler(handler: DragSource | DropTarget): boolean
getSource(sourceId: Identifier, includePinned?: boolean): DragSource
getSourceType(sourceId: Identifier): SourceType
getTargetType(targetId: Identifier): TargetType
getTarget(targetId: Identifier): DropTarget
isSourceId(handlerId: Identifier): boolean
isTargetId(handlerId: Identifier): boolean
removeSource(sourceId: Identifier): void
removeTarget(targetId: Identifier): void
pinSource(sourceId: Identifier): void
unpinSource(): void
}
export interface Action<Payload> {
type: Identifier
payload: Payload
}
export interface SentinelAction {
type: Identifier
}
export type ActionCreator<Payload> = (args: any[]) => Action<Payload>
export interface BeginDragOptions {
publishSource?: boolean
clientOffset?: XYCoord
getSourceClientOffset?: (sourceId: Identifier | undefined) => XYCoord
}
export interface InitCoordsPayload {
clientOffset: XYCoord | null
sourceClientOffset: XYCoord | null
}
export interface BeginDragPayload {
itemType: Identifier
item: any
sourceId: Identifier
clientOffset: XYCoord | null
sourceClientOffset: XYCoord | null
isSourcePublic: boolean
}
export interface HoverPayload {
targetIds: Identifier[]
clientOffset: XYCoord | null
}
export interface HoverOptions {
clientOffset?: XYCoord
}
export interface DropPayload {
dropResult: any
}
export interface TargetIdPayload {
targetId: Identifier
}
export interface SourceIdPayload {
sourceId: Identifier
}
export interface DragDropActions {
beginDrag(
sourceIds?: Identifier[],
options?: any,
): Action<BeginDragPayload> | undefined
publishDragSource(): SentinelAction | undefined
hover(targetIds: Identifier[], options?: any): Action<HoverPayload>
drop(options?: any): void
endDrag(): SentinelAction
}
export interface DragDropManager {
getMonitor(): DragDropMonitor
getBackend(): Backend
getRegistry(): HandlerRegistry
getActions(): DragDropActions
dispatch(action: any): void
}
export type BackendFactory = (
manager: DragDropManager,
globalContext?: any,
configuration?: any,
) => Backend
export interface DragSource {
beginDrag(monitor: DragDropMonitor, targetId: Identifier): void
endDrag(monitor: DragDropMonitor, targetId: Identifier): void
canDrag(monitor: DragDropMonitor, targetId: Identifier): boolean
isDragging(monitor: DragDropMonitor, targetId: Identifier): boolean
}
export interface DropTarget {
canDrop(monitor: DragDropMonitor, targetId: Identifier): boolean
hover(monitor: DragDropMonitor, targetId: Identifier): void
drop(monitor: DragDropMonitor, targetId: Identifier): any
}

View File

@@ -1,70 +0,0 @@
import {
BEGIN_DRAG,
DROP,
END_DRAG,
HOVER,
PUBLISH_DRAG_SOURCE,
} from '../actions/dragDrop/index.js'
import {
ADD_SOURCE,
ADD_TARGET,
REMOVE_SOURCE,
REMOVE_TARGET,
} from '../actions/registry.js'
import type { Action } from '../interfaces.js'
import { ALL, NONE } from '../utils/dirtiness.js'
import { areArraysEqual } from '../utils/equality.js'
import { xor } from '../utils/js_utils.js'
export type State = string[]
export interface DirtyHandlerIdPayload {
targetIds: string[]
prevTargetIds: string[]
}
export function reduce(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_state: State = NONE,
action: Action<DirtyHandlerIdPayload>,
): State {
switch (action.type) {
case HOVER:
break
case ADD_SOURCE:
case ADD_TARGET:
case REMOVE_TARGET:
case REMOVE_SOURCE:
return NONE
case BEGIN_DRAG:
case PUBLISH_DRAG_SOURCE:
case END_DRAG:
case DROP:
default:
return ALL
}
const { targetIds = [], prevTargetIds = [] } = action.payload
const result = xor(targetIds, prevTargetIds)
const didChange =
result.length > 0 || !areArraysEqual(targetIds, prevTargetIds)
if (!didChange) {
return NONE
}
// Check the target ids at the innermost position. If they are valid, add them
// to the result
const prevInnermostTargetId = prevTargetIds[prevTargetIds.length - 1]
const innermostTargetId = targetIds[targetIds.length - 1]
if (prevInnermostTargetId !== innermostTargetId) {
if (prevInnermostTargetId) {
result.push(prevInnermostTargetId)
}
if (innermostTargetId) {
result.push(innermostTargetId)
}
}
return result
}

View File

@@ -1,53 +0,0 @@
import {
BEGIN_DRAG,
DROP,
END_DRAG,
HOVER,
INIT_COORDS,
} from '../actions/dragDrop/index.js'
import type { Action, XYCoord } from '../interfaces.js'
import { areCoordsEqual } from '../utils/equality.js'
export interface State {
initialSourceClientOffset: XYCoord | null
initialClientOffset: XYCoord | null
clientOffset: XYCoord | null
}
const initialState: State = {
initialSourceClientOffset: null,
initialClientOffset: null,
clientOffset: null,
}
export function reduce(
state: State = initialState,
action: Action<{
sourceClientOffset: XYCoord
clientOffset: XYCoord
}>,
): State {
const { payload } = action
switch (action.type) {
case INIT_COORDS:
case BEGIN_DRAG:
return {
initialSourceClientOffset: payload.sourceClientOffset,
initialClientOffset: payload.clientOffset,
clientOffset: payload.clientOffset,
}
case HOVER:
if (areCoordsEqual(state.clientOffset, payload.clientOffset)) {
return state
}
return {
...state,
clientOffset: payload.clientOffset,
}
case END_DRAG:
case DROP:
return initialState
default:
return state
}
}

View File

@@ -1,95 +0,0 @@
import {
BEGIN_DRAG,
DROP,
END_DRAG,
HOVER,
PUBLISH_DRAG_SOURCE,
} from '../actions/dragDrop/index.js'
import { REMOVE_TARGET } from '../actions/registry.js'
import type { Action, Identifier } from '../interfaces.js'
import { without } from '../utils/js_utils.js'
export interface State {
itemType: Identifier | Identifier[] | null
item: any
sourceId: string | null
targetIds: string[]
dropResult: any
didDrop: boolean
isSourcePublic: boolean | null
}
const initialState: State = {
itemType: null,
item: null,
sourceId: null,
targetIds: [],
dropResult: null,
didDrop: false,
isSourcePublic: null,
}
export function reduce(
state: State = initialState,
action: Action<{
itemType: Identifier | Identifier[]
item: any
sourceId: string
targetId: string
targetIds: string[]
isSourcePublic: boolean
dropResult: any
}>,
): State {
const { payload } = action
switch (action.type) {
case BEGIN_DRAG:
return {
...state,
itemType: payload.itemType,
item: payload.item,
sourceId: payload.sourceId,
isSourcePublic: payload.isSourcePublic,
dropResult: null,
didDrop: false,
}
case PUBLISH_DRAG_SOURCE:
return {
...state,
isSourcePublic: true,
}
case HOVER:
return {
...state,
targetIds: payload.targetIds,
}
case REMOVE_TARGET:
if (state.targetIds.indexOf(payload.targetId) === -1) {
return state
}
return {
...state,
targetIds: without(state.targetIds, payload.targetId),
}
case DROP:
return {
...state,
dropResult: payload.dropResult,
didDrop: true,
targetIds: [],
}
case END_DRAG:
return {
...state,
itemType: null,
item: null,
sourceId: null,
dropResult: null,
didDrop: false,
isSourcePublic: null,
targetIds: [],
}
default:
return state
}
}

View File

@@ -1,36 +0,0 @@
import type { Action } from '../interfaces.js'
import { get } from '../utils/js_utils.js'
import type { State as DirtyHandlerIdsState } from './dirtyHandlerIds.js'
import { reduce as dirtyHandlerIds } from './dirtyHandlerIds.js'
import type { State as DragOffsetState } from './dragOffset.js'
import { reduce as dragOffset } from './dragOffset.js'
import type { State as DragOperationState } from './dragOperation.js'
import { reduce as dragOperation } from './dragOperation.js'
import type { State as RefCountState } from './refCount.js'
import { reduce as refCount } from './refCount.js'
import type { State as StateIdState } from './stateId.js'
import { reduce as stateId } from './stateId.js'
export interface State {
dirtyHandlerIds: DirtyHandlerIdsState
dragOffset: DragOffsetState
refCount: RefCountState
dragOperation: DragOperationState
stateId: StateIdState
}
export function reduce(state: State = {} as State, action: Action<any>): State {
return {
dirtyHandlerIds: dirtyHandlerIds(state.dirtyHandlerIds, {
type: action.type,
payload: {
...action.payload,
prevTargetIds: get<string[]>(state, 'dragOperation.targetIds', []),
},
}),
dragOffset: dragOffset(state.dragOffset, action),
refCount: refCount(state.refCount, action),
dragOperation: dragOperation(state.dragOperation, action),
stateId: stateId(state.stateId),
}
}

View File

@@ -1,22 +0,0 @@
import {
ADD_SOURCE,
ADD_TARGET,
REMOVE_SOURCE,
REMOVE_TARGET,
} from '../actions/registry.js'
import type { Action } from '../interfaces.js'
export type State = number
export function reduce(state: State = 0, action: Action<any>): State {
switch (action.type) {
case ADD_SOURCE:
case ADD_TARGET:
return state + 1
case REMOVE_SOURCE:
case REMOVE_TARGET:
return state - 1
default:
return state
}
}

View File

@@ -1,5 +0,0 @@
export type State = number
export function reduce(state: State = 0): State {
return state + 1
}

View File

@@ -1,58 +0,0 @@
import type { XYCoord } from '../interfaces.js'
import type { State } from '../reducers/dragOffset.js'
/**
* Coordinate addition
* @param a The first coordinate
* @param b The second coordinate
*/
export function add(a: XYCoord, b: XYCoord): XYCoord {
return {
x: a.x + b.x,
y: a.y + b.y,
}
}
/**
* Coordinate subtraction
* @param a The first coordinate
* @param b The second coordinate
*/
export function subtract(a: XYCoord, b: XYCoord): XYCoord {
return {
x: a.x - b.x,
y: a.y - b.y,
}
}
/**
* Returns the cartesian distance of the drag source component's position, based on its position
* at the time when the current drag operation has started, and the movement difference.
*
* Returns null if no item is being dragged.
*
* @param state The offset state to compute from
*/
export function getSourceClientOffset(state: State): XYCoord | null {
const { clientOffset, initialClientOffset, initialSourceClientOffset } = state
if (!clientOffset || !initialClientOffset || !initialSourceClientOffset) {
return null
}
return subtract(
add(clientOffset, initialSourceClientOffset),
initialClientOffset,
)
}
/**
* Determines the x,y offset between the client offset and the initial client offset
*
* @param state The offset state to compute from
*/
export function getDifferenceFromInitialOffset(state: State): XYCoord | null {
const { clientOffset, initialClientOffset } = state
if (!clientOffset || !initialClientOffset) {
return null
}
return subtract(clientOffset, initialClientOffset)
}

View File

@@ -1,29 +0,0 @@
import { intersection } from './js_utils.js'
export const NONE: string[] = []
export const ALL: string[] = []
// Add these flags for debug
;(NONE as any).__IS_NONE__ = true
;(ALL as any).__IS_ALL__ = true
/**
* Determines if the given handler IDs are dirty or not.
*
* @param dirtyIds The set of dirty handler ids
* @param handlerIds The set of handler ids to check
*/
export function areDirty(
dirtyIds: string[],
handlerIds: string[] | undefined,
): boolean {
if (dirtyIds === NONE) {
return false
}
if (dirtyIds === ALL || typeof handlerIds === 'undefined') {
return true
}
const commonIds = intersection(handlerIds, dirtyIds)
return commonIds.length > 0
}

View File

@@ -1,43 +0,0 @@
import type { XYCoord } from '../interfaces.js'
export type EqualityCheck<T> = (a: T, b: T) => boolean
export const strictEquality = <T>(a: T, b: T): boolean => a === b
/**
* Determine if two cartesian coordinate offsets are equal
* @param offsetA
* @param offsetB
*/
export function areCoordsEqual(
offsetA: XYCoord | null | undefined,
offsetB: XYCoord | null | undefined,
): boolean {
if (!offsetA && !offsetB) {
return true
} else if (!offsetA || !offsetB) {
return false
} else {
return offsetA.x === offsetB.x && offsetA.y === offsetB.y
}
}
/**
* Determines if two arrays of items are equal
* @param a The first array of items
* @param b The second array of items
*/
export function areArraysEqual<T>(
a: T[],
b: T[],
isEqual: EqualityCheck<T> = strictEquality,
): boolean {
if (a.length !== b.length) {
return false
}
for (let i = 0; i < a.length; ++i) {
if (!isEqual(a[i] as T, b[i] as T)) {
return false
}
}
return true
}

View File

@@ -1,5 +0,0 @@
let nextUniqueId = 0
export function getNextUniqueId(): number {
return nextUniqueId++
}

View File

@@ -1,67 +0,0 @@
// cheap lodash replacements
/**
* drop-in replacement for _.get
* @param obj
* @param path
* @param defaultValue
*/
export function get<T>(obj: any, path: string, defaultValue: T): T {
return path
.split('.')
.reduce((a, c) => (a && a[c] ? a[c] : defaultValue || null), obj) as T
}
/**
* drop-in replacement for _.without
*/
export function without<T>(items: T[], item: T): T[] {
return items.filter((i) => i !== item)
}
/**
* drop-in replacement for _.isString
* @param input
*/
export function isString(input: any): boolean {
return typeof input === 'string'
}
/**
* drop-in replacement for _.isString
* @param input
*/
export function isObject(input: any): boolean {
return typeof input === 'object'
}
/**
* replacement for _.xor
* @param itemsA
* @param itemsB
*/
export function xor<T extends string | number>(itemsA: T[], itemsB: T[]): T[] {
const map = new Map<T, number>()
const insertItem = (item: T) => {
map.set(item, map.has(item) ? (map.get(item) as number) + 1 : 1)
}
itemsA.forEach(insertItem)
itemsB.forEach(insertItem)
const result: T[] = []
map.forEach((count, key) => {
if (count === 1) {
result.push(key)
}
})
return result
}
/**
* replacement for _.intersection
* @param itemsA
* @param itemsB
*/
export function intersection<T>(itemsA: T[], itemsB: T[]): T[] {
return itemsA.filter((t) => itemsB.indexOf(t) > -1)
}

View File

@@ -1,13 +0,0 @@
import type { Identifier } from '../interfaces.js'
export function matchesType(
targetType: Identifier | Identifier[] | null,
draggedItemType: Identifier | null,
): boolean {
if (draggedItemType === null) {
return targetType === null
}
return Array.isArray(targetType)
? (targetType as Identifier[]).some((t) => t === draggedItemType)
: targetType === draggedItemType
}