fix(editor): missing signal of optional flat props (#13762)

Close https://github.com/toeverything/AFFiNE/issues/13750

#### PR Dependency Tree


* **PR #13762** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Optional block properties are now supported (e.g., flat-table), with
default values applied automatically when not set.

* **Bug Fixes**
* More reliable initialization and syncing of block properties, ensuring
defaults appear consistently.
* Change notifications now correctly reflect updates to
optional/defaulted properties.

* **Tests**
* Added tests verifying optional property behavior, default application,
syncing, and change events.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->





#### PR Dependency Tree


* **PR #13762** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
L-Sun
2025-10-16 12:38:47 +08:00
committed by GitHub
parent e4f9d42990
commit 5c0e3b8a7f
3 changed files with 36 additions and 14 deletions

View File

@@ -51,6 +51,7 @@ const flatTableSchema = defineBlockSchema({
textCols: {} as Record<string, Text>,
rows: {} as Record<string, { color: string }>,
labels: [] as Array<string>,
optional: undefined as string | undefined,
}),
metadata: {
role: 'content',
@@ -494,6 +495,16 @@ describe('flat', () => {
expect(model.props.textCols$.value.a.toDelta()).toEqual([
{ insert: 'test' },
]);
onChange.mockClear();
expect(model.props).not.toHaveProperty('optional');
expect(model.props).toHaveProperty('optional$');
model.props.optional$.value = 'test';
expect(model.props.optional).toBe('test');
expect(model.props.optional$.value).toBe('test');
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(expect.anything(), 'optional', true);
expect(yBlock.get('prop:optional')).toBe('test');
});
test('stash and pop', () => {

View File

@@ -47,6 +47,7 @@ export class FlatSyncController {
}
const model = schema.model.toModel?.() ?? new BlockModel<object>();
const defaultProps = schema.model.props?.(internalPrimitives);
model.schema = schema;
model.id = this.id;
@@ -55,7 +56,8 @@ export class FlatSyncController {
const reactive = new ReactiveFlatYMap(
this.yBlock,
model.deleted,
this.onChange
this.onChange,
defaultProps
);
this._reactive = reactive;
const proxy = reactive.proxy;
@@ -66,17 +68,6 @@ export class FlatSyncController {
model.store = this.doc;
}
const defaultProps = schema.model.props?.(internalPrimitives);
if (defaultProps) {
Object.entries(defaultProps).forEach(([key, value]) => {
if (key in proxy) {
return;
}
if (value === undefined) return;
proxy[key] = value;
});
}
return model;
}

View File

@@ -99,7 +99,8 @@ export class ReactiveFlatYMap extends BaseReactiveYData<
constructor(
protected readonly _ySource: YMap<unknown>,
private readonly _onDispose: Subject<void>,
private readonly _onChange?: OnChange
private readonly _onChange?: OnChange,
defaultProps?: Record<string, unknown>
) {
super();
this._initialized = false;
@@ -112,7 +113,7 @@ export class ReactiveFlatYMap extends BaseReactiveYData<
const proxy = this._getProxy(source, source);
Object.entries(source).forEach(([key, value]) => {
const initSignals = (key: string, value: unknown) => {
const signalData = signal(value);
source[`${key}$`] = signalData;
const unsubscribe = signalData.subscribe(next => {
@@ -128,11 +129,30 @@ export class ReactiveFlatYMap extends BaseReactiveYData<
subscription.unsubscribe();
unsubscribe();
});
};
Object.entries(source).forEach(([key, value]) => {
initSignals(key, value);
});
if (defaultProps) {
Object.entries(defaultProps).forEach(([key, value]) => {
if (!(key in proxy) && value === undefined) {
initSignals(key, value);
}
});
}
this._proxy = proxy;
this._ySource.observe(this._observer);
this._initialized = true;
if (defaultProps) {
Object.entries(defaultProps).forEach(([key, value]) => {
if (key in proxy || value === undefined) return;
proxy[key] = value;
});
}
}
pop = (prop: string): void => {