@duro-app/ui 3.2.0 → 3.2.1
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/dist/components/DragDrop/DragDrop.d.ts.map +1 -1
- package/dist/components/DragDrop/DragDrop.stories.d.ts +3 -0
- package/dist/components/DragDrop/DragDrop.stories.d.ts.map +1 -1
- package/dist/index.js +274 -283
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/DragDrop/DragDrop.stories.tsx +29 -8
- package/src/components/DragDrop/DragDrop.tsx +17 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@tanstack/react-virtual": "^3.14.6",
|
|
67
|
-
"@duro-app/tokens": "^3.2.
|
|
67
|
+
"@duro-app/tokens": "^3.2.1"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {Meta, StoryObj} from '@storybook/react'
|
|
2
|
-
import {expect, fn} from 'storybook/test'
|
|
3
|
-
import {useState} from 'react'
|
|
2
|
+
import {expect, fn, waitFor} from 'storybook/test'
|
|
3
|
+
import {StrictMode, useState} from 'react'
|
|
4
4
|
import {css, html} from 'react-strict-dom'
|
|
5
5
|
import {DragDrop, type DragDropEvent} from './DragDrop'
|
|
6
6
|
import {Button} from '../Button/Button'
|
|
@@ -219,21 +219,42 @@ export const TouchBurstAfterHold: Story = {
|
|
|
219
219
|
),
|
|
220
220
|
)
|
|
221
221
|
}
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
await expect(canvas.getByRole('status')).toHaveTextContent('Picked up Noor.')
|
|
222
|
+
// Checkpoint: the burst alone must have activated the drag. The update
|
|
223
|
+
// came from a native event, so React flushes it on its own task.
|
|
224
|
+
await waitFor(() => expect(canvas.getByRole('status')).toHaveTextContent('Picked up Noor.'))
|
|
225
225
|
document.dispatchEvent(new PointerEvent('pointerup', init(ex, ey, 0)))
|
|
226
226
|
} finally {
|
|
227
227
|
window.setTimeout = original
|
|
228
228
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
await waitFor(() =>
|
|
230
|
+
expect(canvas.getByRole('status')).toHaveTextContent('Dropped Noor in Gates.'),
|
|
231
|
+
)
|
|
232
232
|
await expect(args.onDrop).toHaveBeenCalledTimes(1)
|
|
233
233
|
await expect(canvas.queryByRole('button', {name: 'Noor'})).toBeNull()
|
|
234
234
|
},
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/** onDrop runs from the release handler, not from a state updater, so
|
|
238
|
+
* StrictMode's double-invoked updaters cannot fire it twice. */
|
|
239
|
+
export const StrictModeDropsOnce: Story = {
|
|
240
|
+
args: {onDrop: fn()},
|
|
241
|
+
render: (args) => (
|
|
242
|
+
<StrictMode>
|
|
243
|
+
<Board onDrop={args.onDrop as never} />
|
|
244
|
+
</StrictMode>
|
|
245
|
+
),
|
|
246
|
+
play: async ({canvas, args}) => {
|
|
247
|
+
const marie = await canvas.findByRole('button', {name: 'Marie'})
|
|
248
|
+
const gatesZone = canvas.getByText('Drop a person here.')
|
|
249
|
+
await pointerDrag(marie.parentElement as Element, gatesZone)
|
|
250
|
+
await waitFor(() =>
|
|
251
|
+
expect(canvas.getByRole('status')).toHaveTextContent('Dropped Marie in Gates.'),
|
|
252
|
+
)
|
|
253
|
+
await expect(args.onDrop).toHaveBeenCalledTimes(1)
|
|
254
|
+
await expect(canvas.queryByRole('button', {name: 'Marie'})).toBeNull()
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
|
|
237
258
|
/** A plain click on an item still reaches the button inside: no drag starts
|
|
238
259
|
* under the movement threshold. */
|
|
239
260
|
export const TapStaysAClick: Story = {
|
|
@@ -188,8 +188,12 @@ function Root<T = unknown>({onDrop, announce, children}: DragDropRootProps<T>) {
|
|
|
188
188
|
return null
|
|
189
189
|
}, [])
|
|
190
190
|
|
|
191
|
+
// `target` is the resolved drop target (null cancels). The consumer's
|
|
192
|
+
// onDrop runs here, in the event handler, never inside a state updater:
|
|
193
|
+
// React may call an updater more than once and forbids updating another
|
|
194
|
+
// component from it.
|
|
191
195
|
const finish = useCallback(
|
|
192
|
-
(
|
|
196
|
+
(target: DragDropTarget | null) => {
|
|
193
197
|
const s = session.current
|
|
194
198
|
if (!s) return
|
|
195
199
|
if (s.holdTimer) clearTimeout(s.holdTimer)
|
|
@@ -202,17 +206,14 @@ function Root<T = unknown>({onDrop, announce, children}: DragDropRootProps<T>) {
|
|
|
202
206
|
// already released
|
|
203
207
|
}
|
|
204
208
|
if (!s.active) return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
return null
|
|
215
|
-
})
|
|
209
|
+
const item = items.current.get(s.id)
|
|
210
|
+
setDrag(null)
|
|
211
|
+
if (target && item) {
|
|
212
|
+
onDrop({item: {id: s.id, zone: item.zone, data: item.data as T}, target})
|
|
213
|
+
say('drop', s.id, target.zone)
|
|
214
|
+
} else {
|
|
215
|
+
say('cancel', s.id)
|
|
216
|
+
}
|
|
216
217
|
},
|
|
217
218
|
[onDrop, say],
|
|
218
219
|
)
|
|
@@ -295,7 +296,7 @@ function Root<T = unknown>({onDrop, announce, children}: DragDropRootProps<T>) {
|
|
|
295
296
|
activate(s, s.startX, s.startY)
|
|
296
297
|
} else {
|
|
297
298
|
// Moved before the hold elapsed: a flick, not a drag.
|
|
298
|
-
if (moved > MOVE_THRESHOLD_PX) finish(
|
|
299
|
+
if (moved > MOVE_THRESHOLD_PX) finish(null)
|
|
299
300
|
return
|
|
300
301
|
}
|
|
301
302
|
} else {
|
|
@@ -319,19 +320,15 @@ function Root<T = unknown>({onDrop, announce, children}: DragDropRootProps<T>) {
|
|
|
319
320
|
const s = session.current
|
|
320
321
|
if (!s || e.pointerId !== s.pointerId) return
|
|
321
322
|
// Resolve the target from the release point, not the last frame.
|
|
322
|
-
|
|
323
|
-
const over = locate(s.id, e.clientX, e.clientY)
|
|
324
|
-
setDrag((current) => (current ? {...current, over} : current))
|
|
325
|
-
}
|
|
326
|
-
finish(true)
|
|
323
|
+
finish(s.active ? locate(s.id, e.clientX, e.clientY) : null)
|
|
327
324
|
}
|
|
328
325
|
const onCancel = (e: PointerEvent) => {
|
|
329
326
|
const s = session.current
|
|
330
327
|
if (!s || e.pointerId !== s.pointerId) return
|
|
331
|
-
finish(
|
|
328
|
+
finish(null)
|
|
332
329
|
}
|
|
333
330
|
const onKey = (e: KeyboardEvent) => {
|
|
334
|
-
if (e.key === 'Escape' && session.current) finish(
|
|
331
|
+
if (e.key === 'Escape' && session.current) finish(null)
|
|
335
332
|
}
|
|
336
333
|
document.addEventListener('pointermove', onMove)
|
|
337
334
|
document.addEventListener('pointerup', onUp)
|