Current Path : /storage/v11800/affypharma/public_html/wp-admin/js/

Linux v11800 5.3.0-1023-aws #25~18.04.1-Ubuntu SMP Fri Jun 5 15:19:18 UTC 2020 aarch64

Upload File :
Current File : /storage/v11800/affypharma/public_html/wp-admin/js/customize-widgets.js
/**
 * @output wp-admin/js/customize-widgets.js
 */

/* global _wpCustomizeWidgetsSettings */
(function( wp, $ ){

	if ( ! wp || ! wp.customize ) { return; }

	// Set up our namespace...
	var api = wp.customize,
		l10n;

	/**
	 * @namespace wp.customize.Widgets
	 */
	api.Widgets = api.Widgets || {};
	api.Widgets.savedWidgetIds = {};

	// Link settings.
	api.Widgets.data = _wpCustomizeWidgetsSettings || {};
	l10n = api.Widgets.data.l10n;

	/**
	 * wp.customize.Widgets.WidgetModel
	 *
	 * A single widget model.
	 *
	 * @class    wp.customize.Widgets.WidgetModel
	 * @augments Backbone.Model
	 */
	api.Widgets.WidgetModel = Backbone.Model.extend(/** @lends wp.customize.Widgets.WidgetModel.prototype */{
		id: null,
		temp_id: null,
		classname: null,
		control_tpl: null,
		description: null,
		is_disabled: null,
		is_multi: null,
		multi_number: null,
		name: null,
		id_base: null,
		transport: null,
		params: [],
		width: null,
		height: null,
		search_matched: true
	});

	/**
	 * wp.customize.Widgets.WidgetCollection
	 *
	 * Collection for widget models.
	 *
	 * @class    wp.customize.Widgets.WidgetCollection
	 * @augments Backbone.Collection
	 */
	api.Widgets.WidgetCollection = Backbone.Collection.extend(/** @lends wp.customize.Widgets.WidgetCollection.prototype */{
		model: api.Widgets.WidgetModel,

		// Controls searching on the current widget collection
		// and triggers an update event.
		doSearch: function( value ) {

			// Don't do anything if we've already done this search.
			// Useful because the search handler fires multiple times per keystroke.
			if ( this.terms === value ) {
				return;
			}

			// Updates terms with the value passed.
			this.terms = value;

			// If we have terms, run a search...
			if ( this.terms.length > 0 ) {
				this.search( this.terms );
			}

			// If search is blank, set all the widgets as they matched the search to reset the views.
			if ( this.terms === '' ) {
				this.each( function ( widget ) {
					widget.set( 'search_matched', true );
				} );
			}
		},

		// Performs a search within the collection.
		// @uses RegExp
		search: function( term ) {
			var match, haystack;

			// Escape the term string for RegExp meta characters.
			term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );

			// Consider spaces as word delimiters and match the whole string
			// so matching terms can be combined.
			term = term.replace( / /g, ')(?=.*' );
			match = new RegExp( '^(?=.*' + term + ').+', 'i' );

			this.each( function ( data ) {
				haystack = [ data.get( 'name' ), data.get( 'description' ) ].join( ' ' );
				data.set( 'search_matched', match.test( haystack ) );
			} );
		}
	});
	api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets );

	/**
	 * wp.customize.Widgets.SidebarModel
	 *
	 * A single sidebar model.
	 *
	 * @class    wp.customize.Widgets.SidebarModel
	 * @augments Backbone.Model
	 */
	api.Widgets.SidebarModel = Backbone.Model.extend(/** @lends wp.customize.Widgets.SidebarModel.prototype */{
		after_title: null,
		after_widget: null,
		before_title: null,
		before_widget: null,
		'class': null,
		description: null,
		id: null,
		name: null,
		is_rendered: false
	});

	/**
	 * wp.customize.Widgets.SidebarCollection
	 *
	 * Collection for sidebar models.
	 *
	 * @class    wp.customize.Widgets.SidebarCollection
	 * @augments Backbone.Collection
	 */
	api.Widgets.SidebarCollection = Backbone.Collection.extend(/** @lends wp.customize.Widgets.SidebarCollection.prototype */{
		model: api.Widgets.SidebarModel
	});
	api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars );

	api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend(/** @lends wp.customize.Widgets.AvailableWidgetsPanelView.prototype */{

		el: '#available-widgets',

		events: {
			'input #widgets-search': 'search',
			'focus .widget-tpl' : 'focus',
			'click .widget-tpl' : '_submit',
			'keypress .widget-tpl' : '_submit',
			'keydown' : 'keyboardAccessible'
		},

		// Cache current selected widget.
		selected: null,

		// Cache sidebar control which has opened panel.
		currentSidebarControl: null,
		$search: null,
		$clearResults: null,
		searchMatchesCount: null,

		/**
		 * View class for the available widgets panel.
		 *
		 * @constructs wp.customize.Widgets.AvailableWidgetsPanelView
		 * @augments   wp.Backbone.View
		 */
		initialize: function() {
			var self = this;

			this.$search = $( '#widgets-search' );

			this.$clearResults = this.$el.find( '.clear-results' );

			_.bindAll( this, 'close' );

			this.listenTo( this.collection, 'change', this.updateList );

			this.updateList();

			// Set the initial search count to the number of available widgets.
			this.searchMatchesCount = this.collection.length;

			/*
			 * If the available widgets panel is open and the customize controls
			 * are interacted with (i.e. available widgets panel is blurred) then
			 * close the available widgets panel. Also close on back button click.
			 */
			$( '#customize-controls, #available-widgets .customize-section-title' ).on( 'click keydown', function( e ) {
				var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
				if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) {
					self.close();
				}
			} );

			// Clear the search results and trigger an `input` event to fire a new search.
			this.$clearResults.on( 'click', function() {
				self.$search.val( '' ).trigger( 'focus' ).trigger( 'input' );
			} );

			// Close the panel if the URL in the preview changes.
			api.previewer.bind( 'url', this.close );
		},

		/**
		 * Performs a search and handles selected widget.
		 */
		search: _.debounce( function( event ) {
			var firstVisible;

			this.collection.doSearch( event.target.value );
			// Update the search matches count.
			this.updateSearchMatchesCount();
			// Announce how many search results.
			this.announceSearchMatches();

			// Remove a widget from being selected if it is no longer visible.
			if ( this.selected && ! this.selected.is( ':visible' ) ) {
				this.selected.removeClass( 'selected' );
				this.selected = null;
			}

			// If a widget was selected but the filter value has been cleared out, clear selection.
			if ( this.selected && ! event.target.value ) {
				this.selected.removeClass( 'selected' );
				this.selected = null;
			}

			// If a filter has been entered and a widget hasn't been selected, select the first one shown.
			if ( ! this.selected && event.target.value ) {
				firstVisible = this.$el.find( '> .widget-tpl:visible:first' );
				if ( firstVisible.length ) {
					this.select( firstVisible );
				}
			}

			// Toggle the clear search results button.
			if ( '' !== event.target.value ) {
				this.$clearResults.addClass( 'is-visible' );
			} else if ( '' === event.target.value ) {
				this.$clearResults.removeClass( 'is-visible' );
			}

			// Set a CSS class on the search container when there are no search results.
			if ( ! this.searchMatchesCount ) {
				this.$el.addClass( 'no-widgets-found' );
			} else {
				this.$el.removeClass( 'no-widgets-found' );
			}
		}, 500 ),

		/**
		 * Updates the count of the available widgets that have the `search_matched` attribute.
 		 */
		updateSearchMatchesCount: function() {
			this.searchMatchesCount = this.collection.where({ search_matched: true }).length;
		},

		/**
		 * Sends a message to the aria-live region to announce how many search results.
		 */
		announceSearchMatches: function() {
			var message = l10n.widgetsFound.replace( '%d', this.searchMatchesCount ) ;

			if ( ! this.searchMatchesCount ) {
				message = l10n.noWidgetsFound;
			}

			wp.a11y.speak( message );
		},

		/**
		 * Changes visibility of available widgets.
 		 */
		updateList: function() {
			this.collection.each( function( widget ) {
				var widgetTpl = $( '#widget-tpl-' + widget.id );
				widgetTpl.toggle( widget.get( 'search_matched' ) && ! widget.get( 'is_disabled' ) );
				if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) {
					this.selected = null;
				}
			} );
		},

		/**
		 * Highlights a widget.
 		 */
		select: function( widgetTpl ) {
			this.selected = $( widgetTpl );
			this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' );
			this.selected.addClass( 'selected' );
		},

		/**
		 * Highlights a widget on focus.
		 */
		focus: function( event ) {
			this.select( $( event.currentTarget ) );
		},

		/**
		 * Handles submit for keypress and click on widget.
		 */
		_submit: function( event ) {
			// Only proceed with keypress if it is Enter or Spacebar.
			if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
				return;
			}

			this.submit( $( event.currentTarget ) );
		},

		/**
		 * Adds a selected widget to the sidebar.
 		 */
		submit: function( widgetTpl ) {
			var widgetId, widget, widgetFormControl;

			if ( ! widgetTpl ) {
				widgetTpl = this.selected;
			}

			if ( ! widgetTpl || ! this.currentSidebarControl ) {
				return;
			}

			this.select( widgetTpl );

			widgetId = $( this.selected ).data( 'widget-id' );
			widget = this.collection.findWhere( { id: widgetId } );
			if ( ! widget ) {
				return;
			}

			widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) );
			if ( widgetFormControl ) {
				widgetFormControl.focus();
			}

			this.close();
		},

		/**
		 * Opens the panel.
		 */
		open: function( sidebarControl ) {
			this.currentSidebarControl = sidebarControl;

			// Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens.
			_( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) {
				if ( control.params.is_wide ) {
					control.collapseForm();
				}
			} );

			if ( api.section.has( 'publish_settings' ) ) {
				api.section( 'publish_settings' ).collapse();
			}

			$( 'body' ).addClass( 'adding-widget' );

			this.$el.find( '.selected' ).removeClass( 'selected' );

			// Reset search.
			this.collection.doSearch( '' );

			if ( ! api.settings.browser.mobile ) {
				this.$search.trigger( 'focus' );
			}
		},

		/**
		 * Closes the panel.
		 */
		close: function( options ) {
			options = options || {};

			if ( options.returnFocus && this.currentSidebarControl ) {
				this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
			}

			this.currentSidebarControl = null;
			this.selected = null;

			$( 'body' ).removeClass( 'adding-widget' );

			this.$search.val( '' ).trigger( 'input' );
		},

		/**
		 * Adds keyboard accessiblity to the panel.
		 */
		keyboardAccessible: function( event ) {
			var isEnter = ( event.which === 13 ),
				isEsc = ( event.which === 27 ),
				isDown = ( event.which === 40 ),
				isUp = ( event.which === 38 ),
				isTab = ( event.which === 9 ),
				isShift = ( event.shiftKey ),
				selected = null,
				firstVisible = this.$el.find( '> .widget-tpl:visible:first' ),
				lastVisible = this.$el.find( '> .widget-tpl:visible:last' ),
				isSearchFocused = $( event.target ).is( this.$search ),
				isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' );

			if ( isDown || isUp ) {
				if ( isDown ) {
					if ( isSearchFocused ) {
						selected = firstVisible;
					} else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
						selected = this.selected.nextAll( '.widget-tpl:visible:first' );
					}
				} else if ( isUp ) {
					if ( isSearchFocused ) {
						selected = lastVisible;
					} else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
						selected = this.selected.prevAll( '.widget-tpl:visible:first' );
					}
				}

				this.select( selected );

				if ( selected ) {
					selected.trigger( 'focus' );
				} else {
					this.$search.trigger( 'focus' );
				}

				return;
			}

			// If enter pressed but nothing entered, don't do anything.
			if ( isEnter && ! this.$search.val() ) {
				return;
			}

			if ( isEnter ) {
				this.submit();
			} else if ( isEsc ) {
				this.close( { returnFocus: true } );
			}

			if ( this.currentSidebarControl && isTab && ( isShift && isSearchFocused || ! isShift && isLastWidgetFocused ) ) {
				this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
				event.preventDefault();
			}
		}
	});

	/**
	 * Handlers for the widget-synced event, organized by widget ID base.
	 * Other widgets may provide their own update handlers by adding
	 * listeners for the widget-synced event.
	 *
	 * @alias    wp.customize.Widgets.formSyncHandlers
	 */
	api.Widgets.formSyncHandlers = {

		/**
		 * @param {jQuery.Event} e
		 * @param {jQuery} widget
		 * @param {string} newForm
		 */
		rss: function( e, widget, newForm ) {
			var oldWidgetError = widget.find( '.widget-error:first' ),
				newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' );

			if ( oldWidgetError.length && newWidgetError.length ) {
				oldWidgetError.replaceWith( newWidgetError );
			} else if ( oldWidgetError.length ) {
				oldWidgetError.remove();
			} else if ( newWidgetError.length ) {
				widget.find( '.widget-content:first' ).prepend( newWidgetError );
			}
		}
	};

	api.Widgets.WidgetControl = api.Control.extend(/** @lends wp.customize.Widgets.WidgetControl.prototype */{
		defaultExpandedArguments: {
			duration: 'fast',
			completeCallback: $.noop
		},

		/**
		 * wp.customize.Widgets.WidgetControl
		 *
		 * Customizer control for widgets.
		 * Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type
		 *
		 * @since 4.1.0
		 *
		 * @constructs wp.customize.Widgets.WidgetControl
		 * @augments   wp.customize.Control
		 */
		initialize: function( id, options ) {
			var control = this;

			control.widgetControlEmbedded = false;
			control.widgetContentEmbedded = false;
			control.expanded = new api.Value( false );
			control.expandedArgumentsQueue = [];
			control.expanded.bind( function( expanded ) {
				var args = control.expandedArgumentsQueue.shift();
				args = $.extend( {}, control.defaultExpandedArguments, args );
				control.onChangeExpanded( expanded, args );
			});
			control.altNotice = true;

			api.Control.prototype.initialize.call( control, id, options );
		},

		/**
		 * Set up the control.
		 *
		 * @since 3.9.0
		 */
		ready: function() {
			var control = this;

			/*
			 * Embed a placeholder once the section is expanded. The full widget
			 * form content will be embedded once the control itself is expanded,
			 * and at this point the widget-added event will be triggered.
			 */
			if ( ! control.section() ) {
				control.embedWidgetControl();
			} else {
				api.section( control.section(), function( section ) {
					var onExpanded = function( isExpanded ) {
						if ( isExpanded ) {
							control.embedWidgetControl();
							section.expanded.unbind( onExpanded );
						}
					};
					if ( section.expanded() ) {
						onExpanded( true );
					} else {
						section.expanded.bind( onExpanded );
					}
				} );
			}
		},

		/**
		 * Embed the .widget element inside the li container.
		 *
		 * @since 4.4.0
		 */
		embedWidgetControl: function() {
			var control = this, widgetControl;

			if ( control.widgetControlEmbedded ) {
				return;
			}
			control.widgetControlEmbedded = true;

			widgetControl = $( control.params.widget_control );
			control.container.append( widgetControl );

			control._setupModel();
			control._setupWideWidget();
			control._setupControlToggle();

			control._setupWidgetTitle();
			control._setupReorderUI();
			control._setupHighlightEffects();
			control._setupUpdateUI();
			control._setupRemoveUI();
		},

		/**
		 * Embed the actual widget form inside of .widget-content and finally trigger the widget-added event.
		 *
		 * @since 4.4.0
		 */
		embedWidgetContent: function() {
			var control = this, widgetContent;

			control.embedWidgetControl();
			if ( control.widgetContentEmbedded ) {
				return;
			}
			control.widgetContentEmbedded = true;

			// Update the notification container element now that the widget content has been embedded.
			control.notifications.container = control.getNotificationsContainerElement();
			control.notifications.render();

			widgetContent = $( control.params.widget_content );
			control.container.find( '.widget-content:first' ).append( widgetContent );

			/*
			 * Trigger widget-added event so that plugins can attach any event
			 * listeners and dynamic UI elements.
			 */
			$( document ).trigger( 'widget-added', [ control.container.find( '.widget:first' ) ] );

		},

		/**
		 * Handle changes to the setting
		 */
		_setupModel: function() {
			var self = this, rememberSavedWidgetId;

			// Remember saved widgets so we know which to trash (move to inactive widgets sidebar).
			rememberSavedWidgetId = function() {
				api.Widgets.savedWidgetIds[self.params.widget_id] = true;
			};
			api.bind( 'ready', rememberSavedWidgetId );
			api.bind( 'saved', rememberSavedWidgetId );

			this._updateCount = 0;
			this.isWidgetUpdating = false;
			this.liveUpdateMode = true;

			// Update widget whenever model changes.
			this.setting.bind( function( to, from ) {
				if ( ! _( from ).isEqual( to ) && ! self.isWidgetUpdating ) {
					self.updateWidget( { instance: to } );
				}
			} );
		},

		/**
		 * Add special behaviors for wide widget controls
		 */
		_setupWideWidget: function() {
			var self = this, $widgetInside, $widgetForm, $customizeSidebar,
				$themeControlsContainer, positionWidget;

			if ( ! this.params.is_wide || $( window ).width() <= 640 /* max-width breakpoint in customize-controls.css */ ) {
				return;
			}

			$widgetInside = this.container.find( '.widget-inside' );
			$widgetForm = $widgetInside.find( '> .form' );
			$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' );
			this.container.addClass( 'wide-widget-control' );

			this.container.find( '.form:first' ).css( {
				'max-width': this.params.width,
				'min-height': this.params.height
			} );

			/**
			 * Keep the widget-inside positioned so the top of fixed-positioned
			 * element is at the same top position as the widget-top. When the
			 * widget-top is scrolled out of view, keep the widget-top in view;
			 * likewise, don't allow the widget to drop off the bottom of the window.
			 * If a widget is too tall to fit in the window, don't let the height
			 * exceed the window height so that the contents of the widget control
			 * will become scrollable (overflow:auto).
			 */
			positionWidget = function() {
				var offsetTop = self.container.offset().top,
					windowHeight = $( window ).height(),
					formHeight = $widgetForm.outerHeight(),
					top;
				$widgetInside.css( 'max-height', windowHeight );
				top = Math.max(
					0, // Prevent top from going off screen.
					Math.min(
						Math.max( offsetTop, 0 ), // Distance widget in panel is from top of screen.
						windowHeight - formHeight // Flush up against bottom of screen.
					)
				);
				$widgetInside.css( 'top', top );
			};

			$themeControlsContainer = $( '#customize-theme-controls' );
			this.container.on( 'expand', function() {
				positionWidget();
				$customizeSidebar.on( 'scroll', positionWidget );
				$( window ).on( 'resize', positionWidget );
				$themeControlsContainer.on( 'expanded collapsed', positionWidget );
			} );
			this.container.on( 'collapsed', function() {
				$customizeSidebar.off( 'scroll', positionWidget );
				$( window ).off( 'resize', positionWidget );
				$themeControlsContainer.off( 'expanded collapsed', positionWidget );
			} );

			// Reposition whenever a sidebar's widgets are changed.
			api.each( function( setting ) {
				if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
					setting.bind( function() {
						if ( self.container.hasClass( 'expanded' ) ) {
							positionWidget();
						}
					} );
				}
			} );
		},

		/**
		 * Show/hide the control when clicking on the form title, when clicking
		 * the close button
		 */
		_setupControlToggle: function() {
			var self = this, $closeBtn;

			this.container.find( '.widget-top' ).on( 'click', function( e ) {
				e.preventDefault();
				var sidebarWidgetsControl = self.getSidebarWidgetsControl();
				if ( sidebarWidgetsControl.isReordering ) {
					return;
				}
				self.expanded( ! self.expanded() );
			} );

			$closeBtn = this.container.find( '.widget-control-close' );
			$closeBtn.on( 'click', function() {
				self.collapse();
				self.container.find( '.widget-top .widget-action:first' ).focus(); // Keyboard accessibility.
			} );
		},

		/**
		 * Update the title of the form if a title field is entered
		 */
		_setupWidgetTitle: function() {
			var self = this, updateTitle;

			updateTitle = function() {
				var title = self.setting().title,
					inWidgetTitle = self.container.find( '.in-widget-title' );

				if ( title ) {
					inWidgetTitle.text( ': ' + title );
				} else {
					inWidgetTitle.text( '' );
				}
			};
			this.setting.bind( updateTitle );
			updateTitle();
		},

		/**
		 * Set up the widget-reorder-nav
		 */
		_setupReorderUI: function() {
			var self = this, selectSidebarItem, $moveWidgetArea,
				$reorderNav, updateAvailableSidebars, template;

			/**
			 * select the provided sidebar list item in the move widget area
			 *
			 * @param {jQuery} li
			 */
			selectSidebarItem = function( li ) {
				li.siblings( '.selected' ).removeClass( 'selected' );
				li.addClass( 'selected' );
				var isSelfSidebar = ( li.data( 'id' ) === self.params.sidebar_id );
				self.container.find( '.move-widget-btn' ).prop( 'disabled', isSelfSidebar );
			};

			/**
			 * Add the widget reordering elements to the widget control
			 */
			this.container.find( '.widget-title-action' ).after( $( api.Widgets.data.tpl.widgetReorderNav ) );


			template = _.template( api.Widgets.data.tpl.moveWidgetArea );
			$moveWidgetArea = $( template( {
					sidebars: _( api.Widgets.registeredSidebars.toArray() ).pluck( 'attributes' )
				} )
			);
			this.container.find( '.widget-top' ).after( $moveWidgetArea );

			/**
			 * Update available sidebars when their rendered state changes
			 */
			updateAvailableSidebars = function() {
				var $sidebarItems = $moveWidgetArea.find( 'li' ), selfSidebarItem,
					renderedSidebarCount = 0;

				selfSidebarItem = $sidebarItems.filter( function(){
					return $( this ).data( 'id' ) === self.params.sidebar_id;
				} );

				$sidebarItems.each( function() {
					var li = $( this ),
						sidebarId, sidebar, sidebarIsRendered;

					sidebarId = li.data( 'id' );
					sidebar = api.Widgets.registeredSidebars.get( sidebarId );
					sidebarIsRendered = sidebar.get( 'is_rendered' );

					li.toggle( sidebarIsRendered );

					if ( sidebarIsRendered ) {
						renderedSidebarCount += 1;
					}

					if ( li.hasClass( 'selected' ) && ! sidebarIsRendered ) {
						selectSidebarItem( selfSidebarItem );
					}
				} );

				if ( renderedSidebarCount > 1 ) {
					self.container.find( '.move-widget' ).show();
				} else {
					self.container.find( '.move-widget' ).hide();
				}
			};

			updateAvailableSidebars();
			api.Widgets.registeredSidebars.on( 'change:is_rendered', updateAvailableSidebars );

			/**
			 * Handle clicks for up/down/move on the reorder nav
			 */
			$reorderNav = this.container.find( '.widget-reorder-nav' );
			$reorderNav.find( '.move-widget, .move-widget-down, .move-widget-up' ).each( function() {
				$( this ).prepend( self.container.find( '.widget-title' ).text() + ': ' );
			} ).on( 'click keypress', function( event ) {
				if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
					return;
				}
				$( this ).trigger( 'focus' );

				if ( $( this ).is( '.move-widget' ) ) {
					self.toggleWidgetMoveArea();
				} else {
					var isMoveDown = $( this ).is( '.move-widget-down' ),
						isMoveUp = $( this ).is( '.move-widget-up' ),
						i = self.getWidgetSidebarPosition();

					if ( ( isMoveUp && i === 0 ) || ( isMoveDown && i === self.getSidebarWidgetsControl().setting().length - 1 ) ) {
						return;
					}

					if ( isMoveUp ) {
						self.moveUp();
						wp.a11y.speak( l10n.widgetMovedUp );
					} else {
						self.moveDown();
						wp.a11y.speak( l10n.widgetMovedDown );
					}

					$( this ).trigger( 'focus' ); // Re-focus after the container was moved.
				}
			} );

			/**
			 * Handle selecting a sidebar to move to
			 */
			this.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function( event ) {
				if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
					return;
				}
				event.preventDefault();
				selectSidebarItem( $( this ) );
			} );

			/**
			 * Move widget to another sidebar
			 */
			this.container.find( '.move-widget-btn' ).click( function() {
				self.getSidebarWidgetsControl().toggleReordering( false );

				var oldSidebarId = self.params.sidebar_id,
					newSidebarId = self.container.find( '.widget-area-select li.selected' ).data( 'id' ),
					oldSidebarWidgetsSetting, newSidebarWidgetsSetting,
					oldSidebarWidgetIds, newSidebarWidgetIds, i;

				oldSidebarWidgetsSetting = api( 'sidebars_widgets[' + oldSidebarId + ']' );
				newSidebarWidgetsSetting = api( 'sidebars_widgets[' + newSidebarId + ']' );
				oldSidebarWidgetIds = Array.prototype.slice.call( oldSidebarWidgetsSetting() );
				newSidebarWidgetIds = Array.prototype.slice.call( newSidebarWidgetsSetting() );

				i = self.getWidgetSidebarPosition();
				oldSidebarWidgetIds.splice( i, 1 );
				newSidebarWidgetIds.push( self.params.widget_id );

				oldSidebarWidgetsSetting( oldSidebarWidgetIds );
				newSidebarWidgetsSetting( newSidebarWidgetIds );

				self.focus();
			} );
		},

		/**
		 * Highlight widgets in preview when interacted with in the Customizer
		 */
		_setupHighlightEffects: function() {
			var self = this;

			// Highlight whenever hovering or clicking over the form.
			this.container.on( 'mouseenter click', function() {
				self.setting.previewer.send( 'highlight-widget', self.params.widget_id );
			} );

			// Highlight when the setting is updated.
			this.setting.bind( function() {
				self.setting.previewer.send( 'highlight-widget', self.params.widget_id );
			} );
		},

		/**
		 * Set up event handlers for widget updating
		 */
		_setupUpdateUI: function() {
			var self = this, $widgetRoot, $widgetContent,
				$saveBtn, updateWidgetDebounced, formSyncHandler;

			$widgetRoot = this.container.find( '.widget:first' );
			$widgetContent = $widgetRoot.find( '.widget-content:first' );

			// Configure update button.
			$saveBtn = this.container.find( '.widget-control-save' );
			$saveBtn.val( l10n.saveBtnLabel );
			$saveBtn.attr( 'title', l10n.saveBtnTooltip );
			$saveBtn.removeClass( 'button-primary' );
			$saveBtn.on( 'click', function( e ) {
				e.preventDefault();
				self.updateWidget( { disable_form: true } ); // @todo disable_form is unused?
			} );

			updateWidgetDebounced = _.debounce( function() {
				self.updateWidget();
			}, 250 );

			// Trigger widget form update when hitting Enter within an input.
			$widgetContent.on( 'keydown', 'input', function( e ) {
				if ( 13 === e.which ) { // Enter.
					e.preventDefault();
					self.updateWidget( { ignoreActiveElement: true } );
				}
			} );

			// Handle widgets that support live previews.
			$widgetContent.on( 'change input propertychange', ':input', function( e ) {
				if ( ! self.liveUpdateMode ) {
					return;
				}
				if ( e.type === 'change' || ( this.checkValidity && this.checkValidity() ) ) {
					updateWidgetDebounced();
				}
			} );

			// Remove loading indicators when the setting is saved and the preview updates.
			this.setting.previewer.channel.bind( 'synced', function() {
				self.container.removeClass( 'previewer-loading' );
			} );

			api.previewer.bind( 'widget-updated', function( updatedWidgetId ) {
				if ( updatedWidgetId === self.params.widget_id ) {
					self.container.removeClass( 'previewer-loading' );
				}
			} );

			formSyncHandler = api.Widgets.formSyncHandlers[ this.params.widget_id_base ];
			if ( formSyncHandler ) {
				$( document ).on( 'widget-synced', function( e, widget ) {
					if ( $widgetRoot.is( widget ) ) {
						formSyncHandler.apply( document, arguments );
					}
				} );
			}
		},

		/**
		 * Update widget control to indicate whether it is currently rendered.
		 *
		 * Overrides api.Control.toggle()
		 *
		 * @since 4.1.0
		 *
		 * @param {boolean}   active
		 * @param {Object}    args
		 * @param {function}  args.completeCallback
		 */
		onChangeActive: function ( active, args ) {
			// Note: there is a second 'args' parameter being passed, merged on top of this.defaultActiveArguments.
			this.container.toggleClass( 'widget-rendered', active );
			if ( args.completeCallback ) {
				args.completeCallback();
			}
		},

		/**
		 * Set up event handlers for widget removal
		 */
		_setupRemoveUI: function() {
			var self = this, $removeBtn, replaceDeleteWithRemove;

			// Configure remove button.
			$removeBtn = this.container.find( '.widget-control-remove' );
			$removeBtn.on( 'click', function() {
				// Find an adjacent element to add focus to when this widget goes away.
				var $adjacentFocusTarget;
				if ( self.container.next().is( '.customize-control-widget_form' ) ) {
					$adjacentFocusTarget = self.container.next().find( '.widget-action:first' );
				} else if ( self.container.prev().is( '.customize-control-widget_form' ) ) {
					$adjacentFocusTarget = self.container.prev().find( '.widget-action:first' );
				} else {
					$adjacentFocusTarget = self.container.next( '.customize-control-sidebar_widgets' ).find( '.add-new-widget:first' );
				}

				self.container.slideUp( function() {
					var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ),
						sidebarWidgetIds, i;

					if ( ! sidebarsWidgetsControl ) {
						return;
					}

					sidebarWidgetIds = sidebarsWidgetsControl.setting().slice();
					i = _.indexOf( sidebarWidgetIds, self.params.widget_id );
					if ( -1 === i ) {
						return;
					}

					sidebarWidgetIds.splice( i, 1 );
					sidebarsWidgetsControl.setting( sidebarWidgetIds );

					$adjacentFocusTarget.focus(); // Keyboard accessibility.
				} );
			} );

			replaceDeleteWithRemove = function() {
				$removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the button as "Delete".
				$removeBtn.attr( 'title', l10n.removeBtnTooltip );
			};

			if ( this.params.is_new ) {
				api.bind( 'saved', replaceDeleteWithRemove );
			} else {
				replaceDeleteWithRemove();
			}
		},

		/**
		 * Find all inputs in a widget container that should be considered when
		 * comparing the loaded form with the sanitized form, whose fields will
		 * be aligned to copy the sanitized over. The elements returned by this
		 * are passed into this._getInputsSignature(), and they are iterated
		 * over when copying sanitized values over to the form loaded.
		 *
		 * @param {jQuery} container element in which to look for inputs
		 * @return {jQuery} inputs
		 * @private
		 */
		_getInputs: function( container ) {
			return $( container ).find( ':input[name]' );
		},

		/**
		 * Iterate over supplied inputs and create a signature string for all of them together.
		 * This string can be used to compare whether or not the form has all of the same fields.
		 *
		 * @param {jQuery} inputs
		 * @return {string}
		 * @private
		 */
		_getInputsSignature: function( inputs ) {
			var inputsSignatures = _( inputs ).map( function( input ) {
				var $input = $( input ), signatureParts;

				if ( $input.is( ':checkbox, :radio' ) ) {
					signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ), $input.prop( 'value' ) ];
				} else {
					signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ) ];
				}

				return signatureParts.join( ',' );
			} );

			return inputsSignatures.join( ';' );
		},

		/**
		 * Get the state for an input depending on its type.
		 *
		 * @param {jQuery|Element} input
		 * @return {string|boolean|Array|*}
		 * @private
		 */
		_getInputState: function( input ) {
			input = $( input );
			if ( input.is( ':radio, :checkbox' ) ) {
				return input.prop( 'checked' );
			} else if ( input.is( 'select[multiple]' ) ) {
				return input.find( 'option:selected' ).map( function () {
					return $( this ).val();
				} ).get();
			} else {
				return input.val();
			}
		},

		/**
		 * Update an input's state based on its type.
		 *
		 * @param {jQuery|Element} input
		 * @param {string|boolean|Array|*} state
		 * @private
		 */
		_setInputState: function ( input, state ) {
			input = $( input );
			if ( input.is( ':radio, :checkbox' ) ) {
				input.prop( 'checked', state );
			} else if ( input.is( 'select[multiple]' ) ) {
				if ( ! Array.isArray( state ) ) {
					state = [];
				} else {
					// Make sure all state items are strings since the DOM value is a string.
					state = _.map( state, function ( value ) {
						return String( value );
					} );
				}
				input.find( 'option' ).each( function () {
					$( this ).prop( 'selected', -1 !== _.indexOf( state, String( this.value ) ) );
				} );
			} else {
				input.val( state );
			}
		},

		/***********************************************************************
		 * Begin public API methods
		 **********************************************************************/

		/**
		 * @return {wp.customize.controlConstructor.sidebar_widgets[]}
		 */
		getSidebarWidgetsControl: function() {
			var settingId, sidebarWidgetsControl;

			settingId = 'sidebars_widgets[' + this.params.sidebar_id + ']';
			sidebarWidgetsControl = api.control( settingId );

			if ( ! sidebarWidgetsControl ) {
				return;
			}

			return sidebarWidgetsControl;
		},

		/**
		 * Submit the widget form via Ajax and get back the updated instance,
		 * along with the new widget control form to render.
		 *
		 * @param {Object} [args]
		 * @param {Object|null} [args.instance=null]  When the model changes, the instance is sent here; otherwise, the inputs from the form are used
		 * @param {Function|null} [args.complete=null]  Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success.
		 * @param {boolean} [args.ignoreActiveElement=false] Whether or not updating a field will be deferred if focus is still on the element.
		 */
		updateWidget: function( args ) {
			var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent,
				updateNumber, params, data, $inputs, processing, jqxhr, isChanged;

			// The updateWidget logic requires that the form fields to be fully present.
			self.embedWidgetContent();

			args = $.extend( {
				instance: null,
				complete: null,
				ignoreActiveElement: false
			}, args );

			instanceOverride = args.instance;
			completeCallback = args.complete;

			this._updateCount += 1;
			updateNumber = this._updateCount;

			$widgetRoot = this.container.find( '.widget:first' );
			$widgetContent = $widgetRoot.find( '.widget-content:first' );

			// Remove a previous error message.
			$widgetContent.find( '.widget-error' ).remove();

			this.container.addClass( 'widget-form-loading' );
			this.container.addClass( 'previewer-loading' );
			processing = api.state( 'processing' );
			processing( processing() + 1 );

			if ( ! this.liveUpdateMode ) {
				this.container.addClass( 'widget-form-disabled' );
			}

			params = {};
			params.action = 'update-widget';
			params.wp_customize = 'on';
			params.nonce = api.settings.nonce['update-widget'];
			params.customize_theme = api.settings.theme.stylesheet;
			params.customized = wp.customize.previewer.query().customized;

			data = $.param( params );
			$inputs = this._getInputs( $widgetContent );

			/*
			 * Store the value we're submitting in data so that when the response comes back,
			 * we know if it got sanitized; if there is no difference in the sanitized value,
			 * then we do not need to touch the UI and mess up the user's ongoing editing.
			 */
			$inputs.each( function() {
				$( this ).data( 'state' + updateNumber, self._getInputState( this ) );
			} );

			if ( instanceOverride ) {
				data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } );
			} else {
				data += '&' + $inputs.serialize();
			}
			data += '&' + $widgetContent.find( '~ :input' ).serialize();

			if ( this._previousUpdateRequest ) {
				this._previousUpdateRequest.abort();
			}
			jqxhr = $.post( wp.ajax.settings.url, data );
			this._previousUpdateRequest = jqxhr;

			jqxhr.done( function( r ) {
				var message, sanitizedForm,	$sanitizedInputs, hasSameInputsInResponse,
					isLiveUpdateAborted = false;

				// Check if the user is logged out.
				if ( '0' === r ) {
					api.previewer.preview.iframe.hide();
					api.previewer.login().done( function() {
						self.updateWidget( args );
						api.previewer.preview.iframe.show();
					} );
					return;
				}

				// Check for cheaters.
				if ( '-1' === r ) {
					api.previewer.cheatin();
					return;
				}

				if ( r.success ) {
					sanitizedForm = $( '<div>' + r.data.form + '</div>' );
					$sanitizedInputs = self._getInputs( sanitizedForm );
					hasSameInputsInResponse = self._getInputsSignature( $inputs ) === self._getInputsSignature( $sanitizedInputs );

					// Restore live update mode if sanitized fields are now aligned with the existing fields.
					if ( hasSameInputsInResponse && ! self.liveUpdateMode ) {
						self.liveUpdateMode = true;
						self.container.removeClass( 'widget-form-disabled' );
						self.container.find( 'input[name="savewidget"]' ).hide();
					}

					// Sync sanitized field states to existing fields if they are aligned.
					if ( hasSameInputsInResponse && self.liveUpdateMode ) {
						$inputs.each( function( i ) {
							var $input = $( this ),
								$sanitizedInput = $( $sanitizedInputs[i] ),
								submittedState, sanitizedState,	canUpdateState;

							submittedState = $input.data( 'state' + updateNumber );
							sanitizedState = self._getInputState( $sanitizedInput );
							$input.data( 'sanitized', sanitizedState );

							canUpdateState = ( ! _.isEqual( submittedState, sanitizedState ) && ( args.ignoreActiveElement || ! $input.is( document.activeElement ) ) );
							if ( canUpdateState ) {
								self._setInputState( $input, sanitizedState );
							}
						} );

						$( document ).trigger( 'widget-synced', [ $widgetRoot, r.data.form ] );

					// Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled.
					} else if ( self.liveUpdateMode ) {
						self.liveUpdateMode = false;
						self.container.find( 'input[name="savewidget"]' ).show();
						isLiveUpdateAborted = true;

					// Otherwise, replace existing form with the sanitized form.
					} else {
						$widgetContent.html( r.data.form );

						self.container.removeClass( 'widget-form-disabled' );

						$( document ).trigger( 'widget-updated', [ $widgetRoot ] );
					}

					/**
					 * If the old instance is identical to the new one, there is nothing new
					 * needing to be rendered, and so we can preempt the event for the
					 * preview finishing loading.
					 */
					isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance );
					if ( isChanged ) {
						self.isWidgetUpdating = true; // Suppress triggering another updateWidget.
						self.setting( r.data.instance );
						self.isWidgetUpdating = false;
					} else {
						// No change was made, so stop the spinner now instead of when the preview would updates.
						self.container.removeClass( 'previewer-loading' );
					}

					if ( completeCallback ) {
						completeCallback.call( self, null, { noChange: ! isChanged, ajaxFinished: true } );
					}
				} else {
					// General error message.
					message = l10n.error;

					if ( r.data && r.data.message ) {
						message = r.data.message;
					}

					if ( completeCallback ) {
						completeCallback.call( self, message );
					} else {
						$widgetContent.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' );
					}
				}
			} );

			jqxhr.fail( function( jqXHR, textStatus ) {
				if ( completeCallback ) {
					completeCallback.call( self, textStatus );
				}
			} );

			jqxhr.always( function() {
				self.container.removeClass( 'widget-form-loading' );

				$inputs.each( function() {
					$( this ).removeData( 'state' + updateNumber );
				} );

				processing( processing() - 1 );
			} );
		},

		/**
		 * Expand the accordion section containing a control
		 */
		expandControlSection: function() {
			api.Control.prototype.expand.call( this );
		},

		/**
		 * @since 4.1.0
		 *
		 * @param {Boolean} expanded
		 * @param {Object} [params]
		 * @return {Boolean} False if state already applied.
		 */
		_toggleExpanded: api.Section.prototype._toggleExpanded,

		/**
		 * @since 4.1.0
		 *
		 * @param {Object} [params]
		 * @return {Boolean} False if already expanded.
		 */
		expand: api.Section.prototype.expand,

		/**
		 * Expand the widget form control
		 *
		 * @deprecated 4.1.0 Use this.expand() instead.
		 */
		expandForm: function() {
			this.expand();
		},

		/**
		 * @since 4.1.0
		 *
		 * @param {Object} [params]
		 * @return {Boolean} False if already collapsed.
		 */
		collapse: api.Section.prototype.collapse,

		/**
		 * Collapse the widget form control
		 *
		 * @deprecated 4.1.0 Use this.collapse() instead.
		 */
		collapseForm: function() {
			this.collapse();
		},

		/**
		 * Expand or collapse the widget control
		 *
		 * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide )
		 *
		 * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility
		 */
		toggleForm: function( showOrHide ) {
			if ( typeof showOrHide === 'undefined' ) {
				showOrHide = ! this.expanded();
			}
			this.expanded( showOrHide );
		},

		/**
		 * Respond to change in the expanded state.
		 *
		 * @param {boolean} expanded
		 * @param {Object} args  merged on top of this.defaultActiveArguments
		 */
		onChangeExpanded: function ( expanded, args ) {
			var self = this, $widget, $inside, complete, prevComplete, expandControl, $toggleBtn;

			self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI.
			if ( expanded ) {
				self.embedWidgetContent();
			}

			// If the expanded state is unchanged only manipulate container expanded states.
			if ( args.unchanged ) {
				if ( expanded ) {
					api.Control.prototype.expand.call( self, {
						completeCallback:  args.completeCallback
					});
				}
				return;
			}

			$widget = this.container.find( 'div.widget:first' );
			$inside = $widget.find( '.widget-inside:first' );
			$toggleBtn = this.container.find( '.widget-top button.widget-action' );

			expandControl = function() {

				// Close all other widget controls before expanding this one.
				api.control.each( function( otherControl ) {
					if ( self.params.type === otherControl.params.type && self !== otherControl ) {
						otherControl.collapse();
					}
				} );

				complete = function() {
					self.container.removeClass( 'expanding' );
					self.container.addClass( 'expanded' );
					$widget.addClass( 'open' );
					$toggleBtn.attr( 'aria-expanded', 'true' );
					self.container.trigger( 'expanded' );
				};
				if ( args.completeCallback ) {
					prevComplete = complete;
					complete = function () {
						prevComplete();
						args.completeCallback();
					};
				}

				if ( self.params.is_wide ) {
					$inside.fadeIn( args.duration, complete );
				} else {
					$inside.slideDown( args.duration, complete );
				}

				self.container.trigger( 'expand' );
				self.container.addClass( 'expanding' );
			};

			if ( $toggleBtn.attr( 'aria-expanded' ) === 'false' ) {
				if ( api.section.has( self.section() ) ) {
					api.section( self.section() ).expand( {
						completeCallback: expandControl
					} );
				} else {
					expandControl();
				}
			} else {
				complete = function() {
					self.container.removeClass( 'collapsing' );
					self.container.removeClass( 'expanded' );
					$widget.removeClass( 'open' );
					$toggleBtn.attr( 'aria-expanded', 'false' );
					self.container.trigger( 'collapsed' );
				};
				if ( args.completeCallback ) {
					prevComplete = complete;
					complete = function () {
						prevComplete();
						args.completeCallback();
					};
				}

				self.container.trigger( 'collapse' );
				self.container.addClass( 'collapsing' );

				if ( self.params.is_wide ) {
					$inside.fadeOut( args.duration, complete );
				} else {
					$inside.slideUp( args.duration, function() {
						$widget.css( { width:'', margin:'' } );
						complete();
					} );
				}
			}
		},

		/**
		 * Get the position (index) of the widget in the containing sidebar
		 *
		 * @return {number}
		 */
		getWidgetSidebarPosition: function() {
			var sidebarWidgetIds, position;

			sidebarWidgetIds = this.getSidebarWidgetsControl().setting();
			position = _.indexOf( sidebarWidgetIds, this.params.widget_id );

			if ( position === -1 ) {
				return;
			}

			return position;
		},

		/**
		 * Move widget up one in the sidebar
		 */
		moveUp: function() {
			this._moveWidgetByOne( -1 );
		},

		/**
		 * Move widget up one in the sidebar
		 */
		moveDown: function() {
			this._moveWidgetByOne( 1 );
		},

		/**
		 * @private
		 *
		 * @param {number} offset 1|-1
		 */
		_moveWidgetByOne: function( offset ) {
			var i, sidebarWidgetsSetting, sidebarWidgetIds,	adjacentWidgetId;

			i = this.getWidgetSidebarPosition();

			sidebarWidgetsSetting = this.getSidebarWidgetsControl().setting;
			sidebarWidgetIds = Array.prototype.slice.call( sidebarWidgetsSetting() ); // Clone.
			adjacentWidgetId = sidebarWidgetIds[i + offset];
			sidebarWidgetIds[i + offset] = this.params.widget_id;
			sidebarWidgetIds[i] = adjacentWidgetId;

			sidebarWidgetsSetting( sidebarWidgetIds );
		},

		/**
		 * Toggle visibility of the widget move area
		 *
		 * @param {boolean} [showOrHide]
		 */
		toggleWidgetMoveArea: function( showOrHide ) {
			var self = this, $moveWidgetArea;

			$moveWidgetArea = this.container.find( '.move-widget-area' );

			if ( typeof showOrHide === 'undefined' ) {
				showOrHide = ! $moveWidgetArea.hasClass( 'active' );
			}

			if ( showOrHide ) {
				// Reset the selected sidebar.
				$moveWidgetArea.find( '.selected' ).removeClass( 'selected' );

				$moveWidgetArea.find( 'li' ).filter( function() {
					return $( this ).data( 'id' ) === self.params.sidebar_id;
				} ).addClass( 'selected' );

				this.container.find( '.move-widget-btn' ).prop( 'disabled', true );
			}

			$moveWidgetArea.toggleClass( 'active', showOrHide );
		},

		/**
		 * Highlight the widget control and section
		 */
		highlightSectionAndControl: function() {
			var $target;

			if ( this.container.is( ':hidden' ) ) {
				$target = this.container.closest( '.control-section' );
			} else {
				$target = this.container;
			}

			$( '.highlighted' ).removeClass( 'highlighted' );
			$target.addClass( 'highlighted' );

			setTimeout( function() {
				$target.removeClass( 'highlighted' );
			}, 500 );
		}
	} );

	/**
	 * wp.customize.Widgets.WidgetsPanel
	 *
	 * Customizer panel containing the widget area sections.
	 *
	 * @since 4.4.0
	 *
	 * @class    wp.customize.Widgets.WidgetsPanel
	 * @augments wp.customize.Panel
	 */
	api.Widgets.WidgetsPanel = api.Panel.extend(/** @lends wp.customize.Widgets.WigetsPanel.prototype */{

		/**
		 * Add and manage the display of the no-rendered-areas notice.
		 *
		 * @since 4.4.0
		 */
		ready: function () {
			var panel = this;

			api.Panel.prototype.ready.call( panel );

			panel.deferred.embedded.done(function() {
				var panelMetaContainer, noticeContainer, updateNotice, getActiveSectionCount, shouldShowNotice;
				panelMetaContainer = panel.container.find( '.panel-meta' );

				// @todo This should use the Notifications API introduced to panels. See <https://core.trac.wordpress.org/ticket/38794>.
				noticeContainer = $( '<div></div>', {
					'class': 'no-widget-areas-rendered-notice'
				});
				panelMetaContainer.append( noticeContainer );

				/**
				 * Get the number of active sections in the panel.
				 *
				 * @return {number} Number of active sidebar sections.
				 */
				getActiveSectionCount = function() {
					return _.filter( panel.sections(), function( section ) {
						return 'sidebar' === section.params.type && section.active();
					} ).length;
				};

				/**
				 * Determine whether or not the notice should be displayed.
				 *
				 * @return {boolean}
				 */
				shouldShowNotice = function() {
					var activeSectionCount = getActiveSectionCount();
					if ( 0 === activeSectionCount ) {
						return true;
					} else {
						return activeSectionCount !== api.Widgets.data.registeredSidebars.length;
					}
				};

				/**
				 * Update the notice.
				 *
				 * @return {void}
				 */
				updateNotice = function() {
					var activeSectionCount = getActiveSectionCount(), someRenderedMessage, nonRenderedAreaCount, registeredAreaCount;
					noticeContainer.empty();

					registeredAreaCount = api.Widgets.data.registeredSidebars.length;
					if ( activeSectionCount !== registeredAreaCount ) {

						if ( 0 !== activeSectionCount ) {
							nonRenderedAreaCount = registeredAreaCount - activeSectionCount;
							someRenderedMessage = l10n.someAreasShown[ nonRenderedAreaCount ];
						} else {
							someRenderedMessage = l10n.noAreasShown;
						}
						if ( someRenderedMessage ) {
							noticeContainer.append( $( '<p></p>', {
								text: someRenderedMessage
							} ) );
						}

						noticeContainer.append( $( '<p></p>', {
							text: l10n.navigatePreview
						} ) );
					}
				};
				updateNotice();

				/*
				 * Set the initial visibility state for rendered notice.
				 * Update the visibility of the notice whenever a reflow happens.
				 */
				noticeContainer.toggle( shouldShowNotice() );
				api.previewer.deferred.active.done( function () {
					noticeContainer.toggle( shouldShowNotice() );
				});
				api.bind( 'pane-contents-reflowed', function() {
					var duration = ( 'resolved' === api.previewer.deferred.active.state() ) ? 'fast' : 0;
					updateNotice();
					if ( shouldShowNotice() ) {
						noticeContainer.slideDown( duration );
					} else {
						noticeContainer.slideUp( duration );
					}
				});
			});
		},

		/**
		 * Allow an active widgets panel to be contextually active even when it has no active sections (widget areas).
		 *
		 * This ensures that the widgets panel appears even when there are no
		 * sidebars displayed on the URL currently being previewed.
		 *
		 * @since 4.4.0
		 *
		 * @return {boolean}
		 */
		isContextuallyActive: function() {
			var panel = this;
			return panel.active();
		}
	});

	/**
	 * wp.customize.Widgets.SidebarSection
	 *
	 * Customizer section representing a widget area widget
	 *
	 * @since 4.1.0
	 *
	 * @class    wp.customize.Widgets.SidebarSection
	 * @augments wp.customize.Section
	 */
	api.Widgets.SidebarSection = api.Section.extend(/** @lends wp.customize.Widgets.SidebarSection.prototype */{

		/**
		 * Sync the section's active state back to the Backbone model's is_rendered attribute
		 *
		 * @since 4.1.0
		 */
		ready: function () {
			var section = this, registeredSidebar;
			api.Section.prototype.ready.call( this );
			registeredSidebar = api.Widgets.registeredSidebars.get( section.params.sidebarId );
			section.active.bind( function ( active ) {
				registeredSidebar.set( 'is_rendered', active );
			});
			registeredSidebar.set( 'is_rendered', section.active() );
		}
	});

	/**
	 * wp.customize.Widgets.SidebarControl
	 *
	 * Customizer control for widgets.
	 * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type
	 *
	 * @since 3.9.0
	 *
	 * @class    wp.customize.Widgets.SidebarControl
	 * @augments wp.customize.Control
	 */
	api.Widgets.SidebarControl = api.Control.extend(/** @lends wp.customize.Widgets.SidebarControl.prototype */{

		/**
		 * Set up the control
		 */
		ready: function() {
			this.$controlSection = this.container.closest( '.control-section' );
			this.$sectionContent = this.container.closest( '.accordion-section-content' );

			this._setupModel();
			this._setupSortable();
			this._setupAddition();
			this._applyCardinalOrderClassNames();
		},

		/**
		 * Update ordering of widget control forms when the setting is updated
		 */
		_setupModel: function() {
			var self = this;

			this.setting.bind( function( newWidgetIds, oldWidgetIds ) {
				var widgetFormControls, removedWidgetIds, priority;

				removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds );

				// Filter out any persistent widget IDs for widgets which have been deactivated.
				newWidgetIds = _( newWidgetIds ).filter( function( newWidgetId ) {
					var parsedWidgetId = parseWidgetId( newWidgetId );

					return !! api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } );
				} );

				widgetFormControls = _( newWidgetIds ).map( function( widgetId ) {
					var widgetFormControl = api.Widgets.getWidgetFormControlForWidget( widgetId );

					if ( ! widgetFormControl ) {
						widgetFormControl = self.addWidget( widgetId );
					}

					return widgetFormControl;
				} );

				// Sort widget controls to their new positions.
				widgetFormControls.sort( function( a, b ) {
					var aIndex = _.indexOf( newWidgetIds, a.params.widget_id ),
						bIndex = _.indexOf( newWidgetIds, b.params.widget_id );
					return aIndex - bIndex;
				});

				priority = 0;
				_( widgetFormControls ).each( function ( control ) {
					control.priority( priority );
					control.section( self.section() );
					priority += 1;
				});
				self.priority( priority ); // Make sure sidebar control remains at end.

				// Re-sort widget form controls (including widgets form other sidebars newly moved here).
				self._applyCardinalOrderClassNames();

				// If the widget was dragged into the sidebar, make sure the sidebar_id param is updated.
				_( widgetFormControls ).each( function( widgetFormControl ) {
					widgetFormControl.params.sidebar_id = self.params.sidebar_id;
				} );

				// Cleanup after widget removal.
				_( removedWidgetIds ).each( function( removedWidgetId ) {

					// Using setTimeout so that when moving a widget to another sidebar,
					// the other sidebars_widgets settings get a chance to update.
					setTimeout( function() {
						var removedControl, wasDraggedToAnotherSidebar, inactiveWidgets, removedIdBase,
							widget, isPresentInAnotherSidebar = false;

						// Check if the widget is in another sidebar.
						api.each( function( otherSetting ) {
							if ( otherSetting.id === self.setting.id || 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) || otherSetting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
								return;
							}

							var otherSidebarWidgets = otherSetting(), i;

							i = _.indexOf( otherSidebarWidgets, removedWidgetId );
							if ( -1 !== i ) {
								isPresentInAnotherSidebar = true;
							}
						} );

						// If the widget is present in another sidebar, abort!
						if ( isPresentInAnotherSidebar ) {
							return;
						}

						removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );

						// Detect if widget control was dragged to another sidebar.
						wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );

						// Delete any widget form controls for removed widgets.
						if ( removedControl && ! wasDraggedToAnotherSidebar ) {
							api.control.remove( removedControl.id );
							removedControl.container.remove();
						}

						// Move widget to inactive widgets sidebar (move it to Trash) if has been previously saved.
						// This prevents the inactive widgets sidebar from overflowing with throwaway widgets.
						if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {
							inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice();
							inactiveWidgets.push( removedWidgetId );
							api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() );
						}

						// Make old single widget available for adding again.
						removedIdBase = parseWidgetId( removedWidgetId ).id_base;
						widget = api.Widgets.availableWidgets.findWhere( { id_base: removedIdBase } );
						if ( widget && ! widget.get( 'is_multi' ) ) {
							widget.set( 'is_disabled', false );
						}
					} );

				} );
			} );
		},

		/**
		 * Allow widgets in sidebar to be re-ordered, and for the order to be previewed
		 */
		_setupSortable: function() {
			var self = this;

			this.isReordering = false;

			/**
			 * Update widget order setting when controls are re-ordered
			 */
			this.$sectionContent.sortable( {
				items: '> .customize-control-widget_form',
				handle: '.widget-top',
				axis: 'y',
				tolerance: 'pointer',
				connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
				update: function() {
					var widgetContainerIds = self.$sectionContent.sortable( 'toArray' ), widgetIds;

					widgetIds = $.map( widgetContainerIds, function( widgetContainerId ) {
						return $( '#' + widgetContainerId ).find( ':input[name=widget-id]' ).val();
					} );

					self.setting( widgetIds );
				}
			} );

			/**
			 * Expand other Customizer sidebar section when dragging a control widget over it,
			 * allowing the control to be dropped into another section
			 */
			this.$controlSection.find( '.accordion-section-title' ).droppable({
				accept: '.customize-control-widget_form',
				over: function() {
					var section = api.section( self.section.get() );
					section.expand({
						allowMultiple: true, // Prevent the section being dragged from to be collapsed.
						completeCallback: function () {
							// @todo It is not clear when refreshPositions should be called on which sections, or if it is even needed.
							api.section.each( function ( otherSection ) {
								if ( otherSection.container.find( '.customize-control-sidebar_widgets' ).length ) {
									otherSection.container.find( '.accordion-section-content:first' ).sortable( 'refreshPositions' );
								}
							} );
						}
					});
				}
			});

			/**
			 * Keyboard-accessible reordering
			 */
			this.container.find( '.reorder-toggle' ).on( 'click', function() {
				self.toggleReordering( ! self.isReordering );
			} );
		},

		/**
		 * Set up UI for adding a new widget
		 */
		_setupAddition: function() {
			var self = this;

			this.container.find( '.add-new-widget' ).on( 'click', function() {
				var addNewWidgetBtn = $( this );

				if ( self.$sectionContent.hasClass( 'reordering' ) ) {
					return;
				}

				if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) {
					addNewWidgetBtn.attr( 'aria-expanded', 'true' );
					api.Widgets.availableWidgetsPanel.open( self );
				} else {
					addNewWidgetBtn.attr( 'aria-expanded', 'false' );
					api.Widgets.availableWidgetsPanel.close();
				}
			} );
		},

		/**
		 * Add classes to the widget_form controls to assist with styling
		 */
		_applyCardinalOrderClassNames: function() {
			var widgetControls = [];
			_.each( this.setting(), function ( widgetId ) {
				var widgetControl = api.Widgets.getWidgetFormControlForWidget( widgetId );
				if ( widgetControl ) {
					widgetControls.push( widgetControl );
				}
			});

			if ( 0 === widgetControls.length || ( 1 === api.Widgets.registeredSidebars.length && widgetControls.length <= 1 ) ) {
				this.container.find( '.reorder-toggle' ).hide();
				return;
			} else {
				this.container.find( '.reorder-toggle' ).show();
			}

			$( widgetControls ).each( function () {
				$( this.container )
					.removeClass( 'first-widget' )
					.removeClass( 'last-widget' )
					.find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 );
			});

			_.first( widgetControls ).container
				.addClass( 'first-widget' )
				.find( '.move-widget-up' ).prop( 'tabIndex', -1 );

			_.last( widgetControls ).container
				.addClass( 'last-widget' )
				.find( '.move-widget-down' ).prop( 'tabIndex', -1 );
		},


		/***********************************************************************
		 * Begin public API methods
		 **********************************************************************/

		/**
		 * Enable/disable the reordering UI
		 *
		 * @param {boolean} showOrHide to enable/disable reordering
		 *
		 * @todo We should have a reordering state instead and rename this to onChangeReordering
		 */
		toggleReordering: function( showOrHide ) {
			var addNewWidgetBtn = this.$sectionContent.find( '.add-new-widget' ),
				reorderBtn = this.container.find( '.reorder-toggle' ),
				widgetsTitle = this.$sectionContent.find( '.widget-title' );

			showOrHide = Boolean( showOrHide );

			if ( showOrHide === this.$sectionContent.hasClass( 'reordering' ) ) {
				return;
			}

			this.isReordering = showOrHide;
			this.$sectionContent.toggleClass( 'reordering', showOrHide );

			if ( showOrHide ) {
				_( this.getWidgetFormControls() ).each( function( formControl ) {
					formControl.collapse();
				} );

				addNewWidgetBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
				reorderBtn.attr( 'aria-label', l10n.reorderLabelOff );
				wp.a11y.speak( l10n.reorderModeOn );
				// Hide widget titles while reordering: title is already in the reorder controls.
				widgetsTitle.attr( 'aria-hidden', 'true' );
			} else {
				addNewWidgetBtn.removeAttr( 'tabindex aria-hidden' );
				reorderBtn.attr( 'aria-label', l10n.reorderLabelOn );
				wp.a11y.speak( l10n.reorderModeOff );
				widgetsTitle.attr( 'aria-hidden', 'false' );
			}
		},

		/**
		 * Get the widget_form Customize controls associated with the current sidebar.
		 *
		 * @since 3.9.0
		 * @return {wp.customize.controlConstructor.widget_form[]}
		 */
		getWidgetFormControls: function() {
			var formControls = [];

			_( this.setting() ).each( function( widgetId ) {
				var settingId = widgetIdToSettingId( widgetId ),
					formControl = api.control( settingId );
				if ( formControl ) {
					formControls.push( formControl );
				}
			} );

			return formControls;
		},

		/**
		 * @param {string} widgetId or an id_base for adding a previously non-existing widget.
		 * @return {Object|false} widget_form control instance, or false on error.
		 */
		addWidget: function( widgetId ) {
			var self = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor,
				parsedWidgetId = parseWidgetId( widgetId ),
				widgetNumber = parsedWidgetId.number,
				widgetIdBase = parsedWidgetId.id_base,
				widget = api.Widgets.availableWidgets.findWhere( {id_base: widgetIdBase} ),
				settingId, isExistingWidget, widgetFormControl, sidebarWidgets, settingArgs, setting;

			if ( ! widget ) {
				return false;
			}

			if ( widgetNumber && ! widget.get( 'is_multi' ) ) {
				return false;
			}

			// Set up new multi widget.
			if ( widget.get( 'is_multi' ) && ! widgetNumber ) {
				widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 );
				widgetNumber = widget.get( 'multi_number' );
			}

			controlHtml = $( '#widget-tpl-' + widget.get( 'id' ) ).html().trim();
			if ( widget.get( 'is_multi' ) ) {
				controlHtml = controlHtml.replace( /<[^<>]+>/g, function( m ) {
					return m.replace( /__i__|%i%/g, widgetNumber );
				} );
			} else {
				widget.set( 'is_disabled', true ); // Prevent single widget from being added again now.
			}

			$widget = $( controlHtml );

			controlContainer = $( '<li/>' )
				.addClass( 'customize-control' )
				.addClass( 'customize-control-' + controlType )
				.append( $widget );

			// Remove icon which is visible inside the panel.
			controlContainer.find( '> .widget-icon' ).remove();

			if ( widget.get( 'is_multi' ) ) {
				controlContainer.find( 'input[name="widget_number"]' ).val( widgetNumber );
				controlContainer.find( 'input[name="multi_number"]' ).val( widgetNumber );
			}

			widgetId = controlContainer.find( '[name="widget-id"]' ).val();

			controlContainer.hide(); // To be slid-down below.

			settingId = 'widget_' + widget.get( 'id_base' );
			if ( widget.get( 'is_multi' ) ) {
				settingId += '[' + widgetNumber + ']';
			}
			controlContainer.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) );

			// Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget).
			isExistingWidget = api.has( settingId );
			if ( ! isExistingWidget ) {
				settingArgs = {
					transport: api.Widgets.data.selectiveRefreshableWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh',
					previewer: this.setting.previewer
				};
				setting = api.create( settingId, settingId, '', settingArgs );
				setting.set( {} ); // Mark dirty, changing from '' to {}.
			}

			controlConstructor = api.controlConstructor[controlType];
			widgetFormControl = new controlConstructor( settingId, {
				settings: {
					'default': settingId
				},
				content: controlContainer,
				sidebar_id: self.params.sidebar_id,
				widget_id: widgetId,
				widget_id_base: widget.get( 'id_base' ),
				type: controlType,
				is_new: ! isExistingWidget,
				width: widget.get( 'width' ),
				height: widget.get( 'height' ),
				is_wide: widget.get( 'is_wide' )
			} );
			api.control.add( widgetFormControl );

			// Make sure widget is removed from the other sidebars.
			api.each( function( otherSetting ) {
				if ( otherSetting.id === self.setting.id ) {
					return;
				}

				if ( 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) ) {
					return;
				}

				var otherSidebarWidgets = otherSetting().slice(),
					i = _.indexOf( otherSidebarWidgets, widgetId );

				if ( -1 !== i ) {
					otherSidebarWidgets.splice( i );
					otherSetting( otherSidebarWidgets );
				}
			} );

			// Add widget to this sidebar.
			sidebarWidgets = this.setting().slice();
			if ( -1 === _.indexOf( sidebarWidgets, widgetId ) ) {
				sidebarWidgets.push( widgetId );
				this.setting( sidebarWidgets );
			}

			controlContainer.slideDown( function() {
				if ( isExistingWidget ) {
					widgetFormControl.updateWidget( {
						instance: widgetFormControl.setting()
					} );
				}
			} );

			return widgetFormControl;
		}
	} );

	// Register models for custom panel, section, and control types.
	$.extend( api.panelConstructor, {
		widgets: api.Widgets.WidgetsPanel
	});
	$.extend( api.sectionConstructor, {
		sidebar: api.Widgets.SidebarSection
	});
	$.extend( api.controlConstructor, {
		widget_form: api.Widgets.WidgetControl,
		sidebar_widgets: api.Widgets.SidebarControl
	});

	/**
	 * Init Customizer for widgets.
	 */
	api.bind( 'ready', function() {
		// Set up the widgets panel.
		api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
			collection: api.Widgets.availableWidgets
		});

		// Highlight widget control.
		api.previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );

		// Open and focus widget control.
		api.previewer.bind( 'focus-widget-control', api.Widgets.focusWidgetFormControl );
	} );

	/**
	 * Highlight a widget control.
	 *
	 * @param {string} widgetId
	 */
	api.Widgets.highlightWidgetFormControl = function( widgetId ) {
		var control = api.Widgets.getWidgetFormControlForWidget( widgetId );

		if ( control ) {
			control.highlightSectionAndControl();
		}
	},

	/**
	 * Focus a widget control.
	 *
	 * @param {string} widgetId
	 */
	api.Widgets.focusWidgetFormControl = function( widgetId ) {
		var control = api.Widgets.getWidgetFormControlForWidget( widgetId );

		if ( control ) {
			control.focus();
		}
	},

	/**
	 * Given a widget control, find the sidebar widgets control that contains it.
	 * @param {string} widgetId
	 * @return {Object|null}
	 */
	api.Widgets.getSidebarWidgetControlContainingWidget = function( widgetId ) {
		var foundControl = null;

		// @todo This can use widgetIdToSettingId(), then pass into wp.customize.control( x ).getSidebarWidgetsControl().
		api.control.each( function( control ) {
			if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widgetId ) ) {
				foundControl = control;
			}
		} );

		return foundControl;
	};

	/**
	 * Given a widget ID for a widget appearing in the preview, get the widget form control associated with it.
	 *
	 * @param {string} widgetId
	 * @return {Object|null}
	 */
	api.Widgets.getWidgetFormControlForWidget = function( widgetId ) {
		var foundControl = null;

		// @todo We can just use widgetIdToSettingId() here.
		api.control.each( function( control ) {
			if ( control.params.type === 'widget_form' && control.params.widget_id === widgetId ) {
				foundControl = control;
			}
		} );

		return foundControl;
	};

	/**
	 * Initialize Edit Menu button in Nav Menu widget.
	 */
	$( document ).on( 'widget-added', function( event, widgetContainer ) {
		var parsedWidgetId, widgetControl, navMenuSelect, editMenuButton;
		parsedWidgetId = parseWidgetId( widgetContainer.find( '> .widget-inside > .form > .widget-id' ).val() );
		if ( 'nav_menu' !== parsedWidgetId.id_base ) {
			return;
		}
		widgetControl = api.control( 'widget_nav_menu[' + String( parsedWidgetId.number ) + ']' );
		if ( ! widgetControl ) {
			return;
		}
		navMenuSelect = widgetContainer.find( 'select[name*="nav_menu"]' );
		editMenuButton = widgetContainer.find( '.edit-selected-nav-menu > button' );
		if ( 0 === navMenuSelect.length || 0 === editMenuButton.length ) {
			return;
		}
		navMenuSelect.on( 'change', function() {
			if ( api.section.has( 'nav_menu[' + navMenuSelect.val() + ']' ) ) {
				editMenuButton.parent().show();
			} else {
				editMenuButton.parent().hide();
			}
		});
		editMenuButton.on( 'click', function() {
			var section = api.section( 'nav_menu[' + navMenuSelect.val() + ']' );
			if ( section ) {
				focusConstructWithBreadcrumb( section, widgetControl );
			}
		} );
	} );

	/**
	 * Focus (expand) one construct and then focus on another construct after the first is collapsed.
	 *
	 * This overrides the back button to serve the purpose of breadcrumb navigation.
	 *
	 * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} focusConstruct - The object to initially focus.
	 * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} returnConstruct - The object to return focus.
	 */
	function focusConstructWithBreadcrumb( focusConstruct, returnConstruct ) {
		focusConstruct.focus();
		function onceCollapsed( isExpanded ) {
			if ( ! isExpanded ) {
				focusConstruct.expanded.unbind( onceCollapsed );
				returnConstruct.focus();
			}
		}
		focusConstruct.expanded.bind( onceCollapsed );
	}

	/**
	 * @param {string} widgetId
	 * @return {Object}
	 */
	function parseWidgetId( widgetId ) {
		var matches, parsed = {
			number: null,
			id_base: null
		};

		matches = widgetId.match( /^(.+)-(\d+)$/ );
		if ( matches ) {
			parsed.id_base = matches[1];
			parsed.number = parseInt( matches[2], 10 );
		} else {
			// Likely an old single widget.
			parsed.id_base = widgetId;
		}

		return parsed;
	}

	/**
	 * @param {string} widgetId
	 * @return {string} settingId
	 */
	function widgetIdToSettingId( widgetId ) {
		var parsed = parseWidgetId( widgetId ), settingId;

		settingId = 'widget_' + parsed.id_base;
		if ( parsed.number ) {
			settingId += '[' + parsed.number + ']';
		}

		return settingId;
	}

})( window.wp, jQuery );

