@aurelia/runtime-html
Advanced tools
Comparing version 2.0.0-beta.18 to 2.0.0-beta.19
import { type IServiceLocator } from '@aurelia/kernel'; | ||
import { IAstEvaluator } from '../ast.eval'; | ||
import type { ITask, TaskQueue } from '@aurelia/platform'; | ||
import type { ICollectionSubscriber, IObserverLocator, IObserverLocatorBasedConnectable, ISubscriber } from '@aurelia/runtime'; | ||
import type { IAccessor, ICollectionSubscriber, IObserverLocator, IObserverLocatorBasedConnectable, ISubscriber } from '@aurelia/runtime'; | ||
import { type Scope } from './scope'; | ||
@@ -21,2 +21,6 @@ import type { IBinding, BindingMode, IBindingController } from './interfaces-bindings'; | ||
unbind(): void; | ||
/** | ||
* Start using a given observer to update the target | ||
*/ | ||
useAccessor(accessor: IAccessor): void; | ||
} | ||
@@ -23,0 +27,0 @@ export interface InterpolationPartBinding extends IAstEvaluator, IObserverLocatorBasedConnectable, IServiceLocator { |
@@ -111,3 +111,3 @@ import { IContainer } from '@aurelia/kernel'; | ||
readonly target: "rf"; | ||
render(renderingCtrl: import(".").IHydratableController<import(".").IViewModel>, target: import(".").IController<import(".").IViewModel>, instruction: import("@aurelia/template-compiler").InterpolationInstruction, 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 | import(".").IController<import(".").IViewModel>, instruction: import("@aurelia/template-compiler").InterpolationInstruction, platform: import("./platform").IPlatform, exprParser: import("@aurelia/expression-parser").IExpressionParser<import("@aurelia/expression-parser").CustomExpression>, observerLocator: import("@aurelia/runtime").IObserverLocator): void; | ||
}; | ||
@@ -114,0 +114,0 @@ } | { |
@@ -0,1 +1,2 @@ | ||
import { IContainer } from '@aurelia/kernel'; | ||
import { IPlatform } from './platform'; | ||
@@ -14,2 +15,6 @@ import type { IHydratedController } from './templating/controller'; | ||
export declare const IEventTarget: import("@aurelia/kernel").InterfaceSymbol<EventTarget>; | ||
/** | ||
* An interface describing a marker. | ||
* Components can use this to anchor where their content should be rendered in place of a host element. | ||
*/ | ||
export declare const IRenderLocation: import("@aurelia/kernel").InterfaceSymbol<IRenderLocation<ChildNode>>; | ||
@@ -19,5 +24,7 @@ export type IRenderLocation<T extends ChildNode = ChildNode> = T & { | ||
}; | ||
export declare const ICssModulesMapping: import("@aurelia/kernel").InterfaceSymbol<Record<string, string>>; | ||
/** | ||
* Represents a DocumentFragment | ||
* Represents a DocumentFragment with a memory of what it has. | ||
* This is known as many names, a live fragment for example. | ||
* | ||
* Relevant discussion at https://github.com/whatwg/dom/issues/736 | ||
*/ | ||
@@ -200,2 +207,6 @@ export interface INodeSequence<T extends INode = INode> { | ||
} | ||
/** | ||
* An utility to register a host node with the container with all the commonly used keys. | ||
*/ | ||
export declare const registerHostNode: (container: IContainer, host: INode | null, platform?: IPlatform) => IContainer; | ||
//# sourceMappingURL=dom.d.ts.map |
@@ -33,3 +33,3 @@ export { type IAstEvaluator, astAssign, astBind, astEvaluate, astUnbind, } from './ast.eval'; | ||
export { DataAttributeAccessor, } from './observation/data-attribute-accessor'; | ||
export { NodeObserverLocator, type INodeObserverConfig, type INodeObserverConstructor as IHtmlObserverConstructor, } from './observation/observer-locator'; | ||
export { NodeObserverLocator, type INodeObserverConfig, type INodeObserverConstructor, } from './observation/observer-locator'; | ||
export { type ISelectElement, type IOptionElement, SelectValueObserver } from './observation/select-value-observer'; | ||
@@ -65,3 +65,3 @@ export { StyleAttributeAccessor } from './observation/style-attribute-accessor'; | ||
export { ViewFactory, IViewFactory, } from './templating/view'; | ||
export { INode, IEventTarget, IRenderLocation, type INodeSequence, FragmentNodeSequence, IHistory, IWindow, ILocation, getEffectiveParentNode, setEffectiveParentNode, convertToRenderLocation, isRenderLocation, getRef, setRef, } from './dom'; | ||
export { INode, IEventTarget, IRenderLocation, type INodeSequence, FragmentNodeSequence, IHistory, IWindow, ILocation, getEffectiveParentNode, setEffectiveParentNode, convertToRenderLocation, isRenderLocation, getRef, setRef, registerHostNode, } from './dom'; | ||
export { IPlatform, } from './platform'; | ||
@@ -68,0 +68,0 @@ export { CSSModulesProcessorRegistry, cssModules, ShadowDOMRegistry, shadowCSS, StyleConfiguration, IShadowDOMStyleFactory, type IShadowDOMConfiguration, AdoptedStyleSheetsStyles, StyleElementStyles, IShadowDOMStyles, IShadowDOMGlobalStyles, } from './templating/styles'; |
@@ -1,7 +0,10 @@ | ||
import type { AccessorType, IAccessor } from '@aurelia/runtime'; | ||
import type { AccessorType, IAccessor, IObserver } from '@aurelia/runtime'; | ||
export interface ClassAttributeAccessor extends IObserver { | ||
} | ||
export declare class ClassAttributeAccessor implements IAccessor { | ||
readonly obj: HTMLElement; | ||
readonly mapping: Record<string, string>; | ||
get doNotCache(): true; | ||
type: AccessorType; | ||
constructor(obj: HTMLElement); | ||
constructor(obj: HTMLElement, mapping?: Record<string, string>); | ||
getValue(): unknown; | ||
@@ -8,0 +11,0 @@ setValue(newValue: unknown): void; |
@@ -69,3 +69,3 @@ import { type Constructable } from '@aurelia/kernel'; | ||
readonly target: "rf"; | ||
render(renderingCtrl: IHydratableController, target: IController, instruction: InterpolationInstruction, platform: IPlatform, exprParser: IExpressionParser, observerLocator: IObserverLocator): void; | ||
render(renderingCtrl: IHydratableController, target: IController | HTMLElement, instruction: InterpolationInstruction, platform: IPlatform, exprParser: IExpressionParser, observerLocator: IObserverLocator): void; | ||
}; | ||
@@ -72,0 +72,0 @@ }; |
@@ -88,2 +88,3 @@ import { Key } from '@aurelia/kernel'; | ||
readonly containerStrategy: 'reuse' | 'new'; | ||
static warnDuplicate: boolean; | ||
get type(): 'custom-attribute'; | ||
@@ -90,0 +91,0 @@ private constructor(); |
import { type IServiceLocator } from '@aurelia/kernel'; | ||
import { type ISubscriberCollection } from '@aurelia/runtime'; | ||
import { type ICustomElementViewModel, type ICustomElementController } from './controller'; | ||
import type { INode } from '../dom'; | ||
import { type ICustomElementViewModel } from './controller'; | ||
import { IBinding } from '../binding/interfaces-bindings'; | ||
export type PartialChildrenDefinition = { | ||
/** | ||
* An interface describing options to observe the children elements of a custom element host | ||
*/ | ||
export type PartialChildrenDefinition<TQuery extends string = string> = { | ||
query?: TQuery; | ||
callback?: PropertyKey; | ||
name?: PropertyKey; | ||
options?: MutationObserverInit; | ||
query?: (controller: ICustomElementController) => ArrayLike<Node>; | ||
filter?: (node: Node, controller?: ICustomElementController | null, viewModel?: ICustomElementViewModel) => boolean; | ||
map?: (node: Node, controller?: ICustomElementController | null, viewModel?: ICustomElementViewModel) => unknown; | ||
filter?: (node: TQuery extends '$all' ? Node : HTMLElement, viewModel: ICustomElementViewModel | null) => boolean; | ||
map?: (node: TQuery extends '$all' ? Node : HTMLElement, viewModel: ICustomElementViewModel | null) => unknown; | ||
}; | ||
@@ -19,7 +20,8 @@ /** | ||
*/ | ||
export declare function children<TThis, TValue>(config?: PartialChildrenDefinition): (target: undefined, context: ClassFieldDecoratorContext<TThis, TValue>) => void; | ||
export declare function children<TThis, TValue, TQuery extends string>(config?: PartialChildrenDefinition<TQuery>): (target: undefined, context: ClassFieldDecoratorContext<TThis, TValue>) => void; | ||
/** | ||
* Decorator: Specifies an array property on a class that synchronizes its items with child content nodes of the element. | ||
* | ||
* @param selector - The CSS element selector for filtering children | ||
* @param selector - The CSS element selector for filtering children. Use `$all` to select everything including non element nodes. | ||
* If nothing is provided, it defaults to `*`, which means all elements | ||
*/ | ||
@@ -34,2 +36,5 @@ export declare function children<TThis, TValue>(selector: string): (target: undefined, context: ClassFieldDecoratorContext<TThis, TValue>) => void; | ||
export declare function children<TThis, TValue>(target: undefined, context: ClassFieldDecoratorContext<TThis, TValue>): void; | ||
export declare namespace children { | ||
var mixed: boolean; | ||
} | ||
export interface ChildrenBinding extends ISubscriberCollection { | ||
@@ -43,3 +48,3 @@ } | ||
readonly obj: ICustomElementViewModel; | ||
constructor(controller: ICustomElementController, obj: ICustomElementViewModel, callback: undefined | (() => void), query?: (controller: ICustomElementController<ICustomElementViewModel>) => ArrayLike<INode>, filter?: (node: INode, controller?: ICustomElementController<ICustomElementViewModel> | null | undefined, viewModel?: any) => boolean, map?: (node: INode, controller?: ICustomElementController<ICustomElementViewModel> | null | undefined, viewModel?: any) => any, options?: MutationObserverInit); | ||
constructor(host: HTMLElement, obj: ICustomElementViewModel, callback: undefined | (() => void), query: string, filter?: (node: Node, viewModel: ICustomElementViewModel | null) => boolean, map?: (node: Node, viewModel: ICustomElementViewModel | null) => unknown); | ||
getValue(): unknown[]; | ||
@@ -46,0 +51,0 @@ setValue(_value: unknown): void; |
@@ -7,6 +7,6 @@ import { IContainer } from '@aurelia/kernel'; | ||
* | ||
* CSS registry alters the way class attribute works instead. | ||
* - CSS registry alters the way class bindings work via altering templates and register interfaces that will alter bindings to class attribute. | ||
* | ||
* Shadow dom registry regisiters some interfaces with the custom element container to handle shadow dom styles. | ||
* abtraction summary: | ||
* - Shadow dom registry regisiters some interfaces with the custom element container to handle shadow dom styles. | ||
* Shadow DOM abtraction summary: | ||
* CSS registry ---(register)---> IShadowDOMStyleFactory ---(createStyles)---> IShadowDOMStyles ---(applyTo)---> ShadowRoot | ||
@@ -13,0 +13,0 @@ */ |
{ | ||
"name": "@aurelia/runtime-html", | ||
"version": "2.0.0-beta.18", | ||
"version": "2.0.0-beta.19", | ||
"main": "dist/cjs/index.cjs", | ||
@@ -57,9 +57,9 @@ "module": "dist/esm/index.mjs", | ||
"dependencies": { | ||
"@aurelia/kernel": "2.0.0-beta.18", | ||
"@aurelia/metadata": "2.0.0-beta.18", | ||
"@aurelia/platform": "2.0.0-beta.18", | ||
"@aurelia/platform-browser": "2.0.0-beta.18", | ||
"@aurelia/runtime": "2.0.0-beta.18", | ||
"@aurelia/expression-parser": "2.0.0-beta.18", | ||
"@aurelia/template-compiler": "2.0.0-beta.18" | ||
"@aurelia/kernel": "2.0.0-beta.19", | ||
"@aurelia/metadata": "2.0.0-beta.19", | ||
"@aurelia/platform": "2.0.0-beta.19", | ||
"@aurelia/platform-browser": "2.0.0-beta.19", | ||
"@aurelia/runtime": "2.0.0-beta.19", | ||
"@aurelia/expression-parser": "2.0.0-beta.19", | ||
"@aurelia/template-compiler": "2.0.0-beta.19" | ||
}, | ||
@@ -66,0 +66,0 @@ "devDependencies": { |
@@ -82,3 +82,3 @@ import { BrowserPlatform } from '@aurelia/platform-browser'; | ||
registerResolver(container, IEventTarget, new InstanceProvider<IEventTarget>('IEventTarget', host)); | ||
registerHostNode(container, this.platform = this._createPlatform(container, host), host); | ||
registerHostNode(container, host, this.platform = this._createPlatform(container, host)); | ||
@@ -85,0 +85,0 @@ this._hydratePromise = onResolve(this._runAppTasks('creating'), () => { |
@@ -18,2 +18,3 @@ import { type IServiceLocator, isArray } from '@aurelia/kernel'; | ||
AccessorOrObserver, | ||
IAccessor, | ||
ICollectionSubscriber, | ||
@@ -38,4 +39,4 @@ IObserverLocator, | ||
// value converters and binding behaviors. | ||
// Each expression represents one ${interpolation}, and for each we create a child TextBinding unless there is only one, | ||
// in which case the renderer will create the TextBinding directly | ||
// Each expression represents one ${interpolation}, and for each we create a child InterpolationPartBinding | ||
export interface InterpolationBinding extends IObserverLocatorBasedConnectable, IAstEvaluator, IServiceLocator {} | ||
@@ -51,3 +52,3 @@ export class InterpolationBinding implements IBinding, ISubscriber, ICollectionSubscriber { | ||
/** @internal */ | ||
private readonly _targetObserver: AccessorOrObserver; | ||
private _targetObserver: AccessorOrObserver; | ||
@@ -172,2 +173,9 @@ /** @internal */ | ||
} | ||
/** | ||
* Start using a given observer to update the target | ||
*/ | ||
public useAccessor(accessor: IAccessor): void { | ||
this._targetObserver = accessor; | ||
} | ||
} | ||
@@ -174,0 +182,0 @@ |
@@ -261,2 +261,38 @@ import { type IsBindingBehavior } from '@aurelia/expression-parser'; | ||
/** | ||
* A generic event handler that can be used for any event type | ||
*/ | ||
class ModifiedEventHandler implements IModifiedEventHandlerCreator { | ||
public static register(c: IContainer) { | ||
c.register(singletonRegistration(IModifiedEventHandlerCreator, ModifiedEventHandler)); | ||
} | ||
public readonly type = ['$ALL']; | ||
public getHandler(modifier: string): IModifiedEventHandler { | ||
const modifiers = modifier.split(/[:+.]/); | ||
return ((event: Event) => { | ||
let prevent = false; | ||
let stop = false; | ||
let mod: string; | ||
for (mod of modifiers) { | ||
switch (mod) { | ||
case 'prevent': prevent = true; continue; | ||
case 'stop': stop = true; continue; | ||
} | ||
if (__DEV__) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`Modifier '${mod}' is not supported for event "${event.type}".`); | ||
} | ||
} | ||
if (prevent) event.preventDefault(); | ||
if (stop) event.stopPropagation(); | ||
return true; | ||
}) as IModifiedEventHandler; | ||
} | ||
} | ||
export interface IEventModifier { | ||
@@ -289,3 +325,3 @@ getHandler(type: string, modifier: string | null): IModifiedEventHandler | null; | ||
public getHandler(type: string, modifier: string | null): IModifiedEventHandler | null { | ||
return isString(modifier) ? this._reg[type]?.getHandler(modifier) ?? null : null; | ||
return isString(modifier) ? (this._reg[type] ?? this._reg.$ALL)?.getHandler(modifier) ?? null : null; | ||
} | ||
@@ -300,4 +336,5 @@ } | ||
ModifiedKeyboardEventHandler, | ||
ModifiedEventHandler, | ||
); | ||
} | ||
}; |
@@ -1,2 +0,2 @@ | ||
import { IContainer, InstanceProvider, type Writable } from '@aurelia/kernel'; | ||
import { IContainer, IResolver, InstanceProvider, emptyArray, type Writable } from '@aurelia/kernel'; | ||
import { IAppRoot } from './app-root'; | ||
@@ -34,2 +34,6 @@ import { IPlatform } from './platform'; | ||
/** | ||
* An interface describing a marker. | ||
* Components can use this to anchor where their content should be rendered in place of a host element. | ||
*/ | ||
export const IRenderLocation = /*@__PURE__*/createInterface<IRenderLocation>('IRenderLocation'); | ||
@@ -40,6 +44,20 @@ export type IRenderLocation<T extends ChildNode = ChildNode> = T & { | ||
export const ICssModulesMapping = /*@__PURE__*/createInterface<Record<string, string>>('CssModules'); | ||
/** @internal */ | ||
export const ICssClassMapping = /*@__PURE__*/createInterface<Record<string, string>>('ICssClassMapping'); | ||
/** @internal */ | ||
export const cssMappings: IResolver<Record<string, string>[]> = { | ||
$isResolver: true, | ||
resolve(_, requestor) { | ||
if (requestor.has(ICssClassMapping, false)) { | ||
return requestor.getAll(ICssClassMapping); | ||
} | ||
return emptyArray; | ||
} | ||
}; | ||
/** | ||
* Represents a DocumentFragment | ||
* Represents a DocumentFragment with a memory of what it has. | ||
* This is known as many names, a live fragment for example. | ||
* | ||
* Relevant discussion at https://github.com/whatwg/dom/issues/736 | ||
*/ | ||
@@ -509,4 +527,6 @@ export interface INodeSequence<T extends INode = INode> { | ||
/** @internal */ | ||
export const registerHostNode = (container: IContainer, platform: IPlatform, host: INode | null) => { | ||
/** | ||
* An utility to register a host node with the container with all the commonly used keys. | ||
*/ | ||
export const registerHostNode = (container: IContainer, host: INode | null, platform = container.get(IPlatform)) => { | ||
registerResolver( | ||
@@ -513,0 +533,0 @@ container, |
@@ -128,2 +128,3 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
slotted_decorator_invalid_usage = 9990, | ||
children_invalid_query = 9989, | ||
} | ||
@@ -149,4 +150,4 @@ _END_CONST_ENUM(); | ||
[ErrorNames.value_converter_def_not_found]: `No value converter definition found for type {{0:name}}`, | ||
[ErrorNames.element_existed]: `Element {{0}} has already been registered.`, | ||
[ErrorNames.attribute_existed]: `Attribute {{0}} has already been registered.`, | ||
[ErrorNames.element_existed]: `Element "{{0}}" has already been registered.`, | ||
[ErrorNames.attribute_existed]: `Attribute "{{0}}" has already been registered.`, | ||
[ErrorNames.value_converter_existed]: `Value converter {{0}} has already been registered.`, | ||
@@ -253,2 +254,3 @@ [ErrorNames.binding_behavior_existed]: `Binding behavior {{0}} has already been registered.`, | ||
[ErrorNames.slotted_decorator_invalid_usage]: `Invalid @slotted usage. @slotted decorator can only be used on a field`, | ||
[ErrorNames.children_invalid_query]: `Invalid query selector. Only selectors with alpha-numeric characters, or $all are allowed. Got {{0}} instead.` | ||
}; | ||
@@ -255,0 +257,0 @@ |
@@ -169,3 +169,3 @@ export { | ||
type INodeObserverConfig, | ||
type INodeObserverConstructor as IHtmlObserverConstructor, | ||
type INodeObserverConstructor, | ||
} from './observation/observer-locator'; | ||
@@ -384,2 +384,3 @@ export { | ||
setRef, | ||
registerHostNode, | ||
} from './dom'; | ||
@@ -386,0 +387,0 @@ export { |
import { emptyArray, isArray, isString } from '@aurelia/kernel'; | ||
import { atLayout, atNode } from '../utilities'; | ||
import type { AccessorType, IAccessor } from '@aurelia/runtime'; | ||
import type { AccessorType, IAccessor, IObserver } from '@aurelia/runtime'; | ||
import { mixinNoopSubscribable } from './observation-utils'; | ||
export interface ClassAttributeAccessor extends IObserver {} | ||
export class ClassAttributeAccessor implements IAccessor { | ||
@@ -25,2 +26,3 @@ static { | ||
public readonly obj: HTMLElement, | ||
public readonly mapping: Record<string, string> = {} | ||
) { | ||
@@ -54,2 +56,4 @@ } | ||
name = classesToAdd[i]; | ||
name = this.mapping[name] || name; | ||
if (name.length === 0) { | ||
@@ -69,2 +73,3 @@ continue; | ||
for (name in nameIndex) { | ||
name = this.mapping[name] || name; | ||
if (nameIndex[name] === version) { | ||
@@ -71,0 +76,0 @@ continue; |
@@ -30,3 +30,3 @@ import { | ||
import { CustomAttribute, CustomAttributeDefinition, findAttributeControllerFor } from './resources/custom-attribute'; | ||
import { convertToRenderLocation, IRenderLocation, INode, setRef, ICssModulesMapping, registerHostNode } from './dom'; | ||
import { convertToRenderLocation, IRenderLocation, INode, setRef, ICssClassMapping, registerHostNode } from './dom'; | ||
import { Controller, ICustomElementController, ICustomElementViewModel, IController, ICustomAttributeViewModel, IHydrationContext } from './templating/controller'; | ||
@@ -67,2 +67,4 @@ import { IPlatform } from './platform'; | ||
} from '@aurelia/template-compiler'; | ||
import { ClassAttributeAccessor } from './observation/class-attribute-accessor'; | ||
import { fromHydrationContext } from './resources/resolvers'; | ||
@@ -446,3 +448,3 @@ /** | ||
renderingCtrl: IHydratableController, | ||
target: IController, | ||
target: IController | HTMLElement, | ||
instruction: InterpolationInstruction, | ||
@@ -453,5 +455,6 @@ platform: IPlatform, | ||
): void { | ||
renderingCtrl.addBinding(new InterpolationBinding( | ||
const container = renderingCtrl.container; | ||
const binding = new InterpolationBinding( | ||
renderingCtrl, | ||
renderingCtrl.container, | ||
container, | ||
observerLocator, | ||
@@ -462,4 +465,9 @@ platform.domQueue, | ||
instruction.to, | ||
toView, | ||
)); | ||
toView | ||
); | ||
if (instruction.to === 'class' && (binding.target as Node).nodeType > 0) { | ||
const cssMapping = container.get(fromHydrationContext(ICssClassMapping)); | ||
binding.useAccessor(new ClassAttributeAccessor(binding.target as HTMLElement, cssMapping)); | ||
} | ||
renderingCtrl.addBinding(binding); | ||
} | ||
@@ -481,5 +489,6 @@ }, null!); | ||
): void { | ||
renderingCtrl.addBinding(new PropertyBinding( | ||
const container = renderingCtrl.container; | ||
const binding = new PropertyBinding( | ||
renderingCtrl, | ||
renderingCtrl.container, | ||
container, | ||
observerLocator, | ||
@@ -490,4 +499,9 @@ platform.domQueue, | ||
instruction.to, | ||
instruction.mode, | ||
)); | ||
instruction.mode | ||
); | ||
if (instruction.to === 'class' && (binding.target as Node).nodeType > 0) { | ||
const cssMapping = container.get(fromHydrationContext(ICssClassMapping)); | ||
binding.useTargetObserver(new ClassAttributeAccessor(binding.target as HTMLElement, cssMapping)); | ||
} | ||
renderingCtrl.addBinding(binding); | ||
} | ||
@@ -716,4 +730,4 @@ }, null!); | ||
const classMapping = | ||
container.has(ICssModulesMapping, false) | ||
? container.get(ICssModulesMapping) | ||
container.has(ICssClassMapping, false) | ||
? container.get(ICssClassMapping) | ||
: null; | ||
@@ -828,3 +842,3 @@ renderingCtrl.addBinding(new AttributeBinding( | ||
registerHostNode(ctn, p, host); | ||
registerHostNode(ctn, host, p); | ||
registerResolver(ctn, IController, new InstanceProvider(controllerProviderName, renderingCtrl)); | ||
@@ -889,3 +903,3 @@ registerResolver(ctn, IInstruction, new InstanceProvider(instructionProviderName, instruction)); | ||
const ctn = renderingCtrl.container.createChild(); | ||
registerHostNode(ctn, p, host); | ||
registerHostNode(ctn, host, p); | ||
registerResolver(ctn, IController, new InstanceProvider(controllerProviderName, renderingCtrl)); | ||
@@ -892,0 +906,0 @@ registerResolver(ctn, IInstruction, new InstanceProvider<IInstruction>(instructionProviderName, instruction)); |
@@ -1,2 +0,2 @@ | ||
import { mergeArrays, firstDefined, Key, resourceBaseName, getResourceKeyFor, isFunction, isString } from '@aurelia/kernel'; | ||
import { mergeArrays, firstDefined, Key, resourceBaseName, getResourceKeyFor, isFunction, isString, ILogger } from '@aurelia/kernel'; | ||
import { Bindable } from '../bindable'; | ||
@@ -119,2 +119,3 @@ import { Watch } from '../watch'; | ||
export class CustomAttributeDefinition<T extends Constructable = Constructable> implements ResourceDefinition<T, ICustomAttributeViewModel, PartialCustomAttributeDefinition> { | ||
public static warnDuplicate = true; | ||
// a simple marker to distinguish between Custom Element definition & Custom attribute definition | ||
@@ -179,5 +180,11 @@ public get type(): 'custom-attribute' { return dtAttribute; } | ||
); | ||
} /* istanbul ignore next */ else if (__DEV__) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`[DEV:aurelia] ${createMappedError(ErrorNames.attribute_existed, this.name)}`); | ||
} /* istanbul ignore next */ else { | ||
if (CustomAttributeDefinition.warnDuplicate) { | ||
container.get(ILogger).warn(createMappedError(ErrorNames.attribute_existed, this.name)); | ||
} | ||
/* istanbul ignore if */ | ||
if (__DEV__) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`[DEV:aurelia] ${createMappedError(ErrorNames.attribute_existed, this.name)}`); | ||
} | ||
} | ||
@@ -184,0 +191,0 @@ } |
@@ -371,3 +371,3 @@ import { isFunction, isPromise, type Constructable, IContainer, InstanceProvider, type MaybePromise, emptyArray, onResolve, resolve, transient } from '@aurelia/kernel'; | ||
const p = this._platform; | ||
registerHostNode(container, p, host); | ||
registerHostNode(container, host, p); | ||
registerResolver( | ||
@@ -374,0 +374,0 @@ container, |
@@ -179,2 +179,11 @@ import { Scope } from '../../binding/scope'; | ||
// | ||
// example: | ||
// <template as-custom-element="parent"> | ||
// <child> | ||
// <au-slot> #2 | ||
// </child> | ||
// ... | ||
// <template as-custom-element="child"> | ||
// <au-slot> #1 | ||
// | ||
// because of this structure, walk 2 level of controller at once to find the right parent scope for $host value | ||
@@ -181,0 +190,0 @@ while (parent.vmKind === 'synthetic' && parent.parent?.viewModel instanceof AuSlot) { |
@@ -8,5 +8,4 @@ import { emptyArray, isString, type IContainer, type IServiceLocator, Key, IIndexable } from '@aurelia/kernel'; | ||
import { type ICustomElementViewModel, type ICustomElementController } from './controller'; | ||
import { createMutationObserver, isElement } from '../utilities-dom'; | ||
import { createMutationObserver } from '../utilities-dom'; | ||
import type { INode } from '../dom'; | ||
import { ErrorNames, createMappedError } from '../errors'; | ||
@@ -16,9 +15,11 @@ import { getAnnotationKeyFor } from '../utilities-metadata'; | ||
export type PartialChildrenDefinition = { | ||
/** | ||
* An interface describing options to observe the children elements of a custom element host | ||
*/ | ||
export type PartialChildrenDefinition<TQuery extends string = string> = { | ||
query?: TQuery; | ||
callback?: PropertyKey; | ||
name?: PropertyKey; | ||
options?: MutationObserverInit; | ||
query?: (controller: ICustomElementController) => ArrayLike<Node>; | ||
filter?: (node: Node, controller?: ICustomElementController | null, viewModel?: ICustomElementViewModel) => boolean; | ||
map?: (node: Node, controller?: ICustomElementController | null, viewModel?: ICustomElementViewModel) => unknown; | ||
filter?: (node: TQuery extends '$all' ? Node : HTMLElement, viewModel: ICustomElementViewModel | null) => boolean; | ||
map?: (node: TQuery extends '$all' ? Node : HTMLElement, viewModel: ICustomElementViewModel | null) => unknown; | ||
}; | ||
@@ -31,9 +32,10 @@ | ||
*/ | ||
export function children<TThis,TValue>(config?: PartialChildrenDefinition): (target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>) => void; | ||
export function children<TThis, TValue, TQuery extends string>(config?: PartialChildrenDefinition<TQuery>): (target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>) => void; | ||
/** | ||
* Decorator: Specifies an array property on a class that synchronizes its items with child content nodes of the element. | ||
* | ||
* @param selector - The CSS element selector for filtering children | ||
* @param selector - The CSS element selector for filtering children. Use `$all` to select everything including non element nodes. | ||
* If nothing is provided, it defaults to `*`, which means all elements | ||
*/ | ||
export function children<TThis,TValue>(selector: string): (target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>) => void; | ||
export function children<TThis, TValue>(selector: string): (target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>) => void; | ||
/** | ||
@@ -45,6 +47,6 @@ * Decorator: Decorator: Specifies an array property that synchronizes its items with child content nodes of the element. | ||
*/ | ||
export function children<TThis,TValue>(target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>): void; | ||
export function children<TThis,TValue>(configOrTarget?: PartialChildrenDefinition | string | undefined, context?: ClassFieldDecoratorContext<TThis,TValue>): void | ((target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>) => void) { | ||
if (!mixed) { | ||
mixed = true; | ||
export function children<TThis, TValue>(target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>): void; | ||
export function children<TThis, TValue, TQuery extends string>(configOrTarget?: PartialChildrenDefinition<TQuery> | string | undefined, context?: ClassFieldDecoratorContext<TThis,TValue>): void | ((target: undefined, context: ClassFieldDecoratorContext<TThis,TValue>) => void) { | ||
if (!children.mixed) { | ||
children.mixed = true; | ||
subscriberCollection(ChildrenBinding, null!); | ||
@@ -64,3 +66,3 @@ lifecycleHooks()(ChildrenLifecycleHooks, null!); | ||
const dependencies = (context.metadata[dependenciesKey] ??= []) as Key[]; | ||
dependencies.push(new ChildrenLifecycleHooks(config as PartialChildrenDefinition & { name: PropertyKey })); | ||
dependencies.push(new ChildrenLifecycleHooks(config as PartialChildrenDefinition & { name: PropertyKey } ?? {})); | ||
} | ||
@@ -78,4 +80,5 @@ | ||
config = { | ||
filter: (node: Node) => isElement(node) && node.matches(configOrTarget), | ||
map: el => el | ||
query: configOrTarget, | ||
// filter: (node: Node) => isElement(node) && node.matches(configOrTarget), | ||
// map: el => el | ||
}; | ||
@@ -91,2 +94,3 @@ return decorator; | ||
} | ||
children.mixed = false; | ||
@@ -108,11 +112,7 @@ export interface ChildrenBinding extends ISubscriberCollection { } | ||
/** @internal */ | ||
private readonly _controller: ICustomElementController; | ||
private readonly _query: string; | ||
/** @internal */ | ||
private readonly _query = defaultChildQuery; | ||
private readonly _filter?: (node: Node, viewModel: ICustomElementViewModel | null) => boolean; | ||
/** @internal */ | ||
private readonly _filter = defaultChildFilter; | ||
/** @internal */ | ||
private readonly _map = defaultChildMap; | ||
/** @internal */ | ||
private readonly _options?: MutationObserverInit; | ||
private readonly _map?: (node: Node, viewModel: ICustomElementViewModel | null) => unknown; | ||
@@ -123,11 +123,9 @@ public isBound = false; | ||
public constructor( | ||
controller: ICustomElementController, | ||
host: HTMLElement, | ||
obj: ICustomElementViewModel, | ||
callback: undefined | (() => void), | ||
query = defaultChildQuery, | ||
filter = defaultChildFilter, | ||
map = defaultChildMap, | ||
options = childObserverOptions, | ||
query: string, | ||
filter?: (node: Node, viewModel: ICustomElementViewModel | null) => boolean, | ||
map?: (node: Node, viewModel: ICustomElementViewModel | null) => unknown, | ||
) { | ||
this._controller = controller; | ||
this.obj = obj; | ||
@@ -138,4 +136,3 @@ this._callback = callback; | ||
this._map = map; | ||
this._options = options; | ||
this._observer = createMutationObserver(this._host = controller.host, () => { | ||
this._observer = createMutationObserver(this._host = host, () => { | ||
this._onChildrenChanged(); | ||
@@ -156,3 +153,3 @@ }); | ||
this.isBound = true; | ||
this._observer.observe(this._host, this._options); | ||
this._observer.observe(this._host, { childList: true }); | ||
this._children = this._getNodes(); | ||
@@ -166,2 +163,4 @@ } | ||
this.isBound = false; | ||
// prevent memory leaks | ||
this._observer.takeRecords(); | ||
this._observer.disconnect(); | ||
@@ -187,48 +186,26 @@ this._children = emptyArray; | ||
private _getNodes() { | ||
return filterChildren(this._controller, this._query, this._filter, this._map); | ||
const query = this._query; | ||
const filter = this._filter; | ||
const map = this._map; | ||
const nodes = query === '$all' ? this._host.childNodes : this._host.querySelectorAll(`:scope > ${query}`); | ||
const ii = nodes.length; | ||
const results: unknown[] = []; | ||
const findControllerOptions = { optional: true }; | ||
let $controller: ICustomElementController | null; | ||
let viewModel: ICustomElementViewModel | null; | ||
let i = 0; | ||
let node: Node; | ||
while (ii > i) { | ||
node = nodes[i]; | ||
$controller = findElementControllerFor(node, findControllerOptions); | ||
viewModel = $controller?.viewModel ?? null; | ||
if (filter == null ? true : filter(node, viewModel)) { | ||
results.push(map == null ? viewModel ?? node : map(node, viewModel)); | ||
} | ||
++i; | ||
} | ||
return results; | ||
} | ||
} | ||
const childObserverOptions: MutationObserverInit = { childList: true }; | ||
const defaultChildQuery = (controller: ICustomElementController): ArrayLike<INode> => controller.host.childNodes; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const defaultChildFilter = (node: INode, controller?: ICustomElementController | null, viewModel?: any): boolean => | ||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions | ||
!!viewModel; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const defaultChildMap = (node: INode, controller?: ICustomElementController | null, viewModel?: any): any => viewModel; | ||
const forOpts = { optional: true } as const; | ||
const filterChildren = ( | ||
controller: ICustomElementController, | ||
query: typeof defaultChildQuery, | ||
filter: typeof defaultChildFilter, | ||
map: typeof defaultChildMap | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
): any[] => { | ||
const nodes = query(controller); | ||
const ii = nodes.length; | ||
const children: unknown[] = []; | ||
let node: INode; | ||
let $controller: ICustomElementController | null; | ||
let viewModel: ICustomElementViewModel | null; | ||
let i = 0; | ||
for (; i < ii; ++i) { | ||
node = nodes[i]; | ||
$controller = findElementControllerFor(node, forOpts); | ||
viewModel = $controller?.viewModel ?? null; | ||
if (filter(node, $controller, viewModel)) { | ||
children.push(map(node, $controller, viewModel)); | ||
} | ||
} | ||
return children; | ||
}; | ||
class ChildrenLifecycleHooks { | ||
@@ -245,11 +222,14 @@ public constructor( | ||
const $def = this._def; | ||
const query = $def.query ?? '*'; | ||
const childrenObserver = new ChildrenBinding( | ||
controller, | ||
controller.host, | ||
vm, | ||
vm[$def.callback ?? `${safeString($def.name)}Changed`] as () => void, | ||
$def.query ?? defaultChildQuery, | ||
$def.filter ?? defaultChildFilter, | ||
$def.map ?? defaultChildMap, | ||
$def.options ?? childObserverOptions, | ||
query, | ||
$def.filter as PartialChildrenDefinition<'$all'>['filter'], | ||
$def.map as PartialChildrenDefinition<'$all'>['map'], | ||
); | ||
if (/[\s>]/.test(query)) { | ||
throw createMappedError(ErrorNames.children_invalid_query, query); | ||
} | ||
def(vm, $def.name, { | ||
@@ -270,2 +250,19 @@ enumerable: true, | ||
let mixed = false; | ||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/ban-ts-comment */ | ||
function testChildrenDecorator() { | ||
class MyEl { | ||
@children({ | ||
filter: (element) => element.getAttribute('hey') == null, | ||
map: el => el.style | ||
}) | ||
@children({ | ||
map: node => node.style | ||
}) | ||
@children({ | ||
query: '$all', | ||
// @ts-expect-error | ||
map: node => node.style | ||
}) | ||
public nodes: unknown[] = []; | ||
} | ||
} |
@@ -144,2 +144,3 @@ import { emptyArray, type IContainer, type IIndexable, type IServiceLocator, type Key } from '@aurelia/kernel'; | ||
const $nodes: Node[] = []; | ||
const query = this._query; | ||
let $slot: IAuSlot; | ||
@@ -149,3 +150,3 @@ let node: Node; | ||
for (node of $slot === slot ? nodes : $slot.nodes) { | ||
if (this._query === '*' || (isElement(node) && node.matches(this._query))) { | ||
if (query === '$all' || (isElement(node) && (query === '*' || node.matches(query)))) { | ||
$nodes[$nodes.length] = node; | ||
@@ -152,0 +153,0 @@ } |
@@ -1,10 +0,9 @@ | ||
import { IContainer, noop, resolve } from '@aurelia/kernel'; | ||
import { IContainer, createLookup, noop, own, resolve, toArray } from '@aurelia/kernel'; | ||
import { AppTask } from '../app-task'; | ||
import { ICssModulesMapping, INode } from '../dom'; | ||
import { ClassAttributeAccessor } from '../observation/class-attribute-accessor'; | ||
import { ICssClassMapping } from '../dom'; | ||
import { IPlatform } from '../platform'; | ||
import { defineAttribute } from '../resources/custom-attribute'; | ||
import { createInterface, instanceRegistration } from '../utilities-di'; | ||
import type { IRegistry } from '@aurelia/kernel'; | ||
import { ITemplateCompilerHooks, TemplateCompilerHooks } from '@aurelia/template-compiler'; | ||
import { objectAssign } from '../utilities'; | ||
@@ -15,6 +14,6 @@ | ||
* | ||
* CSS registry alters the way class attribute works instead. | ||
* - CSS registry alters the way class bindings work via altering templates and register interfaces that will alter bindings to class attribute. | ||
* | ||
* Shadow dom registry regisiters some interfaces with the custom element container to handle shadow dom styles. | ||
* abtraction summary: | ||
* - Shadow dom registry regisiters some interfaces with the custom element container to handle shadow dom styles. | ||
* Shadow DOM abtraction summary: | ||
* CSS registry ---(register)---> IShadowDOMStyleFactory ---(createStyles)---> IShadowDOMStyles ---(applyTo)---> ShadowRoot | ||
@@ -37,31 +36,43 @@ */ | ||
public register(container: IContainer): void { | ||
// it'd be nice to be able to register a template compiler hook instead | ||
// so that it's lighter weight on the creation of a custom element with css module | ||
// also it'll be more consitent in terms as CSS class output | ||
// if custom attribute is used, the class controlled by custom attribute may come after | ||
// other bindings, regardless what their declaration order is in the template | ||
const classLookup = objectAssign({}, ...this.modules) as Record<string, string>; | ||
const ClassCustomAttribute = defineAttribute({ | ||
name: 'class', | ||
bindables: ['value'], | ||
noMultiBindings: true, | ||
}, class CustomAttributeClass { | ||
/** @internal */ | ||
private readonly _accessor = new ClassAttributeAccessor(resolve(INode) as HTMLElement); | ||
public value: string = ''; | ||
public binding() { | ||
this.valueChanged(); | ||
let existingMapping = container.get(own(ICssClassMapping)); | ||
if (existingMapping == null) { | ||
container.register( | ||
instanceRegistration(ICssClassMapping, existingMapping = createLookup()), | ||
); | ||
} | ||
/* istanbul ignore if */ | ||
if (__DEV__) { | ||
for (const mapping of this.modules) { | ||
for (const originalClass in mapping) { | ||
if (originalClass in existingMapping) { | ||
// eslint-disable-next-line no-console | ||
console.warn(`[DEV:aurelia] CSS class mapping for class "${originalClass}": "${mapping[originalClass]}" is overridden by "${existingMapping[originalClass]}"`); | ||
} | ||
existingMapping[originalClass] = mapping[originalClass]; | ||
} | ||
} | ||
} else { | ||
objectAssign(existingMapping, ...this.modules); | ||
} | ||
public valueChanged() { | ||
this._accessor.setValue(this.value?.split(/\s+/g).map(x => classLookup[x] || x) ?? ''); | ||
class CompilingHook implements ITemplateCompilerHooks { | ||
public compiling(template: HTMLElement): void { | ||
const isTemplate = template.tagName === 'TEMPLATE'; | ||
const container = isTemplate | ||
? (template as HTMLTemplateElement).content | ||
: template; | ||
const plainClasses = [template, ...toArray(container.querySelectorAll('[class]'))]; | ||
for (const element of plainClasses) { | ||
const classes = element.getAttributeNode('class')!; | ||
// we always include container, so there's a case where classes is null | ||
if (classes == null) { | ||
continue; | ||
} | ||
const newClasses = classes.value.split(/\s+/g).map(x => existingMapping![x] || x).join(' '); | ||
classes.value = newClasses; | ||
} | ||
} | ||
}); | ||
} | ||
container.register( | ||
ClassCustomAttribute, | ||
instanceRegistration(ICssModulesMapping, classLookup), | ||
); | ||
container.register(TemplateCompilerHooks.define(CompilingHook)); | ||
} | ||
@@ -68,0 +79,0 @@ } |
@@ -8,6 +8,6 @@ import { | ||
type IContainer, | ||
type IDisposableResolver, | ||
} from '@aurelia/kernel'; | ||
import { defineMetadata, getAnnotationKeyFor, getMetadata } from './utilities-metadata'; | ||
import { IResourceKind } from './resources/resources-shared'; | ||
import { IDisposableResolver } from '@aurelia/kernel/dist/types/di'; | ||
@@ -14,0 +14,0 @@ /** @internal */ |
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
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
4297753
233
78208
+ Added@aurelia/expression-parser@2.0.0-beta.19(transitive)
+ Added@aurelia/kernel@2.0.0-beta.19(transitive)
+ Added@aurelia/metadata@2.0.0-beta.19(transitive)
+ Added@aurelia/platform@2.0.0-beta.19(transitive)
+ Added@aurelia/platform-browser@2.0.0-beta.19(transitive)
+ Added@aurelia/runtime@2.0.0-beta.19(transitive)
+ Added@aurelia/template-compiler@2.0.0-beta.19(transitive)
- Removed@aurelia/expression-parser@2.0.0-beta.18(transitive)
- Removed@aurelia/kernel@2.0.0-beta.18(transitive)
- Removed@aurelia/metadata@2.0.0-beta.18(transitive)
- Removed@aurelia/platform@2.0.0-beta.18(transitive)
- Removed@aurelia/platform-browser@2.0.0-beta.18(transitive)
- Removed@aurelia/runtime@2.0.0-beta.18(transitive)
- Removed@aurelia/template-compiler@2.0.0-beta.18(transitive)