diff --git a/CHANGELOG.md b/CHANGELOG.md index 894e067..01cf14e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ - Older API responses without `years_on_record`, and spans outside 1886 to next year, are handled the same as `null`. - A host-supplied year outside the model's span is kept (inserted in order and selected, with one `console.warn`), never dropped. - The widget disables the year select only for `yearFallback: "none"` with nothing to choose (with `includeOther`, Other stays choosable), and only ever undoes its own disabling. +- New option `otherLabel` (default `"Other / not listed"`): the text of the `includeOther` row on make, model and year, for pages in other languages. The option value stays `"other"`. diff --git a/README.md b/README.md index 6591076..03d96ed 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ VehiclesDropdown.attach({ yearFallback: "range", // a model with no register span: "range" (default) or "none" values: "slug", // option values: "slug" (default) or "name" includeOther: true, // append "Other / not listed" — pickers should never dead-end + otherLabel: "Other / not listed", // its text, to translate; the value is always "other" search: true, // searchable combobox instead of a plain select logos: true, // make logos in the list (search mode; free key feature) diff --git a/demo/index.html b/demo/index.html index 8a486c3..d656f4d 100644 --- a/demo/index.html +++ b/demo/index.html @@ -45,6 +45,7 @@

VehiclesDropdown demo

search: true, logos: true, includeOther: true, + otherLabel: "Other / not listed", onChange: (value) => { document.getElementById("state").textContent = JSON.stringify(value, null, 2) }, diff --git a/make-model-dropdown.js b/make-model-dropdown.js index 6eedacb..586013c 100644 --- a/make-model-dropdown.js +++ b/make-model-dropdown.js @@ -28,6 +28,7 @@ yearFallback: "range", // a model with no register span: "range" | "none" values: "slug", // what option values hold: "slug" | "name" includeOther: false, // append an "Other / not listed" escape hatch + otherLabel: "Other / not listed", // its text; the option value stays "other" logos: false, // render make logos (combobox mode only) search: false, // searchable combobox instead of a plain + + `) + const base = apiBase("other-label") + const changes = [] + global.fetch = async (url) => new URL(url).pathname.endsWith("/models") + ? jsonResponse({ models: [{ name: "A3", slug: "a3", years_on_record: { first: 2020, last: 2021 } }] }) + : jsonResponse({ makes: [{ name: "Audi", slug: "audi" }] }) + const lastText = (select) => select.options[select.options.length - 1].textContent + + attach({ + key: "other-label-key", apiBase: base, make: "#make", model: "#model", year: "#year", + includeOther: true, attribution: false + }) + attach({ + key: "other-label-key", apiBase: base, make: "#make-es", model: "#model-es", year: "#year-es", + includeOther: true, otherLabel: "Otra / no aparece", attribution: false, + onChange(value) { changes.push(value) } + }) + await waitFor(() => document.querySelector("#make").options.length === 3 && + document.querySelector("#make-es").options.length === 3) + + for (const [suffix, label] of [["", "Other / not listed"], ["-es", "Otra / no aparece"]]) { + const make = document.querySelector(`#make${suffix}`) + const model = document.querySelector(`#model${suffix}`) + const year = document.querySelector(`#year${suffix}`) + pick(make, "audi") + await waitFor(() => model.options.length === 3) + pick(model, "a3") + assert.deepEqual(optionValues(year), ["", "2021", "2020", "other"]) + for (const select of [make, model, year]) assert.equal(lastText(select), label) + + // The value is the API contract, whatever the page's language. + pick(make, "other") + assert.deepEqual(optionValues(model), ["", "other"]) + assert.equal(lastText(model), label) + } + assert.deepEqual(changes.at(-1), { make: "other", model: null, year: null }) +}) + +test("otherLabel names the Other row in the searchable combobox too", async () => { + const document = installDOM(``) + dom.window.HTMLElement.prototype.scrollIntoView = () => {} + const base = apiBase("other-label-combobox") + global.fetch = async () => jsonResponse({ makes: [{ name: "Audi", slug: "audi" }] }) + + attach({ + key: "other-label-combobox-key", apiBase: base, make: "#make", search: true, + includeOther: true, otherLabel: "Inna / brak na liście", attribution: false + }) + await waitFor(() => document.querySelector("#make").options.length === 3) + const input = document.querySelector("[role=combobox]") + input.dispatchEvent(new Event("focus")) + const rows = Array.from(document.querySelectorAll("[role=option]")) + assert.equal(rows.at(-1).textContent, "Inna / brak na liście") + input.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowUp", bubbles: true })) + input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true })) + assert.equal(document.querySelector("#make").value, "other") + assert.equal(input.value, "Inna / brak na liście") +}) + test("a widget without a year select keeps the exact {make, model} contract", async () => { const document = installDOM(``) const base = apiBase("year-absent")