lucky8 France – Affy Pharma Pvt Ltd https://affypharma.com Pharmaceutical, Nutra, Cosmetics Manufacturer in India Mon, 11 Dec 2023 00:43:56 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://affypharma.com/wp-content/uploads/2020/01/153026176286385652-Copy-150x150.png lucky8 France – Affy Pharma Pvt Ltd https://affypharma.com 32 32 Lucky8 : retrait en combien de temps https://affypharma.com/lucky8-retrait-en-combien-de-temps/ https://affypharma.com/lucky8-retrait-en-combien-de-temps/#respond Mon, 11 Dec 2023 00:43:56 +0000 https://affypharma.com/?p=2263 Lucky8 : retrait en combien de temps ?

Comment retirer et déposer sur Lucky8 Casino Dépôt Retrait

Content

Il y a plusieurs offres qui en font partie et la majorité des jeux proposés sont également concernés. Aussi, avec le casino en ligne Lucky 8, vous bénéficiez d’une promotion intéressante nommée Surprise Saturday. À l’instar de celle disponible les vendredis, celle-ci est une forme de bonus sans dépôt. Elle vous permet de jouer gratuitement et de gagner des sous sans miser. En effet, vous pouvez gagner 25% de vos trois premières mises dans la limite de 200 €.

Lorsque vous cliquez sur le bouton ”retrait”, vous verrez une liste des moyens de paiements. Vous n’aurez donc qu’à opérer un choix judicieux en fonction du moyen qui vous serait abordable. Ensuite, il faut choisir la somme à retirer et valider son retrait. C’est un moyen de plus en plus utilisé par les internautes, pour leur fiabilité. Par contre, il est bien conseillé de se renseigner sur les différents modes de paiement qui sont compatibles avec votre pays. Le mode démo vous permet de vous familiariser avec les casinos en ligne, et de tester tous les jeux du site avant de faire votre premier dépôt, les tours sont gratuits ou avec de l’argent fictif proposé par le site.

