UNPKG

jsdom

Version:

A JavaScript implementation of many web standards

195 lines (164 loc) • 5.1 kB
"use strict"; const HTMLElementImpl = require("./HTMLElement-impl").implementation; const NODE_TYPE = require("../node-type"); const { stripAndCollapseASCIIWhitespace } = require("../helpers/strings"); const { HTML_NS, SVG_NS } = require("../helpers/namespaces"); const { closest } = require("../helpers/traversal"); const { formOwner } = require("../helpers/form-controls"); class HTMLOptionElementImpl extends HTMLElementImpl { #cachedNearestAncestorSelect = null; #selectedness = false; constructor(globalObject, args, privateData) { super(globalObject, args, privateData); this._dirtyness = false; } _insertionSteps() { this.#updateNearestAncestorSelect(); } _removingSteps(isSubtreeRoot, oldAncestor) { super._removingSteps(isSubtreeRoot, oldAncestor); this.#updateNearestAncestorSelect(); } // https://html.spec.whatwg.org/multipage/form-elements.html#update-an-option%27s-nearest-ancestor-select #updateNearestAncestorSelect() { const oldSelect = this.#cachedNearestAncestorSelect; const newSelect = this.#selectNode; if (oldSelect !== newSelect) { if (oldSelect !== null) { oldSelect._askedForAReset(); } if (newSelect !== null) { let canSkipReset = false; if (this.parentNode === newSelect && !this._selectedness) { const { firstElementChild } = newSelect; // Adding an unselected direct child cannot change an already-selected first option. canSkipReset = firstElementChild._localName === "option" && firstElementChild._selectedness; } if (!canSkipReset) { newSelect._askedForAReset(); } } } this.#cachedNearestAncestorSelect = newSelect; } #removeOtherSelectedness() { // Remove the selectedness flag from all other options in this select const select = this.#selectNode; if (select && !select.hasAttributeNS(null, "multiple")) { for (const option of select.options) { if (option !== this) { option._selectedness = false; } } } } #askForAReset() { const select = this.#selectNode; if (select) { select._askedForAReset(); } } _attributeChangeSteps(localName, oldValue, value, namespace) { super._attributeChangeSteps(localName, oldValue, value, namespace); if (namespace === null && !this._dirtyness && localName === "selected") { this._selectedness = this.hasAttributeNS(null, "selected"); if (this._selectedness) { this.#removeOtherSelectedness(); } this.#askForAReset(); } } get #selectNode() { let select = this.parentNode; if (!select) { return null; } if (select._localName !== "select") { select = select.parentNode; if (!select || select._localName !== "select") { return null; } } return select; } get form() { return formOwner(this); } get text() { return stripAndCollapseASCIIWhitespace(childTextContentExcludingDescendantsOfScript(this)); } set text(value) { this.textContent = value; } // https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-value _getValue() { if (this.hasAttributeNS(null, "value")) { return this.getAttributeNS(null, "value"); } return this.text; } get value() { return this._getValue(); } set value(value) { this.setAttributeNS(null, "value", value); } get index() { const select = closest(this, "select"); if (select === null) { return 0; } return select.options.indexOf(this); } get selected() { return this._selectedness; } set selected(s) { this._dirtyness = true; this._selectedness = Boolean(s); if (this._selectedness) { this.#removeOtherSelectedness(); } this.#askForAReset(); } get _selectedness() { return this.#selectedness; } set _selectedness(value) { if (this.#selectedness !== value) { this.#selectedness = value; this._invalidateSelectorState(); for (let ancestor = this.parentNode; ancestor !== null; ancestor = ancestor.parentNode) { if (ancestor._namespaceURI === HTML_NS && ancestor._localName === "select") { ancestor._selectednessChanged(); } } } } get label() { if (this.hasAttributeNS(null, "label")) { return this.getAttributeNS(null, "label"); } return this.text; } set label(value) { this.setAttributeNS(null, "label", value); } } function childTextContentExcludingDescendantsOfScript(root) { let text = ""; for (const child of root._children()) { if (child._localName === "script" && (child._namespaceURI === HTML_NS || child._namespaceURI === SVG_NS)) { continue; } if (child.nodeType === NODE_TYPE.TEXT_NODE || child.nodeType === NODE_TYPE.CDATA_SECTION_NODE) { text += child.nodeValue; } else { text += childTextContentExcludingDescendantsOfScript(child); } } return text; } module.exports = { implementation: HTMLOptionElementImpl };