luci-base: jsdoc fixes
authorPaul Donald <redacted>
Sun, 15 Feb 2026 23:44:17 +0000 (00:44 +0100)
committerPaul Donald <redacted>
Mon, 16 Feb 2026 00:42:56 +0000 (01:42 +0100)
@name alone does not provide a linkable symbol.
@member and @memberof do.

Signed-off-by: Paul Donald <redacted>
13 files changed:
modules/luci-base/htdocs/luci-static/resources/cbi.js
modules/luci-base/htdocs/luci-static/resources/firewall.js
modules/luci-base/htdocs/luci-static/resources/form.js
modules/luci-base/htdocs/luci-static/resources/fs.js
modules/luci-base/htdocs/luci-static/resources/luci.js
modules/luci-base/htdocs/luci-static/resources/network.js
modules/luci-base/htdocs/luci-static/resources/protocol/static.js
modules/luci-base/htdocs/luci-static/resources/rpc.js
modules/luci-base/htdocs/luci-static/resources/tools/prng.js
modules/luci-base/htdocs/luci-static/resources/tools/views.js
modules/luci-base/htdocs/luci-static/resources/tools/widgets.js
modules/luci-base/htdocs/luci-static/resources/uci.js
modules/luci-base/htdocs/luci-static/resources/ui.js

index e694a105e417fc7daa21a40fc9d3fdac432b6044..ef3bd18b4d51a7d6ec79f567a7aaba89c230eacf 100644 (file)
        http://www.apache.org/licenses/LICENSE-2.0
 */
 
+/**
+ * CBI (Configuration Bindings Interface) helper utilities and DOM helpers.
+ *
+ * Provides initialization for CBI UI elements, dependency handling,
+ * validation wiring and miscellaneous helpers used by LuCI forms. Functions
+ * defined here are registered as global `window.*` symbols.
+ * @module LuCI.cbi
+ */
 var cbi_d = [];
 var cbi_strings = { path: {}, label: {} };
 
+/**
+ * Read signed 8-bit integer from a byte array at the given offset.
+ * @param {Array<number>} bytes - Byte array.
+ * @param {number} off - Offset into the array.
+ * @returns {number} Signed 8-bit value (returned as unsigned number).
+ */
 function s8(bytes, off) {
        var n = bytes[off];
        return (n > 0x7F) ? (n - 256) >>> 0 : n;
 }
 
+/**
+ * Read unsigned 16-bit little-endian integer from a byte array at offset.
+ * @param {Array<number>} bytes - Byte array.
+ * @param {number} off - Offset into the array.
+ * @returns {number} Unsigned 16-bit integer.
+ */
 function u16(bytes, off) {
        return ((bytes[off + 1] << 8) + bytes[off]) >>> 0;
 }
 