Comment déposer et retirer sur Lucky8

Une fois connecté à votre compte, rendez-vous dans la section « Bonus » ou « Promotions » du site. Là, vous trouverez les offres disponibles, y compris le bonus souhaité. En tant que joueur averti, embarquez pour cette odyssée ludique, explorez les méandres de Lucky8 et faites de chaque instant une célébration du jeu. Les bonus doivent être appréciés en tant que prolongement de l’expérience de jeu d’un joueur, et seulement en tant que tel. Lucky8 se réserve le droit d’examiner tout compte client afin de s’assurer que les parties sont en harmonie avec nos règles .

  • Cet “établissement privé” est réservé aux joueurs les plus fidèles de Lucky8.
  • Pour faire un dépôt sur votre compte, vous n’aurez qu’à vous rendre dans l’onglet dépôt et les différentes solutions précitées s’afficheront.
  • Jouez au casino pour le plaisir et ne déposez que ce que vous pouvez vous permettre de perdre.
  • L’on peut considérer cela comme un avantage pour ces moyens de paiements sur le casino en ligne Lucky8.

Que vous soyez un nouvel arrivant cherchant une offre de bienvenue ou un habitué en quête de récompenses régulières, les bonus chez Lucky 8 Casino apportent une dimension enrichissante à votre expérience de jeu. Découvrez les promotions captivantes qui rehaussent votre parcours de jeu, promettant des avantages et des surprises à chaque étape. Entrez dans le monde des bonus chez Lucky 8 Casino, où l’excitation et les récompenses sont au rendez-vous. Notre regard bienveillant sur Lucky8 le qualifie sans réserve de casino en ligne d’exception, une oasis virtuelle où les joueurs sont choyés par une pléthore de divertissements exquis.

