@@ -271,6 +297,7 @@ export default defineComponent({
{hasContent && (
- {showContent ? (
-
- ) : null}
+ {destroyOnHidden && !showContent ? null : renderContent()}
)}
diff --git a/packages/x/components/thought-chain/__tests__/index.test.tsx b/packages/x/components/thought-chain/__tests__/index.test.tsx
index d9a9ce7b..836f4030 100644
--- a/packages/x/components/thought-chain/__tests__/index.test.tsx
+++ b/packages/x/components/thought-chain/__tests__/index.test.tsx
@@ -346,6 +346,149 @@ describe("ThoughtChain", () => {
expect(wrapper.findAll(".antd-thought-chain-node").length).toBe(2);
});
+ it("keeps the item content mounted and toggles its visibility when destroyOnHidden is false", async () => {
+ const wrapper = mount(ThoughtChain, {
+ props: {
+ items: [
+ {
+ key: "1",
+ title: "Step 1",
+ content: h("span", "Hidden but mounted"),
+ collapsible: true,
+ destroyOnHidden: false,
+ },
+ ],
+ defaultExpandedKeys: [],
+ },
+ });
+
+ const content = wrapper.find(".antd-thought-chain-node-content");
+ expect(content.exists()).toBe(true);
+ expect(wrapper.text()).toContain("Hidden but mounted");
+ expect(content.element.parentElement?.getAttribute("style")).toContain(
+ "display: none",
+ );
+
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+
+ expect(
+ content.element.parentElement?.getAttribute("style") ?? "",
+ ).not.toContain("display: none");
+
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+
+ expect(content.element.parentElement?.getAttribute("style")).toContain(
+ "display: none",
+ );
+ });
+
+ it("preserves item content across collapse when destroyOnHidden is false", async () => {
+ const wrapper = mount(ThoughtChain, {
+ props: {
+ items: [
+ {
+ key: "1",
+ title: "Step 1",
+ content: h("input", { class: "inner-input" }),
+ collapsible: true,
+ destroyOnHidden: false,
+ },
+ ],
+ defaultExpandedKeys: ["1"],
+ },
+ });
+
+ const inputEl = wrapper.find
(".inner-input").element;
+ inputEl.value = "streaming";
+
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+
+ const afterCollapse = wrapper.find(".inner-input");
+ expect(afterCollapse.exists()).toBe(true);
+ // Same DOM node, so the value typed before collapsing survives.
+ expect(afterCollapse.element).toBe(inputEl);
+ expect(afterCollapse.element.value).toBe("streaming");
+ });
+
+ it("keeps toggling after destroyOnHidden changes at runtime", async () => {
+ const createItems = (destroyOnHidden: boolean) => [
+ {
+ key: "1",
+ title: "Step 1",
+ content: h("span", "Dynamic content"),
+ collapsible: true,
+ destroyOnHidden,
+ },
+ ];
+ const wrapper = mount(ThoughtChain, {
+ props: {
+ items: createItems(false),
+ defaultExpandedKeys: ["1"],
+ },
+ });
+
+ await wrapper.setProps({ items: createItems(true) });
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+ expect(wrapper.find(".antd-thought-chain-node-content").exists()).toBe(
+ false,
+ );
+
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+ expect(wrapper.find(".antd-thought-chain-node-content").exists()).toBe(
+ true,
+ );
+
+ await wrapper.setProps({ items: createItems(false) });
+ const content = wrapper.find(".antd-thought-chain-node-content");
+
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+ expect(content.exists()).toBe(true);
+ expect(content.element.parentElement?.getAttribute("style")).toContain(
+ "display: none",
+ );
+
+ await wrapper.find(".antd-thought-chain-node-collapsible").trigger("click");
+ await nextTick();
+ expect(
+ content.element.parentElement?.getAttribute("style") ?? "",
+ ).not.toContain("display: none");
+ });
+
+ it("keeps the item content mounted for controlled expandedKeys when destroyOnHidden is false", async () => {
+ const wrapper = mount(ThoughtChain, {
+ props: {
+ items: [
+ {
+ key: "1",
+ title: "Step 1",
+ content: h("span", "Controlled content"),
+ collapsible: true,
+ destroyOnHidden: false,
+ },
+ ],
+ expandedKeys: ["1"],
+ },
+ });
+
+ expect(wrapper.find(".antd-thought-chain-node-content").exists()).toBe(
+ true,
+ );
+
+ await wrapper.setProps({ expandedKeys: [] });
+ await nextTick();
+
+ expect(wrapper.find(".antd-thought-chain-node-content").exists()).toBe(
+ true,
+ );
+ expect(wrapper.text()).toContain("Controlled content");
+ });
+
it("exposes nativeElement ref", () => {
const wrapper = mount(ThoughtChain, {
props: { items: basicItems },
diff --git a/packages/x/components/thought-chain/interface.ts b/packages/x/components/thought-chain/interface.ts
index 7cdd7545..63ac09f4 100644
--- a/packages/x/components/thought-chain/interface.ts
+++ b/packages/x/components/thought-chain/interface.ts
@@ -100,6 +100,13 @@ export interface ThoughtChainItemType {
* @descZH 标题闪烁动画
*/
blink?: boolean;
+
+ /**
+ * @desc Whether to destroy the content node when collapsed
+ * @descZH 折叠时是否销毁内容节点
+ * @default true
+ */
+ destroyOnHidden?: boolean;
}
export interface ThoughtChainProps extends Omit {