Merge branch 'main' into next

This commit is contained in:
nocobase[bot]
2026-08-13 14:54:16 +00:00
2 changed files with 147 additions and 4 deletions
@@ -8,23 +8,76 @@
*/
import type { ButtonProps } from 'antd/es/button';
import { reaction } from '@formily/reactive';
import { tExpr } from '@nocobase/flow-engine';
import { FilterFormActionModel } from './FilterFormActionModel';
type FilterFormBlockState = {
autoTriggerFilter: boolean;
};
const mountedSubmitActions = new WeakSet<object>();
const visibleSubmitActionsByBlock = new WeakMap<FilterFormBlockState, Set<object>>();
const hiddenReactionDisposers = new WeakMap<object, () => void>();
const syncAutoTriggerFilter = (blockModel: FilterFormBlockState, lifecycleAction: object & { hidden?: boolean }) => {
let visibleSubmitActions = visibleSubmitActionsByBlock.get(blockModel);
if (mountedSubmitActions.has(lifecycleAction) && !lifecycleAction.hidden) {
if (!visibleSubmitActions) {
visibleSubmitActions = new Set<object>();
visibleSubmitActionsByBlock.set(blockModel, visibleSubmitActions);
}
visibleSubmitActions.add(lifecycleAction);
} else {
visibleSubmitActions?.delete(lifecycleAction);
}
blockModel.autoTriggerFilter = Boolean(!visibleSubmitActions?.size);
if (!visibleSubmitActions?.size) {
visibleSubmitActionsByBlock.delete(blockModel);
}
};
export class FilterFormSubmitActionModel extends FilterFormActionModel {
defaultProps: ButtonProps = {
title: tExpr('Filter'),
type: 'primary',
};
onInit(options): void {
super.onInit(options);
this.context.blockModel.autoTriggerFilter = false;
private getLifecycleAction(): object & { hidden?: boolean } {
return this.context.model || this;
}
private syncAutoTriggerFilter(): void {
const blockModel = this.context.blockModel as FilterFormBlockState;
const lifecycleAction = this.getLifecycleAction();
syncAutoTriggerFilter(blockModel, lifecycleAction);
}
onMount(): void {
super.onMount();
const blockModel = this.context.blockModel as FilterFormBlockState;
const lifecycleAction = this.getLifecycleAction();
mountedSubmitActions.add(lifecycleAction);
hiddenReactionDisposers.get(lifecycleAction)?.();
hiddenReactionDisposers.set(
lifecycleAction,
reaction(
() => lifecycleAction.hidden,
() => syncAutoTriggerFilter(blockModel, lifecycleAction),
),
);
this.syncAutoTriggerFilter();
}
onUnmount(): void {
super.onUnmount();
this.context.blockModel.autoTriggerFilter = true;
const lifecycleAction = this.getLifecycleAction();
hiddenReactionDisposers.get(lifecycleAction)?.();
hiddenReactionDisposers.delete(lifecycleAction);
mountedSubmitActions.delete(lifecycleAction);
this.syncAutoTriggerFilter();
}
}
@@ -0,0 +1,90 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { define, observable } from '@formily/reactive';
import { describe, expect, it } from 'vitest';
import { FilterFormSubmitActionModel } from '../FilterFormSubmitActionModel';
describe('FilterFormSubmitActionModel lifecycle', () => {
const createAction = (blockModel: { autoTriggerFilter: boolean }, hidden = false, lifecycleAction?: object) => {
const action = Object.create(FilterFormSubmitActionModel.prototype) as FilterFormSubmitActionModel;
Object.defineProperty(action, 'props', {
value: {},
});
Object.defineProperty(action, 'context', {
value: { blockModel, model: lifecycleAction || action, defineProperty: () => undefined },
});
action.hidden = hidden;
define(action, {
hidden: observable,
});
action.onInit({});
return action;
};
it('keeps automatic filtering disabled when the submit action remounts', () => {
const blockModel = { autoTriggerFilter: true };
const action = createAction(blockModel);
action.onMount();
expect(blockModel.autoTriggerFilter).toBe(false);
action.onUnmount();
expect(blockModel.autoTriggerFilter).toBe(true);
action.onMount();
expect(blockModel.autoTriggerFilter).toBe(false);
});
it('keeps automatic filtering disabled until every mounted submit action unmounts', () => {
const blockModel = { autoTriggerFilter: true };
const firstAction = createAction(blockModel);
const secondAction = createAction(blockModel);
firstAction.onMount();
secondAction.onMount();
firstAction.onUnmount();
expect(blockModel.autoTriggerFilter).toBe(false);
secondAction.onUnmount();
expect(blockModel.autoTriggerFilter).toBe(true);
});
it('only disables automatic filtering while a visible submit action is mounted', () => {
const blockModel = { autoTriggerFilter: true };
const action = createAction(blockModel, true);
action.onMount();
expect(blockModel.autoTriggerFilter).toBe(true);
action.hidden = false;
expect(blockModel.autoTriggerFilter).toBe(false);
action.hidden = true;
expect(blockModel.autoTriggerFilter).toBe(true);
});
it('tracks fork lifecycles independently when submit actions share the same uid', () => {
const blockModel = { autoTriggerFilter: true };
const firstFork = {};
const secondFork = {};
const firstAction = createAction(blockModel, false, firstFork);
const secondAction = createAction(blockModel, false, secondFork);
firstAction.onMount();
secondAction.onMount();
firstAction.onUnmount();
expect(blockModel.autoTriggerFilter).toBe(false);
secondAction.onUnmount();
expect(blockModel.autoTriggerFilter).toBe(true);
});
});