Lower Eve select controls natively in browsers
This commit is contained in:
parent
322fa40713
commit
b68b833cd8
3 changed files with 139 additions and 2 deletions
45
packages/eve-browser-lowering/dist/index.js
vendored
45
packages/eve-browser-lowering/dist/index.js
vendored
|
|
@ -683,7 +683,10 @@ function renderEveComponentProjection(node, options) {
|
|||
toggle.append(el("span", "toggle-copy", stringProp(props.label, "Toggle")));
|
||||
return toggle;
|
||||
}
|
||||
if (kind === "input.number" || kind === "input.select" || kind === "control.range" || kind === "control.input.text" || kind === "control.select") {
|
||||
if (kind === "input.select" || kind === "control.select") {
|
||||
return renderSelect(node, props, children, layout, style, options);
|
||||
}
|
||||
if (kind === "input.number" || kind === "control.range" || kind === "control.input.text") {
|
||||
const field = el("label", "field cultui-field");
|
||||
assignId(field, node);
|
||||
applyBoxProps(field, props);
|
||||
|
|
@ -1538,6 +1541,46 @@ function wireCommand(element, node, commandId, props, options) {
|
|||
}
|
||||
});
|
||||
}
|
||||
function renderSelect(node, props, children, layout, style, options) {
|
||||
const field = el("label", "field cultui-field cultui-select");
|
||||
assignId(field, node);
|
||||
applyBoxProps(field, props);
|
||||
applyGeneratedLayout(field, layout, style);
|
||||
const label = stringProp(props.label, "");
|
||||
if (label)
|
||||
field.append(el("span", "field-label", label));
|
||||
const select = el("select", "field-control cultui-field-select");
|
||||
select.setAttribute("aria-label", label || node.id || "Select");
|
||||
for (const child of children) {
|
||||
if (child.kind !== "control.option" && child.kind !== "input.option" && child.kind !== "option")
|
||||
continue;
|
||||
const optionProps = objectProps(child.props);
|
||||
const option = document.createElement("option");
|
||||
option.value = stringProp(optionProps.value, child.id || "");
|
||||
option.textContent = stringProp(optionProps.label, stringProp(optionProps.text, option.value));
|
||||
option.disabled = boolProp(optionProps.disabled) ||
|
||||
(optionProps.enabled !== undefined && !boolProp(optionProps.enabled));
|
||||
select.append(option);
|
||||
}
|
||||
select.value = stringProp(props.value, "");
|
||||
select.disabled = boolProp(props.disabled) || (props.enabled !== undefined && !boolProp(props.enabled));
|
||||
const commandId = resolveComponentCommandId(props, node);
|
||||
if (commandId) {
|
||||
select.dataset.commandId = commandId;
|
||||
select.addEventListener("change", () => {
|
||||
const intent = createEveCommandIntent(commandId, {
|
||||
...props,
|
||||
action: {
|
||||
...objectProps(props.action),
|
||||
value: select.value,
|
||||
},
|
||||
}, options);
|
||||
void options.commandSink?.(intent, node);
|
||||
});
|
||||
}
|
||||
field.append(select);
|
||||
return field;
|
||||
}
|
||||
function renderSlider(props, children, options) {
|
||||
const anatomy = resolveSliderAnatomy(props, children, options);
|
||||
const box = objectProps(anatomy.find(part => part.kind === "control.box")?.props);
|
||||
|
|
|
|||
|
|
@ -885,7 +885,11 @@ function renderEveComponentProjection(
|
|||
return toggle;
|
||||
}
|
||||
|
||||
if (kind === "input.number" || kind === "input.select" || kind === "control.range" || kind === "control.input.text" || kind === "control.select") {
|
||||
if (kind === "input.select" || kind === "control.select") {
|
||||
return renderSelect(node, props, children, layout, style, options);
|
||||
}
|
||||
|
||||
if (kind === "input.number" || kind === "control.range" || kind === "control.input.text") {
|
||||
const field = el("label", "field cultui-field");
|
||||
assignId(field, node);
|
||||
applyBoxProps(field, props);
|
||||
|
|
@ -1816,6 +1820,54 @@ function wireCommand(
|
|||
});
|
||||
}
|
||||
|
||||
function renderSelect(
|
||||
node: EveSurfaceComponent,
|
||||
props: Record<string, unknown>,
|
||||
children: EveSurfaceComponent[],
|
||||
layout: Record<string, unknown>,
|
||||
style: Record<string, unknown>,
|
||||
options: EveBrowserLoweringOptions,
|
||||
): HTMLElement {
|
||||
const field = el("label", "field cultui-field cultui-select");
|
||||
assignId(field, node);
|
||||
applyBoxProps(field, props);
|
||||
applyGeneratedLayout(field, layout, style);
|
||||
const label = stringProp(props.label, "");
|
||||
if (label) field.append(el("span", "field-label", label));
|
||||
|
||||
const select = el("select", "field-control cultui-field-select") as HTMLSelectElement;
|
||||
select.setAttribute("aria-label", label || node.id || "Select");
|
||||
for (const child of children) {
|
||||
if (child.kind !== "control.option" && child.kind !== "input.option" && child.kind !== "option") continue;
|
||||
const optionProps = objectProps(child.props);
|
||||
const option = document.createElement("option");
|
||||
option.value = stringProp(optionProps.value, child.id || "");
|
||||
option.textContent = stringProp(optionProps.label, stringProp(optionProps.text, option.value));
|
||||
option.disabled = boolProp(optionProps.disabled) ||
|
||||
(optionProps.enabled !== undefined && !boolProp(optionProps.enabled));
|
||||
select.append(option);
|
||||
}
|
||||
select.value = stringProp(props.value, "");
|
||||
select.disabled = boolProp(props.disabled) || (props.enabled !== undefined && !boolProp(props.enabled));
|
||||
|
||||
const commandId = resolveComponentCommandId(props, node);
|
||||
if (commandId) {
|
||||
select.dataset.commandId = commandId;
|
||||
select.addEventListener("change", () => {
|
||||
const intent = createEveCommandIntent(commandId, {
|
||||
...props,
|
||||
action: {
|
||||
...objectProps(props.action),
|
||||
value: select.value,
|
||||
},
|
||||
}, options);
|
||||
void options.commandSink?.(intent, node);
|
||||
});
|
||||
}
|
||||
field.append(select);
|
||||
return field;
|
||||
}
|
||||
|
||||
function renderSlider(
|
||||
props: Record<string, unknown>,
|
||||
children: EveSurfaceComponent[],
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ function installDom() {
|
|||
"HTMLElement",
|
||||
"HTMLButtonElement",
|
||||
"HTMLInputElement",
|
||||
"HTMLSelectElement",
|
||||
"HTMLOptionElement",
|
||||
"HTMLTextAreaElement",
|
||||
"HTMLImageElement",
|
||||
];
|
||||
|
|
@ -152,3 +154,43 @@ test("browser lowering isolates two hosts and patches only the bound component",
|
|||
restoreDom();
|
||||
}
|
||||
});
|
||||
|
||||
test("browser lowering renders command-backed select options", () => {
|
||||
const restoreDom = installDom();
|
||||
try {
|
||||
const commands = [];
|
||||
const host = document.querySelector("#a");
|
||||
renderEveSurface({
|
||||
providerId: "hangar-provider",
|
||||
surface: {
|
||||
id: "hangar",
|
||||
root: {
|
||||
id: "hangar.verse",
|
||||
kind: "control.select",
|
||||
props: { label: "VERSE", value: "local", command: "hangar.select_verse" },
|
||||
children: [
|
||||
{ kind: "control.option", props: { label: "Local", value: "local" } },
|
||||
{ kind: "control.option", props: { label: "GameCult", value: "gamecult" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
}, host, {
|
||||
clientId: "browser-test",
|
||||
commandSink: intent => commands.push(intent),
|
||||
});
|
||||
|
||||
const select = host.querySelector("select");
|
||||
assert.deepEqual([...select.options].map(option => [option.text, option.value]), [
|
||||
["Local", "local"],
|
||||
["GameCult", "gamecult"],
|
||||
]);
|
||||
assert.equal(select.value, "local");
|
||||
select.value = "gamecult";
|
||||
select.dispatchEvent(new window.Event("change", { bubbles: true }));
|
||||
assert.equal(commands.length, 1);
|
||||
assert.equal(commands[0].command, "hangar.select_verse");
|
||||
assert.equal(commands[0].payload.value, "gamecult");
|
||||
} finally {
|
||||
restoreDom();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue