<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[27419] trunk/src: Add widget management to the customizer.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/27419">27419</a></dd>
<dt>Author</dt> <dd>nacin</dd>
<dt>Date</dt> <dd>2014-03-05 20:40:36 +0000 (Wed, 05 Mar 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add widget management to the customizer.

This brings in the Widget Customizer plugin: https://wordpress.org/plugins/widget-customizer/.

props westonruter, shaunandrews, michael-arestad, johnregan3, akeda, topher1kenobe, topquarky, bobbravo2, ricardocorreia. And for good measure, props westonruter.
see <a href="http://core.trac.wordpress.org/ticket/27112">#27112</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminadminajaxphp">trunk/src/wp-admin/admin-ajax.php</a></li>
<li><a href="#trunksrcwpadminincludesajaxactionsphp">trunk/src/wp-admin/includes/ajax-actions.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizecontrolphp">trunk/src/wp-includes/class-wp-customize-control.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizemanagerphp">trunk/src/wp-includes/class-wp-customize-manager.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpadmincsscustomizewidgetscss">trunk/src/wp-admin/css/customize-widgets.css</a></li>
<li><a href="#trunksrcwpadminjscustomizewidgetsjs">trunk/src/wp-admin/js/customize-widgets.js</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizewidgetsphp">trunk/src/wp-includes/class-wp-customize-widgets.php</a></li>
<li><a href="#trunksrcwpincludesjscustomizepreviewwidgetsjs">trunk/src/wp-includes/js/customize-preview-widgets.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminadminajaxphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-admin/admin-ajax.php (27418 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/admin-ajax.php        2014-03-05 20:03:33 UTC (rev 27418)
+++ trunk/src/wp-admin/admin-ajax.php   2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -58,7 +58,7 @@
</span><span class="cx">  'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment', 'get-attachment',
</span><span class="cx">  'query-attachments', 'save-attachment', 'save-attachment-compat', 'send-link-to-editor',
</span><span class="cx">  'send-attachment-to-editor', 'save-attachment-order', 'heartbeat', 'get-revision-diffs',
</span><del>-       'save-user-color-scheme',
</del><ins>+        'save-user-color-scheme', 'update-widget',
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> // Register core Ajax calls.
</span></span></pre></div>
<a id="trunksrcwpadmincsscustomizewidgetscss"></a>
<div class="addfile"><h4>Added: trunk/src/wp-admin/css/customize-widgets.css (0 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/css/customize-widgets.css                             (rev 0)
+++ trunk/src/wp-admin/css/customize-widgets.css        2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -0,0 +1,623 @@
</span><ins>+.wp-full-overlay-sidebar {
+       overflow: visible;
+}
+
+/**
+ * Hide all sidebar sections by default, only show them (via JS) once the
+ * preview loads and we know whether the sidebars are used in the template.
+ */
+
+.control-section[id^="accordion-section-sidebar-widgets-"],
+.customize-control-sidebar_widgets label,
+.customize-control-sidebar_widgets .hide-if-js {
+       /* The link in .customize-control-sidebar_widgets .hide-if-js will fail if it ever gets used. */
+       display:none;
+}
+
+.customize-control-widget_form .widget-top {
+       -webkit-transition: opacity 0.5s;
+       transition: opacity 0.5s;
+}
+
+.customize-control-widget_form:not(.widget-rendered) .widget-top {
+       opacity: 0.5;
+}
+
+
+.customize-control-widget_form .widget-control-save {
+       display: none;
+}
+
+.customize-control-widget_form .spinner {
+       display: inline;
+       opacity: 0.0;
+       -webkit-transition: opacity 0.1s;
+       transition: opacity 0.1s;
+}
+.customize-control-widget_form.previewer-loading .spinner {
+       opacity: 1.0;
+}
+
+.customize-control-widget_form .widget {
+       margin-bottom: 0;
+}
+
+.customize-control-widget_form:not(.wide-widget-control) {
+       /**
+        * Prevent plugins (e.g. Widget Visibility in Jetpack) from forcing widget forms
+        * to be wide and so overflow the customizer panel
+        */
+       left: auto !important;
+       max-width: 100%;
+}
+.customize-control-widget_form.wide-widget-control .widget-inside {
+       position: fixed;
+       left: 299px;
+       top: 25%;
+       padding: 20px;
+       border: 1px solid rgb(229, 229, 229);
+       z-index: -1;
+}
+.customize-control-widget_form.wide-widget-control.collapsing .widget-inside {
+       z-index: -2;
+}
+
+.customize-control-widget_form.wide-widget-control .widget-top {
+       -webkit-transition: background-color 0.4s;
+       transition: background-color 0.4s;
+}
+.customize-control-widget_form.wide-widget-control.expanding .widget-top,
+.customize-control-widget_form.wide-widget-control.expanded:not(.collapsing) .widget-top {
+       background-color: rgb(227, 227, 227);
+}
+
+.widget-inside {
+       padding: 1px 10px 10px 10px;
+       border-top: none;
+       line-height: 16px;
+}
+
+.widget-top {
+       cursor: move;
+}
+
+.customize-control-widget_form.expanded a.widget-action:after {
+       content: "\f142";
+}
+
+.customize-control-widget_form.wide-widget-control a.widget-action:after {
+       content: "\f139";
+}
+
+.customize-control-widget_form.wide-widget-control.expanded a.widget-action:after {
+       content: "\f141";
+}
+
+.widget-title-action {
+       cursor: pointer;
+}
+
+.customize-control-widget_form .widget .customize-control-title {
+       cursor: move;
+}
+
+/* @todo What does this do? */
+.control-section.accordion-section.widget-customizer-highlighted > .accordion-section-title,
+.customize-control-widget_form.widget-customizer-highlighted {
+       outline: none;
+       -webkit-box-shadow: 0 0 3px #ce0000;
+       box-shadow: 0 0 3px #ce0000;
+}
+
+#widget-customizer-control-templates {
+       display: none;
+}
+
+
+/* MP6-compat */
+#customize-theme-controls .accordion-section-content .widget {
+       color: black;
+}
+
+
+/**
+* Widget reordering styles
+**/
+
+.reorder-toggle {
+       float: right;
+       padding: 5px 10px;
+       margin-right: 10px;
+       text-decoration: none;
+       cursor: pointer;
+       outline: none;
+       -webkit-user-select: none;
+       -moz-user-select: none;
+       -ms-user-select: none;
+       user-select: none;
+}
+.reorder-toggle:focus {
+       outline: 1px dotted;
+}
+
+.reorder-done,
+.reordering .reorder {
+       display: none;
+}
+
+.reordering .reorder-done {
+       display: block;
+       color: #aa0000;
+}
+
+#customize-theme-controls .reordering .add-new-widget {
+       opacity: 0.2;
+       pointer-events: none;
+       cursor: not-allowed;
+}
+
+#customize-theme-controls .widget-reorder-nav {
+       display: none;
+       float: right;
+       background-color: #fafafa;
+}
+
+.widget-reorder-nav span {
+       position: relative;
+       overflow: hidden;
+       float: left;
+       display: block;
+       width: 33px; /* was 42px for mobile */
+       height: 43px;
+       color: #888;
+       text-indent: -9999px;
+       cursor: pointer;
+       outline: none;
+}
+
+.widget-reorder-nav span:before {
+       display: inline-block;
+       position: absolute;
+       top: 0;
+       right: 0;
+       width: 100%;
+       height: 100%;
+       font: normal normal 20px/43px 'Genericons';
+       text-align: center;
+       text-indent: 0;
+}
+
+.widget-reorder-nav span:hover,
+.widget-reorder-nav span:focus {
+       color: #444;
+       background: #eee;
+}
+
+.move-widget:before {
+       content: '\f442';
+}
+
+.move-widget-down:before {
+       content: '\f431';
+}
+
+.move-widget-up:before {
+       content: '\f432';
+}
+
+#customize-theme-controls .first-widget .move-widget-up,
+#customize-theme-controls .last-widget .move-widget-down {
+       color: #d5d5d5;
+       cursor: default;
+}
+
+#customize-theme-controls  .move-widget-area {
+       display: none;
+       background: #fff;
+       border: 1px solid #dedede;
+       border-top: none;
+       cursor: auto;
+}
+
+#customize-theme-controls .reordering .move-widget-area.active {
+       display: block;
+}
+
+#customize-theme-controls .move-widget-area .description {
+       margin: 0;
+       padding: 15px 20px;
+       font-weight: 400;
+}
+
+#customize-theme-controls .widget-area-select {
+       margin: 0;
+       padding: 0;
+       list-style: none;
+}
+
+#customize-theme-controls .widget-area-select li {
+       position: relative;
+       margin: 0;
+       padding: 13px 15px 15px 42px;
+       color: #555;
+       border-top: 1px solid #eee;
+       cursor: pointer;
+       -webkit-user-select: none;
+       -moz-user-select: none;
+       -ms-user-select: none;
+       user-select: none;
+}
+
+#customize-theme-controls .widget-area-select li:before {
+       display: none;
+       content: '\f418';
+       position: absolute;
+       top: 10px;
+       left: 10px;
+       font-family: 'Genericons';
+       font-size: 24px;
+       line-height: 1;
+}
+
+#customize-theme-controls .widget-area-select li:last-child {
+       border-bottom: 1px solid #eee;
+}
+
+#customize-theme-controls .widget-area-select .selected {
+       color: #fff;
+       text-shadow: 0 -1px 0 rgba(0,0,0,.4);
+       border-top: 1px solid #207fa1;
+       background: #2ea2cc;
+}
+
+#customize-theme-controls .widget-area-select .selected:before {
+       display: block;
+}
+
+#customize-theme-controls .widget-area-select .selected:last-child {
+       border-bottom: 1px solid #207fa1;
+}
+
+#customize-theme-controls .move-widget-actions {
+       text-align: right;
+       padding: 12px;
+}
+
+#customize-theme-controls .widget-area-select + li {
+       border-top: 1px solid #207fa1;
+}
+
+#customize-theme-controls .reordering .widget-title-action {
+       display: none;
+}
+
+#customize-theme-controls .reordering .widget-reorder-nav {
+       display: block;
+}
+
+
+/**
+ * Styles for new widget addition panel
+ */
+.wp-full-overlay-main {
+       right: auto; /* this overrides a right: 0; which causes the preview to resize, I'd rather have it go off screen at the normal size. */
+       width: 100%;
+}
+
+.add-new-widget {
+       cursor: pointer;
+       float: right;
+       -webkit-transition: all 0.2s;
+       transition: all 0.2s;
+       -webkit-user-select: none;
+       -moz-user-select: none;
+       -ms-user-select: none;
+       user-select: none;
+       -moz-outline: none;
+       outline: none;
+}
+
+.add-new-widget:before {
+       content: "\f132";
+       display: inline-block;
+       position: relative;
+               left: -2px;
+               top: -1px;
+       font: normal 16px/1 'dashicons';
+       vertical-align: middle;
+       -webkit-transition: all 0.2s;
+       transition: all 0.2s;
+       -webkit-font-smoothing: antialiased;
+}
+
+body.adding-widget .add-new-widget,
+body.adding-widget .add-new-widget:hover {
+       background: #EEE;
+       border-color: #999;
+       color: #333;
+       -webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
+       box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
+}
+body.adding-widget .add-new-widget:before {
+       -webkit-transform: rotate(45deg);
+       -ms-transform: rotate(45deg);
+       transform: rotate(45deg);
+}
+
+#available-widgets .widget {
+       position: static;
+}
+
+/* override widgets admin page rules in wp-admin/css/wp-admin.css */
+#widgets-left #available-widgets .widget {
+       float: none !important;
+       width: auto !important;
+}
+
+#available-widgets {
+       position: absolute;
+       overflow: auto;
+       top: 0;
+       bottom: 0;
+       left: -301px;
+       width: 300px;
+       margin: 0;
+       z-index: 1;
+       background: #fff;
+       -webkit-transition: all 0.2s;
+       transition: all 0.2s;
+       border-right: 1px solid #dddddd;
+}
+
+#available-widgets-filter {
+       padding: 8px 17px 7px 13px;
+       border-bottom: 1px solid #e4e4e4;
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+}
+
+#available-widgets-filter input {
+       padding: 5px 10px 2px 10px;
+       width: 100%;
+}
+
+#available-widgets .widget-tpl {
+       position: relative;
+       padding: 20px 15px 20px 60px;
+       border-bottom: 1px solid #e4e4e4;
+       cursor: pointer;
+}
+
+#available-widgets .widget-tpl:hover,
+#available-widgets .widget-tpl.selected {
+       background: #fafafa;
+}
+
+#available-widgets .widget-top,
+#available-widgets .widget-top:hover {
+       border: none;
+       background: transparent;
+       -webkit-box-shadow: none;
+       box-shadow: none;
+}
+
+#available-widgets .widget-title h4 {
+       padding: 0 0 5px;
+       font-size: 14px;
+}
+
+#available-widgets .widget .widget-description {
+       padding: 0;
+       color: #777;
+}
+
+#customize-preview {
+       -webkit-transition: all 0.2s;
+       transition: all 0.2s;
+}
+
+body.adding-widget #available-widgets {
+       left: 0;
+}
+
+body.adding-widget .wp-full-overlay-main {
+       left: 300px;
+}
+
+body.adding-widget #customize-preview {
+       opacity: 0.4;
+}
+
+
+/** Widget Icon styling **
+
+* No plurals in naming.
+* Ordered from lowest to highest specificity.
+
+**/
+#available-widgets .widget-title {
+       position: relative;
+}
+
+#available-widgets .widget-title:before {
+       content:"\f132";
+       position: absolute;
+       top: -3px;
+       right: 100%;
+       margin-right: 20px;
+       width: 20px;
+       height: 20px;
+       color: #333;
+       font: normal 20px/1 'dashicons', 'widgeticons';
+       text-align: center;
+       -webkit-border-radius: 2px;
+       border-radius: 2px;
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+       -webkit-font-smoothing: antialiased;
+}
+
+/* smiley */
+#available-widgets [class*="easy"] .widget-title:before { content: "\f328"; top: -4px; }
+
+/* star-filled */
+#available-widgets [class*="super"] .widget-title:before,
+#available-widgets [class*="like"] .widget-title:before { content: "\f155"; top: -4px; }
+
+/* wordpress */
+#available-widgets [class*="meta"] .widget-title:before { content: "\f120"; }
+
+/* archive-box */
+#available-widgets [class*="archives"] .widget-title:before { content: "\f483"; top: -4px; }
+
+/* category */
+#available-widgets [class*="categor"] .widget-title:before { content: "\f318"; top: -4px; }
+
+/* comments */
+#available-widgets [class*="comment"] .widget-title:before,
+#available-widgets [class*="testimonial"] .widget-title:before,
+#available-widgets [class*="chat"] .widget-title:before { content: "\f101"; }
+
+/* post */
+#available-widgets [class*="post"] .widget-title:before { content: "\f109"; }
+
+/* admin-page */
+#available-widgets [class*="page"] .widget-title:before { content: "\f105"; }
+
+/* text */
+#available-widgets [class*="text"] .widget-title:before { content: "\f480"; }
+
+/* links */
+#available-widgets [class*="link"] .widget-title:before { content: "\f103"; }
+
+/* search */
+#available-widgets [class*="search"] .widget-title:before { content: "\f179"; }
+
+/* menu */
+#available-widgets [class*="menu"] .widget-title:before,
+#available-widgets [class*="nav"] .widget-title:before { content: "\f333"; }
+
+/* tag-cloud */
+#available-widgets [class*="tag"] .widget-title:before { content: "\f481"; }
+
+/* rss */
+#available-widgets [class*="rss"] .widget-title:before { content: "\f303"; top: -6px; }
+
+/* calendar */
+#available-widgets [class*="event"] .widget-title:before,
+#available-widgets [class*="calendar"] .widget-title:before { content: "\f145"; top: -4px;}
+
+/* format-image */
+#available-widgets [class*="image"] .widget-title:before,
+#available-widgets [class*="photo"] .widget-title:before,
+#available-widgets [class*="slide"] .widget-title:before,
+#available-widgets [class*="instagram"] .widget-title:before { content: "\f128"; }
+
+/* format-gallery */
+#available-widgets [class*="album"] .widget-title:before,
+#available-widgets [class*="galler"] .widget-title:before { content: "\f161"; }
+
+/* format-video */
+#available-widgets [class*="video"] .widget-title:before,
+#available-widgets [class*="tube"] .widget-title:before { content: "\f126"; }
+
+/* format-audio */
+#available-widgets [class*="music"] .widget-title:before,
+#available-widgets [class*="radio"] .widget-title:before,
+#available-widgets [class*="audio"] .widget-title:before { content: "\f127"; }
+
+/* admin-users */
+#available-widgets [class*="login"] .widget-title:before,
+#available-widgets [class*="user"] .widget-title:before,
+#available-widgets [class*="member"] .widget-title:before,
+#available-widgets [class*="avatar"] .widget-title:before,
+#available-widgets [class*="subscriber"] .widget-title:before,
+#available-widgets [class*="profile"] .widget-title:before,
+#available-widgets [class*="grofile"] .widget-title:before { content: "\f110"; }
+
+/* cart */
+#available-widgets [class*="commerce"] .widget-title:before,
+#available-widgets [class*="shop"] .widget-title:before,
+#available-widgets [class*="cart"] .widget-title:before { content: "\f174"; top: -4px; }
+
+/* shield */
+#available-widgets [class*="secur"] .widget-title:before,
+#available-widgets [class*="firewall"] .widget-title:before { content: "\f332"; }
+
+/* chart-bar */
+#available-widgets [class*="analytic"] .widget-title:before,
+#available-widgets [class*="stat"] .widget-title:before,
+#available-widgets [class*="poll"] .widget-title:before { content: "\f185"; }
+
+/* feedback */
+#available-widgets [class*="form"] .widget-title:before { content: "\f175"; }
+
+/* email-alt */
+#available-widgets [class*="subscribe"] .widget-title:before,
+#available-widgets [class*="news"] .widget-title:before,
+#available-widgets [class*="contact"] .widget-title:before,
+#available-widgets [class*="mail"] .widget-title:before { content: "\f466"; }
+
+/* share */
+#available-widgets [class*="share"] .widget-title:before,
+#available-widgets [class*="socia"] .widget-title:before { content: "\f237"; }
+
+/* translation */
+#available-widgets [class*="lang"] .widget-title:before,
+#available-widgets [class*="translat"] .widget-title:before { content: "\f326"; }
+
+/* location-alt */
+#available-widgets [class*="locat"] .widget-title:before,
+#available-widgets [class*="map"] .widget-title:before { content: "\f231"; }
+
+/* download */
+#available-widgets [class*="download"] .widget-title:before { content: "\f316"; }
+
+/* cloud */
+#available-widgets [class*="weather"] .widget-title:before { content: "\f176"; top: -4px;}
+
+/* facebook */
+#available-widgets [class*="facebook"] .widget-title:before { content: "\f304"; }
+
+/* twitter */
+#available-widgets [class*="tweet"] .widget-title:before,
+#available-widgets [class*="twitter"] .widget-title:before { content: "\f301"; }
+
+
+@media screen and (max-height: 700px) and (min-width: 981px) {
+       .customize-control {
+               margin-bottom: 0;
+       }
+       .widget-top {
+               -webkit-box-shadow: none;
+               box-shadow: none;
+               margin-top: -1px;
+       }
+       .widget-top:hover {
+               position: relative;
+               z-index: 1;
+       }
+       .last-widget {
+               margin-bottom: 15px;
+       }
+       .widget-title h4 {
+               padding: 13px 15px;
+       }
+       .widget-top a.widget-action:after {
+               padding-top: 9px;
+       }
+       .widget-reorder-nav span {
+               height: 39px;
+       }
+       .widget-reorder-nav span:before {
+               line-height: 39px;
+       }
+       #customize-theme-controls .widget-area-select li {
+               padding: 9px 15px 11px 42px;
+       }
+       #customize-theme-controls .widget-area-select li:before {
+               top: 6px;
+       }
+}
</ins></span></pre></div>
<a id="trunksrcwpadminincludesajaxactionsphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-admin/includes/ajax-actions.php (27418 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/includes/ajax-actions.php     2014-03-05 20:03:33 UTC (rev 27418)
+++ trunk/src/wp-admin/includes/ajax-actions.php        2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -1587,6 +1587,13 @@
</span><span class="cx">  wp_die();
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+function wp_ajax_update_widget() {
+       require( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
+       $GLOBALS['wp_customize'] = new WP_Customize_Manager;
+
+       WP_Customize_Widgets::wp_ajax_update_widget();
+}
+
</ins><span class="cx"> function wp_ajax_upload_attachment() {
</span><span class="cx">  check_ajax_referer( 'media-form' );
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunksrcwpadminjscustomizewidgetsjs"></a>
<div class="addfile"><h4>Added: trunk/src/wp-admin/js/customize-widgets.js (0 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/js/customize-widgets.js                               (rev 0)
+++ trunk/src/wp-admin/js/customize-widgets.js  2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -0,0 +1,1733 @@
</span><ins>+/*global wp, Backbone, _, jQuery, WidgetCustomizer_exports */
+/*exported WidgetCustomizer */
+var WidgetCustomizer = ( function ($) {
+       'use strict';
+
+       var customize = wp.customize;
+       var self = {
+               update_widget_ajax_action: null,
+               update_widget_nonce_value: null,
+               update_widget_nonce_post_key: null,
+               i18n: {
+                       save_btn_label: '',
+                       save_btn_tooltip: '',
+                       remove_btn_label: '',
+                       remove_btn_tooltip: ''
+               },
+               available_widgets: [], // available widgets for instantiating
+               registered_widgets: [], // all widgets registered
+               active_sidebar_control: null,
+               previewer: null,
+               saved_widget_ids: {},
+               registered_sidebars: [],
+               tpl: {
+                       move_widget_area: '',
+                       widget_reorder_nav: ''
+               }
+       };
+       $.extend( self, WidgetCustomizer_exports );
+
+       // Lots of widgets expect this old ajaxurl global to be available
+       if ( typeof window.ajaxurl === 'undefined' ) {
+               window.ajaxurl = wp.ajax.settings.url;
+       }
+
+       // Unfortunately many widgets try to look for instances under div#widgets-right,
+       // so we have to add that ID to a container div in the customizer for compat
+       $( '#customize-theme-controls' ).closest( 'div:not([id])' ).attr( 'id', 'widgets-right' );
+
+       /**
+        * Set up model
+        */
+       var Widget = self.Widget = Backbone.Model.extend( {
+               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: 'refresh',
+               params: [],
+               width: null,
+               height: null
+       } );
+       var WidgetCollection = self.WidgetCollection = Backbone.Collection.extend( {
+               model: Widget
+       } );
+       self.available_widgets = new WidgetCollection( self.available_widgets );
+
+       var Sidebar = self.Sidebar = Backbone.Model.extend( {
+               after_title: null,
+               after_widget: null,
+               before_title: null,
+               before_widget: null,
+               'class': null,
+               description: null,
+               id: null,
+               name: null,
+               is_rendered: false
+       } );
+       var SidebarCollection = self.SidebarCollection = Backbone.Collection.extend( {
+               model: Sidebar
+       } );
+       self.registered_sidebars = new SidebarCollection( self.registered_sidebars );
+
+       /**
+        * On DOM ready, initialize some meta functionality independent of specific
+        * customizer controls.
+        */
+       self.init = function () {
+               this.showFirstSidebarIfRequested();
+               this.availableWidgetsPanel.setup();
+       };
+       wp.customize.bind( 'ready', function () {
+               self.init();
+       } );
+
+       /**
+        * Listen for updates to which sidebars are rendered in the preview and toggle
+        * the customizer sections accordingly.
+        */
+       self.showFirstSidebarIfRequested = function () {
+               if ( ! /widget-customizer=open/.test( location.search ) ) {
+                       return;
+               }
+
+               var show_first_visible_sidebar = function () {
+                       self.registered_sidebars.off( 'change:is_rendered', show_first_visible_sidebar );
+                       var first_rendered_sidebar = self.registered_sidebars.find( function ( sidebar ) {
+                               return sidebar.get( 'is_rendered' );
+                       } );
+                       if ( ! first_rendered_sidebar ) {
+                               return;
+                       }
+                       var section = $( '#accordion-section-sidebar-widgets-' + first_rendered_sidebar.get( 'id' ) );
+                       if ( ! section.hasClass( 'open' ) ) {
+                               section.find( '.accordion-section-title' ).trigger( 'click' );
+                       }
+                       section[0].scrollIntoView();
+               };
+               show_first_visible_sidebar = _.debounce( show_first_visible_sidebar, 100 ); // so only fires when all updated at end
+               self.registered_sidebars.on( 'change:is_rendered', show_first_visible_sidebar );
+       };
+
+       /**
+        * Sidebar Widgets control
+        * Note that 'sidebar_widgets' must match the Sidebar_Widgets_WP_Customize_Control::$type
+        */
+       customize.controlConstructor.sidebar_widgets = customize.Control.extend( {
+
+               /**
+                * Set up the control
+                */
+               ready: function() {
+                       var control = this;
+                       control.control_section = control.container.closest( '.control-section' );
+                       control.section_content = control.container.closest( '.accordion-section-content' );
+                       control._setupModel();
+                       control._setupSortable();
+                       control._setupAddition();
+                       control._applyCardinalOrderClassNames();
+               },
+
+               /**
+                * Update ordering of widget control forms when the setting is updated
+                */
+               _setupModel: function() {
+                       var control = this;
+                       var registered_sidebar = self.registered_sidebars.get( control.params.sidebar_id );
+
+                       control.setting.bind( function( new_widget_ids, old_widget_ids ) {
+                               var removed_widget_ids = _( old_widget_ids ).difference( new_widget_ids );
+
+                               // Filter out any persistent widget_ids for widgets which have been deactivated
+                               new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) {
+                                       var parsed_widget_id = parse_widget_id( new_widget_id );
+                                       return !! self.available_widgets.findWhere( { id_base: parsed_widget_id.id_base } );
+                               } );
+
+                               var widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) {
+                                       var widget_form_control = self.getWidgetFormControlForWidget( widget_id );
+                                       if ( ! widget_form_control ) {
+                                               widget_form_control = control.addWidget( widget_id );
+                                       }
+                                       return widget_form_control;
+                               } );
+
+                               // Sort widget controls to their new positions
+                               widget_form_controls.sort( function ( a, b ) {
+                                       var a_index = new_widget_ids.indexOf( a.params.widget_id );
+                                       var b_index = new_widget_ids.indexOf( b.params.widget_id );
+                                       if ( a_index === b_index ) {
+                                               return 0;
+                                       }
+                                       return a_index < b_index ? -1 : 1;
+                               } );
+
+                               var sidebar_widgets_add_control = control.section_content.find( '.customize-control-sidebar_widgets' );
+
+                               // Append the controls to put them in the right order
+                               var final_control_containers = _( widget_form_controls ).map( function( widget_form_controls ) {
+                                       return widget_form_controls.container[0];
+                               } );
+
+                               // Re-sort widget form controls (including widgets form other sidebars newly moved here)
+                               sidebar_widgets_add_control.before( final_control_containers );
+                               control._applyCardinalOrderClassNames();
+
+                               // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated
+                               _( widget_form_controls ).each( function ( widget_form_control ) {
+                                       widget_form_control.params.sidebar_id = control.params.sidebar_id;
+                               } );
+
+                               // Cleanup after widget removal
+                               _( removed_widget_ids ).each( function ( removed_widget_id ) {
+
+                                       // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
+                                       setTimeout( function () {
+                                               var is_present_in_another_sidebar = false;
+
+                                               // Check if the widget is in another sidebar
+                                               wp.customize.each( function ( other_setting ) {
+                                                       if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
+                                                               return;
+                                                       }
+                                                       var other_sidebar_widgets = other_setting();
+                                                       var i = other_sidebar_widgets.indexOf( removed_widget_id );
+                                                       if ( -1 !== i ) {
+                                                               is_present_in_another_sidebar = true;
+                                                       }
+                                               } );
+
+                                               // If the widget is present in another sidebar, abort!
+                                               if ( is_present_in_another_sidebar ) {
+                                                       return;
+                                               }
+
+                                               var removed_control = self.getWidgetFormControlForWidget( removed_widget_id );
+
+                                               // Detect if widget control was dragged to another sidebar
+                                               var was_dragged_to_another_sidebar = (
+                                                       removed_control &&
+                                                       $.contains( document, removed_control.container[0] ) &&
+                                                       ! $.contains( control.section_content[0], removed_control.container[0] )
+                                               );
+
+                                               // Delete any widget form controls for removed widgets
+                                               if ( removed_control && ! was_dragged_to_another_sidebar ) {
+                                                       wp.customize.control.remove( removed_control.id );
+                                                       removed_control.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 ( self.saved_widget_ids[removed_widget_id] ) {
+                                                       var inactive_widgets = wp.customize.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice();
+                                                       inactive_widgets.push( removed_widget_id );
+                                                       wp.customize.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactive_widgets ).unique() );
+                                               }
+
+                                               // Make old single widget available for adding again
+                                               var removed_id_base = parse_widget_id( removed_widget_id ).id_base;
+                                               var widget = self.available_widgets.findWhere( { id_base: removed_id_base } );
+                                               if ( widget && ! widget.get( 'is_multi' ) ) {
+                                                       widget.set( 'is_disabled', false );
+                                               }
+                                       } );
+
+                               } );
+                       } );
+
+                       // Update the model with whether or not the sidebar is rendered
+                       self.previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) {
+                               var is_rendered = !! rendered_sidebars[control.params.sidebar_id];
+                               registered_sidebar.set( 'is_rendered', is_rendered );
+                       } );
+
+                       // Show the sidebar section when it becomes visible
+                       registered_sidebar.on( 'change:is_rendered', function ( ) {
+                               var section_selector = '#accordion-section-sidebar-widgets-' + this.get( 'id' );
+                               var section = $( section_selector );
+                               if ( this.get( 'is_rendered' ) ) {
+                                       section.stop().slideDown( function () {
+                                               $( this ).css( 'height', 'auto' ); // so that the .accordion-section-content won't overflow
+                                       } );
+                               } else {
+                                       // Make sure that hidden sections get closed first
+                                       if ( section.hasClass( 'open' ) ) {
+                                               // it would be nice if accordionSwitch() in accordion.js was public
+                                               section.find( '.accordion-section-title' ).trigger( 'click' );
+                                       }
+                                       section.stop().slideUp();
+                               }
+                       } );
+               },
+
+               /**
+                * Allow widgets in sidebar to be re-ordered, and for the order to be previewed
+                */
+               _setupSortable: function () {
+                       var control = this;
+                       control.is_reordering = false;
+
+                       /**
+                        * Update widget order setting when controls are re-ordered
+                        */
+                       control.section_content.sortable( {
+                               items: '> .customize-control-widget_form',
+                               handle: '.widget-top',
+                               axis: 'y',
+                               connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
+                               update: function () {
+                                       var widget_container_ids = control.section_content.sortable( 'toArray' );
+                                       var widget_ids = $.map( widget_container_ids, function ( widget_container_id ) {
+                                               return $( '#' + widget_container_id ).find( ':input[name=widget-id]' ).val();
+                                       } );
+                                       control.setting( widget_ids );
+                               }
+                       } );
+
+                       /**
+                        * Expand other customizer sidebar section when dragging a control widget over it,
+                        * allowing the control to be dropped into another section
+                        */
+                       control.control_section.find( '.accordion-section-title' ).droppable( {
+                               accept: '.customize-control-widget_form',
+                               over: function () {
+                                       if ( ! control.control_section.hasClass( 'open' ) ) {
+                                               control.control_section.addClass( 'open' );
+                                               control.section_content.toggle( false ).slideToggle( 150, function () {
+                                                       control.section_content.sortable( 'refreshPositions' );
+                                               } );
+                                       }
+                               }
+                       } );
+
+                       /**
+                        * Keyboard-accessible reordering
+                        */
+                       control.container.find( '.reorder-toggle' ).on( 'click keydown', function( event ) {
+                               if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar
+                                       return;
+                               }
+
+                               control.toggleReordering( ! control.is_reordering );
+                       } );
+               },
+
+               /**
+                * Set up UI for adding a new widget
+                */
+               _setupAddition: function () {
+                       var control = this;
+
+                       control.container.find( '.add-new-widget' ).on( 'click keydown', function( event ) {
+                               if ( event.type === 'keydown' && ! ( event.which === 13 || event.which === 32 ) ) { // Enter or Spacebar
+                                       return;
+                               }
+
+                               if ( control.section_content.hasClass( 'reordering' ) ) {
+                                       return;
+                               }
+
+                               // @todo Use an control.is_adding state
+                               if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) {
+                                       self.availableWidgetsPanel.open( control );
+                               } else {
+                                       self.availableWidgetsPanel.close();
+                               }
+                       } );
+               },
+
+               /**
+                * Add classes to the widget_form controls to assist with styling
+                */
+               _applyCardinalOrderClassNames: function () {
+                       var control = this;
+                       control.section_content.find( '.customize-control-widget_form' )
+                               .removeClass( 'first-widget' )
+                               .removeClass( 'last-widget' )
+                               .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 );
+
+                       control.section_content.find( '.customize-control-widget_form:first' )
+                               .addClass( 'first-widget' )
+                               .find( '.move-widget-up' ).prop( 'tabIndex', -1 );
+                       control.section_content.find( '.customize-control-widget_form:last' )
+                               .addClass( 'last-widget' )
+                               .find( '.move-widget-down' ).prop( 'tabIndex', -1 );
+               },
+
+
+               /***********************************************************************
+                * Begin public API methods
+                **********************************************************************/
+
+               /**
+                * Enable/disable the reordering UI
+                *
+                * @param {Boolean} toggle to enable/disable reordering
+                */
+               toggleReordering: function ( toggle ) {
+                       var control = this;
+                       toggle = Boolean( toggle );
+                       if ( toggle === control.section_content.hasClass( 'reordering' ) ) {
+                               return;
+                       }
+
+                       control.is_reordering = toggle;
+                       control.section_content.toggleClass( 'reordering', toggle );
+
+                       if ( toggle ) {
+                               _( control.getWidgetFormControls() ).each( function ( form_control ) {
+                                       form_control.collapseForm();
+                               } );
+                       }
+               },
+
+               /**
+                * @return {wp.customize.controlConstructor.widget_form[]}
+                */
+               getWidgetFormControls: function () {
+                       var control = this;
+                       var form_controls = _( control.setting() ).map( function ( widget_id ) {
+                               var setting_id = widget_id_to_setting_id( widget_id );
+                               var form_control = customize.control( setting_id );
+                               if ( ! form_control ) {
+                                       throw new Error( 'Unable to find widget_form control for ' + widget_id );
+                               }
+                               return form_control;
+                       } );
+                       return form_controls;
+               },
+
+               /**
+                * @param {string} widget_id or an id_base for adding a previously non-existing widget
+                * @returns {object} widget_form control instance
+                */
+               addWidget: function ( widget_id ) {
+                       var control = this;
+                       var parsed_widget_id = parse_widget_id( widget_id );
+                       var widget_number = parsed_widget_id.number;
+                       var widget_id_base = parsed_widget_id.id_base;
+                       var widget = self.available_widgets.findWhere( {id_base: widget_id_base} );
+                       if ( ! widget ) {
+                               throw new Error( 'Widget unexpectedly not found.' );
+                       }
+                       if ( widget_number && ! widget.get( 'is_multi' ) ) {
+                               throw new Error( 'Did not expect a widget number to be supplied for a non-multi widget' );
+                       }
+
+                       // Set up new multi widget
+                       if ( widget.get( 'is_multi' ) && ! widget_number ) {
+                               widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 );
+                               widget_number = widget.get( 'multi_number' );
+                       }
+
+                       var control_html = $( '#widget-tpl-' + widget.get( 'id' ) ).html();
+                       if ( widget.get( 'is_multi' ) ) {
+                               control_html = control_html.replace( /<[^<>]+>/g, function ( m ) {
+                                       return m.replace( /__i__|%i%/g, widget_number );
+                               } );
+                       } else {
+                               widget.set( 'is_disabled', true ); // Prevent single widget from being added again now
+                       }
+
+                       var customize_control_type = 'widget_form';
+                       var customize_control = $( '<li></li>' );
+                       customize_control.addClass( 'customize-control' );
+                       customize_control.addClass( 'customize-control-' + customize_control_type );
+                       customize_control.append( $( control_html ) );
+                       customize_control.find( '> .widget-icon' ).remove();
+                       if ( widget.get( 'is_multi' ) ) {
+                               customize_control.find( 'input[name="widget_number"]' ).val( widget_number );
+                               customize_control.find( 'input[name="multi_number"]' ).val( widget_number );
+                       }
+                       widget_id = customize_control.find( '[name="widget-id"]' ).val();
+                       customize_control.hide(); // to be slid-down below
+
+                       var setting_id = 'widget_' + widget.get( 'id_base' );
+                       if ( widget.get( 'is_multi' ) ) {
+                               setting_id += '[' + widget_number + ']';
+                       }
+                       customize_control.attr( 'id', 'customize-control-' + setting_id.replace( /\]/g, '' ).replace( /\[/g, '-' ) );
+
+                       control.container.after( customize_control );
+
+                       // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget)
+                       var is_existing_widget = wp.customize.has( setting_id );
+                       if ( ! is_existing_widget ) {
+                               var setting_args = {
+                                       transport: 'refresh',
+                                       previewer: control.setting.previewer
+                               };
+                               wp.customize.create( setting_id, setting_id, {}, setting_args );
+                       }
+
+                       var Constructor = wp.customize.controlConstructor[customize_control_type];
+                       var widget_form_control = new Constructor( setting_id, {
+                               params: {
+                                       settings: {
+                                               'default': setting_id
+                                       },
+                                       sidebar_id: control.params.sidebar_id,
+                                       widget_id: widget_id,
+                                       widget_id_base: widget.get( 'id_base' ),
+                                       type: customize_control_type,
+                                       is_new: ! is_existing_widget,
+                                       width: widget.get( 'width' ),
+                                       height: widget.get( 'height' ),
+                                       is_wide: widget.get( 'is_wide' )
+                               },
+                               previewer: control.setting.previewer
+                       } );
+                       wp.customize.control.add( setting_id, widget_form_control );
+
+                       // Make sure widget is removed from the other sidebars
+                       wp.customize.each( function ( other_setting ) {
+                               if ( other_setting.id === control.setting.id ) {
+                                       return;
+                               }
+                               if ( 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) ) {
+                                       return;
+                               }
+                               var other_sidebar_widgets = other_setting().slice();
+                               var i = other_sidebar_widgets.indexOf( widget_id );
+                               if ( -1 !== i ) {
+                                       other_sidebar_widgets.splice( i );
+                                       other_setting( other_sidebar_widgets );
+                               }
+                       } );
+
+                       // Add widget to this sidebar
+                       var sidebar_widgets = control.setting().slice();
+                       if ( -1 === sidebar_widgets.indexOf( widget_id ) ) {
+                               sidebar_widgets.push( widget_id );
+                               control.setting( sidebar_widgets );
+                       }
+
+                       customize_control.slideDown( function () {
+                               if ( is_existing_widget ) {
+                                       widget_form_control.expandForm();
+                                       widget_form_control.updateWidget( {
+                                               instance: widget_form_control.setting(),
+                                               complete: function ( error ) {
+                                                       if ( error ) {
+                                                               throw error;
+                                                       }
+                                                       widget_form_control.focus();
+                                               }
+                                       } );
+                               } else {
+                                       widget_form_control.focus();
+                               }
+                       } );
+
+                       return widget_form_control;
+               }
+
+       } );
+
+       /**
+        * Widget Form control
+        * Note that 'widget_form' must match the Widget_Form_WP_Customize_Control::$type
+        */
+       customize.controlConstructor.widget_form = customize.Control.extend( {
+
+               /**
+                * Set up the control
+                */
+               ready: function() {
+                       var control = this;
+                       control._setupModel();
+                       control._setupWideWidget();
+                       control._setupControlToggle();
+                       control._setupWidgetTitle();
+                       control._setupReorderUI();
+                       control._setupHighlightEffects();
+                       control._setupUpdateUI();
+                       control._setupRemoveUI();
+                       control.hook( 'init' );
+               },
+
+               /**
+                * Hooks for widgets to support living in the customizer control
+                */
+               hooks: {
+                       _default: {},
+                       rss: {
+                               formUpdated: function ( serialized_form ) {
+                                       var control = this;
+                                       var old_widget_error = control.container.find( '.widget-error:first' );
+                                       var new_widget_error = serialized_form.find( '.widget-error:first' );
+                                       if ( old_widget_error.length && new_widget_error.length ) {
+                                               old_widget_error.replaceWith( new_widget_error );
+                                       } else if ( old_widget_error.length ) {
+                                               old_widget_error.remove();
+                                       } else if ( new_widget_error.length ) {
+                                               control.container.find( '.widget-content' ).prepend( new_widget_error );
+                                       }
+                               }
+                       }
+               },
+
+               /**
+                * Trigger an 'action' which a specific widget type can handle
+                *
+                * @param name
+                */
+               hook: function ( name ) {
+                       var args = Array.prototype.slice.call( arguments, 1 );
+                       var handler;
+                       if ( this.hooks[this.params.widget_id_base] && this.hooks[this.params.widget_id_base][name] ) {
+                               handler = this.hooks[this.params.widget_id_base][name];
+                       } else if ( this.hooks._default[name] ) {
+                               handler = this.hooks._default[name];
+                       }
+                       if ( handler ) {
+                               handler.apply( this, args );
+                       }
+               },
+
+               /**
+                * Handle changes to the setting
+                */
+               _setupModel: function () {
+                       var control = this;
+
+                       // Remember saved widgets so we know which to trash (move to inactive widgets sidebar)
+                       var remember_saved_widget_id = function () {
+                               self.saved_widget_ids[control.params.widget_id] = true;
+                       };
+                       wp.customize.bind( 'ready', remember_saved_widget_id );
+                       wp.customize.bind( 'saved', remember_saved_widget_id );
+
+                       control._update_count = 0;
+                       control.is_widget_updating = false;
+
+                       // Update widget whenever model changes
+                       control.setting.bind( function( to, from ) {
+                               if ( ! _( from ).isEqual( to ) && ! control.is_widget_updating ) {
+                                       control.updateWidget( { instance: to } );
+                               }
+                       } );
+               },
+
+               /**
+                * Add special behaviors for wide widget controls
+                */
+               _setupWideWidget: function () {
+                       var control = this;
+                       if ( ! control.params.is_wide ) {
+                               return;
+                       }
+                       var widget_inside = control.container.find( '.widget-inside' );
+                       var customize_sidebar = $( '.wp-full-overlay-sidebar-content:first' );
+                       control.container.addClass( 'wide-widget-control' );
+
+                       control.container.find( '.widget-content:first' ).css( {
+                               'min-width': control.params.width,
+                               'min-height': control.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.
+                        */
+                       var position_widget = function () {
+                               var offset_top = control.container.offset().top;
+                               var height = widget_inside.outerHeight();
+                               var top = Math.max( offset_top, 0 );
+                               var max_top = $( window ).height() - height;
+                               top = Math.min( top, max_top );
+                               widget_inside.css( 'top', top );
+                       };
+
+                       var theme_controls_container = $( '#customize-theme-controls' );
+                       control.container.on( 'expand', function () {
+                               customize_sidebar.on( 'scroll', position_widget );
+                               $( window ).on( 'resize', position_widget );
+                               theme_controls_container.on( 'expanded collapsed', position_widget );
+                               position_widget();
+                       } );
+                       control.container.on( 'collapsed', function () {
+                               customize_sidebar.off( 'scroll', position_widget );
+                               theme_controls_container.off( 'expanded collapsed', position_widget );
+                               $( window ).off( 'resize', position_widget );
+                       } );
+
+                       // Reposition whenever a sidebar's widgets are changed
+                       wp.customize.each( function ( setting ) {
+                               if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
+                                       setting.bind( function () {
+                                               if ( control.container.hasClass( 'expanded' ) ) {
+                                                       position_widget();
+                                               }
+                                       } );
+                               }
+                       } );
+               },
+
+               /**
+                * Show/hide the control when clicking on the form title, when clicking
+                * the close button
+                */
+               _setupControlToggle: function() {
+                       var control = this;
+                       control.container.find( '.widget-top' ).on( 'click', function ( e ) {
+                               e.preventDefault();
+                               var sidebar_widgets_control = control.getSidebarWidgetsControl();
+                               if ( sidebar_widgets_control.is_reordering ) {
+                                       return;
+                               }
+                               control.toggleForm();
+                       } );
+
+                       var close_btn = control.container.find( '.widget-control-close' );
+                       // @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
+                       close_btn.on( 'click', function ( e ) {
+                               e.preventDefault();
+                               control.collapseForm();
+                               control.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 control = this;
+                       var update_title = function () {
+                               var title = control.setting().title;
+                               var in_widget_title = control.container.find( '.in-widget-title' );
+                               if ( title ) {
+                                       in_widget_title.text( ': ' + title );
+                               } else {
+                                       in_widget_title.text( '' );
+                               }
+                       };
+                       control.setting.bind( update_title );
+                       update_title();
+               },
+
+               /**
+                * Set up the widget-reorder-nav
+                */
+               _setupReorderUI: function () {
+                       var control = this;
+
+                       /**
+                        * select the provided sidebar list item in the move widget area
+                        *
+                        * @param {jQuery} li
+                        */
+                       var select_sidebar_item = function ( li ) {
+                               li.siblings( '.selected' ).removeClass( 'selected' );
+                               li.addClass( 'selected' );
+                               var is_self_sidebar = ( li.data( 'id' ) === control.params.sidebar_id );
+                               control.container.find( '.move-widget-btn' ).prop( 'disabled', is_self_sidebar );
+                       };
+
+                       /**
+                        * Add the widget reordering elements to the widget control
+                        */
+                       control.container.find( '.widget-title-action' ).after( $( self.tpl.widget_reorder_nav ) );
+                       var move_widget_area = $(
+                               _.template( self.tpl.move_widget_area, {
+                                       sidebars: _( self.registered_sidebars.toArray() ).pluck( 'attributes' )
+                               } )
+                       );
+                       control.container.find( '.widget-top' ).after( move_widget_area );
+
+                       /**
+                        * Update available sidebars when their rendered state changes
+                        */
+                       var update_available_sidebars = function () {
+                               var sidebar_items = move_widget_area.find( 'li' );
+                               var self_sidebar_item = sidebar_items.filter( function(){
+                                       return $( this ).data( 'id' ) === control.params.sidebar_id;
+                               } );
+                               sidebar_items.each( function () {
+                                       var li = $( this );
+                                       var sidebar_id = li.data( 'id' );
+                                       var sidebar_model = self.registered_sidebars.get( sidebar_id );
+                                       li.toggle( sidebar_model.get( 'is_rendered' ) );
+                                       if ( li.hasClass( 'selected' ) && ! sidebar_model.get( 'is_rendered' ) ) {
+                                               select_sidebar_item( self_sidebar_item );
+                                       }
+                               } );
+                       };
+                       update_available_sidebars();
+                       self.registered_sidebars.on( 'change:is_rendered', update_available_sidebars );
+
+                       /**
+                        * Handle clicks for up/down/move on the reorder nav
+                        */
+                       var reorder_nav = control.container.find( '.widget-reorder-nav' );
+                       reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function ( event ) {
+                               if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+                                       return;
+                               }
+                               $( this ).focus();
+
+                               if ( $( this ).is( '.move-widget' ) ) {
+                                       control.toggleWidgetMoveArea();
+                               } else {
+                                       var is_move_down = $( this ).is( '.move-widget-down' );
+                                       var is_move_up = $( this ).is( '.move-widget-up' );
+                                       var i = control.getWidgetSidebarPosition();
+                                       if ( ( is_move_up && i === 0 ) || ( is_move_down && i === control.getSidebarWidgetsControl().setting().length - 1 ) ) {
+                                               return;
+                                       }
+
+                                       if ( is_move_up ) {
+                                               control.moveUp();
+                                       } else {
+                                               control.moveDown();
+                                       }
+
+                                       $( this ).focus(); // re-focus after the container was moved
+                               }
+                       } );
+
+                       /**
+                        * Handle selecting a sidebar to move to
+                        */
+                       control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function ( e ) {
+                               if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+                                       return;
+                               }
+                               e.preventDefault();
+                               select_sidebar_item( $( this ) );
+                       } );
+
+                       /**
+                        * Move widget to another sidebar
+                        */
+                       control.container.find( '.move-widget-btn' ).click( function () {
+                               control.getSidebarWidgetsControl().toggleReordering( false );
+
+                               var old_sidebar_id = control.params.sidebar_id;
+                               var new_sidebar_id = control.container.find( '.widget-area-select li.selected' ).data( 'id' );
+                               var old_sidebar_widgets_setting = customize( 'sidebars_widgets[' + old_sidebar_id + ']' );
+                               var new_sidebar_widgets_setting = customize( 'sidebars_widgets[' + new_sidebar_id + ']' );
+                               var old_sidebar_widget_ids = Array.prototype.slice.call( old_sidebar_widgets_setting() );
+                               var new_sidebar_widget_ids = Array.prototype.slice.call( new_sidebar_widgets_setting() );
+
+                               var i = control.getWidgetSidebarPosition();
+                               old_sidebar_widget_ids.splice( i, 1 );
+                               new_sidebar_widget_ids.push( control.params.widget_id );
+
+                               old_sidebar_widgets_setting( old_sidebar_widget_ids );
+                               new_sidebar_widgets_setting( new_sidebar_widget_ids );
+
+                               control.focus();
+                       } );
+               },
+
+               /**
+                * Highlight widgets in preview when interacted with in the customizer
+                */
+               _setupHighlightEffects: function() {
+                       var control = this;
+
+                       // Highlight whenever hovering or clicking over the form
+                       control.container.on( 'mouseenter click', function () {
+                               control.highlightPreviewWidget();
+                       } );
+
+                       // Highlight when the setting is updated
+                       control.setting.bind( function () {
+                               control.scrollPreviewWidgetIntoView();
+                               control.highlightPreviewWidget();
+                       } );
+
+                       // Highlight when the widget form is expanded
+                       control.container.on( 'expand', function () {
+                               control.scrollPreviewWidgetIntoView();
+                       } );
+               },
+
+               /**
+                * Set up event handlers for widget updating
+                */
+               _setupUpdateUI: function () {
+                       var control = this;
+
+                       var widget_content = control.container.find( '.widget-content' );
+
+                       // Configure update button
+                       var save_btn = control.container.find( '.widget-control-save' );
+                       save_btn.val( self.i18n.save_btn_label );
+                       save_btn.attr( 'title', self.i18n.save_btn_tooltip );
+                       save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' );
+                       save_btn.on( 'click', function ( e ) {
+                               e.preventDefault();
+                               control.updateWidget();
+                       } );
+
+                       var trigger_save = _.debounce( function () {
+                               // @todo For compatibility with other plugins, should we trigger a click event? What about form submit event?
+                               control.updateWidget();
+                       }, 250 );
+
+                       // Trigger widget form update when hitting Enter within an input
+                       control.container.find( '.widget-content' ).on( 'keydown', 'input', function( e ) {
+                               if ( 13 === e.which ) { // Enter
+                                       e.preventDefault();
+                                       control.updateWidget( { ignore_active_element: true } );
+                               }
+                       } );
+
+                       // Handle widgets that support live previews
+                       widget_content.on( 'change input propertychange', ':input', function ( e ) {
+                               if ( e.type === 'change' || ( this.checkValidity && this.checkValidity() ) ) {
+                                       trigger_save();
+                               }
+                       } );
+
+                       // Remove loading indicators when the setting is saved and the preview updates
+                       control.setting.previewer.channel.bind( 'synced', function () {
+                               control.container.removeClass( 'previewer-loading' );
+                       } );
+                       self.previewer.bind( 'widget-updated', function ( updated_widget_id ) {
+                               if ( updated_widget_id === control.params.widget_id ) {
+                                       control.container.removeClass( 'previewer-loading' );
+                               }
+                       } );
+
+                       // Update widget control to indicate whether it is currently rendered (cf. Widget Visibility)
+                       self.previewer.bind( 'rendered-widgets', function ( rendered_widgets ) {
+                               var is_rendered = !! rendered_widgets[control.params.widget_id];
+                               control.container.toggleClass( 'widget-rendered', is_rendered );
+                       } );
+               },
+
+               /**
+                * Set up event handlers for widget removal
+                */
+               _setupRemoveUI: function () {
+                       var control = this;
+
+                       // Configure remove button
+                       var remove_btn = control.container.find( 'a.widget-control-remove' );
+                       // @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
+                       remove_btn.on( 'click', function ( e ) {
+                               e.preventDefault();
+
+                               // Find an adjacent element to add focus to when this widget goes away
+                               var adjacent_focus_target;
+                               if ( control.container.next().is( '.customize-control-widget_form' ) ) {
+                                       adjacent_focus_target = control.container.next().find( '.widget-action:first' );
+                               } else if ( control.container.prev().is( '.customize-control-widget_form' ) ) {
+                                       adjacent_focus_target = control.container.prev().find( '.widget-action:first' );
+                               } else {
+                                       adjacent_focus_target = control.container.next( '.customize-control-sidebar_widgets' ).find( '.add-new-widget:first' );
+                               }
+
+                               control.container.slideUp( function() {
+                                       var sidebars_widgets_control = self.getSidebarWidgetControlContainingWidget( control.params.widget_id );
+                                       if ( ! sidebars_widgets_control ) {
+                                               throw new Error( 'Unable to find sidebars_widgets_control' );
+                                       }
+                                       var sidebar_widget_ids = sidebars_widgets_control.setting().slice();
+                                       var i = sidebar_widget_ids.indexOf( control.params.widget_id );
+                                       if ( -1 === i ) {
+                                               throw new Error( 'Widget is not in sidebar' );
+                                       }
+                                       sidebar_widget_ids.splice( i, 1 );
+                                       sidebars_widgets_control.setting( sidebar_widget_ids );
+                                       adjacent_focus_target.focus(); // keyboard accessibility
+                               } );
+                       } );
+
+                       var replace_delete_with_remove = function () {
+                               remove_btn.text( self.i18n.remove_btn_label ); // wp_widget_control() outputs the link as "Delete"
+                               remove_btn.attr( 'title', self.i18n.remove_btn_tooltip );
+                       };
+                       if ( control.params.is_new ) {
+                               wp.customize.bind( 'saved', replace_delete_with_remove );
+                       } else {
+                               replace_delete_with_remove();
+                       }
+               },
+
+               /**
+                * 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
+                * @returns {string}
+                * @private
+                */
+               _getInputsSignature: function ( inputs ) {
+                       var inputs_signatures = _( inputs ).map( function ( input ) {
+                               input = $( input );
+                               var signature_parts;
+                               if ( input.is( 'option' ) ) {
+                                       signature_parts = [ input.prop( 'nodeName' ), input.prop( 'value' ) ];
+                               } else if ( input.is( ':checkbox, :radio' ) ) {
+                                       signature_parts = [ input.prop( 'type' ), input.attr( 'id' ), input.attr( 'name' ), input.prop( 'value' ) ];
+                               } else {
+                                       signature_parts = [ input.prop( 'nodeName' ), input.attr( 'id' ), input.attr( 'name' ), input.attr( 'type' ) ];
+                               }
+                               return signature_parts.join( ',' );
+                       } );
+                       return inputs_signatures.join( ';' );
+               },
+
+               /**
+                * Get the property that represents the state of an input.
+                *
+                * @param {jQuery|DOMElement} input
+                * @returns {string}
+                * @private
+                */
+               _getInputStatePropertyName: function ( input ) {
+                       input = $( input );
+                       if ( input.is( ':radio, :checkbox' ) ) {
+                               return 'checked';
+                       } else if ( input.is( 'option' ) ) {
+                               return 'selected';
+                       } else {
+                               return 'value';
+                       }
+               },
+
+               /***********************************************************************
+                * Begin public API methods
+                **********************************************************************/
+
+               /**
+                * @return {wp.customize.controlConstructor.sidebar_widgets[]}
+                */
+               getSidebarWidgetsControl: function () {
+                       var control = this;
+                       var setting_id = 'sidebars_widgets[' + control.params.sidebar_id + ']';
+                       var sidebar_widgets_control = customize.control( setting_id );
+                       if ( ! sidebar_widgets_control ) {
+                               throw new Error( 'Unable to locate sidebar_widgets control for ' + control.params.sidebar_id );
+                       }
+                       return sidebar_widgets_control;
+               },
+
+               /**
+                * 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.ignore_active_element=false] Whether or not updating a field will be deferred if focus is still on the element.
+                */
+               updateWidget: function ( args ) {
+                       var control = this;
+                       args = $.extend( {
+                               instance: null,
+                               complete: null,
+                               ignore_active_element: false
+                       }, args );
+                       var instance_override = args.instance;
+                       var complete_callback = args.complete;
+
+                       control._update_count += 1;
+                       var update_number = control._update_count;
+
+                       var widget_content = control.container.find( '.widget-content' );
+
+                       var element_id_to_refocus = null;
+                       var active_input_selection_start = null;
+                       var active_input_selection_end = null;
+                       // @todo Support more selectors than IDs?
+                       if ( $.contains( control.container[0], document.activeElement ) && $( document.activeElement ).is( '[id]' ) ) {
+                               element_id_to_refocus = $( document.activeElement ).prop( 'id' );
+                               // @todo IE8 support: http://stackoverflow.com/a/4207763/93579
+                               try {
+                                       active_input_selection_start = document.activeElement.selectionStart;
+                                       active_input_selection_end = document.activeElement.selectionEnd;
+                               }
+                               catch( e ) {} // catch InvalidStateError in case of checkboxes
+                       }
+
+                       control.container.addClass( 'widget-form-loading' );
+                       control.container.addClass( 'previewer-loading' );
+
+                       var params = {};
+                       params.action = self.update_widget_ajax_action;
+                       params[self.update_widget_nonce_post_key] = self.update_widget_nonce_value;
+
+                       var data = $.param( params );
+                       var inputs = widget_content.find( ':input, option' );
+
+                       // 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 () {
+                               var input = $( this );
+                               var property = control._getInputStatePropertyName( this );
+                               input.data( 'state' + update_number, input.prop( property ) );
+                       } );
+
+                       if ( instance_override ) {
+                               data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instance_override ) } );
+                       } else {
+                               data += '&' + inputs.serialize();
+                       }
+                       data += '&' + widget_content.find( '~ :input' ).serialize();
+
+                       console.log( wp.ajax.settings.url, data );
+                       var jqxhr = $.post( wp.ajax.settings.url, data, function ( r ) {
+                               if ( r.success ) {
+                                       var sanitized_form = $( '<div>' + r.data.form + '</div>' );
+                                       control.hook( 'formUpdate', sanitized_form );
+
+                                       var sanitized_inputs = sanitized_form.find( ':input, option' );
+                                       var has_same_inputs_in_response = control._getInputsSignature( inputs ) === control._getInputsSignature( sanitized_inputs );
+
+                                       if ( has_same_inputs_in_response ) {
+                                               inputs.each( function ( i ) {
+                                                       var input = $( this );
+                                                       var sanitized_input = $( sanitized_inputs[i] );
+                                                       var property = control._getInputStatePropertyName( this );
+                                                       var state = input.data( 'state' + update_number );
+                                                       var sanitized_state = sanitized_input.prop( property );
+                                                       input.data( 'sanitized', sanitized_state );
+
+                                                       if ( state !== sanitized_state ) {
+
+                                                               // Only update now if not currently focused on it,
+                                                               // so that we don't cause the cursor
+                                                               // it will be updated upon the change event
+                                                               if ( args.ignore_active_element || ! input.is( document.activeElement ) ) {
+                                                                       input.prop( property, sanitized_state );
+                                                               }
+                                                               control.hook( 'unsanitaryField', input, sanitized_state, state );
+
+                                                       } else {
+                                                               control.hook( 'sanitaryField', input, state );
+                                                       }
+                                               } );
+                                               control.hook( 'formUpdated', sanitized_form );
+                                       } else {
+                                               widget_content.html( sanitized_form.html() );
+                                               if ( element_id_to_refocus ) {
+                                                       // not using jQuery selector so we don't have to worry about escaping IDs with brackets and other characters
+                                                       $( document.getElementById( element_id_to_refocus ) )
+                                                               .prop( {
+                                                                       selectionStart: active_input_selection_start,
+                                                                       selectionEnd: active_input_selection_end
+                                                               } )
+                                                               .focus();
+                                               }
+                                               control.hook( 'formRefreshed' );
+                                       }
+
+                                       /**
+                                        * 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.
+                                        */
+                                       var is_instance_identical = _( control.setting() ).isEqual( r.data.instance );
+                                       if ( is_instance_identical ) {
+                                               control.container.removeClass( 'previewer-loading' );
+                                       } else {
+                                               control.is_widget_updating = true; // suppress triggering another updateWidget
+                                               control.setting( r.data.instance );
+                                               control.is_widget_updating = false;
+                                       }
+
+                                       if ( complete_callback ) {
+                                               complete_callback.call( control, null, { no_change: is_instance_identical, ajax_finished: true } );
+                                       }
+                               } else {
+                                       console.log( r );
+                                       var message = 'FAIL';
+                                       if ( r.data && r.data.message ) {
+                                               message = r.data.message;
+                                       }
+                                       if ( complete_callback ) {
+                                               complete_callback.call( control, message );
+                                       } else {
+                                               throw new Error( message );
+                                       }
+                               }
+                       } );
+                       jqxhr.fail( function ( jqXHR, textStatus ) {
+                               if ( complete_callback ) {
+                                       complete_callback.call( control, textStatus );
+                               } else {
+                                       throw new Error( textStatus );
+                               }
+                       } );
+                       jqxhr.always( function () {
+                               control.container.removeClass( 'widget-form-loading' );
+                               inputs.each( function () {
+                                       $( this ).removeData( 'state' + update_number );
+                               } );
+                       } );
+               },
+
+               /**
+                * Expand the accordion section containing a control
+                * @todo it would be nice if accordion had a proper API instead of having to trigger UI events on its elements
+                */
+               expandControlSection: function () {
+                       var section = this.container.closest( '.accordion-section' );
+                       if ( ! section.hasClass( 'open' ) ) {
+                               section.find( '.accordion-section-title:first' ).trigger( 'click' );
+                       }
+               },
+
+               /**
+                * Expand the widget form control
+                */
+               expandForm: function () {
+                       this.toggleForm( true );
+               },
+
+               /**
+                * Collapse the widget form control
+                */
+               collapseForm: function () {
+                       this.toggleForm( false );
+               },
+
+               /**
+                * Expand or collapse the widget control
+                *
+                * @param {boolean|undefined} [do_expand] If not supplied, will be inverse of current visibility
+                */
+               toggleForm: function ( do_expand ) {
+                       var control = this;
+                       var widget = control.container.find( 'div.widget:first' );
+                       var inside = widget.find( '.widget-inside:first' );
+                       if ( typeof do_expand === 'undefined' ) {
+                               do_expand = ! inside.is( ':visible' );
+                       }
+
+                       // Already expanded or collapsed, so noop
+                       if ( inside.is( ':visible' ) === do_expand ) {
+                               return;
+                       }
+
+                       var complete;
+                       if ( do_expand ) {
+                               // Close all other widget controls before expanding this one
+                               wp.customize.control.each( function ( other_control ) {
+                                       if ( control.params.type === other_control.params.type && control !== other_control ) {
+                                               other_control.collapseForm();
+                                       }
+                               } );
+
+                               control.container.trigger( 'expand' );
+                               control.container.addClass( 'expanding' );
+                               complete = function () {
+                                       control.container.removeClass( 'expanding' );
+                                       control.container.addClass( 'expanded' );
+                                       control.container.trigger( 'expanded' );
+                               };
+                               if ( control.params.is_wide ) {
+                                       inside.animate( { width: 'show' }, 'fast', complete );
+                               } else {
+                                       inside.slideDown( 'fast', complete );
+                               }
+                       } else {
+                               control.container.trigger( 'collapse' );
+                               control.container.addClass( 'collapsing' );
+                               complete = function () {
+                                       control.container.removeClass( 'collapsing' );
+                                       control.container.removeClass( 'expanded' );
+                                       control.container.trigger( 'collapsed' );
+                               };
+                               if ( control.params.is_wide ) {
+                                       inside.animate( { width: 'hide' }, 'fast', complete );
+                               } else {
+                                       inside.slideUp( 'fast', function() {
+                                               widget.css( { width:'', margin:'' } );
+                                               complete();
+                                       } );
+                               }
+                       }
+               },
+
+               /**
+                * Expand the containing sidebar section, expand the form, and focus on
+                * the first input in the control
+                */
+               focus: function () {
+                       var control = this;
+                       control.expandControlSection();
+                       control.expandForm();
+                       control.container.find( ':focusable:first' ).focus().trigger( 'click' );
+               },
+
+               /**
+                * Get the position (index) of the widget in the containing sidebar
+                *
+                * @throws Error
+                * @returns {Number}
+                */
+               getWidgetSidebarPosition: function () {
+                       var control = this;
+                       var sidebar_widget_ids = control.getSidebarWidgetsControl().setting();
+                       var position = sidebar_widget_ids.indexOf( control.params.widget_id );
+                       if ( position === -1 ) {
+                               throw new Error( 'Widget was unexpectedly not present in the sidebar.' );
+                       }
+                       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 control = this;
+                       var i = control.getWidgetSidebarPosition();
+
+                       var sidebar_widgets_setting = control.getSidebarWidgetsControl().setting;
+                       var sidebar_widget_ids = Array.prototype.slice.call( sidebar_widgets_setting() ); // clone
+                       var adjacent_widget_id = sidebar_widget_ids[i + offset];
+                       sidebar_widget_ids[i + offset] = control.params.widget_id;
+                       sidebar_widget_ids[i] = adjacent_widget_id;
+
+                       sidebar_widgets_setting( sidebar_widget_ids );
+               },
+
+               /**
+                * Toggle visibility of the widget move area
+                *
+                * @param {Boolean} [toggle]
+                */
+               toggleWidgetMoveArea: function ( toggle ) {
+                       var control = this;
+                       var move_widget_area = control.container.find( '.move-widget-area' );
+                       if ( typeof toggle === 'undefined' ) {
+                               toggle = ! move_widget_area.hasClass( 'active' );
+                       }
+                       if ( toggle ) {
+                               // reset the selected sidebar
+                               move_widget_area.find( '.selected' ).removeClass( 'selected' );
+                               move_widget_area.find( 'li' ).filter( function () {
+                                       return $( this ).data( 'id' ) === control.params.sidebar_id;
+                               } ).addClass( 'selected' );
+                               control.container.find( '.move-widget-btn' ).prop( 'disabled', true );
+                       }
+                       move_widget_area.toggleClass( 'active', toggle );
+               },
+
+               /**
+                * Inverse of WidgetCustomizer.getControlInstanceForWidget
+                * @return {jQuery}
+                */
+               getPreviewWidgetElement: function () {
+                       var control = this;
+                       var widget_customizer_preview = self.getPreviewWindow().WidgetCustomizerPreview;
+                       return widget_customizer_preview.getSidebarWidgetElement( control.params.sidebar_id, control.params.widget_id );
+               },
+
+               /**
+                * Inside of the customizer preview, scroll the widget into view
+                */
+               scrollPreviewWidgetIntoView: function () {
+                       // @todo scrollIntoView() provides a robust but very poor experience. Animation is needed. See https://github.com/x-team/wp-widget-customizer/issues/16
+               },
+
+               /**
+                * Highlight the widget control and section
+                */
+               highlightSectionAndControl: function() {
+                       var control = this;
+                       var target_element;
+                       if ( control.container.is( ':hidden' ) ) {
+                               target_element = control.container.closest( '.control-section' );
+                       } else {
+                               target_element = control.container;
+                       }
+
+                       $( '.widget-customizer-highlighted' ).removeClass( 'widget-customizer-highlighted' );
+                       target_element.addClass( 'widget-customizer-highlighted' );
+                       setTimeout( function () {
+                               target_element.removeClass( 'widget-customizer-highlighted' );
+                       }, 500 );
+               },
+
+               /**
+                * Add the widget-customizer-highlighted-widget class to the widget for 500ms
+                */
+               highlightPreviewWidget: function () {
+                       var control = this;
+                       var widget_el = control.getPreviewWidgetElement();
+                       var root_el = widget_el.closest( 'html' );
+                       root_el.find( '.widget-customizer-highlighted-widget' ).removeClass( 'widget-customizer-highlighted-widget' );
+                       widget_el.addClass( 'widget-customizer-highlighted-widget' );
+                       setTimeout( function () {
+                               widget_el.removeClass( 'widget-customizer-highlighted-widget' );
+                       }, 500 );
+               }
+
+       } );
+
+       /**
+        * Capture the instance of the Previewer since it is private
+        */
+       var OldPreviewer = wp.customize.Previewer;
+       wp.customize.Previewer = OldPreviewer.extend( {
+               initialize: function( params, options ) {
+                       self.previewer = this;
+                       OldPreviewer.prototype.initialize.call( this, params, options );
+                       this.bind( 'refresh', this.refresh );
+               }
+       } );
+
+       /**
+        * Given a widget control, find the sidebar widgets control that contains it.
+        * @param {string} widget_id
+        * @return {object|null}
+        */
+       self.getSidebarWidgetControlContainingWidget = function ( widget_id ) {
+               var found_control = null;
+               // @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
+               wp.customize.control.each( function ( control ) {
+                       if ( control.params.type === 'sidebar_widgets' && -1 !== control.setting().indexOf( widget_id ) ) {
+                               found_control = control;
+                       }
+               } );
+               return found_control;
+       };
+
+       /**
+        * Given a widget_id for a widget appearing in the preview, get the widget form control associated with it
+        * @param {string} widget_id
+        * @return {object|null}
+        */
+       self.getWidgetFormControlForWidget = function ( widget_id ) {
+               var found_control = null;
+               // @todo We can just use widget_id_to_setting_id() here
+               wp.customize.control.each( function ( control ) {
+                       if ( control.params.type === 'widget_form' && control.params.widget_id === widget_id ) {
+                               found_control = control;
+                       }
+               } );
+               return found_control;
+       };
+
+       /**
+        * @returns {Window}
+        */
+       self.getPreviewWindow = function (){
+               return $( '#customize-preview' ).find( 'iframe' ).prop( 'contentWindow' );
+       };
+
+       /**
+        * Available Widgets Panel
+        */
+       self.availableWidgetsPanel = {
+               active_sidebar_widgets_control: null,
+               selected_widget_tpl: null,
+               container: null,
+               filter_input: null,
+
+               /**
+                * Set up event listeners
+                */
+               setup: function () {
+                       var panel = this;
+                       panel.container = $( '#available-widgets' );
+                       panel.filter_input = $( '#available-widgets-filter' ).find( 'input' );
+
+                       var update_available_widgets_list = function () {
+                               self.available_widgets.each( function ( widget ) {
+                                       var widget_tpl = $( '#widget-tpl-' + widget.id );
+                                       widget_tpl.toggle( ! widget.get( 'is_disabled' ) );
+                                       if ( widget.get( 'is_disabled' ) && widget_tpl.is( panel.selected_widget_tpl ) ) {
+                                               panel.selected_widget_tpl = null;
+                                       }
+                               } );
+                       };
+
+                       self.available_widgets.on( 'change', update_available_widgets_list );
+                       update_available_widgets_list();
+
+                       // 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.
+                       $( '#customize-controls' ).on( 'click keydown', function ( e ) {
+                               var is_add_new_widget_btn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
+                               if ( $( 'body' ).hasClass( 'adding-widget' ) && ! is_add_new_widget_btn ) {
+                                       panel.close();
+                               }
+                       } );
+
+                       // Close the panel if the URL in the preview changes
+                       self.previewer.bind( 'url', function () {
+                               panel.close();
+                       } );
+
+                       // Submit a selection when clicked or keypressed
+                       panel.container.find( '.widget-tpl' ).on( 'click keypress', function( event ) {
+
+                               // Only proceed with keypress if it is Enter or Spacebar
+                               if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+                                       return;
+                               }
+
+                               panel.submit( this );
+                       } );
+
+                       panel.container.liveFilter(
+                               '#available-widgets-filter input',
+                               '.widget-tpl',
+                               {
+                                       filterChildSelector: '.widget-title h4',
+                                       after: function () {
+                                               var filter_val = panel.filter_input.val();
+
+                                               // Remove a widget from being selected if it is no longer visible
+                                               if ( panel.selected_widget_tpl && ! panel.selected_widget_tpl.is( ':visible' ) ) {
+                                                       panel.selected_widget_tpl.removeClass( 'selected' );
+                                                       panel.selected_widget_tpl = null;
+                                               }
+
+                                               // If a widget was selected but the filter value has been cleared out, clear selection
+                                               if ( panel.selected_widget_tpl && ! filter_val ) {
+                                                       panel.selected_widget_tpl.removeClass( 'selected' );
+                                                       panel.selected_widget_tpl = null;
+                                               }
+
+                                               // If a filter has been entered and a widget hasn't been selected, select the first one shown
+                                               if ( ! panel.selected_widget_tpl && filter_val ) {
+                                                       var first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' );
+                                                       if ( first_visible_widget.length ) {
+                                                               panel.select( first_visible_widget );
+                                                       }
+                                               }
+
+                                       }
+                               }
+                       );
+
+                       // Select a widget when it is focused on
+                       panel.container.find( ' > .widget-tpl' ).on( 'focus', function () {
+                               panel.select( this );
+                       } );
+
+                       panel.container.on( 'keydown', function ( event ) {
+                               var is_enter = ( event.which === 13 );
+                               var is_esc = ( event.which === 27 );
+                               var is_down = ( event.which === 40 );
+                               var is_up = ( event.which === 38 );
+                               var selected_widget_tpl = null;
+                               var first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' );
+                               var last_visible_widget = panel.container.find( '> .widget-tpl:visible:last' );
+                               var is_input_focused = $( event.target ).is( panel.filter_input );
+
+                               if ( is_down || is_up ) {
+                                       if ( is_down ) {
+                                               if ( is_input_focused ) {
+                                                       selected_widget_tpl = first_visible_widget;
+                                               } else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
+                                                       selected_widget_tpl = panel.selected_widget_tpl.nextAll( '.widget-tpl:visible:first' );
+                                               }
+                                       } else if ( is_up ) {
+                                               if ( is_input_focused ) {
+                                                       selected_widget_tpl = last_visible_widget;
+                                               } else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
+                                                       selected_widget_tpl = panel.selected_widget_tpl.prevAll( '.widget-tpl:visible:first' );
+                                               }
+                                       }
+                                       panel.select( selected_widget_tpl );
+                                       if ( selected_widget_tpl ) {
+                                               selected_widget_tpl.focus();
+                                       } else {
+                                               panel.filter_input.focus();
+                                       }
+                                       return;
+                               }
+
+                               // If enter pressed but nothing entered, don't do anything
+                               if ( is_enter && ! panel.filter_input.val() ) {
+                                       return;
+                               }
+
+                               if ( is_enter ) {
+                                       panel.submit();
+                               } else if ( is_esc ) {
+                                       panel.close( { return_focus: true } );
+                               }
+                       } );
+               },
+
+               /**
+                * @param widget_tpl
+                */
+               select: function ( widget_tpl ) {
+                       var panel = this;
+                       panel.selected_widget_tpl = $( widget_tpl );
+                       panel.selected_widget_tpl.siblings( '.widget-tpl' ).removeClass( 'selected' );
+                       panel.selected_widget_tpl.addClass( 'selected' );
+               },
+
+               submit: function ( widget_tpl ) {
+                       var panel = this;
+                       if ( ! widget_tpl ) {
+                               widget_tpl = panel.selected_widget_tpl;
+                       }
+                       if ( ! widget_tpl || ! panel.active_sidebar_widgets_control ) {
+                               return;
+                       }
+                       panel.select( widget_tpl );
+
+                       var widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' );
+                       var widget = self.available_widgets.findWhere( {id: widget_id} );
+                       if ( ! widget ) {
+                               throw new Error( 'Widget unexpectedly not found.' );
+                       }
+                       panel.active_sidebar_widgets_control.addWidget( widget.get( 'id_base' ) );
+                       panel.close();
+               },
+
+               /**
+                * @param sidebars_widgets_control
+                */
+               open: function ( sidebars_widgets_control ) {
+                       var panel = this;
+                       panel.active_sidebar_widgets_control = sidebars_widgets_control;
+
+                       // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens
+                       _( sidebars_widgets_control.getWidgetFormControls() ).each( function ( control ) {
+                               if ( control.params.is_wide ) {
+                                       control.collapseForm();
+                               }
+                       } );
+
+                       $( 'body' ).addClass( 'adding-widget' );
+                       panel.container.find( '.widget-tpl' ).removeClass( 'selected' );
+                       panel.filter_input.focus();
+               },
+
+               /**
+                * Hide the panel
+                */
+               close: function ( options ) {
+                       var panel = this;
+                       options = options || {};
+                       if ( options.return_focus && panel.active_sidebar_widgets_control ) {
+                               panel.active_sidebar_widgets_control.container.find( '.add-new-widget' ).focus();
+                       }
+                       panel.active_sidebar_widgets_control = null;
+                       panel.selected_widget_tpl = null;
+                       $( 'body' ).removeClass( 'adding-widget' );
+                       panel.filter_input.val( '' );
+               }
+       };
+
+       /**
+        * @param {String} widget_id
+        * @returns {Object}
+        */
+       function parse_widget_id( widget_id ) {
+               var parsed = {
+                       number: null,
+                       id_base: null
+               };
+               var matches = widget_id.match( /^(.+)-(\d+)$/ );
+               if ( matches ) {
+                       parsed.id_base = matches[1];
+                       parsed.number = parseInt( matches[2], 10 );
+               } else {
+                       // likely an old single widget
+                       parsed.id_base = widget_id;
+               }
+               return parsed;
+       }
+
+       /**
+        * @param {String} widget_id
+        * @returns {String} setting_id
+        */
+       function widget_id_to_setting_id( widget_id ) {
+               var parsed = parse_widget_id( widget_id );
+               var setting_id = 'widget_' + parsed.id_base;
+               if ( parsed.number ) {
+                       setting_id += '[' + parsed.number + ']';
+               }
+               return setting_id;
+       }
+
+       return self;
+}( jQuery ));
+
+/* @todo remove this dependency */
+/*
+ * jQuery.liveFilter
+ *
+ * Copyright (c) 2009 Mike Merritt
+ *
+ * Forked by Lim Chee Aun (cheeaun.com)
+ *
+ */
+
+(function($){
+       $.fn.liveFilter = function(inputEl, filterEl, options){
+               var defaults = {
+                       filterChildSelector: null,
+                       filter: function(el, val){
+                               return $(el).text().toUpperCase().indexOf(val.toUpperCase()) >= 0;
+                       },
+                       before: function(){},
+                       after: function(){}
+               };
+               options = $.extend(defaults, options);
+
+               var el = $(this).find(filterEl);
+               if (options.filterChildSelector) {
+                       el = el.find(options.filterChildSelector);
+               }
+
+               var filter = options.filter;
+               $(inputEl).keyup(function(){
+                       var val = $(this).val();
+                       var contains = el.filter(function(){
+                               return filter(this, val);
+                       });
+                       var containsNot = el.not(contains);
+                       if (options.filterChildSelector){
+                               contains = contains.parents(filterEl);
+                               containsNot = containsNot.parents(filterEl).hide();
+                       }
+
+                       options.before.call(this, contains, containsNot);
+
+                       contains.show();
+                       containsNot.hide();
+
+                       if (val === '') {
+                               contains.show();
+                               containsNot.show();
+                       }
+
+                       options.after.call(this, contains, containsNot);
+               });
+       };
+})(jQuery);
</ins></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizecontrolphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/class-wp-customize-control.php (27418 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/class-wp-customize-control.php     2014-03-05 20:03:33 UTC (rev 27418)
+++ trunk/src/wp-includes/class-wp-customize-control.php        2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -813,4 +813,76 @@
</span><span class="cx">          foreach ( $this->default_headers as $choice => $header )
</span><span class="cx">                  $this->print_header_image( $choice, $header );
</span><span class="cx">  }
</span><del>-}
</del><span class="cx">\ No newline at end of file
</span><ins>+}
+
+/**
+ * Widget Area Customize Control Class
+ *
+ */
+class WP_Widget_Area_Customize_Control extends WP_Customize_Control {
+       public $type = 'sidebar_widgets';
+       public $sidebar_id;
+
+       public function to_json() {
+               parent::to_json();
+               $exported_properties = array( 'sidebar_id' );
+               foreach ( $exported_properties as $key ) {
+                       $this->json[ $key ] = $this->$key;
+               }
+       }
+
+       public function render_content() {
+               ?>
+               <span class="button-secondary add-new-widget" tabindex="0">
+                       <?php esc_html_e( 'Add a Widget' ); ?>
+               </span>
+
+               <span class="reorder-toggle" tabindex="0">
+                       <span class="reorder"><?php esc_html_e( 'Reorder' ); ?></span>
+                       <span class="reorder-done"><?php esc_html_e( 'Done' ); ?></span>
+               </span>
+               <?php
+       }
+}
+
+/**
+ * Widget Form Customize Control Class
+ */
+class WP_Widget_Form_Customize_Control extends WP_Customize_Control {
+       public $type = 'widget_form';
+       public $widget_id;
+       public $widget_id_base;
+       public $sidebar_id;
+       public $is_new = false;
+       public $width;
+       public $height;
+       public $is_wide = false;
+       public $is_live_previewable = false;
+
+       public function to_json() {
+               parent::to_json();
+               $exported_properties = array( 'widget_id', 'widget_id_base', 'sidebar_id', 'width', 'height', 'is_wide', 'is_live_previewable' );
+               foreach ( $exported_properties as $key ) {
+                       $this->json[ $key ] = $this->$key;
+               }
+       }
+
+       public function render_content() {
+               global $wp_registered_widgets;
+               require_once ABSPATH . '/wp-admin/includes/widgets.php';
+
+               $widget = $wp_registered_widgets[ $this->widget_id ];
+               if ( ! isset( $widget['params'][0] ) ) {
+                       $widget['params'][0] = array();
+               }
+
+               $args = array(
+                       'widget_id' => $widget['id'],
+                       'widget_name' => $widget['name'],
+               );
+
+               $args = wp_list_widget_controls_dynamic_sidebar( array( 0 => $args, 1 => $widget['params'][0] ) );
+               echo WP_Customize_Widgets::get_widget_control( $args );
+       }
+}
+
</ins></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizemanagerphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/class-wp-customize-manager.php (27418 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/class-wp-customize-manager.php     2014-03-05 20:03:33 UTC (rev 27418)
+++ trunk/src/wp-includes/class-wp-customize-manager.php        2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -61,7 +61,10 @@
</span><span class="cx">          require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
</span><span class="cx">          require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
</span><span class="cx">          require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
</span><ins>+               require( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
</ins><span class="cx"> 
</span><ins>+               WP_Customize_Widgets::setup(); // This should be integrated.
+
</ins><span class="cx">           add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
</span><span class="cx"> 
</span><span class="cx">          add_action( 'setup_theme',  array( $this, 'setup_theme' ) );
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizewidgetsphp"></a>
<div class="addfile"><h4>Added: trunk/src/wp-includes/class-wp-customize-widgets.php (0 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/class-wp-customize-widgets.php                             (rev 0)
+++ trunk/src/wp-includes/class-wp-customize-widgets.php        2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -0,0 +1,1264 @@
</span><ins>+<?php
+/**
+ * Widget customizer manager class.
+ */
+class WP_Customize_Widgets {
+       const UPDATE_WIDGET_AJAX_ACTION    = 'update-widget';
+       const UPDATE_WIDGET_NONCE_POST_KEY = 'update-sidebar-widgets-nonce';
+
+       /**
+        * All id_bases for widgets defined in core
+        *
+        * @var array
+        */
+       protected static $core_widget_id_bases = array(
+               'archives',
+               'calendar',
+               'categories',
+               'links',
+               'meta',
+               'nav_menu',
+               'pages',
+               'recent-comments',
+               'recent-posts',
+               'rss',
+               'search',
+               'tag_cloud',
+               'text',
+       );
+
+       /**
+        * Initial loader.
+        */
+       static function setup() {
+               add_action( 'after_setup_theme', array( __CLASS__, 'setup_widget_addition_previews' ) );
+               add_action( 'customize_controls_init', array( __CLASS__, 'customize_controls_init' ) );
+               add_action( 'customize_register', array( __CLASS__, 'schedule_customize_register' ), 1 );
+               add_action( 'customize_controls_enqueue_scripts', array( __CLASS__, 'customize_controls_enqueue_deps' ) );
+               add_action( 'customize_controls_print_footer_scripts', array( __CLASS__, 'output_widget_control_templates' ) );
+               add_action( 'customize_preview_init', array( __CLASS__, 'customize_preview_init' ) );
+
+               add_action( 'dynamic_sidebar', array( __CLASS__, 'tally_rendered_widgets' ) );
+               add_action( 'dynamic_sidebar', array( __CLASS__, 'tally_sidebars_via_dynamic_sidebar_actions' ) );
+               add_filter( 'temp_is_active_sidebar', array( __CLASS__, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
+               add_filter( 'temp_dynamic_sidebar_has_widgets', array( __CLASS__, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
+
+               /**
+                * Special filter for Settings Revisions plugin until it can handle
+                * dynamically creating settings. Normally this should be handled by
+                * a setting's sanitize_js_callback, but when restoring an old revision
+                * it may include settings which do not currently exist, and so they
+                * do not have the opportunity to be sanitized as needed. Furthermore,
+                * we have to add this filter here because the customizer is not
+                * initialized in WP Ajax, which is where Settings Revisions currently
+                * needs to apply this filter at times.
+                */
+               add_filter( 'temp_customize_sanitize_js', array( __CLASS__, 'temp_customize_sanitize_js' ), 10, 2 );
+       }
+
+       /**
+        * Get an unslashed post value, or return a default
+        *
+        * @param string $name
+        * @param mixed $default
+        * @return mixed
+        */
+       static function get_post_value( $name, $default = null ) {
+               if ( ! isset( $_POST[$name] ) ) {
+                       return $default;
+               }
+               return wp_unslash( $_POST[$name] );
+       }
+
+       protected static $_customized;
+       protected static $_prepreview_added_filters = array();
+
+       /**
+        * Since the widgets get registered (widgets_init) before the customizer settings are set up (customize_register),
+        * we have to filter the options similarly to how the setting previewer will filter the options later.
+        *
+        * @action after_setup_theme
+        */
+       static function setup_widget_addition_previews() {
+               global $wp_customize;
+               $is_customize_preview = (
+                       ( ! empty( $wp_customize ) )
+                       &&
+                       ( ! is_admin() )
+                       &&
+                       ( 'on' === self::get_post_value( 'wp_customize' ) )
+                       &&
+                       check_ajax_referer( 'preview-customize_' . $wp_customize->get_stylesheet(), 'nonce', false )
+               );
+
+               $is_ajax_widget_update = (
+                       ( defined( 'DOING_AJAX' ) && DOING_AJAX )
+                       &&
+                       self::get_post_value( 'action' ) === self::UPDATE_WIDGET_AJAX_ACTION
+                       &&
+                       check_ajax_referer( self::UPDATE_WIDGET_AJAX_ACTION, self::UPDATE_WIDGET_NONCE_POST_KEY, false )
+               );
+
+               $is_ajax_customize_save = (
+                       ( defined( 'DOING_AJAX' ) && DOING_AJAX )
+                       &&
+                       self::get_post_value( 'action' ) === 'customize_save'
+                       &&
+                       check_ajax_referer( 'save-customize_' . $wp_customize->get_stylesheet(), 'nonce' )
+               );
+
+               $is_valid_request = ( $is_ajax_widget_update || $is_customize_preview || $is_ajax_customize_save );
+               if ( ! $is_valid_request ) {
+                       return;
+               }
+
+               // Input from customizer preview
+               if ( isset( $_POST['customized'] ) ) {
+                       $customized = json_decode( self::get_post_value( 'customized' ), true );
+               }
+               // Input from ajax widget update request
+               else {
+                       $customized    = array();
+                       $id_base       = self::get_post_value( 'id_base' );
+                       $widget_number = (int) self::get_post_value( 'widget_number' );
+                       $option_name   = 'widget_' . $id_base;
+                       $customized[$option_name] = array();
+                       if ( false !== $widget_number ) {
+                               $option_name .= '[' . $widget_number . ']';
+                               $customized[$option_name][$widget_number] = array();
+                       }
+               }
+
+               $function = array( __CLASS__, 'prepreview_added_sidebars_widgets' );
+
+               $hook = 'option_sidebars_widgets';
+               add_filter( $hook, $function );
+               self::$_prepreview_added_filters[] = compact( 'hook', 'function' );
+
+               $hook = 'default_option_sidebars_widgets';
+               add_filter( $hook, $function );
+               self::$_prepreview_added_filters[] = compact( 'hook', 'function' );
+
+               foreach ( $customized as $setting_id => $value ) {
+                       if ( preg_match( '/^(widget_.+?)(\[(\d+)\])?$/', $setting_id, $matches ) ) {
+                               $body     = sprintf( 'return %s::prepreview_added_widget_instance( $value, %s );', __CLASS__, var_export( $setting_id, true ) );
+                               $function = create_function( '$value', $body );
+                               $option   = $matches[1];
+
+                               $hook = sprintf( 'option_%s', $option );
+                               add_filter( $hook, $function );
+                               self::$_prepreview_added_filters[] = compact( 'hook', 'function' );
+
+                               $hook = sprintf( 'default_option_%s', $option );
+                               add_filter( $hook, $function );
+                               self::$_prepreview_added_filters[] = compact( 'hook', 'function' );
+
+                               /**
+                                * Make sure the option is registered so that the update_option won't fail due to
+                                * the filters providing a default value, which causes the update_option() to get confused.
+                                */
+                               add_option( $option, array() );
+                       }
+               }
+
+               self::$_customized = $customized;
+       }
+
+       /**
+        * Ensure that newly-added widgets will appear in the widgets_sidebars.
+        * This is necessary because the customizer's setting preview filters are added after the widgets_init action,
+        * which is too late for the widgets to be set up properly.
+        *
+        * @param array $sidebars_widgets
+        * @return array
+        */
+       static function prepreview_added_sidebars_widgets( $sidebars_widgets ) {
+               foreach ( self::$_customized as $setting_id => $value ) {
+                       if ( preg_match( '/^sidebars_widgets\[(.+?)\]$/', $setting_id, $matches ) ) {
+                               $sidebar_id = $matches[1];
+                               $sidebars_widgets[$sidebar_id] = $value;
+                       }
+               }
+               return $sidebars_widgets;
+       }
+
+       /**
+        * Ensure that newly-added widgets will have empty instances so that they will be recognized.
+        * This is necessary because the customizer's setting preview filters are added after the widgets_init action,
+        * which is too late for the widgets to be set up properly.
+        *
+        * @param array $instance
+        * @param string $setting_id
+        * @return array
+        */
+       static function prepreview_added_widget_instance( $instance, $setting_id ) {
+               if ( isset( self::$_customized[$setting_id] ) ) {
+                       $parsed_setting_id = self::parse_widget_setting_id( $setting_id );
+                       $widget_number     = $parsed_setting_id['number'];
+
+                       // Single widget
+                       if ( is_null( $widget_number ) ) {
+                               if ( false === $instance && empty( $value ) ) {
+                                       $instance = array();
+                               }
+                       }
+                       // Multi widget
+                       else if ( false === $instance || ! isset( $instance[$widget_number] ) ) {
+                               if ( empty( $instance ) ) {
+                                       $instance = array( '_multiwidget' => 1 );
+                               }
+                               if ( ! isset( $instance[$widget_number] ) ) {
+                                       $instance[$widget_number] = array();
+                               }
+                       }
+               }
+               return $instance;
+       }
+
+       /**
+        * Remove filters added in setup_widget_addition_previews() which ensure that
+        * widgets are populating the options during widgets_init
+        *
+        * @action wp_loaded
+        */
+       static function remove_prepreview_filters() {
+               foreach ( self::$_prepreview_added_filters as $prepreview_added_filter ) {
+                       remove_filter( $prepreview_added_filter['hook'], $prepreview_added_filter['function'] );
+               }
+               self::$_prepreview_added_filters = array();
+       }
+
+       /**
+        * Make sure that all widgets get loaded into customizer; these actions are also done in the wp_ajax_save_widget()
+        *
+        * @see wp_ajax_save_widget()
+        * @action customize_controls_init
+        */
+       static function customize_controls_init() {
+               do_action( 'load-widgets.php' );
+               do_action( 'widgets.php' );
+               do_action( 'sidebar_admin_setup' );
+       }
+
+       /**
+        * When in preview, invoke customize_register for settings after WordPress is
+        * loaded so that all filters have been initialized (e.g. Widget Visibility)
+        */
+       static function schedule_customize_register( $wp_customize ) {
+               if ( is_admin() ) { // @todo for some reason, $wp_customize->is_preview() is true here?
+                       self::customize_register( $wp_customize );
+               } else {
+                       add_action( 'wp', array( __CLASS__, 'customize_register' ) );
+               }
+       }
+
+       /**
+        * Register customizer settings and controls for all sidebars and widgets
+        *
+        * @action customize_register
+        */
+       static function customize_register( $wp_customize = null ) {
+               global $wp_registered_widgets, $wp_registered_widget_controls;
+               if ( ! ( $wp_customize instanceof WP_Customize_Manager ) ) {
+                       $wp_customize = $GLOBALS['wp_customize'];
+               }
+
+               $sidebars_widgets = array_merge(
+                       array( 'wp_inactive_widgets' => array() ),
+                       array_fill_keys( array_keys( $GLOBALS['wp_registered_sidebars'] ), array() ),
+                       wp_get_sidebars_widgets()
+               );
+
+               $new_setting_ids = array();
+
+               /**
+                * Register a setting for all widgets, including those which are active, inactive, and orphaned
+                * since a widget may get suppressed from a sidebar via a plugin (like Widget Visibility).
+                */
+               foreach ( array_keys( $wp_registered_widgets ) as $widget_id ) {
+                       $setting_id   = self::get_setting_id( $widget_id );
+                       $setting_args = self::get_setting_args( $setting_id );
+                       $setting_args['sanitize_callback']    = array( __CLASS__, 'sanitize_widget_instance' );
+                       $setting_args['sanitize_js_callback'] = array( __CLASS__, 'sanitize_widget_js_instance' );
+                       $wp_customize->add_setting( $setting_id, $setting_args );
+                       $new_setting_ids[] = $setting_id;
+               }
+
+               foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
+                       if ( empty( $sidebar_widget_ids ) ) {
+                               $sidebar_widget_ids = array();
+                       }
+                       $is_registered_sidebar = isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] );
+                       $is_inactive_widgets   = ( 'wp_inactive_widgets' === $sidebar_id );
+                       $is_active_sidebar     = ( $is_registered_sidebar && ! $is_inactive_widgets );
+
+                       /**
+                        * Add setting for managing the sidebar's widgets
+                        */
+                       if ( $is_registered_sidebar || $is_inactive_widgets ) {
+                               $setting_id   = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
+                               $setting_args = self::get_setting_args( $setting_id );
+                               $setting_args['sanitize_callback']    = array( __CLASS__, 'sanitize_sidebar_widgets' );
+                               $setting_args['sanitize_js_callback'] = array( __CLASS__, 'sanitize_sidebar_widgets_js_instance' );
+                               $wp_customize->add_setting( $setting_id, $setting_args );
+                               $new_setting_ids[] = $setting_id;
+
+                               /**
+                                * Add section to contain controls
+                                */
+                               $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id );
+                               if ( $is_active_sidebar ) {
+                                       $section_args = array(
+                                               'title' => sprintf( __( 'Widgets: %s' ), $GLOBALS['wp_registered_sidebars'][$sidebar_id]['name'] ),
+                                               'description' => $GLOBALS['wp_registered_sidebars'][$sidebar_id]['description'],
+                                       );
+                                       $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id );
+                                       $wp_customize->add_section( $section_id, $section_args );
+
+                                       $control = new WP_Widget_Area_Customize_Control(
+                                               $wp_customize,
+                                               $setting_id,
+                                               array(
+                                                       'section' => $section_id,
+                                                       'sidebar_id' => $sidebar_id,
+                                                       //'priority' => 99, // so it appears at the end
+                                               )
+                                       );
+                                       $new_setting_ids[] = $setting_id;
+                                       $wp_customize->add_control( $control );
+                               }
+                       }
+
+                       /**
+                        * Add a control for each active widget (located in a sidebar)
+                        */
+                       foreach ( $sidebar_widget_ids as $i => $widget_id ) {
+                               // Skip widgets that may have gone away due to a plugin being deactivated
+                               if ( ! $is_active_sidebar || ! isset( $GLOBALS['wp_registered_widgets'][$widget_id] ) ) {
+                                       continue;
+                               }
+                               $registered_widget = $GLOBALS['wp_registered_widgets'][$widget_id];
+                               $setting_id = self::get_setting_id( $widget_id );
+                               $id_base = $GLOBALS['wp_registered_widget_controls'][$widget_id]['id_base'];
+                               assert( false !== is_active_widget( $registered_widget['callback'], $registered_widget['id'], false, false ) );
+                               $control = new WP_Widget_Form_Customize_Control(
+                                       $wp_customize,
+                                       $setting_id,
+                                       array(
+                                               'label' => $registered_widget['name'],
+                                               'section' => $section_id,
+                                               'sidebar_id' => $sidebar_id,
+                                               'widget_id' => $widget_id,
+                                               'widget_id_base' => $id_base,
+                                               'priority' => $i,
+                                               'width' => $wp_registered_widget_controls[$widget_id]['width'],
+                                               'height' => $wp_registered_widget_controls[$widget_id]['height'],
+                                               'is_wide' => self::is_wide_widget( $widget_id ),
+                                       )
+                               );
+                               $wp_customize->add_control( $control );
+                       }
+               }
+
+               /**
+                * We have to register these settings later than customize_preview_init so that other
+                * filters have had a chance to run.
+                * @see self::schedule_customize_register()
+                */
+               if ( did_action( 'customize_preview_init' ) ) {
+                       foreach ( $new_setting_ids as $new_setting_id ) {
+                               $wp_customize->get_setting( $new_setting_id )->preview();
+                       }
+               }
+
+               self::remove_prepreview_filters();
+       }
+
+       /**
+        * Covert a widget_id into its corresponding customizer setting id (option name)
+        *
+        * @param string $widget_id
+        * @see _get_widget_id_base()
+        * @return string
+        */
+       static function get_setting_id( $widget_id ) {
+               $parsed_widget_id = self::parse_widget_id( $widget_id );
+               $setting_id = sprintf( 'widget_%s', $parsed_widget_id['id_base'] );
+               if ( ! is_null( $parsed_widget_id['number'] ) ) {
+                       $setting_id .= sprintf( '[%d]', $parsed_widget_id['number'] );
+               }
+               return $setting_id;
+       }
+
+       /**
+        * Core widgets which may have controls wider than 250, but can still be
+        * shown in the narrow customizer panel. The RSS and Text widgets in Core,
+        * for example, have widths of 400 and yet they still render fine in the
+        * customizer panel. This method will return all Core widgets as being
+        * not wide, but this can be overridden with the is_wide_widget_in_customizer
+        * filter.
+        *
+        * @param string $widget_id
+        * @return bool
+        */
+       static function is_wide_widget( $widget_id ) {
+               global $wp_registered_widget_controls;
+               $parsed_widget_id = self::parse_widget_id( $widget_id );
+               $width = $wp_registered_widget_controls[$widget_id]['width'];
+               $is_core = in_array( $parsed_widget_id['id_base'], self::$core_widget_id_bases );
+               $is_wide = ( $width > 250 && ! $is_core );
+               $is_wide = apply_filters( 'is_wide_widget_in_customizer', $is_wide, $widget_id );
+               return $is_wide;
+       }
+
+       /**
+        * Covert a widget ID into its id_base and number components
+        *
+        * @param string $widget_id
+        * @return array
+        */
+       static function parse_widget_id( $widget_id ) {
+               $parsed = array(
+                       'number' => null,
+                       'id_base' => null,
+               );
+               if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
+                       $parsed['id_base'] = $matches[1];
+                       $parsed['number']  = intval( $matches[2] );
+               } else {
+                       // likely an old single widget
+                       $parsed['id_base'] = $widget_id;
+               }
+               return $parsed;
+       }
+
+       /**
+        * Convert a widget setting ID (option path) to its id_base and number components
+        *
+        * @throws Widget_Customizer_Exception
+        * @throws Exception
+        *
+        * @param string $setting_id
+        * @param array
+        * @return array
+        */
+       static function parse_widget_setting_id( $setting_id ) {
+               if ( ! preg_match( '/^(widget_(.+?))(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
+                       throw new Widget_Customizer_Exception( sprintf( 'Invalid widget setting ID: %s', $setting_id ) );
+               }
+               $id_base = $matches[2];
+               $number  = isset( $matches[3] ) ? intval( $matches[3] ) : null;
+               return compact( 'id_base', 'number' );
+       }
+
+       /**
+        * Enqueue scripts and styles for customizer panel and export data to JS
+        *
+        * @action customize_controls_enqueue_scripts
+        */
+       static function customize_controls_enqueue_deps() {
+               wp_enqueue_script( 'jquery-ui-sortable' );
+               wp_enqueue_script( 'jquery-ui-droppable' );
+               wp_enqueue_style(
+                       'widget-customizer',
+                       admin_url( 'css/customize-widgets.css' )
+               );
+               wp_enqueue_script(
+                       'widget-customizer',
+                       admin_url( 'js/customize-widgets.js' ),
+                       array( 'jquery', 'wp-backbone', 'wp-util', 'customize-controls' )
+               );
+
+               // Export available widgets with control_tpl removed from model
+               // since plugins need templates to be in the DOM
+               $available_widgets = array();
+               foreach ( self::get_available_widgets() as $available_widget ) {
+                       unset( $available_widget['control_tpl'] );
+                       $available_widgets[] = $available_widget;
+               }
+
+               $widget_reorder_nav_tpl = sprintf(
+                       '<div class="widget-reorder-nav"><span class="move-widget" tabindex="0" title="%1$s">%2$s</span><span class="move-widget-down" tabindex="0" title="%3$s">%4$s</span><span class="move-widget-up" tabindex="0" title="%5$s">%6$s</span></div>',
+                       esc_attr__( 'Move to another area...' ),
+                       esc_html__( 'Move to another area...' ),
+                       esc_attr__( 'Move down' ),
+                       esc_html__( 'Move down' ),
+                       esc_attr__( 'Move up' ),
+                       esc_html__( 'Move up' )
+               );
+
+               $move_widget_area_tpl = str_replace(
+                       array( '{description}', '{btn}' ),
+                       array(
+                               esc_html__( 'Select an area to move this widget into:' ),
+                               esc_html__( 'Move' ),
+                       ),
+                       '
+                               <div class="move-widget-area">
+                                       <p class="description">{description}</p>
+                                       <ul class="widget-area-select">
+                                               <% _.each( sidebars, function ( sidebar ){ %>
+                                                       <li class="" data-id="<%- sidebar.id %>" title="<%- sidebar.description %>" tabindex="0"><%- sidebar.name %></li>
+                                               <% }); %>
+                                       </ul>
+                                       <div class="move-widget-actions">
+                                               <button class="move-widget-btn button-secondary" type="button">{btn}</button>
+                                       </div>
+                               </div>
+                       '
+               );
+
+               // Why not wp_localize_script? Because we're not localizing, and it forces values into strings
+               global $wp_scripts;
+               $exports = array(
+                       'update_widget_ajax_action' => self::UPDATE_WIDGET_AJAX_ACTION,
+                       'update_widget_nonce_value' => wp_create_nonce( self::UPDATE_WIDGET_AJAX_ACTION ),
+                       'update_widget_nonce_post_key' => self::UPDATE_WIDGET_NONCE_POST_KEY,
+                       'registered_sidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ),
+                       'registered_widgets' => $GLOBALS['wp_registered_widgets'],
+                       'available_widgets' => $available_widgets, // @todo Merge this with registered_widgets
+                       'i18n' => array(
+                               'save_btn_label' => _x( 'Apply', 'button to save changes to a widget' ),
+                               'save_btn_tooltip' => _x( 'Save and preview changes before publishing them.', 'tooltip on the widget save button' ),
+                               'remove_btn_label' => _x( 'Remove', 'link to move a widget to the inactive widgets sidebar' ),
+                               'remove_btn_tooltip' => _x( 'Trash widget by moving it to the inactive widgets sidebar.', 'tooltip on btn a widget to move it to the inactive widgets sidebar' ),
+                       ),
+                       'tpl' => array(
+                               'widget_reorder_nav' => $widget_reorder_nav_tpl,
+                               'move_widget_area' => $move_widget_area_tpl,
+                       ),
+               );
+               foreach ( $exports['registered_widgets'] as &$registered_widget ) {
+                       unset( $registered_widget['callback'] ); // may not be JSON-serializeable
+               }
+
+               $wp_scripts->add_data(
+                       'widget-customizer',
+                       'data',
+                       sprintf( 'var WidgetCustomizer_exports = %s;', json_encode( $exports ) )
+               );
+       }
+
+       /**
+        * Render the widget form control templates into the DOM so that plugin scripts can manipulate them
+        *
+        * @action customize_controls_print_footer_scripts
+        */
+       static function output_widget_control_templates() {
+               ?>
+               <div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
+               <div id="available-widgets">
+                       <div id="available-widgets-filter">
+                               <input type="search" placeholder="<?php esc_attr_e( 'Find widgets&hellip;' ) ?>">
+                       </div>
+                       <?php foreach ( self::get_available_widgets() as $available_widget ): ?>
+                               <div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0">
+                                       <?php echo $available_widget['control_tpl']; // xss ok ?>
+                               </div>
+                       <?php endforeach; ?>
+               </div><!-- #available-widgets -->
+               </div><!-- #widgets-left -->
+               <?php
+       }
+
+       /**
+        * Get common arguments to supply when constructing a customizer setting
+        *
+        * @param string $id
+        * @param array  [$overrides]
+        * @return array
+        */
+       static function get_setting_args( $id, $overrides = array() ) {
+               $args = array(
+                       'type' => 'option',
+                       'capability' => 'edit_theme_options',
+                       'transport' => 'refresh',
+                       'default' => array(),
+               );
+               $args = array_merge( $args, $overrides );
+               $args = apply_filters( 'widget_customizer_setting_args', $args, $id );
+               return $args;
+       }
+
+       /**
+        * Make sure that a sidebars_widgets[x] only ever consists of actual widget IDs.
+        * Used as sanitize_callback for each sidebars_widgets setting.
+        *
+        * @param array $widget_ids
+        * @return array
+        */
+       static function sanitize_sidebar_widgets( $widget_ids ) {
+               global $wp_registered_widgets;
+               $widget_ids = array_map( 'strval', (array) $widget_ids );
+               $sanitized_widget_ids = array();
+               foreach ( $widget_ids as $widget_id ) {
+                       if ( array_key_exists( $widget_id, $wp_registered_widgets ) ) {
+                               $sanitized_widget_ids[] = $widget_id;
+                       }
+               }
+               return $sanitized_widget_ids;
+       }
+
+       /**
+        * Special filter for Settings Revisions plugin until it can handle
+        * dynamically creating settings.
+        *
+        * @param mixed $value
+        * @param stdClass|WP_Customize_Setting $setting
+        * @return mixed
+        */
+       static function temp_customize_sanitize_js( $value, $setting ) {
+               if ( preg_match( '/^widget_/', $setting->id ) && $setting->type === 'option' ) {
+                       $value = self::sanitize_widget_js_instance( $value );
+               }
+               return $value;
+       }
+
+       /**
+        * Build up an index of all available widgets for use in Backbone models
+        *
+        * @see wp_list_widgets()
+        * @return array
+        */
+       static function get_available_widgets() {
+               static $available_widgets = array();
+               if ( ! empty( $available_widgets ) ) {
+                       return $available_widgets;
+               }
+
+               global $wp_registered_widgets, $wp_registered_widget_controls;
+               require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number()
+
+               $sort = $wp_registered_widgets;
+               usort( $sort, array( __CLASS__, '_sort_name_callback' ) );
+               $done = array();
+
+               foreach ( $sort as $widget ) {
+                       if ( in_array( $widget['callback'], $done, true ) ) { // We already showed this multi-widget
+                               continue;
+                       }
+
+                       $sidebar = is_active_widget( $widget['callback'], $widget['id'], false, false );
+                       $done[]  = $widget['callback'];
+
+                       if ( ! isset( $widget['params'][0] ) ) {
+                               $widget['params'][0] = array();
+                       }
+
+                       $available_widget = $widget;
+                       unset( $available_widget['callback'] ); // not serializable to JSON
+
+                       $args = array(
+                               'widget_id' => $widget['id'],
+                               'widget_name' => $widget['name'],
+                               '_display' => 'template',
+                       );
+
+                       $is_disabled     = false;
+                       $is_multi_widget = (
+                               isset( $wp_registered_widget_controls[$widget['id']]['id_base'] )
+                               &&
+                               isset( $widget['params'][0]['number'] )
+                       );
+                       if ( $is_multi_widget ) {
+                               $id_base = $wp_registered_widget_controls[$widget['id']]['id_base'];
+                               $args['_temp_id']   = "$id_base-__i__";
+                               $args['_multi_num'] = next_widget_id_number( $id_base );
+                               $args['_add']       = 'multi';
+                       } else {
+                               $args['_add'] = 'single';
+                               if ( $sidebar && 'wp_inactive_widgets' !== $sidebar ) {
+                                       $is_disabled = true;
+                               }
+                               $id_base = $widget['id'];
+                       }
+
+                       $list_widget_controls_args = wp_list_widget_controls_dynamic_sidebar( array( 0 => $args, 1 => $widget['params'][0] ) );
+                       $control_tpl = self::get_widget_control( $list_widget_controls_args );
+
+                       // The properties here are mapped to the Backbone Widget model
+                       $available_widget = array_merge(
+                               $available_widget,
+                               array(
+                                       'temp_id' => isset( $args['_temp_id'] ) ? $args['_temp_id'] : null,
+                                       'is_multi' => $is_multi_widget,
+                                       'control_tpl' => $control_tpl,
+                                       'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
+                                       'is_disabled' => $is_disabled,
+                                       'id_base' => $id_base,
+                                       'transport' => 'refresh',
+                                       'width' => $wp_registered_widget_controls[$widget['id']]['width'],
+                                       'height' => $wp_registered_widget_controls[$widget['id']]['height'],
+                                       'is_wide' => self::is_wide_widget( $widget['id'] ),
+                               )
+                       );
+
+                       $available_widgets[] = $available_widget;
+               }
+               return $available_widgets;
+       }
+
+       /**
+        * Replace with inline closure once on PHP 5.3:
+        * sort( $array, function ( $a, $b ) { return strnatcasecmp( $a['name'], $b['name'] ); } );
+        *
+        * @access private
+        */
+       static function _sort_name_callback( $a, $b ) {
+               return strnatcasecmp( $a['name'], $b['name'] );
+       }
+
+       /**
+        * Invoke wp_widget_control() but capture the output buffer and transform the markup
+        * so that it can be used in the customizer.
+        *
+        * @see wp_widget_control()
+        * @param array $args
+        * @return string
+        */
+       static function get_widget_control( $args ) {
+               ob_start();
+               call_user_func_array( 'wp_widget_control', $args );
+               $replacements = array(
+                       '<form action="" method="post">' => '<div class="form">',
+                       '</form>' => '</div><!-- .form -->',
+               );
+               $control_tpl = ob_get_clean();
+               $control_tpl = str_replace( array_keys( $replacements ), array_values( $replacements ), $control_tpl );
+               return $control_tpl;
+       }
+
+       /**
+        * Add hooks for the customizer preview
+        *
+        * @action customize_preview_init
+        */
+       static function customize_preview_init() {
+               add_filter( 'sidebars_widgets', array( __CLASS__, 'preview_sidebars_widgets' ), 1 );
+               add_action( 'wp_enqueue_scripts', array( __CLASS__, 'customize_preview_enqueue_deps' ) );
+               add_action( 'wp_footer', array( __CLASS__, 'export_preview_data' ), 9999 );
+       }
+
+       /**
+        * When previewing, make sure the proper previewing widgets are used. Because wp_get_sidebars_widgets()
+        * gets called early at init (via wp_convert_widget_settings()) and can set global variable
+        * $_wp_sidebars_widgets to the value of get_option( 'sidebars_widgets' ) before the customizer
+        * preview filter is added, we have to reset it after the filter has been added.
+        *
+        * @filter sidebars_widgets
+        */
+       static function preview_sidebars_widgets( $sidebars_widgets ) {
+               $sidebars_widgets = get_option( 'sidebars_widgets' );
+               unset( $sidebars_widgets['array_version'] );
+               return $sidebars_widgets;
+       }
+
+       /**
+        * Enqueue scripts for the customizer preview
+        *
+        * @action wp_enqueue_scripts
+        */
+       static function customize_preview_enqueue_deps() {
+               wp_enqueue_script(
+                       'customize-preview-widgets',
+                       includes_url( 'js/customize-preview-widgets.js' ),
+                       array( 'jquery', 'wp-util', 'customize-preview' )
+               );
+
+               /*
+               wp_enqueue_style(
+                       'widget-customizer-preview',
+                       'widget-customizer-preview.css'
+               );
+               */
+
+               // Why not wp_localize_script? Because we're not localizing, and it forces values into strings
+               global $wp_scripts;
+               $exports = array(
+                       'registered_sidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ),
+                       'registered_widgets' => $GLOBALS['wp_registered_widgets'],
+                       'i18n' => array(
+                               'widget_tooltip' => __( 'Press shift and then click to edit widget in customizer...' ),
+                       ),
+                       'request_uri' => wp_unslash( $_SERVER['REQUEST_URI'] ),
+               );
+               foreach ( $exports['registered_widgets'] as &$registered_widget ) {
+                       unset( $registered_widget['callback'] ); // may not be JSON-serializeable
+               }
+               $wp_scripts->add_data(
+                       'customize-preview-widgets',
+                       'data',
+                       sprintf( 'var WidgetCustomizerPreview_exports = %s;', json_encode( $exports ) )
+               );
+       }
+
+       /**
+        * At the very end of the page, at the very end of the wp_footer, communicate the sidebars that appeared on the page
+        *
+        * @action wp_footer
+        */
+       static function export_preview_data() {
+               wp_print_scripts( array( 'customize-preview-widgets' ) );
+               ?>
+               <script>
+               (function () {
+                       /*global WidgetCustomizerPreview */
+                       WidgetCustomizerPreview.rendered_sidebars = <?php echo json_encode( array_fill_keys( array_unique( self::$rendered_sidebars ), true ) ) ?>;
+                       WidgetCustomizerPreview.rendered_widgets = <?php echo json_encode( array_fill_keys( array_keys( self::$rendered_widgets ), true ) ); ?>;
+               }());
+               </script>
+               <?php
+       }
+
+       static protected $rendered_sidebars = array();
+       static protected $rendered_widgets  = array();
+
+       /**
+        * Keep track of the widgets that were rendered
+        *
+        * @action dynamic_sidebar
+        */
+       static function tally_rendered_widgets( $widget ) {
+               self::$rendered_widgets[$widget['id']] = true;
+       }
+
+       /**
+        * This is hacky. It is too bad that dynamic_sidebar is not just called once with the $sidebar_id supplied
+        * This does not get called for a sidebar which lacks widgets.
+        * See core patch which addresses the problem.
+        *
+        * @link http://core.trac.wordpress.org/ticket/25368
+        * @action dynamic_sidebar
+        */
+       static function tally_sidebars_via_dynamic_sidebar_actions( $widget ) {
+               global $sidebars_widgets;
+               foreach ( $sidebars_widgets as $sidebar_id => $widget_ids ) {
+                       if ( in_array( $sidebar_id, self::$rendered_sidebars ) ) {
+                               continue;
+                       }
+                       if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) && is_array( $widget_ids ) && in_array( $widget['id'], $widget_ids ) ) {
+                               self::$rendered_sidebars[] = $sidebar_id;
+                       }
+               }
+       }
+
+       /**
+        * Keep track of the times that is_active_sidebar() is called in the template, and assume that this
+        * means that the sidebar would be rendered on the template if there were widgets populating it.
+        *
+        * @see http://core.trac.wordpress.org/ticket/25368
+        * @filter temp_is_active_sidebar
+        */
+       static function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
+               if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
+                       self::$rendered_sidebars[] = $sidebar_id;
+               }
+               // We may need to force this to true, and also force-true the value for temp_dynamic_sidebar_has_widgets
+               // if we want to ensure that there is an area to drop widgets into, if the sidebar is empty.
+               return $is_active;
+       }
+
+       /**
+        * Keep track of the times that dynamic_sidebar() is called in the template, and assume that this
+        * means that the sidebar would be rendered on the template if there were widgets populating it.
+        *
+        * @see http://core.trac.wordpress.org/ticket/25368
+        * @filter temp_dynamic_sidebar_has_widgets
+        */
+       static function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
+               if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
+                       self::$rendered_sidebars[] = $sidebar_id;
+               }
+               // We may need to force this to true, and also force-true the value for temp_is_active_sidebar
+               // if we want to ensure that there is an area to drop widgets into, if the sidebar is empty.
+               return $has_widgets;
+       }
+
+       /**
+        * Serialize an instance and hash it with the AUTH_KEY; when a JS value is
+        * posted back to save, this instance hash key is used to ensure that the
+        * serialized_instance was not tampered with, but that it had originated
+        * from WordPress and so is sanitized.
+        *
+        * @param array $instance
+        * @return string
+        */
+       protected static function get_instance_hash_key( $instance ) {
+               $hash = md5( AUTH_KEY . serialize( $instance ) );
+               return $hash;
+       }
+
+       /**
+        * Unserialize the JS-instance for storing in the options. It's important
+        * that this filter only get applied to an instance once.
+        *
+        * @see Widget_Customizer::sanitize_widget_js_instance()
+        *
+        * @param array $value
+        * @return array
+        */
+       static function sanitize_widget_instance( $value ) {
+               if ( $value === array() ) {
+                       return $value;
+               }
+               $invalid = (
+                       empty( $value['is_widget_customizer_js_value'] )
+                       ||
+                       empty( $value['instance_hash_key'] )
+                       ||
+                       empty( $value['encoded_serialized_instance'] )
+               );
+               if ( $invalid ) {
+                       return null;
+               }
+               $decoded = base64_decode( $value['encoded_serialized_instance'], true );
+               if ( false === $decoded ) {
+                       return null;
+               }
+               $instance = unserialize( $decoded );
+               if ( false === $instance ) {
+                       return null;
+               }
+               if ( self::get_instance_hash_key( $instance ) !== $value['instance_hash_key'] ) {
+                       return null;
+               }
+               return $instance;
+       }
+
+       /**
+        * Convert widget instance into JSON-representable format
+        *
+        * @see Widget_Customizer::sanitize_widget_instance()
+        *
+        * @param array $value
+        * @return array
+        */
+       static function sanitize_widget_js_instance( $value ) {
+               if ( empty( $value['is_widget_customizer_js_value'] ) ) {
+                       $serialized = serialize( $value );
+                       $value = array(
+                               'encoded_serialized_instance' => base64_encode( $serialized ),
+                               'title' => empty( $value['title'] ) ? '' : $value['title'],
+                               'is_widget_customizer_js_value' => true,
+                               'instance_hash_key' => self::get_instance_hash_key( $value ),
+                       );
+               }
+               return $value;
+       }
+
+       /**
+        * Strip out widget IDs for widgets which are no longer registered, such
+        * as the case when a plugin orphans a widget in a sidebar when it is deactivated.
+        *
+        * @param array $widget_ids
+        * @return array
+        */
+       static function sanitize_sidebar_widgets_js_instance( $widget_ids ) {
+               global $wp_registered_widgets;
+               $widget_ids = array_values( array_intersect( $widget_ids, array_keys( $wp_registered_widgets ) ) );
+               return $widget_ids;
+       }
+
+       /**
+        * Find and invoke the widget update and control callbacks. Requires that
+        * $_POST be populated with the instance data.
+        *
+        * @throws Widget_Customizer_Exception
+        * @throws Exception
+        *
+        * @param string $widget_id
+        * @return array
+        */
+       static function call_widget_update( $widget_id ) {
+               global $wp_registered_widget_updates, $wp_registered_widget_controls;
+
+               $options_transaction = new Options_Transaction();
+
+               try {
+                       $options_transaction->start();
+                       $parsed_id   = self::parse_widget_id( $widget_id );
+                       $option_name = 'widget_' . $parsed_id['id_base'];
+
+                       /**
+                        * If a previously-sanitized instance is provided, populate the input vars
+                        * with its values so that the widget update callback will read this instance
+                        */
+                       $added_input_vars = array();
+                       if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
+                               $sanitized_widget_setting = json_decode( self::get_post_value( 'sanitized_widget_setting' ), true );
+                               if ( empty( $sanitized_widget_setting ) ) {
+                                       throw new Widget_Customizer_Exception( 'Malformed sanitized_widget_setting' );
+                               }
+                               $instance = self::sanitize_widget_instance( $sanitized_widget_setting );
+                               if ( is_null( $instance ) ) {
+                                       throw new Widget_Customizer_Exception( 'Unsanitary sanitized_widget_setting' );
+                               }
+                               if ( ! is_null( $parsed_id['number'] ) ) {
+                                       $value = array();
+                                       $value[$parsed_id['number']] = $instance;
+                                       $key = 'widget-' . $parsed_id['id_base'];
+                                       $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
+                                       $added_input_vars[] = $key;
+                               } else {
+                                       foreach ( $instance as $key => $value ) {
+                                               $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
+                                               $added_input_vars[] = $key;
+                                       }
+                               }
+                       }
+
+                       /**
+                        * Invoke the widget update callback
+                        */
+                       foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
+                               if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) {
+                                       ob_start();
+                                       call_user_func_array( $control['callback'], $control['params'] );
+                                       ob_end_clean();
+                                       break;
+                               }
+                       }
+
+                       // Clean up any input vars that were manually added
+                       foreach ( $added_input_vars as $key ) {
+                               unset( $_POST[$key] );
+                               unset( $_REQUEST[$key] );
+                       }
+
+                       /**
+                        * Make sure the expected option was updated
+                        */
+                       if ( 0 !== $options_transaction->count() ) {
+                               if ( count( $options_transaction->options ) > 1 ) {
+                                       throw new Widget_Customizer_Exception( sprintf( 'Widget %1$s unexpectedly updated more than one option.', $widget_id ) );
+                               }
+                               $updated_option_name = key( $options_transaction->options );
+                               if ( $updated_option_name !== $option_name ) {
+                                       throw new Widget_Customizer_Exception( sprintf( 'Widget %1$s updated option "%2$s", but expected "%3$s".', $widget_id, $updated_option_name, $option_name ) );
+                               }
+                       }
+
+                       /**
+                        * Obtain the widget control with the updated instance in place
+                        */
+                       ob_start();
+                       $form = $wp_registered_widget_controls[$widget_id];
+                       if ( $form ) {
+                               call_user_func_array( $form['callback'], $form['params'] );
+                       }
+                       $form = ob_get_clean();
+
+                       /**
+                        * Obtain the widget instance
+                        */
+                       $option = get_option( $option_name );
+                       if ( null !== $parsed_id['number'] ) {
+                               $instance = $option[$parsed_id['number']];
+                       } else {
+                               $instance = $option;
+                       }
+
+                       $options_transaction->rollback();
+                       return compact( 'instance', 'form' );
+               }
+               catch ( Exception $e ) {
+                       $options_transaction->rollback();
+                       throw $e;
+               }
+       }
+
+       /**
+        * Allow customizer to update a widget using its form, but return the new
+        * instance info via Ajax instead of saving it to the options table.
+        * Most code here copied from wp_ajax_save_widget()
+        *
+        * @see wp_ajax_save_widget
+        * @todo Reuse wp_ajax_save_widget now that we have option transactions?
+        * @action wp_ajax_update_widget
+        */
+       static function wp_ajax_update_widget() {
+               $generic_error = __( 'An error has occurred. Please reload the page and try again.' );
+
+               try {
+                       if ( ! check_ajax_referer( self::UPDATE_WIDGET_AJAX_ACTION, self::UPDATE_WIDGET_NONCE_POST_KEY, false ) ) {
+                               throw new Widget_Customizer_Exception( __( 'Nonce check failed. Reload and try again?' ) );
+                       }
+                       if ( ! current_user_can( 'edit_theme_options' ) ) {
+                               throw new Widget_Customizer_Exception( __( 'Current user cannot!' ) );
+                       }
+                       if ( ! isset( $_POST['widget-id'] ) ) {
+                               throw new Widget_Customizer_Exception( __( 'Incomplete request' ) );
+                       }
+
+                       unset( $_POST[self::UPDATE_WIDGET_NONCE_POST_KEY], $_POST['action'] );
+
+                       do_action( 'load-widgets.php' );
+                       do_action( 'widgets.php' );
+                       do_action( 'sidebar_admin_setup' );
+
+                       $widget_id = self::get_post_value( 'widget-id' );
+                       $parsed_id = self::parse_widget_id( $widget_id );
+                       $id_base   = $parsed_id['id_base'];
+
+                       if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) {
+                               throw new Widget_Customizer_Exception( 'Cannot pass widget templates to create new instances; apply template vars in JS' );
+                       }
+
+                       $updated_widget = self::call_widget_update( $widget_id ); // => {instance,form}
+                       $form = $updated_widget['form'];
+                       $instance = self::sanitize_widget_js_instance( $updated_widget['instance'] );
+
+                       wp_send_json_success( compact( 'form', 'instance' ) );
+               }
+               catch( Exception $e ) {
+                       if ( $e instanceof Widget_Customizer_Exception ) {
+                               $message = $e->getMessage();
+                       } else {
+                               error_log( sprintf( '%s in %s: %s', get_class( $e ), __FUNCTION__, $e->getMessage() ) );
+                               $message = $generic_error;
+                       }
+                       wp_send_json_error( compact( 'message' ) );
+               }
+       }
+}
+
+class Widget_Customizer_Exception extends Exception {}
+
+class Options_Transaction {
+
+       /**
+        * @var array $options values updated while transaction is open
+        */
+       public $options = array();
+
+       protected $_ignore_transients = true;
+       protected $_is_current = false;
+       protected $_operations = array();
+
+       function __construct( $ignore_transients = true ) {
+               $this->_ignore_transients = $ignore_transients;
+       }
+
+       /**
+        * Determine whether or not the transaction is open
+        * @return bool
+        */
+       function is_current() {
+               return $this->_is_current;
+       }
+
+       /**
+        * @param $option_name
+        * @return boolean
+        */
+       function is_option_ignored( $option_name ) {
+               return ( $this->_ignore_transients && 0 === strpos( $option_name, '_transient_' ) );
+       }
+
+       /**
+        * Get the number of operations performed in the transaction
+        * @return bool
+        */
+       function count() {
+               return count( $this->_operations );
+       }
+
+       /**
+        * Start keeping track of changes to options, and cache their new values
+        */
+       function start() {
+               $this->_is_current = true;
+               add_action( 'added_option', array( $this, '_capture_added_option' ), 10, 2 );
+               add_action( 'updated_option', array( $this, '_capture_updated_option' ), 10, 3 );
+               add_action( 'delete_option', array( $this, '_capture_pre_deleted_option' ), 10, 1 );
+               add_action( 'deleted_option', array( $this, '_capture_deleted_option' ), 10, 1 );
+       }
+
+       /**
+        * @action added_option
+        * @param $option_name
+        * @param $new_value
+        */
+       function _capture_added_option( $option_name, $new_value ) {
+               if ( $this->is_option_ignored( $option_name ) ) {
+                       return;
+               }
+               $this->options[$option_name] = $new_value;
+               $operation = 'add';
+               $this->_operations[] = compact( 'operation', 'option_name', 'new_value' );
+       }
+
+       /**
+        * @action updated_option
+        * @param string $option_name
+        * @param mixed $old_value
+        * @param mixed $new_value
+        */
+       function _capture_updated_option( $option_name, $old_value, $new_value ) {
+               if ( $this->is_option_ignored( $option_name ) ) {
+                       return;
+               }
+               $this->options[$option_name] = $new_value;
+               $operation = 'update';
+               $this->_operations[] = compact( 'operation', 'option_name', 'old_value', 'new_value' );
+       }
+
+       protected $_pending_delete_option_autoload;
+       protected $_pending_delete_option_value;
+
+       /**
+        * It's too bad the old_value and autoload aren't passed into the deleted_option action
+        * @action delete_option
+        * @param string $option_name
+        */
+       function _capture_pre_deleted_option( $option_name ) {
+               if ( $this->is_option_ignored( $option_name ) ) {
+                       return;
+               }
+               global $wpdb;
+               $autoload = $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option_name ) ); // db call ok; no-cache ok
+               $this->_pending_delete_option_autoload = $autoload;
+               $this->_pending_delete_option_value    = get_option( $option_name );
+       }
+
+       /**
+        * @action deleted_option
+        * @param string $option_name
+        */
+       function _capture_deleted_option( $option_name ) {
+               if ( $this->is_option_ignored( $option_name ) ) {
+                       return;
+               }
+               unset( $this->options[$option_name] );
+               $operation = 'delete';
+               $old_value = $this->_pending_delete_option_value;
+               $autoload  = $this->_pending_delete_option_autoload;
+               $this->_operations[] = compact( 'operation', 'option_name', 'old_value', 'autoload' );
+       }
+
+       /**
+        * Undo any changes to the options since start() was called
+        */
+       function rollback() {
+               remove_action( 'updated_option', array( $this, '_capture_updated_option' ), 10, 3 );
+               remove_action( 'added_option', array( $this, '_capture_added_option' ), 10, 2 );
+               remove_action( 'delete_option', array( $this, '_capture_pre_deleted_option' ), 10, 1 );
+               remove_action( 'deleted_option', array( $this, '_capture_deleted_option' ), 10, 1 );
+               while ( 0 !== count( $this->_operations ) ) {
+                       $option_operation = array_pop( $this->_operations );
+                       if ( 'add' === $option_operation['operation'] ) {
+                               delete_option( $option_operation['option_name'] );
+                       }
+                       else if ( 'delete' === $option_operation['operation'] ) {
+                               add_option( $option_operation['option_name'], $option_operation['old_value'], null, $option_operation['autoload'] );
+                       }
+                       else if ( 'update' === $option_operation['operation'] ) {
+                               update_option( $option_operation['option_name'], $option_operation['old_value'] );
+                       }
+                       else {
+                               throw new Exception( 'Unexpected operation' );
+                       }
+               }
+               $this->_is_current = false;
+       }
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesjscustomizepreviewwidgetsjs"></a>
<div class="addfile"><h4>Added: trunk/src/wp-includes/js/customize-preview-widgets.js (0 => 27419)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/customize-preview-widgets.js                            (rev 0)
+++ trunk/src/wp-includes/js/customize-preview-widgets.js       2014-03-05 20:40:36 UTC (rev 27419)
</span><span class="lines">@@ -0,0 +1,115 @@
</span><ins>+/*global jQuery, WidgetCustomizerPreview_exports */
+/*exported WidgetCustomizerPreview */
+var WidgetCustomizerPreview = (function ($) {
+       'use strict';
+
+       var self = {
+               rendered_sidebars: {}, // @todo Make rendered a property of the Backbone model
+               rendered_widgets: {}, // @todo Make rendered a property of the Backbone model
+               registered_sidebars: [], // @todo Make a Backbone collection
+               registered_widgets: {}, // @todo Make array, Backbone collection
+               widget_selectors: [],
+               render_widget_ajax_action: null,
+               render_widget_nonce_value: null,
+               render_widget_nonce_post_key: null,
+               preview: null,
+               i18n: {},
+
+               init: function () {
+                       this.buildWidgetSelectors();
+                       this.highlightControls();
+
+                       self.preview.bind( 'active', function() {
+                               self.preview.send( 'rendered-sidebars', self.rendered_sidebars ); // @todo Only send array of IDs
+                               self.preview.send( 'rendered-widgets', self.rendered_widgets ); // @todo Only send array of IDs
+                       } );
+               },
+
+               /**
+                * Calculate the selector for the sidebar's widgets based on the registered sidebar's info
+                */
+               buildWidgetSelectors: function () {
+                       $.each( self.registered_sidebars, function ( i, sidebar ) {
+                               var widget_tpl = [
+                                       sidebar.before_widget.replace('%1$s', '').replace('%2$s', ''),
+                                       sidebar.before_title,
+                                       sidebar.after_title,
+                                       sidebar.after_widget
+                               ].join('');
+                               var empty_widget = $(widget_tpl);
+                               var widget_selector = empty_widget.prop('tagName');
+                               var widget_classes = empty_widget.prop('className').replace(/^\s+|\s+$/g, '');
+                               if ( widget_classes ) {
+                                       widget_selector += '.' + widget_classes.split(/\s+/).join('.');
+                               }
+                               self.widget_selectors.push(widget_selector);
+                       });
+               },
+
+               /**
+                * Obtain a widget instance if it was added to the provided sidebar
+                * This addresses a race condition where a widget is moved between sidebars
+                * We cannot use ID selector because jQuery will only return the first one
+                * that matches. We have to resort to an [id] attribute selector
+                *
+                * @param {String} sidebar_id
+                * @param {String} widget_id
+                * @return {jQuery}
+                */
+               getSidebarWidgetElement: function ( sidebar_id, widget_id ) {
+                       return $( '[id=' + widget_id + ']' ).filter( function () {
+                               return $( this ).data( 'widget_customizer_sidebar_id' ) === sidebar_id;
+                       } );
+               },
+
+               /**
+                *
+                */
+               highlightControls: function() {
+
+                       var selector = this.widget_selectors.join(',');
+
+                       $(selector).attr( 'title', self.i18n.widget_tooltip );
+
+                       $(document).on( 'mouseenter', selector, function () {
+                               var control = parent.WidgetCustomizer.getWidgetFormControlForWidget( $(this).prop('id') );
+                               if ( control ) {
+                                       control.highlightSectionAndControl();
+                               }
+                       });
+
+                       // Open expand the widget control when shift+clicking the widget element
+                       $(document).on( 'click', selector, function ( e ) {
+                               if ( ! e.shiftKey ) {
+                                       return;
+                               }
+                               e.preventDefault();
+                               var control = parent.WidgetCustomizer.getWidgetFormControlForWidget( $(this).prop('id') );
+                               if ( control ) {
+                                       control.focus();
+                               }
+                       });
+               }
+
+       };
+
+       $.extend(self, WidgetCustomizerPreview_exports);
+
+       /**
+        * Capture the instance of the Preview since it is private
+        */
+       var OldPreview = wp.customize.Preview;
+       wp.customize.Preview = OldPreview.extend( {
+               initialize: function( params, options ) {
+                       self.preview = this;
+                       OldPreview.prototype.initialize.call( this, params, options );
+               }
+       } );
+
+       // @todo on customize ready?
+       $(function () {
+               self.init();
+       });
+
+       return self;
+}( jQuery ));
</ins></span></pre>
</div>
</div>

</body>
</html>