packages/runtime-html/src/observation/value-attribute-observer.ts
import { type IIndexable, areEqual } from '@aurelia/kernel';
import { subscriberCollection } from '@aurelia/runtime';
import { mixinNodeObserverUseConfig } from './observation-utils';
import { atLayout, atNode, atObserver } from '../utilities';
import type { AccessorType, ISubscriberCollection } from '@aurelia/runtime';
import type { INode } from '../dom';
import type { INodeObserver, INodeObserverConfigBase } from './observer-locator';
export interface ValueAttributeObserver extends ISubscriberCollection {}
/**
* Observer for non-radio, non-checkbox input.
*/
export class ValueAttributeObserver implements INodeObserver {
static {
mixinNodeObserverUseConfig(ValueAttributeObserver);
subscriberCollection(ValueAttributeObserver, null!);
}
// ObserverType.Layout is not always true, it depends on the element & property combo
// but for simplicity, always treat as such
public type: AccessorType = (atNode | atObserver | atLayout) as AccessorType;
/** @internal */
public readonly _el: INode & IIndexable;
/** @internal */
private readonly _key: PropertyKey;
/** @internal */
private _value: unknown = '';
/** @internal */
private _oldValue: unknown = '';
/** @internal */
private _hasChanges: boolean = false;
/**
* Used by mixing defined methods subscribe/unsubscribe
*
* @internal
*/
public _listened: boolean = false;
/** @internal */
public _config: INodeObserverConfigBase;
/**
* Comes from mixin
*/
public useConfig!: (config: INodeObserverConfigBase) => void;
public constructor(
obj: INode,
key: PropertyKey,
config: INodeObserverConfigBase,
) {
this._el = obj as INode & IIndexable;
this._key = key;
this._config = config;
}
public getValue(): unknown {
// is it safe to assume the observer has the latest value?
// todo: ability to turn on/off cache based on type
return this._value;
}
public setValue(newValue: string | null): void {
if (areEqual(newValue, this._value)) {
return;
}
this._oldValue = this._value;
this._value = newValue;
this._hasChanges = true;
if (!this._config.readonly) {
this._flushChanges();
}
}
/** @internal */
private _flushChanges(): void {
if (this._hasChanges) {
this._hasChanges = false;
this._el[this._key as string] = this._value ?? this._config.default;
this._flush();
}
}
public handleEvent(): void {
this._oldValue = this._value;
this._value = this._el[this._key as string];
if (this._oldValue !== this._value) {
this._hasChanges = false;
this._flush();
}
}
/**
* Used by mixing defined methods subscribe
*
* @internal
*/
public _start(): void {
this._value = this._oldValue = this._el[this._key as string];
}
/** @internal */
private _flush() {
const oV = this._oldValue;
this._oldValue = this._value;
this.subs.notify(this._value, oV);
}
}