UNPKG

igniteui-angular

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

1,394 lines (1,388 loc) 394 kB
import * as i0 from '@angular/core'; import { inject, ElementRef, EventEmitter, booleanAttribute, HostListener, HostBinding, Input, Output, Directive, Renderer2, ChangeDetectorRef, DestroyRef, ViewChild, DOCUMENT, ViewContainerRef, NgZone, RendererStyleFlags2, ContentChildren, Pipe, Component, PLATFORM_ID, Injectable, TemplateRef, IterableDiffers, EnvironmentInjector, createComponent, LOCALE_ID, forwardRef, NgModule } from '@angular/core'; import { PlatformUtil, THEME_TOKEN, getComponentTheme, EDITOR_PROVIDER, resizeObservable, getResizeObserver, compareMaps, IgxOverlayService, IgxNavigationService, AbsoluteScrollStrategy, ConnectedPositioningStrategy, VerticalAlignment, HorizontalAlignment, AutoPositionStrategy, first as first$1, DatePart, isDate, DateTimeUtil } from 'igniteui-angular/core'; import { animationFrameScheduler, Subject, noop, fromEvent, interval, takeUntil as takeUntil$1 } from 'rxjs'; import { NgControl, Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { takeUntil, throttle, throttleTime, filter, first } from 'rxjs/operators'; import * as i1 from '@angular/common'; import { DOCUMENT as DOCUMENT$1, isPlatformBrowser, CommonModule } from '@angular/common'; import { AnimationBuilder, style, animate, useAnimation } from '@angular/animations'; import { IgxIconComponent } from 'igniteui-angular/icon'; import { fadeOut, scaleInCenter } from 'igniteui-angular/animations'; const IgxBaseButtonType = { Flat: 'flat', Contained: 'contained', Outlined: 'outlined' }; class IgxButtonBaseDirective { /** * @hidden * @internal */ onClick(ev) { this.buttonClick.emit(ev); this.focused = false; } /** * @hidden * @internal */ onBlur() { this.focused = false; } /** * @hidden * @internal */ get disabledAttribute() { return this.disabled || null; } constructor() { this._platformUtil = inject(PlatformUtil); this.element = inject(ElementRef); this._viewInit = false; /** * Emitted when the button is clicked. */ this.buttonClick = new EventEmitter(); /** * Sets/gets the `role` attribute. * * @example * ```typescript * this.button.role = 'navbutton'; * let buttonRole = this.button.role; * ``` */ this.role = 'button'; /** * Sets/gets whether the button component is on focus. * Default value is `false`. * ```typescript * this.button.focus = true; * ``` * ```typescript * let isFocused = this.button.focused; * ``` */ this.focused = false; /** * Enables/disables the button. * * @example * ```html * <button igxButton="fab" disabled></button> * ``` */ this.disabled = false; // In browser, set via native API for immediate effect (no-op on server). // In SSR there is no paint, so there’s no visual rendering or transitions to suppress. // Fix style flickering https://github.com/IgniteUI/igniteui-angular/issues/14759 if (this._platformUtil.isBrowser) { this.element.nativeElement.style.setProperty('--_init-transition', '0s'); } } ngAfterViewInit() { if (this._platformUtil.isBrowser && !this._viewInit) { this._viewInit = true; this._animationScheduler = animationFrameScheduler.schedule(() => { this.element.nativeElement.style.removeProperty('--_init-transition'); }); } } ngOnDestroy() { this._animationScheduler.unsubscribe(); } /** * @hidden * @internal */ updateOnKeyUp(event) { if (event.key === "Tab") { this.focused = true; } } /** * Returns the underlying DOM element. */ get nativeElement() { return this.element.nativeElement; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxButtonBaseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: IgxButtonBaseDirective, isStandalone: true, inputs: { disabled: ["disabled", "disabled", booleanAttribute] }, outputs: { buttonClick: "buttonClick" }, host: { listeners: { "click": "onClick($event)", "blur": "onBlur()", "keyup": "updateOnKeyUp($event)" }, properties: { "attr.role": "this.role", "class.igx-button--focused": "this.focused", "class.igx-button--disabled": "this.disabled", "attr.disabled": "this.disabledAttribute" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxButtonBaseDirective, decorators: [{ type: Directive }], ctorParameters: () => [], propDecorators: { buttonClick: [{ type: Output }], role: [{ type: HostBinding, args: ['attr.role'] }], onClick: [{ type: HostListener, args: ['click', ['$event']] }], onBlur: [{ type: HostListener, args: ['blur'] }], focused: [{ type: HostBinding, args: ['class.igx-button--focused'] }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }, { type: HostBinding, args: ['class.igx-button--disabled'] }], disabledAttribute: [{ type: HostBinding, args: ['attr.disabled'] }], updateOnKeyUp: [{ type: HostListener, args: ['keyup', ['$event']] }] } }); const IgxButtonType = { ...IgxBaseButtonType, FAB: 'fab' }; /** * The Button directive provides the Ignite UI Button functionality to every component that's intended to be used as a button. * * @igxModule IgxButtonModule * * @igxParent Data Entry & Display * * @igxTheme igx-button-theme * * @igxKeywords button, span, div, click * * @remarks * The Ignite UI Button directive is intended to be used by any button, span or div and turn it into a fully functional button. * * @example * ```html * <button type="button" igxButton="outlined">A Button</button> * ``` */ class IgxButtonDirective extends IgxButtonBaseDirective { emitSelected() { this.buttonSelected.emit({ button: this }); } /** * Gets or sets whether the button is selected. * Mainly used in the IgxButtonGroup component and it will have no effect if set separately. * * @example * ```html * <button type="button" igxButton="flat" [selected]="button.selected"></button> * ``` */ set selected(value) { if (this._selected !== value) { this._selected = value; this._renderer.setAttribute(this.nativeElement, 'data-selected', value.toString()); } } get selected() { return this._selected; } constructor() { super(); this._renderer = inject(Renderer2); /** * Called when the button is selected. */ this.buttonSelected = new EventEmitter(); /** * @hidden * @internal */ this._cssClass = 'igx-button'; /** * @hidden * @internal */ this._selected = false; } /** * Sets the type of the button. * * @example * ```html * <button type="button" igxButton="outlined"></button> * ``` */ set type(type) { const t = type ? type : IgxButtonType.Flat; if (this._type !== t) { this._type = t; } } /** * Sets the `aria-label` attribute. * * @example * ```html * <button type="button" igxButton="flat" igxLabel="Label"></button> * ``` */ set label(value) { this._label = value || this._label; this._renderer.setAttribute(this.nativeElement, 'aria-label', this._label); } /** * @hidden * @internal */ get flat() { return this._type === IgxButtonType.Flat; } /** * @hidden * @internal */ get contained() { return this._type === IgxButtonType.Contained; } /** * @hidden * @internal */ get outlined() { return this._type === IgxButtonType.Outlined; } /** * @hidden * @internal */ get fab() { return this._type === IgxButtonType.FAB; } /** * @hidden * @internal */ select() { this.selected = true; } /** * @hidden * @internal */ deselect() { this.selected = false; this.focused = false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: IgxButtonDirective, isStandalone: true, selector: "[igxButton]", inputs: { selected: ["selected", "selected", booleanAttribute], type: ["igxButton", "type"], label: ["igxLabel", "label"] }, outputs: { buttonSelected: "buttonSelected" }, host: { listeners: { "click": "emitSelected()" }, properties: { "class.igx-button": "this._cssClass", "class.igx-button--flat": "this.flat", "class.igx-button--contained": "this.contained", "class.igx-button--outlined": "this.outlined", "class.igx-button--fab": "this.fab" } }, usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxButtonDirective, decorators: [{ type: Directive, args: [{ selector: '[igxButton]', standalone: true }] }], ctorParameters: () => [], propDecorators: { buttonSelected: [{ type: Output }], _cssClass: [{ type: HostBinding, args: ['class.igx-button'] }], emitSelected: [{ type: HostListener, args: ['click'] }], selected: [{ type: Input, args: [{ transform: booleanAttribute }] }], type: [{ type: Input, args: ['igxButton'] }], label: [{ type: Input, args: ['igxLabel'] }], flat: [{ type: HostBinding, args: ['class.igx-button--flat'] }], contained: [{ type: HostBinding, args: ['class.igx-button--contained'] }], outlined: [{ type: HostBinding, args: ['class.igx-button--outlined'] }], fab: [{ type: HostBinding, args: ['class.igx-button--fab'] }] } }); const LabelPosition = { BEFORE: 'before', AFTER: 'after' }; let nextId = 0; class CheckboxBaseDirective { get checked() { return this._checked; } set checked(value) { if (this._checked !== value) { this._checked = value; this._onChangeCallback(this._checked); } } /** * Returns reference to the `nativeElement` of the igx-checkbox/igx-switch. * * @example * ```typescript * let nativeElement = this.component.nativeElement; * ``` */ get nativeElement() { return this.nativeInput.nativeElement; } constructor() { this.cdr = inject(ChangeDetectorRef); this.themeToken = inject(THEME_TOKEN); this.ngControl = inject(NgControl, { optional: true, self: true }); /** * An event that is emitted after the checkbox state is changed. * Provides references to the `IgxCheckboxComponent` and the `checked` property as event arguments. */ // eslint-disable-next-line @angular-eslint/no-output-native this.change = new EventEmitter(); /** * @hidden * @internal */ this.destroy$ = new Subject(); /** * Sets/gets the `id` of the checkbox component. * If not set, the `id` of the first checkbox component will be `"igx-checkbox-0"`. * * @example * ```html * <igx-checkbox id="my-first-checkbox"></igx-checkbox> * ``` * ```typescript * let checkboxId = this.checkbox.id; * ``` */ this.id = `igx-checkbox-${nextId++}`; /** * Sets/gets the id of the `label` element. * If not set, the id of the `label` in the first checkbox component will be `"igx-checkbox-0-label"`. * * @example * ```html * <igx-checkbox labelId="Label1"></igx-checkbox> * ``` * ```typescript * let labelId = this.component.labelId; * ``` */ this.labelId = `${this.id}-label`; /** * Sets/gets the value of the `tabindex` attribute. * * @example * ```html * <igx-checkbox [tabindex]="1"></igx-checkbox> * ``` * ```typescript * let tabIndex = this.checkbox.tabindex; * ``` */ this.tabindex = null; /** * Sets/gets the position of the `label`. * If not set, the `labelPosition` will have value `"after"`. * * @example * ```html * <igx-checkbox labelPosition="before"></igx-checkbox> * ``` * ```typescript * let labelPosition = this.checkbox.labelPosition; * ``` */ this.labelPosition = LabelPosition.AFTER; /** * Enables/Disables the ripple effect. * If not set, `disableRipple` will have value `false`. * * @example * ```html * <igx-checkbox [disableRipple]="true"></igx-checkbox> * ``` * ```typescript * let isRippleDisabled = this.checkbox.desableRipple; * ``` */ this.disableRipple = false; /** * Sets/gets the `aria-labelledby` attribute. * If not set, the `aria-labelledby` will be equal to the value of `labelId` attribute. * * @example * ```html * <igx-checkbox aria-labelledby="Checkbox1"></igx-checkbox> * ``` * ```typescript * let ariaLabelledBy = this.checkbox.ariaLabelledBy; * ``` */ this.ariaLabelledBy = this.labelId; /** * Sets/gets the value of the `aria-label` attribute. * * @example * ```html * <igx-checkbox aria-label="Checkbox1"></igx-checkbox> * ``` * ```typescript * let ariaLabel = this.checkbox.ariaLabel; * ``` */ this.ariaLabel = null; /** * @hidden * @internal */ this.inputId = `${this.id}-input`; /** * @hidden */ this._onChangeCallback = noop; /** * @hidden */ this._onTouchedCallback = noop; /** * @hidden * @internal */ this._checked = false; /** * @hidden * @internal */ this._required = false; this.elRef = inject(ElementRef); this.destroyRef = inject(DestroyRef); if (this.ngControl !== null) { this.ngControl.valueAccessor = this; } this.theme = this.themeToken.theme; const themeChange = this.themeToken.onChange((theme) => { if (this.theme !== theme) { this.theme = theme; this.cdr.detectChanges(); } }); this.destroyRef.onDestroy(() => themeChange.unsubscribe()); } /** * Sets/gets whether the checkbox is required. * If not set, `required` will have value `false`. * * @example * ```html * <igx-checkbox required></igx-checkbox> * ``` * ```typescript * let isRequired = this.checkbox.required; * ``` */ get required() { return this._required || this.nativeElement.hasAttribute('required'); } set required(value) { if (!value) { this.nativeElement.removeAttribute('required'); } this._required = value; } /** * @hidden * @internal */ ngAfterViewInit() { if (this.ngControl) { this.ngControl.statusChanges .pipe(takeUntil(this.destroy$)) .subscribe(this.updateValidityState.bind(this)); if (this.ngControl.control.validator || this.ngControl.control.asyncValidator) { this._required = this.ngControl?.control?.hasValidator(Validators.required); this.cdr.detectChanges(); } } this.setComponentTheme(); } setComponentTheme() { if (!this.themeToken.preferToken) { const theme = getComponentTheme(this.elRef.nativeElement); if (theme && theme !== this.theme) { this.theme = theme; this.cdr.markForCheck(); } } } /** @hidden @internal */ onKeyUp(event) { event.stopPropagation(); this.focused = true; } /** @hidden @internal */ _onCheckboxClick(event) { // Since the original checkbox is hidden and the label // is used for styling and to change the checked state of the checkbox, // we need to prevent the checkbox click event from bubbling up // as it gets triggered on label click // NOTE: The above is no longer valid, as the native checkbox is not labeled // by the SVG anymore. if (this.disabled || this.readonly) { // readonly prevents the component from changing state (see toggle() method). // However, the native checkbox can still be activated through user interaction (focus + space, label click) // Prevent the native change so the input remains in sync event.preventDefault(); return; } this.nativeElement.focus(); this.indeterminate = false; this.checked = !this.checked; this.updateValidityState(); // K.D. March 23, 2021 Emitting on click and not on the setter because otherwise every component // bound on change would have to perform self checks for weather the value has changed because // of the initial set on initialization this.change.emit({ checked: this.checked, value: this.value, owner: this, }); } /** * @hidden * @internal */ get ariaChecked() { if (this.indeterminate) { return 'mixed'; } else { return this.checked; } } /** @hidden @internal */ _onCheckboxChange(event) { // We have to stop the original checkbox change event // from bubbling up since we emit our own change event event.stopPropagation(); } /** @hidden @internal */ onBlur() { this.focused = false; this._onTouchedCallback(); this.updateValidityState(); } /** @hidden @internal */ writeValue(value) { this._checked = value; } /** @hidden @internal */ get labelClass() { switch (this.labelPosition) { case LabelPosition.BEFORE: return `${this.cssClass}__label--before`; case LabelPosition.AFTER: default: return `${this.cssClass}__label`; } } /** @hidden @internal */ registerOnChange(fn) { this._onChangeCallback = fn; } /** @hidden @internal */ registerOnTouched(fn) { this._onTouchedCallback = fn; } /** @hidden @internal */ setDisabledState(isDisabled) { this.disabled = isDisabled; } /** @hidden @internal */ getEditElement() { return this.nativeInput.nativeElement; } /** * @hidden * @internal */ updateValidityState() { if (this.ngControl) { if (!this.disabled && !this.readonly && (this.ngControl.control.touched || this.ngControl.control.dirty)) { // the control is not disabled and is touched or dirty this.invalid = this.ngControl.invalid; } else { // if the control is untouched, pristine, or disabled, its state is initial. This is when the user did not interact // with the checkbox or when the form/control is reset this.invalid = false; } } else { this.checkNativeValidity(); } } /** * A function to assign a native validity property of a checkbox. * This should be used when there's no ngControl * * @hidden * @internal */ checkNativeValidity() { if (!this.disabled && this._required && !this.checked && !this.readonly) { this.invalid = true; } else { this.invalid = false; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: CheckboxBaseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: CheckboxBaseDirective, isStandalone: true, inputs: { checked: ["checked", "checked", booleanAttribute], id: "id", labelId: "labelId", value: "value", name: "name", tabindex: "tabindex", labelPosition: "labelPosition", disableRipple: ["disableRipple", "disableRipple", booleanAttribute], ariaLabelledBy: ["aria-labelledby", "ariaLabelledBy"], ariaLabel: ["aria-label", "ariaLabel"], required: ["required", "required", booleanAttribute] }, outputs: { change: "change" }, host: { listeners: { "keyup": "onKeyUp($event)", "click": "_onCheckboxClick($event)", "blur": "onBlur()" }, properties: { "attr.id": "this.id" } }, viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["checkbox"], descendants: true, static: true }, { propertyName: "nativeLabel", first: true, predicate: ["label"], descendants: true, static: true }, { propertyName: "placeholderLabel", first: true, predicate: ["placeholderLabel"], descendants: true, static: true }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: CheckboxBaseDirective, decorators: [{ type: Directive }], ctorParameters: () => [], propDecorators: { change: [{ type: Output }], nativeInput: [{ type: ViewChild, args: ['checkbox', { static: true }] }], nativeLabel: [{ type: ViewChild, args: ['label', { static: true }] }], checked: [{ type: Input, args: [{ transform: booleanAttribute }] }], placeholderLabel: [{ type: ViewChild, args: ['placeholderLabel', { static: true }] }], id: [{ type: HostBinding, args: ['attr.id'] }, { type: Input }], labelId: [{ type: Input }], value: [{ type: Input }], name: [{ type: Input }], tabindex: [{ type: Input }], labelPosition: [{ type: Input }], disableRipple: [{ type: Input, args: [{ transform: booleanAttribute }] }], ariaLabelledBy: [{ type: Input, args: ['aria-labelledby'] }], ariaLabel: [{ type: Input, args: ['aria-label'] }], required: [{ type: Input, args: [{ transform: booleanAttribute }] }], onKeyUp: [{ type: HostListener, args: ['keyup', ['$event']] }], _onCheckboxClick: [{ type: HostListener, args: ['click', ['$event']] }], onBlur: [{ type: HostListener, args: ['blur'] }] } }); const IgxDividerType = { SOLID: 'solid', DASHED: 'dashed' }; let NEXT_ID$1 = 0; class IgxDividerDirective { constructor() { /** * Sets/gets the `id` of the divider. * If not set, `id` will have value `"igx-divider-0"`; * ```html * <igx-divider id="my-divider"></igx-divider> * ``` * ```typescript * let dividerId = this.divider.id; * ``` */ this.id = `igx-divider-${NEXT_ID$1++}`; /** * Sets the value of `role` attribute. * If not the default value of `separator` will be used. */ this.role = 'separator'; /** * Sets the type of the divider. The default value * is `default`. The divider can also be `dashed`; * ```html * <igx-divider type="dashed"></igx-divider> * ``` */ this.type = IgxDividerType.SOLID; /** * If set to `true` and an `inset` value has been provided, * the divider will start shrinking from both ends. * ```html * <igx-divider [middle]="true"></igx-divider> * ``` */ this.middle = false; /** * Sets the divider in vertical orientation. * ```html * <igx-divider [vertical]="true"></igx-divider> * ``` */ this.vertical = false; /** * Sets the value of the `inset` attribute. * If not provided it will be set to `'0'`. * ```html * <igx-divider inset="16px"></igx-divider> * ``` */ this._inset = '0'; } get isDashed() { return this.type === IgxDividerType.DASHED; } /** * Sets the inset of the divider from the side(s). * If the divider attribute `middle` is set to `true`, * it will inset the divider on both sides. * ```typescript * this.divider.inset = '32px'; * ``` */ set inset(value) { this._inset = value; } /** * Gets the current divider inset in terms of * inset-inline-start representation as applied to the divider. * ```typescript * const inset = this.divider.inset; * ``` */ get inset() { return this._inset; } /** * A getter that returns `true` if the type of the divider is `default`; * ```typescript * const isDefault = this.divider.isDefault; * ``` */ get isSolid() { return this.type === IgxDividerType.SOLID; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDividerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: IgxDividerDirective, isStandalone: true, selector: "igx-divider", inputs: { id: "id", role: "role", type: "type", middle: ["middle", "middle", booleanAttribute], vertical: ["vertical", "vertical", booleanAttribute], inset: "inset" }, host: { properties: { "attr.id": "this.id", "attr.role": "this.role", "class.igx-divider": "this.type", "class.igx-divider--dashed": "this.isDashed", "class.igx-divider--inset": "this.middle", "class.igx-divider--vertical": "this.vertical", "style.--inset": "this.inset" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDividerDirective, decorators: [{ type: Directive, args: [{ selector: 'igx-divider', standalone: true }] }], propDecorators: { id: [{ type: HostBinding, args: ['attr.id'] }, { type: Input }], role: [{ type: HostBinding, args: ['attr.role'] }, { type: Input }], type: [{ type: HostBinding, args: ['class.igx-divider'] }, { type: Input }], isDashed: [{ type: HostBinding, args: ['class.igx-divider--dashed'] }], middle: [{ type: HostBinding, args: ['class.igx-divider--inset'] }, { type: Input, args: [{ transform: booleanAttribute }] }], vertical: [{ type: HostBinding, args: ['class.igx-divider--vertical'] }, { type: Input, args: [{ transform: booleanAttribute }] }], inset: [{ type: HostBinding, args: ['style.--inset'] }, { type: Input }] } }); // @dynamic class IgxDefaultDropStrategy { dropAction(_drag, _drop, _atIndex) { } } // @dynamic class IgxAppendDropStrategy { constructor(_renderer) { this._renderer = _renderer; } dropAction(drag, drop, _atIndex) { const dragElement = drag.element.nativeElement; const dropAreaElement = drop.element.nativeElement; this._renderer.removeChild(dragElement.parentNode, dragElement); this._renderer.appendChild(dropAreaElement, dragElement); } } // @dynamic class IgxPrependDropStrategy { constructor(_renderer) { this._renderer = _renderer; } dropAction(drag, drop, _atIndex) { const dragElement = drag.element.nativeElement; const dropAreaElement = drop.element.nativeElement; this._renderer.removeChild(dragElement.parentNode, dragElement); if (dropAreaElement.children.length) { this._renderer.insertBefore(dropAreaElement, dragElement, dropAreaElement.children[0]); } else { this._renderer.appendChild(dropAreaElement, dragElement); } } } // @dynamic class IgxInsertDropStrategy { constructor(_renderer) { this._renderer = _renderer; } dropAction(drag, drop, atIndex) { if (drag.element.nativeElement.parentElement === drop.element.nativeElement && atIndex === -1) { return; } const dragElement = drag.element.nativeElement; const dropAreaElement = drop.element.nativeElement; this._renderer.removeChild(dragElement.parentNode, dragElement); if (atIndex !== -1 && dropAreaElement.children.length > atIndex) { this._renderer.insertBefore(dropAreaElement, dragElement, dropAreaElement.children[atIndex]); } else { this._renderer.appendChild(dropAreaElement, dragElement); } } } var DragScrollDirection; (function (DragScrollDirection) { DragScrollDirection[DragScrollDirection["UP"] = 0] = "UP"; DragScrollDirection[DragScrollDirection["DOWN"] = 1] = "DOWN"; DragScrollDirection[DragScrollDirection["LEFT"] = 2] = "LEFT"; DragScrollDirection[DragScrollDirection["RIGHT"] = 3] = "RIGHT"; })(DragScrollDirection || (DragScrollDirection = {})); var DragDirection; (function (DragDirection) { DragDirection[DragDirection["VERTICAL"] = 0] = "VERTICAL"; DragDirection[DragDirection["HORIZONTAL"] = 1] = "HORIZONTAL"; DragDirection[DragDirection["BOTH"] = 2] = "BOTH"; })(DragDirection || (DragDirection = {})); class IgxDragLocation { constructor(_pageX, _pageY) { this._pageX = _pageX; this._pageY = _pageY; this.pageX = parseFloat(_pageX); this.pageY = parseFloat(_pageY); } } class IgxDragHandleDirective { constructor() { this.element = inject((ElementRef)); this.baseClass = true; /** * @hidden */ this.parentDragElement = null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDragHandleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxDragHandleDirective, isStandalone: true, selector: "[igxDragHandle]", host: { properties: { "class.igx-drag__handle": "this.baseClass" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDragHandleDirective, decorators: [{ type: Directive, args: [{ selector: '[igxDragHandle]', standalone: true }] }], propDecorators: { baseClass: [{ type: HostBinding, args: ['class.igx-drag__handle'] }] } }); class IgxDragIgnoreDirective { constructor() { this.element = inject((ElementRef)); this.baseClass = true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDragIgnoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxDragIgnoreDirective, isStandalone: true, selector: "[igxDragIgnore]", host: { properties: { "class.igx-drag__ignore": "this.baseClass" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDragIgnoreDirective, decorators: [{ type: Directive, args: [{ selector: '[igxDragIgnore]', standalone: true }] }], propDecorators: { baseClass: [{ type: HostBinding, args: ['class.igx-drag__ignore'] }] } }); class IgxDragDirective { /** * - Save data inside the `igxDrag` directive. This can be set when instancing `igxDrag` on an element. * ```html * <div [igxDrag]="{ source: myElement }"></div> * ``` * * @memberof IgxDragDirective */ set data(value) { this._data = value; } get data() { return this._data; } /** * Gets the current location of the element relative to the page. */ get location() { return new IgxDragLocation(this.pageX, this.pageY); } /** * Gets the original location of the element before dragging started. */ get originLocation() { return new IgxDragLocation(this.baseOriginLeft, this.baseOriginTop); } /** * @hidden */ get pointerEventsEnabled() { return typeof PointerEvent !== 'undefined'; } /** * @hidden */ get touchEventsEnabled() { return 'ontouchstart' in window; } /** * @hidden */ get pageX() { if (this.ghost && this.ghostElement) { return this.ghostLeft; } return this.baseLeft + this.windowScrollLeft; } /** * @hidden */ get pageY() { if (this.ghost && this.ghostElement) { return this.ghostTop; } return this.baseTop + this.windowScrollTop; } get baseLeft() { return this.element.nativeElement.getBoundingClientRect().left; } get baseTop() { return this.element.nativeElement.getBoundingClientRect().top; } get baseOriginLeft() { return this.baseLeft - this.getTransformX(this.element.nativeElement); } get baseOriginTop() { return this.baseTop - this.getTransformY(this.element.nativeElement); } set ghostLeft(pageX) { if (this.ghostElement) { // We need to take into account marginLeft, since top style does not include margin, but pageX includes the margin. const ghostMarginLeft = parseInt(this.document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); // If ghost host is defined it needs to be taken into account. this.ghostElement.style.left = (pageX - ghostMarginLeft - this._ghostHostX) + 'px'; } } get ghostLeft() { if (this.ghostElement) { return parseInt(this.ghostElement.style.left, 10) + this._ghostHostX; } } set ghostTop(pageY) { if (this.ghostElement) { // We need to take into account marginTop, since top style does not include margin, but pageY includes the margin. const ghostMarginTop = parseInt(this.document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); // If ghost host is defined it needs to be taken into account. this.ghostElement.style.top = (pageY - ghostMarginTop - this._ghostHostY) + 'px'; } } get ghostTop() { if (this.ghostElement) { return parseInt(this.ghostElement.style.top, 10) + this._ghostHostY; } } get windowScrollTop() { return this.document.documentElement.scrollTop || window.scrollY; } get windowScrollLeft() { return this.document.documentElement.scrollLeft || window.scrollX; } get windowScrollHeight() { return this.document.documentElement.scrollHeight; } get windowScrollWidth() { return this.document.documentElement.scrollWidth; } /** * Sets the offset of the dragged element relative to the mouse in pixels. * By default it's taking the relative position to the mouse when the drag started and keeps it the same. * ```html * <div #hostDiv></div> * <div igxDrag [ghostOffsetX]="0"> * <span>Drag Me!</span> * </div> * ``` * * @memberof IgxDragDirective */ set ghostOffsetX(value) { this._offsetX = parseInt(value, 10); } get ghostOffsetX() { return this._offsetX !== undefined ? this._offsetX : this._defaultOffsetX; } /** * Sets the offset of the dragged element relative to the mouse in pixels. * By default it's taking the relative position to the mouse when the drag started and keeps it the same. * ```html * <div #hostDiv></div> * <div igxDrag [ghostOffsetY]="0"> * <span>Drag Me!</span> * </div> * ``` * * @memberof IgxDragDirective */ set ghostOffsetY(value) { this._offsetY = parseInt(value, 10); } get ghostOffsetY() { return this._offsetY !== undefined ? this._offsetY : this._defaultOffsetY; } constructor() { /** * Sets the tolerance in pixels before drag starts. * By default the drag starts after the draggable element is moved by 5px. * ```html * <div igxDrag [dragTolerance]="100"> * <span>Drag Me!</span> * </div> * ``` * * @memberof IgxDragDirective */ this.dragTolerance = 5; /** * Sets the directions that the element can be dragged. * By default it is set to both horizontal and vertical directions. * ```html * <div igxDrag [dragDirection]="dragDir"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public dragDir = DragDirection.HORIZONTAL; * ``` * * @memberof IgxDragDirective */ this.dragDirection = DragDirection.BOTH; /** * Sets whether the base element should be moved, or a ghost element should be rendered that represents it instead. * By default it is set to `true`. * If it is set to `false` when dragging the base element is moved instead and no ghost elements are rendered. * ```html * <div igxDrag [ghost]="false"> * <span>Drag Me!</span> * </div> * ``` * * @memberof IgxDragDirective */ this.ghost = true; /** * Sets a custom class that will be added to the `ghostElement` element. * ```html * <div igxDrag [ghostClass]="'ghostElement'"> * <span>Drag Me!</span> * </div> * ``` * * @memberof IgxDragDirective */ this.ghostClass = ''; /** * Set styles that will be added to the `ghostElement` element. * ```html * <div igxDrag [ghostStyle]="{'--ig-size': 'var(--ig-size-small)'}"> * <span>Drag Me!</span> * </div> * ``` * * @memberof IgxDragDirective */ this.ghostStyle = {}; /** * Overrides the scroll container of the dragged element. By default its the window. */ this.scrollContainer = null; /** * Event triggered when the draggable element drag starts. * ```html * <div igxDrag (dragStart)="onDragStart()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragStart(){ * alert("The drag has stared!"); * } * ``` * * @memberof IgxDragDirective */ this.dragStart = new EventEmitter(); /** * Event triggered when the draggable element has been moved. * ```html * <div igxDrag (dragMove)="onDragMove()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragMove(){ * alert("The element has moved!"); * } * ``` * * @memberof IgxDragDirective */ this.dragMove = new EventEmitter(); /** * Event triggered when the draggable element is released. * ```html * <div igxDrag (dragEnd)="onDragEnd()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragEnd(){ * alert("The drag has ended!"); * } * ``` * * @memberof IgxDragDirective */ this.dragEnd = new EventEmitter(); /** * Event triggered when the draggable element is clicked. * ```html * <div igxDrag (dragClick)="onDragClick()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragClick(){ * alert("The element has been clicked!"); * } * ``` * * @memberof IgxDragDirective */ this.dragClick = new EventEmitter(); /** * Event triggered when the drag ghost element is created. * ```html * <div igxDrag (ghostCreate)="ghostCreated()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public ghostCreated(){ * alert("The ghost has been created!"); * } * ``` * * @memberof IgxDragDirective */ this.ghostCreate = new EventEmitter(); /** * Event triggered when the drag ghost element is created. * ```html * <div igxDrag (ghostDestroy)="ghostDestroyed()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public ghostDestroyed(){ * alert("The ghost has been destroyed!"); * } * ``` * * @memberof IgxDragDirective */ this.ghostDestroy = new EventEmitter(); /** * Event triggered after the draggable element is released and after its animation has finished. * ```html * <div igxDrag (transitioned)="onMoveEnd()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onMoveEnd(){ * alert("The move has ended!"); * } * ``` * * @memberof IgxDragDirective */ this.transitioned = new EventEmitter(); /** * @hidden */ this.baseClass = true; /** * @hidden */ this.selectDisabled = false; /** * @hidden */ this.defaultReturnDuration = '0.5s'; /** * @hidden */ this.animInProgress = false; this.ghostContext = null; this._startX = 0; this._startY = 0; this._lastX = 0; this._lastY = 0; this._dragStarted = false; this._ghostHostX = 0; this._ghostHostY = 0; this._pointerDownId = null; this._clicked = false; this._lastDropArea = null; this._destroy = new Subject(); this._removeOnDestroy = true; this._scrollContainer = null; this._originalScrollContainerWidth = 0; this._originalScrollContainerHeight = 0; this._scrollContainerStep = 5; this._scrollContainerStepMs = 10; this._scrollContainerThreshold = 25; this._containerScrollIntervalId = null; this.document = inject(DOCUMENT); this.cdr = inject(ChangeDetectorRef); this.element = inject(ElementRef); this.viewContainer = inject(ViewContainerRef); this.zone = inject(NgZone); this.renderer = inject(Renderer2); this.platformUtil = inject(PlatformUtil); this.onTransitionEnd = this.onTransitionEnd.bind(this); this.onPointerMove = this.onPointerMove.bind(this); this.onPointerUp = this.onPointerUp.bind(this); this.onPointerLost = this.onPointerLost.bind(this); } /** * @hidden */ ngAfterContentInit() { if (!this.dragHandles || !this.dragHandles.length) { // Set user select none to the whole draggable element if no drag handles are defined. this.selectDisabled = true; } // Bind events this.zone.runOutsideAngular(() => { if (!this.platformUtil.isBrowser) { return; } const targetElements = this.dragHandles && this.dragHandles.length ? this.dragHandles .filter(item => item.parentDragElement === null) .map(item => { item.parentDragElement = this.element.nativeElement; return item.element.nativeElement; }) : [this.element.nativeElement]; targetElements.forEach((element) => { if (this.pointerEventsEnabled) { fromEvent(element, 'pointerdown').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerDown(res)); fromEvent(element, 'pointermove').pipe(throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy)).subscribe((res) => this.onPointerMove(res)); fromEvent(element, 'pointerup').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); if (!this.ghost) { // Do not bind `lostpointercapture` to the target, because we will bind it on the ghost later. fromEvent(element, 'lostpointercapture').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerLost(res)); } } else if (this.touchEventsEnabled) { fromEvent(element, 'touchstart').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerDown(res)); } else { // We don't have pointer events and touch events. Use then mouse events. fromEvent(element, 'mousedown').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerDown(res)); } }); // We should bind to document events only once when there are no pointer events. if (!this.pointerEventsEnabled && this.touchEventsEnabled) { fromEvent(this.document.defaultView, 'touchmove').pipe(throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy)).subscribe((res) => this.onPointerMove(res)); fromEvent(this.document.defaultView, 'touchend').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); } else if (!this.pointerEventsEnabled) { fromEvent(this.document.defaultView, 'mousemove').pipe(throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy)).subscribe((res) => this.onPointerMove(res)); fromEvent(this.document.defaultView, 'mouseup').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); } this.element.nativeElement.addEventListener('transitionend', this.onTransitionEnd); }); // Set transition duration to 0s. This also helps with setting `visibility: hidden` to the base to not lag. this.element.nat