Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
## Changelog


### 2.14.6

_2026-09-18_

#### Features

- Components [text] add `isTruncated` expose (#24822 by @rzzf)

#### Bug fixes

- Components [table] defer multiple filters until confirm (#24749 by @william-xue)
- Components [form] avoid warning for zero label width (#24820 by @wjp980108)
- Components [select/v2] avoid circular tag width calculation (#24743 by @rzzf)
- Components [upload] preserve dropped directory paths (#24838 by @rzzf)
- Components prevent duplicate keyboard handling in dates picker (#24860 by @ixyzorg)
- Components [loading] memory leak (#24865 by @btea)

### 2.14.5

_2026-08-21_
Expand Down
21 changes: 21 additions & 0 deletions packages/components/loading/__tests__/loading.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,27 @@ describe('Loading', () => {
expect(loadingInstance.visible.value).toBeFalsy()
})

test('close service should release detached dom', async () => {
loadingInstance = Loading()
const mask = document.querySelector('.el-loading-mask') as HTMLElement
expect(mask).toBeTruthy()

vi.useFakeTimers()
loadingInstance.close()
vi.runAllTimers()
vi.useRealTimers()
await nextTick()

// the mask is removed from the document ...
expect(mask.parentNode).toBeNull()
// ... and no reference on the instance keeps it alive anymore
expect(loadingInstance.$el).toBeNull()
const internalInstance = loadingInstance.vm.$
expect(internalInstance.vnode.el).toBeNull()
expect(internalInstance.subTree).toBeNull()
expect(internalInstance.appContext.app._container).toBeNull()
})

test('target service', async () => {
const container = document.createElement('div')
container.className = 'loading-container'
Expand Down
16 changes: 13 additions & 3 deletions packages/components/loading/src/loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function createLoadingComponent(
appContext: AppContext | null
) {
let afterLeaveTimer: ReturnType<typeof setTimeout>
let destroyed = false
// IMPORTANT NOTE: this is only a hacking way to expose the injections on an
// instance, DO NOT FOLLOW this pattern in your own code.
const afterLeaveFlag = ref(false)
Expand All @@ -38,8 +39,12 @@ export function createLoadingComponent(
}

function destroySelf() {
if (destroyed) return
destroyed = true
const target = data.parent
const ns = (vm as any).ns as UseNamespaceReturn
// Compatible with the instance data format of vue@3.2.12 and earlier versions #12351
const ns =
((vm as any).ns as UseNamespaceReturn) || (vm as any)._.exposed.ns
if (!target.vLoadingAddClassList) {
let loadingNumber: number | string | null =
target.getAttribute('loading-number')
Expand All @@ -54,6 +59,11 @@ export function createLoadingComponent(
}
removeElLoadingChild()
loadingInstance.unmount()

const internalInstance = vm.$ as any
internalInstance.vnode.el = null
internalInstance.subTree = null
loadingInstance._container = null
}
function removeElLoadingChild(): void {
vm.$el?.parentNode?.removeChild(vm.$el)
Expand Down Expand Up @@ -165,8 +175,8 @@ export function createLoadingComponent(
close,
handleAfterLeave,
vm,
get $el(): HTMLElement {
return vm.$el
get $el(): HTMLElement | null {
return destroyed ? null : vm.$el
},
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/components/loading/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Loading = function (
}
resolved.parent.setAttribute('loading-number', loadingNumber)

resolved.parent.appendChild(instance.$el)
resolved.parent.appendChild(instance.$el!)

// after instance render, then modify visible to trigger transition
nextTick(() => (instance.visible.value = resolved.visible))
Expand Down Expand Up @@ -140,7 +140,7 @@ const addStyle = async (
instance.originalPosition.value = getStyle(parent, 'position')
}
for (const [key, value] of Object.entries(maskStyle)) {
instance.$el.style[key] = value
instance.$el!.style[key] = value
}
}

Expand Down
Loading