@phosphor/widgets
Version:
PhosphorJS - Widgets
778 lines (777 loc) • 25.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
var algorithm_1 = require("@phosphor/algorithm");
var domutils_1 = require("@phosphor/domutils");
var messaging_1 = require("@phosphor/messaging");
var properties_1 = require("@phosphor/properties");
var signaling_1 = require("@phosphor/signaling");
var widget_1 = require("./widget");
/**
* An abstract base class for creating Phosphor layouts.
*
* #### Notes
* A layout is used to add widgets to a parent and to arrange those
* widgets within the parent's DOM node.
*
* This class implements the base functionality which is required of
* nearly all layouts. It must be subclassed in order to be useful.
*
* Notably, this class does not define a uniform interface for adding
* widgets to the layout. A subclass should define that API in a way
* which is meaningful for its intended use.
*/
var Layout = /** @class */ (function () {
/**
* Construct a new layout.
*
* @param options - The options for initializing the layout.
*/
function Layout(options) {
if (options === void 0) { options = {}; }
this._disposed = false;
this._parent = null;
this._fitPolicy = options.fitPolicy || 'set-min-size';
}
/**
* Dispose of the resources held by the layout.
*
* #### Notes
* This should be reimplemented to clear and dispose of the widgets.
*
* All reimplementations should call the superclass method.
*
* This method is called automatically when the parent is disposed.
*/
Layout.prototype.dispose = function () {
this._parent = null;
this._disposed = true;
signaling_1.Signal.clearData(this);
properties_1.AttachedProperty.clearData(this);
};
Object.defineProperty(Layout.prototype, "isDisposed", {
/**
* Test whether the layout is disposed.
*/
get: function () {
return this._disposed;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Layout.prototype, "parent", {
/**
* Get the parent widget of the layout.
*/
get: function () {
return this._parent;
},
/**
* Set the parent widget of the layout.
*
* #### Notes
* This is set automatically when installing the layout on the parent
* widget. The parent widget should not be set directly by user code.
*/
set: function (value) {
if (this._parent === value) {
return;
}
if (this._parent) {
throw new Error('Cannot change parent widget.');
}
if (value.layout !== this) {
throw new Error('Invalid parent widget.');
}
this._parent = value;
this.init();
},
enumerable: true,
configurable: true
});
Object.defineProperty(Layout.prototype, "fitPolicy", {
/**
* Get the fit policy for the layout.
*
* #### Notes
* The fit policy controls the computed size constraints which are
* applied to the parent widget by the layout.
*
* Some layout implementations may ignore the fit policy.
*/
get: function () {
return this._fitPolicy;
},
/**
* Set the fit policy for the layout.
*
* #### Notes
* The fit policy controls the computed size constraints which are
* applied to the parent widget by the layout.
*
* Some layout implementations may ignore the fit policy.
*
* Changing the fit policy will clear the current size constraint
* for the parent widget and then re-fit the parent.
*/
set: function (value) {
// Bail if the policy does not change
if (this._fitPolicy === value) {
return;
}
// Update the internal policy.
this._fitPolicy = value;
// Clear the size constraints and schedule a fit of the parent.
if (this._parent) {
var style = this._parent.node.style;
style.minWidth = '';
style.minHeight = '';
style.maxWidth = '';
style.maxHeight = '';
this._parent.fit();
}
},
enumerable: true,
configurable: true
});
/**
* Process a message sent to the parent widget.
*
* @param msg - The message sent to the parent widget.
*
* #### Notes
* This method is called by the parent widget to process a message.
*
* Subclasses may reimplement this method as needed.
*/
Layout.prototype.processParentMessage = function (msg) {
switch (msg.type) {
case 'resize':
this.onResize(msg);
break;
case 'update-request':
this.onUpdateRequest(msg);
break;
case 'fit-request':
this.onFitRequest(msg);
break;
case 'before-show':
this.onBeforeShow(msg);
break;
case 'after-show':
this.onAfterShow(msg);
break;
case 'before-hide':
this.onBeforeHide(msg);
break;
case 'after-hide':
this.onAfterHide(msg);
break;
case 'before-attach':
this.onBeforeAttach(msg);
break;
case 'after-attach':
this.onAfterAttach(msg);
break;
case 'before-detach':
this.onBeforeDetach(msg);
break;
case 'after-detach':
this.onAfterDetach(msg);
break;
case 'child-removed':
this.onChildRemoved(msg);
break;
case 'child-shown':
this.onChildShown(msg);
break;
case 'child-hidden':
this.onChildHidden(msg);
break;
}
};
/**
* Perform layout initialization which requires the parent widget.
*
* #### Notes
* This method is invoked immediately after the layout is installed
* on the parent widget.
*
* The default implementation reparents all of the widgets to the
* layout parent widget.
*
* Subclasses should reimplement this method and attach the child
* widget nodes to the parent widget's node.
*/
Layout.prototype.init = function () {
var _this = this;
algorithm_1.each(this, function (widget) {
widget.parent = _this.parent;
});
};
/**
* A message handler invoked on a `'resize'` message.
*
* #### Notes
* The layout should ensure that its widgets are resized according
* to the specified layout space, and that they are sent a `'resize'`
* message if appropriate.
*
* The default implementation of this method sends an `UnknownSize`
* resize message to all widgets.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onResize = function (msg) {
algorithm_1.each(this, function (widget) {
messaging_1.MessageLoop.sendMessage(widget, widget_1.Widget.ResizeMessage.UnknownSize);
});
};
/**
* A message handler invoked on an `'update-request'` message.
*
* #### Notes
* The layout should ensure that its widgets are resized according
* to the available layout space, and that they are sent a `'resize'`
* message if appropriate.
*
* The default implementation of this method sends an `UnknownSize`
* resize message to all widgets.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onUpdateRequest = function (msg) {
algorithm_1.each(this, function (widget) {
messaging_1.MessageLoop.sendMessage(widget, widget_1.Widget.ResizeMessage.UnknownSize);
});
};
/**
* A message handler invoked on a `'before-attach'` message.
*
* #### Notes
* The default implementation of this method forwards the message
* to all widgets. It assumes all widget nodes are attached to the
* parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onBeforeAttach = function (msg) {
algorithm_1.each(this, function (widget) {
messaging_1.MessageLoop.sendMessage(widget, msg);
});
};
/**
* A message handler invoked on an `'after-attach'` message.
*
* #### Notes
* The default implementation of this method forwards the message
* to all widgets. It assumes all widget nodes are attached to the
* parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onAfterAttach = function (msg) {
algorithm_1.each(this, function (widget) {
messaging_1.MessageLoop.sendMessage(widget, msg);
});
};
/**
* A message handler invoked on a `'before-detach'` message.
*
* #### Notes
* The default implementation of this method forwards the message
* to all widgets. It assumes all widget nodes are attached to the
* parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onBeforeDetach = function (msg) {
algorithm_1.each(this, function (widget) {
messaging_1.MessageLoop.sendMessage(widget, msg);
});
};
/**
* A message handler invoked on an `'after-detach'` message.
*
* #### Notes
* The default implementation of this method forwards the message
* to all widgets. It assumes all widget nodes are attached to the
* parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onAfterDetach = function (msg) {
algorithm_1.each(this, function (widget) {
messaging_1.MessageLoop.sendMessage(widget, msg);
});
};
/**
* A message handler invoked on a `'before-show'` message.
*
* #### Notes
* The default implementation of this method forwards the message to
* all non-hidden widgets. It assumes all widget nodes are attached
* to the parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onBeforeShow = function (msg) {
algorithm_1.each(this, function (widget) {
if (!widget.isHidden) {
messaging_1.MessageLoop.sendMessage(widget, msg);
}
});
};
/**
* A message handler invoked on an `'after-show'` message.
*
* #### Notes
* The default implementation of this method forwards the message to
* all non-hidden widgets. It assumes all widget nodes are attached
* to the parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onAfterShow = function (msg) {
algorithm_1.each(this, function (widget) {
if (!widget.isHidden) {
messaging_1.MessageLoop.sendMessage(widget, msg);
}
});
};
/**
* A message handler invoked on a `'before-hide'` message.
*
* #### Notes
* The default implementation of this method forwards the message to
* all non-hidden widgets. It assumes all widget nodes are attached
* to the parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onBeforeHide = function (msg) {
algorithm_1.each(this, function (widget) {
if (!widget.isHidden) {
messaging_1.MessageLoop.sendMessage(widget, msg);
}
});
};
/**
* A message handler invoked on an `'after-hide'` message.
*
* #### Notes
* The default implementation of this method forwards the message to
* all non-hidden widgets. It assumes all widget nodes are attached
* to the parent widget node.
*
* This may be reimplemented by subclasses as needed.
*/
Layout.prototype.onAfterHide = function (msg) {
algorithm_1.each(this, function (widget) {
if (!widget.isHidden) {
messaging_1.MessageLoop.sendMessage(widget, msg);
}
});
};
/**
* A message handler invoked on a `'child-removed'` message.
*
* #### Notes
* This will remove the child widget from the layout.
*
* Subclasses should **not** typically reimplement this method.
*/
Layout.prototype.onChildRemoved = function (msg) {
this.removeWidget(msg.child);
};
/**
* A message handler invoked on a `'fit-request'` message.
*
* #### Notes
* The default implementation of this handler is a no-op.
*/
Layout.prototype.onFitRequest = function (msg) { };
/**
* A message handler invoked on a `'child-shown'` message.
*
* #### Notes
* The default implementation of this handler is a no-op.
*/
Layout.prototype.onChildShown = function (msg) { };
/**
* A message handler invoked on a `'child-hidden'` message.
*
* #### Notes
* The default implementation of this handler is a no-op.
*/
Layout.prototype.onChildHidden = function (msg) { };
return Layout;
}());
exports.Layout = Layout;
/**
* The namespace for the `Layout` class statics.
*/
(function (Layout) {
/**
* Get the horizontal alignment for a widget.
*
* @param widget - The widget of interest.
*
* @returns The horizontal alignment for the widget.
*
* #### Notes
* If the layout width allocated to a widget is larger than its max
* width, the horizontal alignment controls how the widget is placed
* within the extra horizontal space.
*
* If the allocated width is less than the widget's max width, the
* horizontal alignment has no effect.
*
* Some layout implementations may ignore horizontal alignment.
*/
function getHorizontalAlignment(widget) {
return Private.horizontalAlignmentProperty.get(widget);
}
Layout.getHorizontalAlignment = getHorizontalAlignment;
/**
* Set the horizontal alignment for a widget.
*
* @param widget - The widget of interest.
*
* @param value - The value for the horizontal alignment.
*
* #### Notes
* If the layout width allocated to a widget is larger than its max
* width, the horizontal alignment controls how the widget is placed
* within the extra horizontal space.
*
* If the allocated width is less than the widget's max width, the
* horizontal alignment has no effect.
*
* Some layout implementations may ignore horizontal alignment.
*
* Changing the horizontal alignment will post an `update-request`
* message to widget's parent, provided the parent has a layout
* installed.
*/
function setHorizontalAlignment(widget, value) {
Private.horizontalAlignmentProperty.set(widget, value);
}
Layout.setHorizontalAlignment = setHorizontalAlignment;
/**
* Get the vertical alignment for a widget.
*
* @param widget - The widget of interest.
*
* @returns The vertical alignment for the widget.
*
* #### Notes
* If the layout height allocated to a widget is larger than its max
* height, the vertical alignment controls how the widget is placed
* within the extra vertical space.
*
* If the allocated height is less than the widget's max height, the
* vertical alignment has no effect.
*
* Some layout implementations may ignore vertical alignment.
*/
function getVerticalAlignment(widget) {
return Private.verticalAlignmentProperty.get(widget);
}
Layout.getVerticalAlignment = getVerticalAlignment;
/**
* Set the vertical alignment for a widget.
*
* @param widget - The widget of interest.
*
* @param value - The value for the vertical alignment.
*
* #### Notes
* If the layout height allocated to a widget is larger than its max
* height, the vertical alignment controls how the widget is placed
* within the extra vertical space.
*
* If the allocated height is less than the widget's max height, the
* vertical alignment has no effect.
*
* Some layout implementations may ignore vertical alignment.
*
* Changing the horizontal alignment will post an `update-request`
* message to widget's parent, provided the parent has a layout
* installed.
*/
function setVerticalAlignment(widget, value) {
Private.verticalAlignmentProperty.set(widget, value);
}
Layout.setVerticalAlignment = setVerticalAlignment;
})(Layout = exports.Layout || (exports.Layout = {}));
exports.Layout = Layout;
/**
* An object which assists in the absolute layout of widgets.
*
* #### Notes
* This class is useful when implementing a layout which arranges its
* widgets using absolute positioning.
*
* This class is used by nearly all of the built-in Phosphor layouts.
*/
var LayoutItem = /** @class */ (function () {
/**
* Construct a new layout item.
*
* @param widget - The widget to be managed by the item.
*
* #### Notes
* The widget will be set to absolute positioning.
*/
function LayoutItem(widget) {
this._top = NaN;
this._left = NaN;
this._width = NaN;
this._height = NaN;
this._minWidth = 0;
this._minHeight = 0;
this._maxWidth = Infinity;
this._maxHeight = Infinity;
this._disposed = false;
this.widget = widget;
this.widget.node.style.position = 'absolute';
}
/**
* Dispose of the the layout item.
*
* #### Notes
* This will reset the positioning of the widget.
*/
LayoutItem.prototype.dispose = function () {
// Do nothing if the item is already disposed.
if (this._disposed) {
return;
}
// Mark the item as disposed.
this._disposed = true;
// Reset the widget style.
var style = this.widget.node.style;
style.position = '';
style.top = '';
style.left = '';
style.width = '';
style.height = '';
};
Object.defineProperty(LayoutItem.prototype, "minWidth", {
/**
* The computed minimum width of the widget.
*
* #### Notes
* This value can be updated by calling the `fit` method.
*/
get: function () {
return this._minWidth;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "minHeight", {
/**
* The computed minimum height of the widget.
*
* #### Notes
* This value can be updated by calling the `fit` method.
*/
get: function () {
return this._minHeight;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "maxWidth", {
/**
* The computed maximum width of the widget.
*
* #### Notes
* This value can be updated by calling the `fit` method.
*/
get: function () {
return this._maxWidth;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "maxHeight", {
/**
* The computed maximum height of the widget.
*
* #### Notes
* This value can be updated by calling the `fit` method.
*/
get: function () {
return this._maxHeight;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "isDisposed", {
/**
* Whether the layout item is disposed.
*/
get: function () {
return this._disposed;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "isHidden", {
/**
* Whether the managed widget is hidden.
*/
get: function () {
return this.widget.isHidden;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "isVisible", {
/**
* Whether the managed widget is visible.
*/
get: function () {
return this.widget.isVisible;
},
enumerable: true,
configurable: true
});
Object.defineProperty(LayoutItem.prototype, "isAttached", {
/**
* Whether the managed widget is attached.
*/
get: function () {
return this.widget.isAttached;
},
enumerable: true,
configurable: true
});
/**
* Update the computed size limits of the managed widget.
*/
LayoutItem.prototype.fit = function () {
var limits = domutils_1.ElementExt.sizeLimits(this.widget.node);
this._minWidth = limits.minWidth;
this._minHeight = limits.minHeight;
this._maxWidth = limits.maxWidth;
this._maxHeight = limits.maxHeight;
};
/**
* Update the position and size of the managed widget.
*
* @param left - The left edge position of the layout box.
*
* @param top - The top edge position of the layout box.
*
* @param width - The width of the layout box.
*
* @param height - The height of the layout box.
*/
LayoutItem.prototype.update = function (left, top, width, height) {
// Clamp the size to the computed size limits.
var clampW = Math.max(this._minWidth, Math.min(width, this._maxWidth));
var clampH = Math.max(this._minHeight, Math.min(height, this._maxHeight));
// Adjust the left edge for the horizontal alignment, if needed.
if (clampW < width) {
switch (Layout.getHorizontalAlignment(this.widget)) {
case 'left':
break;
case 'center':
left += (width - clampW) / 2;
break;
case 'right':
left += width - clampW;
break;
default:
throw 'unreachable';
}
}
// Adjust the top edge for the vertical alignment, if needed.
if (clampH < height) {
switch (Layout.getVerticalAlignment(this.widget)) {
case 'top':
break;
case 'center':
top += (height - clampH) / 2;
break;
case 'bottom':
top += height - clampH;
break;
default:
throw 'unreachable';
}
}
// Set up the resize variables.
var resized = false;
var style = this.widget.node.style;
// Update the top edge of the widget if needed.
if (this._top !== top) {
this._top = top;
style.top = top + "px";
}
// Update the left edge of the widget if needed.
if (this._left !== left) {
this._left = left;
style.left = left + "px";
}
// Update the width of the widget if needed.
if (this._width !== clampW) {
resized = true;
this._width = clampW;
style.width = clampW + "px";
}
// Update the height of the widget if needed.
if (this._height !== clampH) {
resized = true;
this._height = clampH;
style.height = clampH + "px";
}
// Send a resize message to the widget if needed.
if (resized) {
var msg = new widget_1.Widget.ResizeMessage(clampW, clampH);
messaging_1.MessageLoop.sendMessage(this.widget, msg);
}
};
return LayoutItem;
}());
exports.LayoutItem = LayoutItem;
/**
* The namespace for the module implementation details.
*/
var Private;
(function (Private) {
/**
* The attached property for a widget horizontal alignment.
*/
Private.horizontalAlignmentProperty = new properties_1.AttachedProperty({
name: 'horizontalAlignment',
create: function () { return 'center'; },
changed: onAlignmentChanged
});
/**
* The attached property for a widget vertical alignment.
*/
Private.verticalAlignmentProperty = new properties_1.AttachedProperty({
name: 'verticalAlignment',
create: function () { return 'top'; },
changed: onAlignmentChanged
});
/**
* The change handler for the attached alignment properties.
*/
function onAlignmentChanged(child) {
if (child.parent && child.parent.layout) {
child.parent.update();
}
}
})(Private || (Private = {}));