@aurelia/runtime-html
Advanced tools
Comparing version 2.0.1-dev.202405031042 to 2.0.1-dev.202405110109
@@ -0,12 +1,14 @@ | ||
import { IExpressionParser, IsBindingBehavior } from '@aurelia/expression-parser'; | ||
import { IServiceLocator, Key } from '@aurelia/kernel'; | ||
import { IExpressionParser } from '@aurelia/expression-parser'; | ||
import { IObserverLocator } from '@aurelia/runtime'; | ||
import { type Scope } from './scope'; | ||
import { TaskQueue } from '@aurelia/platform'; | ||
import { IObserverLocator, IObserverLocatorBasedConnectable } from '@aurelia/runtime'; | ||
import { ITemplateCompiler } from '@aurelia/template-compiler'; | ||
import { IAstEvaluator } from '../ast.eval'; | ||
import { IPlatform } from '../platform'; | ||
import { IHasController } from '../renderer'; | ||
import { CustomElementDefinition } from '../resources/custom-element'; | ||
import { ICustomElementController, IHydrationContext, IController } from '../templating/controller'; | ||
import { IHasController } from '../renderer'; | ||
import { ITemplateCompiler } from '@aurelia/template-compiler'; | ||
import { IController, ICustomElementController, IHydrationContext } from '../templating/controller'; | ||
import { IRendering } from '../templating/rendering'; | ||
import { IPlatform } from '../platform'; | ||
import { IBinding } from './interfaces-bindings'; | ||
import { IBinding, IBindingController } from './interfaces-bindings'; | ||
import { Scope } from './scope'; | ||
/** | ||
@@ -43,2 +45,16 @@ * The public methods of this binding emulates the necessary of an IHydratableController, | ||
} | ||
export interface SpreadValueBinding extends IAstEvaluator, IServiceLocator, IObserverLocatorBasedConnectable { | ||
} | ||
export declare class SpreadValueBinding implements IBinding { | ||
target: object; | ||
targetKeys: string[]; | ||
ast: IsBindingBehavior; | ||
isBound: boolean; | ||
constructor(controller: IBindingController, target: object, targetKeys: string[], ast: IsBindingBehavior, ol: IObserverLocator, l: IServiceLocator, taskQueue: TaskQueue); | ||
updateTarget(): void; | ||
handleChange(): void; | ||
handleCollectionChange(): void; | ||
bind(scope: Scope): void; | ||
unbind(): void; | ||
} | ||
//# sourceMappingURL=spread-binding.d.ts.map |
@@ -42,4 +42,2 @@ import { IContainer } from '@aurelia/kernel'; | ||
}) | (new () => { | ||
'...$attrs'(rawName: string, rawValue: string, _parts: readonly string[]): import("@aurelia/template-compiler").AttrSyntax; | ||
}) | (new () => { | ||
'PART.trigger:PART'(rawName: string, rawValue: string, parts: readonly string[]): import("@aurelia/template-compiler").AttrSyntax; | ||
@@ -182,4 +180,9 @@ 'PART.capture:PART'(rawName: string, rawValue: string, parts: readonly string[]): import("@aurelia/template-compiler").AttrSyntax; | ||
readonly target: "hs"; | ||
render(renderingCtrl: import(".").IHydratableController<import(".").IViewModel>, target: HTMLElement, _instruction: import("@aurelia/template-compiler").SpreadBindingInstruction, platform: import("./platform").IPlatform, exprParser: import("@aurelia/expression-parser").IExpressionParser<import("@aurelia/expression-parser").CustomExpression>, observerLocator: import("@aurelia/runtime").IObserverLocator): void; | ||
render(renderingCtrl: import(".").IHydratableController<import(".").IViewModel>, target: HTMLElement, instruction: import("@aurelia/template-compiler").SpreadTransferedBindingInstruction, platform: import("./platform").IPlatform, exprParser: import("@aurelia/expression-parser").IExpressionParser<import("@aurelia/expression-parser").CustomExpression>, observerLocator: import("@aurelia/runtime").IObserverLocator): void; | ||
}; | ||
} | { | ||
new (): { | ||
readonly target: "svb"; | ||
render(renderingCtrl: import(".").IHydratableController<import(".").IViewModel>, target: import(".").ICustomElementController<import(".").ICustomElementViewModel>, instruction: import("@aurelia/template-compiler").SpreadValueBindingInstruction, platform: import("./platform").IPlatform, exprParser: import("@aurelia/expression-parser").IExpressionParser<import("@aurelia/expression-parser").CustomExpression>, observerLocator: import("@aurelia/runtime").IObserverLocator): void; | ||
}; | ||
})[]; | ||
@@ -186,0 +189,0 @@ export declare const StandardConfiguration: { |
@@ -43,3 +43,3 @@ export { type IAstEvaluator, astAssign, astBind, astEvaluate, astUnbind, } from './ast.eval'; | ||
export { If, Else, } from './resources/template-controllers/if'; | ||
export { Repeat } from './resources/template-controllers/repeat'; | ||
export { Repeat, IRepeatableHandlerResolver, IRepeatableHandler, ArrayLikeHandler, } from './resources/template-controllers/repeat'; | ||
export { With } from './resources/template-controllers/with'; | ||
@@ -46,0 +46,0 @@ export { Switch, Case, DefaultCase, } from './resources/template-controllers/switch'; |
@@ -8,7 +8,7 @@ import { type Constructable } from '@aurelia/kernel'; | ||
import { INode } from './dom'; | ||
import { IController } from './templating/controller'; | ||
import { ICustomElementController, IController } from './templating/controller'; | ||
import { IPlatform } from './platform'; | ||
import { IRendering } from './templating/rendering'; | ||
import type { IHydratableController } from './templating/controller'; | ||
import { AttributeBindingInstruction, HydrateAttributeInstruction, HydrateElementInstruction, HydrateLetElementInstruction, HydrateTemplateController, IInstruction, ITemplateCompiler, InterpolationInstruction, IteratorBindingInstruction, ListenerBindingInstruction, PropertyBindingInstruction, RefBindingInstruction, SetAttributeInstruction, SetClassAttributeInstruction, SetPropertyInstruction, SetStyleAttributeInstruction, SpreadBindingInstruction, StylePropertyBindingInstruction, TextBindingInstruction } from '@aurelia/template-compiler'; | ||
import { AttributeBindingInstruction, HydrateAttributeInstruction, HydrateElementInstruction, HydrateLetElementInstruction, HydrateTemplateController, IInstruction, ITemplateCompiler, InterpolationInstruction, IteratorBindingInstruction, ListenerBindingInstruction, PropertyBindingInstruction, RefBindingInstruction, SetAttributeInstruction, SetClassAttributeInstruction, SetPropertyInstruction, SetStyleAttributeInstruction, SpreadTransferedBindingInstruction, SpreadValueBindingInstruction, StylePropertyBindingInstruction, TextBindingInstruction } from '@aurelia/template-compiler'; | ||
/** | ||
@@ -146,5 +146,11 @@ * An interface describing an instruction renderer | ||
readonly target: "hs"; | ||
render(renderingCtrl: IHydratableController, target: HTMLElement, _instruction: SpreadBindingInstruction, platform: IPlatform, exprParser: IExpressionParser, observerLocator: IObserverLocator): void; | ||
render(renderingCtrl: IHydratableController, target: HTMLElement, instruction: SpreadTransferedBindingInstruction, platform: IPlatform, exprParser: IExpressionParser, observerLocator: IObserverLocator): void; | ||
}; | ||
}; | ||
export declare const SpreadValueRenderer: { | ||
new (): { | ||
readonly target: "svb"; | ||
render(renderingCtrl: IHydratableController, target: ICustomElementController, instruction: SpreadValueBindingInstruction, platform: IPlatform, exprParser: IExpressionParser, observerLocator: IObserverLocator): void; | ||
}; | ||
}; | ||
//# sourceMappingURL=renderer.d.ts.map |
@@ -8,3 +8,2 @@ import type { Constructable, IContainer, ResourceType, PartialResourceDefinition, Key, ResourceDefinition, InterfaceSymbol } from '@aurelia/kernel'; | ||
export type PartialCustomElementDefinition<TBindables extends string = string> = PartialResourceDefinition<Omit<IElementComponentDefinition<TBindables>, 'type'> & { | ||
readonly cache?: number | '*'; | ||
/** | ||
@@ -121,3 +120,2 @@ * An semi internal property used to signal the rendering process not to try to compile the template again | ||
readonly key: string; | ||
readonly cache: '*' | number; | ||
readonly capture: boolean | ((attr: string) => boolean); | ||
@@ -124,0 +122,0 @@ readonly template: null | string | Node; |
@@ -0,9 +1,6 @@ | ||
import { IContainer } from '@aurelia/kernel'; | ||
import { ForOfStatement, type IsBindingBehavior } from '@aurelia/expression-parser'; | ||
import { type Collection, type IndexMap } from '@aurelia/runtime'; | ||
import { IExpressionParser } from '@aurelia/expression-parser'; | ||
import { IRenderLocation } from '../../dom'; | ||
import { IViewFactory } from '../../templating/view'; | ||
import { type Collection, CollectionObserver, type IndexMap } from '@aurelia/runtime'; | ||
import { CustomAttributeStaticAuDefinition } from '../custom-attribute'; | ||
import { HydrateTemplateController } from '@aurelia/template-compiler'; | ||
import type { ISyntheticView, ICustomAttributeController, IHydratableController, ICustomAttributeViewModel, IHydratedController, IHydratedParentController, ControllerVisitor } from '../../templating/controller'; | ||
import type { ISyntheticView, ICustomAttributeController, ICustomAttributeViewModel, IHydratedController, IHydratedParentController, ControllerVisitor } from '../../templating/controller'; | ||
type Items<C extends Collection = unknown[]> = C | undefined; | ||
@@ -19,3 +16,3 @@ export declare class Repeat<C extends Collection = unknown[]> implements ICustomAttributeViewModel { | ||
key: null | string | IsBindingBehavior; | ||
constructor(instruction: HydrateTemplateController, parser: IExpressionParser, location: IRenderLocation, parent: IHydratableController, factory: IViewFactory); | ||
constructor(); | ||
binding(_initiator: IHydratedController, _parent: IHydratedParentController): void | Promise<void>; | ||
@@ -30,3 +27,32 @@ attaching(initiator: IHydratedController, _parent: IHydratedParentController): void | Promise<void>; | ||
} | ||
export declare const IRepeatableHandlerResolver: import("@aurelia/kernel").InterfaceSymbol<IRepeatableHandlerResolver>; | ||
/** | ||
* An interface describings the capabilities of a repeatable handler. | ||
*/ | ||
export interface IRepeatableHandlerResolver { | ||
resolve(value: unknown): IRepeatableHandler; | ||
} | ||
/** | ||
* A simple implementation for handling common array like values, such as: | ||
* - HTMLCollection | ||
* - NodeList | ||
* - FileList, | ||
* - etc... | ||
*/ | ||
export declare class ArrayLikeHandler implements IRepeatableHandler<ArrayLike<unknown>> { | ||
static register(c: IContainer): void; | ||
handles(value: NonNullable<unknown>): boolean; | ||
iterate(items: ArrayLike<unknown>, func: (item: unknown, index: number, arr: ArrayLike<unknown>) => void): void; | ||
} | ||
/** | ||
* An interface describing a repeatable value handler | ||
*/ | ||
export declare const IRepeatableHandler: import("@aurelia/kernel").InterfaceSymbol<IRepeatableHandler<Repeatable>>; | ||
export interface IRepeatableHandler<TValue extends Repeatable = Repeatable> { | ||
handles(value: unknown): boolean; | ||
getObserver?(value: TValue): CollectionObserver | undefined; | ||
iterate(value: TValue, func: (item: unknown, index: number, value: TValue) => void): void; | ||
} | ||
type Repeatable = Collection | ArrayLike<unknown> | number | null | undefined; | ||
export {}; | ||
//# sourceMappingURL=repeat.d.ts.map |
{ | ||
"name": "@aurelia/runtime-html", | ||
"version": "2.0.1-dev.202405031042", | ||
"version": "2.0.1-dev.202405110109", | ||
"main": "dist/cjs/index.cjs", | ||
@@ -57,9 +57,9 @@ "module": "dist/esm/index.mjs", | ||
"dependencies": { | ||
"@aurelia/kernel": "2.0.1-dev.202405031042", | ||
"@aurelia/metadata": "2.0.1-dev.202405031042", | ||
"@aurelia/platform": "2.0.1-dev.202405031042", | ||
"@aurelia/platform-browser": "2.0.1-dev.202405031042", | ||
"@aurelia/runtime": "2.0.1-dev.202405031042", | ||
"@aurelia/expression-parser": "2.0.1-dev.202405031042", | ||
"@aurelia/template-compiler": "2.0.1-dev.202405031042" | ||
"@aurelia/kernel": "2.0.1-dev.202405110109", | ||
"@aurelia/metadata": "2.0.1-dev.202405110109", | ||
"@aurelia/platform": "2.0.1-dev.202405110109", | ||
"@aurelia/platform-browser": "2.0.1-dev.202405110109", | ||
"@aurelia/runtime": "2.0.1-dev.202405110109", | ||
"@aurelia/expression-parser": "2.0.1-dev.202405110109", | ||
"@aurelia/template-compiler": "2.0.1-dev.202405110109" | ||
}, | ||
@@ -66,0 +66,0 @@ "devDependencies": { |
@@ -89,5 +89,5 @@ import { BrowserPlatform } from '@aurelia/platform-browser'; | ||
const target = e.target as HTMLFormElement; | ||
const hasAction = (target.getAttribute('action')?.length ?? 0) > 0; | ||
const noAction = !target.getAttribute('action'); | ||
if (target.tagName === 'FORM' && !hasAction) { | ||
if (target.tagName === 'FORM' && noAction) { | ||
e.preventDefault(); | ||
@@ -94,0 +94,0 @@ } |
@@ -229,3 +229,3 @@ /* eslint-disable no-fallthrough */ | ||
case ekAccessMember: { | ||
const instance = astEvaluate(ast.object, s, e, c) as IIndexable; | ||
const instance = astEvaluate(ast.object, s, e, c) as IIndexable | null; | ||
let ret: unknown; | ||
@@ -248,3 +248,2 @@ if (e?.strict) { | ||
} | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
if (instance) { | ||
@@ -251,0 +250,0 @@ ret = instance[ast.name]; |
@@ -91,3 +91,2 @@ import { kebabCase, getPrototypeChain, noop, Class } from '@aurelia/kernel'; | ||
const prop = configOrProp.name; | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
if (!prop) throw createMappedError(ErrorNames.invalid_bindable_decorator_usage_class_without_property_name_configuration); | ||
@@ -94,0 +93,0 @@ if (typeof prop !== 'string') throw createMappedError(ErrorNames.invalid_bindable_decorator_usage_symbol); |
@@ -297,4 +297,3 @@ import { type IServiceLocator, Key, type Constructable, IDisposable, IContainer } from '@aurelia/kernel'; | ||
export const createPrototypeMixer = (() => { | ||
const mixed = new WeakSet<Constructable<IBinding>>(); | ||
export const createPrototypeMixer = ((mixed = new WeakSet<Constructable<IBinding>>()) => { | ||
return (mixer: () => void) => { | ||
@@ -301,0 +300,0 @@ return function<T extends Constructable<IBinding>>(this: T) { |
@@ -117,3 +117,3 @@ import { | ||
if (!this.isBound) { | ||
/* istanbul-ignore-next */ | ||
/* istanbul ignore next */ | ||
return; | ||
@@ -120,0 +120,0 @@ } |
@@ -0,13 +1,18 @@ | ||
import { AccessScopeExpression, IExpressionParser, IsBindingBehavior } from '@aurelia/expression-parser'; | ||
import { IServiceLocator, Key, emptyArray } from '@aurelia/kernel'; | ||
import { IExpressionParser } from '@aurelia/expression-parser'; | ||
import { IObserverLocator } from '@aurelia/runtime'; | ||
import { type Scope } from './scope'; | ||
import { createMappedError, ErrorNames } from '../errors'; | ||
import { TaskQueue } from '@aurelia/platform'; | ||
import { IObserverLocator, IObserverLocatorBasedConnectable, connectable } from '@aurelia/runtime'; | ||
import { BindingMode, IInstruction, ITemplateCompiler, InstructionType, SpreadElementPropBindingInstruction } from '@aurelia/template-compiler'; | ||
import { IAstEvaluator, astBind, astEvaluate, astUnbind } from '../ast.eval'; | ||
import { ErrorNames, createMappedError } from '../errors'; | ||
import { IPlatform } from '../platform'; | ||
import { IHasController, } from '../renderer'; | ||
import { CustomElementDefinition, findElementControllerFor } from '../resources/custom-element'; | ||
import { ICustomElementController, IHydrationContext, IController, IHydratableController, vmkCa } from '../templating/controller'; | ||
import { IHasController,} from '../renderer'; | ||
import { IInstruction, ITemplateCompiler, SpreadElementPropBindingInstruction, InstructionType } from '@aurelia/template-compiler'; | ||
import { IController, ICustomElementController, IHydratableController, IHydrationContext, vmkCa } from '../templating/controller'; | ||
import { IRendering } from '../templating/rendering'; | ||
import { IPlatform } from '../platform'; | ||
import { IBinding } from './interfaces-bindings'; | ||
import { createPrototypeMixer, mixinAstEvaluator, mixinUseScope, mixingBindingLimited } from './binding-utils'; | ||
import { IBinding, IBindingController } from './interfaces-bindings'; | ||
import { PropertyBinding } from './property-binding'; | ||
import { Scope } from './scope'; | ||
import { isObject } from '../utilities'; | ||
@@ -67,10 +72,10 @@ /** | ||
switch (inst.type) { | ||
case InstructionType.spreadBinding: | ||
case InstructionType.spreadTransferedBinding: | ||
renderSpreadInstruction(ancestor + 1); | ||
break; | ||
case InstructionType.spreadElementProp: | ||
renderers[(inst as SpreadElementPropBindingInstruction).instructions.type].render( | ||
renderers[(inst as SpreadElementPropBindingInstruction).instruction.type].render( | ||
spreadBinding, | ||
findElementControllerFor(target), | ||
(inst as SpreadElementPropBindingInstruction).instructions, | ||
(inst as SpreadElementPropBindingInstruction).instruction, | ||
platform, | ||
@@ -123,4 +128,5 @@ exprParser, | ||
public bind(_scope: Scope): void { | ||
/* istanbul ignore if */ | ||
if (this.isBound) { | ||
/* istanbul-ignore-next */ | ||
/* istanbul ignore next */ | ||
return; | ||
@@ -153,1 +159,181 @@ } | ||
} | ||
export interface SpreadValueBinding extends IAstEvaluator, IServiceLocator, IObserverLocatorBasedConnectable {} | ||
export class SpreadValueBinding implements IBinding { | ||
/** @internal */ | ||
public static mix = /*@__PURE__*/ createPrototypeMixer(() => { | ||
mixinUseScope(SpreadValueBinding); | ||
mixingBindingLimited(SpreadValueBinding, () => 'updateTarget'); | ||
connectable(SpreadValueBinding, null!); | ||
mixinAstEvaluator(true, false)(SpreadValueBinding); | ||
}); | ||
/** @internal */ | ||
private static readonly _astCache: Record<string, AccessScopeExpression> = {}; | ||
public isBound = false; | ||
/** @internal */ | ||
public _scope?: Scope = void 0; | ||
/** | ||
* A semi-private property used by connectable mixin | ||
* | ||
* @internal | ||
*/ | ||
public readonly oL: IObserverLocator; | ||
/** @internal */ | ||
public l: IServiceLocator; | ||
/** @internal */ | ||
private readonly _taskQueue: TaskQueue; | ||
// see Listener binding for explanation | ||
/** @internal */ | ||
public readonly boundFn = false; | ||
/** @internal */ | ||
private readonly _controller: IBindingController; | ||
/** @internal */ | ||
private readonly _bindingCache: Record<PropertyKey, PropertyBinding> = {}; | ||
// not a static weakmap because we want to clear the cache when the binding is disposed | ||
// also different binding at different logic with the same object shouldn't be sharing the same override context | ||
/** @internal */ | ||
private readonly _scopeCache = new WeakMap<object, Scope>(); | ||
public constructor( | ||
controller: IBindingController, | ||
public target: object, | ||
public targetKeys: string[], | ||
public ast: IsBindingBehavior, | ||
ol: IObserverLocator, | ||
l: IServiceLocator, | ||
taskQueue: TaskQueue, | ||
) { | ||
this._controller = controller; | ||
this.oL = ol; | ||
this.l = l; | ||
this._taskQueue = taskQueue; | ||
} | ||
public updateTarget(): void { | ||
this.obs.version++; | ||
const newValue = astEvaluate( | ||
this.ast, | ||
this._scope!, | ||
this, | ||
this | ||
); | ||
this.obs.clear(); | ||
this._createBindings(newValue as Record<PropertyKey, unknown> | null, true); | ||
} | ||
public handleChange(): void { | ||
/* istanbul ignore if */ | ||
if (!this.isBound) { | ||
/* istanbul ignore next */ | ||
return; | ||
} | ||
this.updateTarget(); | ||
} | ||
public handleCollectionChange(): void { | ||
/* istanbul ignore if */ | ||
if (!this.isBound) { | ||
/* istanbul ignore next */ | ||
return; | ||
} | ||
this.updateTarget(); | ||
} | ||
public bind(scope: Scope) { | ||
/* istanbul ignore if */ | ||
if (this.isBound) { | ||
/* istanbul ignore if */ | ||
if (scope === this._scope) { | ||
/* istanbul ignore next */ | ||
return; | ||
} | ||
} | ||
this.isBound = true; | ||
this._scope = scope; | ||
astBind(this.ast, scope, this); | ||
const value = astEvaluate(this.ast, scope, this, this); | ||
this._createBindings(value as Record<string, unknown> | null, false); | ||
} | ||
public unbind(): void { | ||
/* istanbul ignore if */ | ||
if (!this.isBound) { | ||
/* istanbul ignore next */ | ||
return; | ||
} | ||
this.isBound = false; | ||
astUnbind(this.ast, this._scope!, this); | ||
this._scope = void 0; | ||
let key: string; | ||
// can also try to keep track of what the active bindings are | ||
// but we know in our impl, all unbind are idempotent | ||
// so just be simple and unbind all | ||
for (key in this._bindingCache) { | ||
this._bindingCache[key].unbind(); | ||
} | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
private _createBindings(value: Record<string, unknown> | null, unbind: boolean) { | ||
if (value == null) { | ||
value = {}; | ||
/* istanbul ignore if */ | ||
if (__DEV__) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`[DEV:aurelia] $bindable spread is given a null/undefined value for properties: "${this.targetKeys.join(', ')}"`); | ||
} | ||
} else if (!isObject(value)) { | ||
value = {}; | ||
/* istanbul ignore if */ | ||
if (__DEV__) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`[DEV:aurelia] $bindable spread is given a non-object value for properties: "${this.targetKeys.join(', ')}"`); | ||
} | ||
} | ||
let key: string; | ||
let binding: PropertyBinding; | ||
// use a cache as we don't wanna cause bindings to "move" (bind/unbind) | ||
// whenever there's a new evaluation | ||
let scope = this._scopeCache.get(value); | ||
if (scope == null) { | ||
this._scopeCache.set(value, scope = Scope.fromParent(this._scope!, value)); | ||
} | ||
for (key of this.targetKeys) { | ||
binding = this._bindingCache[key]; | ||
if (key in value) { | ||
if (binding == null) { | ||
binding = this._bindingCache[key] = new PropertyBinding( | ||
this._controller, | ||
this.l, | ||
this.oL, | ||
this._taskQueue, | ||
SpreadValueBinding._astCache[key] ??= new AccessScopeExpression(key, 0), | ||
this.target, | ||
key, | ||
BindingMode.toView | ||
); | ||
} | ||
binding.bind(scope); | ||
} else if (unbind) { | ||
binding?.unbind(); | ||
} | ||
} | ||
} | ||
} |
@@ -13,8 +13,4 @@ import { BindableDefinition } from '../bindable'; | ||
import { | ||
BindingCommand, | ||
BindingCommandDefinition, | ||
type BindingCommandInstance, | ||
type IAttributeBindablesInfo, | ||
type IElementBindablesInfo, | ||
IBindablesInfoResolver, | ||
IResourceResolver, | ||
@@ -35,3 +31,2 @@ TemplateCompiler, | ||
AttrMapper, | ||
BindablesInfoResolver, | ||
ResourceResolver, | ||
@@ -42,11 +37,39 @@ ); | ||
class BindablesInfoResolver implements IBindablesInfoResolver<CustomElementDefinition, CustomAttributeDefinition> { | ||
public static register = /*@__PURE__*/ createImplementationRegister(IBindablesInfoResolver); | ||
class BindablesInfo { | ||
public constructor( | ||
public readonly attrs: Record<string, BindableDefinition>, | ||
public readonly bindables: Record<string, BindableDefinition>, | ||
public readonly primary: BindableDefinition | null, | ||
) {} | ||
} | ||
class ResourceResolver implements IResourceResolver<CustomElementDefinition, CustomAttributeDefinition> { | ||
public static register = /*@__PURE__*/ createImplementationRegister(IResourceResolver); | ||
/** @internal */ | ||
private readonly _cache = new WeakMap<CustomElementDefinition | CustomAttributeDefinition, BindablesInfo>(); | ||
private readonly _resourceCache = new WeakMap<IContainer, RecordCache>(); | ||
public get(def: CustomAttributeDefinition): IAttributeBindablesInfo; | ||
public get(def: CustomElementDefinition): IElementBindablesInfo; | ||
public get(def: CustomAttributeDefinition | CustomElementDefinition): IAttributeBindablesInfo | IElementBindablesInfo { | ||
let info = this._cache.get(def); | ||
public el(c: IContainer, name: string): CustomElementDefinition | null { | ||
let record = this._resourceCache.get(c); | ||
if (record == null) { | ||
this._resourceCache.set(c, record = new RecordCache()); | ||
} | ||
return name in record._element ? record._element[name] : (record._element[name] = CustomElement.find(c, name)); | ||
} | ||
public attr(c: IContainer, name: string): CustomAttributeDefinition | null { | ||
let record = this._resourceCache.get(c); | ||
if (record == null) { | ||
this._resourceCache.set(c, record = new RecordCache()); | ||
} | ||
return name in record._attr ? record._attr[name] : (record._attr[name] = CustomAttribute.find(c, name)); | ||
} | ||
/** @internal */ | ||
private readonly _bindableCache = new WeakMap<CustomElementDefinition | CustomAttributeDefinition, BindablesInfo>(); | ||
public bindables(def: CustomAttributeDefinition): IAttributeBindablesInfo; | ||
public bindables(def: CustomElementDefinition): IElementBindablesInfo; | ||
public bindables(def: CustomAttributeDefinition | CustomElementDefinition): IAttributeBindablesInfo | IElementBindablesInfo { | ||
let info = this._bindableCache.get(def); | ||
if (info == null) { | ||
@@ -87,3 +110,3 @@ const bindables = def.bindables; | ||
this._cache.set(def, info = new BindablesInfo(attrs, bindables, primary ?? null)); | ||
this._bindableCache.set(def, info = new BindablesInfo(attrs, bindables, primary ?? null)); | ||
} | ||
@@ -94,58 +117,5 @@ return info; | ||
class BindablesInfo { | ||
public constructor( | ||
public readonly attrs: Record<string, BindableDefinition>, | ||
public readonly bindables: Record<string, BindableDefinition>, | ||
public readonly primary: BindableDefinition | null, | ||
) {} | ||
} | ||
class ResourceResolver implements IResourceResolver<CustomElementDefinition, CustomAttributeDefinition> { | ||
public static register = /*@__PURE__*/ createImplementationRegister(IResourceResolver); | ||
private readonly _resourceCache = new WeakMap<IContainer, RecordCache>(); | ||
private readonly _commandCache = new WeakMap<IContainer, Record<string, BindingCommandInstance | null>>(); | ||
public el(c: IContainer, name: string): CustomElementDefinition | null { | ||
let record = this._resourceCache.get(c); | ||
if (record == null) { | ||
this._resourceCache.set(c, record = new RecordCache()); | ||
} | ||
return name in record.element ? record.element[name] : (record.element[name] = CustomElement.find(c, name)); | ||
} | ||
public attr(c: IContainer, name: string): CustomAttributeDefinition | null { | ||
let record = this._resourceCache.get(c); | ||
if (record == null) { | ||
this._resourceCache.set(c, record = new RecordCache()); | ||
} | ||
return name in record.attr ? record.attr[name] : (record.attr[name] = CustomAttribute.find(c, name)); | ||
} | ||
public command(c: IContainer, name: string): BindingCommandInstance | null { | ||
let commandInstanceCache = this._commandCache.get(c); | ||
if (commandInstanceCache == null) { | ||
this._commandCache.set(c, commandInstanceCache = createLookup()); | ||
} | ||
let result = commandInstanceCache[name]; | ||
if (result === void 0) { | ||
let record = this._resourceCache.get(c); | ||
if (record == null) { | ||
this._resourceCache.set(c, record = new RecordCache()); | ||
} | ||
const commandDef = name in record.command ? record.command[name] : (record.command[name] = BindingCommand.find(c, name)); | ||
if (commandDef == null) { | ||
throw createMappedError(ErrorNames.compiler_unknown_binding_command, name); | ||
} | ||
commandInstanceCache[name] = result = BindingCommand.get(c, name); | ||
} | ||
return result; | ||
} | ||
} | ||
class RecordCache { | ||
public element = createLookup<CustomElementDefinition | null>(); | ||
public attr = createLookup<CustomAttributeDefinition | null>(); | ||
public command = createLookup<BindingCommandDefinition | null>(); | ||
public _element = createLookup<CustomElementDefinition | null>(); | ||
public _attr = createLookup<CustomAttributeDefinition | null>(); | ||
} |
@@ -6,3 +6,2 @@ import { IContainer, noop } from '@aurelia/kernel'; | ||
ColonPrefixedBindAttributePattern, | ||
SpreadAttributePattern, | ||
DotSeparatedAttributePattern, | ||
@@ -24,3 +23,3 @@ RefAttributePattern, | ||
TriggerBindingCommand, | ||
SpreadBindingCommand, | ||
SpreadValueBindingCommand, | ||
} from '@aurelia/template-compiler'; | ||
@@ -45,2 +44,3 @@ import { | ||
SpreadRenderer, | ||
SpreadValueRenderer, | ||
} from './renderer'; | ||
@@ -104,3 +104,2 @@ import { | ||
DotSeparatedAttributePattern, | ||
SpreadAttributePattern, | ||
EventAttributePattern, | ||
@@ -139,3 +138,3 @@ EventModifierRegistration, | ||
AttrBindingCommand, | ||
SpreadBindingCommand, | ||
SpreadValueBindingCommand, | ||
]; | ||
@@ -220,2 +219,3 @@ | ||
SpreadRenderer, | ||
SpreadValueRenderer, | ||
]; | ||
@@ -222,0 +222,0 @@ |
@@ -57,21 +57,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
compiler_root_is_local = 701, | ||
compiler_invalid_surrogate_attr = 702, | ||
compiler_no_tc_on_surrogate = 703, | ||
compiler_invalid_let_command = 704, | ||
compiler_au_slot_on_non_element = 706, | ||
compiler_binding_to_non_bindable = 707, | ||
compiler_template_only_local_template = 708, | ||
compiler_local_el_not_under_root = 709, | ||
compiler_local_el_bindable_not_under_root = 710, | ||
compiler_local_el_bindable_name_missing = 711, | ||
compiler_local_el_bindable_duplicate = 712, | ||
compiler_unknown_binding_command = 713, | ||
compiler_primary_already_existed = 714, | ||
compiler_local_name_empty = 715, | ||
compiler_duplicate_local_name = 716, | ||
compiler_slot_without_shadowdom = 717, | ||
compiler_no_spread_tc = 718, | ||
compiler_attr_mapper_duplicate_mapping = 719, | ||
root_not_found = 767, | ||
@@ -113,2 +96,4 @@ aurelia_instance_existed_in_container = 768, | ||
portal_invalid_insert_position = 779, | ||
self_behavior_invalid_usage = 801, | ||
@@ -131,2 +116,5 @@ update_trigger_behavior_no_triggers = 802, | ||
spreading_bindable_onto_non_component = 819, | ||
spreading_invalid_target = 820, | ||
no_spread_scope_context_found = 9999, | ||
@@ -203,20 +191,4 @@ no_spread_template_controller = 9998, | ||
[ErrorNames.compiler_root_is_local]: `Template compilation error in element "{{0:name}}": the root <template> cannot be a local element template.`, | ||
[ErrorNames.compiler_invalid_surrogate_attr]: `Template compilation error: attribute "{{0}}" is invalid on element surrogate.`, | ||
[ErrorNames.compiler_no_tc_on_surrogate]: `Template compilation error: template controller "{{0}}" is invalid on element surrogate.`, | ||
[ErrorNames.compiler_invalid_let_command]: `Template compilation error: Invalid command "{{0:.command}}" for <let>. Only to-view/bind supported.`, | ||
[ErrorNames.compiler_au_slot_on_non_element]: `Template compilation error: detected projection with [au-slot="{{0}}"] attempted on a non custom element {{1}}.`, | ||
[ErrorNames.compiler_binding_to_non_bindable]: `Template compilation error: creating binding to non-bindable property {{0}} on {{1}}.`, | ||
[ErrorNames.compiler_template_only_local_template]: `Template compilation error: the custom element "{{0}}" does not have any content other than local template(s).`, | ||
[ErrorNames.compiler_local_el_not_under_root]: `Template compilation error: local element template needs to be defined directly under root of element "{{0}}".`, | ||
[ErrorNames.compiler_local_el_bindable_not_under_root]: `Template compilation error: bindable properties of local element "{{0}}" template needs to be defined directly under <template>.`, | ||
[ErrorNames.compiler_local_el_bindable_name_missing]: `Template compilation error: the attribute 'property' is missing in {{0:outerHTML}} in local element "{{1}}"`, | ||
[ErrorNames.compiler_local_el_bindable_duplicate]: `Template compilation error: Bindable property and attribute needs to be unique; found property: {{0}}, attribute: {{1}}`, | ||
[ErrorNames.compiler_unknown_binding_command]: `Template compilation error: unknown binding command: "{{0}}".{{0:bindingCommandHelp}}`, | ||
[ErrorNames.compiler_primary_already_existed]: `Template compilation error: primary already exists on element/attribute "{{0}}"`, | ||
[ErrorNames.compiler_local_name_empty]: `Template compilation error: the value of "as-custom-element" attribute cannot be empty for local element in element "{{0}}"`, | ||
[ErrorNames.compiler_duplicate_local_name]: `Template compilation error: duplicate definition of the local template named "{{0}} in element {{1}}"`, | ||
[ErrorNames.compiler_slot_without_shadowdom]: `Template compilation error: detected a usage of "<slot>" element without specifying shadow DOM options in element: {{0}}`, | ||
[ErrorNames.compiler_attr_mapper_duplicate_mapping]: `Attribute {{0}} has been already registered for {{1:element}}`, | ||
[ErrorNames.compiler_no_spread_tc]: `Spreading template controller "{{0}}" is not supported.`, | ||
@@ -249,2 +221,4 @@ [ErrorNames.root_not_found]: `Aurelia.root was accessed without a valid root.`, | ||
[ErrorNames.portal_invalid_insert_position]: 'Invalid portal insertion position: {{0}}', | ||
[ErrorNames.self_behavior_invalid_usage]: `"& self" binding behavior only supports listener binding via trigger/capture command.`, | ||
@@ -267,2 +241,5 @@ [ErrorNames.update_trigger_behavior_no_triggers]: `"& updateTrigger" invalid usage. This binding behavior requires at least one event name argument: eg <input value.bind="firstName & updateTrigger:'blur'">`, | ||
[ErrorNames.spreading_bindable_onto_non_component]: 'Spreading to bindables onto non custom element', | ||
[ErrorNames.spreading_invalid_target]: `Invalid spread target {{0}}`, | ||
[ErrorNames.no_spread_scope_context_found]: 'No scope context for spread binding.', | ||
@@ -269,0 +246,0 @@ [ErrorNames.no_spread_template_controller]: 'Spread binding does not support spreading custom attributes/template controllers. Did you build the spread instruction manually?', |
@@ -214,3 +214,6 @@ export { | ||
export { | ||
Repeat | ||
Repeat, | ||
IRepeatableHandlerResolver, | ||
IRepeatableHandler, | ||
ArrayLikeHandler, | ||
} from './resources/template-controllers/repeat'; | ||
@@ -217,0 +220,0 @@ export { |
@@ -40,4 +40,27 @@ import { | ||
import { ErrorNames, createMappedError } from './errors'; | ||
import { SpreadBinding } from './binding/spread-binding'; | ||
import { AttributeBindingInstruction, HydrateAttributeInstruction, HydrateElementInstruction, HydrateLetElementInstruction, HydrateTemplateController, IInstruction, ITemplateCompiler, InstructionType, InterpolationInstruction, IteratorBindingInstruction, LetBindingInstruction, ListenerBindingInstruction, PropertyBindingInstruction, RefBindingInstruction, SetAttributeInstruction, SetClassAttributeInstruction, SetPropertyInstruction, SetStyleAttributeInstruction, SpreadBindingInstruction, StylePropertyBindingInstruction, TextBindingInstruction } from '@aurelia/template-compiler'; | ||
import { SpreadBinding, SpreadValueBinding } from './binding/spread-binding'; | ||
import { | ||
AttributeBindingInstruction, | ||
HydrateAttributeInstruction, | ||
HydrateElementInstruction, | ||
HydrateLetElementInstruction, | ||
HydrateTemplateController, | ||
IInstruction, | ||
ITemplateCompiler, | ||
InstructionType, | ||
InterpolationInstruction, | ||
IteratorBindingInstruction, | ||
LetBindingInstruction, | ||
ListenerBindingInstruction, | ||
PropertyBindingInstruction, | ||
RefBindingInstruction, | ||
SetAttributeInstruction, | ||
SetClassAttributeInstruction, | ||
SetPropertyInstruction, | ||
SetStyleAttributeInstruction, | ||
SpreadTransferedBindingInstruction, | ||
SpreadValueBindingInstruction, | ||
StylePropertyBindingInstruction, | ||
TextBindingInstruction | ||
} from '@aurelia/template-compiler'; | ||
@@ -705,3 +728,3 @@ /** | ||
public readonly target = InstructionType.spreadBinding; | ||
public readonly target = InstructionType.spreadTransferedBinding; | ||
@@ -711,3 +734,3 @@ public render( | ||
target: HTMLElement, | ||
_instruction: SpreadBindingInstruction, | ||
instruction: SpreadTransferedBindingInstruction, | ||
platform: IPlatform, | ||
@@ -717,17 +740,47 @@ exprParser: IExpressionParser, | ||
): void { | ||
SpreadBinding | ||
.create( | ||
renderingCtrl.container.get(IHydrationContext), | ||
target, | ||
void 0, | ||
this._rendering, | ||
this._compiler, | ||
platform, | ||
exprParser, | ||
observerLocator | ||
) | ||
.forEach(b => renderingCtrl.addBinding(b)); | ||
SpreadBinding.create( | ||
renderingCtrl.container.get(IHydrationContext), | ||
target, | ||
void 0, | ||
this._rendering, | ||
this._compiler, | ||
platform, | ||
exprParser, | ||
observerLocator | ||
) | ||
.forEach(b => renderingCtrl.addBinding(b)); | ||
} | ||
}, null!); | ||
export const SpreadValueRenderer = /*@__PURE__*/ renderer(class SpreadValueRenderer implements IRenderer { | ||
public readonly target = InstructionType.spreadValueBinding; | ||
public constructor() { | ||
SpreadValueBinding.mix(); | ||
} | ||
public render( | ||
renderingCtrl: IHydratableController, | ||
target: ICustomElementController, | ||
instruction: SpreadValueBindingInstruction, | ||
platform: IPlatform, | ||
exprParser: IExpressionParser, | ||
observerLocator: IObserverLocator, | ||
): void { | ||
const instructionTarget = instruction.target; | ||
if (instructionTarget === '$bindables') { | ||
renderingCtrl.addBinding(new SpreadValueBinding( | ||
renderingCtrl, | ||
target.viewModel, | ||
objectKeys(target.definition.bindables), | ||
exprParser.parse(instruction.from, etIsProperty), | ||
observerLocator, | ||
renderingCtrl.container, | ||
platform.domWriteQueue | ||
)); | ||
} else { | ||
throw createMappedError(ErrorNames.spreading_invalid_target, instructionTarget); | ||
} | ||
} | ||
}, null!); | ||
// http://jsben.ch/7n5Kt | ||
@@ -734,0 +787,0 @@ function addClasses(classList: DOMTokenList, className: string): void { |
@@ -39,3 +39,2 @@ import { | ||
export type PartialCustomElementDefinition<TBindables extends string = string> = PartialResourceDefinition<Omit<IElementComponentDefinition<TBindables>, 'type'> & { | ||
readonly cache?: number | '*'; | ||
/** | ||
@@ -211,3 +210,2 @@ * An semi internal property used to signal the rendering process not to try to compile the template again | ||
public readonly key: string, | ||
public readonly cache: '*' | number, | ||
public readonly capture: boolean | ((attr: string) => boolean), | ||
@@ -248,3 +246,2 @@ public readonly template: null | string | Node, | ||
): CustomElementDefinition { | ||
// TODO(Sayan): aggregate the info from decorator metadata instead of using the Reflect API | ||
if (Type === null) { | ||
@@ -272,3 +269,2 @@ const def = nameOrDef; | ||
fromDefinitionOrDefault('key', def as CustomElementDefinition, () => getElementKeyFrom(name)), | ||
fromDefinitionOrDefault('cache', def, returnZero), | ||
fromAnnotationOrDefinitionOrTypeOrDefault('capture', def, Type, returnFalse), | ||
@@ -300,3 +296,2 @@ fromDefinitionOrDefault('template', def, returnNull), | ||
getElementKeyFrom(nameOrDef), | ||
fromAnnotationOrTypeOrDefault('cache', Type, returnZero), | ||
fromAnnotationOrTypeOrDefault('capture', Type, returnFalse), | ||
@@ -334,3 +329,2 @@ fromAnnotationOrTypeOrDefault('template', Type, returnNull as () => string | Node | null), | ||
getElementKeyFrom(name), | ||
fromAnnotationOrDefinitionOrTypeOrDefault('cache', nameOrDef, Type, returnZero), | ||
fromAnnotationOrDefinitionOrTypeOrDefault('capture', nameOrDef, Type, returnFalse), | ||
@@ -407,3 +401,2 @@ fromAnnotationOrDefinitionOrTypeOrDefault('template', nameOrDef, Type, returnNull), | ||
}; | ||
const returnZero = () => 0; | ||
const returnNull = <T>(): T | null => null; | ||
@@ -410,0 +403,0 @@ const returnFalse = () => false; |
@@ -105,3 +105,3 @@ import { Scope } from '../../binding/scope'; | ||
// | ||
// since we are construction the projection (2) view based on the | ||
// since we are constructing the projection (2) view based on the | ||
// container of <my-app>, we need to pre-register all information stored | ||
@@ -170,2 +170,6 @@ // in projection (1) into the container created for the projection (2) view | ||
): void | Promise<void> { | ||
this._parentScope = parent.scope; | ||
// The following block finds the real host scope for the content of this <au-slot> | ||
// | ||
// if this <au-slot> was created by another au slot, the controller hierarchy will be like this: | ||
@@ -181,3 +185,3 @@ // C(au-slot)#1 --> C(synthetic)#1 --> C(au-slot)#2 --> C(synthetic)#2 | ||
} | ||
this._parentScope = parent.scope; | ||
const host = parent.scope.bindingContext; | ||
@@ -193,3 +197,3 @@ let outerScope: Scope; | ||
(this._outerScope = Scope.fromParent(outerScope, outerScope.bindingContext)) | ||
.overrideContext.$host = this.expose ?? this._parentScope.bindingContext; | ||
.overrideContext.$host = this.expose ?? host; | ||
} | ||
@@ -196,0 +200,0 @@ } |
@@ -300,4 +300,5 @@ import { onResolve, resolve } from '@aurelia/kernel'; | ||
break; | ||
/* istanbul ignore next */ | ||
default: | ||
throw new Error('Invalid portal insertion position'); | ||
throw createMappedError(ErrorNames.portal_invalid_insert_position, position); | ||
} | ||
@@ -304,0 +305,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { type IDisposable, onResolve, IIndexable } from '@aurelia/kernel'; | ||
import { type IDisposable, onResolve, IIndexable, resolve, all, emptyArray, IContainer } from '@aurelia/kernel'; | ||
import { | ||
@@ -30,3 +30,3 @@ BindingBehaviorExpression, | ||
import { IController } from '../../templating/controller'; | ||
import { areEqual, isArray, isPromise, baseObjectPrototype, rethrow, etIsProperty } from '../../utilities'; | ||
import { areEqual, isArray, isPromise, isMap, isSet, isNumber, rethrow, etIsProperty } from '../../utilities'; | ||
import { HydrateTemplateController, IInstruction, IteratorBindingInstruction } from '@aurelia/template-compiler'; | ||
@@ -37,2 +37,3 @@ | ||
import { ErrorNames, createMappedError } from '../../errors'; | ||
import { createInterface, singletonRegistration } from '../../utilities-di'; | ||
@@ -58,4 +59,2 @@ type Items<C extends Collection = unknown[]> = C | undefined; | ||
/** @internal */ protected static inject = [IInstruction, IExpressionParser, IRenderLocation, IController, IViewFactory]; | ||
public views: ISyntheticView[] = []; | ||
@@ -83,13 +82,9 @@ private _oldViews: ISyntheticView[] = []; | ||
/** @internal */ private readonly _location: IRenderLocation; | ||
/** @internal */ private readonly _parent: IHydratableController; | ||
/** @internal */ private readonly _factory: IViewFactory; | ||
/** @internal */ private readonly _location = resolve(IRenderLocation); | ||
/** @internal */ private readonly _parent = resolve(IController) as IHydratableController; | ||
/** @internal */ private readonly _factory = resolve(IViewFactory); | ||
/** @internal */ private readonly _resolver = resolve(IRepeatableHandlerResolver); | ||
public constructor( | ||
instruction: HydrateTemplateController, | ||
parser: IExpressionParser, | ||
location: IRenderLocation, | ||
parent: IHydratableController, | ||
factory: IViewFactory, | ||
) { | ||
public constructor() { | ||
const instruction = resolve(IInstruction) as HydrateTemplateController; | ||
const keyProp = (instruction.props[0] as IteratorBindingInstruction).props[0]; | ||
@@ -102,3 +97,3 @@ if (keyProp !== void 0) { | ||
} else if (command === 'bind') { | ||
this.key = parser.parse(value, etIsProperty); | ||
this.key = resolve(IExpressionParser).parse(value, etIsProperty); | ||
} else { | ||
@@ -111,5 +106,2 @@ throw createMappedError(ErrorNames.repeat_invalid_key_binding_command, command); | ||
} | ||
this._location = location; | ||
this._parent = parent; | ||
this._factory = factory; | ||
} | ||
@@ -156,3 +148,3 @@ | ||
return this._activateAllViews(initiator); | ||
return this._activateAllViews(initiator, this._normalizedItems ?? emptyArray); | ||
} | ||
@@ -379,3 +371,3 @@ | ||
// TODO(fkleuver): add logic to the controller that ensures correct handling of race conditions and add a variety of `if` integration tests | ||
return this._activateAllViews(null); | ||
return this._activateAllViews(null, this._normalizedItems ?? emptyArray); | ||
}, | ||
@@ -419,3 +411,4 @@ ); | ||
if (this.$controller.isActive) { | ||
newObserver = this._observer = getCollectionObserver(observingInnerItems ? innerItems : this.items); | ||
const items = observingInnerItems ? innerItems : this.items; | ||
newObserver = this._observer = this._resolver.resolve(items).getObserver?.(items); | ||
if (oldObserver !== newObserver) { | ||
@@ -433,5 +426,5 @@ oldObserver?.unsubscribe(this); | ||
private _normalizeToArray(): void { | ||
const { items } = this; | ||
const items = this.items; | ||
if (isArray(items)) { | ||
this._normalizedItems = items; | ||
this._normalizedItems = items.slice(0); | ||
return; | ||
@@ -441,3 +434,3 @@ } | ||
iterate(items, (item, index) => { | ||
this._resolver.resolve(items).iterate(items, (item, index) => { | ||
normalizedItems[index] = item; | ||
@@ -451,2 +444,3 @@ }); | ||
initiator: IHydratedController | null, | ||
$items: unknown[], | ||
): void | Promise<void> { | ||
@@ -458,8 +452,8 @@ let promises: Promise<void>[] | undefined = void 0; | ||
const { $controller, _factory, local, _location, items, _scopeMap, _forOfBinding, forOf, _hasDestructuredLocal } = this; | ||
const { $controller, _factory, local, _location, _scopeMap, _forOfBinding, forOf, _hasDestructuredLocal } = this; | ||
const parentScope = $controller.scope; | ||
const newLen = getCount(items); | ||
const newLen = $items.length; | ||
const views = this.views = Array(newLen); | ||
iterate(items, (item, i) => { | ||
$items.forEach((item, i) => { | ||
view = views[i] = _factory.create().setLocation(_location); | ||
@@ -472,3 +466,3 @@ view.nodes.unlink(); | ||
if (isPromise(ret)) { | ||
(promises ?? (promises = [])).push(ret); | ||
(promises ??= []).push(ret); | ||
} | ||
@@ -745,62 +739,150 @@ }); | ||
const toStringTag = baseObjectPrototype.toString as { | ||
call(obj: unknown): keyof '[object Array]' | '[object Map]' | '[object Set]' | '[object Number]' | '[object Null]' | '[object Undefined]'; | ||
}; | ||
type AcceptableCollection = Collection | number | null | undefined; | ||
const getCount = (result: AcceptableCollection): number => { | ||
switch (toStringTag.call(result) as string) { | ||
case '[object Array]': return (result as unknown[]).length; | ||
case '[object Map]': return (result as Map<unknown, unknown>).size; | ||
case '[object Set]': return (result as Set<unknown>).size; | ||
case '[object Number]': return result as number; | ||
case '[object Null]': return 0; | ||
case '[object Undefined]': return 0; | ||
default: throw createMappedError(ErrorNames.repeat_non_countable, result); | ||
export const IRepeatableHandlerResolver = /*@__PURE__*/ createInterface<IRepeatableHandlerResolver>( | ||
'IRepeatableHandlerResolver', | ||
x => x.singleton(RepeatableHandlerResolver) | ||
); | ||
/** | ||
* An interface describings the capabilities of a repeatable handler. | ||
*/ | ||
export interface IRepeatableHandlerResolver { | ||
resolve(value: unknown): IRepeatableHandler; | ||
} | ||
/** | ||
* The default implementation of the IRepeatableHandlerResolver interface | ||
*/ | ||
class RepeatableHandlerResolver implements IRepeatableHandlerResolver { | ||
/** @internal */ | ||
private readonly _handlers = resolve(all(IRepeatableHandler)); | ||
public resolve(value: Repeatable): IRepeatableHandler { | ||
if (_arrayHandler.handles(value)) { | ||
return _arrayHandler; | ||
} | ||
if (_setHandler.handles(value)) { | ||
return _setHandler; | ||
} | ||
if (_mapHandler.handles(value)) { | ||
return _mapHandler; | ||
} | ||
if (_numberHandler.handles(value)) { | ||
return _numberHandler; | ||
} | ||
if (_nullishHandler.handles(value)) { | ||
return _nullishHandler; | ||
} | ||
const handler = this._handlers.find(x => x.handles(value)); | ||
if (handler !== void 0) { | ||
return handler; | ||
} | ||
return _unknownHandler; | ||
} | ||
}; | ||
} | ||
const iterate = (result: AcceptableCollection, func: (item: unknown, index: number, arr: AcceptableCollection) => void): void => { | ||
switch (toStringTag.call(result) as string) { | ||
case '[object Array]': return $array(result as unknown[], func); | ||
case '[object Map]': return $map(result as Map<unknown, unknown>, func); | ||
case '[object Set]': return $set(result as Set<unknown>, func); | ||
case '[object Number]': return $number(result as number, func); | ||
case '[object Null]': return; | ||
case '[object Undefined]': return; | ||
// todo: remove this count method | ||
default: createMappedError(ErrorNames.repeat_non_iterable, result); | ||
/** | ||
* A simple implementation for handling common array like values, such as: | ||
* - HTMLCollection | ||
* - NodeList | ||
* - FileList, | ||
* - etc... | ||
*/ | ||
export class ArrayLikeHandler implements IRepeatableHandler<ArrayLike<unknown>> { | ||
public static register(c: IContainer) { | ||
c.register(singletonRegistration(IRepeatableHandler, this)); | ||
} | ||
}; | ||
const $array = (result: unknown[], func: (item: unknown, index: number, arr: Collection) => void): void => { | ||
const ii = result.length; | ||
let i = 0; | ||
for (; i < ii; ++i) { | ||
func(result[i], i, result); | ||
public handles(value: NonNullable<unknown>): boolean { | ||
return 'length' in value && isNumber(value.length); | ||
} | ||
}; | ||
const $map = (result: Map<unknown, unknown>, func: (item: unknown, index: number, arr: Collection) => void): void => { | ||
let i = -0; | ||
let entry: [unknown, unknown] | undefined; | ||
for (entry of result.entries()) { | ||
func(entry, i++, result); | ||
public iterate(items: ArrayLike<unknown>, func: (item: unknown, index: number, arr: ArrayLike<unknown>) => void): void { | ||
for (let i = 0, ii = items.length; i < ii; ++i) { | ||
func(items[i], i, items); | ||
} | ||
} | ||
} | ||
/** | ||
* An interface describing a repeatable value handler | ||
*/ | ||
export const IRepeatableHandler = /*@__PURE__*/ createInterface<IRepeatableHandler>('IRepeatableHandler'); | ||
export interface IRepeatableHandler<TValue extends Repeatable = Repeatable> { | ||
handles(value: unknown): boolean; | ||
getObserver?(value: TValue): CollectionObserver | undefined; | ||
iterate(value: TValue, func: (item: unknown, index: number, value: TValue) => void): void; | ||
// getCount(items: TValue): number; | ||
} | ||
const _arrayHandler: IRepeatableHandler<unknown[]> = { | ||
handles: isArray, | ||
getObserver: getCollectionObserver, | ||
/* istanbul ignore next */ | ||
iterate(value, func): void { | ||
const ii = value.length; | ||
let i = 0; | ||
for (; i < ii; ++i) { | ||
func(value[i], i, value); | ||
} | ||
}, | ||
// getCount: items => items.length, | ||
}; | ||
const $set = (result: Set<unknown>, func: (item: unknown, index: number, arr: Collection) => void): void => { | ||
let i = 0; | ||
let key: unknown; | ||
for (key of result.keys()) { | ||
func(key, i++, result); | ||
} | ||
const _setHandler: IRepeatableHandler<Set<unknown>> = { | ||
handles: isSet, | ||
getObserver: getCollectionObserver, | ||
iterate(value, func): void { | ||
let i = 0; | ||
let key: unknown; | ||
for (key of value.keys()) { | ||
func(key, i++, value); | ||
} | ||
}, | ||
// getCount: s => s.size, | ||
}; | ||
const $number = (result: number, func: (item: number, index: number, arr: number) => void): void => { | ||
let i = 0; | ||
for (; i < result; ++i) { | ||
func(i, i, result); | ||
} | ||
const _mapHandler: IRepeatableHandler<Map<unknown, unknown>> = { | ||
handles: isMap, | ||
getObserver: getCollectionObserver, | ||
iterate(value, func): void { | ||
let i = 0; | ||
let entry: [unknown, unknown] | undefined; | ||
for (entry of value.entries()) { | ||
func(entry, i++, value); | ||
} | ||
}, | ||
// getCount: s => s.size, | ||
}; | ||
const _numberHandler: IRepeatableHandler<number> = { | ||
handles: isNumber, | ||
iterate(value, func): void { | ||
let i = 0; | ||
for (; i < value; ++i) { | ||
func(i, i, value); | ||
} | ||
}, | ||
// getCount: v => v, | ||
}; | ||
const _nullishHandler: IRepeatableHandler<null | undefined> = { | ||
handles: v => v == null, | ||
iterate() {/* do nothing */}, | ||
// getCount: () => 0, | ||
}; | ||
const _unknownHandler: IRepeatableHandler = { | ||
handles(_value: unknown): boolean { | ||
// Should only return as an explicit last fallback | ||
return false; | ||
}, | ||
iterate(value: Repeatable, _func: (item: unknown, index: number, value: Repeatable) => void): void { | ||
throw createMappedError(ErrorNames.repeat_non_iterable, value); | ||
}, | ||
// getCount: () => 0, | ||
}; | ||
type Repeatable = Collection | ArrayLike<unknown> | number | null | undefined; | ||
const getKeyValue = ( | ||
@@ -807,0 +889,0 @@ keyMap: Map<unknown, unknown>, |
@@ -54,2 +54,4 @@ import { IContainer, resolve } from '@aurelia/kernel'; | ||
private readonly _empty: INodeSequence; | ||
/** @internal */ | ||
private readonly _marker: Node; | ||
@@ -71,6 +73,7 @@ public get renderers(): Record<string, IRenderer> { | ||
const ctn = this._ctn = resolve(IContainer).root; | ||
this._platform = ctn.get(IPlatform); | ||
const p = this._platform = ctn.get(IPlatform); | ||
this._exprParser= ctn.get(IExpressionParser); | ||
this._observerLocator = ctn.get(IObserverLocator); | ||
this._empty = new FragmentNodeSequence(this._platform, this._platform.document.createDocumentFragment()); | ||
this._marker = p.document.createElement('au-m'); | ||
this._empty = new FragmentNodeSequence(p, p.document.createDocumentFragment()); | ||
} | ||
@@ -197,7 +200,2 @@ | ||
/** @internal */ | ||
private _marker() { | ||
return this._platform.document.createElement('au-m'); | ||
} | ||
/** @internal */ | ||
private _transformMarker(fragment: Node | null) { | ||
@@ -207,40 +205,54 @@ if (fragment == null) { | ||
} | ||
let parent: Node = fragment; | ||
let current: Node | null | undefined = parent.firstChild; | ||
let next: Node | null | undefined = null; | ||
while (current != null) { | ||
if (current.nodeType === 8 && current.nodeValue === 'au*') { | ||
next = current.nextSibling!; | ||
parent.removeChild(current); | ||
parent.insertBefore(this._marker(), next); | ||
if (next.nodeType === 8) { | ||
current = next.nextSibling; | ||
// todo: maybe validate? | ||
} else { | ||
current = next; | ||
} | ||
const walker = this._platform.document.createTreeWalker(fragment, /* NodeFilter.SHOW_COMMENT */ 128); | ||
let currentNode: Node | null; | ||
while ((currentNode = walker.nextNode()) != null) { | ||
if (currentNode.nodeValue === 'au*') { | ||
currentNode.parentNode!.replaceChild(walker.currentNode = this._marker.cloneNode(), currentNode); | ||
} | ||
next = current?.firstChild; | ||
if (next == null) { | ||
next = current?.nextSibling; | ||
if (next == null) { | ||
current = parent.nextSibling; | ||
parent = parent.parentNode!; | ||
// needs to keep walking up all the way til a valid next node | ||
while (current == null && parent != null) { | ||
current = parent.nextSibling; | ||
parent = parent.parentNode!; | ||
} | ||
} else { | ||
current = next; | ||
} | ||
} else { | ||
parent = current!; | ||
current = next; | ||
} | ||
} | ||
return fragment; | ||
// below is a homemade "comment query selector that seems to be as efficient as the TreeWalker | ||
// also it works with very minimal set of APIs (.nextSibling, .parentNode, .insertBefore, .removeChild) | ||
// while TreeWalker maynot be always available in platform that we may potentially support | ||
// | ||
// so leaving it here just in case we need it again, TreeWalker is slightly less code | ||
// let parent: Node = fragment; | ||
// let current: Node | null | undefined = parent.firstChild; | ||
// let next: Node | null | undefined = null; | ||
// while (current != null) { | ||
// if (current.nodeType === 8 && current.nodeValue === 'au*') { | ||
// next = current.nextSibling!; | ||
// parent.removeChild(current); | ||
// parent.insertBefore(this._marker(), next); | ||
// if (next.nodeType === 8) { | ||
// current = next.nextSibling; | ||
// // todo: maybe validate? | ||
// } else { | ||
// current = next; | ||
// } | ||
// } | ||
// next = current?.firstChild; | ||
// if (next == null) { | ||
// next = current?.nextSibling; | ||
// if (next == null) { | ||
// current = parent.nextSibling; | ||
// parent = parent.parentNode!; | ||
// // needs to keep walking up all the way til a valid next node | ||
// while (current == null && parent != null) { | ||
// current = parent.nextSibling; | ||
// parent = parent.parentNode!; | ||
// } | ||
// } else { | ||
// current = next; | ||
// } | ||
// } else { | ||
// parent = current!; | ||
// current = next; | ||
// } | ||
// } | ||
// return fragment; | ||
} | ||
} |
import { type IRenderLocation } from './dom'; | ||
import { ErrorNames, createMappedError } from './errors'; | ||
import { type IPlatform } from './platform'; | ||
/** @internal */ | ||
export const auLocationStart = 'au-start'; | ||
/** @internal */ | ||
export const auLocationEnd = 'au-end'; | ||
export const createLocation = /*@__PURE__*/ (() => { | ||
const createComment = (p: IPlatform, text: string) => p.document.createComment(text); | ||
return (p: IPlatform) => { | ||
const locationEnd = createComment(p, 'au-end') as IRenderLocation; | ||
locationEnd.$start = createComment(p, 'au-start') as IRenderLocation; | ||
/** @internal */ | ||
export const createElement | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
= <K extends string>(p: IPlatform, name: K): K extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[K] : HTMLElement => p.document.createElement(name) as any; | ||
return locationEnd; | ||
}; | ||
})(); | ||
/** @internal */ | ||
export const createComment = (p: IPlatform, text: string) => p.document.createComment(text); | ||
/** @internal */ | ||
export const createLocation = (p: IPlatform) => { | ||
const locationEnd = createComment(p, auLocationEnd) as IRenderLocation; | ||
locationEnd.$start = createComment(p, auLocationStart) as IRenderLocation; | ||
return locationEnd; | ||
}; | ||
/** @internal */ | ||
export const createText = (p: IPlatform, text: string) => p.document.createTextNode(text); | ||
/** @internal */ | ||
export const insertBefore = <T extends Node>(parent: Node, newChildNode: T, target: Node | null) => { | ||
return parent.insertBefore(newChildNode, target); | ||
}; | ||
/** @internal */ | ||
export const insertManyBefore = (parent: Node | null, target: Node | null, newChildNodes: ArrayLike<Node>) => { | ||
@@ -48,53 +29,2 @@ if (parent === null) { | ||
/** @internal */ | ||
export const getPreviousSibling = (node: Node) => node.previousSibling; | ||
/** @internal */ | ||
export const appendChild = <T extends Node>(parent: Node, child: T) => { | ||
return parent.appendChild(child); | ||
}; | ||
/** @internal */ | ||
export const appendToTemplate = <T extends Node>(parent: HTMLTemplateElement, child: T) => { | ||
return parent.content.appendChild(child); | ||
}; | ||
/** @internal */ | ||
export const appendManyToTemplate = (parent: HTMLTemplateElement, children: ArrayLike<Node>) => { | ||
const ii = children.length; | ||
let i = 0; | ||
while (ii > i) { | ||
parent.content.appendChild(children[i]); | ||
++i; | ||
} | ||
}; | ||
/** @internal */ | ||
export const markerToTarget = (el: Element) => { | ||
const nextSibling = el.nextSibling; | ||
let locationStart: Comment; | ||
let locationEnd: IRenderLocation; | ||
if (nextSibling == null) { | ||
throw createMappedError(ErrorNames.marker_malformed); | ||
} | ||
if (nextSibling.nodeType === /* Comment */8) { | ||
if (nextSibling.textContent === 'au-start') { | ||
locationStart = nextSibling as Comment; | ||
if ((locationEnd = locationStart.nextSibling! as IRenderLocation) == null) { | ||
throw createMappedError(ErrorNames.marker_malformed); | ||
} | ||
el.remove(); | ||
locationEnd.$start = locationStart; | ||
return locationEnd; | ||
} else { | ||
throw createMappedError(ErrorNames.marker_malformed); | ||
} | ||
} | ||
el.remove(); | ||
return nextSibling; | ||
}; | ||
/** @internal */ | ||
export const createMutationObserver = (node: Node, callback: MutationCallback) => new node.ownerDocument!.defaultView!.MutationObserver(callback); | ||
@@ -104,4 +34,1 @@ | ||
export const isElement = (node: Node): node is Element => node.nodeType === 1; | ||
/** @internal */ | ||
export const isTextNode = (node: Node): node is Text => node.nodeType === 3; |
@@ -45,2 +45,4 @@ import { AccessorType, type ISubscriber } from '@aurelia/runtime'; | ||
/** @internal */ export const isArray = <T>(v: unknown): v is T[] => v instanceof Array; | ||
/** @internal */ export const isSet = <T>(v: unknown): v is Set<T> => v instanceof Set; | ||
/** @internal */ export const isMap = <T, K>(v: unknown): v is Map<T, K> => v instanceof Map; | ||
@@ -47,0 +49,0 @@ // eslint-disable-next-line @typescript-eslint/ban-types |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4266247
77565
+ Added@aurelia/expression-parser@2.0.1-dev.202405110109(transitive)
+ Added@aurelia/kernel@2.0.1-dev.202405110109(transitive)
+ Added@aurelia/metadata@2.0.1-dev.202405110109(transitive)
+ Added@aurelia/platform@2.0.1-dev.202405110109(transitive)
+ Added@aurelia/platform-browser@2.0.1-dev.202405110109(transitive)
+ Added@aurelia/runtime@2.0.1-dev.202405110109(transitive)
+ Added@aurelia/template-compiler@2.0.1-dev.202405110109(transitive)
- Removed@aurelia/expression-parser@2.0.1-dev.202405031042(transitive)
- Removed@aurelia/kernel@2.0.1-dev.202405031042(transitive)
- Removed@aurelia/metadata@2.0.1-dev.202405031042(transitive)
- Removed@aurelia/platform@2.0.1-dev.202405031042(transitive)
- Removed@aurelia/platform-browser@2.0.1-dev.202405031042(transitive)
- Removed@aurelia/runtime@2.0.1-dev.202405031042(transitive)
- Removed@aurelia/template-compiler@2.0.1-dev.202405031042(transitive)