Environment
-
Photoshop: 27.3.1
-
UXP manifestVersion 5
-
OS: Microsoft Windows 11 Pro
Summary
When dynamically replacing the options of one <select> element, other <select> elements appear to be affected as well.
The issue becomes visible after a short delay or when hovering over the other dropdowns.
Expected Behavior
Only the targeted <select> element should update.
Other dropdowns should remain unchanged.
Actual Behavior
After clicking the button to replace the top dropdown’s options:
-
Other dropdowns initially appear unchanged.
-
After waiting briefly or hovering over them, their options visually update.
-
This suggests a rendering or invalidation issue.
If this behavior is a known limitation or rendering issue, any recommended workaround would be greatly appreciated.
The following is a sample code.
Since I was unable to attach files, I am pasting it directly below.
(() => {
const targetSelect = document.getElementById("dropdown-1");
const replaceButton = document.getElementById("replace-top-dropdown");
const replaceSecondButton = document.getElementById("replace-second-dropdown");
// const randomizeButton = document.getElementById("randomize-top-selection");
const statusText = document.getElementById("status");
const internalTemplate = document.getElementById("internal-html-option-template");
if (!targetSelect || !replaceButton || !replaceSecondButton || !statusText || !internalTemplate) {
console.error("[dynamicdropdown] required element was not found", {
targetSelect,
replaceButton,
replaceSecondButton,
// randomizeButton,
statusText,
internalTemplate,
});
return;
}
const dynamicOptions = [
{ value: "dynamic-metal", label: "Metal" },
{ value: "dynamic-wood", label: "Wood" },
{ value: "dynamic-stone", label: "Stone" },
{ value: "dynamic-fabric", label: "Fabric" },
];
const dynamicOptionsSecond = [
{ value: "logic2-carbon", label: "Carbon" },
{ value: "logic2-rubber", label: "Rubber" },
{ value: "logic2-ceramic", label: "Ceramic" },
{ value: "logic2-glass", label: "Glass" },
];
function updateStatus(message) {
statusText.textContent = message;
}
function getDropdownSnapshot() {
const selects = Array.from(document.querySelectorAll("select"));
return selects.map((select) => ({
id: select.id,
selectedIndex: select.selectedIndex,
selectedValue: select.value,
options: Array.from(select.options).map((option) => ({
value: option.value,
text: option.text,
})),
}));
}
function removeInternalHtmlTemplate() {
const parent = internalTemplate.parentElement;
if (!parent) {
console.warn("[dynamicdropdown] templateの親要素がありません。", { internalTemplate });
return;
}
parent.removeChild(internalTemplate);
console.log("[dynamicdropdown] 内部HTML(template)を削除しました");
}
function buildOptions(optionDefs) {
return optionDefs.map(({ value, label }) => {
const option = document.createElement("option");
option.value = value;
option.textContent = label;
return option;
});
}
function appendDynamicOptions() {
targetSelect.innerHTML = "";
dynamicOptions.forEach((optionDef) => {
const option = document.createElement("option");
option.value = optionDef.value;
option.textContent = optionDef.label;
targetSelect.appendChild(option);
});
targetSelect.selectedIndex = 0;
console.log("[dynamicdropdown] 動的オプションを追加しました", {
optionCount: targetSelect.options.length,
firstOption: targetSelect.options[0]?.value,
});
}
function replaceTopOptionsWithFragment() {
const options = buildOptions(dynamicOptionsSecond);
const fragment = document.createDocumentFragment();
options.forEach((option) => {
fragment.appendChild(option);
});
targetSelect.replaceChildren(fragment);
targetSelect.selectedIndex = 0;
console.log("[dynamicdropdown] トップドロップダウンへ動的オプションを追加しました(logic=replaceChildren-fragment)", {
optionCount: targetSelect.options.length,
firstOption: targetSelect.options[0]?.value,
});
}
replaceButton.addEventListener("click", () => {
const beforeSnapshot = getDropdownSnapshot();
console.log("[dynamicdropdown] 置換ボタン(トップ/ロジック1)が押されました");
removeInternalHtmlTemplate();
appendDynamicOptions();
console.log("[dynamicdropdown] ドロップダウン状態(ロジック1置換前/後)", {
before: beforeSnapshot,
after: getDropdownSnapshot(),
});
updateStatus("Dropdown 1 was switched to dynamic options using logic 1 (innerHTML clear + append)");
});
replaceSecondButton.addEventListener("click", () => {
const beforeSnapshot = getDropdownSnapshot();
console.log("[dynamicdropdown] 置換ボタン(トップ/ロジック2)が押されました");
replaceTopOptionsWithFragment();
console.log("[dynamicdropdown] ドロップダウン状態(ロジック2置換前/後)", {
before: beforeSnapshot,
after: getDropdownSnapshot(),
});
updateStatus("Dropdown 1 was switched to dynamic options using logic 2 (replaceChildren + fragment)");
});
// randomizeButton.addEventListener("click", () => {
// const beforeSnapshot = getDropdownSnapshot();
// const optionCount = targetSelect.options.length;
// if (optionCount <= 0) {
// console.warn("[dynamicdropdown] randomize skipped: no options", {
// optionCount,
// });
// updateStatus("Randomize is unavailable because Dropdown 1 has no options");
// return;
// }
// const randomIndex = Math.floor(Math.random() * optionCount);
// const beforeValue = targetSelect.value;
// targetSelect.selectedIndex = randomIndex;
// const afterValue = targetSelect.value;
// console.log("[dynamicdropdown] Dropdown 1 selection was randomized", {
// beforeValue,
// afterValue,
// randomIndex,
// optionCount,
// beforeSnapshot,
// afterSnapshot: getDropdownSnapshot(),
// });
// updateStatus(`Dropdown 1 selection was randomized: ${afterValue}`);
// });
})();
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="styles.css" />
<title>Dynamic Dropdown Sample</title>
</head>
<body>
<main class="container">
<h1>Dynamic Dropdown Sample</h1>
<p class="hint">
Initially, four dropdowns are rendered statically. Use the buttons to dynamically rebuild only the first dropdown.
</p>
<section class="control-group">
<label for="dropdown-1">Dropdown 1 (dynamic target)</label>
<select id="dropdown-1" data-role="replace-target">
<option value="initial-a">Static A</option>
<option value="initial-b">Static B</option>
<option value="initial-c">Static C</option>
</select>
</section>
<section class="control-group">
<label for="dropdown-2">Dropdown 2</label>
<select id="dropdown-2">
<option value="shape-circle">Circle</option>
<option value="shape-square">Square</option>
<option value="shape-triangle">Triangle</option>
</select>
</section>
<section class="control-group">
<label for="dropdown-3">Dropdown 3</label>
<select id="dropdown-3">
<option value="size-small">Small</option>
<option value="size-medium">Medium</option>
<option value="size-large">Large</option>
</select>
</section>
<section class="control-group">
<label for="dropdown-4">Dropdown 4</label>
<select id="dropdown-4">
<option value="blend-normal">Normal</option>
<option value="blend-multiply">Multiply</option>
<option value="blend-screen">Screen</option>
</select>
</section>
<button id="replace-top-dropdown" type="button">Dynamically rewrite Dropdown 1 options (innerHTML rewrite)</button>
<button id="replace-second-dropdown" type="button">Dynamically rewrite Dropdown 1 options (replace)</button>
<!-- <button id="randomize-top-selection" type="button">Randomize Dropdown 1 selection</button> -->
<p id="status" class="status">Ready</p>
<template id="internal-html-option-template">
<option value="dynamic-metal">Metal</option>
<option value="dynamic-wood">Wood</option>
<option value="dynamic-stone">Stone</option>
<option value="dynamic-fabric">Fabric</option>
</template>
</main>
<script src="main.js"></script>
</body>
</html>
{
"id": "com.example.dynamicdropdownsample",
"name": "Dynamic Dropdown Sample",
"version": "0.1.0",
"main": "index.html",
"host": {
"app": "PS",
"minVersion": "24.0.0"
},
"manifestVersion": 5,
"entrypoints": [
{
"type": "panel",
"id": "dynamic-dropdown-panel",
"label": "Dynamic Dropdown Sample",
"minimumSize": {
"width": 320,
"height": 320
},
"maximumSize": {
"width": 640,
"height": 800
},
"preferredDockedSize": {
"width": 360,
"height": 420
},
"preferredFloatingSize": {
"width": 420,
"height": 500
}
}
]
}
thanks.