Tout savoir sur le retrait Lucky8

Fabrice MENARD est un auteur expérimenté qui côtoie l’industrie des jeux d’argent en ligne depuis plus de 8 ans. Propriétaire, éditeur et auteur de casinos-en-ligne.fr, c’est l’un des rares du domaine à mettre à la disposition des lecteurs un contenu 100% fiable et sans parti pris. Tous les sites français en ligne ont des conditions générales d’utilisation. À l’instar des termes et conditions, il faut les lire intégralement afin de ne pas enfreindre les différentes clauses qui y sont mentionnées. C’est l’une des causes du retard ou même du refus de paiement par certains sites.

  • Pour finir de vous convaincre, nous avons réuni pour vous les avis Lucky8 et les différents commentaires.
  • Il est important lorsque l’on est amené à choisir un casino de prendre en compte la qualité de son interface ainsi que les fonctionnalités offertes par la plateforme.
  • En cliquant dessus, vous allez recevoir un formulaire, avec plusieurs cases à remplir.
  • Une fois connecté à votre compte, rendez-vous dans la section « Bonus » ou « Promotions » du site.

Vous pouvez utiliser plusieurs moyens de paiement ( comme on va le voir un peu plus en bas ). Vous pouvez encaisser vos gains sans frais, à tout moment, via votre mode de paiement préféré sur Lucky8. Néanmoins, il faudra tenir compte de plusieurs règles si vous souhaitez que votre demande de retrait aboutisse. Compte tenu de la qualité des prestations de Lucky8 en termes de retrait, on comprend plus aisément son positionnement sur le marché https://lucky8fr1.com/free-spins/.

Comment vérifier son compte Lucky 8 Casino  ? L’étape obligatoire avant de retirer ses gains

Passé ce délai, le bonus sera supprimé et les gains correspondants seront également perdus. Veuillez noter que Lucky8 peut également avoir besoin de vérifier votre identité avant de traiter votre retrait. Il s’agit d’une procédure standard dans la plupart des casinos en ligne, qui vise à prévenir les fraudes. Vous devrez fournir une preuve d’identité, telle qu’un passeport ou un permis de conduire, ainsi qu’une preuve d’adresse, telle qu’une facture ou un relevé bancaire. Une fois votre compte vérifié, votre retrait sera traité aussi rapidement que possible. Avec une forte emphase sur la qualité et la variété, Lucky8 Casino offre une expérience de jeu immersive inégalée dans l’industrie.

Ne considérez pas le casino comme un moyen de gagner de l’argent. Jouez au casino pour le plaisir et ne déposez que ce que vous pouvez vous permettre de perdre. Lucky8 propose plusieurs machines à sous avec des jackpots progressifs, offrant aux joueurs la chance de remporter d’importants gains cumulés. Le programme de fidélité de Lucky8 propose des récompenses exclusives, des bonus personnalisés et d’autres avantages pour les joueurs réguliers. Cependant, il est crucial de jouer de manière responsable en fixant des limites de paris et en comprenant les risques associés au jeu en ligne. La décision de jouer au casino Lucky8 dépendra donc des préférences individuelles, de la recherche préalable sur le casino, et du niveau de confort de chaque joueur par rapport à l’environnement de jeu en ligne.

