element-vir 8.0.2 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -596,16 +596,18 @@ export const MyWithRenderIfElement = defineElement<{shouldRender: boolean}>()({
596
596
  });
597
597
  ```
598
598
 
599
- ### renderAsyncProp
599
+ ### asyncState
600
600
 
601
- Use the `renderAsyncProp` directive in conjunction with the `asyncProp` property definer and the `ensureAsyncProp` render callback property to seamlessly render and update element state:
601
+ Use the `renderAsyncState` directive in conjunction with the `asyncState` property definer to seamlessly render and update element state based on async values:
602
602
 
603
603
  <!-- example-link: src/readme-examples/my-with-async-prop.element.ts -->
604
604
 
605
605
  ```TypeScript
606
- import {asyncProp, defineElement, html, renderAsyncProp} from 'element-vir';
606
+ import {asyncState, defineElement, html, listen, renderAsyncState} from 'element-vir';
607
607
 
608
- async function loadSomething(endpoint: string) {
608
+ type EndpointData = number[];
609
+
610
+ async function loadSomething(endpoint: string): Promise<EndpointData> {
609
611
  // load something from the network
610
612
  const data = await (
611
613
  await fetch(
@@ -619,35 +621,44 @@ async function loadSomething(endpoint: string) {
619
621
  return data;
620
622
  }
621
623
 
622
- export const MyWithAsyncPropElement = defineElement<{endpointToHit: string}>()({
624
+ export const MyWithAsyncStateElement = defineElement<{endpoint: string}>()({
623
625
  tagName: 'my-simple-with-render-if',
624
626
  stateInit: {
625
- data: asyncProp(),
627
+ data: asyncState<EndpointData>(),
626
628
  },
627
- renderCallback: ({inputs, state, ensureAsyncProp}) => {
629
+ renderCallback: ({inputs, state, updateState}) => {
628
630
  /**
629
631
  * This creates a promise which automatically updates the state.loadsLater prop once the
630
- * promise resolves.
632
+ * promise resolves. It only creates a new promise if the "trigger" value changes.
631
633
  */
632
- ensureAsyncProp({
634
+ updateState({
633
635
  data: {
634
- createPromise: () => loadSomething(inputs.endpointToHit),
635
- updateIfThisChanges: inputs.endpointToHit,
636
+ createPromise: () => loadSomething(inputs.endpoint),
637
+ trigger: inputs.endpoint,
636
638
  },
637
639
  });
638
640
 
639
641
  return html`
640
642
  Here's the data:
641
643
  <br />
642
- ${renderAsyncProp({
643
- asyncProp: state.data,
644
- fallback: 'Loading...',
645
- resolutionRender: (loadedData) => {
646
- return html`
647
- Got the data: ${loadedData}
648
- `;
649
- },
644
+ ${renderAsyncState(state.data, 'Loading...', (loadedData) => {
645
+ return html`
646
+ Got the data: ${loadedData}
647
+ `;
650
648
  })}
649
+ <br />
650
+ <button
651
+ ${listen('click', () => {
652
+ updateState({
653
+ data: {
654
+ /** You can force asyncState to update by passing in forceUpdate: true. */
655
+ forceUpdate: true,
656
+ },
657
+ });
658
+ })}
659
+ >
660
+ Refresh
661
+ </button>
651
662
  `;
652
663
  },
653
664
  });
@@ -1,5 +1,6 @@
1
1
  import { CSSResult } from 'lit';
2
2
  import { DeclarativeElementDefinitionOptions } from './definition-options';
3
+ import { MaybeAsyncStateToSync } from './properties/async-state';
3
4
  import { CssVarsInitMap } from './properties/css-vars';
4
5
  import { EventsInitMap } from './properties/element-events';
5
6
  import { PropertyInitMapBase } from './properties/element-properties';
@@ -7,7 +8,7 @@ import { HostClassesInitMap } from './properties/host-classes';
7
8
  import { StylesCallback } from './properties/styles';
8
9
  import { InitCallback, RenderCallback } from './render-callback';
9
10
  export type CustomElementTagName = `${string}-${string}`;
10
- export type DeclarativeElementInit<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateInitGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeysGeneric extends string, CssVarKeysGeneric extends string> = {
11
+ export type DeclarativeElementInit<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateInitMaybeAsyncGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeysGeneric extends string, CssVarKeysGeneric extends string> = {
11
12
  /**
12
13
  * HTML tag name. This should not be used directly, as interpolating it with the html tagged
13
14
  * template from this package is preferred.
@@ -16,7 +17,7 @@ export type DeclarativeElementInit<TagNameGeneric extends CustomElementTagName,
16
17
  /** Static styles. These should not and cannot change. */
17
18
  styles?: CSSResult | StylesCallback<HostClassKeysGeneric, CssVarKeysGeneric>;
18
19
  /** Element properties. (These can be thought of as "inputs".) */
19
- stateInit?: StateInitGeneric;
20
+ stateInit?: StateInitMaybeAsyncGeneric;
20
21
  /** Events that the element can dispatch. (These can be thought of as "outputs".) */
21
22
  events?: EventsInitGeneric;
22
23
  /**
@@ -24,7 +25,7 @@ export type DeclarativeElementInit<TagNameGeneric extends CustomElementTagName,
24
25
  * based on current instance state or inputs, or just undefined to indicate that the host class
25
26
  * will only be manually set.
26
27
  */
27
- hostClasses?: HostClassesInitMap<HostClassKeysGeneric, InputsGeneric, StateInitGeneric>;
28
+ hostClasses?: HostClassesInitMap<HostClassKeysGeneric, InputsGeneric, MaybeAsyncStateToSync<StateInitMaybeAsyncGeneric>>;
28
29
  /**
29
30
  * CSS Vars for the component. Keys of this object should be camelCased (or whatever your casing
30
31
  * convention is). They will be transformed, at runtime, to CSS vars with kebab-casing, to match
@@ -37,9 +38,9 @@ export type DeclarativeElementInit<TagNameGeneric extends CustomElementTagName,
37
38
  */
38
39
  cssVars?: CssVarsInitMap<CssVarKeysGeneric>;
39
40
  /** Called as part of the first renderCallback call, before the first renderCallback call. */
40
- initCallback?: InitCallback<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric>;
41
- renderCallback: RenderCallback<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric>;
42
- cleanupCallback?: InitCallback<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric>;
41
+ initCallback?: InitCallback<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric>;
42
+ renderCallback: RenderCallback<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric>;
43
+ cleanupCallback?: InitCallback<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric>;
43
44
  options?: Partial<DeclarativeElementDefinitionOptions> | undefined;
44
45
  };
45
46
  //# sourceMappingURL=declarative-element-init.d.ts.map
@@ -1,16 +1,17 @@
1
1
  import { RequiredAndNotNullBy, RequiredBy } from '@augment-vir/common';
2
2
  import { CSSResult, LitElement } from 'lit';
3
3
  import { CustomElementTagName, DeclarativeElementInit } from './declarative-element-init';
4
+ import { AsyncStateProperties, MaybeAsyncStateToSync } from './properties/async-state';
4
5
  import { CssVarNameOrValueMap } from './properties/css-vars';
5
6
  import { EventDescriptorMap, EventsInitMap } from './properties/element-events';
6
7
  import { ElementPropertyDescriptorMap, PropertyInitMapBase } from './properties/element-properties';
7
8
  import { HostClassNamesMap } from './properties/host-classes';
8
9
  import { RenderCallback, RenderOutput } from './render-callback';
9
- export type HostInstanceType<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string> = RequiredAndNotNullBy<DeclarativeElement<TagNameGeneric, InputsGeneric, StateGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>, 'shadowRoot'>;
10
- export type DeclarativeElementDefinition<TagNameGeneric extends CustomElementTagName = any, InputsGeneric extends PropertyInitMapBase = any, StateInitGeneric extends PropertyInitMapBase = any, EventsInitGeneric extends EventsInitMap = any, HostClassKeys extends string = string, CssVarKeys extends string = string> = (new () => HostInstanceType<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>) & StaticDeclarativeElementProperties<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys> & {
11
- instanceType: HostInstanceType<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
10
+ export type HostInstanceType<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateInitMaybeAsyncGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string> = RequiredAndNotNullBy<DeclarativeElement<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>, 'shadowRoot'>;
11
+ export type DeclarativeElementDefinition<TagNameGeneric extends CustomElementTagName = any, InputsGeneric extends PropertyInitMapBase = any, StateInitMaybeAsyncGeneric extends PropertyInitMapBase = any, EventsInitGeneric extends EventsInitMap = any, HostClassKeys extends string = string, CssVarKeys extends string = string> = (new () => HostInstanceType<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>) & StaticDeclarativeElementProperties<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys> & {
12
+ instanceType: HostInstanceType<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
12
13
  };
13
- export declare abstract class DeclarativeElement<TagNameGeneric extends CustomElementTagName = CustomElementTagName, InputsGeneric extends PropertyInitMapBase = any, StateInitGeneric extends PropertyInitMapBase = any, EventsInitGeneric extends EventsInitMap = any, HostClassKeys extends string = string, CssVarKeys extends string = string> extends LitElement {
14
+ export declare abstract class DeclarativeElement<TagNameGeneric extends CustomElementTagName = CustomElementTagName, InputsGeneric extends PropertyInitMapBase = any, StateInitMaybeAsyncGeneric extends PropertyInitMapBase = any, EventsInitGeneric extends EventsInitMap = any, HostClassKeys extends string = string, CssVarKeys extends string = string> extends LitElement {
14
15
  static readonly tagName: StaticDeclarativeElementProperties<CustomElementTagName, PropertyInitMapBase, PropertyInitMapBase, EventsInitMap, string, string>['tagName'];
15
16
  static readonly styles: StaticDeclarativeElementProperties<CustomElementTagName, PropertyInitMapBase, PropertyInitMapBase, EventsInitMap, string, string>['styles'];
16
17
  static readonly isStrictInstance: StaticDeclarativeElementProperties<CustomElementTagName, PropertyInitMapBase, PropertyInitMapBase, EventsInitMap, string, string>['isStrictInstance'];
@@ -24,22 +25,23 @@ export declare abstract class DeclarativeElement<TagNameGeneric extends CustomEl
24
25
  static readonly cssVarNames: StaticDeclarativeElementProperties<CustomElementTagName, PropertyInitMapBase, PropertyInitMapBase, EventsInitMap, string, string>['cssVarNames'];
25
26
  static readonly cssVarValues: StaticDeclarativeElementProperties<CustomElementTagName, PropertyInitMapBase, PropertyInitMapBase, EventsInitMap, string, string>['cssVarValues'];
26
27
  abstract render(): RenderOutput;
27
- abstract readonly instanceState: StateInitGeneric;
28
+ abstract readonly instanceState: MaybeAsyncStateToSync<StateInitMaybeAsyncGeneric>;
29
+ abstract readonly asyncStateProperties: AsyncStateProperties<keyof StateInitMaybeAsyncGeneric>;
28
30
  abstract readonly instanceInputs: InputsGeneric;
29
31
  abstract assignInputs(inputs: InputsGeneric): void;
30
32
  abstract readonly haveInputsBeenSet: boolean;
31
33
  abstract markInputsAsHavingBeenSet(): void;
32
- abstract readonly definition: DeclarativeElementDefinition<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
34
+ abstract readonly definition: DeclarativeElementDefinition<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
33
35
  }
34
- export interface StaticDeclarativeElementProperties<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateInitGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string> {
36
+ export interface StaticDeclarativeElementProperties<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateInitMaybeAsyncGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string> {
35
37
  /** Pass through the render callback for direct unit testability */
36
- readonly renderCallback: RenderCallback<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
38
+ readonly renderCallback: RenderCallback<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
37
39
  events: EventDescriptorMap<EventsInitGeneric>;
38
- stateInit: ElementPropertyDescriptorMap<StateInitGeneric>;
39
- init: RequiredBy<DeclarativeElementInit<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>, 'stateInit' | 'events'>;
40
+ stateInit: ElementPropertyDescriptorMap<StateInitMaybeAsyncGeneric>;
41
+ init: RequiredBy<DeclarativeElementInit<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>, 'stateInit' | 'events'>;
40
42
  inputsType: InputsGeneric;
41
- stateType: StateInitGeneric;
42
- isStrictInstance: (element: unknown) => element is DeclarativeElement<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
43
+ stateType: MaybeAsyncStateToSync<StateInitMaybeAsyncGeneric>;
44
+ isStrictInstance: (element: unknown) => element is DeclarativeElement<TagNameGeneric, InputsGeneric, StateInitMaybeAsyncGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
43
45
  hostClasses: HostClassNamesMap<string, HostClassKeys>;
44
46
  cssVarNames: CssVarNameOrValueMap<CssVarKeys>;
45
47
  cssVarValues: CssVarNameOrValueMap<CssVarKeys>;
@@ -2,5 +2,5 @@ import { DeclarativeElementDefinition } from './declarative-element';
2
2
  import { CustomElementTagName, DeclarativeElementInit } from './declarative-element-init';
3
3
  import { EventsInitMap } from './properties/element-events';
4
4
  import { PropertyInitMapBase } from './properties/element-properties';
5
- export declare function defineElementNoInputs<TagNameGeneric extends CustomElementTagName = '-', InputsGeneric extends PropertyInitMapBase = {}, StateInitGeneric extends PropertyInitMapBase = {}, EventsInitGeneric extends EventsInitMap = {}, HostClassKeys extends string = '', CssVarKeys extends string = ''>(initInput: DeclarativeElementInit<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>): DeclarativeElementDefinition<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
5
+ export declare function defineElementNoInputs<TagNameGeneric extends CustomElementTagName = '-', InputsGeneric extends PropertyInitMapBase = {}, MaybeAsyncStateInitGeneric extends PropertyInitMapBase = {}, EventsInitGeneric extends EventsInitMap = {}, HostClassKeys extends string = '', CssVarKeys extends string = ''>(initInput: DeclarativeElementInit<TagNameGeneric, InputsGeneric, MaybeAsyncStateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>): DeclarativeElementDefinition<TagNameGeneric, InputsGeneric, MaybeAsyncStateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
6
6
  //# sourceMappingURL=define-element-no-inputs.d.ts.map
@@ -6,6 +6,7 @@ import { DeclarativeElement, } from './declarative-element';
6
6
  import { defaultDeclarativeElementDefinitionOptions, IgnoreInputsNotBeenSetBeforeRenderWarningSymbol, } from './definition-options';
7
7
  import { assign } from './directives/assign.directive';
8
8
  import { hasDeclarativeElementParent } from './has-declarative-element-parent';
9
+ import { mapToAsyncStateProperties, } from './properties/async-state';
9
10
  import { createCssVarNamesMap, createCssVarValuesMap } from './properties/css-vars';
10
11
  import { createEventDescriptorMap } from './properties/element-events';
11
12
  import { createElementUpdaterProxy } from './properties/element-updater-proxy';
@@ -26,6 +27,7 @@ export function defineElementNoInputs(initInput) {
26
27
  ? initInput.styles(hostClassNamesToStylesInput({ hostClassNames, cssVarNames, cssVarValues }))
27
28
  : initInput.styles || css ``;
28
29
  const typedRenderCallback = initInput.renderCallback;
30
+ const asyncStateProperties = mapToAsyncStateProperties(initInput.stateInit);
29
31
  const anonymousClass = (_a = class extends DeclarativeElement {
30
32
  createRenderParams() {
31
33
  return createRenderParams(this, eventsMap);
@@ -89,12 +91,24 @@ export function defineElementNoInputs(initInput) {
89
91
  this.haveInputsBeenSet = false;
90
92
  // this is set below in Object.defineProperties
91
93
  this.definition = {};
94
+ this.asyncStateProperties = asyncStateProperties;
92
95
  this.instanceInputs = createElementUpdaterProxy(this, false);
93
96
  this.instanceState = createElementUpdaterProxy(this, true);
94
- const stateInit = initInput.stateInit || {};
95
- Object.keys(stateInit).forEach((propName) => {
96
- property()(this, propName);
97
- this.instanceState[propName] = stateInit[propName];
97
+ const stateInit = initInput.stateInit ||
98
+ {};
99
+ getObjectTypedKeys(stateInit).forEach((stateKey) => {
100
+ property()(this, stateKey);
101
+ const asyncStateClassInstance = asyncStateProperties[stateKey];
102
+ if (asyncStateClassInstance) {
103
+ this.instanceState[stateKey] = asyncStateClassInstance.getValue();
104
+ asyncStateClassInstance.addSettleListener(() => {
105
+ this[stateKey] =
106
+ asyncStateClassInstance.getValue();
107
+ });
108
+ }
109
+ else {
110
+ this.instanceState[stateKey] = stateInit[stateKey];
111
+ }
98
112
  });
99
113
  this.definition = anonymousClass;
100
114
  }
@@ -129,7 +143,7 @@ export function defineElementNoInputs(initInput) {
129
143
  },
130
144
  });
131
145
  if (window.customElements.get(initInput.tagName)) {
132
- console.warn(`Tried to define custom element ${initInput.tagName} but it is already defined.`);
146
+ console.warn(`Tried to define custom element '${initInput.tagName}' but it is already defined.`);
133
147
  }
134
148
  else {
135
149
  window.customElements.define(initInput.tagName, anonymousClass);
@@ -0,0 +1,6 @@
1
+ import { UnPromise } from '@augment-vir/common';
2
+ import { AsyncState } from '../properties/async-state';
3
+ export declare function renderAsyncState<T>(asyncState: AsyncState<T>,
4
+ /** This value will be rendered if the async state has not settled yet. */
5
+ fallback: unknown, resolutionRender?: ((resolved: UnPromise<T>) => unknown) | undefined, errorRender?: ((error: Error) => unknown) | undefined): unknown;
6
+ //# sourceMappingURL=render-async-state.directive.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { extractErrorMessage, isPromiseLike } from '@augment-vir/common';
2
+ export function renderAsyncState(asyncState,
3
+ /** This value will be rendered if the async state has not settled yet. */
4
+ fallback, resolutionRender, errorRender) {
5
+ if (asyncState instanceof Error) {
6
+ return errorRender ? errorRender(asyncState) : extractErrorMessage(asyncState);
7
+ }
8
+ else if (isPromiseLike(asyncState)) {
9
+ return fallback;
10
+ }
11
+ else {
12
+ return resolutionRender ? resolutionRender(asyncState) : asyncState;
13
+ }
14
+ }
@@ -0,0 +1,50 @@
1
+ import { UnPromise } from '@augment-vir/common';
2
+ import { JsonValue, Promisable } from 'type-fest';
3
+ import { PropertyInitMapBase } from './element-properties';
4
+ export type AsyncState<ValueGeneric> = Error | Promisable<UnPromise<ValueGeneric>>;
5
+ export type AsyncStateSetValue<ValueGeneric> = {
6
+ createPromise: () => Promise<UnPromise<ValueGeneric>>;
7
+ /**
8
+ * When trigger changes (according to deep equality checking through JSON stringify), the
9
+ * createPromise callback will be called and the element's state will be updated again.
10
+ * Otherwise, the createPromise callback will only be called the first time.
11
+ *
12
+ * Set this to undefined to disabled automatic updating. Meaning, createPromise will only
13
+ * be called the first time.
14
+ */
15
+ trigger: JsonValue | Readonly<JsonValue> | undefined;
16
+ newPromise?: never;
17
+ forceUpdate?: never;
18
+ } | {
19
+ createPromise?: never;
20
+ trigger?: never;
21
+ forceUpdate?: never;
22
+ newPromise: Promise<UnPromise<ValueGeneric>>;
23
+ } | {
24
+ createPromise?: never;
25
+ trigger?: never;
26
+ newPromise?: never;
27
+ /**
28
+ * Clear the current value and trigger createPromise to get called again on the next
29
+ * render.
30
+ */
31
+ forceUpdate: true;
32
+ };
33
+ export type MaybeAsyncStateToSync<PropertyMapInit extends PropertyInitMapBase> = {
34
+ [Prop in keyof PropertyMapInit]: PropertyMapInit[Prop] extends AsyncStateHandler<infer ValueGeneric> ? AsyncState<ValueGeneric> : PropertyMapInit[Prop];
35
+ };
36
+ export type AsyncStateInputs<PropertyMapInit extends PropertyInitMapBase> = {
37
+ [Prop in keyof PropertyMapInit]: PropertyMapInit[Prop] extends AsyncStateHandler<infer ValueGeneric> ? AsyncStateSetValue<ValueGeneric> : PropertyMapInit[Prop];
38
+ };
39
+ export type AsyncStateProperties<KeysGeneric extends keyof PropertyInitMapBase> = Partial<Record<KeysGeneric, AsyncStateHandler<any>>>;
40
+ export declare function mapToAsyncStateProperties(propertyInitMap?: PropertyInitMapBase | undefined): AsyncStateProperties<keyof PropertyInitMapBase>;
41
+ export declare class AsyncStateHandler<ValueGeneric> {
42
+ #private;
43
+ constructor(initialValue: Promise<UnPromise<ValueGeneric>> | undefined);
44
+ setValue(setInputs: AsyncStateSetValue<ValueGeneric>): void;
45
+ getValue(): AsyncState<ValueGeneric>;
46
+ addSettleListener(callback: () => void): void;
47
+ removeSettleListener(callback: (value: AsyncState<ValueGeneric>) => void): void;
48
+ }
49
+ export declare function asyncState<ValueGeneric>(initialValue?: Promise<UnPromise<ValueGeneric>> | undefined): AsyncStateHandler<ValueGeneric>;
50
+ //# sourceMappingURL=async-state.d.ts.map
@@ -0,0 +1,124 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _AsyncStateHandler_instances, _AsyncStateHandler_lastTrigger, _AsyncStateHandler_resolutionValue, _AsyncStateHandler_rejectionError, _AsyncStateHandler_listeners, _AsyncStateHandler_lastSetPromise, _AsyncStateHandler_waitingForValuePromise, _AsyncStateHandler_fireListeners, _AsyncStateHandler_setPromise;
13
+ import { areJsonEqual, createDeferredPromiseWrapper, ensureError, filterObject, } from '@augment-vir/common';
14
+ export function mapToAsyncStateProperties(propertyInitMap) {
15
+ if (!propertyInitMap) {
16
+ return {};
17
+ }
18
+ const asyncStateProperties = filterObject(propertyInitMap, (key, value) => {
19
+ return value instanceof AsyncStateHandler;
20
+ });
21
+ return asyncStateProperties;
22
+ }
23
+ const notSetSymbol = Symbol('not set');
24
+ export class AsyncStateHandler {
25
+ constructor(initialValue) {
26
+ _AsyncStateHandler_instances.add(this);
27
+ _AsyncStateHandler_lastTrigger.set(this, notSetSymbol);
28
+ _AsyncStateHandler_resolutionValue.set(this, void 0);
29
+ _AsyncStateHandler_rejectionError.set(this, void 0);
30
+ _AsyncStateHandler_listeners.set(this, []);
31
+ _AsyncStateHandler_lastSetPromise.set(this, void 0);
32
+ _AsyncStateHandler_waitingForValuePromise.set(this, createDeferredPromiseWrapper());
33
+ if (initialValue) {
34
+ this.setValue({ newPromise: initialValue });
35
+ }
36
+ }
37
+ setValue(setInputs) {
38
+ if ('createPromise' in setInputs) {
39
+ if (__classPrivateFieldGet(this, _AsyncStateHandler_lastTrigger, "f") === notSetSymbol ||
40
+ !areJsonEqual(setInputs.trigger, __classPrivateFieldGet(this, _AsyncStateHandler_lastTrigger, "f"))) {
41
+ __classPrivateFieldSet(this, _AsyncStateHandler_lastTrigger, setInputs.trigger, "f");
42
+ const newValue = setInputs.createPromise();
43
+ __classPrivateFieldGet(this, _AsyncStateHandler_instances, "m", _AsyncStateHandler_setPromise).call(this, newValue);
44
+ }
45
+ }
46
+ else if ('newPromise' in setInputs) {
47
+ __classPrivateFieldGet(this, _AsyncStateHandler_lastTrigger, "f") === notSetSymbol;
48
+ __classPrivateFieldGet(this, _AsyncStateHandler_instances, "m", _AsyncStateHandler_setPromise).call(this, setInputs.newPromise);
49
+ // force a re-render
50
+ __classPrivateFieldGet(this, _AsyncStateHandler_instances, "m", _AsyncStateHandler_fireListeners).call(this);
51
+ }
52
+ else {
53
+ if (setInputs.forceUpdate) {
54
+ __classPrivateFieldSet(this, _AsyncStateHandler_lastTrigger, notSetSymbol, "f");
55
+ __classPrivateFieldSet(this, _AsyncStateHandler_resolutionValue, undefined, "f");
56
+ if (!__classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").isSettled()) {
57
+ __classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").reject('Canceled by force update');
58
+ }
59
+ __classPrivateFieldSet(this, _AsyncStateHandler_waitingForValuePromise, createDeferredPromiseWrapper(), "f");
60
+ // force a re-render
61
+ __classPrivateFieldGet(this, _AsyncStateHandler_instances, "m", _AsyncStateHandler_fireListeners).call(this);
62
+ }
63
+ }
64
+ }
65
+ getValue() {
66
+ if (__classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").isSettled()) {
67
+ if (__classPrivateFieldGet(this, _AsyncStateHandler_rejectionError, "f")) {
68
+ return __classPrivateFieldGet(this, _AsyncStateHandler_rejectionError, "f");
69
+ }
70
+ else
71
+ return __classPrivateFieldGet(this, _AsyncStateHandler_resolutionValue, "f");
72
+ }
73
+ else {
74
+ return __classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").promise;
75
+ }
76
+ }
77
+ addSettleListener(callback) {
78
+ __classPrivateFieldGet(this, _AsyncStateHandler_listeners, "f").push(callback);
79
+ }
80
+ removeSettleListener(callback) {
81
+ __classPrivateFieldSet(this, _AsyncStateHandler_listeners, __classPrivateFieldGet(this, _AsyncStateHandler_listeners, "f").filter((listener) => listener !== callback), "f");
82
+ }
83
+ }
84
+ _AsyncStateHandler_lastTrigger = new WeakMap(), _AsyncStateHandler_resolutionValue = new WeakMap(), _AsyncStateHandler_rejectionError = new WeakMap(), _AsyncStateHandler_listeners = new WeakMap(), _AsyncStateHandler_lastSetPromise = new WeakMap(), _AsyncStateHandler_waitingForValuePromise = new WeakMap(), _AsyncStateHandler_instances = new WeakSet(), _AsyncStateHandler_fireListeners = function _AsyncStateHandler_fireListeners() {
85
+ __classPrivateFieldGet(this, _AsyncStateHandler_listeners, "f").forEach((listener) => {
86
+ listener();
87
+ });
88
+ }, _AsyncStateHandler_setPromise = function _AsyncStateHandler_setPromise(newPromise) {
89
+ if (newPromise === __classPrivateFieldGet(this, _AsyncStateHandler_lastSetPromise, "f")) {
90
+ // abort setting the promise if we already have set this promise
91
+ return;
92
+ }
93
+ __classPrivateFieldSet(this, _AsyncStateHandler_lastSetPromise, newPromise, "f");
94
+ if (__classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").isSettled()) {
95
+ __classPrivateFieldSet(this, _AsyncStateHandler_waitingForValuePromise, createDeferredPromiseWrapper(), "f");
96
+ }
97
+ newPromise
98
+ .then((value) => {
99
+ // make sure we're still actually waiting for this promise
100
+ if (__classPrivateFieldGet(this, _AsyncStateHandler_lastSetPromise, "f") === newPromise) {
101
+ __classPrivateFieldSet(this, _AsyncStateHandler_rejectionError, undefined, "f");
102
+ __classPrivateFieldSet(this, _AsyncStateHandler_resolutionValue, value, "f");
103
+ __classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").resolve(value);
104
+ __classPrivateFieldGet(this, _AsyncStateHandler_instances, "m", _AsyncStateHandler_fireListeners).call(this);
105
+ }
106
+ })
107
+ .catch((reason) => {
108
+ // make sure we're still actually waiting for this promise
109
+ if (__classPrivateFieldGet(this, _AsyncStateHandler_lastSetPromise, "f") === newPromise) {
110
+ __classPrivateFieldSet(this, _AsyncStateHandler_rejectionError, ensureError(reason), "f");
111
+ __classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").promise.catch(() => {
112
+ /**
113
+ * Don't actually do anything, we just want to make sure the error is
114
+ * handled so it doesn't throw errors in the browser.
115
+ */
116
+ });
117
+ __classPrivateFieldGet(this, _AsyncStateHandler_waitingForValuePromise, "f").reject(reason);
118
+ __classPrivateFieldGet(this, _AsyncStateHandler_instances, "m", _AsyncStateHandler_fireListeners).call(this);
119
+ }
120
+ });
121
+ };
122
+ export function asyncState(initialValue) {
123
+ return new AsyncStateHandler(initialValue);
124
+ }
@@ -1,9 +1,9 @@
1
1
  function assertValidPropertyName(propKey, element, elementTagName) {
2
2
  if (typeof propKey !== 'string' && typeof propKey !== 'number' && typeof propKey !== 'symbol') {
3
- throw new Error(`Property name must be a string, got type "${typeof propKey}" from: "${String(propKey)}" for ${elementTagName.toLowerCase()}`);
3
+ throw new Error(`Property name must be a string, got type '${typeof propKey}' from: '${String(propKey)}' for '${elementTagName.toLowerCase()}'`);
4
4
  }
5
5
  if (!(propKey in element)) {
6
- throw new Error(`Property "${String(propKey)}" does not exist on ${elementTagName.toLowerCase()}.`);
6
+ throw new Error(`Property '${String(propKey)}' does not exist on '${elementTagName.toLowerCase()}'.`);
7
7
  }
8
8
  }
9
9
  export function createElementUpdaterProxy(element, verifyExists) {
@@ -13,13 +13,20 @@ export function createElementUpdaterProxy(element, verifyExists) {
13
13
  * back in here.
14
14
  */
15
15
  const elementAsProps = element;
16
- const propsProxy = new Proxy({}, {
17
- get: (target, propertyName) => {
18
- if (verifyExists) {
19
- assertValidPropertyName(propertyName, element, element.tagName);
20
- }
16
+ function valueGetter(target, propertyName) {
17
+ if (verifyExists) {
18
+ assertValidPropertyName(propertyName, element, element.tagName);
19
+ }
20
+ const asyncState = element.asyncStateProperties[propertyName];
21
+ if (asyncState) {
22
+ return asyncState.getValue();
23
+ }
24
+ else {
21
25
  return elementAsProps[propertyName];
22
- },
26
+ }
27
+ }
28
+ const propsProxy = new Proxy({}, {
29
+ get: valueGetter,
23
30
  set: (target, propertyName, newValue) => {
24
31
  if (verifyExists) {
25
32
  assertValidPropertyName(propertyName, element, element.tagName);
@@ -30,7 +37,13 @@ export function createElementUpdaterProxy(element, verifyExists) {
30
37
  * "getOwnPropertyDescriptor".
31
38
  */
32
39
  target[propertyName] = undefined;
33
- elementAsProps[propertyName] = newValue;
40
+ const asyncState = element.asyncStateProperties[propertyName];
41
+ if (asyncState) {
42
+ asyncState.setValue(newValue);
43
+ }
44
+ else {
45
+ elementAsProps[propertyName] = newValue;
46
+ }
34
47
  return true;
35
48
  },
36
49
  ownKeys: (target) => {
@@ -40,7 +53,7 @@ export function createElementUpdaterProxy(element, verifyExists) {
40
53
  if (propertyName in target) {
41
54
  return {
42
55
  get value() {
43
- return elementAsProps[propertyName];
56
+ return valueGetter(target, propertyName);
44
57
  },
45
58
  configurable: true,
46
59
  enumerable: true,
@@ -2,28 +2,20 @@ import { TemplateResult } from 'lit';
2
2
  import { TypedEvent } from '../typed-event/typed-event';
3
3
  import { DeclarativeElement, HostInstanceType } from './declarative-element';
4
4
  import { CustomElementTagName } from './declarative-element-init';
5
- import { AsyncProp, SetAsyncPropInputs } from './properties/async-prop';
5
+ import { AsyncStateInputs, MaybeAsyncStateToSync } from './properties/async-state';
6
6
  import { EventDescriptorMap, EventInitMapEventDetailExtractor, EventsInitMap } from './properties/element-events';
7
7
  import { PropertyInitMapBase } from './properties/element-properties';
8
8
  export type RenderOutput = TemplateResult | string | ReadonlyArray<TemplateResult> | ReadonlyArray<string>;
9
9
  export type RenderCallback<TagNameGeneric extends CustomElementTagName = any, InputsGeneric extends PropertyInitMapBase = any, StateGeneric extends PropertyInitMapBase = any, EventsInitGeneric extends EventsInitMap = any, HostClassKeys extends string = any, CssVarKeys extends string = any> = (params: RenderParams<TagNameGeneric, InputsGeneric, StateGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>) => RenderOutput;
10
10
  export type InitCallback<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string> = (params: RenderParams<TagNameGeneric, InputsGeneric, StateGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>) => void;
11
- export type UpdateStateCallback<StateGeneric extends PropertyInitMapBase> = (newState: Partial<StateGeneric>) => void;
11
+ export type UpdateStateCallback<StateGeneric extends PropertyInitMapBase> = (newState: Partial<AsyncStateInputs<StateGeneric>>) => void;
12
12
  export type RenderParams<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateInitGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string> = {
13
- state: Readonly<StateInitGeneric>;
13
+ state: Readonly<MaybeAsyncStateToSync<StateInitGeneric>>;
14
14
  updateState: UpdateStateCallback<StateInitGeneric>;
15
15
  events: EventDescriptorMap<EventsInitGeneric>;
16
16
  host: HostInstanceType<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
17
17
  dispatch: <EventTypeNameGeneric extends keyof EventsInitGeneric>(event: TypedEvent<EventTypeNameGeneric extends string ? EventTypeNameGeneric : never, EventInitMapEventDetailExtractor<EventTypeNameGeneric, EventsInitGeneric>> | Event) => boolean;
18
18
  inputs: InputsGeneric;
19
- /**
20
- * Updates async props in the state if they have not already been set. Once promises settle,
21
- * this automatically updates the state. In order to re-trigger an async prop, set it to
22
- * undefined first.
23
- */
24
- ensureAsyncProp: (values: Partial<{
25
- [StateKey in keyof StateInitGeneric as StateInitGeneric[StateKey] extends AsyncProp<any> ? StateKey : never]: StateInitGeneric[StateKey] extends AsyncProp<infer ValueGeneric> ? SetAsyncPropInputs<ValueGeneric> : never;
26
- }>) => void;
27
19
  };
28
20
  export declare function createRenderParams<TagNameGeneric extends CustomElementTagName, InputsGeneric extends PropertyInitMapBase, StateGeneric extends PropertyInitMapBase, EventsInitGeneric extends EventsInitMap, HostClassKeys extends string, CssVarKeys extends string>(element: DeclarativeElement<TagNameGeneric, InputsGeneric, StateGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>, eventsMap: EventDescriptorMap<EventsInitGeneric>): RenderParams<TagNameGeneric, InputsGeneric, StateGeneric, EventsInitGeneric, HostClassKeys, CssVarKeys>;
29
21
  //# sourceMappingURL=render-callback.d.ts.map
@@ -1,9 +1,15 @@
1
1
  import { getObjectTypedKeys } from '@augment-vir/common';
2
- import { ensureAsyncProp } from './properties/async-prop';
3
2
  export function createRenderParams(element, eventsMap) {
4
- function updateState(partialProps) {
5
- getObjectTypedKeys(partialProps).forEach((propKey) => {
6
- element.instanceState[propKey] = partialProps[propKey];
3
+ function updateState(newStatePartial) {
4
+ getObjectTypedKeys(newStatePartial).forEach((stateKey) => {
5
+ const newValue = newStatePartial[stateKey];
6
+ const asyncState = element.asyncStateProperties[stateKey];
7
+ if (asyncState) {
8
+ asyncState.setValue(newValue);
9
+ }
10
+ else {
11
+ element.instanceState[stateKey] = newValue;
12
+ }
7
13
  });
8
14
  }
9
15
  const renderParams = {
@@ -13,19 +19,6 @@ export function createRenderParams(element, eventsMap) {
13
19
  host: element,
14
20
  state: element.instanceState,
15
21
  events: eventsMap,
16
- ensureAsyncProp: (values) => {
17
- Object.entries(values).forEach(([stateKey, newSet,]) => {
18
- if (!(stateKey in element.instanceState)) {
19
- throw new Error(`Invalid key given to ensureAsyncProp: ${stateKey}`);
20
- }
21
- ensureAsyncProp({
22
- state: element.instanceState,
23
- updateState: updateState,
24
- stateProp: stateKey,
25
- ...newSet,
26
- });
27
- });
28
- },
29
22
  };
30
23
  return renderParams;
31
24
  }
package/dist/index.d.ts CHANGED
@@ -9,9 +9,9 @@ export * from './declarative-element/directives/directive-helpers';
9
9
  export * from './declarative-element/directives/listen.directive';
10
10
  export * from './declarative-element/directives/on-dom-created.directive';
11
11
  export * from './declarative-element/directives/on-resize.directive';
12
- export * from './declarative-element/directives/render-async-prop.directive';
12
+ export * from './declarative-element/directives/render-async-state.directive';
13
13
  export * from './declarative-element/directives/render-if.directive';
14
- export * from './declarative-element/properties/async-prop';
14
+ export * from './declarative-element/properties/async-state';
15
15
  export * from './declarative-element/properties/css-vars';
16
16
  export * from './declarative-element/properties/element-events';
17
17
  export * from './declarative-element/properties/element-properties';
package/dist/index.js CHANGED
@@ -8,9 +8,9 @@ export * from './declarative-element/directives/directive-helpers';
8
8
  export * from './declarative-element/directives/listen.directive';
9
9
  export * from './declarative-element/directives/on-dom-created.directive';
10
10
  export * from './declarative-element/directives/on-resize.directive';
11
- export * from './declarative-element/directives/render-async-prop.directive';
11
+ export * from './declarative-element/directives/render-async-state.directive';
12
12
  export * from './declarative-element/directives/render-if.directive';
13
- export * from './declarative-element/properties/async-prop';
13
+ export * from './declarative-element/properties/async-state';
14
14
  export * from './declarative-element/properties/css-vars';
15
15
  export * from './declarative-element/properties/element-events';
16
16
  export * from './declarative-element/properties/element-properties';
@@ -13,7 +13,7 @@ const htmlChecksAndTransforms = [
13
13
  currentLitString,
14
14
  currentValue,
15
15
  });
16
- throw new Error(`Got interpolated tag name but it wasn't of type VirElement: ${currentValue.prototype.constructor.name}`);
16
+ throw new Error(`Got interpolated tag name but it wasn't of type VirElement: '${currentValue.prototype.constructor.name}'`);
17
17
  }
18
18
  return shouldHaveTagNameHere && staticTagName;
19
19
  }, (input) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "element-vir",
3
- "version": "8.0.2",
3
+ "version": "9.0.0",
4
4
  "keywords": [
5
5
  "custom",
6
6
  "web",
@@ -30,7 +30,7 @@
30
30
  "format": "virmator format",
31
31
  "publish": "virmator publish \"npm run compile && npm run test:all\"",
32
32
  "start": "npm install && virmator frontend",
33
- "test": "npm run test:types && virmator test-web",
33
+ "test": "virmator test-web",
34
34
  "test:all": "npm run test:types && npm run test:coverage && npm run test:spelling && npm run test:format && npm run test:docs",
35
35
  "test:coverage": "virmator test-web coverage",
36
36
  "test:docs": "virmator code-in-markdown check",
@@ -39,13 +39,13 @@
39
39
  "test:types": "tsc --noEmit"
40
40
  },
41
41
  "dependencies": {
42
- "@augment-vir/browser": "^11.0.0",
43
- "@augment-vir/common": "^11.0.0",
42
+ "@augment-vir/browser": "^11.1.2",
43
+ "@augment-vir/common": "^11.1.2",
44
44
  "lit": "2.6.1"
45
45
  },
46
46
  "devDependencies": {
47
- "@augment-vir/browser-testing": "^11.0.0",
48
- "@augment-vir/node-js": "^11.0.0",
47
+ "@augment-vir/browser-testing": "^11.1.2",
48
+ "@augment-vir/node-js": "^11.1.2",
49
49
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
50
50
  "@open-wc/testing": "^3.1.7",
51
51
  "@types/mocha": "^10.0.1",
@@ -1,10 +0,0 @@
1
- import { UnPromise } from '@augment-vir/common';
2
- import { AsyncProp } from '../properties/async-prop';
3
- export declare function renderAsyncProp<T>({ asyncProp, fallback, resolutionRender, errorRender, }: {
4
- asyncProp: AsyncProp<T>;
5
- /** This value will be rendered if the async state has not settled yet. */
6
- fallback: unknown;
7
- resolutionRender?: ((resolved: UnPromise<T>) => unknown) | undefined;
8
- errorRender?: ((error: Error) => unknown) | undefined;
9
- }): unknown;
10
- //# sourceMappingURL=render-async-prop.directive.d.ts.map
@@ -1,12 +0,0 @@
1
- import { extractErrorMessage } from '@augment-vir/common';
2
- export function renderAsyncProp({ asyncProp, fallback, resolutionRender, errorRender, }) {
3
- if (asyncProp === null || asyncProp === void 0 ? void 0 : asyncProp.error) {
4
- return errorRender ? errorRender(asyncProp.error) : extractErrorMessage(asyncProp.error);
5
- }
6
- else if (asyncProp === null || asyncProp === void 0 ? void 0 : asyncProp.resolution) {
7
- return resolutionRender ? resolutionRender(asyncProp.resolution) : asyncProp.resolution;
8
- }
9
- else {
10
- return fallback;
11
- }
12
- }
@@ -1,27 +0,0 @@
1
- import { UnPromise } from '@augment-vir/common';
2
- import { JsonValue } from 'type-fest';
3
- export type AsyncProp<ValueGeneric> = {
4
- error?: Error;
5
- promise?: Promise<UnPromise<ValueGeneric>>;
6
- resolution?: UnPromise<ValueGeneric>;
7
- isChainedAlready?: true;
8
- lastTrigger?: JsonValue;
9
- } | undefined | null;
10
- declare const unsetSymbol: unique symbol;
11
- export declare function asyncProp<ValueGeneric>(initialValue?: UnPromise<ValueGeneric> | Promise<UnPromise<ValueGeneric>> | typeof unsetSymbol): AsyncProp<ValueGeneric>;
12
- export type SetAsyncPropInputs<ValueGeneric> = {
13
- updateIfThisChanges?: JsonValue;
14
- } & ({
15
- createPromise: () => Promise<UnPromise<ValueGeneric>>;
16
- } | {
17
- resolution: UnPromise<ValueGeneric>;
18
- } | {
19
- promise: Promise<UnPromise<ValueGeneric>>;
20
- });
21
- export declare function ensureAsyncProp<ValueGeneric, StatePropGeneric extends PropertyKey>({ state, stateProp, updateState: inputUpdateState, ...newValue }: {
22
- state: Record<StatePropGeneric, AsyncProp<ValueGeneric>>;
23
- stateProp: StatePropGeneric;
24
- updateState: (newState: Record<StatePropGeneric, AsyncProp<ValueGeneric>>) => void;
25
- } & SetAsyncPropInputs<ValueGeneric>): void;
26
- export {};
27
- //# sourceMappingURL=async-prop.d.ts.map
@@ -1,102 +0,0 @@
1
- import { areJsonEqual, ensureError, isPromiseLike, typedHasProperty, } from '@augment-vir/common';
2
- const unsetSymbol = Symbol('unset');
3
- export function asyncProp(initialValue = unsetSymbol) {
4
- if (initialValue === unsetSymbol) {
5
- return undefined;
6
- }
7
- else if (isPromiseLike(initialValue)) {
8
- return {
9
- promise: initialValue,
10
- };
11
- }
12
- else {
13
- return {
14
- resolution: initialValue,
15
- };
16
- }
17
- }
18
- function shouldForceUpdate(newValue, currentValue) {
19
- if (!currentValue) {
20
- return true;
21
- }
22
- if (typedHasProperty(newValue, 'updateIfThisChanges')) {
23
- if (typedHasProperty(currentValue, 'lastTrigger')) {
24
- return !areJsonEqual(currentValue.lastTrigger, newValue.updateIfThisChanges);
25
- }
26
- else {
27
- return true;
28
- }
29
- }
30
- return false;
31
- }
32
- export function ensureAsyncProp({ state, stateProp, updateState: inputUpdateState, ...newValue }) {
33
- function updateState(newStateValue) {
34
- /**
35
- * As cast needed here because the RecordGeneric type isn't strong enough to narrow its keys
36
- * to StatePropGeneric, it just thinks they're all strings
37
- */
38
- inputUpdateState({ [stateProp]: newStateValue });
39
- }
40
- const forceUpdate = shouldForceUpdate(newValue, state[stateProp]);
41
- const currentValue = forceUpdate ? undefined : state[stateProp];
42
- if (currentValue === null || currentValue === void 0 ? void 0 : currentValue.error) {
43
- // abort if there's an error
44
- return;
45
- }
46
- if (currentValue && 'resolution' in currentValue) {
47
- // abort if the async value has already been resolved
48
- return;
49
- }
50
- const lastTriggerObject = typedHasProperty(newValue, 'updateIfThisChanges')
51
- ? { lastTrigger: newValue.updateIfThisChanges }
52
- : {};
53
- // if given a resolution value, set that
54
- if ('resolution' in newValue) {
55
- return updateState({
56
- resolution: newValue.resolution,
57
- ...lastTriggerObject,
58
- });
59
- }
60
- // if there is no promise or resolution, set a promise
61
- const needsNewPromise = currentValue == undefined || !('promise' in currentValue);
62
- const needsChaining = currentValue == undefined || !currentValue.isChainedAlready;
63
- if (!needsNewPromise && !needsChaining) {
64
- // abort, there is nothing to do
65
- return;
66
- }
67
- const newPromise = needsNewPromise
68
- ? 'promise' in newValue
69
- ? newValue.promise
70
- : 'createPromise' in newValue
71
- ? newValue.createPromise()
72
- : undefined
73
- : undefined;
74
- if (needsNewPromise && !newPromise) {
75
- throw new Error(`Was not able to create a new promise for state.${String(stateProp)}: missing "promise" or "createPromise" key for ${ensureAsyncProp.name}`);
76
- }
77
- const promiseToChain = newPromise !== null && newPromise !== void 0 ? newPromise : currentValue === null || currentValue === void 0 ? void 0 : currentValue.promise;
78
- if (!promiseToChain) {
79
- // at this point there is definitely a promise to chain
80
- throw new Error('Failed to find promise to chain.');
81
- }
82
- promiseToChain
83
- .then((value) => {
84
- updateState({
85
- promise: promiseToChain,
86
- resolution: value,
87
- ...lastTriggerObject,
88
- });
89
- })
90
- .catch((error) => {
91
- updateState({
92
- promise: promiseToChain,
93
- error: ensureError(error),
94
- ...lastTriggerObject,
95
- });
96
- });
97
- updateState({
98
- promise: promiseToChain,
99
- isChainedAlready: true,
100
- ...lastTriggerObject,
101
- });
102
- }