UNPKG

svgedit

Version:

Powerful SVG-Editor for your browser

789 lines (665 loc) 32.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: extensions/ext-connector/ext-connector.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: extensions/ext-connector/ext-connector.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/** * @file ext-connector.js * * @license MIT * * @copyright 2010 Alexis Deveria * @copyright 2023 Optimistik SAS * */ const name = 'connector' const loadExtensionTranslation = async function (svgEditor) { let translationModule const lang = svgEditor.configObj.pref('lang') try { translationModule = await import(`./locale/${lang}.js`) } catch (_error) { // eslint-disable-next-line no-console console.warn(`Missing translation (${lang}) for ${name} - using 'en'`) translationModule = await import('./locale/en.js') } svgEditor.i18next.addResourceBundle(lang, name, translationModule.default) } export default { name, async init (S) { const svgEditor = this const { svgCanvas } = svgEditor const { getElement, $id, $click, addSVGElementsFromJson } = svgCanvas const { svgroot, selectorManager } = S const seNs = svgCanvas.getEditorNS() await loadExtensionTranslation(svgEditor) let startX let startY let curLine let startElem let endElem let started = false let connections = [] // Save the original groupSelectedElements method const originalGroupSelectedElements = svgCanvas.groupSelectedElements // Override the original groupSelectedElements to exclude connectors svgCanvas.groupSelectedElements = function (...args) { // Remove connectors from selection svgCanvas.removeFromSelection(document.querySelectorAll('[id^="conn_"]')) // Call the original method return originalGroupSelectedElements.apply(this, args) } // Save the original moveSelectedElements method const originalMoveSelectedElements = svgCanvas.moveSelectedElements // Override the original moveSelectedElements to handle connectors svgCanvas.moveSelectedElements = function (...args) { // Call the original method and store its result const cmd = originalMoveSelectedElements.apply(this, args) // Update connectors updateConnectors(svgCanvas.getSelectedElements()) // Return the result of the original method return cmd } /** * getBBintersect * @param {Float} x * @param {Float} y * @param {module:utilities.BBoxObject} bb * @param {Float} offset * @returns {module:math.XYObject} */ const getBBintersect = (x, y, bb, offset) => { // Adjust bounding box if offset is provided if (offset) { bb = { ...bb } // Create a shallow copy bb.width += offset bb.height += offset bb.x -= offset / 2 bb.y -= offset / 2 } // Calculate center of bounding box const midX = bb.x + bb.width / 2 const midY = bb.y + bb.height / 2 // Calculate lengths from (x, y) to center const lenX = x - midX const lenY = y - midY // Calculate slope of line from (x, y) to center const slope = Math.abs(lenY / lenX) // Calculate ratio to find intersection point let ratio if (slope &lt; bb.height / bb.width) { ratio = bb.width / 2 / Math.abs(lenX) } else { ratio = lenY ? bb.height / 2 / Math.abs(lenY) : 0 } // Calculate intersection point return { x: midX + lenX * ratio, y: midY + lenY * ratio } } /** * getOffset * @param {"start"|"end"} side - The side of the line ("start" or "end") where the marker may be present. * @param {Element} line - The line element to check for a marker. * @returns {Float} - Returns the calculated offset if a marker is present, otherwise returns 0. */ const getOffset = (side, line) => { // Check for marker attribute on the given side ("marker-start" or "marker-end") const hasMarker = line.getAttribute('marker-' + side) // Calculate size based on stroke-width, multiplied by a constant factor (here, 5) // TODO: This factor should ideally be based on the actual size of the marker. const size = line.getAttribute('stroke-width') * 5 // Return calculated size if marker is present, otherwise return 0. return hasMarker ? size : 0 } /** * showPanel * @param {boolean} on - Determines whether to show or hide the elements. * @returns {void} */ const showPanel = on => { // Find the 'connector_rules' or create it if it doesn't exist. let connRules = $id('connector_rules') if (!connRules) { connRules = document.createElement('style') connRules.setAttribute('id', 'connector_rules') document.getElementsByTagName('head')[0].appendChild(connRules) } // Update the content of &lt;style> element to either hide or show certain elements. connRules.textContent = !on ? '' : '#tool_clone, #tool_topath, #tool_angle, #xy_panel { display: none !important; }' // Update the display property of the &lt;style> element itself based on the 'on' value. if ($id('connector_rules')) { $id('connector_rules').style.display = on ? 'block' : 'none' } } /** * setPoint * @param {Element} elem - The SVG element. * @param {Integer|"end"} pos - The position index or "end". * @param {Float} x - The x-coordinate. * @param {Float} y - The y-coordinate. * @param {boolean} [setMid] - Whether to set the midpoint. * @returns {void} */ const setPoint = (elem, pos, x, y, setMid) => { // Create a new SVG point const pts = elem.points const pt = svgroot.createSVGPoint() pt.x = x pt.y = y // If position is "end", set it to the last index if (pos === 'end') { pos = pts.numberOfItems - 1 } // Try replacing the point at the specified position pts.replaceItem(pt, pos) // Optionally, set the midpoint if (setMid) { const ptStart = pts.getItem(0) const ptEnd = pts.getItem(pts.numberOfItems - 1) setPoint(elem, 1, (ptEnd.x + ptStart.x) / 2, (ptEnd.y + ptStart.y) / 2) } } /** * @param {Float} diffX * @param {Float} diffY * @returns {void} */ const updatePoints = (line, conn, bb, altBB, pre, altPre) => { const srcX = altBB.x + altBB.width / 2 const srcY = altBB.y + altBB.height / 2 const pt = getBBintersect(srcX, srcY, bb, getOffset(pre, line)) setPoint(line, conn.is_start ? 0 : 'end', pt.x, pt.y, true) const pt2 = getBBintersect(pt.x, pt.y, altBB, getOffset(altPre, line)) setPoint(line, conn.is_start ? 'end' : 0, pt2.x, pt2.y, true) } const updateLine = (diffX, diffY) => { const dataStorage = svgCanvas.getDataStorage() for (const conn of connections) { const { connector: line, is_start: isStart, start_x: startX, start_y: startY } = conn const pre = isStart ? 'start' : 'end' const altPre = isStart ? 'end' : 'start' // Update bbox for this element const bb = { ...dataStorage.get(line, `${pre}_bb`) } bb.x = startX + diffX bb.y = startY + diffY dataStorage.put(line, `${pre}_bb`, bb) // Get center point of connected element const altBB = dataStorage.get(line, `${altPre}_bb`) updatePoints(line, conn, bb, altBB, pre, altPre) } } // Finds connectors associated with selected elements const findConnectors = (elems = []) => { // Fetch data storage object from svgCanvas const dataStorage = svgCanvas.getDataStorage() // Query all connector elements (id startss with conn_) const connectors = document.querySelectorAll('[id^="conn_"]') // Reset connections array connections = [] // Loop through each connector for (const connector of connectors) { let addThis = false // Flag to indicate whether to add this connector const parts = [] // To hold the starting and ending elements connected by the connector // Loop through the connector ends ("start" and "end") for (const [i, pos] of ['start', 'end'].entries()) { // Fetch connected element and its bounding box let part = dataStorage.get(connector, `c_${pos}`) // If part is null or undefined, fetch it and store it if (!part) { part = document.getElementById( connector.attributes['se:connector'].value.split(' ')[i] ) dataStorage.put(connector, `c_${pos}`, part.id) dataStorage.put( connector, `${pos}_bb`, svgCanvas.getStrokedBBox([part]) ) } else { // If part is already stored, fetch it by ID part = document.getElementById(part) } // Add the part to the parts array parts.push(part) } // Loop through the starting and ending elements connected by the connector for (let i = 0; i &lt; 2; i++) { const cElem = parts[i] const parents = svgCanvas.getParents(cElem?.parentNode) // Check if the element is part of a selected group for (const el of parents) { if (elems.includes(el)) { addThis = true break } } // If element is missing or parent is null, remove the connector if (!cElem || !cElem.parentNode) { connector.remove() continue } // If element is in the selection or part of a selected group if (elems.includes(cElem) || addThis) { const bb = svgCanvas.getStrokedBBox([cElem]) // Add connection information to the connections array connections.push({ elem: cElem, connector, is_start: i === 0, start_x: bb.x, start_y: bb.y }) } } } } /** * Updates the connectors based on selected elements. * @param {Element[]} [elems] - Optional array of selected elements. * @returns {void} */ const updateConnectors = elems => { const dataStorage = svgCanvas.getDataStorage() // Find connectors associated with selected elements findConnectors(elems) if (connections.length) { // Iterate through each connection to update its state for (const conn of connections) { const { elem, connector: line, is_start: isStart, start_x: startX, start_y: startY } = conn // Determine whether the connection starts or ends with this element const pre = isStart ? 'start' : 'end' // Update the bounding box for this element const bb = svgCanvas.getStrokedBBox([elem]) bb.x = startX bb.y = startY dataStorage.put(line, `${pre}_bb`, bb) // Determine the opposite end ('start' or 'end') of the connection const altPre = isStart ? 'end' : 'start' // Retrieve the bounding box for the connected element at the opposite end const bb2 = dataStorage.get(line, `${altPre}_bb`) // Calculate the center point of the connected element const srcX = bb2?.x + bb2?.width / 2 const srcY = bb2?.y + bb2?.height / 2 // Update the point of the element being moved const pt = getBBintersect(srcX, srcY, bb, getOffset(pre, line)) setPoint(line, isStart ? 0 : 'end', pt.x, pt.y, true) // Update the point of the connected element at the opposite end const pt2 = getBBintersect( pt.x, pt.y, dataStorage.get(line, `${altPre}_bb`), getOffset(altPre, line) ) setPoint(line, isStart ? 'end' : 0, pt2.x, pt2.y, true) } } } /** * Do on reset. * @returns {void} */ const reset = () => { const dataStorage = svgCanvas.getDataStorage() // Make sure all connectors have data set const svgContent = svgCanvas.getSvgContent() const elements = svgContent.querySelectorAll('*') elements.forEach(element => { const conn = element.getAttributeNS(seNs, 'connector') if (conn) { const connData = conn.split(' ') const sbb = svgCanvas.getStrokedBBox([getElement(connData[0])]) const ebb = svgCanvas.getStrokedBBox([getElement(connData[1])]) dataStorage.put(element, 'c_start', connData[0]) dataStorage.put(element, 'c_end', connData[1]) dataStorage.put(element, 'start_bb', sbb) dataStorage.put(element, 'end_bb', ebb) svgCanvas.getEditorNS(true) } }) } reset() return { name: svgEditor.i18next.t(`${name}:name`), callback () { // Add the button and its handler(s) const buttonTemplate = document.createElement('template') const title = `${name}:buttons.0.title` buttonTemplate.innerHTML = ` &lt;se-button id="tool_connect" title="${title}" src="conn.svg">&lt;/se-button> ` $id('tools_left').append(buttonTemplate.content.cloneNode(true)) $click($id('tool_connect'), () => { if (this.leftPanel.updateLeftPanel('tool_connect')) { svgCanvas.setMode('connector') } }) }, mouseDown (opts) { // Retrieve necessary data from the SVG canvas and the event object const dataStorage = svgCanvas.getDataStorage() const svgContent = svgCanvas.getSvgContent() const { event: e, start_x: startX, start_y: startY } = opts const mode = svgCanvas.getMode() const { curConfig: { initStroke } } = svgEditor.configObj if (mode === 'connector') { // Return if the line is already started if (started) return undefined const mouseTarget = e.target const parents = svgCanvas.getParents(mouseTarget.parentNode) // Check if the target is a child of the main SVG content if (parents.includes(svgContent)) { // Identify the connectable element, considering foreignObject elements const fo = svgCanvas.getClosest( mouseTarget.parentNode, 'foreignObject' ) startElem = fo || mouseTarget // Retrieve the bounding box and calculate the center of the start element const bb = svgCanvas.getStrokedBBox([startElem]) const x = bb.x + bb.width / 2 const y = bb.y + bb.height / 2 // Set the flag to indicate the line has started started = true // Create a new polyline element curLine = addSVGElementsFromJson({ element: 'polyline', attr: { id: 'conn_' + svgCanvas.getNextId(), points: `${x},${y} ${x},${y} ${startX},${startY}`, stroke: `#${initStroke.color}`, 'stroke-width': !startElem.stroke_width || startElem.stroke_width === 0 ? initStroke.width : startElem.stroke_width, fill: 'none', opacity: initStroke.opacity, style: 'pointer-events:none' } }) // Store the bounding box of the start element dataStorage.put(curLine, 'start_bb', bb) } return { started: true } } if (mode === 'select') { // Find connectors if the mode is 'select' findConnectors(opts.selectedElements) } return undefined }, mouseMove (opts) { // Exit early if there are no connectors if (connections.length === 0) return const dataStorage = svgCanvas.getDataStorage() const zoom = svgCanvas.getZoom() // const e = opts.event; const x = opts.mouse_x / zoom const y = opts.mouse_y / zoom /** @todo We have a concern if startX or startY are undefined */ if (!startX || !startY) return const diffX = x - startX const diffY = y - startY const mode = svgCanvas.getMode() if (mode === 'connector' &amp;&amp; started) { // const sw = curLine.getAttribute('stroke-width') * 3; // Set start point (adjusts based on bb) const pt = getBBintersect( x, y, dataStorage.get(curLine, 'start_bb'), getOffset('start', curLine) ) startX = pt.x startY = pt.y setPoint(curLine, 0, pt.x, pt.y, true) // Set end point setPoint(curLine, 'end', x, y, true) } else if (mode === 'select') { for (const elem of svgCanvas.getSelectedElements()) { if (elem &amp;&amp; dataStorage.has(elem, 'c_start')) { svgCanvas.removeFromSelection([elem]) elem.transform.baseVal.clear() } } if (connections.length) { updateLine(diffX, diffY) } } }, mouseUp (opts) { // Get necessary data and initial setups const dataStorage = svgCanvas.getDataStorage() const svgContent = svgCanvas.getSvgContent() const { event: e } = opts let mouseTarget = e.target // Early exit if not in connector mode if (svgCanvas.getMode() !== 'connector') return undefined // Check for a foreignObject parent and update mouseTarget if found const fo = svgCanvas.getClosest(mouseTarget.parentNode, 'foreignObject') if (fo) mouseTarget = fo // Check if the target is a child of the main SVG content const parents = svgCanvas.getParents(mouseTarget.parentNode) const isInSvgContent = parents.includes(svgContent) if (mouseTarget === startElem) { // Case: Started drawing line via click started = true return { keep: true, element: null, started } } if (!isInSvgContent) { // Case: Invalid target element; remove the line curLine?.remove() started = false return { keep: false, element: null, started } } // Valid target element for the end of the line endElem = mouseTarget const startId = startElem?.id || '' const endId = endElem?.id || '' const connStr = `${startId} ${endId}` const altStr = `${endId} ${startId}` // Prevent duplicate connectors const dupe = Array.from( document.querySelectorAll('[id^="conn_"]') ).filter( conn => conn.getAttributeNS(seNs, 'connector') === connStr || conn.getAttributeNS(seNs, 'connector') === altStr ) if (dupe.length) { curLine.remove() return { keep: false, element: null, started: false } } // Update the end point of the connector const bb = svgCanvas.getStrokedBBox([endElem]) const pt = getBBintersect( startX, startY, bb, getOffset('start', curLine) ) setPoint(curLine, 'end', pt.x, pt.y, true) // Save metadata to the connector dataStorage.put(curLine, 'c_start', startId) dataStorage.put(curLine, 'c_end', endId) dataStorage.put(curLine, 'end_bb', bb) curLine.setAttributeNS(seNs, 'se:connector', connStr) curLine.setAttribute('opacity', 1) // Finalize the connector svgCanvas.addToSelection([curLine]) svgCanvas.moveToBottomSelectedElement() selectorManager.requestSelector(curLine).showGrips(false) started = false return { keep: true, element: curLine, started } }, selectedChanged (opts) { // Get necessary data storage and SVG content const dataStorage = svgCanvas.getDataStorage() const svgContent = svgCanvas.getSvgContent() // Exit early if there are no connectors if (!svgContent.querySelectorAll('[id^="conn_"]').length) return // If the current mode is 'connector', switch to 'select' if (svgCanvas.getMode() === 'connector') { svgCanvas.setMode('select') } // Get currently selected elements const { elems: selElems } = opts // Iterate through selected elements for (const elem of selElems) { // If the element has a connector start, handle it if (elem &amp;&amp; dataStorage.has(elem, 'c_start')) { selectorManager.requestSelector(elem).showGrips(false) // Show panel depending on selection state showPanel(opts.selectedElement &amp;&amp; !opts.multiselected) } else { // Hide panel if no connector start showPanel(false) } } // Update connectors based on selected elements updateConnectors(svgCanvas.getSelectedElements()) }, elementChanged (opts) { // Get the necessary data storage const dataStorage = svgCanvas.getDataStorage() // Get the first element from the options; exit early if it's null let [elem] = opts.elems if (!elem) return // Reinitialize if it's the main SVG content if (elem.tagName === 'svg' &amp;&amp; elem.id === 'svgcontent') { reset() } // Check for marker attributes and update offsets const { markerStart, markerMid, markerEnd } = elem.attributes if (markerStart || markerMid || markerEnd) { curLine = elem dataStorage.put(elem, 'start_off', Boolean(markerStart)) dataStorage.put(elem, 'end_off', Boolean(markerEnd)) // Convert lines to polyline if there's a mid-marker if (elem.tagName === 'line' &amp;&amp; markerMid) { const { x1, x2, y1, y2, id } = elem.attributes const midPt = `${(Number(x1.value) + Number(x2.value)) / 2},${ (Number(y1.value) + Number(y2.value)) / 2 }` const pline = addSVGElementsFromJson({ element: 'polyline', attr: { points: `${x1.value},${y1.value} ${midPt} ${x2.value},${y2.value}`, stroke: elem.getAttribute('stroke'), 'stroke-width': elem.getAttribute('stroke-width'), 'marker-mid': markerMid.value, fill: 'none', opacity: elem.getAttribute('opacity') || 1 } }) elem.insertAdjacentElement('afterend', pline) elem.remove() svgCanvas.clearSelection() pline.id = id.value svgCanvas.addToSelection([pline]) elem = pline } } // Update connectors based on the current element if (elem?.id.startsWith('conn_')) { const start = getElement(dataStorage.get(elem, 'c_start')) updateConnectors([start]) } else { updateConnectors(svgCanvas.getSelectedElements()) } }, IDsUpdated (input) { const remove = [] input.elems.forEach(function (elem) { if ('se:connector' in elem.attr) { elem.attr['se:connector'] = elem.attr['se:connector'] .split(' ') .map(function (oldID) { return input.changes[oldID] }) .join(' ') // Check validity - the field would be something like 'svg_21 svg_22', but // if one end is missing, it would be 'svg_21' and therefore fail this test if (!/. ./.test(elem.attr['se:connector'])) { remove.push(elem.attr.id) } } }) return { remove } }, toolButtonStateUpdate (opts) { const button = document.getElementById('tool_connect') if (opts.nostroke &amp;&amp; button.pressed === true) { svgEditor.clickSelect() } button.disabled = opts.nostroke } } } } </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="editor_extensions_ext-layer_view_locale_zh-CN.module_js.html">editor/extensions/ext-layer_view/locale/zh-CN.js</a></li><li><a href="module-SVGEditor.html">SVGEditor</a></li><li><a href="module-contextmenu.html">contextmenu</a></li><li><a href="module-jGraduate.html">jGraduate</a></li><li><a href="module-jPicker.html">jPicker</a></li><li><a href="module-locale.html">locale</a></li></ul><h3>Externals</h3><ul><li><a href="external-JamilihArray.html">JamilihArray</a></li><li><a href="external-Math.html">Math</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-jQuery.html">jQuery</a></li></ul><h3>Namespaces</h3><ul><li><a href="external-jQuery.fn.html">fn</a></li><li><a href="external-jQuery.fn.$.fn.jPicker.defaults.html">defaults</a></li><li><a href="external-jQuery.fn.exports.jPickerMethod.html">exports.jPickerMethod</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.html">jGraduateDefaults</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.images.html">images</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.window.html">window</a></li><li><a href="external-jQuery.jGraduate.html">jGraduate</a></li><li><a href="external-jQuery.jPicker.html">jPicker</a></li><li><a href="external-jQuery.jPicker.ColorMethods.html">ColorMethods</a></li></ul><h3>Classes</h3><ul><li><a href="BottomPanel.html">BottomPanel</a></li><li><a href="Dropdown.html">Dropdown</a></li><li><a href="EditorStartup.html">EditorStartup</a></li><li><a href="ElixMenuButton.html">ElixMenuButton</a></li><li><a href="ElixNumberSpinBox.html">ElixNumberSpinBox</a></li><li><a href="ExplorerButton.html">ExplorerButton</a></li><li><a href="FlyingButton.html">FlyingButton</a></li><li><a href="LayersPanel.html">LayersPanel</a></li><li><a href="LeftPanel.html">LeftPanel</a></li><li><a href="MainMenu.html">MainMenu</a></li><li><a href="NumberSpinBox.html">NumberSpinBox</a></li><li><a href="PaintBox.html">PaintBox</a></li><li><a href="PlainNumberSpinBox.html">PlainNumberSpinBox</a></li><li><a href="Rulers.html">Rulers</a></li><li><a href="SEInput.html">SEInput</a></li><li><a href="SEPalette.html">SEPalette</a></li><li><a href="SESpinInput.html">SESpinInput</a></li><li><a href="SeCMenuDialog.html">SeCMenuDialog</a></li><li><a href="SeCMenuLayerDialog.html">SeCMenuLayerDialog</a></li><li><a href="SeColorPicker.html">SeColorPicker</a></li><li><a href="SeEditPrefsDialog.html">SeEditPrefsDialog</a></li><li><a href="SeExportDialog.html">SeExportDialog</a></li><li><a href="SeImgPropDialog.html">SeImgPropDialog</a></li><li><a href="SeList.html">SeList</a></li><li><a href="SeMenu.html">SeMenu</a></li><li><a href="SeMenuItem.html">SeMenuItem</a></li><li><a href="SePlainAlertDialog.html">SePlainAlertDialog</a></li><li><a href="SePlainBorderButton.html">SePlainBorderButton</a></li><li><a href="SePromptDialog.html">SePromptDialog</a></li><li><a href="SeStorageDialog.html">SeStorageDialog</a></li><li><a href="SeSvgSourceEditorDialog.html">SeSvgSourceEditorDialog</a></li><li><a href="SeText.html">SeText</a></li><li><a href="ToolButton.html">ToolButton</a></li><li><a href="TopPanel.html">TopPanel</a></li><li><a href="configObj.html">configObj</a></li><li><a href="external-jQuery.jGraduate.Paint.html">Paint</a></li><li><a href="external-jQuery.jPicker.Color.html">Color</a></li><li><a href="module.exports.html">exports</a></li><li><a href="module.exports_module.exports.html">exports</a></li><li><a href="module-SVGEditor-Editor.html">Editor</a></li><li><a href="module-jPicker.module.exports.html">module.exports</a></li></ul><h3>Interfaces</h3><ul><li><a href="module-SVGEditor.Config.html">Config</a></li><li><a href="module-SVGEditor.Prefs.html">Prefs</a></li><li><a href="module-SVGthis.CustomHandler.html">CustomHandler</a></li><li><a href="module-locale.LocaleEditorInit.html">LocaleEditorInit</a></li></ul><h3>Events</h3><ul><li><a href="module-SVGEditor.html#event:event:svgEditorReadyEvent">svgEditorReadyEvent</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-CanvasAPI.html">CanvasAPI</a></li><li><a href="tutorial-Editor.html">Editor</a></li><li><a href="tutorial-EditorAPI.html">EditorAPI</a></li><li><a href="tutorial-Events.html">Events</a></li><li><a href="tutorial-FrequentlyAskedQuestions.html">Frequently Asked Questions (FAQ)</a></li></ul><h3>Global</h3><ul><li><a href="global.html#attributeChangedCallback">attributeChangedCallback</a></li><li><a href="global.html#connectedCallback">connectedCallback</a></li><li><a href="global.html#constructor">constructor</a></li><li><a href="global.html#createTemplate">createTemplate</a></li><li><a href="global.html#decrement">decrement</a></li><li><a href="global.html#expireCookie">expireCookie</a></li><li><a href="global.html#formatValueFormatthenumericvalueasastring.Thisisusedafterincrementing/decrementingthevaluetoreformatthevalueasastring.">formatValue Format the numeric value as a string. This is used after incrementing/decrementing the value to reformat the value as a string.</a></li><li><a href="global.html#get">get</a></li><li><a href="global.html#handleClick">handleClick</a></li><li><a href="global.html#handleClose">handleClose</a></li><li><a href="global.html#handleInput">handleInput</a></li><li><a href="global.html#handleKeyDown">handleKeyDown</a></li><li><a href="global.html#handleMouseDown">handleMouseDown</a></li><li><a href="global.html#handleMouseUp">handleMouseUp</a></li><li><a href="global.html#handleOptionsChange">handleOptionsChange</a></li><li><a href="global.html#handleSelect">handleSelect</a></li><li><a href="global.html#handleShow">handleShow</a></li><li><a href="global.html#increment">increment</a></li><li><a href="global.html#init">init</a></li><li><a href="global.html#inputsize">inputsize</a></li><li><a href="global.html#isNullish">isNullish</a></li><li><a href="global.html#loadloadConfig">load load Config</a></li><li><a href="global.html#loadFromURLLoadconfig/datafromURLifgiven">loadFromURL Load config/data from URL if given</a></li><li><a href="global.html#name">name</a></li><li><a href="global.html#observedAttributes">observedAttributes</a></li><li><a href="global.html#parseValue">parseValue</a></li><li><a href="global.html#pref">pref</a></li><li><a href="global.html#readySignal">readySignal</a></li><li><a href="global.html#regexEscape">regexEscape</a></li><li><a href="global.html#removeStoragePrefCookie">removeStoragePrefCookie</a></li><li><a href="global.html#replaceStoragePrompt">replaceStoragePrompt</a></li><li><a href="global.html#set">set</a></li><li><a href="global.html#setupCurConfig">setupCurConfig</a></li><li><a href="global.html#setupCurPrefs">setupCurPrefs</a></li><li><a href="global.html#src">src</a></li><li><a href="global.html#stateEffects">stateEffects</a></li><li><a href="global.html#stepDown">stepDown</a></li><li><a href="global.html#stepUp">stepUp</a></li><li><a href="global.html#triggerInputChanged">triggerInputChanged</a></li><li><a href="global.html#updateLib">updateLib</a></li><li><a href="global.html#value">value</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.5</a> on Sun Dec 07 2025 19:46:40 GMT+0100 (Central European Standard Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>