2021-08-23 23:56:36 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
// Box that can contain other components
|
|
|
|
Toolkit.Panel = class Panel extends Toolkit.Component {
|
|
|
|
|
|
|
|
// Object constructor
|
2021-08-26 19:23:18 +00:00
|
|
|
constructor(application, options) {
|
|
|
|
super(application, "div", options);
|
2021-08-30 02:14:06 +00:00
|
|
|
options = options || {};
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure instance fields
|
2021-08-26 19:23:18 +00:00
|
|
|
this.alignCross = "start";
|
|
|
|
this.alignMain = "start";
|
2021-08-23 23:56:36 +00:00
|
|
|
this.application = application;
|
|
|
|
this.children = [];
|
2021-08-26 19:23:18 +00:00
|
|
|
this.columns = null;
|
2021-08-23 23:56:36 +00:00
|
|
|
this.direction = "row";
|
2021-09-02 00:16:22 +00:00
|
|
|
this.focusable = "focusable" in options ? !!options.focusable:false;
|
|
|
|
this.hollow = "hollow" in options ? !!options.hollow : true;
|
2021-08-26 19:23:18 +00:00
|
|
|
this.layout = null;
|
2021-09-02 00:16:22 +00:00
|
|
|
this.name = options.name || "";
|
2021-08-26 19:23:18 +00:00
|
|
|
this.overflowX = options.overflowX || "hidden";
|
|
|
|
this.overflowY = options.overflowY || "hidden";
|
|
|
|
this.rows = null;
|
2021-08-23 23:56:36 +00:00
|
|
|
this.wrap = false;
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Configure properties
|
|
|
|
this.setFocusable(this.focusable);
|
|
|
|
this.setHollow (this.hollow);
|
|
|
|
this.setLayout (options.layout || null, options);
|
|
|
|
this.setName (this.name);
|
|
|
|
this.setOverflow (this.overflowX, this.overflowY);
|
|
|
|
if (this.application && this.focusable)
|
|
|
|
this.application.addComponent(this);
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
|
|
|
|
// Add a component as a child of this container
|
|
|
|
add(component, index) {
|
|
|
|
|
|
|
|
// Determine the ordinal position of the element within the container
|
|
|
|
index = !(typeof index == "number") ? this.children.length :
|
|
|
|
Math.floor(Math.min(Math.max(0, index), this.children.length));
|
|
|
|
|
|
|
|
// Add the component to the container
|
2021-08-26 19:23:18 +00:00
|
|
|
let ref = this.children[index] || null;
|
2021-08-23 23:56:36 +00:00
|
|
|
component.parent = this;
|
2021-08-26 19:23:18 +00:00
|
|
|
this.element.insertBefore(component.element,
|
|
|
|
ref == null ? null : ref.element);
|
2021-08-23 23:56:36 +00:00
|
|
|
this.children.splice(index, 0, component);
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
return component;
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Request focus on the appropriate element
|
|
|
|
focus() {
|
|
|
|
if (this.focusable)
|
|
|
|
this.element.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve the component's accessible name
|
|
|
|
getName() {
|
|
|
|
return this.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine whether the component is focusable
|
|
|
|
isFocusable() {
|
|
|
|
return this.focusable;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine whether the component is hollow
|
|
|
|
isHollow() {
|
|
|
|
return this.hollow;
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// Create a Button and associate it with the application
|
|
|
|
newButton(options) {
|
|
|
|
return new Toolkit.Button(this.application, options);
|
|
|
|
}
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Create a CheckBox and associate it with the application
|
|
|
|
newCheckBox(options) {
|
|
|
|
return new Toolkit.CheckBox(this.application, options);
|
|
|
|
}
|
|
|
|
|
2021-08-30 02:14:06 +00:00
|
|
|
// Create a Label and associate it with the application
|
|
|
|
newLabel(options) {
|
|
|
|
return new Toolkit.Label(this.application, options);
|
|
|
|
}
|
|
|
|
|
2021-09-03 17:38:48 +00:00
|
|
|
// Create a ListBox and associate it with the application
|
|
|
|
newListBox(options) {
|
|
|
|
return new Toolkit.ListBox(this.application, options);
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// Create a MenuBar and associate it with the application
|
|
|
|
newMenuBar(options) {
|
|
|
|
return new Toolkit.MenuBar(this.application, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a Panel and associate it with the application
|
|
|
|
newPanel(options) {
|
|
|
|
return new Toolkit.Panel(this.application, options);
|
|
|
|
}
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Create a RadioButton and associate it with the application
|
|
|
|
newRadioButton(options) {
|
|
|
|
return new Toolkit.RadioButton(this.application, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a Splitter and associate it with the application
|
|
|
|
newSplitter(options) {
|
|
|
|
return new Toolkit.Splitter(this.application, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a TextBox and associate it with the application
|
|
|
|
newTextBox(options) {
|
|
|
|
return new Toolkit.TextBox(this.application, options);
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Create a Window and associate it with the application
|
|
|
|
newWindow(options) {
|
|
|
|
return new Toolkit.Window(this.application, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the index of the next visible child
|
|
|
|
nextChild(index) {
|
|
|
|
for (let x = 0; x <= this.children.length; x++) {
|
|
|
|
index = (index + 1) % this.children.length;
|
|
|
|
let comp = this.children[index];
|
|
|
|
if (comp.isVisible())
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the index of the previous visible child
|
|
|
|
previousChild(index) {
|
|
|
|
for (let x = 0; x <= this.children.length; x++) {
|
|
|
|
index = (index + this.children.length - 1) % this.children.length;
|
|
|
|
let comp = this.children[index];
|
|
|
|
if (comp.isVisible())
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// Remove a component from the container
|
|
|
|
remove(component) {
|
|
|
|
|
|
|
|
// Locate the component in the children
|
|
|
|
let index = this.children.indexOf(component);
|
|
|
|
if (index == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Remove the component
|
|
|
|
component.parent = null;
|
|
|
|
component.element.remove();
|
|
|
|
this.children.splice(index, 1);
|
|
|
|
}
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Specify whether the component is focusable
|
|
|
|
setFocusable(focusable) {
|
|
|
|
this.focusable = focusable = !!focusable;
|
|
|
|
if (focusable) {
|
|
|
|
this.element.setAttribute("tabindex", "0");
|
|
|
|
this.application && this.application.addComponent(this);
|
|
|
|
} else {
|
|
|
|
this.element.removeAttribute("aria-label");
|
|
|
|
this.element.removeAttribute("tabindex");
|
|
|
|
this.application && this.application.removeComponent(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Specify whether the component is hollow
|
|
|
|
setHollow(hollow) {
|
|
|
|
this.hollow = hollow = !!hollow;
|
|
|
|
if (hollow) {
|
|
|
|
this.element.style.minHeight = "0";
|
|
|
|
this.element.style.minWidth = "0";
|
|
|
|
} else {
|
|
|
|
this.element.style.removeProperty("min-height");
|
|
|
|
this.element.style.removeProperty("min-width" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure the element's layout
|
|
|
|
setLayout(layout, options) {
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure instance fields
|
2021-08-26 19:23:18 +00:00
|
|
|
this.layout = layout;
|
|
|
|
|
|
|
|
// Processing by layout
|
2021-09-02 00:16:22 +00:00
|
|
|
options = options || {};
|
2021-08-26 19:23:18 +00:00
|
|
|
switch (layout) {
|
|
|
|
case "block" : this.setBlockLayout (options); break;
|
|
|
|
case "desktop": this.setDesktopLayout(options); break;
|
|
|
|
case "flex" : this.setFlexLayout (options); break;
|
|
|
|
case "grid" : this.setGridLayout (options); break;
|
|
|
|
default : this.setNullLayout (options); break;
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Specify the component's accessible name
|
|
|
|
setName(name) {
|
|
|
|
this.name = name || "";
|
|
|
|
if (this.focusable)
|
|
|
|
this.localize();
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure the panel's overflow scrolling behavior
|
|
|
|
setOverflow(x, y) {
|
2021-09-02 00:16:22 +00:00
|
|
|
this.element.style.overflowX = this.overflowX = x || "hidden";
|
|
|
|
this.element.style.overflowY = this.overflowY = y || this.overflowX;
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Specify the semantic role of the panel
|
|
|
|
setRole(role) {
|
|
|
|
if (!role)
|
|
|
|
this.element.removeAttribute("role");
|
|
|
|
else this.element.setAttribute("role", "" + role);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-08-30 02:14:06 +00:00
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Move a window to the foreground
|
|
|
|
bringToFront(wnd) {
|
|
|
|
for (let child of this.children)
|
2021-09-02 00:16:22 +00:00
|
|
|
child.element.style.zIndex = child == wnd ? "1" : "0";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update display text with localized strings
|
|
|
|
localize() {
|
|
|
|
let name = this.name;
|
|
|
|
if (this.application)
|
|
|
|
name = this.application.translate(name, this);
|
|
|
|
this.element.setAttribute("aria-label", name);
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Resize event handler
|
|
|
|
onresize(desktop) {
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (this.layout != "desktop")
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Ensure all child windows are visible in the viewport
|
|
|
|
for (let wnd of this.children) {
|
2021-08-30 02:14:06 +00:00
|
|
|
if (!wnd.isVisible())
|
|
|
|
continue;
|
2021-08-26 19:23:18 +00:00
|
|
|
let bounds = wnd.getBounds();
|
|
|
|
wnd.contain(
|
|
|
|
bounds.x - desktop.x,
|
2021-08-30 02:14:06 +00:00
|
|
|
bounds.y - desktop.y,
|
|
|
|
desktop, bounds
|
2021-08-26 19:23:18 +00:00
|
|
|
);
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Configure a block layout
|
|
|
|
setBlockLayout(options) {
|
|
|
|
|
|
|
|
// Configure instance fields
|
|
|
|
this.layout = "block";
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// Configure element
|
2021-08-26 19:23:18 +00:00
|
|
|
this.setDisplay("block");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure a desktop layout
|
|
|
|
setDesktopLayout(options) {
|
|
|
|
|
|
|
|
// Configure instance fields
|
|
|
|
this.layout = "desktop";
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure element
|
|
|
|
this.setDisplay("block");
|
|
|
|
this.element.style.position = "relative";
|
|
|
|
if (this.resizeObserver == null)
|
|
|
|
this.addResizeListener(b=>this.onresize(b));
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure a flex layout
|
|
|
|
setFlexLayout(options) {
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure instance fields
|
2021-08-26 19:23:18 +00:00
|
|
|
this.alignCross = options.alignCross || "start";
|
|
|
|
this.alignMain = options.alignMain || "start";
|
|
|
|
this.direction = options.direction || this.direction;
|
|
|
|
this.layout = "flex";
|
|
|
|
this.wrap = !!options.wrap;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Working variables
|
2021-08-26 19:23:18 +00:00
|
|
|
let alignCross = this.alignCross;
|
|
|
|
let alignMain = this.alignMain;
|
|
|
|
if (alignCross == "start" || alignCross == "end")
|
|
|
|
alignCross = "flex-" + alignCross;
|
|
|
|
if (alignMain == "start" || alignMain == "end")
|
|
|
|
alignMain = "flex-" + alignMain;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure element
|
2021-08-26 19:23:18 +00:00
|
|
|
this.setDisplay("flex");
|
|
|
|
this.element.style.alignItems = alignCross;
|
|
|
|
this.element.style.flexDirection = this.direction;
|
|
|
|
this.element.style.justifyContent = alignMain;
|
|
|
|
this.element.style.flexWrap = this.wrap ? "wrap" : "nowrap";
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure a grid layout
|
|
|
|
setGridLayout(options) {
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure instance fields
|
|
|
|
this.columns = options.columns || null;
|
|
|
|
this.layout = "grid";
|
|
|
|
this.rows = options.rows || null;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure element
|
|
|
|
this.setDisplay("grid");
|
|
|
|
if (this.columns == null)
|
|
|
|
this.element.style.removeProperty("grid-template-columns");
|
|
|
|
else this.element.style.gridTemplateColumns = this.columns;
|
|
|
|
if (this.rows == null)
|
|
|
|
this.element.style.removeProperty("grid-template-rows");
|
|
|
|
else this.element.style.gridTemplateRows = this.rows;
|
|
|
|
}
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure a null layout
|
|
|
|
setNullLayout(options) {
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure instance fields
|
|
|
|
this.layout = null;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure element
|
|
|
|
this.setDisplay(null);
|
|
|
|
this.element.style.removeProperty("align-items" );
|
|
|
|
this.element.style.removeProperty("flex-wrap" );
|
|
|
|
this.element.style.removeProperty("grid-template-columns");
|
|
|
|
this.element.style.removeProperty("grid-template-rows" );
|
|
|
|
this.element.style.removeProperty("justify-content" );
|
|
|
|
this.element.style.removeProperty("flex-direction" );
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|