UNPKG

@lumino/widgets

Version:
1,343 lines (1,339 loc) 599 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@lumino/algorithm'), require('@lumino/coreutils'), require('@lumino/domutils'), require('@lumino/messaging'), require('@lumino/properties'), require('@lumino/signaling'), require('@lumino/dragdrop'), require('@lumino/commands'), require('@lumino/virtualdom'), require('@lumino/disposable'), require('@lumino/keyboard')) : typeof define === 'function' && define.amd ? define(['exports', '@lumino/algorithm', '@lumino/coreutils', '@lumino/domutils', '@lumino/messaging', '@lumino/properties', '@lumino/signaling', '@lumino/dragdrop', '@lumino/commands', '@lumino/virtualdom', '@lumino/disposable', '@lumino/keyboard'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.lumino_widgets = {}, global.lumino_algorithm, global.lumino_coreutils, global.lumino_domutils, global.lumino_messaging, global.lumino_properties, global.lumino_signaling, global.lumino_dragdrop, global.lumino_commands, global.lumino_virtualdom, global.lumino_disposable, global.lumino_keyboard)); })(this, (function (exports, algorithm, coreutils, domutils, messaging, properties, signaling, dragdrop, commands, virtualdom, disposable, keyboard) { 'use strict'; // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | 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. |----------------------------------------------------------------------------*/ /** * A sizer object for use with the box engine layout functions. * * #### Notes * A box sizer holds the geometry information for an object along an * arbitrary layout orientation. * * For best performance, this class should be treated as a raw data * struct. It should not typically be subclassed. */ class BoxSizer { constructor() { /** * The preferred size for the sizer. * * #### Notes * The sizer will be given this initial size subject to its size * bounds. The sizer will not deviate from this size unless such * deviation is required to fit into the available layout space. * * There is no limit to this value, but it will be clamped to the * bounds defined by {@link minSize} and {@link maxSize}. * * The default value is `0`. */ this.sizeHint = 0; /** * The minimum size of the sizer. * * #### Notes * The sizer will never be sized less than this value, even if * it means the sizer will overflow the available layout space. * * It is assumed that this value lies in the range `[0, Infinity)` * and that it is `<=` to {@link maxSize}. Failure to adhere to this * constraint will yield undefined results. * * The default value is `0`. */ this.minSize = 0; /** * The maximum size of the sizer. * * #### Notes * The sizer will never be sized greater than this value, even if * it means the sizer will underflow the available layout space. * * It is assumed that this value lies in the range `[0, Infinity]` * and that it is `>=` to {@link minSize}. Failure to adhere to this * constraint will yield undefined results. * * The default value is `Infinity`. */ this.maxSize = Infinity; /** * The stretch factor for the sizer. * * #### Notes * This controls how much the sizer stretches relative to its sibling * sizers when layout space is distributed. A stretch factor of zero * is special and will cause the sizer to only be resized after all * other sizers with a stretch factor greater than zero have been * resized to their limits. * * It is assumed that this value is an integer that lies in the range * `[0, Infinity)`. Failure to adhere to this constraint will yield * undefined results. * * The default value is `1`. */ this.stretch = 1; /** * The computed size of the sizer. * * #### Notes * This value is the output of a call to {@link BoxEngine.calc}. It represents * the computed size for the object along the layout orientation, * and will always lie in the range `[minSize, maxSize]`. * * This value is output only. * * Changing this value will have no effect. */ this.size = 0; /** * An internal storage property for the layout algorithm. * * #### Notes * This value is used as temporary storage by the layout algorithm. * * Changing this value will have no effect. */ this.done = false; } } /** * The namespace for the box engine layout functions. */ exports.BoxEngine = void 0; (function (BoxEngine) { /** * Calculate the optimal layout sizes for a sequence of box sizers. * * This distributes the available layout space among the box sizers * according to the following algorithm: * * 1. Initialize the sizers's size to its size hint and compute the * sums for each of size hint, min size, and max size. * * 2. If the total size hint equals the available space, return. * * 3. If the available space is less than the total min size, set all * sizers to their min size and return. * * 4. If the available space is greater than the total max size, set * all sizers to their max size and return. * * 5. If the layout space is less than the total size hint, distribute * the negative delta as follows: * * a. Shrink each sizer with a stretch factor greater than zero by * an amount proportional to the negative space and the sum of * stretch factors. If the sizer reaches its min size, remove * it and its stretch factor from the computation. * * b. If after adjusting all stretch sizers there remains negative * space, distribute the space equally among the sizers with a * stretch factor of zero. If a sizer reaches its min size, * remove it from the computation. * * 6. If the layout space is greater than the total size hint, * distribute the positive delta as follows: * * a. Expand each sizer with a stretch factor greater than zero by * an amount proportional to the postive space and the sum of * stretch factors. If the sizer reaches its max size, remove * it and its stretch factor from the computation. * * b. If after adjusting all stretch sizers there remains positive * space, distribute the space equally among the sizers with a * stretch factor of zero. If a sizer reaches its max size, * remove it from the computation. * * 7. return * * @param sizers - The sizers for a particular layout line. * * @param space - The available layout space for the sizers. * * @returns The delta between the provided available space and the * actual consumed space. This value will be zero if the sizers * can be adjusted to fit, negative if the available space is too * small, and positive if the available space is too large. * * #### Notes * The {@link BoxSizer.size} of each sizer is updated with the computed size. * * This function can be called at any time to recompute the layout for * an existing sequence of sizers. The previously computed results will * have no effect on the new output. It is therefore not necessary to * create new sizer objects on each resize event. */ function calc(sizers, space) { // Bail early if there is nothing to do. let count = sizers.length; if (count === 0) { return space; } // Setup the size and stretch counters. let totalMin = 0; let totalMax = 0; let totalSize = 0; let totalStretch = 0; let stretchCount = 0; // Setup the sizers and compute the totals. for (let i = 0; i < count; ++i) { let sizer = sizers[i]; let min = sizer.minSize; let max = sizer.maxSize; let hint = sizer.sizeHint; sizer.done = false; sizer.size = Math.max(min, Math.min(hint, max)); totalSize += sizer.size; totalMin += min; totalMax += max; if (sizer.stretch > 0) { totalStretch += sizer.stretch; stretchCount++; } } // If the space is equal to the total size, return early. if (space === totalSize) { return 0; } // If the space is less than the total min, minimize each sizer. if (space <= totalMin) { for (let i = 0; i < count; ++i) { let sizer = sizers[i]; sizer.size = sizer.minSize; } return space - totalMin; } // If the space is greater than the total max, maximize each sizer. if (space >= totalMax) { for (let i = 0; i < count; ++i) { let sizer = sizers[i]; sizer.size = sizer.maxSize; } return space - totalMax; } // The loops below perform sub-pixel precision sizing. A near zero // value is used for compares instead of zero to ensure that the // loop terminates when the subdivided space is reasonably small. let nearZero = 0.01; // A counter which is decremented each time a sizer is resized to // its limit. This ensures the loops terminate even if there is // space remaining to distribute. let notDoneCount = count; // Distribute negative delta space. if (space < totalSize) { // Shrink each stretchable sizer by an amount proportional to its // stretch factor. If a sizer reaches its min size it's marked as // done. The loop progresses in phases where each sizer is given // a chance to consume its fair share for the pass, regardless of // whether a sizer before it reached its limit. This continues // until the stretchable sizers or the free space is exhausted. let freeSpace = totalSize - space; while (stretchCount > 0 && freeSpace > nearZero) { let distSpace = freeSpace; let distStretch = totalStretch; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done || sizer.stretch === 0) { continue; } let amt = (sizer.stretch * distSpace) / distStretch; if (sizer.size - amt <= sizer.minSize) { freeSpace -= sizer.size - sizer.minSize; totalStretch -= sizer.stretch; sizer.size = sizer.minSize; sizer.done = true; notDoneCount--; stretchCount--; } else { freeSpace -= amt; sizer.size -= amt; } } } // Distribute any remaining space evenly among the non-stretchable // sizers. This progresses in phases in the same manner as above. while (notDoneCount > 0 && freeSpace > nearZero) { let amt = freeSpace / notDoneCount; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done) { continue; } if (sizer.size - amt <= sizer.minSize) { freeSpace -= sizer.size - sizer.minSize; sizer.size = sizer.minSize; sizer.done = true; notDoneCount--; } else { freeSpace -= amt; sizer.size -= amt; } } } } // Distribute positive delta space. else { // Expand each stretchable sizer by an amount proportional to its // stretch factor. If a sizer reaches its max size it's marked as // done. The loop progresses in phases where each sizer is given // a chance to consume its fair share for the pass, regardless of // whether a sizer before it reached its limit. This continues // until the stretchable sizers or the free space is exhausted. let freeSpace = space - totalSize; while (stretchCount > 0 && freeSpace > nearZero) { let distSpace = freeSpace; let distStretch = totalStretch; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done || sizer.stretch === 0) { continue; } let amt = (sizer.stretch * distSpace) / distStretch; if (sizer.size + amt >= sizer.maxSize) { freeSpace -= sizer.maxSize - sizer.size; totalStretch -= sizer.stretch; sizer.size = sizer.maxSize; sizer.done = true; notDoneCount--; stretchCount--; } else { freeSpace -= amt; sizer.size += amt; } } } // Distribute any remaining space evenly among the non-stretchable // sizers. This progresses in phases in the same manner as above. while (notDoneCount > 0 && freeSpace > nearZero) { let amt = freeSpace / notDoneCount; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done) { continue; } if (sizer.size + amt >= sizer.maxSize) { freeSpace -= sizer.maxSize - sizer.size; sizer.size = sizer.maxSize; sizer.done = true; notDoneCount--; } else { freeSpace -= amt; sizer.size += amt; } } } } // Indicate that the consumed space equals the available space. return 0; } BoxEngine.calc = calc; /** * Adjust a sizer by a delta and update its neighbors accordingly. * * @param sizers - The sizers which should be adjusted. * * @param index - The index of the sizer to grow. * * @param delta - The amount to adjust the sizer, positive or negative. * * #### Notes * This will adjust the indicated sizer by the specified amount, along * with the sizes of the appropriate neighbors, subject to the limits * specified by each of the sizers. * * This is useful when implementing box layouts where the boundaries * between the sizers are interactively adjustable by the user. */ function adjust(sizers, index, delta) { // Bail early when there is nothing to do. if (sizers.length === 0 || delta === 0) { return; } // Dispatch to the proper implementation. if (delta > 0) { growSizer(sizers, index, delta); } else { shrinkSizer(sizers, index, -delta); } } BoxEngine.adjust = adjust; /** * Grow a sizer by a positive delta and adjust neighbors. */ function growSizer(sizers, index, delta) { // Compute how much the items to the left can expand. let growLimit = 0; for (let i = 0; i <= index; ++i) { let sizer = sizers[i]; growLimit += sizer.maxSize - sizer.size; } // Compute how much the items to the right can shrink. let shrinkLimit = 0; for (let i = index + 1, n = sizers.length; i < n; ++i) { let sizer = sizers[i]; shrinkLimit += sizer.size - sizer.minSize; } // Clamp the delta adjustment to the limits. delta = Math.min(delta, growLimit, shrinkLimit); // Grow the sizers to the left by the delta. let grow = delta; for (let i = index; i >= 0 && grow > 0; --i) { let sizer = sizers[i]; let limit = sizer.maxSize - sizer.size; if (limit >= grow) { sizer.sizeHint = sizer.size + grow; grow = 0; } else { sizer.sizeHint = sizer.size + limit; grow -= limit; } } // Shrink the sizers to the right by the delta. let shrink = delta; for (let i = index + 1, n = sizers.length; i < n && shrink > 0; ++i) { let sizer = sizers[i]; let limit = sizer.size - sizer.minSize; if (limit >= shrink) { sizer.sizeHint = sizer.size - shrink; shrink = 0; } else { sizer.sizeHint = sizer.size - limit; shrink -= limit; } } } /** * Shrink a sizer by a positive delta and adjust neighbors. */ function shrinkSizer(sizers, index, delta) { // Compute how much the items to the right can expand. let growLimit = 0; for (let i = index + 1, n = sizers.length; i < n; ++i) { let sizer = sizers[i]; growLimit += sizer.maxSize - sizer.size; } // Compute how much the items to the left can shrink. let shrinkLimit = 0; for (let i = 0; i <= index; ++i) { let sizer = sizers[i]; shrinkLimit += sizer.size - sizer.minSize; } // Clamp the delta adjustment to the limits. delta = Math.min(delta, growLimit, shrinkLimit); // Grow the sizers to the right by the delta. let grow = delta; for (let i = index + 1, n = sizers.length; i < n && grow > 0; ++i) { let sizer = sizers[i]; let limit = sizer.maxSize - sizer.size; if (limit >= grow) { sizer.sizeHint = sizer.size + grow; grow = 0; } else { sizer.sizeHint = sizer.size + limit; grow -= limit; } } // Shrink the sizers to the left by the delta. let shrink = delta; for (let i = index; i >= 0 && shrink > 0; --i) { let sizer = sizers[i]; let limit = sizer.size - sizer.minSize; if (limit >= shrink) { sizer.sizeHint = sizer.size - shrink; shrink = 0; } else { sizer.sizeHint = sizer.size - limit; shrink -= limit; } } } })(exports.BoxEngine || (exports.BoxEngine = {})); /** * An object which holds data related to an object's title. * * #### Notes * A title object is intended to hold the data necessary to display a * header for a particular object. A common example is the `TabPanel`, * which uses the widget title to populate the tab for a child widget. * * It is the responsibility of the owner to call the title disposal. */ class Title { /** * Construct a new title. * * @param options - The options for initializing the title. */ constructor(options) { this._label = ''; this._caption = ''; this._mnemonic = -1; this._icon = undefined; this._iconClass = ''; this._iconLabel = ''; this._className = ''; this._closable = false; this._changed = new signaling.Signal(this); this._isDisposed = false; this.owner = options.owner; if (options.label !== undefined) { this._label = options.label; } if (options.mnemonic !== undefined) { this._mnemonic = options.mnemonic; } if (options.icon !== undefined) { this._icon = options.icon; } if (options.iconClass !== undefined) { this._iconClass = options.iconClass; } if (options.iconLabel !== undefined) { this._iconLabel = options.iconLabel; } if (options.caption !== undefined) { this._caption = options.caption; } if (options.className !== undefined) { this._className = options.className; } if (options.closable !== undefined) { this._closable = options.closable; } this._dataset = options.dataset || {}; } /** * A signal emitted when the state of the title changes. */ get changed() { return this._changed; } /** * Get the label for the title. * * #### Notes * The default value is an empty string. */ get label() { return this._label; } /** * Set the label for the title. */ set label(value) { if (this._label === value) { return; } this._label = value; this._changed.emit(undefined); } /** * Get the mnemonic index for the title. * * #### Notes * The default value is `-1`. */ get mnemonic() { return this._mnemonic; } /** * Set the mnemonic index for the title. */ set mnemonic(value) { if (this._mnemonic === value) { return; } this._mnemonic = value; this._changed.emit(undefined); } /** * Get the icon renderer for the title. * * #### Notes * The default value is undefined. */ get icon() { return this._icon; } /** * Set the icon renderer for the title. * * #### Notes * A renderer is an object that supplies a render and unrender function. */ set icon(value) { if (this._icon === value) { return; } this._icon = value; this._changed.emit(undefined); } /** * Get the icon class name for the title. * * #### Notes * The default value is an empty string. */ get iconClass() { return this._iconClass; } /** * Set the icon class name for the title. * * #### Notes * Multiple class names can be separated with whitespace. */ set iconClass(value) { if (this._iconClass === value) { return; } this._iconClass = value; this._changed.emit(undefined); } /** * Get the icon label for the title. * * #### Notes * The default value is an empty string. */ get iconLabel() { return this._iconLabel; } /** * Set the icon label for the title. * * #### Notes * Multiple class names can be separated with whitespace. */ set iconLabel(value) { if (this._iconLabel === value) { return; } this._iconLabel = value; this._changed.emit(undefined); } /** * Get the caption for the title. * * #### Notes * The default value is an empty string. */ get caption() { return this._caption; } /** * Set the caption for the title. */ set caption(value) { if (this._caption === value) { return; } this._caption = value; this._changed.emit(undefined); } /** * Get the extra class name for the title. * * #### Notes * The default value is an empty string. */ get className() { return this._className; } /** * Set the extra class name for the title. * * #### Notes * Multiple class names can be separated with whitespace. */ set className(value) { if (this._className === value) { return; } this._className = value; this._changed.emit(undefined); } /** * Get the closable state for the title. * * #### Notes * The default value is `false`. */ get closable() { return this._closable; } /** * Set the closable state for the title. * * #### Notes * This controls the presence of a close icon when applicable. */ set closable(value) { if (this._closable === value) { return; } this._closable = value; this._changed.emit(undefined); } /** * Get the dataset for the title. * * #### Notes * The default value is an empty dataset. */ get dataset() { return this._dataset; } /** * Set the dataset for the title. * * #### Notes * This controls the data attributes when applicable. */ set dataset(value) { if (this._dataset === value) { return; } this._dataset = value; this._changed.emit(undefined); } /** * Test whether the title has been disposed. */ get isDisposed() { return this._isDisposed; } /** * Dispose of the resources held by the title. * * #### Notes * It is the responsibility of the owner to call the title disposal. */ dispose() { if (this.isDisposed) { return; } this._isDisposed = true; signaling.Signal.clearData(this); } } /** * The base class of the lumino widget hierarchy. * * #### Notes * This class will typically be subclassed in order to create a useful * widget. However, it can be used directly to host externally created * content. */ class Widget { /** * Construct a new widget. * * @param options - The options for initializing the widget. */ constructor(options = {}) { this._flags = 0; this._layout = null; this._parent = null; this._disposed = new signaling.Signal(this); this._hiddenMode = Widget.HiddenMode.Display; this.node = Private$j.createNode(options); this.addClass('lm-Widget'); } /** * Dispose of the widget and its descendant widgets. * * #### Notes * It is unsafe to use the widget after it has been disposed. * * All calls made to this method after the first are a no-op. */ dispose() { // Do nothing if the widget is already disposed. if (this.isDisposed) { return; } // Set the disposed flag and emit the disposed signal. this.setFlag(Widget.Flag.IsDisposed); this._disposed.emit(undefined); // Remove or detach the widget if necessary. if (this.parent) { this.parent = null; } else if (this.isAttached) { Widget.detach(this); } // Dispose of the widget layout. if (this._layout) { this._layout.dispose(); this._layout = null; } // Dispose the title this.title.dispose(); // Clear the extra data associated with the widget. signaling.Signal.clearData(this); messaging.MessageLoop.clearData(this); properties.AttachedProperty.clearData(this); } /** * A signal emitted when the widget is disposed. */ get disposed() { return this._disposed; } /** * Test whether the widget has been disposed. */ get isDisposed() { return this.testFlag(Widget.Flag.IsDisposed); } /** * Test whether the widget's node is attached to the DOM. */ get isAttached() { return this.testFlag(Widget.Flag.IsAttached); } /** * Test whether the widget is explicitly hidden. * * #### Notes * You should prefer `!{@link isVisible}` over `{@link isHidden}` if you want to know if the * widget is hidden as this does not test if the widget is hidden because one of its ancestors is hidden. */ get isHidden() { return this.testFlag(Widget.Flag.IsHidden); } /** * Test whether the widget is visible. * * #### Notes * A widget is visible when it is attached to the DOM, is not * explicitly hidden, and has no explicitly hidden ancestors. * * Since 2.7.0, this does not rely on the {@link Widget.Flag.IsVisible} flag. * It recursively checks the visibility of all parent widgets. */ get isVisible() { // eslint-disable-next-line @typescript-eslint/no-this-alias let parent = this; do { if (parent.isHidden || !parent.isAttached) { return false; } parent = parent.parent; } while (parent != null); return true; } /** * The title object for the widget. * * #### Notes * The title object is used by some container widgets when displaying * the widget alongside some title, such as a tab panel or side bar. * * Since not all widgets will use the title, it is created on demand. * * The `owner` property of the title is set to this widget. */ get title() { return Private$j.titleProperty.get(this); } /** * Get the id of the widget's DOM node. */ get id() { return this.node.id; } /** * Set the id of the widget's DOM node. */ set id(value) { this.node.id = value; } /** * The dataset for the widget's DOM node. */ get dataset() { return this.node.dataset; } /** * Get the method for hiding the widget. */ get hiddenMode() { return this._hiddenMode; } /** * Set the method for hiding the widget. */ set hiddenMode(value) { if (this._hiddenMode === value) { return; } if (this.isHidden) { // Reset styles set by previous mode. this._toggleHidden(false); } if (value == Widget.HiddenMode.Scale) { this.node.style.willChange = 'transform'; } else { this.node.style.willChange = 'auto'; } this._hiddenMode = value; if (this.isHidden) { // Set styles for new mode. this._toggleHidden(true); } } /** * Get the parent of the widget. */ get parent() { return this._parent; } /** * Set the parent of the widget. * * #### Notes * Children are typically added to a widget by using a layout, which * means user code will not normally set the parent widget directly. * * The widget will be automatically removed from its old parent. * * This is a no-op if there is no effective parent change. */ set parent(value) { if (this._parent === value) { return; } if (value && this.contains(value)) { throw new Error('Invalid parent widget.'); } if (this._parent && !this._parent.isDisposed) { let msg = new Widget.ChildMessage('child-removed', this); messaging.MessageLoop.sendMessage(this._parent, msg); } this._parent = value; if (this._parent && !this._parent.isDisposed) { let msg = new Widget.ChildMessage('child-added', this); messaging.MessageLoop.sendMessage(this._parent, msg); } if (!this.isDisposed) { messaging.MessageLoop.sendMessage(this, Widget.Msg.ParentChanged); } } /** * Get the layout for the widget. */ get layout() { return this._layout; } /** * Set the layout for the widget. * * #### Notes * The layout is single-use only. It cannot be changed after the * first assignment. * * The layout is disposed automatically when the widget is disposed. */ set layout(value) { if (this._layout === value) { return; } if (this.testFlag(Widget.Flag.DisallowLayout)) { throw new Error('Cannot set widget layout.'); } if (this._layout) { throw new Error('Cannot change widget layout.'); } if (value.parent) { throw new Error('Cannot change layout parent.'); } this._layout = value; value.parent = this; } /** * Create an iterator over the widget's children. * * @returns A new iterator over the children of the widget. * * #### Notes * The widget must have a populated layout in order to have children. * * If a layout is not installed, the returned iterator will be empty. */ *children() { if (this._layout) { yield* this._layout; } } /** * Test whether a widget is a descendant of this widget. * * @param widget - The descendant widget of interest. * * @returns `true` if the widget is a descendant, `false` otherwise. */ contains(widget) { for (let value = widget; value; value = value._parent) { if (value === this) { return true; } } return false; } /** * Test whether the widget's DOM node has the given class name. * * @param name - The class name of interest. * * @returns `true` if the node has the class, `false` otherwise. */ hasClass(name) { return this.node.classList.contains(name); } /** * Add a class name to the widget's DOM node. * * @param name - The class name to add to the node. * * #### Notes * If the class name is already added to the node, this is a no-op. * * The class name must not contain whitespace. */ addClass(name) { this.node.classList.add(name); } /** * Remove a class name from the widget's DOM node. * * @param name - The class name to remove from the node. * * #### Notes * If the class name is not yet added to the node, this is a no-op. * * The class name must not contain whitespace. */ removeClass(name) { this.node.classList.remove(name); } /** * Toggle a class name on the widget's DOM node. * * @param name - The class name to toggle on the node. * * @param force - Whether to force add the class (`true`) or force * remove the class (`false`). If not provided, the presence of * the class will be toggled from its current state. * * @returns `true` if the class is now present, `false` otherwise. * * #### Notes * The class name must not contain whitespace. */ toggleClass(name, force) { if (force === true) { this.node.classList.add(name); return true; } if (force === false) { this.node.classList.remove(name); return false; } return this.node.classList.toggle(name); } /** * Post an `'update-request'` message to the widget. * * #### Notes * This is a simple convenience method for posting the message. */ update() { messaging.MessageLoop.postMessage(this, Widget.Msg.UpdateRequest); } /** * Post a `'fit-request'` message to the widget. * * #### Notes * This is a simple convenience method for posting the message. */ fit() { messaging.MessageLoop.postMessage(this, Widget.Msg.FitRequest); } /** * Post an `'activate-request'` message to the widget. * * #### Notes * This is a simple convenience method for posting the message. */ activate() { messaging.MessageLoop.postMessage(this, Widget.Msg.ActivateRequest); } /** * Send a `'close-request'` message to the widget. * * #### Notes * This is a simple convenience method for sending the message. */ close() { messaging.MessageLoop.sendMessage(this, Widget.Msg.CloseRequest); } /** * Show the widget and make it visible to its parent widget. * * #### Notes * This causes the {@link isHidden} property to be `false`. * * If the widget is not explicitly hidden, this is a no-op. */ show() { if (!this.testFlag(Widget.Flag.IsHidden)) { return; } if (this.isAttached && (!this.parent || this.parent.isVisible)) { messaging.MessageLoop.sendMessage(this, Widget.Msg.BeforeShow); } this.clearFlag(Widget.Flag.IsHidden); this._toggleHidden(false); if (this.isAttached && (!this.parent || this.parent.isVisible)) { messaging.MessageLoop.sendMessage(this, Widget.Msg.AfterShow); } if (this.parent) { let msg = new Widget.ChildMessage('child-shown', this); messaging.MessageLoop.sendMessage(this.parent, msg); } } /** * Hide the widget and make it hidden to its parent widget. * * #### Notes * This causes the {@link isHidden} property to be `true`. * * If the widget is explicitly hidden, this is a no-op. */ hide() { if (this.testFlag(Widget.Flag.IsHidden)) { return; } if (this.isAttached && (!this.parent || this.parent.isVisible)) { messaging.MessageLoop.sendMessage(this, Widget.Msg.BeforeHide); } this.setFlag(Widget.Flag.IsHidden); this._toggleHidden(true); if (this.isAttached && (!this.parent || this.parent.isVisible)) { messaging.MessageLoop.sendMessage(this, Widget.Msg.AfterHide); } if (this.parent) { let msg = new Widget.ChildMessage('child-hidden', this); messaging.MessageLoop.sendMessage(this.parent, msg); } } /** * Show or hide the widget according to a boolean value. * * @param hidden - `true` to hide the widget, or `false` to show it. * * #### Notes * This is a convenience method for `hide()` and `show()`. */ setHidden(hidden) { if (hidden) { this.hide(); } else { this.show(); } } /** * Test whether the given widget flag is set. * * #### Notes * This will not typically be called directly by user code. * * Since 2.7.0, {@link Widget.Flag.IsVisible} is deprecated. * It will be removed in a future version. */ testFlag(flag) { return (this._flags & flag) !== 0; } /** * Set the given widget flag. * * #### Notes * This will not typically be called directly by user code. * * Since 2.7.0, {@link Widget.Flag.IsVisible} is deprecated. * It will be removed in a future version. */ setFlag(flag) { this._flags |= flag; } /** * Clear the given widget flag. * * #### Notes * This will not typically be called directly by user code. * * Since 2.7.0, {@link Widget.Flag.IsVisible} is deprecated. * It will be removed in a future version. */ clearFlag(flag) { this._flags &= ~flag; } /** * Process a message sent to the widget. * * @param msg - The message sent to the widget. * * #### Notes * Subclasses may reimplement this method as needed. */ processMessage(msg) { switch (msg.type) { case 'resize': this.notifyLayout(msg); this.onResize(msg); break; case 'update-request': this.notifyLayout(msg); this.onUpdateRequest(msg); break; case 'fit-request': this.notifyLayout(msg); this.onFitRequest(msg); break; case 'before-show': this.notifyLayout(msg); this.onBeforeShow(msg); break; case 'after-show': this.setFlag(Widget.Flag.IsVisible); this.notifyLayout(msg); this.onAfterShow(msg); break; case 'before-hide': this.notifyLayout(msg); this.onBeforeHide(msg); break; case 'after-hide': this.clearFlag(Widget.Flag.IsVisible); this.notifyLayout(msg); this.onAfterHide(msg); break; case 'before-attach': this.notifyLayout(msg); this.onBeforeAttach(msg); break; case 'after-attach': if (!this.isHidden && (!this.parent || this.parent.isVisible)) { this.setFlag(Widget.Flag.IsVisible); } this.setFlag(Widget.Flag.IsAttached); this.notifyLayout(msg); this.onAfterAttach(msg); break; case 'before-detach': this.notifyLayout(msg); this.onBeforeDetach(msg); break; case 'after-detach': this.clearFlag(Widget.Flag.IsVisible); this.clearFlag(Widget.Flag.IsAttached); this.notifyLayout(msg); this.onAfterDetach(msg); break; case 'activate-request': this.notifyLayout(msg); this.onActivateRequest(msg); break; case 'close-request': this.notifyLayout(msg); this.onCloseRequest(msg); break; case 'child-added': this.notifyLayout(msg); this.onChildAdded(msg); break; case 'child-removed': this.notifyLayout(msg); this.onChildRemoved(msg); break; default: this.notifyLayout(msg); break; } } /** * Invoke the message processing routine of the widget's layout. * * @param msg - The message to dispatch to the layout. * * #### Notes * This is a no-op if the widget does not have a layout. * * This will not typically be called directly by user code. */ notifyLayout(msg) { if (this._layout) { this._layout.processParentMessage(msg); } } /** * A message handler invoked on a `'close-request'` message. * * #### Notes * The default implementation unparents or detaches the widget. */ onCloseRequest(msg) { if (this.parent) { this.parent = null; } else if (this.isAttached) { Widget.detach(this); } } /** * A message handler invoked on a `'resize'` message. * * #### Notes * The default implementation of this handler is a no-op. */ onResize(msg) { } /** * A message handler invoked on an `'update-request'` message. * * #### Notes * The default implementation of this handler is a no-op. */ onUpdateRequest(msg) { } /** * A message handler invoked on a `'fit-request'` message. * * #### Notes * The default implementation of this handler is a no-op. */ onFitRequest(msg) { } /** * A message handler invoked on an `'activate-request'` message.