@bquery/bquery 1.7.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +760 -716
- package/dist/{a11y-C5QOVvRn.js → a11y-DVBCy09c.js} +3 -3
- package/dist/a11y-DVBCy09c.js.map +1 -0
- package/dist/a11y.es.mjs +1 -1
- package/dist/component/library.d.ts.map +1 -1
- package/dist/{component-CuuTijA6.js → component-L3-JfOFz.js} +5 -5
- package/dist/component-L3-JfOFz.js.map +1 -0
- package/dist/component.es.mjs +1 -1
- package/dist/{config-BW35FKuA.js → config-DhT9auRm.js} +1 -1
- package/dist/{config-BW35FKuA.js.map → config-DhT9auRm.js.map} +1 -1
- package/dist/{constraints-3lV9yyBw.js → constraints-D5RHQLmP.js} +1 -1
- package/dist/constraints-D5RHQLmP.js.map +1 -0
- package/dist/core/collection.d.ts +86 -0
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/element.d.ts +28 -0
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/shared.d.ts +6 -0
- package/dist/core/shared.d.ts.map +1 -1
- package/dist/core-DdtZHzsS.js +168 -0
- package/dist/core-DdtZHzsS.js.map +1 -0
- package/dist/{core-Cjl7GUu8.js → core-EMYSLzaT.js} +289 -259
- package/dist/core-EMYSLzaT.js.map +1 -0
- package/dist/core.es.mjs +48 -47
- package/dist/{custom-directives-7wAShnnd.js → custom-directives-Dr4C5lVV.js} +1 -1
- package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
- package/dist/{devtools-D2fQLhDN.js → devtools-BhB2iDPT.js} +2 -2
- package/dist/devtools-BhB2iDPT.js.map +1 -0
- package/dist/devtools.es.mjs +1 -1
- package/dist/{dnd-B8EgyzaI.js → dnd-NwZBYh4l.js} +1 -1
- package/dist/dnd-NwZBYh4l.js.map +1 -0
- package/dist/dnd.es.mjs +1 -1
- package/dist/{env-NeVmr4Gf.js → env-CTdvLaH2.js} +1 -1
- package/dist/env-CTdvLaH2.js.map +1 -0
- package/dist/forms/create-form.d.ts.map +1 -1
- package/dist/forms/index.d.ts +3 -2
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/types.d.ts +46 -0
- package/dist/forms/types.d.ts.map +1 -1
- package/dist/forms/use-field.d.ts +34 -0
- package/dist/forms/use-field.d.ts.map +1 -0
- package/dist/forms/validators.d.ts +25 -0
- package/dist/forms/validators.d.ts.map +1 -1
- package/dist/forms-UcRHsYxC.js +227 -0
- package/dist/forms-UcRHsYxC.js.map +1 -0
- package/dist/forms.es.mjs +14 -12
- package/dist/full.d.ts +17 -26
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +206 -181
- package/dist/full.iife.js +33 -33
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +33 -33
- package/dist/full.umd.js.map +1 -1
- package/dist/function-Cybd57JV.js +33 -0
- package/dist/function-Cybd57JV.js.map +1 -0
- package/dist/{i18n-BnnhTFOS.js → i18n-kuF6Ekj6.js} +3 -3
- package/dist/i18n-kuF6Ekj6.js.map +1 -0
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.es.mjs +251 -228
- package/dist/media/breakpoints.d.ts.map +1 -1
- package/dist/media/types.d.ts +2 -2
- package/dist/media/types.d.ts.map +1 -1
- package/dist/{media-Di2Ta22s.js → media-i-fB5WxI.js} +3 -3
- package/dist/media-i-fB5WxI.js.map +1 -0
- package/dist/media.es.mjs +1 -1
- package/dist/{motion-qPj_TYGv.js → motion-BJsAuULb.js} +2 -2
- package/dist/motion-BJsAuULb.js.map +1 -0
- package/dist/motion.es.mjs +1 -1
- package/dist/{mount-SM07RUa6.js → mount-B4Y8bk8Z.js} +5 -5
- package/dist/mount-B4Y8bk8Z.js.map +1 -0
- package/dist/{platform-CPbCprb6.js → platform-Dw2gE3zI.js} +3 -3
- package/dist/{platform-CPbCprb6.js.map → platform-Dw2gE3zI.js.map} +1 -1
- package/dist/platform.es.mjs +2 -2
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/{plugin-cPoOHFLY.js → plugin-C2WuC8SF.js} +20 -18
- package/dist/plugin-C2WuC8SF.js.map +1 -0
- package/dist/plugin.es.mjs +1 -1
- package/dist/reactive/async-data.d.ts +28 -3
- package/dist/reactive/async-data.d.ts.map +1 -1
- package/dist/reactive/computed.d.ts +3 -0
- package/dist/reactive/computed.d.ts.map +1 -1
- package/dist/reactive/effect.d.ts +3 -0
- package/dist/reactive/effect.d.ts.map +1 -1
- package/dist/reactive/http.d.ts +194 -0
- package/dist/reactive/http.d.ts.map +1 -0
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/pagination.d.ts +126 -0
- package/dist/reactive/pagination.d.ts.map +1 -0
- package/dist/reactive/polling.d.ts +55 -0
- package/dist/reactive/polling.d.ts.map +1 -0
- package/dist/reactive/readonly.d.ts +20 -1
- package/dist/reactive/readonly.d.ts.map +1 -1
- package/dist/reactive/rest.d.ts +293 -0
- package/dist/reactive/rest.d.ts.map +1 -0
- package/dist/reactive/scope.d.ts +140 -0
- package/dist/reactive/scope.d.ts.map +1 -0
- package/dist/reactive/signal.d.ts +16 -2
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/to-value.d.ts +57 -0
- package/dist/reactive/to-value.d.ts.map +1 -0
- package/dist/reactive/websocket.d.ts +285 -0
- package/dist/reactive/websocket.d.ts.map +1 -0
- package/dist/reactive-DwkhUJfP.js +1148 -0
- package/dist/reactive-DwkhUJfP.js.map +1 -0
- package/dist/reactive.es.mjs +38 -19
- package/dist/{registry-CWf368tT.js → registry-B08iilIh.js} +1 -1
- package/dist/{registry-CWf368tT.js.map → registry-B08iilIh.js.map} +1 -1
- package/dist/router/constraints.d.ts.map +1 -1
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/state.d.ts +25 -2
- package/dist/router/state.d.ts.map +1 -1
- package/dist/router-CQikC9Ed.js +492 -0
- package/dist/router-CQikC9Ed.js.map +1 -0
- package/dist/router.es.mjs +9 -8
- package/dist/ssr/hydrate.d.ts.map +1 -1
- package/dist/{ssr-B2qd_WBB.js → ssr-_dAcGdzu.js} +4 -4
- package/dist/ssr-_dAcGdzu.js.map +1 -0
- package/dist/ssr.es.mjs +1 -1
- package/dist/store/persisted.d.ts.map +1 -1
- package/dist/{store-DWpyH6p5.js → store-Cb3gPRve.js} +7 -7
- package/dist/store-Cb3gPRve.js.map +1 -0
- package/dist/store.es.mjs +2 -2
- package/dist/storybook.es.mjs.map +1 -1
- package/dist/{testing-CsqjNUyy.js → testing-C5Sjfsna.js} +8 -8
- package/dist/testing-C5Sjfsna.js.map +1 -0
- package/dist/testing.es.mjs +1 -1
- package/dist/{type-guards-Do9DWgNp.js → type-guards-BMX2c0LP.js} +1 -1
- package/dist/{type-guards-Do9DWgNp.js.map → type-guards-BMX2c0LP.js.map} +1 -1
- package/dist/untrack-D0fnO5k2.js +36 -0
- package/dist/untrack-D0fnO5k2.js.map +1 -0
- package/dist/view/custom-directives.d.ts.map +1 -1
- package/dist/view.es.mjs +4 -4
- package/package.json +178 -177
- package/src/a11y/announce.ts +131 -131
- package/src/a11y/audit.ts +314 -314
- package/src/a11y/index.ts +68 -68
- package/src/a11y/media-preferences.ts +255 -255
- package/src/a11y/roving-tab-index.ts +164 -164
- package/src/a11y/skip-link.ts +255 -255
- package/src/a11y/trap-focus.ts +184 -184
- package/src/a11y/types.ts +183 -183
- package/src/component/component.ts +599 -599
- package/src/component/html.ts +153 -153
- package/src/component/index.ts +52 -52
- package/src/component/library.ts +540 -542
- package/src/component/scope.ts +212 -212
- package/src/component/types.ts +310 -310
- package/src/core/collection.ts +876 -707
- package/src/core/element.ts +1015 -981
- package/src/core/env.ts +60 -60
- package/src/core/index.ts +49 -49
- package/src/core/shared.ts +77 -62
- package/src/core/utils/index.ts +148 -148
- package/src/devtools/devtools.ts +410 -410
- package/src/devtools/index.ts +48 -48
- package/src/devtools/types.ts +104 -104
- package/src/dnd/draggable.ts +296 -296
- package/src/dnd/droppable.ts +228 -228
- package/src/dnd/index.ts +62 -62
- package/src/dnd/sortable.ts +307 -307
- package/src/dnd/types.ts +293 -293
- package/src/forms/create-form.ts +320 -278
- package/src/forms/index.ts +70 -65
- package/src/forms/types.ts +203 -154
- package/src/forms/use-field.ts +231 -0
- package/src/forms/validators.ts +294 -265
- package/src/full.ts +554 -480
- package/src/i18n/formatting.ts +67 -67
- package/src/i18n/i18n.ts +200 -200
- package/src/i18n/index.ts +67 -67
- package/src/i18n/translate.ts +182 -182
- package/src/i18n/types.ts +171 -171
- package/src/index.ts +108 -108
- package/src/media/battery.ts +116 -116
- package/src/media/breakpoints.ts +129 -131
- package/src/media/clipboard.ts +80 -80
- package/src/media/device-sensors.ts +158 -158
- package/src/media/geolocation.ts +119 -119
- package/src/media/index.ts +76 -76
- package/src/media/media-query.ts +92 -92
- package/src/media/network.ts +115 -115
- package/src/media/types.ts +177 -177
- package/src/media/viewport.ts +84 -84
- package/src/motion/index.ts +57 -57
- package/src/motion/morph.ts +151 -151
- package/src/motion/parallax.ts +120 -120
- package/src/motion/reduced-motion.ts +66 -66
- package/src/motion/types.ts +271 -271
- package/src/motion/typewriter.ts +164 -164
- package/src/plugin/index.ts +37 -37
- package/src/plugin/registry.ts +284 -269
- package/src/plugin/types.ts +137 -137
- package/src/reactive/async-data.ts +250 -29
- package/src/reactive/computed.ts +144 -130
- package/src/reactive/effect.ts +29 -6
- package/src/reactive/http.ts +790 -0
- package/src/reactive/index.ts +60 -0
- package/src/reactive/pagination.ts +317 -0
- package/src/reactive/polling.ts +179 -0
- package/src/reactive/readonly.ts +52 -8
- package/src/reactive/rest.ts +859 -0
- package/src/reactive/scope.ts +276 -0
- package/src/reactive/signal.ts +61 -1
- package/src/reactive/to-value.ts +71 -0
- package/src/reactive/websocket.ts +849 -0
- package/src/router/bq-link.ts +279 -279
- package/src/router/constraints.ts +204 -201
- package/src/router/index.ts +49 -49
- package/src/router/match.ts +312 -312
- package/src/router/path-pattern.ts +52 -52
- package/src/router/query.ts +38 -38
- package/src/router/router.ts +421 -402
- package/src/router/state.ts +51 -3
- package/src/router/types.ts +139 -139
- package/src/router/use-route.ts +68 -68
- package/src/router/utils.ts +157 -157
- package/src/security/index.ts +12 -12
- package/src/ssr/hydrate.ts +84 -82
- package/src/ssr/index.ts +70 -70
- package/src/ssr/render.ts +508 -508
- package/src/ssr/serialize.ts +296 -296
- package/src/ssr/types.ts +81 -81
- package/src/store/create-store.ts +467 -467
- package/src/store/index.ts +27 -27
- package/src/store/persisted.ts +245 -249
- package/src/store/types.ts +247 -247
- package/src/store/utils.ts +135 -135
- package/src/storybook/index.ts +480 -480
- package/src/testing/index.ts +42 -42
- package/src/testing/testing.ts +593 -593
- package/src/testing/types.ts +170 -170
- package/src/view/custom-directives.ts +28 -30
- package/src/view/evaluate.ts +292 -292
- package/src/view/process.ts +108 -108
- package/dist/a11y-C5QOVvRn.js.map +0 -1
- package/dist/component-CuuTijA6.js.map +0 -1
- package/dist/constraints-3lV9yyBw.js.map +0 -1
- package/dist/core-Cjl7GUu8.js.map +0 -1
- package/dist/core-DnlyjbF2.js +0 -112
- package/dist/core-DnlyjbF2.js.map +0 -1
- package/dist/custom-directives-7wAShnnd.js.map +0 -1
- package/dist/devtools-D2fQLhDN.js.map +0 -1
- package/dist/dnd-B8EgyzaI.js.map +0 -1
- package/dist/env-NeVmr4Gf.js.map +0 -1
- package/dist/forms-C3yovgH9.js +0 -141
- package/dist/forms-C3yovgH9.js.map +0 -1
- package/dist/i18n-BnnhTFOS.js.map +0 -1
- package/dist/media-Di2Ta22s.js.map +0 -1
- package/dist/motion-qPj_TYGv.js.map +0 -1
- package/dist/mount-SM07RUa6.js.map +0 -1
- package/dist/plugin-cPoOHFLY.js.map +0 -1
- package/dist/reactive-Cfv0RK6x.js +0 -233
- package/dist/reactive-Cfv0RK6x.js.map +0 -1
- package/dist/router-BrthaP_z.js +0 -473
- package/dist/router-BrthaP_z.js.map +0 -1
- package/dist/ssr-B2qd_WBB.js.map +0 -1
- package/dist/store-DWpyH6p5.js.map +0 -1
- package/dist/testing-CsqjNUyy.js.map +0 -1
- package/dist/untrack-DJVQQ2WM.js +0 -33
- package/dist/untrack-DJVQQ2WM.js.map +0 -1
package/src/dnd/droppable.ts
CHANGED
|
@@ -1,228 +1,228 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Define drop zones for draggable elements.
|
|
3
|
-
*
|
|
4
|
-
* Drop zones detect when draggable elements enter, move over,
|
|
5
|
-
* leave, or are dropped onto them using pointer event hit-testing.
|
|
6
|
-
*
|
|
7
|
-
* @module bquery/dnd
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { getActiveDrag } from './draggable';
|
|
11
|
-
import type { DropEventData, DroppableHandle, DroppableOptions } from './types';
|
|
12
|
-
|
|
13
|
-
type DroppableListener = {
|
|
14
|
-
handlePointerMove: (event: PointerEvent) => void;
|
|
15
|
-
handlePointerUp: (event: PointerEvent) => void;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const passivePointerMoveListenerOptions = { passive: true } as const;
|
|
19
|
-
|
|
20
|
-
const droppableListeners = new Set<DroppableListener>();
|
|
21
|
-
let queuedPointerMove: PointerEvent | null = null;
|
|
22
|
-
let pointerMoveFrame: number | null = null;
|
|
23
|
-
|
|
24
|
-
const getDroppableListenersSnapshot = (): DroppableListener[] => Array.from(droppableListeners);
|
|
25
|
-
|
|
26
|
-
const hasDroppableEnvironment = (): boolean => {
|
|
27
|
-
return (
|
|
28
|
-
typeof document !== 'undefined' &&
|
|
29
|
-
typeof document.addEventListener === 'function' &&
|
|
30
|
-
typeof document.removeEventListener === 'function' &&
|
|
31
|
-
typeof requestAnimationFrame === 'function' &&
|
|
32
|
-
typeof cancelAnimationFrame === 'function'
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const dispatchPointerMove = (event: PointerEvent): void => {
|
|
37
|
-
for (const listener of getDroppableListenersSnapshot()) {
|
|
38
|
-
listener.handlePointerMove(event);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const flushPointerMove = (): void => {
|
|
43
|
-
pointerMoveFrame = null;
|
|
44
|
-
const event = queuedPointerMove;
|
|
45
|
-
queuedPointerMove = null;
|
|
46
|
-
if (!event) return;
|
|
47
|
-
dispatchPointerMove(event);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const handleDocumentPointerMove = (event: PointerEvent): void => {
|
|
51
|
-
queuedPointerMove = event;
|
|
52
|
-
if (pointerMoveFrame === null) {
|
|
53
|
-
pointerMoveFrame = requestAnimationFrame(flushPointerMove);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const handleDocumentPointerUp = (event: PointerEvent): void => {
|
|
58
|
-
if (pointerMoveFrame !== null) {
|
|
59
|
-
cancelAnimationFrame(pointerMoveFrame);
|
|
60
|
-
pointerMoveFrame = null;
|
|
61
|
-
queuedPointerMove = null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
for (const listener of getDroppableListenersSnapshot()) {
|
|
65
|
-
listener.handlePointerUp(event);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const registerDroppableListener = (listener: DroppableListener): void => {
|
|
70
|
-
if (droppableListeners.size === 0) {
|
|
71
|
-
document.addEventListener(
|
|
72
|
-
'pointermove',
|
|
73
|
-
handleDocumentPointerMove,
|
|
74
|
-
passivePointerMoveListenerOptions
|
|
75
|
-
);
|
|
76
|
-
document.addEventListener('pointerup', handleDocumentPointerUp);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
droppableListeners.add(listener);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const unregisterDroppableListener = (listener: DroppableListener): void => {
|
|
83
|
-
droppableListeners.delete(listener);
|
|
84
|
-
|
|
85
|
-
if (droppableListeners.size !== 0) return;
|
|
86
|
-
|
|
87
|
-
document.removeEventListener('pointermove', handleDocumentPointerMove);
|
|
88
|
-
document.removeEventListener('pointerup', handleDocumentPointerUp);
|
|
89
|
-
if (pointerMoveFrame !== null) {
|
|
90
|
-
cancelAnimationFrame(pointerMoveFrame);
|
|
91
|
-
pointerMoveFrame = null;
|
|
92
|
-
}
|
|
93
|
-
queuedPointerMove = null;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Checks whether a dragged element is accepted by the drop zone.
|
|
98
|
-
* @internal
|
|
99
|
-
*/
|
|
100
|
-
const isAccepted = (dragged: HTMLElement, accept: DroppableOptions['accept']): boolean => {
|
|
101
|
-
if (!accept) return true;
|
|
102
|
-
if (typeof accept === 'string') return dragged.matches(accept);
|
|
103
|
-
return accept(dragged);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Defines an element as a drop zone.
|
|
108
|
-
*
|
|
109
|
-
* Drop zones respond to draggable elements being moved over them
|
|
110
|
-
* by firing callbacks and applying CSS classes. They work with
|
|
111
|
-
* the `draggable()` function from this module.
|
|
112
|
-
*
|
|
113
|
-
* @param el - The drop zone element
|
|
114
|
-
* @param options - Configuration options
|
|
115
|
-
* @returns A handle with a `destroy()` method
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* ```ts
|
|
119
|
-
* import { droppable } from '@bquery/bquery/dnd';
|
|
120
|
-
*
|
|
121
|
-
* const handle = droppable(document.querySelector('#dropzone'), {
|
|
122
|
-
* accept: '.draggable-item',
|
|
123
|
-
* overClass: 'drop-active',
|
|
124
|
-
* onDrop: ({ dragged }) => {
|
|
125
|
-
* console.log('Dropped:', dragged);
|
|
126
|
-
* },
|
|
127
|
-
* });
|
|
128
|
-
*
|
|
129
|
-
* // Later:
|
|
130
|
-
* handle.destroy();
|
|
131
|
-
* ```
|
|
132
|
-
*/
|
|
133
|
-
export const droppable = (el: HTMLElement, options: DroppableOptions = {}): DroppableHandle => {
|
|
134
|
-
const {
|
|
135
|
-
overClass = 'bq-drop-over',
|
|
136
|
-
accept,
|
|
137
|
-
onDragEnter,
|
|
138
|
-
onDragOver,
|
|
139
|
-
onDragLeave,
|
|
140
|
-
onDrop,
|
|
141
|
-
} = options;
|
|
142
|
-
|
|
143
|
-
if (!hasDroppableEnvironment()) {
|
|
144
|
-
return {
|
|
145
|
-
destroy: () => {},
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
let isOver = false;
|
|
150
|
-
let currentDragged: HTMLElement | null = null;
|
|
151
|
-
|
|
152
|
-
const createEventData = (dragged: HTMLElement, event: PointerEvent): DropEventData => ({
|
|
153
|
-
zone: el,
|
|
154
|
-
dragged,
|
|
155
|
-
event,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const isPointerInside = (event: PointerEvent): boolean => {
|
|
159
|
-
const rect = el.getBoundingClientRect();
|
|
160
|
-
return (
|
|
161
|
-
event.clientX >= rect.left &&
|
|
162
|
-
event.clientX <= rect.right &&
|
|
163
|
-
event.clientY >= rect.top &&
|
|
164
|
-
event.clientY <= rect.bottom
|
|
165
|
-
);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const resolveDraggedElement = (): HTMLElement | null => {
|
|
169
|
-
return getActiveDrag()?.element ?? currentDragged;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const clearOverState = (event: PointerEvent, dragged = currentDragged): void => {
|
|
173
|
-
if (!isOver) return;
|
|
174
|
-
isOver = false;
|
|
175
|
-
el.classList.remove(overClass);
|
|
176
|
-
if (dragged) {
|
|
177
|
-
onDragLeave?.(createEventData(dragged, event));
|
|
178
|
-
}
|
|
179
|
-
currentDragged = null;
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const handlePointerMove = (e: PointerEvent): void => {
|
|
183
|
-
const dragged = getActiveDrag()?.element ?? null;
|
|
184
|
-
const isInside = isPointerInside(e);
|
|
185
|
-
const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);
|
|
186
|
-
|
|
187
|
-
if (!acceptsDragged || !isInside) {
|
|
188
|
-
clearOverState(e, dragged ?? currentDragged);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (!isOver) {
|
|
193
|
-
isOver = true;
|
|
194
|
-
currentDragged = dragged;
|
|
195
|
-
el.classList.add(overClass);
|
|
196
|
-
onDragEnter?.(createEventData(dragged, e));
|
|
197
|
-
} else {
|
|
198
|
-
onDragOver?.(createEventData(dragged, e));
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const handlePointerUp = (e: PointerEvent): void => {
|
|
203
|
-
const dragged = resolveDraggedElement();
|
|
204
|
-
const isInside = isPointerInside(e);
|
|
205
|
-
const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);
|
|
206
|
-
|
|
207
|
-
if (isInside && acceptsDragged && dragged) {
|
|
208
|
-
onDrop?.(createEventData(dragged, e));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (isOver) {
|
|
212
|
-
isOver = false;
|
|
213
|
-
el.classList.remove(overClass);
|
|
214
|
-
}
|
|
215
|
-
currentDragged = null;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const listener: DroppableListener = { handlePointerMove, handlePointerUp };
|
|
219
|
-
registerDroppableListener(listener);
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
destroy: () => {
|
|
223
|
-
unregisterDroppableListener(listener);
|
|
224
|
-
el.classList.remove(overClass);
|
|
225
|
-
currentDragged = null;
|
|
226
|
-
},
|
|
227
|
-
};
|
|
228
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Define drop zones for draggable elements.
|
|
3
|
+
*
|
|
4
|
+
* Drop zones detect when draggable elements enter, move over,
|
|
5
|
+
* leave, or are dropped onto them using pointer event hit-testing.
|
|
6
|
+
*
|
|
7
|
+
* @module bquery/dnd
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getActiveDrag } from './draggable';
|
|
11
|
+
import type { DropEventData, DroppableHandle, DroppableOptions } from './types';
|
|
12
|
+
|
|
13
|
+
type DroppableListener = {
|
|
14
|
+
handlePointerMove: (event: PointerEvent) => void;
|
|
15
|
+
handlePointerUp: (event: PointerEvent) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const passivePointerMoveListenerOptions = { passive: true } as const;
|
|
19
|
+
|
|
20
|
+
const droppableListeners = new Set<DroppableListener>();
|
|
21
|
+
let queuedPointerMove: PointerEvent | null = null;
|
|
22
|
+
let pointerMoveFrame: number | null = null;
|
|
23
|
+
|
|
24
|
+
const getDroppableListenersSnapshot = (): DroppableListener[] => Array.from(droppableListeners);
|
|
25
|
+
|
|
26
|
+
const hasDroppableEnvironment = (): boolean => {
|
|
27
|
+
return (
|
|
28
|
+
typeof document !== 'undefined' &&
|
|
29
|
+
typeof document.addEventListener === 'function' &&
|
|
30
|
+
typeof document.removeEventListener === 'function' &&
|
|
31
|
+
typeof requestAnimationFrame === 'function' &&
|
|
32
|
+
typeof cancelAnimationFrame === 'function'
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const dispatchPointerMove = (event: PointerEvent): void => {
|
|
37
|
+
for (const listener of getDroppableListenersSnapshot()) {
|
|
38
|
+
listener.handlePointerMove(event);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const flushPointerMove = (): void => {
|
|
43
|
+
pointerMoveFrame = null;
|
|
44
|
+
const event = queuedPointerMove;
|
|
45
|
+
queuedPointerMove = null;
|
|
46
|
+
if (!event) return;
|
|
47
|
+
dispatchPointerMove(event);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleDocumentPointerMove = (event: PointerEvent): void => {
|
|
51
|
+
queuedPointerMove = event;
|
|
52
|
+
if (pointerMoveFrame === null) {
|
|
53
|
+
pointerMoveFrame = requestAnimationFrame(flushPointerMove);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleDocumentPointerUp = (event: PointerEvent): void => {
|
|
58
|
+
if (pointerMoveFrame !== null) {
|
|
59
|
+
cancelAnimationFrame(pointerMoveFrame);
|
|
60
|
+
pointerMoveFrame = null;
|
|
61
|
+
queuedPointerMove = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const listener of getDroppableListenersSnapshot()) {
|
|
65
|
+
listener.handlePointerUp(event);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const registerDroppableListener = (listener: DroppableListener): void => {
|
|
70
|
+
if (droppableListeners.size === 0) {
|
|
71
|
+
document.addEventListener(
|
|
72
|
+
'pointermove',
|
|
73
|
+
handleDocumentPointerMove,
|
|
74
|
+
passivePointerMoveListenerOptions
|
|
75
|
+
);
|
|
76
|
+
document.addEventListener('pointerup', handleDocumentPointerUp);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
droppableListeners.add(listener);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const unregisterDroppableListener = (listener: DroppableListener): void => {
|
|
83
|
+
droppableListeners.delete(listener);
|
|
84
|
+
|
|
85
|
+
if (droppableListeners.size !== 0) return;
|
|
86
|
+
|
|
87
|
+
document.removeEventListener('pointermove', handleDocumentPointerMove);
|
|
88
|
+
document.removeEventListener('pointerup', handleDocumentPointerUp);
|
|
89
|
+
if (pointerMoveFrame !== null) {
|
|
90
|
+
cancelAnimationFrame(pointerMoveFrame);
|
|
91
|
+
pointerMoveFrame = null;
|
|
92
|
+
}
|
|
93
|
+
queuedPointerMove = null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Checks whether a dragged element is accepted by the drop zone.
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
const isAccepted = (dragged: HTMLElement, accept: DroppableOptions['accept']): boolean => {
|
|
101
|
+
if (!accept) return true;
|
|
102
|
+
if (typeof accept === 'string') return dragged.matches(accept);
|
|
103
|
+
return accept(dragged);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Defines an element as a drop zone.
|
|
108
|
+
*
|
|
109
|
+
* Drop zones respond to draggable elements being moved over them
|
|
110
|
+
* by firing callbacks and applying CSS classes. They work with
|
|
111
|
+
* the `draggable()` function from this module.
|
|
112
|
+
*
|
|
113
|
+
* @param el - The drop zone element
|
|
114
|
+
* @param options - Configuration options
|
|
115
|
+
* @returns A handle with a `destroy()` method
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { droppable } from '@bquery/bquery/dnd';
|
|
120
|
+
*
|
|
121
|
+
* const handle = droppable(document.querySelector('#dropzone'), {
|
|
122
|
+
* accept: '.draggable-item',
|
|
123
|
+
* overClass: 'drop-active',
|
|
124
|
+
* onDrop: ({ dragged }) => {
|
|
125
|
+
* console.log('Dropped:', dragged);
|
|
126
|
+
* },
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* // Later:
|
|
130
|
+
* handle.destroy();
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export const droppable = (el: HTMLElement, options: DroppableOptions = {}): DroppableHandle => {
|
|
134
|
+
const {
|
|
135
|
+
overClass = 'bq-drop-over',
|
|
136
|
+
accept,
|
|
137
|
+
onDragEnter,
|
|
138
|
+
onDragOver,
|
|
139
|
+
onDragLeave,
|
|
140
|
+
onDrop,
|
|
141
|
+
} = options;
|
|
142
|
+
|
|
143
|
+
if (!hasDroppableEnvironment()) {
|
|
144
|
+
return {
|
|
145
|
+
destroy: () => {},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let isOver = false;
|
|
150
|
+
let currentDragged: HTMLElement | null = null;
|
|
151
|
+
|
|
152
|
+
const createEventData = (dragged: HTMLElement, event: PointerEvent): DropEventData => ({
|
|
153
|
+
zone: el,
|
|
154
|
+
dragged,
|
|
155
|
+
event,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const isPointerInside = (event: PointerEvent): boolean => {
|
|
159
|
+
const rect = el.getBoundingClientRect();
|
|
160
|
+
return (
|
|
161
|
+
event.clientX >= rect.left &&
|
|
162
|
+
event.clientX <= rect.right &&
|
|
163
|
+
event.clientY >= rect.top &&
|
|
164
|
+
event.clientY <= rect.bottom
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const resolveDraggedElement = (): HTMLElement | null => {
|
|
169
|
+
return getActiveDrag()?.element ?? currentDragged;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const clearOverState = (event: PointerEvent, dragged = currentDragged): void => {
|
|
173
|
+
if (!isOver) return;
|
|
174
|
+
isOver = false;
|
|
175
|
+
el.classList.remove(overClass);
|
|
176
|
+
if (dragged) {
|
|
177
|
+
onDragLeave?.(createEventData(dragged, event));
|
|
178
|
+
}
|
|
179
|
+
currentDragged = null;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const handlePointerMove = (e: PointerEvent): void => {
|
|
183
|
+
const dragged = getActiveDrag()?.element ?? null;
|
|
184
|
+
const isInside = isPointerInside(e);
|
|
185
|
+
const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);
|
|
186
|
+
|
|
187
|
+
if (!acceptsDragged || !isInside) {
|
|
188
|
+
clearOverState(e, dragged ?? currentDragged);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!isOver) {
|
|
193
|
+
isOver = true;
|
|
194
|
+
currentDragged = dragged;
|
|
195
|
+
el.classList.add(overClass);
|
|
196
|
+
onDragEnter?.(createEventData(dragged, e));
|
|
197
|
+
} else {
|
|
198
|
+
onDragOver?.(createEventData(dragged, e));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handlePointerUp = (e: PointerEvent): void => {
|
|
203
|
+
const dragged = resolveDraggedElement();
|
|
204
|
+
const isInside = isPointerInside(e);
|
|
205
|
+
const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);
|
|
206
|
+
|
|
207
|
+
if (isInside && acceptsDragged && dragged) {
|
|
208
|
+
onDrop?.(createEventData(dragged, e));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (isOver) {
|
|
212
|
+
isOver = false;
|
|
213
|
+
el.classList.remove(overClass);
|
|
214
|
+
}
|
|
215
|
+
currentDragged = null;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const listener: DroppableListener = { handlePointerMove, handlePointerUp };
|
|
219
|
+
registerDroppableListener(listener);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
destroy: () => {
|
|
223
|
+
unregisterDroppableListener(listener);
|
|
224
|
+
el.classList.remove(overClass);
|
|
225
|
+
currentDragged = null;
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
};
|
package/src/dnd/index.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bQuery Drag & Drop module.
|
|
3
|
-
*
|
|
4
|
-
* Provides pointer-event-based drag-and-drop, drop zones, and sortable
|
|
5
|
-
* lists with built-in touch support, axis locking, bounds constraints,
|
|
6
|
-
* and animated reordering.
|
|
7
|
-
*
|
|
8
|
-
* @module bquery/dnd
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { draggable, droppable, sortable } from '@bquery/bquery/dnd';
|
|
13
|
-
*
|
|
14
|
-
* // Make an element draggable
|
|
15
|
-
* const drag = draggable(document.querySelector('#box'), {
|
|
16
|
-
* axis: 'both',
|
|
17
|
-
* bounds: 'parent',
|
|
18
|
-
* ghost: true,
|
|
19
|
-
* onDragEnd: ({ position }) => console.log(position),
|
|
20
|
-
* });
|
|
21
|
-
*
|
|
22
|
-
* // Define a drop zone
|
|
23
|
-
* const drop = droppable(document.querySelector('#zone'), {
|
|
24
|
-
* accept: '.draggable',
|
|
25
|
-
* onDrop: ({ dragged }) => console.log('Dropped!', dragged),
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* // Make a list sortable
|
|
29
|
-
* const sort = sortable(document.querySelector('#list'), {
|
|
30
|
-
* items: 'li',
|
|
31
|
-
* axis: 'y',
|
|
32
|
-
* onSortEnd: ({ oldIndex, newIndex }) => {
|
|
33
|
-
* console.log(`Moved from ${oldIndex} to ${newIndex}`);
|
|
34
|
-
* },
|
|
35
|
-
* });
|
|
36
|
-
*
|
|
37
|
-
* // Cleanup when done
|
|
38
|
-
* drag.destroy();
|
|
39
|
-
* drop.destroy();
|
|
40
|
-
* sort.destroy();
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
export { draggable } from './draggable';
|
|
45
|
-
export { droppable } from './droppable';
|
|
46
|
-
export { sortable } from './sortable';
|
|
47
|
-
|
|
48
|
-
export type {
|
|
49
|
-
BoundsRect,
|
|
50
|
-
DragAxis,
|
|
51
|
-
DragBounds,
|
|
52
|
-
DragEventData,
|
|
53
|
-
DragPosition,
|
|
54
|
-
DraggableHandle,
|
|
55
|
-
DraggableOptions,
|
|
56
|
-
DropEventData,
|
|
57
|
-
DroppableHandle,
|
|
58
|
-
DroppableOptions,
|
|
59
|
-
SortEventData,
|
|
60
|
-
SortableHandle,
|
|
61
|
-
SortableOptions,
|
|
62
|
-
} from './types';
|
|
1
|
+
/**
|
|
2
|
+
* bQuery Drag & Drop module.
|
|
3
|
+
*
|
|
4
|
+
* Provides pointer-event-based drag-and-drop, drop zones, and sortable
|
|
5
|
+
* lists with built-in touch support, axis locking, bounds constraints,
|
|
6
|
+
* and animated reordering.
|
|
7
|
+
*
|
|
8
|
+
* @module bquery/dnd
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { draggable, droppable, sortable } from '@bquery/bquery/dnd';
|
|
13
|
+
*
|
|
14
|
+
* // Make an element draggable
|
|
15
|
+
* const drag = draggable(document.querySelector('#box'), {
|
|
16
|
+
* axis: 'both',
|
|
17
|
+
* bounds: 'parent',
|
|
18
|
+
* ghost: true,
|
|
19
|
+
* onDragEnd: ({ position }) => console.log(position),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Define a drop zone
|
|
23
|
+
* const drop = droppable(document.querySelector('#zone'), {
|
|
24
|
+
* accept: '.draggable',
|
|
25
|
+
* onDrop: ({ dragged }) => console.log('Dropped!', dragged),
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Make a list sortable
|
|
29
|
+
* const sort = sortable(document.querySelector('#list'), {
|
|
30
|
+
* items: 'li',
|
|
31
|
+
* axis: 'y',
|
|
32
|
+
* onSortEnd: ({ oldIndex, newIndex }) => {
|
|
33
|
+
* console.log(`Moved from ${oldIndex} to ${newIndex}`);
|
|
34
|
+
* },
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Cleanup when done
|
|
38
|
+
* drag.destroy();
|
|
39
|
+
* drop.destroy();
|
|
40
|
+
* sort.destroy();
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export { draggable } from './draggable';
|
|
45
|
+
export { droppable } from './droppable';
|
|
46
|
+
export { sortable } from './sortable';
|
|
47
|
+
|
|
48
|
+
export type {
|
|
49
|
+
BoundsRect,
|
|
50
|
+
DragAxis,
|
|
51
|
+
DragBounds,
|
|
52
|
+
DragEventData,
|
|
53
|
+
DragPosition,
|
|
54
|
+
DraggableHandle,
|
|
55
|
+
DraggableOptions,
|
|
56
|
+
DropEventData,
|
|
57
|
+
DroppableHandle,
|
|
58
|
+
DroppableOptions,
|
|
59
|
+
SortEventData,
|
|
60
|
+
SortableHandle,
|
|
61
|
+
SortableOptions,
|
|
62
|
+
} from './types';
|