Skip to content

Commit 51e27f5

Browse files
authored
Merge pull request #4809 from easyops-cn/steve/v3-set-css-custom-property
feat(): add support for context.set and state.set (like React setState)
2 parents 122eb51 + 8218bba commit 51e27f5

File tree

6 files changed

+115
-5
lines changed

6 files changed

+115
-5
lines changed

packages/runtime/src/internal/bindListeners.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export function listenerFactory(
223223
case "context.replace":
224224
case "context.refresh":
225225
case "context.load":
226+
case "context.set":
226227
handleContextAction(
227228
event,
228229
method,
@@ -236,6 +237,7 @@ export function listenerFactory(
236237
case "state.update":
237238
case "state.refresh":
238239
case "state.load":
240+
case "state.set":
239241
handleTplStateAction(
240242
event,
241243
method,
@@ -687,7 +689,7 @@ function batchUpdate(
687689

688690
function handleContextAction(
689691
event: Event,
690-
method: "assign" | "replace",
692+
method: "assign" | "replace" | "refresh" | "load" | "set",
691693
args: unknown[] | undefined,
692694
batch: boolean,
693695
callback: BrickEventHandlerCallback | undefined,
@@ -717,7 +719,7 @@ function handleContextAction(
717719

718720
function handleTplStateAction(
719721
event: Event,
720-
method: "update" | "refresh" | "load",
722+
method: "update" | "refresh" | "load" | "set",
721723
args: unknown[] | undefined,
722724
batch: boolean,
723725
callback: BrickEventHandlerCallback | undefined,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { setValueForStyle } from "./setRealProperties.js";
2+
3+
describe("setValueForStyle", () => {
4+
let mockStyle: CSSStyleDeclaration;
5+
6+
beforeEach(() => {
7+
mockStyle = {
8+
setProperty: jest.fn(),
9+
cssFloat: "",
10+
} as unknown as CSSStyleDeclaration;
11+
});
12+
13+
it("should set custom CSS property using setProperty when key starts with --", () => {
14+
setValueForStyle(mockStyle, "--custom-color", "red");
15+
16+
expect(mockStyle.setProperty).toHaveBeenCalledWith("--custom-color", "red");
17+
});
18+
19+
it('should set cssFloat property when key is "float"', () => {
20+
setValueForStyle(mockStyle, "float", "left");
21+
22+
expect(mockStyle.cssFloat).toBe("left");
23+
});
24+
25+
it("should set regular style property directly", () => {
26+
setValueForStyle(mockStyle, "color", "blue");
27+
28+
expect((mockStyle as any).color).toBe("blue");
29+
});
30+
31+
it("should set multiple regular properties", () => {
32+
setValueForStyle(mockStyle, "fontSize", "16px");
33+
setValueForStyle(mockStyle, "margin", "10px");
34+
35+
expect((mockStyle as any).fontSize).toBe("16px");
36+
expect((mockStyle as any).margin).toBe("10px");
37+
});
38+
});

packages/runtime/src/internal/compute/setRealProperties.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export function setRealProperties(
1212
for (const [k, v] of Object.entries(
1313
propValue as Record<string, unknown>
1414
)) {
15-
(brick[propName] as unknown as Record<string, unknown>)[k] = v;
15+
if (propName === "style") {
16+
setValueForStyle(brick.style, k, v);
17+
} else {
18+
(brick[propName] as unknown as Record<string, unknown>)[k] = v;
19+
}
1620
}
1721
break;
1822
case "constructor":
@@ -25,3 +29,17 @@ export function setRealProperties(
2529
}
2630
}
2731
}
32+
33+
export function setValueForStyle(
34+
style: CSSStyleDeclaration,
35+
key: string,
36+
value: unknown
37+
) {
38+
if (key.startsWith("--")) {
39+
style.setProperty(key, value as string);
40+
} else if (key === "float") {
41+
style.cssFloat = value as string;
42+
} else {
43+
(style as unknown as Record<string, unknown>)[key] = value;
44+
}
45+
}

packages/runtime/src/internal/data/DataStore.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,43 @@ describe("DataStore", () => {
378378
expect(mockCallRealTimeDataInspectHooks).toHaveBeenCalledTimes(2);
379379
});
380380

381+
test("context.set", async () => {
382+
setRealTimeDataInspectRoot({});
383+
const ctxStore = new DataStore("CTX");
384+
const runtimeContext = {
385+
ctxStore,
386+
} as Partial<RuntimeContext> as RuntimeContext;
387+
ctxStore.define(
388+
[
389+
{
390+
name: "primitive",
391+
value: "any",
392+
onChange: {
393+
action: "context.set",
394+
args: ["count", "<% (count) => count + 1 %>"],
395+
},
396+
},
397+
{
398+
name: "count",
399+
value: 0,
400+
},
401+
],
402+
runtimeContext
403+
);
404+
await ctxStore.waitForAll();
405+
expect(ctxStore.getValue("primitive")).toEqual("any");
406+
expect(ctxStore.getValue("count")).toBe(0);
407+
408+
const newValue = { amount: 42 };
409+
ctxStore.updateValue("primitive", newValue, "set");
410+
expect(ctxStore.getValue("primitive")).toEqual({ amount: 42 });
411+
expect(ctxStore.getValue("count")).toBe(1);
412+
413+
ctxStore.updateValue("primitive", newValue, "set");
414+
expect(ctxStore.getValue("primitive")).toEqual({ amount: 42 });
415+
expect(ctxStore.getValue("count")).toBe(1);
416+
});
417+
381418
test("state and onChange", async () => {
382419
jest.useFakeTimers();
383420
const tplStateStoreId = "tpl-state-1";

packages/runtime/src/internal/data/DataStore.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export class DataStore<T extends DataStoreType = "CTX"> {
193193
updateValue(
194194
name: string,
195195
value: unknown,
196-
method: "assign" | "replace" | "refresh" | "load",
196+
method: "assign" | "replace" | "refresh" | "load" | "set",
197197
callback?: BrickEventHandlerCallback,
198198
callbackRuntimeContext?: RuntimeContext
199199
): void {
@@ -276,7 +276,7 @@ export class DataStore<T extends DataStoreType = "CTX"> {
276276

277277
if (method === "replace") {
278278
item.value = value;
279-
} else {
279+
} else if (method === "assign") {
280280
if (isObject(item.value)) {
281281
Object.assign(item.value, value);
282282
} else {
@@ -286,6 +286,19 @@ export class DataStore<T extends DataStoreType = "CTX"> {
286286
);
287287
item.value = value;
288288
}
289+
} else {
290+
// method === "set"
291+
// `context.set` and `state.set` is similar to React's setState,
292+
// which accepts either a value or an updater function.
293+
// And if the new value is the same as the current one, do nothing.
294+
let nextValue = value;
295+
if (typeof value === "function") {
296+
nextValue = (value as (prevState: unknown) => unknown)(item.value);
297+
}
298+
if (Object.is(item.value, nextValue)) {
299+
return;
300+
}
301+
item.value = nextValue;
289302
}
290303

291304
if (this.batchUpdate) return;

packages/types/src/manifest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,11 +980,13 @@ export interface BuiltinBrickEventHandler {
980980
| "context.replace"
981981
| "context.refresh"
982982
| "context.load"
983+
| "context.set"
983984

984985
// Update template state
985986
| "state.update"
986987
| "state.refresh"
987988
| "state.load"
989+
| "state.set"
988990

989991
// Find related tpl and dispatch event.
990992
| "tpl.dispatchEvent"

0 commit comments

Comments
 (0)