Skip to content

Commit

Permalink
allow callbacks to be passed in triggers to asyncProp
Browse files Browse the repository at this point in the history
  • Loading branch information
electrovir committed Jun 20, 2023
1 parent 788e0c4 commit 11b22e6
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 11 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "element-vir",
"version": "14.0.0",
"version": "14.0.1",
"keywords": [
"custom",
"web",
Expand Down
23 changes: 20 additions & 3 deletions src/declarative-element/directives/async-prop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe(asyncProp.name, () => {
max?: Dimensions | undefined;
min?: Dimensions | undefined;
originalImageSize?: Dimensions | undefined;
doSomething: () => void;
};
type SomethingObject = {something: number};

Expand All @@ -39,12 +40,17 @@ describe(asyncProp.name, () => {
stateInitStatic: {
myAsyncProp: asyncProp({
updateCallback(trigger: TriggerType) {
trigger.doSomething();
return Promise.resolve({something: 4});
},
}),
},
renderCallback({state, updateState}) {
const bigType = {} as TriggerType;
const bigType = {
doSomething() {
// do a thing
},
} as TriggerType;

updateState({
myAsyncProp: {
Expand Down Expand Up @@ -85,18 +91,29 @@ describe(asyncProp.name, () => {
}>()({
tagName: `element-with-async-prop-${randomString()}`,
stateInitStatic: {
error: undefined as undefined | string,
myAsyncProp: asyncProp({
updateCallback({newNumber}: {newNumber: number}) {
updateCallback({
newNumber,
updateState,
}: {
newNumber: number;
updateState: (newState: {error: string | undefined}) => void;
}) {
const newDeferredPromise = createDeferredPromiseWrapper<typeof newNumber>();
deferredPromiseWrappers.push(newDeferredPromise);
updateState({error: undefined});
return newDeferredPromise.promise;
},
}),
},
renderCallback({inputs, state, updateState}) {
updateState({
myAsyncProp: {
trigger: {newNumber: inputs.promiseUpdateTrigger ?? startingNumber},
trigger: {
newNumber: inputs.promiseUpdateTrigger ?? startingNumber,
updateState,
},
},
});

Expand Down
39 changes: 34 additions & 5 deletions src/declarative-element/directives/async-prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ensureError,
isLengthAtLeast,
JsonCompatibleObject,
JsonCompatibleValue,
typedHasProperty,
UnPromise,
} from '@augment-vir/common';
Expand All @@ -24,11 +25,32 @@ export type AsyncProp<ValueGeneric> =

const notSetSymbol = Symbol('not set');

export type AsyncPropTriggerInputBase = JsonCompatibleObject | undefined;
type JsonCompatibleObjectWithFunctionsAllowedAtTopLevel =
| Partial<{
readonly [key: string | number]:
| JsonCompatibleValue
| Readonly<JsonCompatibleValue>
| ((...args: any[]) => void);
}>
| Partial<{
[key: string | number]:
| JsonCompatibleValue
| Readonly<JsonCompatibleValue>
| ((...args: any[]) => void);
}>;

export type AsyncPropTriggerInputBase =
| JsonCompatibleObjectWithFunctionsAllowedAtTopLevel
| undefined;

type AllSetValueProperties<ValueGeneric, TriggerInput extends AsyncPropTriggerInputBase> = {
/** Set a new value directly without using any promises. */
resolvedValue: UnPromise<ValueGeneric>;
/**
* A value that, if it changes from one assignment to another, triggers the asyncProp to
* re-evaluate itself. Must be a JSON-compatible object for quick equality checks. Functions are
* also be allowed at the top level, but they are ignored for the equality checking.
*/
trigger: TriggerInput;
newPromise: Promise<UnPromise<ValueGeneric>>;
/** Clear the current value and trigger updateCallback to get called again on the next render. */
Expand Down Expand Up @@ -157,20 +179,27 @@ export class AsyncObservablePropertyHandler<
* This will expand proxies so that `inputs` or `state` can be used directly as a
* trigger without issues.
*/
const expandedTrigger = {...setInputs.trigger};
const expandedNewTrigger = {...setInputs.trigger};

if (
this.lastTrigger === notSetSymbol ||
!areJsonEqual(expandedTrigger, this.lastTrigger)
/**
* No need to explicitly remove function properties here before the equality check:
* JSON.stringify (which areJsonEqual is based on) will automatically do that.
*/
!areJsonEqual(
expandedNewTrigger as JsonCompatibleObject,
this.lastTrigger as JsonCompatibleObject,
)
) {
this.lastTrigger = expandedTrigger;
this.lastTrigger = expandedNewTrigger;
if (!this.promiseUpdater) {
throw new Error(
`got trigger input to updateState for asyncProp but no updateCallback has been defined.`,
);
}

const newValue = this.promiseUpdater(expandedTrigger);
const newValue = this.promiseUpdater(expandedNewTrigger);

this.setPromise(newValue);
this.fireListeners();
Expand Down

0 comments on commit 11b22e6

Please sign in to comment.