Les moyens de paiement disponibles pour un retrait Lucky8

Une option similaire se trouve dans la section «Mon compte» – «Paramètres» de votre compte Joueur. Si vous avez des problèmes ou des questions concernant le processus de retrait sur Lucky8 Casino, vous pouvez contacter le service clientèle en cliquant sur le bouton « Aide » en bas de la page. Il n’y a pas de frais associés à un retrait de votre compte Lucky8. Cependant, veuillez noter que votre banque ou votre processeur de paiement peut facturer des frais pour le traitement de la transaction. Nous vous recommandons de vérifier au préalable avec votre banque ou votre processeur de paiement afin d’éviter toute surprise. Nous espérons que cet article a permis de clarifier toutes les questions que vous vous posiez sur la procédure de retrait à Lucky8 Casino.

  • Il n’y a même pas besoin d’être inscrit pour profiter de ce mode de jeux, en se rendant simplement sur son site web, vous pourrez jouer à des jeux gratuits et faire de test avant inscription.
  • Il dispose des méthodes de paiement les plus rapides et sans frais à l’instar des cryptomonnaies, des portefeuilles électroniques ou des cartes prépayées.
  • Une fois que vous avez pris connaissance de toutes les informations, acceptez le bonus et commencez à profiter des avantages qu’il offre dans le monde captivant de Lucky8.
  • Vous devrez fournir une preuve d’identité, telle qu’un passeport ou un permis de conduire, ainsi qu’une preuve d’adresse, telle qu’une facture ou un relevé bancaire.

