ngx-virtual-dnd 3.1.3 → 3.2.0-alpha.0
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 +57 -18
- package/fesm2022/ngx-virtual-dnd.mjs +351 -9
- package/fesm2022/ngx-virtual-dnd.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-virtual-dnd.d.ts +67 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, InjectionToken, computed, effect, Injectable, inject, NgZone, input, ChangeDetectionStrategy, Component, DestroyRef, ElementRef, Injector,
|
|
2
|
+
import { signal, InjectionToken, computed, effect, Injectable, inject, NgZone, input, ChangeDetectionStrategy, Component, DestroyRef, afterNextRender, ElementRef, Injector, untracked, viewChild, Directive, isDevMode, output, contentChild, EnvironmentInjector, TemplateRef, ViewContainerRef } from '@angular/core';
|
|
3
3
|
import { NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -452,6 +452,25 @@ const VDND_SCROLL_CONTAINER = new InjectionToken('VDND_SCROLL_CONTAINER');
|
|
|
452
452
|
*/
|
|
453
453
|
const VDND_VIRTUAL_VIEWPORT = new InjectionToken('VDND_VIRTUAL_VIEWPORT');
|
|
454
454
|
|
|
455
|
+
/** Default shift animation duration (ms) used when the config omits `shiftDuration`. */
|
|
456
|
+
const DEFAULT_SHIFT_DURATION = 200;
|
|
457
|
+
/** Default shift animation easing used when the config omits `shiftEasing`. */
|
|
458
|
+
const DEFAULT_SHIFT_EASING = 'cubic-bezier(0.2, 0, 0, 1)';
|
|
459
|
+
/**
|
|
460
|
+
* Opt-in animation configuration. When provided, items displaced by the placeholder
|
|
461
|
+
* slide into their new position instead of jumping. Resolved through the element
|
|
462
|
+
* injector, so it can be provided app-wide or per component subtree.
|
|
463
|
+
*
|
|
464
|
+
* The animation is skipped when the user prefers reduced motion. Values are read each
|
|
465
|
+
* time an animation starts, so getters can toggle the animation at runtime.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```typescript
|
|
469
|
+
* providers: [{ provide: VDND_ANIMATION_CONFIG, useValue: { shiftDuration: 200 } }]
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
const VDND_ANIMATION_CONFIG = new InjectionToken('VDND_ANIMATION_CONFIG');
|
|
473
|
+
|
|
455
474
|
const INITIAL_CORE_STATE = {
|
|
456
475
|
isDragging: false,
|
|
457
476
|
draggedItem: null,
|
|
@@ -2426,6 +2445,181 @@ function createAutoScrollRegistration(options) {
|
|
|
2426
2445
|
destroyRef.onDestroy(unregisterCurrent);
|
|
2427
2446
|
}
|
|
2428
2447
|
|
|
2448
|
+
/** Layout changes smaller than this are ignored (sub-pixel rounding noise). */
|
|
2449
|
+
const MIN_SHIFT_PX = 0.5;
|
|
2450
|
+
/**
|
|
2451
|
+
* FLIP animator for items displaced by the drag placeholder.
|
|
2452
|
+
*
|
|
2453
|
+
* Call `beforeUpdate()` before the DOM reflects a new drag/placeholder state. When the
|
|
2454
|
+
* placeholder moved, it snapshots every rendered element's visual position, then after
|
|
2455
|
+
* the render measures again and plays a compositor-only `transform` animation from the
|
|
2456
|
+
* old position to the new one.
|
|
2457
|
+
*
|
|
2458
|
+
* Positions are measured relative to the scroll content, so scrolling (including
|
|
2459
|
+
* programmatic scroll in the same tick) never animates. Measurements include any
|
|
2460
|
+
* in-flight animation, so a new displacement retargets from where the item currently
|
|
2461
|
+
* is instead of stacking or jumping. Elements whose target did not change keep their
|
|
2462
|
+
* running animation untouched.
|
|
2463
|
+
* @internal
|
|
2464
|
+
*/
|
|
2465
|
+
class ShiftAnimator {
|
|
2466
|
+
#config;
|
|
2467
|
+
#injector;
|
|
2468
|
+
#getScrollElement;
|
|
2469
|
+
#getEntries;
|
|
2470
|
+
#running = new Map();
|
|
2471
|
+
#snapshot = null;
|
|
2472
|
+
#reducedMotionQuery;
|
|
2473
|
+
#wasDragging = false;
|
|
2474
|
+
#lastPlaceholderIndex = -1;
|
|
2475
|
+
constructor(options) {
|
|
2476
|
+
this.#config = options.config;
|
|
2477
|
+
this.#injector = options.injector;
|
|
2478
|
+
this.#getScrollElement = options.getScrollElement;
|
|
2479
|
+
this.#getEntries = options.getEntries;
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Notify the animator of the drag state about to be rendered.
|
|
2483
|
+
* @param placeholderIndex placeholder index in this list, or -1 when not shown here
|
|
2484
|
+
*/
|
|
2485
|
+
beforeUpdate(isDragging, placeholderIndex) {
|
|
2486
|
+
const wasDragging = this.#wasDragging;
|
|
2487
|
+
const previousIndex = this.#lastPlaceholderIndex;
|
|
2488
|
+
this.#wasDragging = isDragging;
|
|
2489
|
+
this.#lastPlaceholderIndex = placeholderIndex;
|
|
2490
|
+
if (!isDragging) {
|
|
2491
|
+
// Drop/cancel: the list re-renders with the committed order, so snap everything.
|
|
2492
|
+
this.#snapshot = null;
|
|
2493
|
+
if (wasDragging) {
|
|
2494
|
+
this.cancelAll();
|
|
2495
|
+
}
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
// Drag start renders the placeholder in the hidden item's slot — nothing moves.
|
|
2499
|
+
// A pending snapshot means another update already captured this tick's "before".
|
|
2500
|
+
if (!wasDragging || placeholderIndex === previousIndex || this.#snapshot) {
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
if (!this.#isEnabled()) {
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
this.#snapshot = this.#measure();
|
|
2507
|
+
afterNextRender(() => this.#play(), { injector: this.#injector });
|
|
2508
|
+
}
|
|
2509
|
+
/** Stop the shift animation on an element (e.g. before its view is recycled). */
|
|
2510
|
+
cancel(element) {
|
|
2511
|
+
const running = this.#running.get(element);
|
|
2512
|
+
if (!running)
|
|
2513
|
+
return;
|
|
2514
|
+
this.#running.delete(element);
|
|
2515
|
+
running.animation.cancel();
|
|
2516
|
+
}
|
|
2517
|
+
/** Stop every running shift animation. */
|
|
2518
|
+
cancelAll() {
|
|
2519
|
+
for (const running of this.#running.values()) {
|
|
2520
|
+
running.animation.cancel();
|
|
2521
|
+
}
|
|
2522
|
+
this.#running.clear();
|
|
2523
|
+
}
|
|
2524
|
+
#isEnabled() {
|
|
2525
|
+
if (this.#duration() <= 0)
|
|
2526
|
+
return false;
|
|
2527
|
+
if (typeof Element === 'undefined' || typeof Element.prototype.animate !== 'function') {
|
|
2528
|
+
return false;
|
|
2529
|
+
}
|
|
2530
|
+
return !this.#prefersReducedMotion();
|
|
2531
|
+
}
|
|
2532
|
+
#duration() {
|
|
2533
|
+
return this.#config.shiftDuration ?? DEFAULT_SHIFT_DURATION;
|
|
2534
|
+
}
|
|
2535
|
+
#prefersReducedMotion() {
|
|
2536
|
+
if (this.#reducedMotionQuery === undefined) {
|
|
2537
|
+
this.#reducedMotionQuery =
|
|
2538
|
+
typeof matchMedia === 'function' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
|
|
2539
|
+
}
|
|
2540
|
+
return this.#reducedMotionQuery?.matches ?? false;
|
|
2541
|
+
}
|
|
2542
|
+
/** Scroll-content origin, so positions are independent of the scroll offset. */
|
|
2543
|
+
#origin() {
|
|
2544
|
+
const scrollElement = this.#getScrollElement();
|
|
2545
|
+
if (!scrollElement)
|
|
2546
|
+
return { x: 0, y: 0 };
|
|
2547
|
+
const rect = scrollElement.getBoundingClientRect();
|
|
2548
|
+
return { x: rect.left - scrollElement.scrollLeft, y: rect.top - scrollElement.scrollTop };
|
|
2549
|
+
}
|
|
2550
|
+
#measure() {
|
|
2551
|
+
const origin = this.#origin();
|
|
2552
|
+
const positions = new Map();
|
|
2553
|
+
for (const [key, element] of this.#getEntries()) {
|
|
2554
|
+
const rect = element.getBoundingClientRect();
|
|
2555
|
+
// Hidden (display: none) elements, such as the dragged item, have no box.
|
|
2556
|
+
if (rect.width === 0 && rect.height === 0)
|
|
2557
|
+
continue;
|
|
2558
|
+
positions.set(element, { key, x: rect.left - origin.x, y: rect.top - origin.y });
|
|
2559
|
+
}
|
|
2560
|
+
return positions;
|
|
2561
|
+
}
|
|
2562
|
+
#play() {
|
|
2563
|
+
const first = this.#snapshot;
|
|
2564
|
+
this.#snapshot = null;
|
|
2565
|
+
if (!first || !this.#wasDragging)
|
|
2566
|
+
return;
|
|
2567
|
+
const last = this.#measure();
|
|
2568
|
+
const stale = [];
|
|
2569
|
+
const shifts = [];
|
|
2570
|
+
// Read phase — no writes until every element is measured.
|
|
2571
|
+
for (const [element, to] of last) {
|
|
2572
|
+
const from = first.get(element);
|
|
2573
|
+
if (!from || !Object.is(from.key, to.key)) {
|
|
2574
|
+
// Newly rendered or recycled for another item: must not animate from a stale offset.
|
|
2575
|
+
stale.push(element);
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
const dx = from.x - to.x;
|
|
2579
|
+
const dy = from.y - to.y;
|
|
2580
|
+
if (Math.abs(dx) < MIN_SHIFT_PX && Math.abs(dy) < MIN_SHIFT_PX) {
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
// Both measurements include the in-flight offset, so the new animation must
|
|
2584
|
+
// start from that offset plus the layout delta to continue from the current spot.
|
|
2585
|
+
const current = this.#currentOffset(element);
|
|
2586
|
+
shifts.push({ element, x: dx + current.x, y: dy + current.y });
|
|
2587
|
+
}
|
|
2588
|
+
// Write phase
|
|
2589
|
+
for (const element of stale) {
|
|
2590
|
+
this.cancel(element);
|
|
2591
|
+
}
|
|
2592
|
+
for (const shift of shifts) {
|
|
2593
|
+
this.#start(shift.element, shift.x, shift.y);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
#currentOffset(element) {
|
|
2597
|
+
const running = this.#running.get(element);
|
|
2598
|
+
// Effect-level easing makes `progress` the eased progress, i.e. the fraction travelled.
|
|
2599
|
+
const progress = running?.animation.effect?.getComputedTiming().progress;
|
|
2600
|
+
if (!running || progress === null || progress === undefined) {
|
|
2601
|
+
return { x: 0, y: 0 };
|
|
2602
|
+
}
|
|
2603
|
+
const remaining = 1 - progress;
|
|
2604
|
+
return { x: running.x * remaining, y: running.y * remaining };
|
|
2605
|
+
}
|
|
2606
|
+
#start(element, x, y) {
|
|
2607
|
+
this.#running.get(element)?.animation.cancel();
|
|
2608
|
+
const animation = element.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: 'translate(0px, 0px)' }], {
|
|
2609
|
+
duration: this.#duration(),
|
|
2610
|
+
easing: this.#config.shiftEasing ?? DEFAULT_SHIFT_EASING,
|
|
2611
|
+
// Layer on top of any transform the consumer already applies to the item.
|
|
2612
|
+
composite: 'add',
|
|
2613
|
+
});
|
|
2614
|
+
this.#running.set(element, { animation, x, y });
|
|
2615
|
+
animation.onfinish = () => {
|
|
2616
|
+
if (this.#running.get(element)?.animation === animation) {
|
|
2617
|
+
this.#running.delete(element);
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2429
2623
|
/**
|
|
2430
2624
|
* A virtual scroll container that only renders visible items.
|
|
2431
2625
|
*
|
|
@@ -2481,6 +2675,8 @@ class VirtualScrollContainerComponent {
|
|
|
2481
2675
|
#observedElementByKey = new Map();
|
|
2482
2676
|
/** Measured height from ResizeObserver (used when containerHeight is not provided) */
|
|
2483
2677
|
#measuredHeight = signal(0, ...(ngDevMode ? [{ debugName: "#measuredHeight" }] : []));
|
|
2678
|
+
/** Slides items displaced by the placeholder (only when VDND_ANIMATION_CONFIG is provided) */
|
|
2679
|
+
#shiftAnimator = this.#createShiftAnimator();
|
|
2484
2680
|
/** Template for rendering each item - passed as input instead of content child for reliability */
|
|
2485
2681
|
itemTemplate = input.required(...(ngDevMode ? [{ debugName: "itemTemplate" }] : []));
|
|
2486
2682
|
/** Unique ID for this scroll container (used for auto-scroll registration) */
|
|
@@ -2773,6 +2969,16 @@ class VirtualScrollContainerComponent {
|
|
|
2773
2969
|
const draggedIndex = this.#draggedItemIndex();
|
|
2774
2970
|
strategy.setExcludedIndex(draggedIndex >= 0 ? draggedIndex : null);
|
|
2775
2971
|
});
|
|
2972
|
+
// Snapshot item positions before the template re-renders the placeholder move.
|
|
2973
|
+
// Component effects run before the component's own template is refreshed.
|
|
2974
|
+
if (this.#shiftAnimator) {
|
|
2975
|
+
const animator = this.#shiftAnimator;
|
|
2976
|
+
effect(() => {
|
|
2977
|
+
const isDragging = this.#dragState.isDragging();
|
|
2978
|
+
const placeholderIndex = this.placeholderIndex();
|
|
2979
|
+
untracked(() => animator.beforeUpdate(isDragging, placeholderIndex));
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2776
2982
|
// Keyboard drag autoscroll: scroll to keep target index visible
|
|
2777
2983
|
effect(() => {
|
|
2778
2984
|
// Only apply when this droppable is active during keyboard drag
|
|
@@ -2898,6 +3104,32 @@ class VirtualScrollContainerComponent {
|
|
|
2898
3104
|
}
|
|
2899
3105
|
this.#observedElementByKey.clear();
|
|
2900
3106
|
this.#itemResizeObserver?.disconnect();
|
|
3107
|
+
this.#shiftAnimator?.cancelAll();
|
|
3108
|
+
}
|
|
3109
|
+
#createShiftAnimator() {
|
|
3110
|
+
const config = inject(VDND_ANIMATION_CONFIG, { optional: true });
|
|
3111
|
+
if (!config)
|
|
3112
|
+
return null;
|
|
3113
|
+
return new ShiftAnimator({
|
|
3114
|
+
config,
|
|
3115
|
+
injector: this.#injector,
|
|
3116
|
+
getScrollElement: () => this.#elementRef.nativeElement,
|
|
3117
|
+
getEntries: () => this.#shiftAnimationEntries(),
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Rendered item roots and the placeholder. `@for` never reuses a view for a different
|
|
3122
|
+
* track key, so each element is its own stable identity.
|
|
3123
|
+
*/
|
|
3124
|
+
*#shiftAnimationEntries() {
|
|
3125
|
+
const wrapper = this.#elementRef.nativeElement.querySelector('.vdnd-virtual-scroll-content-wrapper');
|
|
3126
|
+
if (!wrapper)
|
|
3127
|
+
return;
|
|
3128
|
+
for (const child of Array.from(wrapper.children)) {
|
|
3129
|
+
if (child instanceof HTMLElement) {
|
|
3130
|
+
yield [child, child];
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
2901
3133
|
}
|
|
2902
3134
|
/**
|
|
2903
3135
|
* Set up ResizeObserver for measuring individual item heights.
|
|
@@ -3461,6 +3693,11 @@ class DroppableDirective {
|
|
|
3461
3693
|
/** Emits when an item is dropped on this droppable */
|
|
3462
3694
|
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
3463
3695
|
drop = output();
|
|
3696
|
+
/**
|
|
3697
|
+
* Emits each time the placeholder moves within this droppable during a drag
|
|
3698
|
+
* (every item displacement) — e.g. to trigger haptic feedback.
|
|
3699
|
+
*/
|
|
3700
|
+
placeholderMove = output();
|
|
3464
3701
|
/** Whether this droppable is currently being targeted */
|
|
3465
3702
|
isActive = computed(() => {
|
|
3466
3703
|
const activeId = this.#dragState.activeDroppableId();
|
|
@@ -3473,6 +3710,27 @@ class DroppableDirective {
|
|
|
3473
3710
|
}
|
|
3474
3711
|
return this.#dragState.placeholderId();
|
|
3475
3712
|
}, ...(ngDevMode ? [{ debugName: "placeholderId" }] : []));
|
|
3713
|
+
/**
|
|
3714
|
+
* The placeholder's insertion index in this droppable (DropEvent convention),
|
|
3715
|
+
* or null when the placeholder is not here.
|
|
3716
|
+
*/
|
|
3717
|
+
#placeholderInsertionIndex = computed(() => {
|
|
3718
|
+
if (!this.isActive() || !this.#dragState.isDragging()) {
|
|
3719
|
+
return null;
|
|
3720
|
+
}
|
|
3721
|
+
const sourceIndex = this.#dragState.sourceIndex();
|
|
3722
|
+
return normalizeDropDestinationIndex({
|
|
3723
|
+
sourceIndex: sourceIndex ?? -1,
|
|
3724
|
+
placeholderIndex: this.#dragState.placeholderIndex(),
|
|
3725
|
+
// Without a known source index there is no hidden-source adjustment to undo
|
|
3726
|
+
sourceDroppableId: sourceIndex === null ? null : this.#dragState.sourceDroppableId(),
|
|
3727
|
+
activeDroppableId: this.vdndDroppable(),
|
|
3728
|
+
});
|
|
3729
|
+
}, ...(ngDevMode ? [{ debugName: "#placeholderInsertionIndex" }] : []));
|
|
3730
|
+
/** Last insertion index reported via placeholderMove (null when not here) */
|
|
3731
|
+
#lastPlaceholderIndex = null;
|
|
3732
|
+
/** Drag whose initial placeholder position has already been seen by this droppable */
|
|
3733
|
+
#placeholderTrackedDrag = null;
|
|
3476
3734
|
/** Track previous active state to detect the drag-end transition */
|
|
3477
3735
|
#wasActive = false;
|
|
3478
3736
|
/**
|
|
@@ -3502,6 +3760,10 @@ class DroppableDirective {
|
|
|
3502
3760
|
this.#positionCalculator.notifyCandidatesChanged(group);
|
|
3503
3761
|
}
|
|
3504
3762
|
});
|
|
3763
|
+
effect(() => {
|
|
3764
|
+
const currentIndex = this.#placeholderInsertionIndex();
|
|
3765
|
+
untracked(() => this.#handlePlaceholderMove(currentIndex));
|
|
3766
|
+
});
|
|
3505
3767
|
// React to state changes and handle drop events.
|
|
3506
3768
|
//
|
|
3507
3769
|
// Keyed only on the low-frequency fields (isActive/isDragging/draggedItem). The final
|
|
@@ -3551,6 +3813,37 @@ class DroppableDirective {
|
|
|
3551
3813
|
this.#positionCalculator.notifyCandidatesChanged(group);
|
|
3552
3814
|
}
|
|
3553
3815
|
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Emit placeholderMove when the placeholder moved within (or entered) this droppable.
|
|
3818
|
+
*/
|
|
3819
|
+
#handlePlaceholderMove(currentIndex) {
|
|
3820
|
+
let previousIndex = this.#lastPlaceholderIndex;
|
|
3821
|
+
this.#lastPlaceholderIndex = currentIndex;
|
|
3822
|
+
const draggedItem = this.#dragState.draggedItem();
|
|
3823
|
+
if (currentIndex === null || currentIndex === previousIndex || !draggedItem) {
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
// The drag's first placement in the source list starts from the item's own slot:
|
|
3827
|
+
// nothing is displaced when the placeholder simply takes the hidden item's place.
|
|
3828
|
+
const isFirstPlacement = this.#placeholderTrackedDrag !== draggedItem;
|
|
3829
|
+
this.#placeholderTrackedDrag = draggedItem;
|
|
3830
|
+
const sourceDroppableId = this.#dragState.sourceDroppableId();
|
|
3831
|
+
const sourceIndex = this.#dragState.sourceIndex();
|
|
3832
|
+
if (isFirstPlacement && sourceDroppableId === this.vdndDroppable() && sourceIndex !== null) {
|
|
3833
|
+
if (currentIndex === sourceIndex) {
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
previousIndex = sourceIndex;
|
|
3837
|
+
}
|
|
3838
|
+
this.placeholderMove.emit({
|
|
3839
|
+
draggableId: draggedItem.draggableId,
|
|
3840
|
+
sourceDroppableId: sourceDroppableId ?? '',
|
|
3841
|
+
droppableId: this.vdndDroppable(),
|
|
3842
|
+
previousIndex,
|
|
3843
|
+
currentIndex,
|
|
3844
|
+
data: draggedItem.data,
|
|
3845
|
+
});
|
|
3846
|
+
}
|
|
3554
3847
|
/**
|
|
3555
3848
|
* Handle a drop on this droppable.
|
|
3556
3849
|
*/
|
|
@@ -3654,7 +3947,7 @@ class DroppableDirective {
|
|
|
3654
3947
|
return this.#elementRef.nativeElement.scrollHeight;
|
|
3655
3948
|
}
|
|
3656
3949
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DroppableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3657
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: DroppableDirective, isStandalone: true, selector: "[vdndDroppable]", inputs: { vdndDroppable: { classPropertyName: "vdndDroppable", publicName: "vdndDroppable", isSignal: true, isRequired: true, transformFunction: null }, vdndDroppableGroup: { classPropertyName: "vdndDroppableGroup", publicName: "vdndDroppableGroup", isSignal: true, isRequired: false, transformFunction: null }, vdndDroppableData: { classPropertyName: "vdndDroppableData", publicName: "vdndDroppableData", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEnabled: { classPropertyName: "autoScrollEnabled", publicName: "autoScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollConfig: { classPropertyName: "autoScrollConfig", publicName: "autoScrollConfig", isSignal: true, isRequired: false, transformFunction: null }, constrainToContainer: { classPropertyName: "constrainToContainer", publicName: "constrainToContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drop: "drop" }, host: { properties: { "attr.data-droppable-id": "vdndDroppable()", "attr.data-droppable-group": "effectiveGroup()", "attr.data-droppable-disabled": "disabled() || null", "attr.data-constrain-to-container": "constrainToContainer() || null", "attr.aria-dropeffect": "\"move\"", "class.vdnd-droppable": "true", "class.vdnd-droppable-active": "isActive()", "class.vdnd-droppable-disabled": "disabled()" } }, ngImport: i0 });
|
|
3950
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: DroppableDirective, isStandalone: true, selector: "[vdndDroppable]", inputs: { vdndDroppable: { classPropertyName: "vdndDroppable", publicName: "vdndDroppable", isSignal: true, isRequired: true, transformFunction: null }, vdndDroppableGroup: { classPropertyName: "vdndDroppableGroup", publicName: "vdndDroppableGroup", isSignal: true, isRequired: false, transformFunction: null }, vdndDroppableData: { classPropertyName: "vdndDroppableData", publicName: "vdndDroppableData", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEnabled: { classPropertyName: "autoScrollEnabled", publicName: "autoScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollConfig: { classPropertyName: "autoScrollConfig", publicName: "autoScrollConfig", isSignal: true, isRequired: false, transformFunction: null }, constrainToContainer: { classPropertyName: "constrainToContainer", publicName: "constrainToContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drop: "drop", placeholderMove: "placeholderMove" }, host: { properties: { "attr.data-droppable-id": "vdndDroppable()", "attr.data-droppable-group": "effectiveGroup()", "attr.data-droppable-disabled": "disabled() || null", "attr.data-constrain-to-container": "constrainToContainer() || null", "attr.aria-dropeffect": "\"move\"", "class.vdnd-droppable": "true", "class.vdnd-droppable-active": "isActive()", "class.vdnd-droppable-disabled": "disabled()" } }, ngImport: i0 });
|
|
3658
3951
|
}
|
|
3659
3952
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DroppableDirective, decorators: [{
|
|
3660
3953
|
type: Directive,
|
|
@@ -3671,7 +3964,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
3671
3964
|
'[class.vdnd-droppable-disabled]': 'disabled()',
|
|
3672
3965
|
},
|
|
3673
3966
|
}]
|
|
3674
|
-
}], ctorParameters: () => [], propDecorators: { vdndDroppable: [{ type: i0.Input, args: [{ isSignal: true, alias: "vdndDroppable", required: true }] }], vdndDroppableGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "vdndDroppableGroup", required: false }] }], vdndDroppableData: [{ type: i0.Input, args: [{ isSignal: true, alias: "vdndDroppableData", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], autoScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollEnabled", required: false }] }], autoScrollConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollConfig", required: false }] }], constrainToContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "constrainToContainer", required: false }] }], drop: [{ type: i0.Output, args: ["drop"] }] } });
|
|
3967
|
+
}], ctorParameters: () => [], propDecorators: { vdndDroppable: [{ type: i0.Input, args: [{ isSignal: true, alias: "vdndDroppable", required: true }] }], vdndDroppableGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "vdndDroppableGroup", required: false }] }], vdndDroppableData: [{ type: i0.Input, args: [{ isSignal: true, alias: "vdndDroppableData", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], autoScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollEnabled", required: false }] }], autoScrollConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollConfig", required: false }] }], constrainToContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "constrainToContainer", required: false }] }], drop: [{ type: i0.Output, args: ["drop"] }], placeholderMove: [{ type: i0.Output, args: ["placeholderMove"] }] } });
|
|
3675
3968
|
|
|
3676
3969
|
/**
|
|
3677
3970
|
* A high-level component that combines droppable, virtual scroll, and placeholder
|
|
@@ -3760,8 +4053,10 @@ class VirtualSortableListComponent {
|
|
|
3760
4053
|
/** Emits when an item is dropped on this list */
|
|
3761
4054
|
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
3762
4055
|
drop = output();
|
|
4056
|
+
/** Emits each time the placeholder moves within this list (every item displacement) */
|
|
4057
|
+
placeholderMove = output();
|
|
3763
4058
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VirtualSortableListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3764
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: VirtualSortableListComponent, isStandalone: true, selector: "vdnd-sortable-list", inputs: { droppableId: { classPropertyName: "droppableId", publicName: "droppableId", isSignal: true, isRequired: true, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: true, transformFunction: null }, dynamicItemHeight: { classPropertyName: "dynamicItemHeight", publicName: "dynamicItemHeight", isSignal: true, isRequired: false, transformFunction: null }, itemIdFn: { classPropertyName: "itemIdFn", publicName: "itemIdFn", isSignal: true, isRequired: true, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: true, transformFunction: null }, trackByFn: { classPropertyName: "trackByFn", publicName: "trackByFn", isSignal: true, isRequired: false, transformFunction: null }, droppableData: { classPropertyName: "droppableData", publicName: "droppableData", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, containerHeight: { classPropertyName: "containerHeight", publicName: "containerHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEnabled: { classPropertyName: "autoScrollEnabled", publicName: "autoScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollConfig: { classPropertyName: "autoScrollConfig", publicName: "autoScrollConfig", isSignal: true, isRequired: false, transformFunction: null }, constrainToContainer: { classPropertyName: "constrainToContainer", publicName: "constrainToContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drop: "drop" }, host: { classAttribute: "vdnd-sortable-list" }, ngImport: i0, template: `
|
|
4059
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: VirtualSortableListComponent, isStandalone: true, selector: "vdnd-sortable-list", inputs: { droppableId: { classPropertyName: "droppableId", publicName: "droppableId", isSignal: true, isRequired: true, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: true, transformFunction: null }, dynamicItemHeight: { classPropertyName: "dynamicItemHeight", publicName: "dynamicItemHeight", isSignal: true, isRequired: false, transformFunction: null }, itemIdFn: { classPropertyName: "itemIdFn", publicName: "itemIdFn", isSignal: true, isRequired: true, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: true, transformFunction: null }, trackByFn: { classPropertyName: "trackByFn", publicName: "trackByFn", isSignal: true, isRequired: false, transformFunction: null }, droppableData: { classPropertyName: "droppableData", publicName: "droppableData", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, containerHeight: { classPropertyName: "containerHeight", publicName: "containerHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEnabled: { classPropertyName: "autoScrollEnabled", publicName: "autoScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollConfig: { classPropertyName: "autoScrollConfig", publicName: "autoScrollConfig", isSignal: true, isRequired: false, transformFunction: null }, constrainToContainer: { classPropertyName: "constrainToContainer", publicName: "constrainToContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drop: "drop", placeholderMove: "placeholderMove" }, host: { classAttribute: "vdnd-sortable-list" }, ngImport: i0, template: `
|
|
3765
4060
|
<div
|
|
3766
4061
|
class="vdnd-sortable-list-droppable"
|
|
3767
4062
|
[vdndDroppable]="droppableId()"
|
|
@@ -3770,6 +4065,7 @@ class VirtualSortableListComponent {
|
|
|
3770
4065
|
[disabled]="disabled()"
|
|
3771
4066
|
[constrainToContainer]="constrainToContainer()"
|
|
3772
4067
|
(drop)="drop.emit($event)"
|
|
4068
|
+
(placeholderMove)="placeholderMove.emit($event)"
|
|
3773
4069
|
>
|
|
3774
4070
|
<vdnd-virtual-scroll
|
|
3775
4071
|
[items]="items()"
|
|
@@ -3787,7 +4083,7 @@ class VirtualSortableListComponent {
|
|
|
3787
4083
|
>
|
|
3788
4084
|
</vdnd-virtual-scroll>
|
|
3789
4085
|
</div>
|
|
3790
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: VirtualScrollContainerComponent, selector: "vdnd-virtual-scroll", inputs: ["itemTemplate", "scrollContainerId", "autoScrollEnabled", "autoScrollConfig", "items", "itemHeight", "dynamicItemHeight", "containerHeight", "overscan", "stickyItemIds", "itemIdFn", "trackByFn", "droppableId", "autoStickyDraggedItem"] }, { kind: "directive", type: DroppableDirective, selector: "[vdndDroppable]", inputs: ["vdndDroppable", "vdndDroppableGroup", "vdndDroppableData", "disabled", "autoScrollEnabled", "autoScrollConfig", "constrainToContainer"], outputs: ["drop"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4086
|
+
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: VirtualScrollContainerComponent, selector: "vdnd-virtual-scroll", inputs: ["itemTemplate", "scrollContainerId", "autoScrollEnabled", "autoScrollConfig", "items", "itemHeight", "dynamicItemHeight", "containerHeight", "overscan", "stickyItemIds", "itemIdFn", "trackByFn", "droppableId", "autoStickyDraggedItem"] }, { kind: "directive", type: DroppableDirective, selector: "[vdndDroppable]", inputs: ["vdndDroppable", "vdndDroppableGroup", "vdndDroppableData", "disabled", "autoScrollEnabled", "autoScrollConfig", "constrainToContainer"], outputs: ["drop", "placeholderMove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3791
4087
|
}
|
|
3792
4088
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VirtualSortableListComponent, decorators: [{
|
|
3793
4089
|
type: Component,
|
|
@@ -3802,6 +4098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
3802
4098
|
[disabled]="disabled()"
|
|
3803
4099
|
[constrainToContainer]="constrainToContainer()"
|
|
3804
4100
|
(drop)="drop.emit($event)"
|
|
4101
|
+
(placeholderMove)="placeholderMove.emit($event)"
|
|
3805
4102
|
>
|
|
3806
4103
|
<vdnd-virtual-scroll
|
|
3807
4104
|
[items]="items()"
|
|
@@ -3820,7 +4117,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
3820
4117
|
</vdnd-virtual-scroll>
|
|
3821
4118
|
</div>
|
|
3822
4119
|
`, styles: [":host{display:block}\n"] }]
|
|
3823
|
-
}], propDecorators: { droppableId: [{ type: i0.Input, args: [{ isSignal: true, alias: "droppableId", required: true }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: true }] }], dynamicItemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamicItemHeight", required: false }] }], itemIdFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemIdFn", required: true }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: true }] }], trackByFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByFn", required: false }] }], droppableData: [{ type: i0.Input, args: [{ isSignal: true, alias: "droppableData", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], containerHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], autoScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollEnabled", required: false }] }], autoScrollConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollConfig", required: false }] }], constrainToContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "constrainToContainer", required: false }] }], drop: [{ type: i0.Output, args: ["drop"] }] } });
|
|
4120
|
+
}], propDecorators: { droppableId: [{ type: i0.Input, args: [{ isSignal: true, alias: "droppableId", required: true }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: true }] }], dynamicItemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamicItemHeight", required: false }] }], itemIdFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemIdFn", required: true }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: true }] }], trackByFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByFn", required: false }] }], droppableData: [{ type: i0.Input, args: [{ isSignal: true, alias: "droppableData", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], containerHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], autoScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollEnabled", required: false }] }], autoScrollConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollConfig", required: false }] }], constrainToContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "constrainToContainer", required: false }] }], drop: [{ type: i0.Output, args: ["drop"] }], placeholderMove: [{ type: i0.Output, args: ["placeholderMove"] }] } });
|
|
3824
4121
|
|
|
3825
4122
|
/**
|
|
3826
4123
|
* A virtual viewport component that provides efficient wrapper-based positioning
|
|
@@ -5537,6 +5834,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
5537
5834
|
}]
|
|
5538
5835
|
}], ctorParameters: () => [], propDecorators: { scrollContainerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollContainerId", required: false }] }], autoScrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollEnabled", required: false }] }], autoScrollConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScrollConfig", required: false }] }] } });
|
|
5539
5836
|
|
|
5837
|
+
/** Render-queue key of the placeholder entry */
|
|
5838
|
+
const PLACEHOLDER_KEY = '__placeholder__';
|
|
5540
5839
|
/**
|
|
5541
5840
|
* A structural directive for virtual scrolling within custom scroll containers.
|
|
5542
5841
|
* Provides maximum flexibility for advanced use cases where the component wrapper
|
|
@@ -5588,6 +5887,8 @@ class VirtualForDirective {
|
|
|
5588
5887
|
* When present, droppableId is inherited automatically.
|
|
5589
5888
|
*/
|
|
5590
5889
|
#droppable = inject(DroppableDirective, { optional: true });
|
|
5890
|
+
/** Slides items displaced by the placeholder (only when VDND_ANIMATION_CONFIG is provided) */
|
|
5891
|
+
#shiftAnimator = this.#createShiftAnimator();
|
|
5591
5892
|
/** Whether we're inside a viewport component (use wrapper positioning) */
|
|
5592
5893
|
#useViewportPositioning = this.#viewport !== null;
|
|
5593
5894
|
/** Pool of views for reuse */
|
|
@@ -5787,6 +6088,31 @@ class VirtualForDirective {
|
|
|
5787
6088
|
this.#placeholder?.remove();
|
|
5788
6089
|
// Clean up ResizeObserver
|
|
5789
6090
|
this.#resizeObserver?.disconnect();
|
|
6091
|
+
this.#shiftAnimator?.cancelAll();
|
|
6092
|
+
}
|
|
6093
|
+
#createShiftAnimator() {
|
|
6094
|
+
const config = inject(VDND_ANIMATION_CONFIG, { optional: true });
|
|
6095
|
+
if (!config)
|
|
6096
|
+
return null;
|
|
6097
|
+
return new ShiftAnimator({
|
|
6098
|
+
config,
|
|
6099
|
+
injector: this.#injector,
|
|
6100
|
+
getScrollElement: () => this.#scrollContainer.nativeElement,
|
|
6101
|
+
getEntries: () => this.#shiftAnimationEntries(),
|
|
6102
|
+
});
|
|
6103
|
+
}
|
|
6104
|
+
/** Rendered item root elements (keyed by trackBy key) plus the placeholder. */
|
|
6105
|
+
*#shiftAnimationEntries() {
|
|
6106
|
+
for (const [key, view] of this.#activeViews) {
|
|
6107
|
+
for (const node of view.rootNodes) {
|
|
6108
|
+
if (node instanceof HTMLElement) {
|
|
6109
|
+
yield [key, node];
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
if (this.#placeholder && this.#placeholderInDom) {
|
|
6114
|
+
yield [PLACEHOLDER_KEY, this.#placeholder];
|
|
6115
|
+
}
|
|
5790
6116
|
}
|
|
5791
6117
|
/**
|
|
5792
6118
|
* Create the placeholder element for drag operations.
|
|
@@ -5860,6 +6186,19 @@ class VirtualForDirective {
|
|
|
5860
6186
|
}
|
|
5861
6187
|
}
|
|
5862
6188
|
}
|
|
6189
|
+
/**
|
|
6190
|
+
* Stop any shift animation on a view about to be pooled — a recycled view must not
|
|
6191
|
+
* carry an in-flight offset over to the item it renders next.
|
|
6192
|
+
*/
|
|
6193
|
+
#cancelShiftAnimation(view) {
|
|
6194
|
+
if (!this.#shiftAnimator)
|
|
6195
|
+
return;
|
|
6196
|
+
for (const node of view.rootNodes) {
|
|
6197
|
+
if (node instanceof HTMLElement) {
|
|
6198
|
+
this.#shiftAnimator.cancel(node);
|
|
6199
|
+
}
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
5863
6202
|
/**
|
|
5864
6203
|
* Stop observing an element's root nodes.
|
|
5865
6204
|
*/
|
|
@@ -5892,6 +6231,8 @@ class VirtualForDirective {
|
|
|
5892
6231
|
draggedIndex >= 0 &&
|
|
5893
6232
|
isSourceList &&
|
|
5894
6233
|
draggedIndex < items.length;
|
|
6234
|
+
// Snapshot positions before the DOM changes so displaced items can slide
|
|
6235
|
+
this.#shiftAnimator?.beforeUpdate(this.#dragState.isDragging(), placeholderIndex);
|
|
5895
6236
|
// Notify viewport of render start index for wrapper positioning
|
|
5896
6237
|
this.#notifyViewportRenderStart(start);
|
|
5897
6238
|
// 1. Build the list of items to render
|
|
@@ -5937,7 +6278,7 @@ class VirtualForDirective {
|
|
|
5937
6278
|
if (showPlaceholder && placeholderIndex === i && !placeholderInserted) {
|
|
5938
6279
|
itemsToRender.push({
|
|
5939
6280
|
type: 'placeholder',
|
|
5940
|
-
key:
|
|
6281
|
+
key: PLACEHOLDER_KEY,
|
|
5941
6282
|
context: null,
|
|
5942
6283
|
visualIndex: placeholderIndex,
|
|
5943
6284
|
});
|
|
@@ -5961,7 +6302,7 @@ class VirtualForDirective {
|
|
|
5961
6302
|
if (showPlaceholder && placeholderIndex >= items.length && !placeholderInserted) {
|
|
5962
6303
|
itemsToRender.push({
|
|
5963
6304
|
type: 'placeholder',
|
|
5964
|
-
key:
|
|
6305
|
+
key: PLACEHOLDER_KEY,
|
|
5965
6306
|
context: null,
|
|
5966
6307
|
visualIndex: placeholderIndex,
|
|
5967
6308
|
});
|
|
@@ -6004,6 +6345,7 @@ class VirtualForDirective {
|
|
|
6004
6345
|
this.#viewContainer.detach(index);
|
|
6005
6346
|
}
|
|
6006
6347
|
this.#unobserveViewElements(view);
|
|
6348
|
+
this.#cancelShiftAnimation(view);
|
|
6007
6349
|
this.#viewPool.push(view);
|
|
6008
6350
|
this.#activeViews.delete(key);
|
|
6009
6351
|
}
|
|
@@ -6398,5 +6740,5 @@ function removeAt(list, index) {
|
|
|
6398
6740
|
* Generated bundle index. Do not edit.
|
|
6399
6741
|
*/
|
|
6400
6742
|
|
|
6401
|
-
export { AutoScrollService, ContentHeaderDirective, DragPlaceholderComponent, DragPreviewComponent, DragStateService, DraggableDirective, DroppableDirective, DroppableGroupDirective, DynamicHeightStrategy, END_OF_LIST, ElementCloneService, FixedHeightStrategy, INITIAL_DRAG_STATE, KeyboardDragService, OverlayContainerService, PlaceholderComponent, PositionCalculatorService, ScrollableDirective, VDND_GROUP_TOKEN, VDND_SCROLL_CONTAINER, VDND_VIRTUAL_VIEWPORT, VirtualContentComponent, VirtualForDirective, VirtualScrollContainerComponent, VirtualSortableListComponent, VirtualViewportComponent, applyMove, insertAt, isNoOpDrop, moveItem, removeAt, reorderItems };
|
|
6743
|
+
export { AutoScrollService, ContentHeaderDirective, DragPlaceholderComponent, DragPreviewComponent, DragStateService, DraggableDirective, DroppableDirective, DroppableGroupDirective, DynamicHeightStrategy, END_OF_LIST, ElementCloneService, FixedHeightStrategy, INITIAL_DRAG_STATE, KeyboardDragService, OverlayContainerService, PlaceholderComponent, PositionCalculatorService, ScrollableDirective, VDND_ANIMATION_CONFIG, VDND_GROUP_TOKEN, VDND_SCROLL_CONTAINER, VDND_VIRTUAL_VIEWPORT, VirtualContentComponent, VirtualForDirective, VirtualScrollContainerComponent, VirtualSortableListComponent, VirtualViewportComponent, applyMove, insertAt, isNoOpDrop, moveItem, removeAt, reorderItems };
|
|
6402
6744
|
//# sourceMappingURL=ngx-virtual-dnd.mjs.map
|