Skip to content

Commit f05b523

Browse files
committed
add type maps to useProperties
- fix #6
1 parent 81d5eb5 commit f05b523

6 files changed

Lines changed: 49 additions & 7 deletions

File tree

packages/shadow-objects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
});
2323
```
2424
- **Refactor** the `EntityApi` type
25+
- **Refactor** the `useProperties` supports type maps now
2526
- **Documentation:** Comprehensive update to the documentation structure and content.
2627

2728
### ⚠️ Breaking Changes

packages/shadow-objects/docs/03-api/01-shadow-object-api.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ createEffect(() => {
3939

4040
A convenience helper to create multiple property signals at once.
4141

42-
* **Signature:** `useProperties(map: Record<string, any>): Record<string, () => any>`
42+
* **Signature:** `useProperties<T extends Record<string, unknown>>(map: {[K in keyof T]: string}): {[K in keyof T]: SignalReader<Maybe<T[K]>>}`
4343
* **Returns:** An object where keys match the input map, and values are signal readers.
4444

4545
```typescript
46-
const { x, y } = useProperties({ x: 0, y: 0 });
47-
// x() and y() are now signals
46+
const { x, y, title } = useProperties{ x: number; y: number, title: string }>({ x: "x", y: "y", title: "title" });
47+
// x() and y() are now number|undefined values
48+
// title is now a signal-reader with a string value
4849
```
4950

5051
---

packages/shadow-objects/docs/04-patterns/best-practices.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ const myMeshResource = createResource(
127127
If you need multiple properties, avoid calling `useProperty` multiple times. Use `useProperties` to get a structured object of signals.
128128

129129
```typescript
130-
const { x, y, visible } = useProperties({
130+
const { x, y, visible } = useProperties<{ x: number; y: number; visible: boolean }>({
131131
x: "position-x",
132132
y: "position-y",
133133
visible: "is-visible"

packages/shadow-objects/src/in-the-dark/Kernel.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,42 @@ describe('Kernel', () => {
340340
});
341341
});
342342

343+
it('should support typed property maps', () => {
344+
const registry = new Registry();
345+
const kernel = new Kernel(registry);
346+
347+
let capturedProps:
348+
| {
349+
foo: SignalReader<number | undefined>;
350+
bar: SignalReader<string | undefined>;
351+
}
352+
| undefined;
353+
354+
@ShadowObject({registry, token: 'testTypedUseProperties'})
355+
class TestTypedUseProperties {
356+
constructor({useProperties}: ShadowObjectCreationAPI) {
357+
const props = useProperties<{foo: number; bar: string}>({
358+
foo: 'propA',
359+
bar: 'propB',
360+
});
361+
capturedProps = props;
362+
}
363+
}
364+
expect(TestTypedUseProperties).toBeDefined();
365+
366+
const uuid = generateUUID();
367+
kernel.createEntity(uuid, 'testTypedUseProperties', undefined, 0, [
368+
['propA', 123],
369+
['propB', 'valueB'],
370+
]);
371+
372+
expect(capturedProps).toBeDefined();
373+
expect(value(capturedProps!.foo)).toBe(123);
374+
expect(value(capturedProps!.bar)).toBe('valueB');
375+
376+
kernel.destroy();
377+
});
378+
343379
describe('provideContext and useContext', () => {
344380
it('should provide and consume context values between parent and child', async () => {
345381
const registry = new Registry();

packages/shadow-objects/src/in-the-dark/Kernel.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,10 @@ export class Kernel {
523523

524524
useProperty: getUseProperty,
525525

526-
useProperties<K extends string>(props: Record<K, string>): Record<K, SignalReader<any>> {
527-
const result = {} as Record<K, SignalReader<any>>;
526+
useProperties<T extends Record<string, unknown> = Record<string, unknown>>(props: {[K in keyof T]: string}): {
527+
[K in keyof T]: SignalReader<Maybe<T[K]>>;
528+
} {
529+
const result = {} as {[K in keyof T]: SignalReader<Maybe<T[K]>>};
528530
for (const key in props) {
529531
if (Object.hasOwn(props, key)) {
530532
result[key] = getUseProperty(props[key]);

packages/shadow-objects/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ export interface ShadowObjectCreationAPI {
132132

133133
useProperty<T = unknown>(name: string, options?: SignalValueOptions<T> | CompareFunc<T | undefined>): SignalReader<Maybe<T>>;
134134

135-
useProperties<K extends string>(props: Record<K, string>): Record<K, SignalReader<any>>;
135+
useProperties<T extends Record<string, unknown> = Record<string, unknown>>(props: {[K in keyof T]: string}): {
136+
[K in keyof T]: SignalReader<Maybe<T[K]>>;
137+
};
136138

137139
createResource<T = unknown>(factory: () => T | undefined, cleanup?: (resource: NonNullable<T>) => unknown): Signal<Maybe<T>>;
138140

0 commit comments

Comments
 (0)