Les retraits sont protégés par la présence d’une licence de jeu en cours de validité, et par l’utilisation d’un système SSL 256 bit qui crypte les données privées. Cette option est un peu moins en vue puisque les joueurs préfèrent généralement le virement bancaire, qui est plus simple et plus rassurant. De plus, il est possible de mémoriser vos coordonnées sur Lucky8 pour gagner plus de temps sur les futurs retraits. Cela signifie que la demande de retrait a été envoyée au service comptable et sera traitée dans les plus brefs délais.

Comment faire un retrait sur Lucky8

Un geste accueillant qui érige Lucky8 en forteresse de générosité dans le royaume des casinos en ligne. En tête de ces atouts, un généreux bonus de bienvenue se profile comme une invitation séduisante à l’univers ludique de Lucky8. Ce geste chaleureux de la part du casino offre une expérience d’inscription des plus gratifiantes, propulsant les nouveaux joueurs dans une aventure immersive dès le départ. Cela nous permet de garantir à nos clients des conditions de navigation sûres. Lucky8 Casino s’engage à protéger votre vie privée et vos informations personnelles.

  • À l’instar de celle disponible les vendredis, celle-ci est une forme de bonus sans dépôt.
  • Les virements bancaires sont également des moyens de paiement appropriés pour faire un retrait en ligne d’une grande somme.
  • En effet, en allongeant la durée de cotisation pour tous, le texte du gouvernement permet mécaniquement au régime de retraite complémentaire de garnir ses caisses.
  • En effet, il vous permet de suivre en temps réel l’avancée du traitement de votre retrait Lucky8.
  • En explorant ces différentes avenues, les joueurs peuvent augmenter leurs chances de gagner gros sur Lucky 8 Casino tout en préservant l’aspect ludique du jeu en ligne.

Le catalogue des meilleurs jeux du casino Lucky8 ne figure pas parmi les plus massifs du marché, cependant les titres disponibles ont été créés par les meilleurs éditeurs de logiciels du gaming. Avec plus de jeux disponibles provenant des meilleurs éditeurs du marché, vous allez avoir l’embarras du choix sur la plateforme. Une fois votre inscription finalisée et votre premier dépôt effectué, vous allez pouvoir profiter du Lucky8 bonus de bienvenue pouvant aller jusqu’à 200 €. Le site étant disponible en français, les joueurs pourront très aisément suivre les étapes à respecter pour finaliser l’enregistrement qui ne devrait pas prendre plus d’une minute.

Comment s’inscrire sur Lucky8 Casino ?

Profitez sur le casino en ligne Lucky8 de 200% jusqu’à 500€ de bonus + 500 free spins pour 50€ déposés. En jouant avec un bonus sur Lucky8 vous avez l’obligation de ne pas dépasser la mise maximale de 5€ par tour de jeu et 0,50€ par ligne. Vous n’avez pas le droit d’effectuer des paris de 25% ou plus de votre solde total sur un seul tour de jeu.

  • Par exemple, vous ne pouvez retirer que le montant que vous avez déposé au cours d’une période de 24 heures.
  • Les joueurs peuvent participer à des parties de blackjack, de roulette, de poker et de baccarat en temps réel, et interagir avec les croupiers et les autres joueurs grâce à un chat en direct.
  • Vous allez pouvoir effectuer vos dépôts à l’aide d’un ensemble de moyens de paiement assez variés et totalement fiables.

Une fois cette phase franchie, il est temps d’accéder à l’interface de retrait pour récupérer ses gains. Contrairement à ce que nous pensions au préalable, retirer ses gains sur cette plateforme s’est révélé être beaucoup plus facile que prévu. Nous avons beaucoup aimé le processus de retrait de cet établissement de jeux, et nous allons à présent vous expliquer dans les détails la marche à suivre. Il faudra prévoir jusqu’à 5 jours bancaires après la validation du retrait Lucky8 pour recevoir votre paiement. Lucky8 Casino prend toutes les dispositions nécessaires contre le blanchiment de capitaux, le financement du terrorisme et autres activités illégales ou criminelles. Toutes les transactions sont passées à la loupe et de fait, le compte du joueur peut être suspendu, bloqué ou fermé.

Les meilleures machines à sous chez Lucky 8 Casino

Une fois que vous avez sélectionné votre méthode de paiement, entrez les informations demandées et les fonds seront crédités sur votre compte Lucky8 dans les délais indiqués. Rien à dire, Lucky8 Casino est une plateforme totalement sécurisée. Ce n’est pas pour rien que les avis sur les retraits sont plutôt positifs sur internet. Les joueurs ne semblent jamais rencontrer de difficulté avec cet opérateur et c’est très bon signe !

  • La décision de jouer au casino Lucky8 dépendra donc des préférences individuelles, de la recherche préalable sur le casino, et du niveau de confort de chaque joueur par rapport à l’environnement de jeu en ligne.
  • Cela signifie que le service financier du casino est entrain de le vérifier et de le valider.
  • Ces machines offrent statistiquement de meilleures chances de gains à long terme.
  • C’est une équipe très performante, qui a une expérience de plus de 15 ans, sur le marché, et qui connait tout ce qui peut intéresser les joueurs français et comment les conquérir avec efficacité.
  • 2- Il vous faudra fournir un justificatif de domicile daté de moins de 3 mois lors de l’envoi.

Cette étape est également très facile, puisqu’il faut seulement veiller à ne pas être en dessous du montant minimum de retrait, qui est de 30 €. Par la suite, la première chose à faire est de sélectionner le moyen de paiement de votre choix pour ce retrait. Pour cela, il suffit de se rendre sur l’inscription Vous avez déjà un compte ? Cliquez ici, en haut à droite de la page d’accueil, puis d’entrer notre adresse mail et notre mot de passe. Bien évidemment, la première étape que nous avons dû franchir a été de se connecter à notre compte joueur.

Méthodes de retrait disponibles sur Lucky8

