diff --git a/packages/client/src/behaviors/context.ts b/packages/client/src/behaviors/context.ts index 76c5825ede..9b5f320967 100644 --- a/packages/client/src/behaviors/context.ts +++ b/packages/client/src/behaviors/context.ts @@ -2,7 +2,7 @@ import { Binding, ObservableObject, Parser, Subscription, each } from "@akala/co import { IScope } from "../scope.js"; import { Composer } from "../template.js"; import { AttributeComposer } from "./shared.js"; -import { CallExpression, ConstantExpression, MemberExpression, NewExpression } from "@akala/core/expressions"; +import { ConstantExpression, MemberExpression, NewExpression } from "@akala/core/expressions"; // import { MemberExpression, NewExpression } from "@akala/core/expressions"; type Scope = IScope; @@ -11,24 +11,24 @@ export type IDataContext = Partial { - static propagateProperties: string[] = ['controller']; + static readonly propagateProperties: string[] = ['controller']; static define(item: HTMLElement, context: Record): void { - item.setAttribute('data-context', ''); - if (item['dataContext']) + if (!item['dataContext']) item['dataContext'] = DataContext.extend(DataContext.find(item), context) + item.setAttribute('data-context', ''); } static extend(sourceContext: Binding, options: Record, newContextPath?: string): Binding { - return sourceContext.pipe(new CallExpression(new ConstantExpression(Object), new ConstantExpression('assign'), [new NewExpression<{ context: any, controller: Partial }>( + return sourceContext.pipe(new NewExpression<{ context: any, controller: Partial }>( ...Object.entries(options).filter(e => e[0] !== 'context').map(e => new MemberExpression(new ConstantExpression(e[1]), new ConstantExpression(e[0]), false)), - ...DataContext.propagateProperties.map(e => + ...DataContext.propagateProperties.filter(p => !(p in options)).map(e => new MemberExpression(new MemberExpression(null, new ConstantExpression(e), false), new ConstantExpression(e), false)), new MemberExpression(Parser.parameterLess.parse(newContextPath || 'context') as any, new ConstantExpression('context'), false), - ), new MemberExpression(null, null, false)])); + )); } private static readonly dataContextExpression = Parser.parameterLess.parse('dataContext'); @@ -39,7 +39,7 @@ export class DataContext implements Composer optionGetter(options: object): { context: Scope; controller: Partial; } { - return { context: options['$rootScope'], controller: options['controller'] }; + return { context: options['$rootScope'], controller: options['controller'], ...options }; } apply(item: HTMLElement, options?: { context: Scope, controller: Partial }, root?: HTMLElement | ShadowRoot): Disposable @@ -118,10 +118,7 @@ export class DataBind> extends AttributeComposer( - new MemberExpression(new MemberExpression(undefined, new ConstantExpression('context'), false), new ConstantExpression('context'), false), - new MemberExpression(new ConstantExpression(options) as any, new ConstantExpression('controller'), false), - )); + return DataContext.find(item); } optionName = 'controller'; diff --git a/packages/client/src/behaviors/shared.ts b/packages/client/src/behaviors/shared.ts index c83502f4d4..f2007e06a1 100644 --- a/packages/client/src/behaviors/shared.ts +++ b/packages/client/src/behaviors/shared.ts @@ -19,11 +19,17 @@ export interface WebComponent attributeChangedCallback(name: string, oldValue: string, newValue: string): void; } -export function webComponent(tagName: string) + + +export function webComponent(tagName: string, options?: ElementDefinitionOptions) { return function >(target: (new (element: HTMLElement) => T) & { observedAttributes?: string[] }) { - customElements.define(tagName, class extends HTMLElement + let parent = HTMLElement; + if (options?.extends) + parent = window[Object.getPrototypeOf(document.createElement(options.extends)).constructor.name] as any; + + customElements.define(tagName, class extends parent { control: T; constructor() @@ -54,7 +60,7 @@ export function webComponent(tagName: string) static readonly observedAttributes = target.observedAttributes; - }); + }, options); } } export function wcObserve(name: string) diff --git a/packages/client/src/controlsv2/each.ts b/packages/client/src/controlsv2/each.ts index 1c5b43ebb3..101f671044 100644 --- a/packages/client/src/controlsv2/each.ts +++ b/packages/client/src/controlsv2/each.ts @@ -6,7 +6,7 @@ import { DataContext } from "../common.js"; export class Each extends Control { each: Binding; - template: Element; + template: Node; get indexPropertyName() { @@ -15,7 +15,7 @@ export class Each extends Control get valuePropertyName() { - return this.element.getAttribute('value-property-name') || 'item'; + return this.element.getAttribute('item-property-name') || 'item'; } private options: {}[] = [] @@ -26,12 +26,19 @@ export class Each extends Control if (this.element.childElementCount > 1) throw new Error('Each control can only have one child element'); this.template = this.element.firstElementChild; - this.template.setAttribute('data-context', ''); - this.element.removeChild(this.template); + if (this.template instanceof HTMLTemplateElement) + { + this.template = this.template.content; + if (Array.from(this.template.childNodes).filter(c => c instanceof HTMLElement).length > 1) + throw new Error('Each control can only have one child element'); + } + this.element.removeChild(this.element.firstElementChild); let observableArraySubscription: Subscription; this.each.onChanged(ev => { + if (ev.value === ev.oldValue) + return; observableArraySubscription?.(); const observableArray = Array.isArray(ev.value) ? new ObservableArray(ev.value) : ev.value; if (observableArray instanceof ObservableArray) @@ -83,9 +90,23 @@ export class Each extends Control // const options = { [this.indexPropertyName]: observableArray.length, [this.valuePropertyName]: arg.replacedItems[i] } this.options[arg.replacedItems[i].index][this.valuePropertyName] = arg.replacedItems[i].newItem; } + break; case "init": + for (let i = 0; i < arg.newItems.length; i++) + { + const item = (this.template.cloneNode(true) as HTMLElement).firstElementChild as HTMLElement; + const options = {}; + Binding.defineProperty(options, this.indexPropertyName, i); + Binding.defineProperty(options, this.valuePropertyName, arg.newItems[i]); + this.options.push(options); + this.element.appendChild(item); + DataContext.define(item, options); + this.teardown(Template.composeAll([item], this.element, options)); + } + break; } - })) + })); + observableArray.init(); } }, true) }