import { h, render, withDirectives } from 'vue'; import { formatNumber, t } from '../i18n/index.js'; import { vRangeValue } from './rail/rangeValue.js'; /** A slider value in the UI language: 0.85 in English, 0,85 in Spanish. */ const formatParam = (value, digits) => formatNumber(value, { minimumFractionDigits: digits, maximumFractionDigits: digits, useGrouping: false, }); /** * The generated rows: a label, the shared native slider and its value in the * UI language. A render function, so the portable input package needs no * single-file-component compiler. */ const StyleParameterRows = { props: { rows: { type: Array, required: true }, onInput: { type: Function, required: true }, }, render() { return this.rows.map((row) => h('div', { key: row.name, class: 'param-slider-row' }, [ h('span', { class: 'param-label' }, row.label), withDirectives( h('input', { type: 'range', class: 'gev-quantitative-slider param-slider', min: row.min, max: row.max, step: row.step, 'aria-label': row.label, onInput: (event) => this.onInput(row.name, event.target.value), }), [[vRangeValue, row.value]], ), h('span', { class: 'param-value' }, row.text), ]), ); }, }; /** * Own generated parameter rows and their input handling. Renders * StyleParameterRows into `container` and patches it synchronously. * The caller supplies uniform metadata/value access and rendering/persistence * callbacks. Replacing or destroying rows revokes all old input handlers. * @param {object} options * @param {HTMLElement} options.container Container owned by this component. * @returns {{render: Function, clear: Function, destroy: Function}} */ export function createStyleParameters({ container }) { let rows = []; let onInput = () => {}; // Each render's handlers act only while that render is current. let generation = 0; let destroyed = false; const paint = () => render(h(StyleParameterRows, { rows, onInput }), container); const clear = () => { if (destroyed) return; generation++; rows = []; onInput = () => {}; render(null, container); }; return { /** Render uniform metadata with readValue, writeValue and onChange callbacks. */ render({ uniforms, readValue, writeValue, onChange }) { if (destroyed) return; clear(); const current = generation; rows = Object.entries(uniforms).map(([name, metadata]) => ({ name, label: t(metadata.label), min: metadata.min, max: metadata.max, step: metadata.max <= 1 ? '0.01' : '0.1', digits: metadata.max <= 1 ? 2 : 1, value: readValue(name), text: '', })); onInput = (name, raw) => { if (destroyed || current !== generation) return; const value = parseFloat(raw); writeValue(name, value); rows = rows.map((row) => row.name === name ? { ...row, value, text: formatParam(value, row.digits) } : row, ); paint(); onChange(); }; paint(); // The readout shows the value the slider holds once the browser has // clamped and snapped it to its step. const sliders = container.querySelectorAll('input.param-slider'); rows = rows.map((row, index) => ({ ...row, text: formatParam(parseFloat(sliders[index].value), row.digits), })); paint(); }, clear, destroy() { if (destroyed) return; clear(); destroyed = true; }, }; }