Cela nous permet de vivre notre aventure en toute confiance, car on sait que les conseillers sont toujours là, prêts à nous aider à n’importe quel moment. Abordons maintenant les aspects pratiques des retraits et des dépôts sur Lucky8 Casino. Comprendre ces processus est crucial pour une expérience de jeu sans accroc. Entrez dans ce guide complet sur les transactions financières sur le Lucky8 Casino.

  • Vous avez réalisé des premiers gains sur votre machine à sous préférée sur Lucky8 !
  • A l’image de ce qui se pratique dans certains pays d’Europe du Nord, l’idée est d’instaurer un « contrat de travail particulier » ouvrant droit à temps partiel au-delà de 55 ans.
  • Énormément de mes lecteurs viennent vers moi après avoir effectués leurs premiers gains, c’est pourquoi j’ai décidé de les aider en donnant toutes les clefs pour récupérer votre argent.

Si celui-ci n’est pas disponible dans les options de retrait, libre à vous de vous tourner vers une méthode alternative. En tout cas, le casino sans depot ne déduit aucun frais sur ce type de transaction monétaire. Un nombre important de raisons de refus de votre retrait peuvent exister. C’est pourquoi je trouve important que vous consultiez les règles et termes de Lucky8 directement sur le site, afin de ne jamais violer les conditions et ne pas être face à une situation embêtante. Dans tous les cas, le support se montre toujours très disponible et là pour vous aider. Maintenant que votre retrait a bien été demandé, vous le voyez désormais apparaître comme “En attente” dans votre historique de transactions.

Les autres jeux : qu’en penser ?

Vous ne pouvez que vous conformer à ceux que l’on vous propose uniquement. Le fonctionnement des opérations de versement à travers ces moyens de paiement, n’est pas toujours fiable. Il peut arriver des moments où le casino en ligne ne souhaite pas opérer sur un portefeuille donné, surtout pour un retrait d’une grande somme. Comme l’on l’avait expliqué, lucky8 casino ne présente pas un seul moyen de paiement. Ces mêmes moyens sont aussi ceux qui vous pouvez choisir pour faire un retrait.

  • Lucky8 offre également la possibilité à sa communauté de jouer gratuitement grâce au mode démo.
  • Lucky8 est selon nous un casino en ligne exceptionnel qui offre à ses joueurs un très grand choix de jeux.
  • De là, vous devrez sélectionner l’option « Retrait » et choisir votre méthode de retrait préférée.
  • Cette politique dynamique offre aux joueurs une diversité d’avantages, propices à l’amplification du plaisir de jouer et à l’optimisation des opportunités de gains.

Dans notre avis sur lucky8casino nous avons constaté que c’est un casino fiable qui a vu le jour en 2017 et qui détient une licence de jeu gaming légale délivrée par les autorités de Curaçao. A l’image de ce qui se pratique dans certains pays d’Europe du Nord, l’idée est d’instaurer un « contrat de travail particulier » ouvrant droit à temps partiel au-delà de 55 ans. A titre d’exemple, le salarié pourrait travailler en quatre cinquièmes (80 % du temps), tout en étant payé 90 % de son salaire et en cotisant à 100 % pour la retraite.

Quelles sont les conditions de bonus du casino Lucky8 ?

Mais, si vous avez choisi les portefeuilles électroniques, vos transactions se feront instantanément. Vous le savez sans aucun doute, il y a sur le marché des établissements qui mettent à la disposition des joueurs une multitude de méthodes de paiement fiables, populaires et sécurisées. Parmi les sites que je peux vous recommander, il y a Amon Casino qui fait énormément parler de lui depuis sa sortie. Vous pouvez y retirer vos gains par virement bancaire, par cryptomonnaies (Bitcoin, Litecoin, Ethereum) ou avec les portefeuilles électroniques.

  • En explorant ces astuces, vous pouvez maximiser votre expérience de jeu, en mettant en avant des stratégies intelligentes pour tirer le meilleur parti des machines à sous proposées sur la plateforme.
  • Il vous faudra compléter un wager de x40, ce qui n’est pas insurmontable, afin de pouvoir retirer vos futurs potentiels gains !
  • En général, il n’y a pas de règles ou principes particuliers à propos d’un dépôt ou retrait en ligne sur lucky8.
  • Attention, la limite de retrait sur Lucky8 est de 2500 euros par semaine !

Profitez sur le casino en ligne Lucky8 d’un bonus de bienvenue de 200% jusqu’à 500€ + 500 free spins pour 50€ déposés. Pour conclure, on peut donc dire que le casino en ligne Lucky 8 est un casino très fiable et notre avis reste vraiment positif. On conseille ce casino à tous les joueurs débutants soient-ils ou confirmés. Tout le monde sera certainement satisfait de son expérience sur le site. Lucky8 peut éventuellement refuser votre demande de retrait s’il constate des manquements aux termes et conditions du site. Si c’est le cas, essayez de corriger chacune des erreurs (compte non validé par KYC, wagers non respectés, etc.) mentionnées dans l’email de refus.

Support client sur Lucky8 Casino

Le temps nécessaire pour que votre retrait soit traité et que les fonds vous parviennent dépend de la méthode que vous choisissez. Une fois que vous avez sélectionné votre méthode de retrait, vous devez saisir le montant que vous souhaitez retirer. Vous avez la possibilité sur l’interface de Lucky8 de rechercher les jeux en fonction de l’éditeur, ce qui est pratique si vous avez envie de privilégier un studio plus qu’un autre. Nous avons passé en revue le site de Lucky8 en nous basant sur des critères de sélection essentiels lorsque l’on est amené à choisir un casino. Il n’est pas possible de proposer sa candidature au Club Privilège.

  • Tout à fait, il est possible d’utiliser son portefeuille de crypto-monnaie pour faire des dépôts et bénéficier du bonus de bienvenue exclusif jusqu’à 200 € offerts.
  • Le monde du gaming en ligne évolue rapidement, et Lucky8 est à la hauteur des attentes.
  • Un mail de confirmation sera alors envoyé à votre boite de réception.
  • De plus, il est possible de mémoriser vos coordonnées sur Lucky8 pour gagner plus de temps sur les futurs retraits.
  • Il acquiert rapidement de l’expérience notamment grâce à son aîné, le géant et leader francophone Cresus casino.

Dans l’optique de mettre toutes vos chances de votre côté, nous vous conseillons de privilégier des jeux ayant un taux de redistribution le plus élevé possible. Le wagering (nombre de mises qu’il faudra faire pour débloquer le montant du bonus) est fixé à 40 fois. C’est beaucoup moins que la plupart des sites où il faut parfois miser 70 fois un bonus pour retirer. Le Lucky8 bonus de bienvenue est valable pendant 14 jours à compter de son versement.

Le service client

Vous devez alors vous renseigner à propos des délais de retrait selon chaque moyen de paiement. Sur Lucky8 la seule possibilité pour effectuer votre retrait est le virement bancaire. Pour retirer sur Lucky8, il vous suffit de vous rendre dans « Mon Compte » puis sur « Mon Portefeuille » et enfin cliquez sur « Retrait ». Sachez qu’avant de pouvoir effectuer votre retrait vous devez terminer ou annuler votre bonus.

  • Cet accord établit les termes et conditions qui constituent l’engament contractuel entre Lucky8 Casino et tout joueur s’inscrivant au casino.
  • Comme tous les meilleurs casinos en ligne, Lucky8 dispose de limites de retrait à ne pas dépasser pour que la demande soit acceptée.
  • Nous nous réservons le droit d’appliquer des restrictions ou de fermer tout compte sans préavis, dans les cas où nous constatons des preuves de non-conformité ou de violation des présentes conditions générales.
  • Vous pouvez retrouver la plupart du temps ces conditions en bas de page du site.

Pour s’inscrire sur un site de jeu d’argent, il est nécessaire de remplir certaines conditions. (Pas besoin de code bonus lucky8 pour profiter du bonus sans dépôt lucky 8, une inscription avec le lien suffit pour en bénéficier). Des bonus boostés, un suivi plus poussé de votre activité, des limites de retrait plus élevées, un conseiller privé, des offres et promotions en exclusivité, des cadeaux personnalisés et d’autres bénéfices encore. Celui-ci se trouve en bas à gauche lorsque vous êtes connecté sur la plateforme de jeux d’argent. Il s’agit d’un site qui a obtenu une licence de jeux à Curaçao, assurant une sécurité des données, un jeu responsable, des machines équitables, et une plateforme de paiement infranchissable.

Les étapes d’inscription sur le casino en ligne Lucky 8

Par conséquent, une mise de 1€ sur ce type de jeux contribuerait à hauteur de 0.10 € au lieu de 1 €, comme sur les machine à sous. Nous nous réservons le droit d’appliquer des restrictions ou de clôturer les comptes des titulaires enregistrés n’ayant pas fourni les documents requis dans les 7 jours suivant la demande. Des restrictions sur les comptes des joueurs pourraient également être appliquées à notre satisfaction jusqu’à ce que la vérification du compte soit complète et terminée. Il est essentiel que tous les joueurs lisent, acceptent et consentent d’être liés par les termes et conditions suivantes avant d’accéder, de s’inscrire ou de jouer à l’un des jeux de ce site. Dès vos premiers pas sur Lucky 8 Casino, ce qui vous fascinera à coup sûr, c’est l’efficacité de son interface.

  • Maintenant que votre retrait a bien été demandé, vous le voyez désormais apparaître comme “En attente” dans votre historique de transactions.
  • Ces événements offrent aux joueurs la possibilité de rivaliser pour des récompenses attractives tout en appréciant leurs jeux préférés.
  • • En attenteVotre retrait apparaîtra comme “en attente” jusqu’à ce que nous le traitions ou le refusions.
  • De plus, Lucky8 Casino offre une section dédiée au vidéo poker, où les joueurs peuvent profiter de différents titres tels que Jacks or Better, Deuces Wild et Joker Poker.

Lucky8 donne la possibilité aux joueurs d’essayer toute sa gamme de jeux de manière totalement gratuite. Il n’y a même pas besoin d’être inscrit pour profiter de ce mode de jeux, en se rendant simplement sur son site web, vous pourrez jouer à des jeux gratuits et faire de test avant inscription. C’est une excellente mesure de sécurité de la part du casino Lucky 8, afin que l’équipe soit sûre que personne ne ment et usurpe des données personnelles d’autrui.

Combien peut-on retirer au minimum et au maximum ?

Chaque nouvelle arrivée dans le monde des sites de casino en ligne est toujours un grand évènement ! Avez-vous entendu parler de Lucky8, un objet non-identifié arrivant avec fracas dans l’univers du casino virtuel français ? Non-identifié, pas tout à fait puisque ce casino en ligne français bénéficie de la licence de jeux d’argent émanant de l’Autorité de Curaçao. Tout d’abord les gains du joueur sont vérifiés, cela prend en général 24h00 puis vient ensuite le paiement en lui-même (qui est envoyé par le fournisseur de paiement) qui met environ 48h00 à arriver ! Cela signifie que le paiement est effectué 3 jours après la demande de retrait, quel que soit le montant. Attention, la limite de retrait sur Lucky8 est de 2500 euros par semaine !

  • Bien entendu, les joueurs VIP ont un plafond de retrait maximum plus élevé.
  • Normalement, vous devez utiliser le même mode de paiement pour le dépôt que pour le retrait Lucky8.
  • Alors, rejoignez-nous et à votre tour, donnez-nous votre avis sincère sur notre casino en ligne, en utilisant le formulaire de contact disponible depuis notre site ou depuis votre compte joueur directement.
  • C’est un bonus offert au joueur dès son inscription sur le site, c’est un bonus de bienvenue.
  • Avec une superbe plateforme de jeu futuriste et un système de wager unique au monde, Le casino Lucky8 fournit aux joueurs en recherche de sensation tout ce dont ils ont besoin.

Sur une page sécurisée, vous verrez l’icône cadenas dans l’URL de votre navigateur Web. Les Termes et Conditions peuvent être révisés à tout moment, il est de la seule responsabilité du joueur de lire cette page régulièrement (au moins une fois par mois). Chaque fois que les conditions générales seront mises à jour, la société informera les joueurs avant l’entrée en vigueur des nouvelles règles. Cet accord établit les termes et conditions qui constituent l’engament contractuel entre Lucky8 Casino et tout joueur s’inscrivant au casino.

]]>
https://affypharma.com/lucky8-retrait-en-combien-de-temps/feed/ 0