diff --git a/glass-easel/src/component.ts b/glass-easel/src/component.ts index 9c72acd..a95156d 100644 --- a/glass-easel/src/component.ts +++ b/glass-easel/src/component.ts @@ -159,7 +159,11 @@ export const resolvePlaceholder = ( } } if (ret) return ret - const comp = space.getGlobalUsingComponent(placeholder) ?? space.getDefaultComponent() + let comp = space.getGlobalUsingComponent(placeholder) + if (comp === null && space._$allowUnusedNativeNode && placeholder !== '') { + comp = placeholder + } + if (!comp) comp = space.getDefaultComponent() if (!comp) { throw new Error( `Cannot find default component for placeholder target "${placeholder}" (on component "${behavior.is}")`, @@ -467,7 +471,7 @@ export class Component< owner: ShadowRoot | null, backendContext: GeneralBackendContext | null, genericImpls: { [name: string]: ComponentDefinitionWithPlaceholder } | null, - placeholderHandler: (() => void) | undefined, + placeholderHandlerRemover: (() => void) | undefined, initPropValues?: (comp: ComponentInstance) => void, ): ComponentInstance { if (!def._$detail) def.prepare() @@ -487,7 +491,7 @@ export class Component< // initialize component instance object const comp = Object.create(proto) as ComponentInstance comp._$genericImpls = genericImpls - comp._$placeholderHandler = placeholderHandler + comp._$placeholderHandlerRemover = placeholderHandlerRemover comp._$external = external comp.tagName = tagName comp._$methodCaller = comp diff --git a/glass-easel/src/component_space.ts b/glass-easel/src/component_space.ts index 833796a..58871ad 100644 --- a/glass-easel/src/component_space.ts +++ b/glass-easel/src/component_space.ts @@ -153,6 +153,8 @@ export class ComponentSpace { _$componentWaitingListener: | ((isPub: boolean, alias: string, owner: GeneralComponent) => void) | null = null + /** @internal */ + _$allowUnusedNativeNode = true /** * Create a new component space @@ -168,6 +170,7 @@ export class ComponentSpace { defaultComponent?: string, baseSpace?: ComponentSpace, styleScopeManager?: StyleScopeManager, + allowUnusedNativeNode = true, ) { if (baseSpace) { Object.assign(this._$list, baseSpace._$pubList) @@ -176,6 +179,7 @@ export class ComponentSpace { this._$defaultComponent = defaultComponent ?? '' this._$componentOptions = normalizeComponentOptions({}, baseSpace?._$componentOptions) this.styleScopeManager = styleScopeManager || new StyleScopeManager() + this._$allowUnusedNativeNode = allowUnusedNativeNode } /** diff --git a/glass-easel/src/element.ts b/glass-easel/src/element.ts index 76fb010..e669d81 100644 --- a/glass-easel/src/element.ts +++ b/glass-easel/src/element.ts @@ -99,7 +99,7 @@ export class Element implements NodeCast { /** @internal */ _$inheritSlots: boolean /** @internal */ - _$placeholderHandler: (() => void) | undefined + _$placeholderHandlerRemover: (() => void) | undefined /** @internal */ _$virtual: boolean dataset: { [name: string]: unknown } @@ -152,7 +152,7 @@ export class Element implements NodeCast { this._$subtreeSlotStart = null this._$subtreeSlotEnd = null this._$inheritSlots = false - this._$placeholderHandler = undefined + this._$placeholderHandlerRemover = undefined this._$virtual = virtual this.dataset = {} this._$marks = null @@ -439,7 +439,7 @@ export class Element implements NodeCast { } node.childNodes.forEach(callFunc) if (node instanceof Component) { - const f = node._$placeholderHandler + const f = node._$placeholderHandlerRemover if (f) f() const shadowRoot = node.getShadowRoot() if (shadowRoot) callFunc(shadowRoot) diff --git a/glass-easel/src/native_node.ts b/glass-easel/src/native_node.ts index 9209058..dd7cec1 100644 --- a/glass-easel/src/native_node.ts +++ b/glass-easel/src/native_node.ts @@ -23,11 +23,11 @@ export class NativeNode extends Element { tagName: string, owner: ShadowRoot, stylingName?: string, - placeholderHandler?: () => void, + placeholderHandlerRemover?: () => void, ): NativeNode { const node = Object.create(NativeNode.prototype) as NativeNode node.is = tagName - node._$placeholderHandler = placeholderHandler + node._$placeholderHandlerRemover = placeholderHandlerRemover const nodeTreeContext = owner._$nodeTreeContext let backendElement: GeneralBackendElement | null if (BM.DOMLIKE || (BM.DYNAMIC && owner.getBackendMode() === BackendMode.Domlike)) { diff --git a/glass-easel/src/shadow_root.ts b/glass-easel/src/shadow_root.ts index 02a1926..40ea158 100644 --- a/glass-easel/src/shadow_root.ts +++ b/glass-easel/src/shadow_root.ts @@ -27,22 +27,6 @@ type AppliedSlotMeta = { updatePathTree: { [key: string]: true } | undefined } -const wrapPlaceholderCallback = ( - f: (c: GeneralComponentDefinition) => void, - cwp: Exclude, - owner: GeneralComponent, -) => { - const waiting = cwp.waiting - if (waiting) { - waiting.add(f) - waiting.hintUsed(owner) - return () => { - waiting.remove(f) - } - } - return undefined -} - export class ShadowRoot extends VirtualNode { private _$host: GeneralComponent /** @internal */ @@ -133,10 +117,10 @@ export class ShadowRoot extends VirtualNode { createNativeNodeWithInit( tagName: string, stylingName: string, - placeholderHandler: (() => void) | undefined, + placeholderHandlerRemover: (() => void) | undefined, initPropValues?: (comp: NativeNode) => void, ): NativeNode { - const ret = NativeNode.create(tagName, this, stylingName, placeholderHandler) + const ret = NativeNode.create(tagName, this, stylingName, placeholderHandlerRemover) initPropValues?.(ret) return ret } @@ -160,26 +144,39 @@ export class ShadowRoot extends VirtualNode { const space = beh.ownerSpace const compName = usingKey === undefined ? tagName : usingKey - // if the target is in using list, then use the one in using list - const using = beh._$using[compName] - if (typeof using === 'string') { - return this.createNativeNodeWithInit(using, tagName, undefined, initPropValues) - } - if (using) { + const possibleComponentDefinitions = [ + // if the target is in using list, then use the one in using list + beh._$using[compName], + // if the target is in generics list, then use the one + hostGenericImpls && hostGenericImpls[compName], + ] + + for (let i = 0; i < possibleComponentDefinitions.length; i += 1) { + const cwp = possibleComponentDefinitions[i] + if (cwp === null || cwp === undefined) continue + if (typeof cwp === 'string') { + return this.createNativeNodeWithInit(cwp, tagName, undefined, initPropValues) + } let usingTarget: GeneralComponentDefinition | string | undefined - if (using.final) { - usingTarget = using.final - } else if (using.placeholder !== null) { - usingTarget = resolvePlaceholder(using.placeholder, space, using.source, hostGenericImpls) + let placeholderHandlerRemover: (() => void) | undefined + if (cwp.final) { + usingTarget = cwp.final + } else if (cwp.placeholder !== null) { + usingTarget = resolvePlaceholder(cwp.placeholder, space, cwp.source, hostGenericImpls) + const waiting = cwp.waiting + if (placeholderCallback && waiting) { + waiting.add(placeholderCallback) + waiting.hintUsed(host) + placeholderHandlerRemover = () => { + waiting.remove(placeholderCallback) + } + } } - const placeholderHandler = placeholderCallback - ? wrapPlaceholderCallback(placeholderCallback, using, host) - : undefined if (typeof usingTarget === 'string') { return this.createNativeNodeWithInit( usingTarget, tagName, - placeholderHandler, + placeholderHandlerRemover, initPropValues, ) } @@ -190,45 +187,18 @@ export class ShadowRoot extends VirtualNode { this, null, convertGenerics(usingTarget, beh, host, genericTargets), - placeholderHandler, - initPropValues, - ) - } - } - - // if the target is in generics list, then use the one - const g = hostGenericImpls && hostGenericImpls[compName] - if (typeof g === 'string') { - return this.createNativeNodeWithInit(g, tagName, undefined, initPropValues) - } - if (g) { - let genImpl: GeneralComponentDefinition | string | undefined - if (g.final) { - genImpl = g.final - } else if (g.placeholder !== null) { - genImpl = resolvePlaceholder(g.placeholder, space, g.source, hostGenericImpls) - } - const placeholderHandler = placeholderCallback - ? wrapPlaceholderCallback(placeholderCallback, g, host) - : undefined - if (typeof genImpl === 'string') { - return this.createNativeNodeWithInit(genImpl, tagName, placeholderHandler, initPropValues) - } - if (genImpl) { - return Component._$advancedCreate( - tagName, - genImpl, - this, - null, - convertGenerics(genImpl, beh, host, genericTargets), - placeholderHandler, + placeholderHandlerRemover, initPropValues, ) } } // find in the space otherwise - const comp = space.getGlobalUsingComponent(compName) ?? space.getDefaultComponent() + let comp = space.getGlobalUsingComponent(compName) + if (comp === null && space._$allowUnusedNativeNode && compName !== '') { + comp = compName + } + if (!comp) comp = space.getDefaultComponent() /* istanbul ignore if */ if (!comp) { throw new Error(`Cannot find component "${compName}"`) @@ -280,10 +250,13 @@ export class ShadowRoot extends VirtualNode { ) } - // use native node otherwise - const node = NativeNode.create(tagName, this) - initPropValues?.(node) - return node + if (space._$allowUnusedNativeNode) { + // use native node otherwise + const node = NativeNode.create(tagName, this) + initPropValues?.(node) + return node + } + throw new Error(`Unknown tag name ${tagName}`) } /** diff --git a/glass-easel/src/tmpl/proc_gen_wrapper.ts b/glass-easel/src/tmpl/proc_gen_wrapper.ts index 4eef1ca..c05d51b 100644 --- a/glass-easel/src/tmpl/proc_gen_wrapper.ts +++ b/glass-easel/src/tmpl/proc_gen_wrapper.ts @@ -782,8 +782,6 @@ export class ProcGenWrapper { children: DefineChildren, dynamicSlotValueNames: string[] | undefined, ): Element { - const placeholding = this.shadowRoot.checkComponentPlaceholder(tagName) - let elem: Element let dynSlot = false const initPropValues = (elem: GeneralComponent | NativeNode) => { const sr = @@ -800,55 +798,43 @@ export class ProcGenWrapper { sr?.applySlotUpdates() } } - if (typeof placeholding === 'boolean') { - let placeholderCb: (() => void) | undefined - if (placeholding) { - placeholderCb = () => { - const replacer = this.shadowRoot.createComponent( - tagName, - tagName, - genericImpls, - undefined, - initPropValues, - ) - replacer.destroyBackendElementOnDetach() - const replacerShadowRoot = (replacer as GeneralComponent).getShadowRoot() - const elemShadowRoot = elem instanceof Component ? elem.getShadowRoot() : null - const isElemDynamicSlots = elemShadowRoot?.getSlotMode() === SlotMode.Dynamic - const isReplacerDynamicSlots = replacerShadowRoot?.getSlotMode() === SlotMode.Dynamic - if (isReplacerDynamicSlots) { - if (!isElemDynamicSlots) { - throw new Error( - 'The "dynamicSlots" option of the component and its placeholder should be the same.', - ) - } - elem.parentNode?.replaceChild(replacer, elem) - } else { - if (isElemDynamicSlots) { - throw new Error( - 'The "dynamicSlots" option of the component and its placeholder should be the same.', - ) - } - elem.selfReplaceWith(replacer) - } - } - } - elem = this.shadowRoot.createComponent( + const placeholderCallback = () => { + const replacer = this.shadowRoot.createComponent( tagName, tagName, genericImpls, - placeholderCb, + undefined, initPropValues, ) - elem.destroyBackendElementOnDetach() - } else { - elem = this.shadowRoot.createComponentOrNativeNode( - placeholding ?? tagName, - genericImpls, - initPropValues, - ) - elem.destroyBackendElementOnDetach() + replacer.destroyBackendElementOnDetach() + const replacerShadowRoot = (replacer as GeneralComponent).getShadowRoot() + const elemShadowRoot = elem instanceof Component ? elem.getShadowRoot() : null + const isElemDynamicSlots = elemShadowRoot?.getSlotMode() === SlotMode.Dynamic + const isReplacerDynamicSlots = replacerShadowRoot?.getSlotMode() === SlotMode.Dynamic + if (isReplacerDynamicSlots) { + if (!isElemDynamicSlots) { + throw new Error( + 'The "dynamicSlots" option of the component and its placeholder should be the same.', + ) + } + elem.parentNode?.replaceChild(replacer, elem) + } else { + if (isElemDynamicSlots) { + throw new Error( + 'The "dynamicSlots" option of the component and its placeholder should be the same.', + ) + } + elem.selfReplaceWith(replacer) + } } + const elem = this.shadowRoot.createComponent( + tagName, + tagName, + genericImpls, + placeholderCallback, + initPropValues, + ) + elem.destroyBackendElementOnDetach() if (dynSlot) { this.bindingMapDisabled = true // IDEA better binding map disable detection } else {