UNPKG

@linkurious/ogma-ui-kit

Version:
1,278 lines (1,271 loc) 48.2 kB
/** * lil-gui * https://lil-gui.georgealways.com * @version 0.20.0 * @author George Michael Brower * @license MIT */ class c { constructor(t, i, e, l, a = "div") { this.parent = t, this.object = i, this.property = e, this._disabled = !1, this._hidden = !1, this.initialValue = this.getValue(), this.domElement = document.createElement(a), this.domElement.classList.add("controller"), this.domElement.classList.add(l), this.$name = document.createElement("div"), this.$name.classList.add("name"), c.nextNameID = c.nextNameID || 0, this.$name.id = `lil-gui-name-${++c.nextNameID}`, this.$widget = document.createElement("div"), this.$widget.classList.add("widget"), this.$disable = this.$widget, this.domElement.appendChild(this.$name), this.domElement.appendChild(this.$widget), this.domElement.addEventListener("keydown", (o) => o.stopPropagation()), this.domElement.addEventListener("keyup", (o) => o.stopPropagation()), this.parent.children.push(this), this.parent.controllers.push(this), this.parent.$children.appendChild(this.domElement), this._listenCallback = this._listenCallback.bind(this), this.name(e); } /** * Sets the name of the controller and its label in the GUI. * @param {string} name * @returns {this} */ name(t) { return this._name = t, this.$name.textContent = t, this; } /** * Pass a function to be called whenever the value is modified by this controller. * The function receives the new value as its first parameter. The value of `this` will be the * controller. * * For function controllers, the `onChange` callback will be fired on click, after the function * executes. * @param {Function} callback * @returns {this} * @example * const controller = gui.add( object, 'property' ); * * controller.onChange( function( v ) { * console.log( 'The value is now ' + v ); * console.assert( this === controller ); * } ); */ onChange(t) { return this._onChange = t, this; } /** * Calls the onChange methods of this controller and its parent GUI. * @protected */ _callOnChange() { this.parent._callOnChange(this), this._onChange !== void 0 && this._onChange.call(this, this.getValue()), this._changed = !0; } /** * Pass a function to be called after this controller has been modified and loses focus. * @param {Function} callback * @returns {this} * @example * const controller = gui.add( object, 'property' ); * * controller.onFinishChange( function( v ) { * console.log( 'Changes complete: ' + v ); * console.assert( this === controller ); * } ); */ onFinishChange(t) { return this._onFinishChange = t, this; } /** * Should be called by Controller when its widgets lose focus. * @protected */ _callOnFinishChange() { this._changed && (this.parent._callOnFinishChange(this), this._onFinishChange !== void 0 && this._onFinishChange.call(this, this.getValue())), this._changed = !1; } /** * Sets the controller back to its initial value. * @returns {this} */ reset() { return this.setValue(this.initialValue), this._callOnFinishChange(), this; } /** * Enables this controller. * @param {boolean} enabled * @returns {this} * @example * controller.enable(); * controller.enable( false ); // disable * controller.enable( controller._disabled ); // toggle */ enable(t = !0) { return this.disable(!t); } /** * Disables this controller. * @param {boolean} disabled * @returns {this} * @example * controller.disable(); * controller.disable( false ); // enable * controller.disable( !controller._disabled ); // toggle */ disable(t = !0) { return t === this._disabled ? this : (this._disabled = t, this.domElement.classList.toggle("disabled", t), this.$disable.toggleAttribute("disabled", t), this); } /** * Shows the Controller after it's been hidden. * @param {boolean} show * @returns {this} * @example * controller.show(); * controller.show( false ); // hide * controller.show( controller._hidden ); // toggle */ show(t = !0) { return this._hidden = !t, this.domElement.style.display = this._hidden ? "none" : "", this; } /** * Hides the Controller. * @returns {this} */ hide() { return this.show(!1); } /** * Changes this controller into a dropdown of options. * * Calling this method on an option controller will simply update the options. However, if this * controller was not already an option controller, old references to this controller are * destroyed, and a new controller is added to the end of the GUI. * @example * // safe usage * * gui.add( obj, 'prop1' ).options( [ 'a', 'b', 'c' ] ); * gui.add( obj, 'prop2' ).options( { Big: 10, Small: 1 } ); * gui.add( obj, 'prop3' ); * * // danger * * const ctrl1 = gui.add( obj, 'prop1' ); * gui.add( obj, 'prop2' ); * * // calling options out of order adds a new controller to the end... * const ctrl2 = ctrl1.options( [ 'a', 'b', 'c' ] ); * * // ...and ctrl1 now references a controller that doesn't exist * assert( ctrl2 !== ctrl1 ) * @param {object|Array} options * @returns {Controller} */ options(t) { const i = this.parent.add(this.object, this.property, t); return i.name(this._name), this.destroy(), i; } /** * Sets the minimum value. Only works on number controllers. * @param {number} min * @returns {this} */ min(t) { return this; } /** * Sets the maximum value. Only works on number controllers. * @param {number} max * @returns {this} */ max(t) { return this; } /** * Values set by this controller will be rounded to multiples of `step`. Only works on number * controllers. * @param {number} step * @returns {this} */ step(t) { return this; } /** * Rounds the displayed value to a fixed number of decimals, without affecting the actual value * like `step()`. Only works on number controllers. * @example * gui.add( object, 'property' ).listen().decimals( 4 ); * @param {number} decimals * @returns {this} */ decimals(t) { return this; } /** * Calls `updateDisplay()` every animation frame. Pass `false` to stop listening. * @param {boolean} listen * @returns {this} */ listen(t = !0) { return this._listening = t, this._listenCallbackID !== void 0 && (cancelAnimationFrame(this._listenCallbackID), this._listenCallbackID = void 0), this._listening && this._listenCallback(), this; } _listenCallback() { this._listenCallbackID = requestAnimationFrame(this._listenCallback); const t = this.save(); t !== this._listenPrevValue && this.updateDisplay(), this._listenPrevValue = t; } /** * Returns `object[ property ]`. * @returns {any} */ getValue() { return this.object[this.property]; } /** * Sets the value of `object[ property ]`, invokes any `onChange` handlers and updates the display. * @param {any} value * @returns {this} */ setValue(t) { return this.getValue() !== t && (this.object[this.property] = t, this._callOnChange(), this.updateDisplay()), this; } /** * Updates the display to keep it in sync with the current value. Useful for updating your * controllers when their values have been modified outside of the GUI. * @returns {this} */ updateDisplay() { return this; } load(t) { return this.setValue(t), this._callOnFinishChange(), this; } save() { return this.getValue(); } /** * Destroys this controller and removes it from the parent GUI. */ destroy() { this.listen(!1), this.parent.children.splice(this.parent.children.indexOf(this), 1), this.parent.controllers.splice(this.parent.controllers.indexOf(this), 1), this.parent.$children.removeChild(this.domElement); } } class L extends c { constructor(t, i, e) { super(t, i, e, "boolean", "label"), this.$input = document.createElement("input"), this.$input.setAttribute("type", "checkbox"), this.$input.setAttribute("aria-labelledby", this.$name.id), this.$widget.appendChild(this.$input), this.$input.addEventListener("change", () => { this.setValue(this.$input.checked), this._callOnFinishChange(); }), this.$disable = this.$input, this.updateDisplay(); } updateDisplay() { return this.$input.checked = this.getValue(), this; } } function $(s) { let t, i; return (t = s.match(/(#|0x)?([a-f0-9]{6})/i)) ? i = t[2] : (t = s.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/)) ? i = parseInt(t[1]).toString(16).padStart(2, 0) + parseInt(t[2]).toString(16).padStart(2, 0) + parseInt(t[3]).toString(16).padStart(2, 0) : (t = s.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i)) && (i = t[1] + t[1] + t[2] + t[2] + t[3] + t[3]), i ? "#" + i : !1; } const S = { isPrimitive: !0, match: (s) => typeof s == "string", fromHexString: $, toHexString: $ }, v = { isPrimitive: !0, match: (s) => typeof s == "number", fromHexString: (s) => parseInt(s.substring(1), 16), toHexString: (s) => "#" + s.toString(16).padStart(6, 0) }, F = { isPrimitive: !1, // The arrow function is here to appease tree shakers like esbuild or webpack. // See https://esbuild.github.io/api/#tree-shaking match: (s) => Array.isArray(s), fromHexString(s, t, i = 1) { const e = v.fromHexString(s); t[0] = (e >> 16 & 255) / 255 * i, t[1] = (e >> 8 & 255) / 255 * i, t[2] = (e & 255) / 255 * i; }, toHexString([s, t, i], e = 1) { e = 255 / e; const l = s * e << 16 ^ t * e << 8 ^ i * e << 0; return v.toHexString(l); } }, D = { isPrimitive: !1, match: (s) => Object(s) === s, fromHexString(s, t, i = 1) { const e = v.fromHexString(s); t.r = (e >> 16 & 255) / 255 * i, t.g = (e >> 8 & 255) / 255 * i, t.b = (e & 255) / 255 * i; }, toHexString({ r: s, g: t, b: i }, e = 1) { e = 255 / e; const l = s * e << 16 ^ t * e << 8 ^ i * e << 0; return v.toHexString(l); } }, M = [S, v, F, D]; function V(s) { return M.find((t) => t.match(s)); } class O extends c { constructor(t, i, e, l) { super(t, i, e, "color"), this.$input = document.createElement("input"), this.$input.setAttribute("type", "color"), this.$input.setAttribute("tabindex", -1), this.$input.setAttribute("aria-labelledby", this.$name.id), this.$text = document.createElement("input"), this.$text.setAttribute("type", "text"), this.$text.setAttribute("spellcheck", "false"), this.$text.setAttribute("aria-labelledby", this.$name.id), this.$display = document.createElement("div"), this.$display.classList.add("display"), this.$display.appendChild(this.$input), this.$widget.appendChild(this.$display), this.$widget.appendChild(this.$text), this._format = V(this.initialValue), this._rgbScale = l, this._initialValueHexString = this.save(), this._textFocused = !1, this.$input.addEventListener("input", () => { this._setValueFromHexString(this.$input.value); }), this.$input.addEventListener("blur", () => { this._callOnFinishChange(); }), this.$text.addEventListener("input", () => { const a = $(this.$text.value); a && this._setValueFromHexString(a); }), this.$text.addEventListener("focus", () => { this._textFocused = !0, this.$text.select(); }), this.$text.addEventListener("blur", () => { this._textFocused = !1, this.updateDisplay(), this._callOnFinishChange(); }), this.$disable = this.$text, this.updateDisplay(); } reset() { return this._setValueFromHexString(this._initialValueHexString), this; } _setValueFromHexString(t) { if (this._format.isPrimitive) { const i = this._format.fromHexString(t); this.setValue(i); } else this._format.fromHexString(t, this.getValue(), this._rgbScale), this._callOnChange(), this.updateDisplay(); } save() { return this._format.toHexString(this.getValue(), this._rgbScale); } load(t) { return this._setValueFromHexString(t), this._callOnFinishChange(), this; } updateDisplay() { return this.$input.value = this._format.toHexString(this.getValue(), this._rgbScale), this._textFocused || (this.$text.value = this.$input.value.substring(1)), this.$display.style.backgroundColor = this.$input.value, this; } } class y extends c { constructor(t, i, e) { super(t, i, e, "function"), this.$button = document.createElement("button"), this.$button.appendChild(this.$name), this.$widget.appendChild(this.$button), this.$button.addEventListener("click", (l) => { l.preventDefault(), this.getValue().call(this.object), this._callOnChange(); }), this.$button.addEventListener("touchstart", () => { }, { passive: !0 }), this.$disable = this.$button; } } class I extends c { constructor(t, i, e, l, a, o) { super(t, i, e, "number"), this._initInput(), this.min(l), this.max(a); const u = o !== void 0; this.step(u ? o : this._getImplicitStep(), u), this.updateDisplay(); } decimals(t) { return this._decimals = t, this.updateDisplay(), this; } min(t) { return this._min = t, this._onUpdateMinMax(), this; } max(t) { return this._max = t, this._onUpdateMinMax(), this; } step(t, i = !0) { return this._step = t, this._stepExplicit = i, this; } updateDisplay() { const t = this.getValue(); if (this._hasSlider) { let i = (t - this._min) / (this._max - this._min); i = Math.max(0, Math.min(i, 1)), this.$fill.style.width = i * 100 + "%"; } return this._inputFocused || (this.$input.value = this._decimals === void 0 ? t : t.toFixed(this._decimals)), this; } _initInput() { this.$input = document.createElement("input"), this.$input.setAttribute("type", "text"), this.$input.setAttribute("aria-labelledby", this.$name.id), window.matchMedia("(pointer: coarse)").matches && (this.$input.setAttribute("type", "number"), this.$input.setAttribute("step", "any")), this.$widget.appendChild(this.$input), this.$disable = this.$input; const i = () => { let n = parseFloat(this.$input.value); isNaN(n) || (this._stepExplicit && (n = this._snap(n)), this.setValue(this._clamp(n))); }, e = (n) => { const h = parseFloat(this.$input.value); isNaN(h) || (this._snapClampSetValue(h + n), this.$input.value = this.getValue()); }, l = (n) => { n.key === "Enter" && this.$input.blur(), n.code === "ArrowUp" && (n.preventDefault(), e(this._step * this._arrowKeyMultiplier(n))), n.code === "ArrowDown" && (n.preventDefault(), e(this._step * this._arrowKeyMultiplier(n) * -1)); }, a = (n) => { this._inputFocused && (n.preventDefault(), e(this._step * this._normalizeMouseWheel(n))); }; let o = !1, u, g, m, p, d; const f = 5, x = (n) => { u = n.clientX, g = m = n.clientY, o = !0, p = this.getValue(), d = 0, window.addEventListener("mousemove", A), window.addEventListener("mouseup", b); }, A = (n) => { if (o) { const h = n.clientX - u, w = n.clientY - g; Math.abs(w) > f ? (n.preventDefault(), this.$input.blur(), o = !1, this._setDraggingStyle(!0, "vertical")) : Math.abs(h) > f && b(); } if (!o) { const h = n.clientY - m; d -= h * this._step * this._arrowKeyMultiplier(n), p + d > this._max ? d = this._max - p : p + d < this._min && (d = this._min - p), this._snapClampSetValue(p + d); } m = n.clientY; }, b = () => { this._setDraggingStyle(!1, "vertical"), this._callOnFinishChange(), window.removeEventListener("mousemove", A), window.removeEventListener("mouseup", b); }, _ = () => { this._inputFocused = !0; }, r = () => { this._inputFocused = !1, this.updateDisplay(), this._callOnFinishChange(); }; this.$input.addEventListener("input", i), this.$input.addEventListener("keydown", l), this.$input.addEventListener("wheel", a, { passive: !1 }), this.$input.addEventListener("mousedown", x), this.$input.addEventListener("focus", _), this.$input.addEventListener("blur", r); } _initSlider() { this._hasSlider = !0, this.$slider = document.createElement("div"), this.$slider.classList.add("slider"), this.$fill = document.createElement("div"), this.$fill.classList.add("fill"), this.$slider.appendChild(this.$fill), this.$widget.insertBefore(this.$slider, this.$input), this.domElement.classList.add("hasSlider"); const t = (r, n, h, w, k) => (r - n) / (h - n) * (k - w) + w, i = (r) => { const n = this.$slider.getBoundingClientRect(); let h = t(r, n.left, n.right, this._min, this._max); this._snapClampSetValue(h); }, e = (r) => { this._setDraggingStyle(!0), i(r.clientX), window.addEventListener("mousemove", l), window.addEventListener("mouseup", a); }, l = (r) => { i(r.clientX); }, a = () => { this._callOnFinishChange(), this._setDraggingStyle(!1), window.removeEventListener("mousemove", l), window.removeEventListener("mouseup", a); }; let o = !1, u, g; const m = (r) => { r.preventDefault(), this._setDraggingStyle(!0), i(r.touches[0].clientX), o = !1; }, p = (r) => { r.touches.length > 1 || (this._hasScrollBar ? (u = r.touches[0].clientX, g = r.touches[0].clientY, o = !0) : m(r), window.addEventListener("touchmove", d, { passive: !1 }), window.addEventListener("touchend", f)); }, d = (r) => { if (o) { const n = r.touches[0].clientX - u, h = r.touches[0].clientY - g; Math.abs(n) > Math.abs(h) ? m(r) : (window.removeEventListener("touchmove", d), window.removeEventListener("touchend", f)); } else r.preventDefault(), i(r.touches[0].clientX); }, f = () => { this._callOnFinishChange(), this._setDraggingStyle(!1), window.removeEventListener("touchmove", d), window.removeEventListener("touchend", f); }, x = this._callOnFinishChange.bind(this), A = 400; let b; const _ = (r) => { if (Math.abs(r.deltaX) < Math.abs(r.deltaY) && this._hasScrollBar) return; r.preventDefault(); const h = this._normalizeMouseWheel(r) * this._step; this._snapClampSetValue(this.getValue() + h), this.$input.value = this.getValue(), clearTimeout(b), b = setTimeout(x, A); }; this.$slider.addEventListener("mousedown", e), this.$slider.addEventListener("touchstart", p, { passive: !1 }), this.$slider.addEventListener("wheel", _, { passive: !1 }); } _setDraggingStyle(t, i = "horizontal") { this.$slider && this.$slider.classList.toggle("active", t), document.body.classList.toggle("lil-gui-dragging", t), document.body.classList.toggle(`lil-gui-${i}`, t); } _getImplicitStep() { return this._hasMin && this._hasMax ? (this._max - this._min) / 1e3 : 0.1; } _onUpdateMinMax() { !this._hasSlider && this._hasMin && this._hasMax && (this._stepExplicit || this.step(this._getImplicitStep(), !1), this._initSlider(), this.updateDisplay()); } _normalizeMouseWheel(t) { let { deltaX: i, deltaY: e } = t; return Math.floor(t.deltaY) !== t.deltaY && t.wheelDelta && (i = 0, e = -t.wheelDelta / 120, e *= this._stepExplicit ? 1 : 10), i + -e; } _arrowKeyMultiplier(t) { let i = this._stepExplicit ? 1 : 10; return t.shiftKey ? i *= 10 : t.altKey && (i /= 10), i; } _snap(t) { let i = 0; return this._hasMin ? i = this._min : this._hasMax && (i = this._max), t -= i, t = Math.round(t / this._step) * this._step, t += i, t = parseFloat(t.toPrecision(15)), t; } _clamp(t) { return t < this._min && (t = this._min), t > this._max && (t = this._max), t; } _snapClampSetValue(t) { this.setValue(this._clamp(this._snap(t))); } get _hasScrollBar() { const t = this.parent.root.$children; return t.scrollHeight > t.clientHeight; } get _hasMin() { return this._min !== void 0; } get _hasMax() { return this._max !== void 0; } } class B extends c { constructor(t, i, e, l) { super(t, i, e, "option"), this.$select = document.createElement("select"), this.$select.setAttribute("aria-labelledby", this.$name.id), this.$display = document.createElement("div"), this.$display.classList.add("display"), this.$select.addEventListener("change", () => { this.setValue(this._values[this.$select.selectedIndex]), this._callOnFinishChange(); }), this.$select.addEventListener("focus", () => { this.$display.classList.add("focus"); }), this.$select.addEventListener("blur", () => { this.$display.classList.remove("focus"); }), this.$widget.appendChild(this.$select), this.$widget.appendChild(this.$display), this.$disable = this.$select, this.options(l); } options(t) { return this._values = Array.isArray(t) ? t : Object.values(t), this._names = Array.isArray(t) ? t : Object.keys(t), this.$select.replaceChildren(), this._names.forEach((i) => { const e = document.createElement("option"); e.textContent = i, this.$select.appendChild(e); }), this.updateDisplay(), this; } updateDisplay() { const t = this.getValue(), i = this._values.indexOf(t); return this.$select.selectedIndex = i, this.$display.textContent = i === -1 ? t : this._names[i], this; } } class z extends c { constructor(t, i, e) { super(t, i, e, "string"), this.$input = document.createElement("input"), this.$input.setAttribute("type", "text"), this.$input.setAttribute("spellcheck", "false"), this.$input.setAttribute("aria-labelledby", this.$name.id), this.$input.addEventListener("input", () => { this.setValue(this.$input.value); }), this.$input.addEventListener("keydown", (l) => { l.code === "Enter" && this.$input.blur(); }), this.$input.addEventListener("blur", () => { this._callOnFinishChange(); }), this.$widget.appendChild(this.$input), this.$disable = this.$input, this.updateDisplay(); } updateDisplay() { return this.$input.value = this.getValue(), this; } } var H = `.lil-gui { font-family: var(--font-family); font-size: var(--font-size); line-height: 1; font-weight: normal; font-style: normal; text-align: left; color: var(--text-color); user-select: none; -webkit-user-select: none; touch-action: manipulation; --background-color: #1f1f1f; --text-color: #ebebeb; --title-background-color: #111111; --title-text-color: #ebebeb; --widget-color: #424242; --hover-color: #4f4f4f; --focus-color: #595959; --number-color: #2cc9ff; --string-color: #a2db3c; --font-size: 11px; --input-font-size: 11px; --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace; --padding: 4px; --spacing: 4px; --widget-height: 20px; --title-height: calc(var(--widget-height) + var(--spacing) * 1.25); --name-width: 45%; --slider-knob-width: 2px; --slider-input-width: 27%; --color-input-width: 27%; --slider-input-min-width: 45px; --color-input-min-width: 45px; --folder-indent: 7px; --widget-padding: 0 0 0 3px; --widget-border-radius: 2px; --checkbox-size: calc(0.75 * var(--widget-height)); --scrollbar-width: 5px; } .lil-gui, .lil-gui * { box-sizing: border-box; margin: 0; padding: 0; } .lil-gui.root { width: var(--width, 245px); display: flex; flex-direction: column; background: var(--background-color); } .lil-gui.root > .title { background: var(--title-background-color); color: var(--title-text-color); } .lil-gui.root > .children { overflow-x: hidden; overflow-y: auto; } .lil-gui.root > .children::-webkit-scrollbar { width: var(--scrollbar-width); height: var(--scrollbar-width); background: var(--background-color); } .lil-gui.root > .children::-webkit-scrollbar-thumb { border-radius: var(--scrollbar-width); background: var(--focus-color); } @media (pointer: coarse) { .lil-gui.allow-touch-styles, .lil-gui.allow-touch-styles .lil-gui { --widget-height: 28px; --padding: 6px; --spacing: 6px; --font-size: 13px; --input-font-size: 16px; --folder-indent: 10px; --scrollbar-width: 7px; --slider-input-min-width: 50px; --color-input-min-width: 65px; } } .lil-gui.force-touch-styles, .lil-gui.force-touch-styles .lil-gui { --widget-height: 28px; --padding: 6px; --spacing: 6px; --font-size: 13px; --input-font-size: 16px; --folder-indent: 10px; --scrollbar-width: 7px; --slider-input-min-width: 50px; --color-input-min-width: 65px; } .lil-gui.autoPlace { max-height: 100%; position: fixed; top: 0; right: 15px; z-index: 1001; } .lil-gui .controller { display: flex; align-items: center; padding: 0 var(--padding); margin: var(--spacing) 0; } .lil-gui .controller.disabled { opacity: 0.5; } .lil-gui .controller.disabled, .lil-gui .controller.disabled * { pointer-events: none !important; } .lil-gui .controller > .name { min-width: var(--name-width); flex-shrink: 0; white-space: pre; padding-right: var(--spacing); line-height: var(--widget-height); } .lil-gui .controller .widget { position: relative; display: flex; align-items: center; width: 100%; min-height: var(--widget-height); } .lil-gui .controller.string input { color: var(--string-color); } .lil-gui .controller.boolean { cursor: pointer; } .lil-gui .controller.color .display { width: 100%; height: var(--widget-height); border-radius: var(--widget-border-radius); position: relative; } @media (hover: hover) { .lil-gui .controller.color .display:hover:before { content: " "; display: block; position: absolute; border-radius: var(--widget-border-radius); border: 1px solid #fff9; top: 0; right: 0; bottom: 0; left: 0; } } .lil-gui .controller.color input[type=color] { opacity: 0; width: 100%; height: 100%; cursor: pointer; } .lil-gui .controller.color input[type=text] { margin-left: var(--spacing); font-family: var(--font-family-mono); min-width: var(--color-input-min-width); width: var(--color-input-width); flex-shrink: 0; } .lil-gui .controller.option select { opacity: 0; position: absolute; width: 100%; max-width: 100%; } .lil-gui .controller.option .display { position: relative; pointer-events: none; border-radius: var(--widget-border-radius); height: var(--widget-height); line-height: var(--widget-height); max-width: 100%; overflow: hidden; word-break: break-all; padding-left: 0.55em; padding-right: 1.75em; background: var(--widget-color); } @media (hover: hover) { .lil-gui .controller.option .display.focus { background: var(--focus-color); } } .lil-gui .controller.option .display.active { background: var(--focus-color); } .lil-gui .controller.option .display:after { font-family: "lil-gui"; content: "↕"; position: absolute; top: 0; right: 0; bottom: 0; padding-right: 0.375em; } .lil-gui .controller.option .widget, .lil-gui .controller.option select { cursor: pointer; } @media (hover: hover) { .lil-gui .controller.option .widget:hover .display { background: var(--hover-color); } } .lil-gui .controller.number input { color: var(--number-color); } .lil-gui .controller.number.hasSlider input { margin-left: var(--spacing); width: var(--slider-input-width); min-width: var(--slider-input-min-width); flex-shrink: 0; } .lil-gui .controller.number .slider { width: 100%; height: var(--widget-height); background: var(--widget-color); border-radius: var(--widget-border-radius); padding-right: var(--slider-knob-width); overflow: hidden; cursor: ew-resize; touch-action: pan-y; } @media (hover: hover) { .lil-gui .controller.number .slider:hover { background: var(--hover-color); } } .lil-gui .controller.number .slider.active { background: var(--focus-color); } .lil-gui .controller.number .slider.active .fill { opacity: 0.95; } .lil-gui .controller.number .fill { height: 100%; border-right: var(--slider-knob-width) solid var(--number-color); box-sizing: content-box; } .lil-gui-dragging .lil-gui { --hover-color: var(--widget-color); } .lil-gui-dragging * { cursor: ew-resize !important; } .lil-gui-dragging.lil-gui-vertical * { cursor: ns-resize !important; } .lil-gui .title { height: var(--title-height); font-weight: 600; padding: 0 var(--padding); width: 100%; text-align: left; background: none; text-decoration-skip: objects; } .lil-gui .title:before { font-family: "lil-gui"; content: "▾"; padding-right: 2px; display: inline-block; } .lil-gui .title:active { background: var(--title-background-color); opacity: 0.75; } @media (hover: hover) { body:not(.lil-gui-dragging) .lil-gui .title:hover { background: var(--title-background-color); opacity: 0.85; } .lil-gui .title:focus { text-decoration: underline var(--focus-color); } } .lil-gui.root > .title:focus { text-decoration: none !important; } .lil-gui.closed > .title:before { content: "▸"; } .lil-gui.closed > .children { transform: translateY(-7px); opacity: 0; } .lil-gui.closed:not(.transition) > .children { display: none; } .lil-gui.transition > .children { transition-duration: 300ms; transition-property: height, opacity, transform; transition-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); overflow: hidden; pointer-events: none; } .lil-gui .children:empty:before { content: "Empty"; padding: 0 var(--padding); margin: var(--spacing) 0; display: block; height: var(--widget-height); font-style: italic; line-height: var(--widget-height); opacity: 0.5; } .lil-gui.root > .children > .lil-gui > .title { border: 0 solid var(--widget-color); border-width: 1px 0; transition: border-color 300ms; } .lil-gui.root > .children > .lil-gui.closed > .title { border-bottom-color: transparent; } .lil-gui + .controller { border-top: 1px solid var(--widget-color); margin-top: 0; padding-top: var(--spacing); } .lil-gui .lil-gui .lil-gui > .title { border: none; } .lil-gui .lil-gui .lil-gui > .children { border: none; margin-left: var(--folder-indent); border-left: 2px solid var(--widget-color); } .lil-gui .lil-gui .controller { border: none; } .lil-gui label, .lil-gui input, .lil-gui button { -webkit-tap-highlight-color: transparent; } .lil-gui input { border: 0; outline: none; font-family: var(--font-family); font-size: var(--input-font-size); border-radius: var(--widget-border-radius); height: var(--widget-height); background: var(--widget-color); color: var(--text-color); width: 100%; } @media (hover: hover) { .lil-gui input:hover { background: var(--hover-color); } .lil-gui input:active { background: var(--focus-color); } } .lil-gui input:disabled { opacity: 1; } .lil-gui input[type=text], .lil-gui input[type=number] { padding: var(--widget-padding); -moz-appearance: textfield; } .lil-gui input[type=text]:focus, .lil-gui input[type=number]:focus { background: var(--focus-color); } .lil-gui input[type=checkbox] { appearance: none; width: var(--checkbox-size); height: var(--checkbox-size); border-radius: var(--widget-border-radius); text-align: center; cursor: pointer; } .lil-gui input[type=checkbox]:checked:before { font-family: "lil-gui"; content: "✓"; font-size: var(--checkbox-size); line-height: var(--checkbox-size); } @media (hover: hover) { .lil-gui input[type=checkbox]:focus { box-shadow: inset 0 0 0 1px var(--focus-color); } } .lil-gui button { outline: none; cursor: pointer; font-family: var(--font-family); font-size: var(--font-size); color: var(--text-color); width: 100%; border: none; } .lil-gui .controller button { height: var(--widget-height); text-transform: none; background: var(--widget-color); border-radius: var(--widget-border-radius); } @media (hover: hover) { .lil-gui .controller button:hover { background: var(--hover-color); } .lil-gui .controller button:focus { box-shadow: inset 0 0 0 1px var(--focus-color); } } .lil-gui .controller button:active { background: var(--focus-color); } @font-face { font-family: "lil-gui"; src: url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff"); }`; function Y(s) { const t = document.createElement("style"); t.innerHTML = s; const i = document.querySelector("head link[rel=stylesheet], head style"); i ? document.head.insertBefore(t, i) : document.head.appendChild(t); } let E = !1, G = class C { /** * Creates a panel that holds controllers. * @example * new GUI(); * new GUI( { container: document.getElementById( 'custom' ) } ); * * @param {object} [options] * @param {boolean} [options.autoPlace=true] * Adds the GUI to `document.body` and fixes it to the top right of the page. * * @param {HTMLElement} [options.container] * Adds the GUI to this DOM element. Overrides `autoPlace`. * * @param {number} [options.width=245] * Width of the GUI in pixels, usually set when name labels become too long. Note that you can make * name labels wider in CSS with `.lil‑gui { ‑‑name‑width: 55% }`. * * @param {string} [options.title=Controls] * Name to display in the title bar. * * @param {boolean} [options.closeFolders=false] * Pass `true` to close all folders in this GUI by default. * * @param {boolean} [options.injectStyles=true] * Injects the default stylesheet into the page if this is the first GUI. * Pass `false` to use your own stylesheet. * * @param {number} [options.touchStyles=true] * Makes controllers larger on touch devices. Pass `false` to disable touch styles. * * @param {GUI} [options.parent] * Adds this GUI as a child in another GUI. Usually this is done for you by `addFolder()`. */ constructor({ parent: t, autoPlace: i = t === void 0, container: e, width: l, title: a = "Controls", closeFolders: o = !1, injectStyles: u = !0, touchStyles: g = !0 } = {}) { if (this.parent = t, this.root = t ? t.root : this, this.children = [], this.controllers = [], this.folders = [], this._closed = !1, this._hidden = !1, this.domElement = document.createElement("div"), this.domElement.classList.add("lil-gui"), this.$title = document.createElement("button"), this.$title.classList.add("title"), this.$title.setAttribute("aria-expanded", !0), this.$title.addEventListener("click", () => this.openAnimated(this._closed)), this.$title.addEventListener("touchstart", () => { }, { passive: !0 }), this.$children = document.createElement("div"), this.$children.classList.add("children"), this.domElement.appendChild(this.$title), this.domElement.appendChild(this.$children), this.title(a), this.parent) { this.parent.children.push(this), this.parent.folders.push(this), this.parent.$children.appendChild(this.domElement); return; } this.domElement.classList.add("root"), g && this.domElement.classList.add("allow-touch-styles"), !E && u && (Y(H), E = !0), e ? e.appendChild(this.domElement) : i && (this.domElement.classList.add("autoPlace"), document.body.appendChild(this.domElement)), l && this.domElement.style.setProperty("--width", l + "px"), this._closeFolders = o; } /** * Adds a controller to the GUI, inferring controller type using the `typeof` operator. * @example * gui.add( object, 'property' ); * gui.add( object, 'number', 0, 100, 1 ); * gui.add( object, 'options', [ 1, 2, 3 ] ); * * @param {object} object The object the controller will modify. * @param {string} property Name of the property to control. * @param {number|object|Array} [$1] Minimum value for number controllers, or the set of * selectable values for a dropdown. * @param {number} [max] Maximum value for number controllers. * @param {number} [step] Step value for number controllers. * @returns {Controller} */ add(t, i, e, l, a) { if (Object(e) === e) return new B(this, t, i, e); const o = t[i]; switch (typeof o) { case "number": return new I(this, t, i, e, l, a); case "boolean": return new L(this, t, i); case "string": return new z(this, t, i); case "function": return new y(this, t, i); } console.error(`gui.add failed property:`, i, ` object:`, t, ` value:`, o); } /** * Adds a color controller to the GUI. * @example * params = { * cssColor: '#ff00ff', * rgbColor: { r: 0, g: 0.2, b: 0.4 }, * customRange: [ 0, 127, 255 ], * }; * * gui.addColor( params, 'cssColor' ); * gui.addColor( params, 'rgbColor' ); * gui.addColor( params, 'customRange', 255 ); * * @param {object} object The object the controller will modify. * @param {string} property Name of the property to control. * @param {number} rgbScale Maximum value for a color channel when using an RGB color. You may * need to set this to 255 if your colors are too bright. * @returns {Controller} */ addColor(t, i, e = 1) { return new O(this, t, i, e); } /** * Adds a folder to the GUI, which is just another GUI. This method returns * the nested GUI so you can add controllers to it. * @example * const folder = gui.addFolder( 'Position' ); * folder.add( position, 'x' ); * folder.add( position, 'y' ); * folder.add( position, 'z' ); * * @param {string} title Name to display in the folder's title bar. * @returns {GUI} */ addFolder(t) { const i = new C({ parent: this, title: t }); return this.root._closeFolders && i.close(), i; } /** * Recalls values that were saved with `gui.save()`. * @param {object} obj * @param {boolean} recursive Pass false to exclude folders descending from this GUI. * @returns {this} */ load(t, i = !0) { return t.controllers && this.controllers.forEach((e) => { e instanceof y || e._name in t.controllers && e.load(t.controllers[e._name]); }), i && t.folders && this.folders.forEach((e) => { e._title in t.folders && e.load(t.folders[e._title]); }), this; } /** * Returns an object mapping controller names to values. The object can be passed to `gui.load()` to * recall these values. * @example * { * controllers: { * prop1: 1, * prop2: 'value', * ... * }, * folders: { * folderName1: { controllers, folders }, * folderName2: { controllers, folders } * ... * } * } * * @param {boolean} recursive Pass false to exclude folders descending from this GUI. * @returns {object} */ save(t = !0) { const i = { controllers: {}, folders: {} }; return this.controllers.forEach((e) => { if (!(e instanceof y)) { if (e._name in i.controllers) throw new Error(`Cannot save GUI with duplicate property "${e._name}"`); i.controllers[e._name] = e.save(); } }), t && this.folders.forEach((e) => { if (e._title in i.folders) throw new Error(`Cannot save GUI with duplicate folder "${e._title}"`); i.folders[e._title] = e.save(); }), i; } /** * Opens a GUI or folder. GUI and folders are open by default. * @param {boolean} open Pass false to close. * @returns {this} * @example * gui.open(); // open * gui.open( false ); // close * gui.open( gui._closed ); // toggle */ open(t = !0) { return this._setClosed(!t), this.$title.setAttribute("aria-expanded", !this._closed), this.domElement.classList.toggle("closed", this._closed), this; } /** * Closes the GUI. * @returns {this} */ close() { return this.open(!1); } _setClosed(t) { this._closed !== t && (this._closed = t, this._callOnOpenClose(this)); } /** * Shows the GUI after it's been hidden. * @param {boolean} show * @returns {this} * @example * gui.show(); * gui.show( false ); // hide * gui.show( gui._hidden ); // toggle */ show(t = !0) { return this._hidden = !t, this.domElement.style.display = this._hidden ? "none" : "", this; } /** * Hides the GUI. * @returns {this} */ hide() { return this.show(!1); } openAnimated(t = !0) { return this._setClosed(!t), this.$title.setAttribute("aria-expanded", !this._closed), requestAnimationFrame(() => { const i = this.$children.clientHeight; this.$children.style.height = i + "px", this.domElement.classList.add("transition"); const e = (a) => { a.target === this.$children && (this.$children.style.height = "", this.domElement.classList.remove("transition"), this.$children.removeEventListener("transitionend", e)); }; this.$children.addEventListener("transitionend", e); const l = t ? this.$children.scrollHeight : 0; this.domElement.classList.toggle("closed", !t), requestAnimationFrame(() => { this.$children.style.height = l + "px"; }); }), this; } /** * Change the title of this GUI. * @param {string} title * @returns {this} */ title(t) { return this._title = t, this.$title.textContent = t, this; } /** * Resets all controllers to their initial values. * @param {boolean} recursive Pass false to exclude folders descending from this GUI. * @returns {this} */ reset(t = !0) { return (t ? this.controllersRecursive() : this.controllers).forEach((e) => e.reset()), this; } /** * Pass a function to be called whenever a controller in this GUI changes. * @param {function({object:object, property:string, value:any, controller:Controller})} callback * @returns {this} * @example * gui.onChange( event => { * event.object // object that was modified * event.property // string, name of property * event.value // new value of controller * event.controller // controller that was modified * } ); */ onChange(t) { return this._onChange = t, this; } _callOnChange(t) { this.parent && this.parent._callOnChange(t), this._onChange !== void 0 && this._onChange.call(this, { object: t.object, property: t.property, value: t.getValue(), controller: t }); } /** * Pass a function to be called whenever a controller in this GUI has finished changing. * @param {function({object:object, property:string, value:any, controller:Controller})} callback * @returns {this} * @example * gui.onFinishChange( event => { * event.object // object that was modified * event.property // string, name of property * event.value // new value of controller * event.controller // controller that was modified * } ); */ onFinishChange(t) { return this._onFinishChange = t, this; } _callOnFinishChange(t) { this.parent && this.parent._callOnFinishChange(t), this._onFinishChange !== void 0 && this._onFinishChange.call(this, { object: t.object, property: t.property, value: t.getValue(), controller: t }); } /** * Pass a function to be called when this GUI or its descendants are opened or closed. * @param {function(GUI)} callback * @returns {this} * @example * gui.onOpenClose( changedGUI => { * console.log( changedGUI._closed ); * } ); */ onOpenClose(t) { return this._onOpenClose = t, this; } _callOnOpenClose(t) { this.parent && this.parent._callOnOpenClose(t), this._onOpenClose !== void 0 && this._onOpenClose.call(this, t); } /** * Destroys all DOM elements and event listeners associated with this GUI. */ destroy() { this.parent && (this.parent.children.splice(this.parent.children.indexOf(this), 1), this.parent.folders.splice(this.parent.folders.indexOf(this), 1)), this.domElement.parentElement && this.domElement.parentElement.removeChild(this.domElement), Array.from(this.children).forEach((t) => t.destroy()); } /** * Returns an array of controllers contained by this GUI and its descendents. * @returns {Controller[]} */ controllersRecursive() { let t = Array.from(this.controllers); return this.folders.forEach((i) => { t = t.concat(i.controllersRecursive()); }), t; } /** * Returns an array of folders contained by this GUI and its descendents. * @returns {GUI[]} */ foldersRecursive() { let t = Array.from(this.folders); return this.folders.forEach((i) => { t = t.concat(i.foldersRecursive()); }), t; } }; const P = '@import"https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap";.lil-gui{--brand-color: #0093ff;--border-radius: 4px;--background-color: #ffffff;--text-color: #202020;--title-background-color: #ffffff;--title-text-color: #202020;--widget-color: #f5f5f5;--hover-color: #d5d5d5;--focus-color: #f5f5f5;--number-color: #202020;--string-color: #202020;--font-size: 12px;--input-font-size: 12px;--font-family: "IBM Plex Sans";--font-family-mono: "IBM Plex Mono";--padding: 12px;--spacing: 10px;--widget-border-radius: 4px}.sl-theme-dark .lil-gui,.dark .lil-gui{--brand-color: #0093ff;--border-radius: 4px;--background-color: #3b4453;--text-color: #fff;--title-background-color: #3b4453;--title-text-color: #fff;--widget-color: #4c5b70;--hover-color: #5c6b80;--focus-color: #5c6b80;--number-color: #fff;--string-color: #fff;--highlight-color: #accfff}.lil-gui input[type=checkbox]:checked:before{background-color:var(--brand-color);color:#fff;border-radius:4px}.lil-gui .controller.number .fill{border-right-color:var(--brand-color)}.sl-theme-dark .lil-gui .controller.number .fill,.dark .lil-gui .controller.number .fill{border-right-color:var(--highlight-color)}.lil-gui.root{border:1px solid #d6d6d6;border-radius:var(--border-radius);padding-bottom:4px}.dark .lil-gui.root,.sl-theme-dark .lil-gui.root{border:none}.lil-gui .title{border-top-left-radius:var(--border-radius);border-top-right-radius:var(--border-radius)}.lil-gui .controller button{padding-top:6px;padding-bottom:6px;height:26px;font-weight:500}.lil-gui .function.primary button{background-color:var(--brand-color);color:#fff;border-radius:var(--widget-border-radius);padding:4px 8px;font-weight:500}'; c.prototype.className = function(s) { return this.domElement.classList.add(s), this; }; class T extends G { constructor(t) { super(t), this._addCustomStyles(); } _addCustomStyles() { const t = document.createElement("style"); t.textContent = P, document.head.appendChild(t); } } export { T as GUI };