+/**
+ * Compute a stable 32-bit-ish string hash used for translation keys.
+ * Encodes UTF-8 surrogate pairs and mixes bytes into a hex hash string.
+ * @param {string|null} s - Input string.
+ * @returns {string|null} Hex hash string or null for empty input.
+ */
 function sfh(s) {
        if (s === null || s.length === 0)
                return null;
@@ -105,15 +131,35 @@ function sfh(s) {
 
 var plural_function = null;
 
+/**
+ * Trim whitespace and normalise internal whitespace sequences to single spaces.
+ * @param {*} s - Value to convert to string and trim.
+ * @returns {string} Trimmed and normalised string.
+ */
 function trimws(s) {
        return String(s).trim().replace(/[ \t\n]+/g, ' ');
 }
 
+/**
+ * Lookup a translated string for the given message and optional context.
+ * Falls back to the source string when no translation found.
+ * @param {string} s - Source string.
+ * @param {string} [c] - Optional translation context.
+ * @returns {string} Translated string or original.
+ */
 function _(s, c) {
        var k = (c != null ? trimws(c) + '\u0001' : '') + trimws(s);
        return (window.TR && TR[sfh(k)]) || s;
 }
 
+/**
+ * Plural-aware translation lookup.
+ * @param {number} n - Quantity to evaluate plural form.
+ * @param {string} s - Singular string.
+ * @param {string} p - Plural string.
+ * @param {string} [c] - Optional context.
+ * @returns {string} Translated plural form or source string.
+ */
 function N_(n, s, p, c) {
        if (plural_function == null && window.TR)
                plural_function = new Function('n', (TR['00000000'] || 'plural=(n != 1);') + 'return +plural');
@@ -125,6 +171,12 @@ function N_(n, s, p, c) {
 }
 
 
+/**
+ * Register a dependency entry for a field.
+ * @param {HTMLElement|string} field - Field element or its id.
+ * @param {Object} dep - Dependency specification object.
+ * @param {number} index - Order index of the dependent node.
+ */
 function cbi_d_add(field, dep, index) {
        var obj = (typeof(field) === 'string') ? document.getElementById(field) : field;
        if (obj) {
@@ -149,6 +201,12 @@ function cbi_d_add(field, dep, index) {
        }
 }
 
+/**
+ * Check whether an input/select identified by target matches the given reference value.
+ * @param {string} target - Element id or name to query.
+ * @param {string} ref - Reference value to compare with.
+ * @returns {boolean} True if the current value matches ref.
+ */
 function cbi_d_checkvalue(target, ref) {
        var value = null,
            query = 'input[id="'+target+'"], input[name="'+target+'"], ' +
@@ -162,6 +220,11 @@ function cbi_d_checkvalue(target, ref) {
        return (((value !== null) ? value : "") == ref);
 }
 
+/**
+ * Evaluate a list of dependency descriptors and return whether any match.
+ * @param {Array<Object>} deps - Array of dependency objects to evaluate.
+ * @returns {boolean} True when dependencies indicate the element should be shown.
+ */
 function cbi_d_check(deps) {
        var reverse;
        var def = false;
@@ -186,6 +249,10 @@ function cbi_d_check(deps) {
        return def;
 }
 
+/**
+ * Update DOM nodes based on registered dependencies, showing or hiding
+ * nodes and restoring their order when dependency state changes.
+ */
 function cbi_d_update() {
        var state = false;
        for (var i=0; i<cbi_d.length; i++) {
@@ -227,6 +294,11 @@ function cbi_d_update() {
                parent.dispatchEvent(new CustomEvent('dependency-update', { bubbles: true }));
 }
 
+/**
+ * Initialize CBI widgets and wire up dependency and validation handlers.
+ * Walks the DOM looking for CBI-specific data attributes and replaces
+ * placeholders with interactive widgets.
+ */
 function cbi_init() {
        var nodes;
 
@@ -356,6 +428,12 @@ function cbi_init() {
        Promise.all(tasks).then(cbi_d_update);
 }
 
+/**
+ * Run all validators associated with a form and optionally show an error.
+ * @param {HTMLFormElement} form - Form element containing validators.
+ * @param {string} [errmsg] - Message to show when validation fails.
+ * @returns {boolean} True when form is valid.
+ */
 function cbi_validate_form(form, errmsg)
 {
        /* if triggered by a section removal or addition, don't validate */
@@ -376,12 +454,21 @@ function cbi_validate_form(form, errmsg)
        return true;
 }
 
+/**
+ * Enable/disable a named-section add button depending on input value.
+ * @param {HTMLInputElement} input - Input that contains the new section name.
+ */
 function cbi_validate_named_section_add(input)
 {
        var button = input.parentNode.parentNode.querySelector('.cbi-button-add');
        button.disabled = input.value === '';
 }
 
+/**
+ * Trigger a delayed form validation (used to allow UI state to settle).
+ * @param {HTMLFormElement} form - Form to validate after a short delay.
+ * @returns {boolean} Always returns true.
+ */
 function cbi_validate_reset(form)
 {
        window.setTimeout(
@@ -391,6 +478,12 @@ function cbi_validate_reset(form)
        return true;
 }
 
+/**
+ * Attach a validator to a field and wire validation events.
+ * @param {HTMLElement|string} cbid - Element or element id to validate.
+ * @param {boolean} optional - Whether an empty value is allowed.
+ * @param {string} type - Validator type expression (passed to L.validation).
+ */
 function cbi_validate_field(cbid, optional, type)
 {
        var field = isElem(cbid) ? cbid : document.getElementById(cbid);
@@ -425,6 +518,13 @@ function cbi_validate_field(cbid, optional, type)
        }
 }
 
+/**
+ * Move a table row up or down within a section and update the storage field.
+ * @param {HTMLElement} elem - Element inside the row that triggers the swap.
+ * @param {boolean} up - If true, move the row up; otherwise move down.
+ * @param {string} store - ID of the hidden input used to store the order.
+ * @returns {boolean} Always returns false to cancel default action.
+ */
 function cbi_row_swap(elem, up, store)
 {
        var tr = findParent(elem.parentNode, '.cbi-section-table-row');
@@ -478,6 +578,10 @@ function cbi_row_swap(elem, up, store)
        return false;
 }
 
+/**
+ * Mark the last visible value container child with class `cbi-value-last`.
+ * @param {HTMLElement} container - Parent container element.
+ */
 function cbi_tag_last(container)
 {
        var last;
@@ -494,6 +598,14 @@ function cbi_tag_last(container)
                last.classList.add('cbi-value-last');
 }
 
+/**
+ * Submit a form, optionally adding a hidden input to pass a name/value pair.
+ * @param {HTMLElement} elem - Element inside the form or an element with a form.
+ * @param {string} [name] - Name of hidden input to include, if any.
+ * @param {string} [value] - Value for the hidden input (defaults to '1').
+ * @param {string} [action] - Optional form action URL override.
+ * @returns {boolean} True on successful submit, false when no form found.
+ */
 function cbi_submit(elem, name, value, action)
 {
        var form = elem.form || findParent(elem, 'form');
@@ -516,6 +628,17 @@ function cbi_submit(elem, name, value, action)
        return true;
 }
 
+/**
+ * @external String
+ */
+
+/**
+ * Format a string using positional arguments.
+ * @function format
+ * @memberof external:String.prototype
+ * @param {...string} args
+ * @returns {string}
+ */
 String.prototype.format = function()
 {
        if (!RegExp)
@@ -524,6 +647,14 @@ String.prototype.format = function()
        var html_esc = [/&/g, '&#38;', /"/g, '&#34;', /'/g, '&#39;', /</g, '&#60;', />/g, '&#62;'];
        var quot_esc = [/"/g, '&#34;', /'/g, '&#39;'];
 
+       /**
+        * Escape a string.
+        * @private
+        * @function esc
+        * @param {string} s
+        * @param {string} r
+        * @returns {string}
+        */
        function esc(s, r) {
                var t = typeof(s);
 
@@ -693,11 +824,25 @@ String.prototype.format = function()
        return out + str;
 }
 
+/**
+ * Format a string using positional arguments.
+ * @function nobr
+ * @memberof external:String.prototype
+ * @param {...string} args
+ * @returns {string}
+ */
 String.prototype.nobr = function()
 {
        return this.replace(/[\s\n]+/g, '&#160;');
 }
 
+/**
+ * Format a string using positional arguments.
+ * @function format
+ * @memberof external:String
+ * @param {...string} args
+ * @returns {string}
+ */
 String.format = function()
 {
        var a = [ ];
@@ -708,6 +853,13 @@ String.format = function()
        return ''.format.apply(arguments[0], a);
 }
 
+/**
+ * Format a string using positional arguments.
+ * @function nobr
+ * @memberof external:String
+ * @param {...string} args
+ * @returns {string}
+ */
 String.nobr = function()
 {
        var a = [ ];
@@ -736,12 +888,48 @@ if (!window.requestAnimationFrame) {
 }
 
 
+/**
+ * Return the element for input which may be an element or an id.
+ * @param {Element|string} e - Element or id.
+ * @returns {HTMLElement|null}
+ */
 function isElem(e) { return L.dom.elem(e) }
+
+/**
+ * Parse an HTML string into a DOM element.
+ * @param {string} s - HTML string.
+ * @returns {HTMLElement} Parsed DOM element.
+ */
 function toElem(s) { return L.dom.parse(s) }
+
+/**
+ * Test whether node matches a CSS selector.
+ * @param {Node} node - Node to test.
+ * @param {string} selector - CSS selector.
+ * @returns {boolean}
+ */
 function matchesElem(node, selector) { return L.dom.matches(node, selector) }
+
+/**
+ * Find the parent matching selector from node upwards.
+ * @param {Node} node - Starting node.
+ * @param {string} selector - CSS selector to match ancestor.
+ * @returns {HTMLElement|null}
+ */
 function findParent(node, selector) { return L.dom.parent(node, selector) }
+
+/**
+ * Create DOM elements using {@link L.dom.create} helper (convenience wrapper).
+ * @returns {HTMLElement}
+ */
 function E() { return L.dom.create.apply(L.dom, arguments) }
 
+/**
+ * Initialize a dropdown element into an {@link L.ui.Dropdown} instance and bind it.
+ * If already bound, this is a no-op.
+ * @param {HTMLElement} sb - The select element to convert.
+ * @returns {L.ui.Dropdown|undefined} Dropdown instance or undefined when already bound.
+ */
 function cbi_dropdown_init(sb) {
        if (sb && L.dom.findClassInstance(sb) instanceof L.ui.Dropdown)
                return;
@@ -750,6 +938,12 @@ function cbi_dropdown_init(sb) {
        return dl.bind(sb);
 }
 
+/**
+ * Update or initialize a table UI widget with new data.
+ * @param {HTMLElement|string} table - Table element or selector.
+ * @param {...Node[]} data - Data to update the table with.
+ * @param {string} [placeholder] - Placeholder text when empty.
+ */
 function cbi_update_table(table, data, placeholder) {
        var target = isElem(table) ? table : document.querySelector(table);
 
@@ -766,11 +960,23 @@ function cbi_update_table(table, data, placeholder) {
        t.update(data, placeholder);
 }
 
+/**
+ * Show a modal dialog with the given title and children content.
+ * @deprecated
+ * @param {string} title - Title of the modal.
+ * @param {HTMLElement|Array<HTMLElement>} children - Content of the modal.
+ * @returns {Promise} Promise that resolves when modal shown or when closed depending on L.showModal.
+ */
 function showModal(title, children)
 {
        return L.showModal(title, children);
 }
 
+/**
+ * Hide any currently shown modal dialog.
+ * @deprecated
+ * @returns {*} Return value forwarded from L.hideModal.
+ */
 function hideModal()
 {
        return L.hideModal();
index a682af46d05bf4239b2d4de32f5fee5c5328012d..4a10e22bc233c9842fe20c5ce6e3b3c72f0cd853 100644 (file)
@@ -4,10 +4,29 @@
 'require tools.prng as random';
 
 
+/**
+ * @namespace LuCI
+ */
+
+/**
+ * @namespace LuCI.firewall
+ * @memberof LuCI
+ */
+
+/**
+ * Load the firewall configuration.
+ * @returns {Promise}
+ */
 function initFirewallState() {
        return L.resolveDefault(uci.load('firewall'));
 }
 
+/**
+ * Parse an enum value.
+ * @param {?string} s 
+ * @param {Array<string>} values
+ * @returns {string}
+ */
 function parseEnum(s, values) {
        if (s == null)
                return null;
@@ -24,6 +43,12 @@ function parseEnum(s, values) {
        return null;
 }
 
+/**
+ * Parse a policy value, or defaultValue if not found.
+ * @param {?string} s 
+ * @param {Array<string>} [defaultValue=['DROP', 'REJECT', 'ACCEPT']]
+ * @returns {?string}
+ */
 function parsePolicy(s, defaultValue) {
        return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue);
 }
@@ -31,6 +56,11 @@ function parsePolicy(s, defaultValue) {
 
 var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule;
 
+/**
+ * Look up a firewall zone.
+ * @param {?string} name
+ * @returns {?Zone}
+ */
 function lookupZone(name) {
        var z = uci.get('firewall', name);
 
@@ -49,6 +79,11 @@ function lookupZone(name) {
        return null;
 }
 
+/**
+ * Generate a colour for a name.
+ * @param {?string} forName
+ * @returns {string}
+ */
 function getColorForName(forName) {
        if (forName == null)
                return '#eeeeee';
index e9e71f8371e73efc5489ebd74242a595d36bdc8a..cb699e7cd8551e3cb2bf27131dcdb40418afd379 100644 (file)
@@ -190,6 +190,11 @@ const CBIJSONConfig = baseclass.extend({
        }
 });
 
+/**
+ * @namespace LuCI.form
+ * @memberof LuCI
+ */
+
 /**
  * @class AbstractElement
  * @memberof LuCI.form
@@ -256,7 +261,11 @@ const CBIAbstractElement = baseclass.extend(/** @lends LuCI.form.AbstractElement
                L.error('InternalError', 'Not implemented');
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {...*} args
+        * @returns {Promise}
+        */
        loadChildren(...args) /* ... */{
                const tasks = [];
 
@@ -268,7 +277,12 @@ const CBIAbstractElement = baseclass.extend(/** @lends LuCI.form.AbstractElement
                return Promise.all(tasks);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} tab_name
+        * @param {...*} args
+        * @returns {Promise}
+        */
        renderChildren(tab_name, ...args) {
                const tasks = [];
                let index = 0;
@@ -322,10 +336,10 @@ const CBIAbstractElement = baseclass.extend(/** @lends LuCI.form.AbstractElement
         *
         * In all other cases, `null` is returned.
         *
-        * @param {string} property
+        * @param {string} attr (property)
         * The name of the element property to use.
         *
-        * @param {...*} fmt_args
+        * @param {...string} args (fmt_args)
         * Extra values to format the title string with.
         *
         * @returns {string|null}
@@ -351,12 +365,10 @@ const CBIAbstractElement = baseclass.extend(/** @lends LuCI.form.AbstractElement
 });
 
 /**
- * @constructor Map
+ * @class Map
  * @memberof LuCI.form
  * @augments LuCI.form.AbstractElement
- *
  * @classdesc
- *
  * The `Map` class represents one complete form. A form usually maps one UCI
  * configuration file and is divided into multiple sections containing multiple
  * fields each.
@@ -397,8 +409,8 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
         * uci configuration upon loading and mark the form readonly if no write
         * permissions are granted.
         *
-        * @name LuCI.form.Map.prototype#readonly
-        * @type boolean
+        * @memberof LuCI.form.Map.prototype
+        * @member {boolean} readonly
         */
 
        /**
@@ -415,6 +427,8 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
         * nodes while `map.findElements('type', 'text')` would find any DOM node
         * with a `type="text"` attribute.
         *
+        * @param {...*} args argument array
+        *
         * @param {string} selector_or_attrname
         * If invoked with only one parameter, this argument is a
         * `querySelectorAll()` compatible selector expression. If invoked with
@@ -458,6 +472,8 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
         * node while `map.findElement('type', 'text')` would find the first DOM
         * node with a `type="text"` attribute.
         *
+        * @param {...*} args argument array
+        *
         * @param {string} selector_or_attrname
         * If invoked with only one parameter, this argument is a `querySelector()`
         * compatible selector expression. If invoked with two parameters, this
@@ -510,13 +526,13 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
         * to present configuration sections in different ways. Refer to the
         * documentation of the different section classes for details.
         *
-        * @param {LuCI.form.AbstractSection} sectionclass
+        * @param {LuCI.form.AbstractSection} cbiClass (sectionclass)
         * The section class to use for rendering the configuration section.
         * Note that this value must be the class itself, not a class instance
         * obtained from calling `new`. It must also be a class derived from
-        * `LuCI.form.AbstractSection`.
+        * {@link LuCI.form.AbstractSection AbstractSection}.
         *
-        * @param {...string} classargs
+        * @param {...string} args (classargs)
         * Additional arguments which are passed as-is to the constructor of the
         * given section class. Refer to the class specific constructor
         * documentation for details.
@@ -526,7 +542,7 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
         */
        section(cbiClass, ...args) {
                if (!CBIAbstractSection.isSubclass(cbiClass))
-                       L.error('TypeError', 'Class must be a descendent of CBIAbstractSection');
+                       L.error('TypeError', 'Class must be a descendant of CBIAbstractSection');
 
                const obj = cbiClass.instantiate([this, ...args]);
                this.append(obj);
@@ -592,7 +608,7 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
         * This function parses the current form, saves the resulting UCI changes,
         * reloads the UCI configuration data and redraws the form elements.
         *
-        * @param {function} [cb]
+        * @param {function()} [cb]
         * An optional callback function that is invoked after the form is parsed
         * but before the changed UCI data is saved. This is useful to perform
         * additional data manipulation steps before saving the changes.
@@ -651,7 +667,10 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
                return this.load().then(this.renderContents.bind(this));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @returns {Promise}
+        */
        renderContents() {
                const mapEl = (this.root ??= E('div', {
                        'id': 'cbi-%s'.format(this.config),
@@ -733,7 +752,11 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
                return (inst instanceof CBIAbstractValue) ? [ inst, sid ] : null;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {number} n
+        */
        checkDepends(ev, n) {
                let changed = false;
 
@@ -747,7 +770,13 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
                ui.tabs.updateTabs(ev, this.root);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string[]} depends
+        * @param {string} config_name
+        * @param {string} section_id
+        * @returns {boolean}
+        */
        isDependencySatisfied(depends, config_name, section_id) {
                let def = false;
 
@@ -788,7 +817,7 @@ const CBIMap = CBIAbstractElement.extend(/** @lends LuCI.form.Map.prototype */ {
 });
 
 /**
- * @constructor JSONMap
+ * @class JSONMap
  * @memberof LuCI.form
  * @augments LuCI.form.Map
  *
@@ -832,7 +861,7 @@ const CBIJSONMap = CBIMap.extend(/** @lends LuCI.form.JSONMap.prototype */ {
  * @classdesc
  *
  * The `AbstractSection` class serves as an abstract base for the different form
- * section styles implemented by `LuCI.form`. It provides the common logic for
+ * section styles implemented by {@link LuCI.form}. It provides the common logic for
  * enumerating underlying configuration section instances, for registering
  * form options and for handling tabs in order to segment child options.
  *
@@ -859,8 +888,9 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
         *
         * If this section is not nested, the property is `null`.
         *
-        * @name LuCI.form.AbstractSection.prototype#parentoption
-        * @type LuCI.form.AbstractValue
+        * @memberof LuCI.form.AbstractSection.prototype
+        * @member parentoption
+        * @type {LuCI.form.AbstractValue}
         * @readonly
         */
 
@@ -887,8 +917,8 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
         * the form section element.
         *
         * The default implementation always returns `true`. User code or
-        * classes extending `AbstractSection` may override this function with
-        * custom implementations.
+        * classes extending {@link LuCI.form.AbstractSection AbstractSection} may
+        * override this function with custom implementations.
         *
         * @abstract
         * @param {string} section_id
@@ -1012,16 +1042,18 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
         * The option class to use for rendering the configuration option. Note
         * that this value must be the class itself, not a class instance obtained
         * from calling `new`. It must also be a class derived from
-        * [LuCI.form.AbstractSection]{@link LuCI.form.AbstractSection}.
+        * {@link LuCI.form.AbstractSection AbstractSection}.
         *
-        * @param {...*} classargs
+        * @param {object} cbiClass (classargs)
         * Additional arguments which are passed as-is to the constructor of the
         * given option class. Refer to the class specific constructor
         * documentation for details.
         *
+        * @param {...*} args argument array
+        *
         * @throws {TypeError}
         * Throws a `TypeError` exception in case the passed class value is not a
-        * descendant of `AbstractValue`.
+        * descendant of {@link LuCI.form.AbstractValue AbstractValue}.
         *
         * @returns {LuCI.form.AbstractValue}
         * Returns the instantiated option class instance.
@@ -1045,9 +1077,9 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
         * The option class to use for rendering the configuration option. Note
         * that this value must be the class itself, not a class instance obtained
         * from calling `new`. It must also be a class derived from
-        * [LuCI.form.AbstractSection]{@link LuCI.form.AbstractSection}.
+        * {@link LuCI.form.AbstractSection AbstractSection}.
         *
-        * @param {...*} classargs
+        * @param {...*} args (classargs)
         * Additional arguments which are passed as-is to the constructor of the
         * given option class. Refer to the class specific constructor
         * documentation for details.
@@ -1058,7 +1090,7 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
         *
         * @throws {TypeError}
         * Throws a `TypeError` exception in case the passed class value is not a
-        * descendant of `AbstractValue`.
+        * descendant of {@link LuCI.form.AbstractValue AbstractValue}.
         *
         * @returns {LuCI.form.AbstractValue}
         * Returns the instantiated option class instance.
@@ -1206,7 +1238,11 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
                return rv;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {Promise}
+        */
        renderUCISection(section_id) {
                const renderTasks = [];
 
@@ -1220,7 +1256,12 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
                        .then(this.renderTabContainers.bind(this, section_id));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {Node[]} nodes
+        * @returns {Node[]}
+        */
        renderTabContainers(section_id, nodes) {
                const config_name = this.uciconfig ?? this.map.config;
                const containerEls = E([]);
@@ -1246,7 +1287,12 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
                return containerEls;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} tab_name
+        * @param {string} section_id
+        * @returns {Node[]}
+        */
        renderOptions(tab_name, section_id) {
                const in_table = (this instanceof CBITableSection);
                return this.renderChildren(tab_name, section_id, in_table).then((nodes) =>  {
@@ -1257,7 +1303,12 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
                });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {number} n
+        * @returns {boolean}
+        */
        checkDepends(ev, n) {
                let changed = false;
                const sids = this.cfgsections();
@@ -1282,7 +1333,13 @@ const CBIAbstractSection = CBIAbstractElement.extend(/** @lends LuCI.form.Abstra
        }
 });
 
-
+/**
+ * Determines equality of two provided parameters. Can be arrays or objects.
+ * @function
+ * @param {*} x
+ * @param {*} y
+ * @returns {boolean}
+ */
 function isEqual(x, y) {
        if (typeof(y) == 'object' && y instanceof RegExp)
                return (x == null) ? false : y.test(x);
@@ -1321,6 +1378,13 @@ function isEqual(x, y) {
        return true;
 };
 
+/**
+ * Determines containment of two provided parameters. Can be arrays or objects.
+ * @function
+ * @param {*} x
+ * @param {*} y
+ * @returns {boolean}
+ */
 function isContained(x, y) {
        if (Array.isArray(x)) {
                for (let i = 0; i < x.length; i++)
@@ -1375,8 +1439,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * the form when the option element is disabled due to unsatisfied
         * dependency constraints.
         *
-        * @name LuCI.form.AbstractValue.prototype#rmempty
-        * @type boolean
+        * @member {boolean} rmempty
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default true
         */
 
@@ -1385,8 +1449,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * otherwise the option element is marked invalid when no value is entered
         * or selected by the user.
         *
-        * @name LuCI.form.AbstractValue.prototype#optional
-        * @type boolean
+        * @member {boolean} optional
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default false
         */
 
@@ -1396,16 +1460,16 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * is to remove the values of all options whose dependencies are not
         * fulfilled.
         *
-        * @name LuCI.form.AbstractValue.prototype#retain
-        * @type boolean
+        * @member {boolean} retain
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default false
         */
 
        /**
         * Sets a default value to use when the underlying UCI option is not set.
         *
-        * @name LuCI.form.AbstractValue.prototype#default
-        * @type *
+        * @member {*} default
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1416,8 +1480,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * If the user entered input does not match the datatype validation, the
         * option element is marked as invalid.
         *
-        * @name LuCI.form.AbstractValue.prototype#datatype
-        * @type string
+        * @member {string} datatype
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1430,8 +1494,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * If the user entered input does not pass the validation function, the
         * option element is marked as invalid.
         *
-        * @name LuCI.form.AbstractValue.prototype#validate
-        * @type function
+        * @member {function()} validate
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1443,8 +1507,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         *
         * The default of null means inherit from the parent form.
         *
-        * @name LuCI.form.AbstractValue.prototype#uciconfig
-        * @type string
+        * @member {string} uciconfig
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1456,8 +1520,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         *
         * The default of null means inherit from the parent section.
         *
-        * @name LuCI.form.AbstractValue.prototype#ucisection
-        * @type string
+        * @member {string} ucisection
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1470,15 +1534,15 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         *
         * The default of null means use the option element name.
         *
-        * @name LuCI.form.AbstractValue.prototype#ucioption
-        * @type string
+        * @member {string} ucioption
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
        /**
         * Mark the grid section option element as editable.
         *
-        * Options which are displayed in the table portion of a `GridSection`
+        * Options which are displayed in the table portion of a {@link LuCI.form.GridSection GridSection}
         * instance are rendered as readonly text by default. By setting the
         * `editable` property of a child option element to `true`, that element
         * is rendered as a full input widget within its cell instead of a text only
@@ -1487,8 +1551,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * This property has no effect on options that are not children of grid
         * section elements.
         *
-        * @name LuCI.form.AbstractValue.prototype#editable
-        * @type boolean
+        * @member {boolean} editable
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default false
         */
 
@@ -1504,8 +1568,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * This property has no effect on options that are not children of grid
         * section elements.
         *
-        * @name LuCI.form.AbstractValue.prototype#modalonly
-        * @type boolean
+        * @member {boolean} modalonly
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1517,8 +1581,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * meaning its contents cannot be changed and the widget cannot be
         * interacted with.
         *
-        * @name LuCI.form.AbstractValue.prototype#readonly
-        * @type boolean
+        * @member {boolean} readonly
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default false
         */
 
@@ -1533,8 +1597,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * This property has no effect on options that are not children of grid or
         * table section elements.
         *
-        * @name LuCI.form.AbstractValue.prototype#width
-        * @type number|string
+        * @member {number|string} width
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1548,8 +1612,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * first and the underlying configuration section ID as well as the input
         * value as second and third argument respectively.
         *
-        * @name LuCI.form.AbstractValue.prototype#onchange
-        * @type function
+        * @member {function()} onchange
+        * @memberof LuCI.form.AbstractValue.prototype
         * @default null
         */
 
@@ -1655,7 +1719,12 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
                this.deps.push(deps);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {string[]} deplist
+        * @returns {string[]}
+        */
        transformDepList(section_id, deplist) {
                const list = deplist ?? this.deps;
                const deps = [];
@@ -1691,7 +1760,10 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
                return deps;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @returns {object} choices
+        */
        transformChoices() {
                if (!Array.isArray(this.keylist) || this.keylist.length == 0)
                        return null;
@@ -1704,7 +1776,11 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
                return choices;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {boolean}
+        */
        checkDepends(section_id) {
                const config_name = this.uciconfig ?? this.section.uciconfig ?? this.map.config;
                const active = this.map.isDependencySatisfied(this.deps, config_name, section_id);
@@ -1715,7 +1791,10 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
                return active;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        */
        updateDefaultValue(section_id) {
                if (!L.isObject(this.defaults))
                        return;
@@ -1777,8 +1856,8 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         *
         * The default implementation of this method reads and returns the
         * underlying UCI option value (or the related JavaScript property for
-        * `JSONMap` instances). It may be overridden by user code to load data
-        * from non-standard sources.
+        * {@link LuCI.form.JSONMap JSONMap} instances). It may be overridden by
+        * user code to load data from non-standard sources.
         *
         * @param {string} section_id
         * The configuration section ID
@@ -1810,7 +1889,7 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * @throws {TypeError}
         * Throws a `TypeError` exception when no `section_id` was specified.
         *
-        * @return {LuCI.ui.AbstractElement|null}
+        * @returns {LuCI.ui.AbstractElement|null}
         * Returns the `LuCI.ui` element instance or `null` in case the form
         * option implementation does not use `LuCI.ui` widgets.
         */
@@ -1831,6 +1910,9 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * @param {string} section_id
         * The configuration section ID
         *
+        * @param {string} set_value
+        * The value to assign
+        *
         * @throws {TypeError}
         * Throws a `TypeError` exception when no `section_id` was specified.
         *
@@ -1936,7 +2018,7 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         * @param {string} section_id
         * The configuration section ID
         *
-        * @returns {function}
+        * @returns {function()}
         * Returns a bound validator function suitable for passing to UI widgets.
         * If this.validate is an array, returns a wrapper that calls each validator
         * serially. Otherwise returns the bound validate method.
@@ -2006,7 +2088,12 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
                return (field != null && !field.classList.contains('hidden'));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {boolean} active
+        * @returns {boolean}
+        */
        setActive(section_id, active) {
                const field = this.map.findElement('data-field', this.cbid(section_id));
 
@@ -2022,7 +2109,11 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
                return false;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {boolean}
+        */
        triggerValidation(section_id) {
                const elem = this.getUIElement(section_id);
                return elem ? elem.triggerValidation() : true;
@@ -2089,15 +2180,17 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         *
         * The default implementation simply sets the given input value in the
         * UCI configuration (or the associated JavaScript object property in
-        * case of `JSONMap` forms). It may be overridden by user code to
-        * implement alternative save logic, e.g. to transform the input value
-        * before it is written.
+        * case of {@link LuCI.form.JSONMap JSONMap} forms). It may be overridden
+        * by user code to implement alternative save logic, e.g. to transform the
+        * input value before it is written.
         *
         * @param {string} section_id
         * The configuration section ID
         *
         * @param {string|string[]}     formvalue
         * The input value to write.
+        *
+        * @returns {null}
         */
        write(section_id, formvalue) {
                return this.map.data.set(
@@ -2116,8 +2209,9 @@ const CBIAbstractValue = CBIAbstractElement.extend(/** @lends LuCI.form.Abstract
         *
         * The default implementation simply removes the associated option from the
         * UCI configuration (or the associated JavaScript object property in
-        * case of `JSONMap` forms). It may be overridden by user code to
-        * implement alternative removal logic, e.g. to retain the original value.
+        * case of {@link LuCI.form.JSONMap JSONMap} forms). It may be overridden
+        * by  user code to implement alternative removal logic, e.g. to retain the
+        * original value.
         *
         * @param {string} section_id
         * The configuration section ID
@@ -2189,8 +2283,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * section widget, otherwise only pre-existing sections may be edited.
         * The default is `false`.
         *
-        * @name LuCI.form.TypedSection.prototype#addremove
-        * @type boolean
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member addremove
+        * @type {boolean}
         * @default false
         */
 
@@ -2200,8 +2295,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * not be rendered in the UI. The default is false, meaning that the
         * title is rendered.
         *
-        * @name LuCI.form.TypedSection.prototype#hidetitle
-        * @type boolean
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member hidetitle
+        * @type {boolean}
         * @default false
         */
 
@@ -2211,8 +2307,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * rendered without a title element and that no name is required when adding
         * new sections. The default is `false`.
         *
-        * @name LuCI.form.TypedSection.prototype#anonymous
-        * @type boolean
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member anonymous
+        * @type {boolean}
         * @default false
         */
 
@@ -2222,8 +2319,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * at the top of the form section element, allowing the user to switch
         * among instances. The default is `false`.
         *
-        * @name LuCI.form.TypedSection.prototype#tabbed
-        * @type boolean
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member tabbed
+        * @type {boolean}
         * @default false
         */
 
@@ -2234,8 +2332,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * is used as a caption, after converting it to a string. If this property
         * is not set, the default is `Add`.
         *
-        * @name LuCI.form.TypedSection.prototype#addbtntitle
-        * @type string|function
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member addbtntitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -2246,8 +2345,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * is used as a caption, after converting it to a string. If this property
         * is not set, the default is `Delete`.
         *
-        * @name LuCI.form.TypedSection.prototype#delbtntitle
-        * @type string|function
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member delbtntitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -2257,8 +2357,9 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
         * By setting this property, a deviating configuration may be specified.
         * The default of `null` means inherit from the parent form.
         *
-        * @name LuCI.form.TypedSection.prototype#uciconfig
-        * @type string
+        * @memberof LuCI.form.TypedSection.prototype
+        * @member uciconfig
+        * @type {string}
         * @default null
         */
 
@@ -2269,7 +2370,12 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
                        .filter(L.bind(this.filter, this));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {string} name
+        * @returns {null}
+        */
        handleAdd(ev, name) {
                const config_name = this.uciconfig ?? this.map.config;
 
@@ -2277,7 +2383,12 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
                return this.map.save(null, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleRemove(section_id, ev) {
                const config_name = this.uciconfig ?? this.map.config;
 
@@ -2285,7 +2396,11 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
                return this.map.save(null, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} extra_class
+        * @returns {Node}
+        */
        renderSectionAdd(extra_class) {
                if (!this.addremove)
                        return E([]);
@@ -2345,12 +2460,20 @@ const CBITypedSection = CBIAbstractSection.extend(/** @lends LuCI.form.TypedSect
                return createEl;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @returns {Node}
+        */
        renderSectionPlaceholder() {
                return E('em', _('This section contains no values yet'));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string[]} cfgsections
+        * @param {Node[]} nodes
+        * @returns {Node[]}
+        */
        renderContents(cfgsections, nodes) {
                const section_id = null;
                const config_name = this.uciconfig ?? this.map.config;
@@ -2457,8 +2580,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * If this property is not set, the default is the name of the underlying
         * UCI configuration section.
         *
-        * @name LuCI.form.TableSection.prototype#sectiontitle
-        * @type string|function
+        * @memberof LuCI.form.TableSection.prototype
+        * @member sectiontitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -2472,8 +2596,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * If this property is not set, the default is the name of the underlying
         * UCI configuration section.
         *
-        * @name LuCI.form.TableSection.prototype#modaltitle
-        * @type string|function
+        * @memberof LuCI.form.TableSection.prototype
+        * @member modaltitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -2481,8 +2606,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * Set a custom text for the actions column header row when actions buttons
         * are present.
         *
-        * @name LuCI.form.TableSection.prototype#actionstitle
-        * @type string|function
+        * @memberof LuCI.form.TableSection.prototype
+        * @member actionstitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -2495,8 +2621,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * opening a modal dialog presenting all options elements in `NamedSection`
         * style when clicked.
         *
-        * @name LuCI.form.TableSection.prototype#max_cols
-        * @type number
+        * @memberof LuCI.form.TableSection.prototype
+        * @member max_cols
+        * @type {number}
         * @default null
         */
 
@@ -2505,8 +2632,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * classes are added to the table row elements. Not all LuCI themes
         * implement these row style classes. The default is `false`.
         *
-        * @name LuCI.form.TableSection.prototype#rowcolors
-        * @type boolean
+        * @memberof LuCI.form.TableSection.prototype
+        * @member rowcolors
+        * @type {boolean}
         * @default false
         */
 
@@ -2515,8 +2643,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * the user to clone section instances mapped by the section form element.
         * The default is `false`.
         *
-        * @name LuCI.form.TypedSection.prototype#cloneable
-        * @type boolean
+        * @memberof LuCI.form.TableSection.prototype
+        * @member cloneable
+        * @type {boolean}
         * @default false
         */
 
@@ -2527,8 +2656,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * is used as a caption, after converting it to a string. If this property
         * is not set, the default is `Clone`.
         *
-        * @name LuCI.form.TypedSection.prototype#clonebtntitle
-        * @type string|function
+        * @memberof LuCI.form.TableSection.prototype
+        * @member clonebtntitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -2543,8 +2673,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * handler on the rendered edit button, receiving the section instance
         * name as the first and the DOM click event as the second argument.
         *
-        * @name LuCI.form.TableSection.prototype#extedit
-        * @type string|function
+        * @memberof LuCI.form.TableSection.prototype
+        * @member extedit
+        * @type {string|function()}
         * @default null
         */
 
@@ -2565,8 +2696,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * column entries on narrow displays which might fold the columns over 
         * multiple lines.
         *
-        * @name LuCI.form.TableSection.prototype#filterrow
-        * @type boolean
+        * @memberof LuCI.form.TableSection.prototype
+        * @member filterrow
+        * @type {boolean}
         * @default null
         */
 
@@ -2583,9 +2715,10 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         *
         * The default implementation returns an empty node.
         *
-        * @name LuCI.form.TableSection.prototype#footer
-        * @type string[]|function
-        * @default E([])
+        * @memberof LuCI.form.TableSection.prototype
+        * @member footer
+        * @type {string[]|function()}
+        * @default `E([])`
         */
 
        /**
@@ -2593,8 +2726,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * the user to reorder the section instances mapped by the section form
         * element.
         *
-        * @name LuCI.form.TableSection.prototype#sortable
-        * @type boolean
+        * @memberof LuCI.form.TableSection.prototype
+        * @member sortable
+        * @type {boolean}
         * @default false
         */
 
@@ -2603,8 +2737,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * not be displayed. By default, the row of descriptions is automatically displayed
         * when at least one option has a description.
         *
-        * @name LuCI.form.TableSection.prototype#nodescriptions
-        * @type boolean
+        * @memberof LuCI.form.TableSection.prototype
+        * @member nodescriptions
+        * @type {boolean}
         * @default false
         */
 
@@ -2614,7 +2749,7 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * invoked.
         *
         * @override
-        * @throws Throws an exception when invoked.
+        * @throws {string} Throws an exception when invoked.
         */
        tab() {
                throw 'Tabs are not supported by TableSection';
@@ -2624,8 +2759,13 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
        /**
         * Clone the section_id, putting the clone immediately after if put_next
         * is true. Optionally supply a name for the new section_id.
+        *
+        * @private
+        * @param {string} section_id
+        * @param {boolean} put_next
+        * @param {string} name
+        * @returns {null}
         */
-       /** @private */
        handleClone(section_id, put_next, name) {
                let config_name = this.uciconfig || this.map.config;
 
@@ -2633,7 +2773,12 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return this.map.save(null, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string[]} cfgsections
+        * @param {Node[]} nodes
+        * @returns {Node}
+        */
        renderContents(cfgsections, nodes) {
                const section_id = null;
                const config_name = this.uciconfig ?? this.map.config;
@@ -2736,7 +2881,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return sectionEl;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {boolean} has_action
+        * @returns {Node[]}
+        */
        renderHeaderRows(has_action) {
                let has_titles = false;
                let has_descriptions = false;
@@ -2933,7 +3082,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return trEls;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {boolean} has_action
+        * @returns {Node}
+        */
        renderFooterRows(has_action) {
                if (this.footer == null)
                        return E([]);
@@ -2975,6 +3128,9 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * (e.g., due to filtering). Measures the widest actions cell and applies
         * a fixed width to header/filter/footer/action cells. Stores measured width
         * in dataset so filtering won't collapse the column if all rows are hidden.
+        *
+        * @private
+        * @param {Node} tableEl
         */
        stabilizeActionColumnWidth(tableEl) {
                if (!tableEl || !tableEl.querySelector) return;
@@ -3013,7 +3169,13 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {string} more_label
+        * @param {Node} trEl
+        * @returns {Node}
+        */
        renderRowActions(section_id, more_label, trEl) {
                const config_name = this.uciconfig ?? this.map.config;
 
@@ -3106,12 +3268,20 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return tdEl;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleDragInit(ev) {
                scope.dragState = { node: ev.target };
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {Node} trEl
+        * @returns {boolean}
+        */
        handleDragStart(ev, trEl) {
                // Only allow drag from the handle
                if (!ev.target || !ev.target.classList || !ev.target.classList.contains('drag-handle')) {
@@ -3125,7 +3295,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                ev.target.style.opacity = 0.4;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {boolean}
+        */
        handleDragOver(ev) {
                if (scope.dragState === null ) return;
                const n = scope.dragState.targetNode;
@@ -3146,20 +3320,32 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return false;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleDragEnter(ev) {
                if (scope.dragState === null ) return;
                scope.dragState.rect = ev.currentTarget.getBoundingClientRect();
                scope.dragState.targetNode = ev.currentTarget;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleDragLeave(ev) {
                ev.currentTarget.classList.remove('drag-over-above');
                ev.currentTarget.classList.remove('drag-over-below');
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {Node} trEl
+        * @returns {boolean}
+        */
        handleDragEnd(ev, trEl) {
                let n;
                if (trEl) {
@@ -3182,7 +3368,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                        });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {boolean|null}
+        */
        handleDrop(ev) {
                const s = scope.dragState;
                if (!s) return;
@@ -3211,7 +3401,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return false;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} node
+        * @returns {number[]}
+        */
        determineBackgroundColor(node) {
                let r = 255;
                let g = 255;
@@ -3242,7 +3436,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return [ r, g, b ];
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleTouchMove(ev) {
                if (!ev.target.classList.contains('drag-handle'))
                        return;
@@ -3328,7 +3526,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                        window.requestAnimationFrame(() => { htmlElem.scrollTop += 30 });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleTouchEnd(ev) {
                const rowElem = dom.parent(ev.target, '.tr');
                const htmlElem = document.querySelector('html');
@@ -3370,7 +3572,12 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                document.body.removeChild(dragHandle);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} modalMap
+        * @param {Event} ev
+        * @returns {Promise}
+        */
        handleModalCancel(modalMap, ev) {
                const prevNode = this.getPreviousModalMap();
                let resetTasks = Promise.resolve();
@@ -3405,7 +3612,12 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return resetTasks;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} modalMap
+        * @param {Event} ev
+        * @returns {Promise[]}
+        */
        handleModalSave(modalMap, ev) {
                const mapNode = this.getActiveModalMap();
                let activeMap = dom.findClassInstance(mapNode);
@@ -3423,7 +3635,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                        .catch(() => {});
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleSort(ev) {
                if (!ev.target.matches('th[data-sortable-row]'))
                        return;
@@ -3513,12 +3729,18 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
 
        },
 
-       /** @private */
+       /**
+        * @private
+        * @returns {Node[]}
+        */
        getActiveModalMap() {
                return document.querySelector('body.modal-overlay-active > #modal_overlay > .modal.cbi-modal > .cbi-map:not(.hidden)');
        },
 
-       /** @private */
+       /**
+        * @private
+        * @returns {Node[]|null}
+        */
        getPreviousModalMap() {
                const mapNode = this.getActiveModalMap();
                const prevNode = mapNode ? mapNode.previousElementSibling : null;
@@ -3526,7 +3748,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                return (prevNode && prevNode.matches('.cbi-map.hidden')) ? prevNode : null;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} src_section
+        * @param {string} dest_section
+        */
        cloneOptions(src_section, dest_section) {
                for (let i = 0; i < src_section.children.length; i++) {
                        const o1 = src_section.children[i];
@@ -3580,7 +3806,12 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {Event} ev
+        * @returns {Promise}
+        */
        renderMoreOptionsModal(section_id, ev) {
                const parent = this.map;
                const sref = parent.data.get(parent.config, section_id);
@@ -3751,7 +3982,12 @@ const CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.p
                CBIAbstractSection.prototype.tab.call(this, name, title, description);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {string} name
+        * @returns {Promise}
+        */
        handleAdd(ev, name) {
                const config_name = this.uciconfig ?? this.map.config;
                const section_id = this.map.data.add(config_name, this.sectiontype, name);
@@ -3763,7 +3999,11 @@ const CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.p
                return this.renderMoreOptionsModal(section_id);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {...*} args
+        * @returns {*}
+        */
        handleModalSave(...args) /* ... */{
                const mapNode = this.getPreviousModalMap();
                const prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
@@ -3771,7 +4011,13 @@ const CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.p
                return this.super('handleModalSave', args);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {*} modalMap
+        * @param {Event} ev
+        * @param {boolean} isSaving
+        * @returns {*}
+        */
        handleModalCancel(modalMap, ev, isSaving) {
                const config_name = this.uciconfig ?? this.map.config;
                const mapNode = this.getPreviousModalMap();
@@ -3785,12 +4031,22 @@ const CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.p
                return this.super('handleModalCancel', arguments);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {*}
+        */
        renderUCISection(section_id) {
                return this.renderOptions(null, section_id);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} tab_name
+        * @param {string} section_id
+        * @param {string} in_table
+        * @returns {Promise[]}
+        */
        renderChildren(tab_name, section_id, in_table) {
                const tasks = [];
                let index = 0;
@@ -3808,7 +4064,12 @@ const CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.p
                return Promise.all(tasks);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {string} opt
+        * @returns {Node}
+        */
        renderTextValue(section_id, opt) {
                const title = this.stripTags(opt.title).trim();
                const descr = this.stripTags(opt.description).trim();
@@ -3823,12 +4084,20 @@ const CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.p
                }, (value != null) ? value : E('em', _('none')));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {Node[]}
+        */
        renderHeaderRows(section_id) {
                return this.super('renderHeaderRows', [ true ]);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {Node[]}
+        */
        renderRowActions(section_id) {
                return this.super('renderRowActions', [ section_id, _('Edit') ]);
        },
@@ -3895,8 +4164,9 @@ const CBINamedSection = CBIAbstractSection.extend(/** @lends LuCI.form.NamedSect
         * configuration instance from the form section widget, otherwise only a
         * pre-existing section may be edited. The default is `false`.
         *
-        * @name LuCI.form.NamedSection.prototype#addremove
-        * @type boolean
+        * @memberof LuCI.form.NamedSection
+        * @member addremove
+        * @type {boolean}
         * @default false
         */
 
@@ -3906,8 +4176,9 @@ const CBINamedSection = CBIAbstractSection.extend(/** @lends LuCI.form.NamedSect
         * not be rendered in the UI. The default is false, meaning that the
         * title is rendered.
         *
-        * @name LuCI.form.NamedSection.prototype#hidetitle
-        * @type boolean
+        * @memberof LuCI.form.NamedSection
+        * @member hidetitle
+        * @type {boolean}
         * @default false
         */
 
@@ -3917,8 +4188,9 @@ const CBINamedSection = CBIAbstractSection.extend(/** @lends LuCI.form.NamedSect
         * By setting this property, a deviating configuration may be specified.
         * The default of `null` means inherit from the parent form.
         *
-        * @name LuCI.form.NamedSection.prototype#uciconfig
-        * @type string
+        * @memberof LuCI.form.NamedSection
+        * @member uciconfig
+        * @type {string}
         * @default null
         */
 
@@ -3934,7 +4206,11 @@ const CBINamedSection = CBIAbstractSection.extend(/** @lends LuCI.form.NamedSect
                return [ this.section ];
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleAdd(ev) {
                const section_id = this.section;
                const config_name = this.uciconfig ?? this.map.config;
@@ -3943,7 +4219,11 @@ const CBINamedSection = CBIAbstractSection.extend(/** @lends LuCI.form.NamedSect
                return this.map.save(null, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleRemove(ev) {
                const section_id = this.section;
                const config_name = this.uciconfig ?? this.map.config;
@@ -3952,7 +4232,11 @@ const CBINamedSection = CBIAbstractSection.extend(/** @lends LuCI.form.NamedSect
                return this.map.save(null, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string[]} data
+        * @returns {Node}
+        */
        renderContents(data) {
                const ucidata = data[0];
                const nodes = data[1];
@@ -4057,16 +4341,18 @@ const CBIValue = CBIAbstractValue.extend(/** @lends LuCI.form.Value.prototype */
         * If set to `true`, the field is rendered as a password input, otherwise
         * as a plain text input.
         *
-        * @name LuCI.form.Value.prototype#password
-        * @type boolean
+        * @memberof LuCI.form.Value.prototype
+        * @member password
+        * @type {boolean}
         * @default false
         */
 
        /**
         * Set a placeholder string to use when the input field is empty.
         *
-        * @name LuCI.form.Value.prototype#placeholder
-        * @type string
+        * @memberof LuCI.form.Value.prototype
+        * @member placeholder
+        * @type {string}
         * @default null
         */
 
@@ -4098,7 +4384,13 @@ const CBIValue = CBIAbstractValue.extend(/** @lends LuCI.form.Value.prototype */
                        .then(this.renderFrame.bind(this, section_id, in_table, option_index));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {object} state
+        * @param {Event} ev
+        * @returns {null}
+        */
        handleValueChange(section_id, state, ev) {
                if (typeof(this.onchange) != 'function')
                        return;
@@ -4112,7 +4404,14 @@ const CBIValue = CBIAbstractValue.extend(/** @lends LuCI.form.Value.prototype */
                this.onchange.call(this, ev, section_id, value);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {string} in_table
+        * @param {number} option_index
+        * @param {Node[]} nodes
+        * @returns {Node}
+        */
        renderFrame(section_id, in_table, option_index, nodes) {
                const config_name = this.uciconfig ?? this.section.uciconfig ?? this.map.config;
                const depend_list = this.transformDepList(section_id);
@@ -4195,7 +4494,13 @@ const CBIValue = CBIAbstractValue.extend(/** @lends LuCI.form.Value.prototype */
                return optionEl;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const value = (cfgvalue != null) ? cfgvalue : this.default;
                const choices = this.transformChoices();
@@ -4272,12 +4577,19 @@ const CBIDynamicList = CBIValue.extend(/** @lends LuCI.form.DynamicList.prototyp
         * Default is `null`. If `true`, the underlying form value will
         * not be checked for duplication.
         *
-        * @name LuCI.form.DynamicList.prototype#allowduplicates
-        * @type boolean
+        * @memberof LuCI.form.DynamicList.prototype
+        * @member allowduplicates
+        * @type {boolean}
         * @default null
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const value = (cfgvalue != null) ? cfgvalue : this.default;
                const choices = this.transformChoices();
@@ -4343,8 +4655,9 @@ const CBIListValue = CBIValue.extend(/** @lends LuCI.form.ListValue.prototype */
        /**
         * Set the size attribute of the underlying HTML select element.
         *
-        * @name LuCI.form.ListValue.prototype#size
-        * @type number
+        * @memberof LuCI.form.ListValue.prototype
+        * @member size
+        * @type {number}
         * @default null
         */
 
@@ -4355,8 +4668,9 @@ const CBIListValue = CBIValue.extend(/** @lends LuCI.form.ListValue.prototype */
         * select element is rendered, otherwise a collection of `radio`
         * elements is used.
         *
-        * @name LuCI.form.ListValue.prototype#widget
-        * @type string
+        * @memberof LuCI.form.ListValue.prototype
+        * @member widget
+        * @type {string}
         * @default select
         */
 
@@ -4366,12 +4680,19 @@ const CBIListValue = CBIValue.extend(/** @lends LuCI.form.ListValue.prototype */
         * May be one of `horizontal` or `vertical`. Only applies to non-select
         * widget types.
         *
-        * @name LuCI.form.ListValue.prototype#orientation
-        * @type string
+        * @memberof LuCI.form.ListValue.prototype
+        * @member orientation
+        * @type {string}
         * @default horizontal
         */
 
-        /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const choices = this.transformChoices();
                const widget = new ui.Select((cfgvalue != null) ? cfgvalue : this.default, choices, {
@@ -4423,7 +4744,7 @@ const CBIListValue = CBIValue.extend(/** @lends LuCI.form.ListValue.prototype */
  * @param {string} [description]
  * The description text of the option element.
  */
-const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.ListValue.prototype */ {
+const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.RichListValue.prototype */ {
        __name__: 'CBI.RichListValue',
 
        __init__() {
@@ -4439,16 +4760,18 @@ const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.ListValue.prot
         * May be one of `horizontal` or `vertical`. Only applies to non-select
         * widget types.
         *
-        * @name LuCI.form.RichListValue.prototype#orientation
-        * @type string
+        * @memberof LuCI.form.RichListValue.prototype
+        * @member orientation
+        * @type {string}
         * @default horizontal
         */
 
        /**
         * Set the size attribute of the underlying HTML select element.
         *
-        * @name LuCI.form.RichListValue.prototype#size
-        * @type number
+        * @memberof LuCI.form.RichListValue.prototype
+        * @member size
+        * @type {number}
         * @default null
         */
 
@@ -4459,12 +4782,19 @@ const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.ListValue.prot
         * select element is rendered, otherwise a collection of `radio`
         * elements is used.
         *
-        * @name LuCI.form.RichListValue.prototype#widget
-        * @type string
+        * @memberof LuCI.form.RichListValue.prototype
+        * @member widget
+        * @type {string}
         * @default select
         */
 
-        /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const choices = this.transformChoices();
                const widget = new ui.Dropdown((cfgvalue != null) ? cfgvalue : this.default, choices, {
@@ -4490,10 +4820,10 @@ const CBIRichListValue = CBIListValue.extend(/** @lends LuCI.form.ListValue.prot
         * which prompts the user to select a predefined choice, or to enter a
         * custom value.
         *
-        * @param {string} key
+        * @param {string} value
         * The choice value to add.
         *
-        * @param {Node|string} val
+        * @param {Node|string} title
         * The caption for the choice value. May be a DOM node, a document fragment
         * or a plain text string. If omitted, the `key` value is used as caption.
         * 
@@ -4558,23 +4888,26 @@ const CBIRangeSliderValue = CBIValue.extend(/** @lends LuCI.form.RangeSliderValu
 
        /**
         * Minimum value the slider can represent.
-        * @name LuCI.form.RangeSliderValue.prototype#min
-        * @type number
+        * @memberof LuCI.form.RangeSliderValue.prototype
+        * @member min
+        * @type {number}
         * @default 0
         */
 
        /**
         * Maximum value the slider can represent.
-        * @name LuCI.form.RangeSliderValue.prototype#max
-        * @type number
+        * @memberof LuCI.form.RangeSliderValue.prototype
+        * @member max
+        * @type {number}
         * @default 100
         */
 
        /**
         * Step size for each tick of the slider, or the special value "any" when
         * handling arbitrary precision floating point numbers.
-        * @name LuCI.form.RangeSliderValue.prototype#step
-        * @type string
+        * @memberof LuCI.form.RangeSliderValue.prototype
+        * @member step
+        * @type {string}
         * @default 1
         */
 
@@ -4582,8 +4915,9 @@ const CBIRangeSliderValue = CBIValue.extend(/** @lends LuCI.form.RangeSliderValu
         * Set the default value for the slider. The default value is elided during
         * save: meaning, a currently chosen value which matches the default is
         * not saved.
-        * @name LuCI.form.RangeSliderValue.prototype#default
-        * @type string
+        * @memberof LuCI.form.RangeSliderValue.prototype
+        * @member default
+        * @type {string}
         * @default null
         */
 
@@ -4595,8 +4929,9 @@ const CBIRangeSliderValue = CBIValue.extend(/** @lends LuCI.form.RangeSliderValu
         * is more meaningful than the currently chosen value. The calculated value
         * is displayed below the slider.
         *
-        * @name LuCI.form.RangeSliderValue.prototype#calculate
-        * @type function
+        * @memberof LuCI.form.RangeSliderValue.prototype
+        * @member calculate
+        * @type {function()}
         * @default null
         */
 
@@ -4605,12 +4940,19 @@ const CBIRangeSliderValue = CBIValue.extend(/** @lends LuCI.form.RangeSliderValu
         *
         * Suffix a unit string to the calculated value, e.g. 'seconds' or 'dBm'.
         *
-        * @name LuCI.form.RangeSliderValue.prototype#calcunits
-        * @type string
+        * @memberof LuCI.form.RangeSliderValue.prototype
+        * @member calcunits
+        * @type {string}
         * @default null
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const slider = new ui.RangeSlider((cfgvalue != null) ? cfgvalue : this.default, {
                        id: this.cbid(section_id),
@@ -4693,16 +5035,18 @@ const CBIFlagValue = CBIValue.extend(/** @lends LuCI.form.Flag.prototype */ {
        /**
         * Sets the input value to use for the checkbox checked state.
         *
-        * @name LuCI.form.Flag.prototype#enabled
-        * @type string
+        * @memberof LuCI.form.Flag.prototype
+        * @member enabled
+        * @type {string}
         * @default 1
         */
 
        /**
         * Sets the input value to use for the checkbox unchecked state.
         *
-        * @name LuCI.form.Flag.prototype#disabled
-        * @type string
+        * @memberof LuCI.form.Flag.prototype
+        * @member disabled
+        * @type {string}
         * @default 0
         */
 
@@ -4715,8 +5059,8 @@ const CBIFlagValue = CBIValue.extend(/** @lends LuCI.form.Flag.prototype */ {
         * value will be shown as a tooltip. If the return value of the function
         * is `null` no tooltip will be set.
         *
-        * @name LuCI.form.Flag.prototype#tooltip
-        * @type string|function
+        * @memberof LuCI.form.Flag.prototype
+        * @member {string|function()} tooltip
         * @default null
         */
 
@@ -4726,12 +5070,19 @@ const CBIFlagValue = CBIValue.extend(/** @lends LuCI.form.Flag.prototype */ {
         * If set, this icon will be shown for the default one.
         * This could also be a png icon from the resources directory.
         *
-        * @name LuCI.form.Flag.prototype#tooltipicon
-        * @type string
+        * @memberof LuCI.form.Flag.prototype
+        * @member tooltipicon
+        * @type {string}
         * @default 'ℹ️';
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                let tooltip = null;
 
@@ -4848,8 +5199,9 @@ const CBIMultiValue = CBIDynamicList.extend(/** @lends LuCI.form.MultiValue.prot
        /**
         * Allows custom value entry in addition to those already specified.
         *
-        * @name LuCI.form.MultiValue.prototype#create
-        * @type boolean
+        * @memberof LuCI.form.MultiValue.prototype
+        * @member create
+        * @type {boolean}
         * @default null
         */
 
@@ -4858,8 +5210,9 @@ const CBIMultiValue = CBIDynamicList.extend(/** @lends LuCI.form.MultiValue.prot
         * property of the underlying dropdown widget. If omitted, the value of
         * the `size` property is used or `3` when `size` is also unspecified.
         *
-        * @name LuCI.form.MultiValue.prototype#display_size
-        * @type number
+        * @memberof LuCI.form.MultiValue.prototype
+        * @member display_size
+        * @type {number}
         * @default null
         */
 
@@ -4868,12 +5221,19 @@ const CBIMultiValue = CBIDynamicList.extend(/** @lends LuCI.form.MultiValue.prot
         * property of the underlying dropdown widget. If omitted, the value of
         * the `size` property is used or `-1` when `size` is also unspecified.
         *
-        * @name LuCI.form.MultiValue.prototype#dropdown_size
-        * @type number
+        * @memberof LuCI.form.MultiValue.prototype
+        * @member dropdown_size
+        * @type {number}
         * @default null
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const value = (cfgvalue != null) ? cfgvalue : this.default;
                const choices = this.transformChoices();
@@ -4936,8 +5296,9 @@ const CBITextValue = CBIValue.extend(/** @lends LuCI.form.TextValue.prototype */
         * Enforces the use of a monospace font for the textarea contents when set
         * to `true`.
         *
-        * @name LuCI.form.TextValue.prototype#monospace
-        * @type boolean
+        * @memberof LuCI.form.TextValue.prototype
+        * @member monospace
+        * @type {boolean}
         * @default false
         */
 
@@ -4945,8 +5306,9 @@ const CBITextValue = CBIValue.extend(/** @lends LuCI.form.TextValue.prototype */
         * Allows specifying the [cols]{@link LuCI.ui.Textarea.InitOptions}
         * property of the underlying textarea widget.
         *
-        * @name LuCI.form.TextValue.prototype#cols
-        * @type number
+        * @memberof LuCI.form.TextValue.prototype
+        * @member cols
+        * @type {number}
         * @default null
         */
 
@@ -4954,8 +5316,9 @@ const CBITextValue = CBIValue.extend(/** @lends LuCI.form.TextValue.prototype */
         * Allows specifying the [rows]{@link LuCI.ui.Textarea.InitOptions}
         * property of the underlying textarea widget.
         *
-        * @name LuCI.form.TextValue.prototype#rows
-        * @type number
+        * @memberof LuCI.form.TextValue.prototype
+        * @member rows
+        * @type {number}
         * @default null
         */
 
@@ -4963,12 +5326,19 @@ const CBITextValue = CBIValue.extend(/** @lends LuCI.form.TextValue.prototype */
         * Allows specifying the [wrap]{@link LuCI.ui.Textarea.InitOptions}
         * property of the underlying textarea widget.
         *
-        * @name LuCI.form.TextValue.prototype#wrap
-        * @type number
+        * @memberof LuCI.form.TextValue.prototype
+        * @member wrap
+        * @type {number}
         * @default null
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const value = (cfgvalue != null) ? cfgvalue : this.default;
 
@@ -5029,8 +5399,9 @@ const CBIDummyValue = CBIValue.extend(/** @lends LuCI.form.DummyValue.prototype
         * By setting this property, the dummy value text is wrapped in an `<a>`
         * element with the property value used as `href` attribute.
         *
-        * @name LuCI.form.DummyValue.prototype#href
-        * @type string
+        * @memberof LuCI.form.DummyValue.prototype
+        * @member href
+        * @type {string}
         * @default null
         */
 
@@ -5041,8 +5412,9 @@ const CBIDummyValue = CBIValue.extend(/** @lends LuCI.form.DummyValue.prototype
         * text. In some cases, HTML content may need to be interpreted and
         * rendered as-is. When set to `true`, HTML escaping is disabled.
         *
-        * @name LuCI.form.DummyValue.prototype#rawhtml
-        * @type boolean
+        * @memberof LuCI.form.DummyValue.prototype
+        * @member rawhtml
+        * @type {boolean}
         * @default null
         */
 
@@ -5052,12 +5424,19 @@ const CBIDummyValue = CBIValue.extend(/** @lends LuCI.form.DummyValue.prototype
         *
         * By default, the value is displayed.
         *
-        * @name LuCI.form.DummyValue.prototype#hidden
-        * @type boolean
+        * @memberof LuCI.form.DummyValue.prototype
+        * @member hidden
+        * @type {boolean}
         * @default null
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const value = (cfgvalue != null) ? cfgvalue : this.default;
                const hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) });
@@ -5130,8 +5509,9 @@ const CBIButtonValue = CBIValue.extend(/** @lends LuCI.form.Button.prototype */
         *
         * The default of `null` means the option title is used as caption.
         *
-        * @name LuCI.form.Button.prototype#inputtitle
-        * @type string|function
+        * @memberof LuCI.form.Button.prototype
+        * @member inputtitle
+        * @type {string|function()}
         * @default null
         */
 
@@ -5146,8 +5526,9 @@ const CBIButtonValue = CBIValue.extend(/** @lends LuCI.form.Button.prototype */
         *
         * The default of `null` means a neutral button styling is used.
         *
-        * @name LuCI.form.Button.prototype#inputstyle
-        * @type string
+        * @memberof LuCI.form.Button.prototype
+        * @member inputstyle
+        * @type {string}
         * @default null
         */
 
@@ -5163,12 +5544,19 @@ const CBIButtonValue = CBIValue.extend(/** @lends LuCI.form.Button.prototype */
         * DOM click element as the first and the underlying configuration section ID
         * as the second argument.
         *
-        * @name LuCI.form.Button.prototype#onclick
-        * @type function
+        * @memberof LuCI.form.Button.prototype
+        * @member onclick
+        * @type {function()}
         * @default null
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const value = (cfgvalue != null) ? cfgvalue : this.default;
                const hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) });
@@ -5240,7 +5628,13 @@ const CBIButtonValue = CBIValue.extend(/** @lends LuCI.form.Button.prototype */
 const CBIHiddenValue = CBIValue.extend(/** @lends LuCI.form.HiddenValue.prototype */ {
        __name__: 'CBI.HiddenValue',
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const widget = new ui.Hiddenfield((cfgvalue != null) ? cfgvalue : this.default, {
                        id: this.cbid(section_id)
@@ -5302,8 +5696,9 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         * Render the widget in browser mode initially instead of a button
         * to 'Select File...'.
         *
-        * @name LuCI.form.FileUpload.prototype#browser
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member browser
+        * @type {boolean}
         * @default false
         */
 
@@ -5316,8 +5711,9 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         *
         * The default of `false` means hidden files are not displayed.
         *
-        * @name LuCI.form.FileUpload.prototype#show_hidden
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member show_hidden
+        * @type {boolean}
         * @default false
         */
 
@@ -5331,8 +5727,9 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         *
         * The default of `true` means file upload functionality is displayed.
         *
-        * @name LuCI.form.FileUpload.prototype#enable_upload
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member enable_upload
+        * @type {boolean}
         * @default true
         */
 
@@ -5346,8 +5743,9 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         *
         * The default of `false` means the directory create button is hidden.
         *
-        * @name LuCI.form.FileUpload.prototype#directory_create
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member directory_create
+        * @type {boolean}
         * @default false
         */
 
@@ -5360,8 +5758,9 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         *
         * The default is `false`.
         *
-        * @name LuCI.form.FileUpload.prototype#directory_select
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member directory_select
+        * @type {boolean}
         * @default false
         */
 
@@ -5375,16 +5774,18 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         *
         * The default is `true`, means file removal buttons are displayed.
         *
-        * @name LuCI.form.FileUpload.prototype#enable_remove
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member enable_remove
+        * @type {boolean}
         * @default true
         */
 
        /**
         * Toggle download file functionality.
         *
-        * @name LuCI.form.FileUpload.prototype#enable_download
-        * @type boolean
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member enable_download
+        * @type {boolean}
         * @default false
         */
 
@@ -5399,12 +5800,19 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
         *
         * The default is `/etc/luci-uploads`.
         *
-        * @name LuCI.form.FileUpload.prototype#root_directory
-        * @type string
+        * @memberof LuCI.form.FileUpload.prototype
+        * @member root_directory
+        * @type {string}
         * @default /etc/luci-uploads
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
                        id: this.cbid(section_id),
@@ -5475,8 +5883,9 @@ const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.
         * Render the widget in browser mode initially instead of a button
         * to 'Select Directory...'.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#browser
-        * @type boolean
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member browser
+        * @type {boolean}
         * @default false
         */
 
@@ -5490,16 +5899,18 @@ const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.
         *
         * The default of `false` means the directory create button is hidden.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#directory_create
-        * @type boolean
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member directory_create
+        * @type {boolean}
         * @default false
         */
 
        /**
         * Toggle download file functionality.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#enable_download
-        * @type boolean
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member enable_download
+        * @type {boolean}
         * @default false
         */
 
@@ -5513,8 +5924,9 @@ const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.
         *
         * The default is `false`, means file removal buttons are not displayed.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#enable_remove
-        * @type boolean
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member enable_remove
+        * @type {boolean}
         * @default false
         */
 
@@ -5528,8 +5940,9 @@ const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.
         *
         * The default of `false` means file upload functionality is disabled.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#enable_upload
-        * @type boolean
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member enable_upload
+        * @type {boolean}
         * @default false
         */
 
@@ -5544,8 +5957,9 @@ const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.
         *
         * The default is `/tmp`.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#root_directory
-        * @type string
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member root_directory
+        * @type {string}
         * @default /tmp
         */
 
@@ -5558,12 +5972,18 @@ const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.
         *
         * The default of `true` means hidden files are displayed.
         *
-        * @name LuCI.form.DirectoryPicker.prototype#show_hidden
-        * @type boolean
+        * @memberof LuCI.form.DirectoryPicker.prototype
+        * @member {boolean} show_hidden
         * @default true
         */
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                const browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
                        id: this.cbid(section_id),
@@ -5627,7 +6047,7 @@ const CBISectionValue = CBIValue.extend(/** @lends LuCI.form.SectionValue.protot
                this.super('__init__', [ map, section, option ]);
 
                if (!CBIAbstractSection.isSubclass(cbiClass))
-                       throw 'Sub section must be a descendent of CBIAbstractSection';
+                       throw 'Sub section must be a descendant of CBIAbstractSection';
 
                this.subsection = cbiClass.instantiate([ this.map, ...args ]);
                this.subsection.parentoption = this;
@@ -5638,8 +6058,9 @@ const CBISectionValue = CBIValue.extend(/** @lends LuCI.form.SectionValue.protot
         *
         * This property holds a reference to the instantiated nested section.
         *
-        * @name LuCI.form.SectionValue.prototype#subsection
-        * @type LuCI.form.AbstractSection
+        * @memberof LuCI.form.SectionValue
+        * @member subsection
+        * @type {LuCI.form.AbstractSection}
         * @readonly
         */
 
@@ -5653,12 +6074,22 @@ const CBISectionValue = CBIValue.extend(/** @lends LuCI.form.SectionValue.protot
                return this.subsection.parse(section_id);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @param {number} option_index
+        * @param {string} cfgvalue
+        * @returns {Node}
+        */
        renderWidget(section_id, option_index, cfgvalue) {
                return this.subsection.render(section_id);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} section_id
+        * @returns {null}
+        */
        checkDepends(section_id) {
                this.subsection.checkDepends(section_id);
                return CBIValue.prototype.checkDepends.apply(this, [ section_id ]);
@@ -5725,30 +6156,28 @@ const CBISectionValue = CBIValue.extend(/** @lends LuCI.form.SectionValue.protot
  * [render()]{@link LuCI.form.Map#render} is invoked on the instance to
  * assemble the HTML markup and insert it into the DOM.
  *
- * Example:
+ * @example
  *
- * <pre>
  * 'use strict';
  * 'require form';
  *
  * let m, s, o;
  *
- * m = new form.Map('example', 'Example form',
- *     'This is an example form mapping the contents of /etc/config/example');
+ * m = new form.Map('example', _('Example form'),
+ *     _('This is an example form mapping the contents of /etc/config/example'));
  *
- * s = m.section(form.NamedSection, 'first_section', 'example', 'The first section',
- *     'This sections maps "config example first_section" of /etc/config/example');
+ * s = m.section(form.NamedSection, 'first_section', 'example', _('The first section'),
+ *     _('This sections maps "config example first_section" of /etc/config/example'));
  *
- * o = s.option(form.Flag, 'some_bool', 'A checkbox option');
+ * o = s.option(form.Flag, 'some_bool', _('A checkbox option'));
  *
- * o = s.option(form.ListValue, 'some_choice', 'A select element');
- * o.value('choice1', 'The first choice');
- * o.value('choice2', 'The second choice');
+ * o = s.option(form.ListValue, 'some_choice', _('A select element'));
+ * o.value('choice1', _('The first choice'));
+ * o.value('choice2', _('The second choice'));
  *
  * m.render().then((node) => {
  *     document.body.appendChild(node);
  * });
- * </pre>
  */
 return baseclass.extend(/** @lends LuCI.form.prototype */ {
        Map: CBIMap,
index 3e9326ee1ff5d25e4c9b779e1fc3dad7a0b719eb..4daa9ef8b30378c5e5455f2ca66a17db7d4072f7 100644 (file)
@@ -3,10 +3,15 @@
 'require request';
 'require baseclass';
 
+/**
+ * @namespace LuCI.fs
+ * @memberof LuCI
+ */
+
 /**
  * @typedef {Object} FileStatEntry
  * @memberof LuCI.fs
-
+ *
  * @property {string} name - Name of the directory entry
  * @property {string} type - Type of the entry, one of `block`, `char`, `directory`, `fifo`, `symlink`, `file`, `socket` or `unknown`
  * @property {number} size - Size in bytes
@@ -85,6 +90,13 @@ var rpcErrors = [
        'UnsupportedError'
 ];
 
+/**
+ * Handle an RPC reply.
+ * @param {object} expect
+ * @param {number} rc
+ * @throws {Error}
+ * @returns {number}
+ */
 function handleRpcReply(expect, rc) {
        if (typeof(rc) == 'number' && rc != 0) {
                var e = new Error(rpc.getStatusText(rc)); e.name = rpcErrors[rc] || 'Error';
@@ -110,6 +122,12 @@ function handleRpcReply(expect, rc) {
        return rc;
 }
 
+/**
+ * Handle a CGI-IO reply.
+ * @param {object} res
+ * @throws {Error}
+ * @returns {string}
+ */
 function handleCgiIoReply(res) {
        if (!res.ok || res.status != 200) {
                var e = new Error(res.statusText);
@@ -401,7 +419,7 @@ var FileSystem = baseclass.extend(/** @lends LuCI.fs.prototype */ {
         * Whether to include stderr output in command output. This is usually
         * not needed but can be useful to execute a command and get full output.
         *
-        * @param {function} [responseProgress=null]
+        * @param {function()} [responseProgress=null]
         * An optional request callback function which receives ProgressEvent
         * instances as sole argument during the HTTP response transfer. This is
         * usually not needed but can be useful to receive realtime command
index 4925c03434ef565c85b9db66bd8a34f7f3aa022b..f0214695d7663ac9c2afec5e76121f105aa8afca 100644 (file)
@@ -1,12 +1,14 @@
 /**
- * @class LuCI
- * @classdesc
  *
  * This is the LuCI base class. It is automatically instantiated and
  * accessible using the global `L` variable.
- *
- * @param {Object} env
- * The environment settings to use for the LuCI runtime.
+ * @class LuCI
+ * @classdesc
+ * @property {object} env The environment settings to use for the LuCI runtime.
+ * @param {Window} window - The browser global `window` object.
+ * @param {Document} document - The DOM `document` root for the current page.
+ * @param {undefined} undefined - Local `undefined` slot (prevents shadowing and
+ * ensures `undefined` is the real undefined value).
  */
 
 ((window, document, undefined) => {
@@ -21,9 +23,9 @@
        const toCamelCase = s => s.replace(/(?:^|[\. -])(.)/g, (m0, m1) => m1.toUpperCase());
 
        /**
-        * @class baseclass
+        * @class 
+        * @name LuCI.baseclass
         * @hideconstructor
-        * @memberof LuCI
         * @classdesc
         *
         * `LuCI.baseclass` is the abstract base class all LuCI classes inherit from.
@@ -46,6 +48,7 @@
                 * An object describing the properties to add to the new
                 * subclass.
                 *
+                * @this {LuCI.baseclass}
                 * @returns {LuCI.baseclass}
                 * Returns a new LuCI.baseclass subclassed from this class, extended
                 * by the given properties and with its prototype set to this base
@@ -89,7 +92,7 @@
                /**
                 * Extends this base class with the properties described in
                 * `properties`, instantiates the resulting subclass using
-                * the additional optional arguments passed to this function
+                * the given arguments passed to this function
                 * and returns the resulting subclassed Class instance.
                 *
                 * This function serves as a convenience shortcut for
                 * An object describing the properties to add to the new
                 * subclass.
                 *
-                * @param {...*} [new_args]
-                * Specifies arguments to be passed to the subclass constructor
-                * as-is in order to instantiate the new subclass.
+                * @param {...*} new_args
+                * Arguments forwarded to the constructor of the generated subclass.
                 *
                 * @returns {LuCI.baseclass}
                 * Returns a new LuCI.baseclass instance extended by the given
                 *
                 * @memberof LuCI.baseclass
                 *
-                * @param {Array<*>} params
+                * @param {Array<*>} args
                 * An array of arbitrary values which will be passed as arguments
                 * to the constructor function.
                 *
                 * @returns {boolean}
                 * Returns `true` when the given `classValue` is a subclass of this
                 * class or `false` if the given value is not a valid class or not
-                * a subclass of this class'.
+                * a subclass of this class.
                 */
                isSubclass(classValue) {
                        return (typeof(classValue) == 'function' && classValue.prototype instanceof this);
                         * `offset` and prepend any further given optional parameters to
                         * the beginning of the resulting array copy.
                         *
-                        * @memberof LuCI.baseclass
+                        * @memberof LuCI.baseclass.prototype
                         * @instance
                         *
                         * @param {Array<*>} args
                         *      Calls the `key()` method with parameters `arg1` and `arg2`
                         *      when found within one of the parent classes.
                         *
-                        * @memberof LuCI.baseclass
+                        * @memberof LuCI.baseclass.prototype
                         * @instance
                         *
                         * @param {string} key
                         * The name of the superclass member to retrieve.
                         *
-                        * @param {Array<*>} [callArgs]
-                        * An optional array of function call parameters to use. When
-                        * this parameter is specified, the found member value is called
-                        * as a function using the values of this array as arguments.
+                        * @param {...*|Array<*>} [callArgs]
+                        * Arguments to pass when invoking the superclass method. May be 
+                        * either an argument array or variadic arguments.
                         *
                         * @throws {ReferenceError}
                         * Throws a `ReferenceError` when `callArgs` are specified and
                        /**
                         * Returns a string representation of this class.
                         *
+                        * @memberof LuCI.baseclass.prototype
+                        * @instance
+                        *
                         * @returns {string}
                         * Returns a string representation of this class containing the
                         * constructor functions `displayName` and describing the class
 
        const requestQueue = [];
 
+       /**
+        * Check whether a Request.options object is eligible to be queued for RPC
+        * batching.
+        *
+        * A request is considered queueable when all of the following hold:
+        *  - `classes.rpc` is available
+        *  - HTTP method is `POST` and `content` is an object
+        *  - `nobatch` is not explicitly `true`
+        *  - the request URL starts with the RPC base URL
+        *
+        * @private
+        * @param {object} opt - Options object passed to `Request.request()`
+        * @returns {boolean} `true` if the request may be queued for batching,
+        * otherwise `false`
+        */
        function isQueueableRequest(opt) {
                if (!classes.rpc)
                        return false;
                return (rpcBaseURL != null && opt.url.indexOf(rpcBaseURL) == 0);
        }
 
+       /**
+        * Send all queued RPC requests in a single batched call and dispatch
+        * individual callbacks for each original request.
+        *
+        * Behaviour:
+        *  - aggregates queued requests' `content` into a single array payload
+        *  - sends the batch to `rpcBaseURL` with `nobatch: true`
+        *  - on success: clones and forwards per-request replies to the
+        *    originally provided resolve callbacks, or rejects when no
+        *    related reply is available
+        *  - on failure: invokes each queued request's reject callback
+        *
+        * @private
+        * @returns {void}
+        */
        function flushRequestQueue() {
                if (!requestQueue.length)
                        return;
                 * @property {Object<string, string>} [header]
                 * Specifies HTTP headers to set for the request.
                 *
-                * @property {function} [progress]
+                * @property {function()} [progress]
                 * An optional request callback function which receives ProgressEvent
                 * instances as sole argument during the HTTP request transfer.
                 *
-                * @property {function} [responseProgress]
+                * @property {function()} [responseProgress]
                 * An optional request callback function which receives ProgressEvent
                 * instances as sole argument during the HTTP response transfer.
                 */
                        });
                },
 
+               /**
+                * Handle XHR readyState changes for an in-flight request and resolve or
+                * reject the originating promise.
+                *
+                * @instance
+                * @memberof LuCI.request
+                * @param {function(LuCI.response)} resolveFn
+                * Callback invoked on success with the constructed {@link LuCI.response}.
+                *
+                * @param {function(Error)} rejectFn
+                * Callback invoked on failure or abort with an `Error` instance.
+                *
+                * @param {Event} [ev]
+                * The XHR `readystatechange` event (optional).
+                *
+                * @returns {void}
+                * No return value; the function resolves or rejects the supplied callbacks.
+                */
                handleReadyStateChange(resolveFn, rejectFn, ev) {
                        const xhr = this.xhr, duration = Date.now() - this.start;
 
                         * @param {LuCI.request.RequestOptions} [options]
                         * Additional options to configure the request.
                         *
-                        * @param {LuCI.request.poll~callbackFn} [callback]
+                        * @param {LuCI.request.poll.callbackFn} [callback]
                         * {@link LuCI.request.poll~callbackFn Callback} function to
                         * invoke for each HTTP reply.
                         *
                         * @throws {TypeError}
                         * Throws `TypeError` when an invalid interval was passed.
                         *
-                        * @returns {function}
+                        * @returns {function()}
                         * Returns the internally created poll function.
                         */
                        add(interval, url, options, callback) {
                         *
                         * @instance
                         * @memberof LuCI.request.poll
-                        * @param {function} entry
+                        * @param {function()} entry
                         * The poll function returned by {@link LuCI.request.poll#add add()}.
                         *
                         * @returns {boolean}
                        remove(entry) { return Poll.remove(entry) },
 
                        /**
-                         * Alias for {@link LuCI.poll.start LuCI.poll.start()}.
-                         *
-                         * @instance
-                         * @memberof LuCI.request.poll
-                         */
+                        * Alias for {@link LuCI.poll.start LuCI.poll.start()}.
+                        *
+                        * @instance
+                        * @memberof LuCI.request.poll
+                        * @returns {boolean}
+                        */
                        start() { return Poll.start() },
 
                        /**
-                         * Alias for {@link LuCI.poll.stop LuCI.poll.stop()}.
-                         *
-                         * @instance
-                         * @memberof LuCI.request.poll
-                         */
+                        * Alias for {@link LuCI.poll.stop LuCI.poll.stop()}.
+                        *
+                        * @instance
+                        * @memberof LuCI.request.poll
+                        * @returns {boolean}
+                        */
                        stop() { return Poll.stop() },
 
                        /**
-                         * Alias for {@link LuCI.poll.active LuCI.poll.active()}.
-                         *
-                         * @instance
-                         * @memberof LuCI.request.poll
-                         */
+                        * Alias for {@link LuCI.poll.active LuCI.poll.active()}.
+                        *
+                        * @instance
+                        * @memberof LuCI.request.poll
+                        * @returns {boolean}
+                        */
                        active() { return Poll.active() }
                }
        });
                 *
                 * @instance
                 * @memberof LuCI.poll
-                * @param {function} fn
+                * @param {function()} fn
                 * The function to invoke on each poll interval.
                 *
                 * @param {number} interval
                 *
                 * @instance
                 * @memberof LuCI.poll
-                * @param {function} fn
+                * @param {function()} fn
                 * The function to remove.
                 *
                 * @throws {TypeError}
                 * When `val` is of any other type, it will be added as an attribute
                 * to the given `node` as-is, with the underlying `setAttribute()`
                 * call implicitly turning it into a string.
+                * @returns {null}
                 */
                attr(node, key, val) {
                        if (!this.elem(node))
                 *
                 * @instance
                 * @memberof LuCI.dom
-                * @param {*} html
+                * @param {string} html
                 * Describes the node to create.
                 *
                 * When the value of `html` is of type array, a `DocumentFragment`
                 * @param {string} method
                 * The name of the method to invoke on the found class instance.
                 *
-                * @param {...*} params
+                * @param {...*} args
                 * Additional arguments to pass to the invoked method as-is.
                 *
                 * @returns {*|null}
                 * @param {Node} node
                 * The DOM `Node` instance to test.
                 *
-                * @param {LuCI.dom~ignoreCallbackFn} [ignoreFn]
+                * @param {LuCI.dom.ignoreCallbackFn} [ignoreFn]
                 * Specifies an optional function which is invoked for each child
                 * node to decide whether the child node should be ignored or not.
                 *
                 * @memberof LuCI.view
                 * @param {Event} ev
                 * The DOM event that triggered the function.
+                * @param {number} mode
+                * Whether to apply the changes checked.
                 *
                 * @returns {*|Promise<*>}
                 * Any return values of this function are discarded, but
                 * @instance
                 * @memberof LuCI
                 *
-                * @param {function} fn
+                * @param {function()} fn
                 * The function to bind.
                 *
                 * @param {*} self
                 * Zero or more variable arguments which are bound to the function
                 * as parameters.
                 *
-                * @returns {function}
+                * @returns {function()}
                 * Returns the bound function.
                 */
                bind(fn, self, ...args) {
                 * be replaced by spaces and joined with the runtime-determined
                 * base URL of LuCI.js to form an absolute URL to load the class
                 * file from.
-                *
+                * @param {string[]} [from=[]]
+                * Optional dependency chain used during dependency resolution. This
+                * array contains the sequence of class names already being resolved
+                * (the caller stack). It is used to detect circular dependencies —
+                * if `name` appears in `from` a `DependencyError` is thrown.
                 * @throws {DependencyError}
                 * Throws a `DependencyError` when the class to load includes
                 * circular dependencies.
                 * such as `sae` or `11w` support. The `subfeature` argument can
                 * be used to query these.
                 *
-                * @return {boolean|null}
+                * @returns {boolean|null}
                 * Return `true` if the queried feature (and sub-feature) is available
                 * or `false` if the requested feature isn't present or known.
                 * Return `null` when a sub-feature was queried for a feature which
                },
 
                /* private */
-               setupDOM(res) {
-                       const domEv = res[0], uiClass = res[1], rpcClass = res[2], formClass = res[3], rpcBaseURL = res[4];
+               setupDOM([domEv, uiClass, rpcClass, formClass, rpcBaseURL]) {
 
                        rpcClass.setBaseURL(rpcBaseURL);
 
                 * @param {...string} [parts]
                 * An array of parts to join into a path.
                 *
-                * @return {string}
+                * @returns {string}
                 * Return the joined path.
                 */
                fspath() /* ... */{
                 * An array of parts to join into a URL path. Parts may contain
                 * slashes and any of the other characters mentioned above.
                 *
-                * @return {string}
+                * @returns {string}
                 * Return the joined URL path.
                 */
                path(prefix = '', parts) {
                 * An array of parts to join into a URL path. Parts may contain
                 * slashes and any of the other characters mentioned above.
                 *
-                * @return {string}
+                * @returns {string}
                 * Returns the resulting URL path.
                 */
                url() {
                 * An array of parts to join into a URL path. Parts may contain
                 * slashes and any of the other characters mentioned above.
                 *
-                * @return {string}
+                * @returns {string}
                 * Returns the resulting URL path.
                 */
                resource() {
                 * An array of parts to join into a URL path. Parts may contain
                 * slashes and any of the other characters mentioned above.
                 *
-                * @return {string}
+                * @returns {string}
                 * Returns the resulting URL path.
                 */
                media() {
                 * @instance
                 * @memberof LuCI
                 *
-                * @return {string}
+                * @returns {string}
                 * Returns the URL path to the current view.
                 */
                location() {
                 * @param {*} [val]
                 * The value to test
                 *
-                * @return {boolean}
+                * @returns {boolean}
                 * Returns `true` if the given value is of a type object and
                 * not `null`, else returns `false`.
                 */
                 * @param {*} [val]
                 * The value to test
                 *
-                * @return {boolean}
+                * @returns {boolean}
                 * Returns `true` if the given value is a function arguments object,
                 * else returns `false`.
                 */
                 * lexicographic sorting with a sorting suitable for IP/MAC style
                 * addresses or numeric values respectively.
                 *
-                * @return {string[]}
+                * @returns {string[]}
                 * Returns an array containing the sorted keys of the given object.
                 */
                sortedKeys(obj, key, sortmode) {
                 * This function is meant to be used as a comparator function for
                 * Array.sort().
                 *
-                * @type {function}
+                * @type {function()}
                 *
                 * @param {*} a
                 * The first value
                 * @param {*} b
                 * The second value.
                 *
-                * @return {number}
+                * @returns {number}
                 * Returns -1 if the first value is smaller than the second one.
                 * Returns 0 if both values are equal.
                 * Returns 1 if the first value is larger than the second one.
                 * @param {*} val
                 * The input value to sort (and convert to an array if needed).
                 *
-                * @return {Array<*>}
+                * @returns {Array<*>}
                 * Returns the resulting, numerically sorted array.
                 */
                sortedArray(val) {
                 * @param {*} val
                 * The value to convert into an array.
                 *
-                * @return {Array<*>}
+                * @returns {Array<*>}
                 * Returns the resulting array.
                 */
                toArray(val) {
                 * @param {LuCI.requestCallbackFn} cb
                 * The callback function to invoke when the request finishes.
                 *
-                * @return {Promise<null>}
+                * @returns {Promise<null>}
                 * Returns a promise resolving to `null` when concluded.
                 */
                get(url, args, cb) {
                 * @param {LuCI.requestCallbackFn} cb
                 * The callback function to invoke when the request finishes.
                 *
-                * @return {Promise<null>}
+                * @returns {Promise<null>}
                 * Returns a promise resolving to `null` when concluded.
                 */
                post(url, args, cb) {
                 * an argument `token` with the current value of `LuCI.env.token` by
                 * default, regardless of the parameters specified with `args`.
                 *
-                * @return {function}
+                * @returns {function()}
                 * Returns the internally created function that has been passed to
                 * {@link LuCI.request.poll#add Request.poll.add()}. This value can
                 * be passed to {@link LuCI.poll.remove Poll.remove()} to remove the
                /**
                 * Check whether a view has sufficient permissions.
                 *
-                * @return {boolean|null}
+                * @returns {boolean|null}
                 * Returns `null` if the current session has no permission at all to
                 * load resources required by the view. Returns `false` if readonly
                 * permissions are granted or `true` if at least one required ACL
                 * @instance
                 * @memberof LuCI
                 *
-                * @param {function} entry
+                * @param {function()} entry
                 * The polling function to remove.
                 *
-                * @return {boolean}
+                * @returns {boolean}
                 * Returns `true` when the function has been removed or `false` if
                 * it could not be found.
                 */
                 * @instance
                 * @memberof LuCI
                 *
-                * @return {boolean}
+                * @returns {boolean}
                 * Returns `true` when the polling loop has been stopped or `false`
                 * when it didn't run to begin with.
                 */
                 * @instance
                 * @memberof LuCI
                 *
-                * @return {boolean}
+                * @returns {boolean}
                 * Returns `true` when the polling loop has been started or `false`
                 * when it was already running.
                 */
                 * @param {number} [timeout]
                 * Request timeout to use
                 *
-                * @return {Promise<null>}
+                * @returns {Promise<null>}
                 */
                get(url, data, callback, timeout) {
                        this.active = true;
                 * @param {number} [timeout]
                 * Request timeout to use
                 *
-                * @return {Promise<null>}
+                * @returns {Promise<null>}
                 */
                post(url, data, callback, timeout) {
                        this.active = true;
index 5799ae297343db3e3fd0249b0b630650ad42a019..a7651d0b019c9a4cacb55da8669d4b288a578dfd 100644 (file)
@@ -645,6 +645,11 @@ function enumerateNetworks() {
 
 var Hosts, Network, Protocol, Device, WifiDevice, WifiNetwork, WifiVlan;
 
+/**
+ * @namespace LuCI.network
+ * @memberof LuCI
+ */
+
 /**
  * @class network
  * @memberof LuCI
@@ -661,7 +666,7 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ {
        /**
         * Converts the given prefix size in bits to a netmask.
         *
-        * @method
+        * @function
         *
         * @param {number} bits
         * The prefix size in bits.
@@ -680,7 +685,7 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ {
        /**
         * Converts the given netmask to a prefix size in bits.
         *
-        * @method
+        * @function
         *
         * @param {string} netmask
         * The netmask to convert into a bits count.
@@ -739,7 +744,7 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ {
         * into a human readable string such as `mixed WPA/WPA2 PSK (TKIP, CCMP)`
         * or `WPA3 SAE (CCMP)`.
         *
-        * @method
+        * @function
         *
         * @param {LuCI.network.WifiEncryption} encryption
         * The wireless encryption entry to convert.
@@ -808,7 +813,7 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ {
         * with the given methods and returns the resulting subclass value.
         *
         * This function internally calls
-        * {@link LuCI.Class.extend Class.extend()} on the `Network.Protocol`
+        * {@link LuCI.baseclass.extend baseclass.extend()} on the `Network.Protocol`
         * base class.
         *
         * @param {string} protoname
@@ -816,7 +821,7 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ {
         *
         * @param {Object<string, *>} methods
         * The member methods and values of the new `Protocol` subclass to
-        * be passed to {@link LuCI.Class.extend Class.extend()}.
+        * be passed to {@link LuCI.baseclass.extend baseclass.extend()}.
         *
         * @returns {LuCI.network.Protocol}
         * Returns the new `Protocol` subclass.
@@ -2036,6 +2041,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
         * @param {null|string|string[]} val
         * The value to set or `null` to remove the given option from the
         * configuration.
+        * @returns {null}
         */
        set: function(opt, val) {
                return uci.set('network', this.sid, opt, val);
@@ -2066,7 +2072,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
        /**
         * Get the name of this network protocol class.
         *
-        * This function will be overwritten by subclasses created by
+        * This function will be overridden by subclasses created by
         * {@link LuCI.network#registerProtocol Network.registerProtocol()}.
         *
         * @abstract
@@ -2082,7 +2088,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
         * Return a human readable description for the protocol, such as
         * `Static address` or `DHCP client`.
         *
-        * This function should be overwritten by subclasses.
+        * This function should be overridden by subclasses.
         *
         * @abstract
         * @returns {string}
@@ -2460,7 +2466,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
         * package is available via the system default package manager. This is used
         * when a config refers to a protocol handler which is not yet installed.
         *
-        * This function should be overwritten by protocol specific subclasses.
+        * This function should be overridden by protocol specific subclasses.
         *
         * @abstract
         *
@@ -2475,7 +2481,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
        /**
         * Check function for the protocol handler if a new interface is creatable.
         *
-        * This function should be overwritten by protocol specific subclasses.
+        * This function should be overridden by protocol specific subclasses.
         *
         * @abstract
         *
@@ -2516,7 +2522,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
         * network device on startup, examples for non-virtual protocols are
         * `dhcp` or `static` which apply IP configuration to existing interfaces.
         *
-        * This function should be overwritten by subclasses.
+        * This function should be overridden by subclasses.
         *
         * @returns {boolean}
         * Returns a boolean indicating whether the underlying protocol spawns
@@ -2846,7 +2852,7 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
         * from the configuration and is responsible for performing any required
         * cleanup tasks, such as unsetting uci entries in related configurations.
         *
-        * It should be overwritten by protocol specific subclasses.
+        * It should be overridden by protocol specific subclasses.
         *
         * @abstract
         *
@@ -3417,6 +3423,7 @@ WifiDevice = baseclass.extend(/** @lends LuCI.network.WifiDevice.prototype */ {
         * @param {null|string|string[]} value
         * The value to set or `null` to remove the given option from the
         * configuration.
+        * @returns {null}
         */
        set: function(opt, value) {
                return uci.set('wireless', this.sid, opt, value);
@@ -3741,6 +3748,7 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */
         * @param {null|string|string[]} value
         * The value to set or `null` to remove the given option from the
         * configuration.
+        * @returns {null}
         */
        set: function(opt, value) {
                return uci.set('wireless', this.sid, opt, value);
@@ -4068,15 +4076,15 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */
         * @property {boolean} tdls
         * Specifies whether TDLS is active.
         *
-        * @property {number} [mesh llid]
+        * @property {number} [mesh_llid]
         * The mesh LLID, may be `0` or absent if not applicable or supported
         * by the driver.
         *
-        * @property {number} [mesh plid]
+        * @property {number} [mesh_plid]
         * The mesh PLID, may be `0` or absent if not applicable or supported
         * by the driver.
         *
-        * @property {string} [mesh plink]
+        * @property {string} [mesh_plink]
         * The mesh peer link state description, may be an empty string (`''`)
         * or absent if not applicable or supported by the driver.
         *
@@ -4090,7 +4098,7 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */
         *  - `BLOCKED`
         *  - `UNKNOWN`
         *
-        * @property {number} [mesh local PS]
+        * @property {number} [mesh_local_PS]
         * The local power-save mode for the peer link, may be an empty
         * string (`''`) or absent if not applicable or supported by
         * the driver.
@@ -4101,7 +4109,7 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */
         *  - `DEEP SLEEP`
         *  - `UNKNOWN`
         *
-        * @property {number} [mesh peer PS]
+        * @property {number} [mesh_peer_PS]
         * The remote power-save mode for the peer link, may be an empty
         * string (`''`) or absent if not applicable or supported by
         * the driver.
@@ -4112,7 +4120,7 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */
         *  - `DEEP SLEEP`
         *  - `UNKNOWN`
         *
-        * @property {number} [mesh non-peer PS]
+        * @property {number} [mesh_non_peer_PS]
         * The power-save mode for all non-peer neighbours, may be an empty
         * string (`''`) or absent if not applicable or supported by the driver.
         *
@@ -4364,6 +4372,8 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */
         * Calculate the current signal.
         *
         * @deprecated
+        * @param {number} signal
+        * @param {number} noise
         * @returns {number}
         * Returns the calculated signal level, which is the difference between
         * noise and signal (SNR), divided by 5.
index bf8684951e59b6d972c0c7f23e32b0f7e95819c7..085bea2996d88de79ccdb3de0b76ce099f3696aa 100644 (file)
@@ -3,10 +3,21 @@
 'require network';
 'require validation';
 
+/**
+ * Determine whether a provided value is a CIDR format IP string.
+ * @param {string} value the IP string to test.
+ * @returns {boolean}
+ */
 function isCIDR(value) {
        return Array.isArray(value) || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/(\d{1,2}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.test(value);
 }
 
+/**
+ * Calculate the broadcast IP for a given IP.
+ * @param {Node} s the ui element to test.
+ * @param {boolean} use_cfgvalue whether to use the config or form value.
+ * @returns {string}
+ */
 function calculateBroadcast(s, use_cfgvalue) {
        const readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue';
        const addropt = s.children.find(o => o.option == 'ipaddr');
@@ -49,6 +60,12 @@ function calculateBroadcast(s, use_cfgvalue) {
        return bc.join('.');
 }
 
+/**
+ * Validate a broadcast IP for a section value.
+ * @param {string} section_id
+ * @param {string} value
+ * @returns {boolean}
+ */
 function validateBroadcast(section_id, value) {
        var opt = this.map.lookupOption('broadcast', section_id),
            node = opt ? this.map.findElement('id', opt[0].cbid(section_id)) : null,
index 9213b2cd5f5bee59ef6c44491d7562e2d45e5aa6..fa48ebef450f599db99263ab930bed145a15a4d9 100644 (file)
@@ -7,6 +7,11 @@ let rpcSessionID = L.env.sessionid ?? '00000000000000000000000000000000';
 let rpcBaseURL = L.url('admin/ubus');
 const rpcInterceptorFns = [];
 
+/**
+ * @namespace LuCI.rpc
+ * @memberof LuCI
+ */
+
 /**
  * @class rpc
  * @memberof LuCI
@@ -130,7 +135,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         *  - `list("objname", ...)`
         *    Returns method signatures for each given `ubus` object name.
         *
-        * @param {...string} [objectNames]
+        * @param {...string} [args] (objectNames)
         * If any object names are given, this function will return the method
         * signatures of each given object.
         *
@@ -175,7 +180,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         *
         * Extraneous parameters passed to the generated function will not be
         * sent to the remote procedure but are passed to the
-        * {@link LuCI.rpc~filterFn filter function} if one is specified.
+        * {@link LuCI.rpc.filterFn filter function} if one is specified.
         *
         * Examples:
         *  - `params: [ "foo", "bar" ]` -
@@ -222,7 +227,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         *    `success` or if `reply.success` is not a boolean value, `false` will
         *    be returned as default instead.
         *
-        * @property {LuCI.rpc~filterFn} [filter]
+        * @property {LuCI.rpc.filterFn} [filter]
         * Specifies an optional filter function which is invoked to transform the
         * received reply data before it is returned to the caller.
         *
@@ -236,7 +241,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         * The filter function is invoked to transform a received `ubus` RPC call
         * reply before returning it to the caller.
         *
-        * @callback LuCI.rpc~filterFn
+        * @callback LuCI.rpc.filterFn
         *
         * @param {*} data
         * The received `ubus` reply data or a subset of it as described in the
@@ -250,7 +255,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         * All extraneous arguments passed to the RPC method exceeding the number
         * of arguments describes in the RPC call declaration.
         *
-        * @return {*}
+        * @returns {*}
         * The return value of the filter function will be returned to the caller
         * of the RPC method as-is.
         */
@@ -264,7 +269,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         * using the arguments passed to it as arguments and return a promise
         * resolving to the received reply values.
         *
-        * @callback LuCI.rpc~invokeFn
+        * @callback LuCI.rpc.invokeFn
         *
         * @param {...*} params
         * The parameters to pass to the remote procedure call. The given
@@ -275,7 +280,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         * `params` declaration are passed as private extra arguments to the
         * declared filter function.
         *
-        * @return {Promise<*>}
+        * @returns {Promise<*>}
         * Returns a promise resolving to the result data of the remote `ubus`
         * RPC method invocation, optionally substituted and filtered according
         * to the `expect` and `filter` declarations.
@@ -289,7 +294,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         * If any object names are given, this function will return the method
         * signatures of each given object.
         *
-        * @returns {LuCI.rpc~invokeFn}
+        * @returns {LuCI.rpc.invokeFn}
         * Returns a new function implementing the method call described in
         * `options`.
         */
@@ -448,7 +453,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
         * declaration object, allowing access to internals of the invocation
         * function such as `filter`, `expect` or `params` values.
         *
-        * @return {Promise<*>|*}
+        * @returns {Promise<*>|*}
         * Interceptor functions may return a promise to defer response
         * processing until some delayed work completed. Any values the returned
         * promise resolves to are ignored.
@@ -460,10 +465,10 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
        /**
         * Registers a new interceptor function.
         *
-        * @param {LuCI.rpc~interceptorFn} interceptorFn
+        * @param {LuCI.rpc.interceptorFn} interceptorFn
         * The interceptor function to register.
         *
-        * @returns {LuCI.rpc~interceptorFn}
+        * @returns {LuCI.rpc.interceptorFn}
         * Returns the given function value.
         */
        addInterceptor(interceptorFn) {
@@ -476,7 +481,7 @@ return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
        /**
         * Removes a registered interceptor function.
         *
-        * @param {LuCI.rpc~interceptorFn} interceptorFn
+        * @param {LuCI.rpc.interceptorFn} interceptorFn
         * The interceptor function to remove.
         *
         * @returns {boolean}
index b916cc77925ce61991170c35f54bb01a0c1546a0..77b4f231fcfb9ebdd68ffd9c3dbfb31861c9c91a 100644 (file)
@@ -2,6 +2,17 @@
 
 var s = [0x0000, 0x0000, 0x0000, 0x0000];
 
+/**
+ * Multiply two 64-bit values represented as arrays of four 16-bit words.
+ *
+ * Arrays use little-endian word order (least-significant 16-bit word first).
+ * The result is truncated to the lower 64 bits and returned as a 4-element
+ * array of 16-bit words.
+ *
+ * @param {Array<number>} a - Multiplicand (4 × 16-bit words, little-endian)
+ * @param {Array<number>} b - Multiplier  (4 × 16-bit words, little-endian)
+ * @returns {Array<number>} Product as 4 × 16-bit words (little-endian)
+ */
 function mul(a, b) {
        var r = [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000];
 
@@ -20,6 +31,18 @@ function mul(a, b) {
        return r;
 }
 
+/**
+ * Add a small integer to a 64-bit value represented as four 16-bit words.
+ *
+ * Treats `a` as a little-endian 64-bit value (4 × 16-bit words). Adds the
+ * integer `n` to the least-significant word and propagates carry across
+ * subsequent 16-bit words. The result is truncated to 64 bits and returned
+ * as a 4-element array of 16-bit words (little-endian).
+ *
+ * @param {Array<number>} a - Addend as 4 × 16-bit words (little-endian)
+ * @param {number} n - Value to add (integer carry)
+ * @returns {Array<number>} Sum as 4 × 16-bit words (little-endian)
+ */
 function add(a, n) {
        var r = [0x0000, 0x0000, 0x0000, 0x0000],
            k = n;
@@ -33,6 +56,17 @@ function add(a, n) {
        return r;
 }
 
+/**
+ * Shift a 64-bit value (4 × 16-bit words, little-endian) right by `n` bits.
+ *
+ * The input array is treated as little-endian 16-bit words. Bits shifted out
+ * on the right are discarded; the returned array contains the lower 64-bit
+ * result after the logical right shift.
+ *
+ * @param {Array<number>} a - Source value as 4 × 16-bit words (little-endian)
+ * @param {number} n - Number of bits to shift right (non-negative integer)
+ * @returns {Array<number>} Shifted value as 4 × 16-bit words (little-endian)
+ */
 function shr(a, n) {
        var r = [a[0], a[1], a[2], a[3], 0x0000],
            i = 4,
@@ -54,6 +88,15 @@ function shr(a, n) {
 }
 
 return L.Class.extend({
+       /**
+        * Seed the PRNG state.
+        *
+        * The seed is treated as a 32-bit integer; the lower 16 bits are stored
+        * in `s[0]`, the upper 16 bits in `s[1]`. `s[2]` and `s[3]` are zeroed.
+        *
+        * @param {number} n - Seed value (32-bit integer)
+        * @returns {void}
+        */
        seed: function(n) {
                n = (n - 1)|0;
                s[0] = n & 0xffff;
@@ -62,6 +105,14 @@ return L.Class.extend({
                s[3] = 0;
        },
 
+       /**
+        * Produce the next PRNG 32-bit integer.
+        *
+        * Advances the internal state and returns a 32-bit pseudo-random integer
+        * derived from the current state.
+        *
+        * @returns {number} 32-bit pseudo-random integer (JS number)
+        */
        int: function() {
                s = mul(s, [0x7f2d, 0x4c95, 0xf42d, 0x5851]);
                s = add(s, 1);
@@ -70,6 +121,18 @@ return L.Class.extend({
                return (r[1] << 16) | r[0];
        },
 
+       /**
+        * Return a pseudo-random value.
+        *
+        * Overloads:
+        * - get() -> number in [0, 1]
+        * - get(upper) -> integer in [1, upper]
+        * - get(lower, upper) -> integer in [lower, upper]
+        *
+        * @param {number} [lower=0] - Lower bound (when two args supplied)
+        * @param {number} [upper=0] - Upper bound (when one or two args supplied)
+        * @returns {number} Random value (float in [0,1] or integer in requested range)
+        */
        get: function() {
                var r = (this.int() % 0x7fffffff) / 0x7fffffff, l, u;
 
@@ -91,6 +154,15 @@ return L.Class.extend({
                return Math.floor(r * (u - l + 1)) + l;
        },
 
+       /**
+        * Derive a deterministic hex color from an input string.
+        *
+        * The color is produced by seeding the PRNG from a string-derived
+        * hash and producing RGB components. Returns a `#rrggbb` hex string.
+        *
+        * @param {string} string - Input string used to derive the color
+        * @returns {string} Hex color string in `#rrggbb` format
+        */
        derive_color: function(string) {
                this.seed(parseInt(sfh(string), 16));
 
index 7b47adc9fa23af1a28dd5df6da224393d572a24f..1c4c384efe1a5ade8308e25b199328d1fa48d8e4 100644 (file)
@@ -213,6 +213,9 @@ var CBILogreadBox = function(logtag, name) {
                                'class': 'cbi-input',
                        });
 
+                       /**
+                        * Update the form when log filter parameters change
+                        */
                        function handleLogFilterChange() {
                                self.logFacilityFilter = facilitySelect.value;
                                self.invertLogFacilitySearch = facilityInvert.checked;
index 29552b09ef45315e2eda3b48e8c4df69be924737..dc82c3b8671cd218a7398f33835824da07e31fbc 100644 (file)
@@ -5,18 +5,32 @@
 'require firewall';
 'require fs';
 
+
+/**
+ * Get users found in `/etc/passwd`.
+ * @returns {string[]}
+ */
 function getUsers() {
     return fs.lines('/etc/passwd').then(function(lines) {
         return lines.map(function(line) { return line.split(/:/)[0] });
     });
 }
 
+/**
+ * Get users found in `/etc/group`.
+ * @returns {string[]}
+ */
 function getGroups() {
     return fs.lines('/etc/group').then(function(lines) {
         return lines.map(function(line) { return line.split(/:/)[0] });
     });
 }
 
+/**
+ * Get bridge devices or Layer 3 devices of a network object.
+ * @param {object} network
+ * @returns {string[]}
+ */
 function getDevices(network) {
        if (network.isBridge()) {
                var devices = network.getDevices();
index 16baebda5fc03a4dd4c90353bb19acb5ebb0c5de..021174e43ea43b2041536d273bef47c02385f6dd 100644 (file)
@@ -2,6 +2,12 @@
 'require rpc';
 'require baseclass';
 
+/**
+ * Determine whether an object is empty.
+ * @param {object} object object to enumerate.
+ * @param {string} ignore property to ignore.
+ * @returns {boolean}
+ */
 function isEmpty(object, ignore) {
        for (const property in object)
                if (object.hasOwnProperty(property) && property != ignore)
@@ -10,6 +16,11 @@ function isEmpty(object, ignore) {
        return true;
 }
 
+/**
+ * @namespace LuCI.uci
+ * @memberof LuCI
+ */
+
 /**
  * @class uci
  * @memberof LuCI
@@ -376,6 +387,7 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
                }
        },
 
+       /* eslint-disable jsdoc/check-property-names */
        /**
         * A section object represents the options and their corresponding values
         * enclosed within a configuration section, as well as some additional
@@ -387,19 +399,19 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         * @typedef {Object<string, boolean|number|string|string[]>} SectionObject
         * @memberof LuCI.uci
         *
-        * @property {boolean} .anonymous
+        * @property {boolean} ".anonymous"
         * The `.anonymous` property specifies whether the configuration is
         * anonymous (`true`) or named (`false`).
         *
-        * @property {number} .index
+        * @property {number} ".index"
         * The `.index` property specifies the sort order of the section.
         *
-        * @property {string} .name
+        * @property {string} ".name"
         * The `.name` property holds the name of the section object. It may be
         * either an anonymous ID in the form `cfgXXXXXX` or `newXXXXXX` with `X`
         * being a hexadecimal digit or a string holding the name of the section.
         *
-        * @property {string} .type
+        * @property {string} ".type"
         * The `.type` property contains the type of the corresponding uci
         * section.
         *
@@ -411,13 +423,14 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         * either contain a string value or an array of strings, in case the
         * underlying option is an UCI list.
         */
+       /* eslint-enable jsdoc/check-property-names */
 
        /**
         * The sections callback is invoked for each section found within
         * the given configuration and receives the section object and its
         * associated name as arguments.
         *
-        * @callback LuCI.uci~sectionsFn
+        * @callback LuCI.uci.sections
         *
         * @param {LuCI.uci.SectionObject} section
         * The section object.
@@ -437,7 +450,7 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         * Enumerate only sections of the given type. If omitted, enumerate
         * all sections.
         *
-        * @param {LuCI.uci~sectionsFn} [cb]
+        * @param {LuCI.uci.sections} [cb]
         * An optional callback to invoke for each enumerated section.
         *
         * @returns {Array<LuCI.uci.SectionObject>}
@@ -662,6 +675,7 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         *
         * @param {string} opt
         * The name of the option to remove.
+        * @returns {null}
         */
        unset(conf, sid, opt) {
                return this.set(conf, sid, opt, null);
@@ -721,8 +735,8 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         * @param {string} conf
         * The name of the configuration to read.
         *
-        * @param {string} sid
-        * The name or ID of the section to read.
+        * @param {string} type
+        * The section type to read.
         *
         * @param {string} [opt]
         * The option name from which to read the value. If the option
@@ -766,6 +780,7 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         * The option value to set. If the value is `null` or an empty string,
         * the option will be removed, otherwise it will be set or overwritten
         * with the given value.
+        * @returns {null}
         */
        set_first(conf, type, opt, val) {
                let sid = null;
@@ -795,6 +810,8 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         *
         * @param {string} opt
         * The option name to set the value for.
+        *
+        * @returns {null}
         */
        unset_first(conf, type, opt) {
                return this.set_first(conf, type, opt, null);
@@ -1015,14 +1032,14 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         * @typedef {string[]} ChangeRecord
         * @memberof LuCI.uci
         *
-        * @property {string} 0
+        * @property {string} operation
         * The operation name - may be one of `add`, `set`, `remove`, `order`,
         * `list-add`, `list-del` or `rename`.
         *
-        * @property {string} 1
+        * @property {string} section_id
         * The section ID targeted by the operation.
         *
-        * @property {string} 2
+        * @property {string} parameter
         * The meaning of the third element depends on the operation.
         * - For `add` it is type of the section that has been added
         * - For `set` it either is the option name if a fourth element exists,
@@ -1040,7 +1057,7 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
         *   a section has been renamed to if the change entry only contains
         *   three elements.
         *
-        * @property {string} 4
+        * @property {string=} value
         * The meaning of the fourth element depends on the operation.
         * - For `set` it is the value an option has been set to.
         * - For `list-add` it is the new value that has been added to a
@@ -1052,7 +1069,7 @@ return baseclass.extend(/** @lends LuCI.uci.prototype */ {
        /**
         * Fetches uncommitted UCI changes from the remote `ubus` RPC api.
         *
-        * @method
+        * @function
         * @returns {Promise<Object<string, Array<LuCI.uci.ChangeRecord>>>}
         * Returns a promise resolving to an object containing the configuration
         * names as keys and arrays of related change records as values.
index 45de90750473d27d8d53f6305b36a2f99baa9574..b3259d219900cce7c1057eb23ceaf7ca76ab728d 100644 (file)
@@ -14,6 +14,11 @@ let tooltipDiv = null;
 let indicatorDiv = null;
 let tooltipTimeout = null;
 
+/**
+ * @namespace LuCI.ui
+ * @memberof LuCI
+ */
+
 /**
  * @class AbstractElement
  * @memberof LuCI.ui
@@ -55,7 +60,7 @@ const UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype
         * It defaults to `string` which will allow any value.
         * See {@link LuCI.validation} for details on the expression format.
         *
-        * @property {function|function[]} [validator]
+        * @property {function()|Array<function()>} [validator]
         * Specifies one or more custom validator functions which are invoked after
         * the standard validation constraints are checked. Each function should
         * return `true` to accept the given input value. When multiple functions
@@ -179,6 +184,7 @@ const UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype
         *
         * @instance
         * @memberof LuCI.ui.AbstractElement
+        * @returns {boolean}
         */
        triggerValidation() {
                if (typeof(this.vfunc) != 'function')
@@ -295,7 +301,7 @@ const UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype
 
        /**
         * Render the widget, set up event listeners and return resulting markup.
-        *
+        * @abstract
         * @instance
         * @memberof LuCI.ui.AbstractElement
         *
@@ -309,7 +315,7 @@ const UIElement = baseclass.extend(/** @lends LuCI.ui.AbstractElement.prototype
 /**
  * Instantiate a text input widget.
  *
- * @constructor Textfield
+ * @class Textfield
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -339,6 +345,8 @@ const UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.Textfield
+        * @param {string} value
+        * @param {object} options
         *
         * @property {boolean} [password=false]
         * Specifies whether the input should be rendered as concealed password field.
@@ -410,7 +418,11 @@ const UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
                return this.bind(frameEl);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} frameEl
+        * @returns {Node}
+        */
        bind(frameEl) {
                const inputEl = frameEl.querySelector('input');
 
@@ -440,7 +452,7 @@ const UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
 /**
  * Instantiate a textarea widget.
  *
- * @constructor Textarea
+ * @class Textarea
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -470,6 +482,8 @@ const UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ {
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.Textarea
+        * @param {string} value
+        * @param {object} options
         *
         * @property {boolean} [readonly=false]
         * Specifies whether the input widget should be rendered readonly.
@@ -528,7 +542,12 @@ const UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ {
                return this.bind(frameEl);
        },
 
-       /** @private */
+
+       /**
+        * @private
+        * @param {Node} frameEl
+        * @returns {Node}
+        */
        bind(frameEl) {
                const inputEl = frameEl.firstElementChild;
 
@@ -556,7 +575,7 @@ const UITextarea = UIElement.extend(/** @lends LuCI.ui.Textarea.prototype */ {
 /**
  * Instantiate a checkbox widget.
  *
- * @constructor Checkbox
+ * @class Checkbox
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -586,6 +605,8 @@ const UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ {
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.Checkbox
+        * @param {string} value
+        * @param {object} options
         *
         * @property {string} [value_enabled=1]
         * Specifies the value corresponding to a checked checkbox.
@@ -652,7 +673,11 @@ const UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ {
                return this.bind(frameEl);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} frameEl
+        * @returns {Node}
+        */
        bind(frameEl) {
                this.node = frameEl;
 
@@ -693,7 +718,7 @@ const UICheckbox = UIElement.extend(/** @lends LuCI.ui.Checkbox.prototype */ {
 /**
  * Instantiate a select dropdown or checkbox/radiobutton group.
  *
- * @constructor Select
+ * @class Select
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -730,6 +755,9 @@ const UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ {
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.Select
+        * @param {string} value
+        * @param {object} choices
+        * @param {object} options
         *
         * @property {boolean} [multiple=false]
         * Specifies whether multiple choice values may be selected.
@@ -847,7 +875,11 @@ const UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ {
                return this.bind(frameEl);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} frameEl
+        * @returns {Node}
+        */
        bind(frameEl) {
                this.node = frameEl;
 
@@ -902,7 +934,7 @@ const UISelect = UIElement.extend(/** @lends LuCI.ui.Select.prototype */ {
 /**
  * Instantiate a rich dropdown choice widget.
  *
- * @constructor Dropdown
+ * @class Dropdown
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -938,6 +970,9 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.Dropdown
+        * @param {string} value
+        * @param {object} choices
+        * @param {object} options
         *
         * @property {boolean} [optional=true]
         * Specifies whether the dropdown selection is optional. In contrast to
@@ -976,7 +1011,7 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
         * which is used to enter custom choice values. This should not normally
         * be used except by widgets derived from the Dropdown class.
         *
-        * @property {string} [create_template=script[type="item-template"]]
+        * @property {string} [create_template="script[type='item-template']"]
         * Specifies a CSS selector expression used to find an HTML element
         * serving as template for newly added custom choice values.
         *
@@ -1112,7 +1147,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                return this.bind(sb);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @returns {Node}
+        */
        bind(sb) {
                const o = this.options;
 
@@ -1225,7 +1264,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                return sb;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} element
+        * @returns {document}
+        */
        getScrollParent(element) {
                let parent = element;
                let style = getComputedStyle(element);
@@ -1247,7 +1290,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                return document.body;
        },
 
-       /** @private */
+
+       /**
+        * @private
+        * @param {Node} sb
+        */
        openDropdown(sb) {
                const st = window.getComputedStyle(sb, null);
                const ul = sb.querySelector('ul');
@@ -1362,7 +1409,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                ul.addEventListener('transitionend', focusFn);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {boolean} no_focus
+        */
        closeDropdown(sb, no_focus) {
                if (!sb.hasAttribute('open'))
                        return;
@@ -1391,7 +1442,12 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                this.saveValues(sb, ul);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {Node} li list item
+        * @param {boolean} force_state
+        */
        toggleItem(sb, li, force_state) {
                const ul = li.parentNode;
 
@@ -1474,7 +1530,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                this.saveValues(sb, ul);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {Node} li list item
+        */
        transformItem(sb, li) {
                const cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' }));
                const label = E('label');
@@ -1486,7 +1546,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                li.appendChild(label);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {Node} ul unordered list
+        */
        saveValues(sb, ul) {
                const sel = ul.querySelectorAll('li[selected]');
                const div = sb.lastElementChild;
@@ -1536,7 +1600,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                }));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {string[]} values
+        */
        setValues(sb, values) {
                const ul = sb.querySelector('ul');
 
@@ -1573,7 +1641,12 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {Node} elem
+        * @param {boolean} scroll
+        */
        setFocus(sb, elem, scroll) {
                if (sb.hasAttribute('locked-in'))
                        return;
@@ -1590,7 +1663,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                elem.focus();
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleMouseout(ev) {
                const sb = ev.currentTarget;
 
@@ -1604,7 +1680,13 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                sb.querySelector('ul.dropdown').focus();
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {string} value
+        * @param {string} label
+        * @returns {Node}
+        */
        createChoiceElement(sb, value, label) {
                const tpl = sb.querySelector(this.options.create_template);
                let markup = null;
@@ -1635,7 +1717,11 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                return new_item;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {string} value
+        */
        createItems(sb, value) {
                const sbox = this;
                let val = (value ?? '').trim();
@@ -1760,7 +1846,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleClick(ev) {
                const sb = ev.currentTarget;
 
@@ -1782,7 +1871,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                ev.stopPropagation();
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleKeydown(ev) {
                const sb = ev.currentTarget;
                const ul = sb.querySelector('ul.dropdown');
@@ -1855,14 +1947,20 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleDropdownClose(ev) {
                const sb = ev.currentTarget;
 
                this.closeDropdown(sb, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleDropdownSelect(ev) {
                const sb = ev.currentTarget;
                const li = findParent(ev.target, 'li');
@@ -1874,7 +1972,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                this.closeDropdown(sb, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleMouseover(ev) {
                const sb = ev.currentTarget;
 
@@ -1887,7 +1988,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                        this.setFocus(sb, li);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleFocus(ev) {
                const sb = ev.currentTarget;
 
@@ -1897,12 +2001,18 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleCanaryFocus(ev) {
                this.closeDropdown(ev.currentTarget.parentNode);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleCreateKeydown(ev) {
                const input = ev.currentTarget;
                const li = findParent(input, 'li');
@@ -1936,7 +2046,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleCreateFocus(ev) {
                const input = ev.currentTarget;
                const li = findParent(input, 'li');
@@ -1950,7 +2063,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                this.setFocus(sb, li, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleCreateBlur(ev) {
                const input = ev.currentTarget;
                const cbox = findParent(input, 'li').querySelector('input[type="checkbox"]');
@@ -1962,7 +2078,10 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
                sb.removeAttribute('locked-in');
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleCreateClick(ev) {
                ev.currentTarget.querySelector(this.options.create_query).focus();
        },
@@ -2010,7 +2129,7 @@ const UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
 /**
  * Instantiate a rich dropdown choice widget allowing custom values.
  *
- * @constructor Combobox
+ * @class Combobox
  * @memberof LuCI.ui
  * @augments LuCI.ui.Dropdown
  *
@@ -2049,16 +2168,19 @@ const UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ {
         *
         * @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
         * @memberof LuCI.ui.Combobox
+        * @param {string} value
+        * @param {object} choices
+        * @param {object} options
         *
-        * @property {boolean} multiple=false
+        * @property {boolean} [multiple=false]
         * Since Comboboxes never allow selecting multiple values, this property
         * is forcibly set to `false`.
         *
-        * @property {boolean} create=true
+        * @property {boolean} [create=true]
         * Since Comboboxes always allow custom choice values, this property is
         * forcibly set to `true`.
         *
-        * @property {boolean} optional=true
+        * @property {boolean} [optional=true]
         * Since Comboboxes are always optional, this property is forcibly set to
         * `true`.
         */
@@ -2079,7 +2201,7 @@ const UICombobox = UIDropdown.extend(/** @lends LuCI.ui.Combobox.prototype */ {
 /**
  * Instantiate a combo button widget offering multiple action choices.
  *
- * @constructor ComboButton
+ * @class ComboButton
  * @memberof LuCI.ui
  * @augments LuCI.ui.Dropdown
  *
@@ -2117,16 +2239,19 @@ const UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype
         *
         * @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
         * @memberof LuCI.ui.ComboButton
+        * @param {string} value
+        * @param {object} choices
+        * @param {object} options
         *
-        * @property {boolean} multiple=false
+        * @property {boolean} [multiple=false]
         * Since ComboButtons never allow selecting multiple actions, this property
         * is forcibly set to `false`.
         *
-        * @property {boolean} create=false
+        * @property {boolean} [create=false]
         * Since ComboButtons never allow creating custom choices, this property
         * is forcibly set to `false`.
         *
-        * @property {boolean} optional=false
+        * @property {boolean} [optional=false]
         * Since ComboButtons must always select one action, this property is
         * forcibly set to `false`.
         *
@@ -2139,7 +2264,7 @@ const UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype
         * This is useful to apply different button styles, such as colors, to the
         * combined button depending on the selected action.
         *
-        * @property {function} [click]
+        * @property {function()} [click]
         * Specifies a handler function to invoke when the user clicks the button.
         * This function will be called with the button DOM node as `this` context
         * and receive the DOM click event as first as well as the selected action
@@ -2166,7 +2291,12 @@ const UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype
                return node;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @param {...*} args
+        * @returns {null}
+        */
        handleClick(ev, ...args) {
                const sb = ev.currentTarget;
                const t = ev.target;
@@ -2178,7 +2308,12 @@ const UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype
                        return this.options.click.call(sb, ev, this.getValue());
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} sb
+        * @param {...*} args
+        * @returns {*}
+        */
        toggleItem(sb, ...args) {
                const rv = UIDropdown.prototype.toggleItem.call(this, sb, ...args);
                const val = this.getValue();
@@ -2195,7 +2330,7 @@ const UIComboButton = UIDropdown.extend(/** @lends LuCI.ui.ComboButton.prototype
 /**
  * Instantiate a dynamic list widget.
  *
- * @constructor DynamicList
+ * @class DynamicList
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -2235,12 +2370,15 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
         *
         * @typedef {LuCI.ui.Dropdown.InitOptions} InitOptions
         * @memberof LuCI.ui.DynamicList
+        * @param {string} values
+        * @param {object} choices
+        * @param {object} options
         *
-        * @property {boolean} multiple=false
+        * @property {boolean} [multiple=false]
         * Since dynamic lists never allow selecting multiple choices when adding
         * another list item, this property is forcibly set to `false`.
         *
-        * @property {boolean} optional=true
+        * @property {boolean} [optional=true]
         * Since dynamic lists use an embedded dropdown to present a list of
         * predefined choice values, the dropdown must be made optional to allow
         * it to remain unselected.
@@ -2307,7 +2445,10 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                return this.bind(dl);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} dl
+        */
        initDragAndDrop(dl) {
                let draggedItem = null;
                let placeholder = null;
@@ -2396,7 +2537,11 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} dl
+        * @returns {Node}
+        */
        bind(dl) {
                dl.addEventListener('click', L.bind(this.handleClick, this));
                dl.addEventListener('keydown', L.bind(this.handleKeydown, this));
@@ -2412,7 +2557,13 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                return dl;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} dl
+        * @param {string} value
+        * @param {string} text
+        * @param {boolean} flash
+        */
        addItem(dl, value, text, flash) {
                let exists = false;
 
@@ -2444,7 +2595,11 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                this.dispatchCbiDynlistChange(dl,value);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} dl
+        * @param {string} value
+        */
        dispatchCbiDynlistChange(dl,value) {
                dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
                        bubbles: true,
@@ -2457,7 +2612,11 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                }));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} dl
+        * @param {Node} item
+        */
        removeItem(dl, item) {
                const value = item.querySelector('input[type="hidden"]').value;
                const sb = dl.querySelector('.cbi-dropdown');
@@ -2476,7 +2635,10 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                this.dispatchCbiDynlistChange(dl, value);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleClick(ev) {
                const dl = ev.currentTarget;
                const item = findParent(ev.target, '.item');
@@ -2506,7 +2668,10 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleDropdownChange(ev) {
                const dl = ev.currentTarget;
                const sbIn = ev.detail.instance;
@@ -2537,7 +2702,10 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
                this.addItem(dl, sbVal.value, label, true);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleKeydown(ev) {
                const dl = ev.currentTarget;
                const item = findParent(ev.target, '.item');
@@ -2651,7 +2819,7 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
 /**
  * Instantiate a range slider widget.
  *
- * @constructor RangeSlider
+ * @class RangeSlider
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -2675,25 +2843,27 @@ const UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype
  * @param {LuCI.ui.RangeSlider.InitOptions} [options]
  * Object describing the widget specific options to initialize the range slider.
  */
-const UIRangeSlider = UIElement.extend({
+const UIRangeSlider = UIElement.extend( /** @lends LuCI.ui.RangeSlider.prototype */ {
        /**
         * In addition to the [AbstractElement.InitOptions]{@link LuCI.ui.AbstractElement.InitOptions}
         * the following properties are recognized:
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.RangeSlider
+        * @param {string} value
+        * @param {object} options
         *
-        * @property {int} [min=1]
+        * @property {number} [min=1]
         * Specifies the minimum value of the range.
         *
-        * @property {int} [max=100]
+        * @property {number} [max=100]
         * Specifies the maximum value of the range.
         *
         * @property {string} [step=1]
         * Specifies the step value of the range slider handle. Use "any" for
         * arbitrary precision floating point numbers.
         *
-        * @param {function} [calculate=null]
+        * @param {function()} [calculate=null]
         * A function to invoke when the slider is adjusted by the user. The function
         * performs a calculation on the selected value to produce a new value.
         *
@@ -2782,6 +2952,7 @@ const UIRangeSlider = UIElement.extend({
         *
         * @instance
         * @memberof LuCI.ui.RangeSlider
+        * @returns {number}
         */
        getCalculatedValue() {
                return this.calculatedvalue;
@@ -2802,7 +2973,7 @@ const UIRangeSlider = UIElement.extend({
 /**
  * Instantiate a hidden input field widget.
  *
- * @constructor Hiddenfield
+ * @class Hiddenfield
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -2845,7 +3016,11 @@ const UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype
                return this.bind(hiddenEl);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} hiddenEl
+        * @returns {Node} hiddenEl
+        */
        bind(hiddenEl) {
                this.node = hiddenEl;
 
@@ -2868,7 +3043,7 @@ const UIHiddenfield = UIElement.extend(/** @lends LuCI.ui.Hiddenfield.prototype
 /**
  * Instantiate a file upload widget.
  *
- * @constructor FileUpload
+ * @class FileUpload
  * @memberof LuCI.ui
  * @augments LuCI.ui.AbstractElement
  *
@@ -2900,6 +3075,8 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
         *
         * @typedef {LuCI.ui.AbstractElement.InitOptions} InitOptions
         * @memberof LuCI.ui.FileUpload
+        * @param {string} value
+        * @param {object} options
         *
         * @property {boolean} [browser=false]
         * Use a file browser mode.
@@ -2954,7 +3131,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                }, options);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} browserEl
+        * @returns {Node} hiddenEl
+        */
        bind(browserEl) {
                this.node = browserEl;
 
@@ -3011,7 +3192,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                return renderFileBrowser
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @returns {string}
+        */
        truncatePath(path) {
                if (path.length > 50)
                        path = `${path.substring(0, 25)}…${path.substring(path.length - 25)}`;
@@ -3019,7 +3204,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                return path;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} type
+        * @returns {Node}
+        */
        iconForType(type) {
                switch (type) {
                case 'symlink':
@@ -3048,7 +3237,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @returns {string}
+        */
        canonicalizePath(path) {
        return path.replace(/\/{2,}/g, '/')                // Collapse multiple slashes
                                .replace(/\/\.(\/|$)/g, '/')           // Remove `/.`
@@ -3056,7 +3249,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                                .replace(/\/$/g, (m, o, s) => s.length > 1 ? '' : '/'); // Remove trailing `/` only if not root
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @returns {string[]}
+        */
        splitPath(path) {
                const croot = this.canonicalizePath(this.options.root_directory ?? '/');
                const cpath = this.canonicalizePath(path ?? '/');
@@ -3071,7 +3268,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                return parts;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @param {Event} ev
+        */
        handleCreateDirectory(path, ev) {
                const container = E('div', { 'class': 'uci-dialog' });
 
@@ -3123,7 +3324,13 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                ]);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @param {object[]} list
+        * @param {Event} ev
+        * @returns {Promise}
+        */
        handleUpload(path, list, ev) {
                const form = ev.target.parentNode;
                const fileinput = form.querySelector('input[type="file"]');
@@ -3162,7 +3369,13 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                }, this, path, ev));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @param {object} fileStat
+        * @param {Event} ev
+        * @returns {Promise}
+        */
        handleDelete(path, fileStat, ev) {
                const parent = path.replace(/\/[^\/]+$/, '') ?? '/';
                const name = path.replace(/^.+\//, '');
@@ -3192,7 +3405,12 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @param {object[]} list
+        * @returns {Promise}
+        */
        renderUpload(path, list) {
                if (!this.options.enable_upload)
                        return E([]);
@@ -3239,7 +3457,12 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                ]);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} container
+        * @param {string} path
+        * @param {object[]} list
+        */
        renderListing(container, path, list) {
                const breadcrumb = E('p');
                const rows = E('ul');
@@ -3337,7 +3560,10 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                ]);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleCancel(ev) {
                const button = this.node.firstElementChild;
                const browser = button.nextElementSibling;
@@ -3350,7 +3576,10 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                ev.preventDefault();
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleReset(ev) {
                const button = this.node.firstElementChild;
                const hidden = this.node.lastElementChild;
@@ -3361,7 +3590,12 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                this.handleCancel(ev);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @param {object} fileStat
+        * @param {Event} ev
+        */
        handleDownload(path, fileStat, ev) {
                fs.read_direct(path, 'blob').then((blob) => {
                        const url = window.URL.createObjectURL(blob);
@@ -3377,7 +3611,12 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                });
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {string} path
+        * @param {object} fileStat
+        * @param {Event} ev
+        */
        handleSelect(path, fileStat, ev) {
                const browser = dom.parent(ev.target, '.cbi-filebrowser');
                const ul = browser.querySelector('ul');
@@ -3406,7 +3645,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        * @returns {Promise}
+        */
        handleFileBrowser(ev) {
                const button = ev.target;
                const browser = button.nextElementSibling;
@@ -3440,7 +3683,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
        }
 });
 
-
+/**
+ * Erase the menu node
+ * @param {Node} node
+ * @returns {Node}
+ */
 function scrubMenu(node) {
        let hasSatisfiedChild = false;
 
@@ -3464,7 +3711,7 @@ function scrubMenu(node) {
 /**
  * Handle menu.
  *
- * @constructor menu
+ * @class menu
  * @memberof LuCI.ui
  *
  * @classdesc
@@ -3475,13 +3722,17 @@ const UIMenu = baseclass.singleton(/** @lends LuCI.ui.menu.prototype */ {
        /**
         * @typedef {Object} MenuNode
         * @memberof LuCI.ui.menu
-
-        * @property {string} name - The internal name of the node, as used in the URL
+        * @property {string} name - The internal name of the node, as used in the
+        * URL.
         * @property {number} order - The sort index of the menu node
-        * @property {string} [title] - The title of the menu node, `null` if the node should be hidden
-        * @property {satisfied} boolean - Boolean indicating whether the menu entries dependencies are satisfied
-        * @property {readonly} [boolean] - Boolean indicating whether the menu entries underlying ACLs are readonly
-        * @property {LuCI.ui.menu.MenuNode[]} [children] - Array of child menu nodes.
+        * @property {string} [title] - The title of the menu node, `null` if the
+        * node should be hidden.
+        * @property {boolean} satisfied - Boolean indicating whether the menu
+        * entries dependencies are satisfied.
+        * @property {boolean} [readonly] - Boolean indicating whether the menu
+        * entries underlying ACLs are readonly.
+        * @property {LuCI.ui.menu.MenuNode[]} [children] - Array of child menu 
+        * nodes.
         */
 
        /**
@@ -3712,7 +3963,10 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
                return this.node;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Node} node
+        */
        initFromMarkup(node) {
                if (!dom.elem(node))
                        node = document.querySelector(node);
@@ -3743,8 +3997,13 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
                this.options = options;
        },
 
-       /** @private */
-       deriveSortKey(value, index) {
+       /**
+        * @private
+        * @param {string} value
+        * @param {number} index
+        * @returns {string}
+        */
+       deriveSortKey(value, index) {
                const opts = this.options ?? {};
                let hint;
                let m;
@@ -3822,7 +4081,10 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @returns {?string}
+        */
        getActiveSortState() {
                if (this.sortState)
                        return this.sortState;
@@ -3840,7 +4102,11 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
                return null;
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {number} index
+        * @param {boolean} descending
+        */
        setActiveSortState(index, descending) {
                this.sortState = [ index, descending ];
 
@@ -3859,7 +4125,10 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
                session.setLocalData('tablesort', state);
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        handleSort(ev) {
                if (!ev.target.matches('th[data-sortable-row]'))
                        return;
@@ -3985,7 +4254,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                modalDiv.blur();
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        cancelModal(ev) {
                if (ev.key == 'Escape') {
                        const btn = modalDiv.querySelector('.right > button, .right > .btn, .button-row > .btn');
@@ -3995,7 +4267,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                }
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        showTooltip(ev) {
                const target = findParent(ev.target, '[data-tooltip]');
 
@@ -4042,7 +4317,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                }));
        },
 
-       /** @private */
+       /**
+        * @private
+        * @param {Event} ev
+        */
        hideTooltip(ev) {
                if (ev.target === tooltipDiv || ev.relatedTarget === tooltipDiv ||
                    tooltipDiv.contains(ev.target) || tooltipDiv.contains(ev.relatedTarget))
@@ -4149,7 +4427,7 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
         * to the `dom.content()` function - refer to its documentation for
         * applicable values.
         * 
-        * @param {int} [timeout]
+        * @param {number} [timeout]
         * A millisecond value after which the notification will disappear
         * automatically. If omitted, the notification will remain until it receives
         * the click event.
@@ -4164,6 +4442,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
        addTimeLimitedNotification(title, children, timeout, ...classes) {
                const msg = this.addNotification(title, children, ...classes);
 
+               /**
+                * Set node parameters to fade the notification out after timeout.
+                * @param {Element} element
+                */
                function fadeOutNotification(element) {
                        if (element) {
                                element.classList.add('fade-out');
@@ -4204,7 +4486,7 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
         * @param {string} label
         * The text to display in the indicator label.
         *
-        * @param {function} [handler]
+        * @param {function()} [handler]
         * A handler function to invoke when the indicator label is clicked/touched
         * by the user. If omitted, the indicator is not clickable/touchable.
         *
@@ -4483,7 +4765,11 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        return dom.isEmpty(pane, n => n.classList.contains('cbi-tab-descr'));
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {object} pane
+                * @returns {string}
+                */
                getPathForPane(pane) {
                        const path = [];
                        let node = null;
@@ -4501,7 +4787,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        return path.join('/');
                },
 
-               /** @private */
+               /**
+                * @private
+                * @returns {object}
+                */
                getActiveTabState() {
                        const page = document.body.getAttribute('data-page');
                        const state = session.getLocalData('tab');
@@ -4514,13 +4803,22 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        return { page: page, paths: {} };
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {object} pane
+                * @returns {string|0}
+                */
                getActiveTabId(pane) {
                        const path = this.getPathForPane(pane);
                        return +this.getActiveTabState().paths[path] ?? 0;
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {object} pane
+                * @param {number} tabIndex
+                * @returns {object}
+                */
                setActiveTabId(pane, tabIndex) {
                        const path = this.getPathForPane(pane);
                        const state = this.getActiveTabState();
@@ -4530,7 +4828,11 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        return session.setLocalData('tab', state);
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {Event} ev
+                * @param {document} root
+                */
                updateTabs(ev, root) {
                        (root ?? document).querySelectorAll('[data-tab-title]').forEach(L.bind((pane) => {
                                const menu = pane.parentNode.previousElementSibling;
@@ -4561,7 +4863,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        }, this));
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {Event} ev
+                */
                switchTab(ev) {
                        const tab = ev.target.parentNode;
                        const name = tab.getAttribute('data-tab');
@@ -4602,7 +4907,6 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
        /**
         * @typedef {Object} FileUploadReply
         * @memberof LuCI.ui
-
         * @property {string} name - Name of the uploaded file without directory components
         * @property {number} size - Size of the uploaded file in bytes
         * @property {string} checksum - The MD5 checksum of the received file data
@@ -4647,7 +4951,7 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
 
                                                                dom.content(body, [
                                                                        E('ul', {}, [
-                                                                               E('li', {}, [ '%s: %s'.format(_('Name'), file.name.replace(/^.*[\\\/]/, '')) ]),
+                                                                               E('li', {}, [ '%s: %s'.format(_('Name'), file.name.replace(/^.*[\\/]/, '')) ]),
                                                                                E('li', {}, [ '%s: %1024mB'.format(_('Size'), file.size) ])
                                                                        ])
                                                                ]);
@@ -4964,7 +5268,11 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        dlg.classList.add('uci-dialog');
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {string} type
+                * @param {string} content
+                */
                displayStatus(type, content) {
                        if (type) {
                                const message = UI.prototype.showModal('', '');
@@ -4988,7 +5296,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        }
                },
 
-               /** @private */
+               /**
+                * @private
+                * @returns {Promise}
+                */
                checkConnectivityAffected() {
                        return L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr', null, 'json')).then(L.bind((info) => {
                                if (L.isObject(info) && Array.isArray(info.inbound_interfaces)) {
@@ -5009,7 +5320,10 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        }, this));
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {boolean} checked
+                */
                rollback(checked) {
                        if (checked) {
                                this.displayStatus('warning spinning',
@@ -5060,7 +5374,12 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                        }
                },
 
-               /** @private */
+               /**
+                * @private
+                * @param {boolean} checked
+                * @param {number} deadline
+                * @param {string} override_token
+                */
                confirm(checked, deadline, override_token) {
                        let tt;
                        let ts = Date.now();
@@ -5136,7 +5455,7 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
                 * @param {boolean} [checked=false]
                 * Whether to perform a checked (`true`) configuration apply or an
                 * unchecked (`false`) one.
-
+                *
                 * In case of a checked apply, the configuration changes must be
                 * confirmed within a specific time interval, otherwise the device
                 * will begin to roll back the changes in order to restore the previous
@@ -5269,7 +5588,7 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
         * If an input element is not marked optional it must not be empty,
         * otherwise it will be marked as invalid.
         *
-        * @param {function|function[]} [vfunc]
+        * @param {function()|Array<function()>} [vfunc]
         * Specifies a custom validation function or an array of validation functions
         * which are invoked after the other validation constraints are applied. Each
         * function must return `true` to accept the passed value. When multiple
@@ -5283,7 +5602,7 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
         * validation. If omitted, the `keyup` and `blur` events are bound by
         * default.
         *
-        * @returns {function}
+        * @returns {function()}
         * Returns the compiled validator function which can be used to trigger
         * field validation manually or to bind it to further events.
         *
@@ -5323,17 +5642,17 @@ const UI = baseclass.extend(/** @lends LuCI.ui.prototype */ {
         * @param {*} ctx
         * The `this` context to use for the wrapped function.
         *
-        * @param {function|string} fn
+        * @param {function()|string} fn
         * Specifies the function to wrap. In case of a function value, the
         * function is used as-is. If a string is specified instead, it is looked
         * up in `ctx` to obtain the function to wrap. In both cases the bound
         * function will be invoked with `ctx` as `this` context
         *
-        * @param {...*} extra_args
+        * @param {...*} args
         * Any further parameter as passed as-is to the bound event handler
         * function in the same order as passed to `createHandlerFn()`.
         *
-        * @returns {function|null}
+        * @returns {?function()}
         * Returns the pre-bound handler function which is suitable to be passed
         * to `addEventListener()`. Returns `null` if the given `fn` argument is
         * a string which could not be found in `ctx` or if `ctx[fn]` is not a
git clone https://git.99rst.org/PROJECT