Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 1x 3x 3x 3x 3x 3x 6x 6x 6x 3x 2x 2x 1x 1x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 2x 2x 6x 4x 4x 4x 4x 2x 2x 2x 4x | /**
* Bundle of form-control helpers wired into the Alpine "app" data object via spread.
* @type {Object}
* @property {function(params: Object, desc: {name: string, min: number, max: number}, delta: number): void} stepBy
* Stepper +/- click handler: bump params[desc.name] by delta clamped to [desc.min, desc.max].
* @property {function(parts: Object[], index: number, counter: string): boolean} isDuplicatePart
* True when a parts-section row at index reuses an earlier label.
* @property {function(parts: Object[], index: number): number} duplicateSourceIndex
* 1-based index of the first earlier rho with the same label, or 0 when none.
* @property {function(voice: {min_pitch: number, max_pitch: number}, fieldName: string): void} clampVoicePitchRange
* Voice-config input handler: snap the just-edited pitch field to the other when the range inverts.
* @property {function(el: HTMLInputElement, getValue: Function, setValue: Function): void} setupChordTonesTristate
* Single checkbox cycling free/scale/chord (getValue: () => 'free'|'scale'|'chord'; setValue: (v) => void);
* chord = checked, scale = indeterminate, free = neither.
* @property {function(formEl: HTMLElement, triData: Object<string,Object<string,string>>): void} setupFeedbackTristates
* Form-scan over fieldset[data-tri-group] input[data-tri]: cycle neutral/negative/positive;
* mutates triData in place.
*/
export const CONTROLS = {
stepBy,
isDuplicatePart,
duplicateSourceIndex,
clampVoicePitchRange,
setupChordTonesTristate,
setupFeedbackTristates,
};
/** @private */
function stepBy(params, descriptor, delta) {
const { name, min, max } = descriptor;
const v = params[name] + delta;
params[name] = Math.max(min, Math.min(max, v));
}
/** @private */
function isDuplicatePart(parts, index, counter) {
const inSection = counter === "parts" || counter === "step1.parts";
return inSection && duplicateSourceIndex(parts, index) > 0;
}
/** @private */
function duplicateSourceIndex(parts, index) {
const list = parts ?? [];
const label = list[index]?.label;
return list.slice(0, index).findIndex((p) => label && p?.label === label) + 1;
}
/** @private */
function clampVoicePitchRange(voice, fieldName) {
if (voice.max_pitch < voice.min_pitch) {
const other = fieldName === "min_pitch" ? "max_pitch" : "min_pitch";
voice[fieldName] = voice[other];
}
}
/** @private */
function setupChordTonesTristate(el, getValue, setValue) {
const states = ["free", "scale", "chord"];
cycleOnChange(el, states, getValue, (v) => {
setValue(v);
el.checked = v === "chord";
el.indeterminate = v === "scale";
});
}
/** @private */
function setupFeedbackTristates(formEl, triData) {
const states = ["neutral", "negative", "positive"];
for (const fieldset of formEl.querySelectorAll("fieldset[data-tri-group]")) {
const group = fieldset.dataset.triGroup;
triData[group] = {};
const inputs = fieldset.querySelectorAll("input[type=checkbox][data-tri]");
for (const input of inputs) {
const name = input.name;
triData[group][name] = states[0];
cycleOnChange(
input,
states,
() => triData[group][name],
(v) => {
triData[group][name] = v;
applyFeedbackVisual(input, v);
},
);
}
}
}
/** @private Apply current value, then advance to next state on each change event. */
function cycleOnChange(target, states, getValue, applyValue) {
applyValue(getValue());
target.addEventListener("change", () => {
applyValue(states[(states.indexOf(getValue()) + 1) % states.length]);
});
}
/** @private Visual side-effect for one feedback tristate: aria-invalid + class flip. */
function applyFeedbackVisual(el, v) {
el.checked = v === "positive";
el.indeterminate = v === "negative";
el.classList.remove("tristate-positive", "tristate-negative");
if (v === "neutral") {
el.removeAttribute("aria-invalid");
return;
}
el.setAttribute("aria-invalid", v === "negative" ? "true" : "false");
el.classList.add(`tristate-${v}`);
}
|